From 118e5af851b08a83d8d5d686a5e659f6a72ca29b Mon Sep 17 00:00:00 2001 From: Alex Mikhalev Date: Sat, 19 Jan 2019 23:42:27 -0800 Subject: [PATCH] command refactor --- src/command.rs | 489 --------------------------------- src/command/command.rs | 80 ++++++ src/command/command_group.rs | 82 ++++++ src/command/generic_command.rs | 157 +++++++++++ src/command/macros.rs | 67 +++++ src/command/mod.rs | 14 + src/command/scheduler.rs | 108 ++++++++ 7 files changed, 508 insertions(+), 489 deletions(-) delete mode 100644 src/command.rs create mode 100644 src/command/command.rs create mode 100644 src/command/command_group.rs create mode 100644 src/command/generic_command.rs create mode 100644 src/command/macros.rs create mode 100644 src/command/mod.rs create mode 100644 src/command/scheduler.rs diff --git a/src/command.rs b/src/command.rs deleted file mode 100644 index 8d928d8..0000000 --- a/src/command.rs +++ /dev/null @@ -1,489 +0,0 @@ -use std::cell::RefCell; -use std::rc::Rc; - -pub trait CommandId { - fn command_id(&self) -> u64; -} - -impl CommandId for u64 { - fn command_id(&self) -> u64 { - *self - } -} - -pub trait WithCommandInfo { - fn command_info(&self) -> &CommandInfo; -} - -pub trait GetCommandInfo: CommandId { - fn name(&self) -> &str; -} - -pub struct CommandInfo { - command_id: u64, - name: String, -} - -impl CommandInfo { - pub fn new>(command_id: u64, name: TName) -> Self { - Self { - command_id, - name: name.into(), - } - } -} - -impl CommandId for CommandInfo { - fn command_id(&self) -> u64 { - self.command_id - } -} - -impl GetCommandInfo for CommandInfo { - fn name(&self) -> &str { - &self.name - } -} - -impl CommandId for T { - fn command_id(&self) -> u64 { - self.command_info().command_id - } -} - -impl GetCommandInfo for T { - fn name(&self) -> &str { - &self.command_info().name - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum CommandResult { - Continue, - Done, - Cancel, -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum CommandStatus { - Stopped, - Running, - Ended, - Cancelling, - Cancelled, -} - -pub trait CommandParent { - fn start_command(&mut self, cmd: CommandRef) -> bool; -} - -pub struct CommandContext -where - T: CommandImp, -{ - info: CommandInfo, - status: CommandStatus, - parent: Option<*mut CommandParent>, - state: ::State, -} - -impl CommandContext { - pub fn status(&self) -> CommandStatus { - self.status - } - - pub fn state(&mut self) -> &mut T::State { - &mut self.state - } - - pub fn parent(&self) -> &mut CommandParent { - self.try_parent() - .expect("CommandContext parent called but no parent exists") - } - - pub fn try_parent<'a>(&'a self) -> Option<&'a mut CommandParent> { - self.parent - .map(|ptr| unsafe { &mut *ptr } as &'a mut CommandParent) - } -} - -impl WithCommandInfo for CommandContext { - fn command_info(&self) -> &CommandInfo { - &self.info - } -} - -pub trait CommandImp { - type State: Default; - - fn init(&self, ctx: &mut CommandContext) - where - Self: Sized; - fn step(&self, ctx: &mut CommandContext) -> CommandResult - where - Self: Sized; - fn finish(&self, ctx: &mut CommandContext) - where - Self: Sized; -} - -pub trait Command: WithCommandInfo { - fn reset(&mut self); - fn status(&self) -> CommandStatus; - fn execute(&mut self, &mut CommandParent) -> CommandStatus; - fn cancel(&mut self); -} - -pub struct GenericCommand { - context: CommandContext, - pub imp: T, -} - -impl GenericCommand { - pub fn new(info: CommandInfo, imp: T) -> Self { - Self { - context: CommandContext { - info, - status: CommandStatus::Stopped, - parent: None, - state: T::State::default(), - }, - imp, - } - } - - pub fn new_ref(info: CommandInfo, imp: T) -> CommandRef { - Rc::new(RefCell::new(Self::new(info, imp))) - } -} - -impl WithCommandInfo for GenericCommand { - fn command_info(&self) -> &CommandInfo { - &self.context.info - } -} - -impl Command for GenericCommand { - fn status(&self) -> CommandStatus { - self.context.status - } - - fn execute(&mut self, parent: &mut CommandParent) -> CommandStatus { - use self::CommandResult::*; - use self::CommandStatus::*; - let parent_ptr: *mut CommandParent = unsafe { std::mem::transmute(parent) }; - self.context.parent = Some(parent_ptr); - let do_cancel = |me: &mut Self| { - me.context.status = Cancelling; - me.imp.finish(&mut me.context); - Cancelled - }; - let do_step = |me: &mut Self| { - let result = me.imp.step(&mut me.context); - match result { - Continue => Running, - Done => { - me.imp.finish(&mut me.context); - Ended - } - Cancel => do_cancel(me), - } - }; - let new_status = match self.status() { - Stopped => { - self.imp.init(&mut self.context); - do_step(self) - } - Running => do_step(self), - Ended => Ended, - Cancelling => do_cancel(self), - Cancelled => Cancelled, - }; - self.context.status = new_status; - self.context.parent = None; - new_status - } - - fn reset(&mut self) { - use self::CommandStatus::*; - self.context.status = match self.context.status { - Stopped | Ended | Cancelled => Stopped, - other => { - warn!( - "GenericCommand({}) reset called in non-resetable status {:?}", - self.name(), - other - ); - self.imp.finish(&mut self.context); - Stopped - } - }; - } - - fn cancel(&mut self) { - use self::CommandStatus::*; - self.context.status = match self.context.status { - Running => Cancelling, - other => other, - }; - } -} - -macro_rules! command_impl { - ( - $command:ident<$tconfig:ty>; - $slf:ident => $init:block - ) => { - command_impl! { $command<$tconfig>; - $slf, ctx => $init } - }; - ( - $command:ident<$tconfig:ty>; - $slf:ident, $ctx:ident => $init:block - ) => { - command_impl! { $command<$tconfig,()>; - $slf, $ctx => $init; { $crate::command::CommandResult::Done }; {} } - }; - ( - $command:ident<$tconfig:ty,$tstate:ty>; - $slf:ident, $ctx:ident => $init:block; - $step:block; - $finish:block - ) => { - impl $crate::command::CommandImp for $tconfig { - type State = $tstate; - - fn init(&$slf, $ctx: &mut $crate::command::CommandContext) { - use $crate::command::GetCommandInfo; - debug!( - "{}({}) init", - stringify!($command), - $ctx.name() - ); - $init; - } - - fn step(&$slf, $ctx: &mut $crate::command::CommandContext) -> $crate::command::CommandResult { - use $crate::command::GetCommandInfo; - debug!( - "{}({}) step", - stringify!($command), - $ctx.name() - ); - $step - } - - fn finish(&$slf, $ctx: &mut $crate::command::CommandContext) { - use $crate::command::GetCommandInfo; - debug!( - "{}({}) finish({:?})", - stringify!($command), - $ctx.name(), - $ctx.status(), - ); - $finish; - } - } - pub type $command = $crate::command::GenericCommand<$tconfig>; - }; - ( - $command:ident<$tconfig:ty,$tstate:ty>; - $slf:ident => $init:block; - $step:block; - $finish:block - ) => { - command_impl! { $command<$tconfig,$tstate>; - $init_self, cts => $init; $step; $finish } - }; - -} - -pub type CommandRef = Rc>; - -pub struct CommandGroupConfig { - pub commands: Vec, -} - -impl CommandGroupConfig { - pub fn new(commands: Vec) -> Self { - Self { commands } - } -} - -pub struct CommandGroupState { - current_idx: usize, - has_cancelled: bool, -} - -impl Default for CommandGroupState { - fn default() -> Self { - CommandGroupState { - current_idx: 0, - has_cancelled: false, - } - } -} - -pub type CommandGroup = GenericCommand; - -impl CommandImp for CommandGroupConfig { - type State = CommandGroupState; - - fn init(&self, ctx: &mut CommandContext) { - *ctx.state() = Default::default(); - debug!("CommandGroup {} init", ctx.name()); - } - - fn step(&self, ctx: &mut CommandContext) -> CommandResult { - use self::CommandResult::*; - use self::CommandStatus::*; - let commands = &self.commands; - if ctx.state().has_cancelled { - debug!("CommandGroup step called after cancelled"); - return CommandResult::Cancel; - } - loop { - if ctx.state().current_idx >= commands.len() { - debug!("CommandGroup out of commands"); - return CommandResult::Done; - } - let mut command = commands[ctx.state().current_idx].borrow_mut(); - let status = command.execute(ctx.parent()); - match status { - Ended => { - command.reset(); - ctx.state().current_idx += 1; - } - Cancelled => { - command.reset(); - ctx.state().has_cancelled = true; - return Cancel; - } - Running => return Continue, - other => { - warn!("CommandGroup unhandled status {:?}", other); - return Continue; - } - } - } - } - - fn finish(&self, ctx: &mut CommandContext) { - let commands = &self.commands; - if ctx.status() == CommandStatus::Cancelling { - if let Some(command) = commands.get(ctx.state().current_idx) { - let mut command = command.borrow_mut(); - command.cancel(); - command.execute(ctx.parent()); - command.reset(); - } - } - } -} - -struct SchedulerCommandParent { - start_queue: Vec, -} - -impl SchedulerCommandParent { - fn new() -> Self { - Self { - start_queue: Vec::new(), - } - } -} - -impl CommandParent for SchedulerCommandParent { - fn start_command(&mut self, command: CommandRef) -> bool { - self.start_queue.push(command); - true - } -} - -pub struct Scheduler { - running_commands: Vec, -} - -impl Scheduler { - pub fn new() -> Self { - Self { - running_commands: Vec::new(), - } - } - - fn get_command(&self, id: T) -> Option<&CommandRef> { - let id = id.command_id(); - for command in &self.running_commands { - if command.borrow().command_info().command_id() == id { - return Some(command); - } - } - None - } - - fn get_command_mut(&mut self, id: T) -> Option<&mut CommandRef> { - let id = id.command_id(); - for command in &mut self.running_commands { - if command.borrow().command_info().command_id() == id { - return Some(command); - } - } - None - } - - pub fn start_command(&mut self, command: CommandRef) -> bool { - let command_id = command.borrow().command_info().command_id(); - if self.is_running(command_id) { - false - } else { - self.running_commands.push(command); - true - } - } - - pub fn is_running(&self, id: T) -> bool { - self.get_command(id).is_some() - } - - pub fn cancel_command(&mut self, id: T) -> bool { - self.get_command_mut(id).map_or(false, |command| { - command.borrow_mut().cancel(); - true - }) - } - - pub fn execute(&mut self) { - use self::CommandStatus::*; - let mut parent = SchedulerCommandParent::new(); - let exec_command = - |command: &mut Command, parent: &mut CommandParent| match command.execute(parent) { - Ended | Cancelled => { - command.reset(); - true - } - Stopped | Running | Cancelling => false, - }; - self.running_commands.drain_filter(|command| { - let mut command = command.borrow_mut(); - exec_command(&mut *command, &mut parent) - }); - while !parent.start_queue.is_empty() { - let mut new_parent = SchedulerCommandParent::new(); - { - let more_commands = parent.start_queue.into_iter().filter_map(|command_ref| { - let done = { - let mut command = command_ref.borrow_mut(); - exec_command(&mut *command, &mut new_parent) - }; - if !done { - Some(command_ref) - } else { - None - } - }); - self.running_commands.extend(more_commands); - } - parent = new_parent; - } - } -} diff --git a/src/command/command.rs b/src/command/command.rs new file mode 100644 index 0000000..18d2cef --- /dev/null +++ b/src/command/command.rs @@ -0,0 +1,80 @@ +use std::cell::RefCell; +use std::rc::Rc; + +pub trait CommandId { + fn command_id(&self) -> u64; +} + +impl CommandId for u64 { + fn command_id(&self) -> u64 { + *self + } +} + +pub trait WithCommandInfo { + fn command_info(&self) -> &CommandInfo; +} + +pub trait GetCommandInfo: CommandId { + fn name(&self) -> &str; +} + +pub struct CommandInfo { + command_id: u64, + name: String, +} + +impl CommandInfo { + pub fn new>(command_id: u64, name: TName) -> Self { + Self { + command_id, + name: name.into(), + } + } +} + +impl CommandId for CommandInfo { + fn command_id(&self) -> u64 { + self.command_id + } +} + +impl GetCommandInfo for CommandInfo { + fn name(&self) -> &str { + &self.name + } +} + +impl CommandId for T { + fn command_id(&self) -> u64 { + self.command_info().command_id + } +} + +impl GetCommandInfo for T { + fn name(&self) -> &str { + &self.command_info().name + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum CommandStatus { + Stopped, + Running, + Ended, + Cancelling, + Cancelled, +} + +pub trait CommandParent { + fn start_command(&mut self, cmd: CommandRef) -> bool; +} + +pub trait Command: WithCommandInfo { + fn reset(&mut self); + fn status(&self) -> CommandStatus; + fn execute(&mut self, &mut CommandParent) -> CommandStatus; + fn cancel(&mut self); +} + +pub type CommandRef = Rc>; diff --git a/src/command/command_group.rs b/src/command/command_group.rs new file mode 100644 index 0000000..c59a628 --- /dev/null +++ b/src/command/command_group.rs @@ -0,0 +1,82 @@ +use super::{CommandContext, CommandImp, CommandRef, CommandResult, GetCommandInfo, CommandStatus, GenericCommand}; + +pub struct CommandGroupConfig { + pub commands: Vec, +} + +impl CommandGroupConfig { + pub fn new(commands: Vec) -> Self { + Self { commands } + } +} + +pub struct CommandGroupState { + current_idx: usize, + has_cancelled: bool, +} + +impl Default for CommandGroupState { + fn default() -> Self { + CommandGroupState { + current_idx: 0, + has_cancelled: false, + } + } +} + +pub type CommandGroup = GenericCommand; + +impl CommandImp for CommandGroupConfig { + type State = CommandGroupState; + + fn init(&self, ctx: &mut CommandContext) { + *ctx.state() = Default::default(); + debug!("CommandGroup {} init", ctx.name()); + } + + fn step(&self, ctx: &mut CommandContext) -> CommandResult { + use self::CommandResult::*; + use self::CommandStatus::*; + let commands = &self.commands; + if ctx.state().has_cancelled { + debug!("CommandGroup step called after cancelled"); + return CommandResult::Cancel; + } + loop { + if ctx.state().current_idx >= commands.len() { + debug!("CommandGroup out of commands"); + return CommandResult::Done; + } + let mut command = commands[ctx.state().current_idx].borrow_mut(); + let status = command.execute(ctx.parent()); + match status { + Ended => { + command.reset(); + ctx.state().current_idx += 1; + } + Cancelled => { + command.reset(); + ctx.state().has_cancelled = true; + return Cancel; + } + Running => return Continue, + other => { + warn!("CommandGroup unhandled status {:?}", other); + return Continue; + } + } + } + } + + fn finish(&self, ctx: &mut CommandContext) { + let commands = &self.commands; + if ctx.status() == CommandStatus::Cancelling { + if let Some(command) = commands.get(ctx.state().current_idx) { + let mut command = command.borrow_mut(); + command.cancel(); + command.execute(ctx.parent()); + command.reset(); + } + } + } +} diff --git a/src/command/generic_command.rs b/src/command/generic_command.rs new file mode 100644 index 0000000..ebe8053 --- /dev/null +++ b/src/command/generic_command.rs @@ -0,0 +1,157 @@ +use super::{ + Command, CommandInfo, CommandParent, CommandRef, CommandStatus, GetCommandInfo, WithCommandInfo, +}; +use std::cell::RefCell; +use std::rc::Rc; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum CommandResult { + Continue, + Done, + Cancel, +} + +pub struct CommandContext +where + T: CommandImp, +{ + info: CommandInfo, + status: CommandStatus, + parent: Option<*mut CommandParent>, + state: ::State, +} + +impl CommandContext { + pub fn status(&self) -> CommandStatus { + self.status + } + + pub fn state(&mut self) -> &mut T::State { + &mut self.state + } + + pub fn parent(&self) -> &mut CommandParent { + self.try_parent() + .expect("CommandContext parent called but no parent exists") + } + + pub fn try_parent<'a>(&'a self) -> Option<&'a mut CommandParent> { + self.parent + .map(|ptr| unsafe { &mut *ptr } as &'a mut CommandParent) + } +} + +impl WithCommandInfo for CommandContext { + fn command_info(&self) -> &CommandInfo { + &self.info + } +} + +pub trait CommandImp { + type State: Default; + + fn init(&self, ctx: &mut CommandContext) + where + Self: Sized; + fn step(&self, ctx: &mut CommandContext) -> CommandResult + where + Self: Sized; + fn finish(&self, ctx: &mut CommandContext) + where + Self: Sized; +} + +pub struct GenericCommand { + context: CommandContext, + pub imp: T, +} + +impl GenericCommand { + pub fn new(info: CommandInfo, imp: T) -> Self { + Self { + context: CommandContext { + info, + status: CommandStatus::Stopped, + parent: None, + state: T::State::default(), + }, + imp, + } + } + + pub fn new_ref(info: CommandInfo, imp: T) -> CommandRef { + Rc::new(RefCell::new(Self::new(info, imp))) + } +} + +impl WithCommandInfo for GenericCommand { + fn command_info(&self) -> &CommandInfo { + &self.context.info + } +} + +impl Command for GenericCommand { + fn status(&self) -> CommandStatus { + self.context.status + } + + fn execute(&mut self, parent: &mut CommandParent) -> CommandStatus { + use self::CommandResult::*; + use self::CommandStatus::*; + let parent_ptr: *mut CommandParent = unsafe { std::mem::transmute(parent) }; + self.context.parent = Some(parent_ptr); + let do_cancel = |me: &mut Self| { + me.context.status = Cancelling; + me.imp.finish(&mut me.context); + Cancelled + }; + let do_step = |me: &mut Self| { + let result = me.imp.step(&mut me.context); + match result { + Continue => Running, + Done => { + me.imp.finish(&mut me.context); + Ended + } + Cancel => do_cancel(me), + } + }; + let new_status = match self.status() { + Stopped => { + self.imp.init(&mut self.context); + do_step(self) + } + Running => do_step(self), + Ended => Ended, + Cancelling => do_cancel(self), + Cancelled => Cancelled, + }; + self.context.status = new_status; + self.context.parent = None; + new_status + } + + fn reset(&mut self) { + use self::CommandStatus::*; + self.context.status = match self.context.status { + Stopped | Ended | Cancelled => Stopped, + other => { + warn!( + "GenericCommand({}) reset called in non-resetable status {:?}", + self.name(), + other + ); + self.imp.finish(&mut self.context); + Stopped + } + }; + } + + fn cancel(&mut self) { + use self::CommandStatus::*; + self.context.status = match self.context.status { + Running => Cancelling, + other => other, + }; + } +} diff --git a/src/command/macros.rs b/src/command/macros.rs new file mode 100644 index 0000000..fbdf4e5 --- /dev/null +++ b/src/command/macros.rs @@ -0,0 +1,67 @@ +macro_rules! command_impl { + ( + $command:ident<$tconfig:ty>; + $slf:ident => $init:block + ) => { + command_impl! { $command<$tconfig>; + $slf, ctx => $init } + }; + ( + $command:ident<$tconfig:ty>; + $slf:ident, $ctx:ident => $init:block + ) => { + command_impl! { $command<$tconfig,()>; + $slf, $ctx => $init; { $crate::command::CommandResult::Done }; {} } + }; + ( + $command:ident<$tconfig:ty,$tstate:ty>; + $slf:ident, $ctx:ident => $init:block; + $step:block; + $finish:block + ) => { + impl $crate::command::CommandImp for $tconfig { + type State = $tstate; + + fn init(&$slf, $ctx: &mut $crate::command::CommandContext) { + use $crate::command::GetCommandInfo; + debug!( + "{}({}) init", + stringify!($command), + $ctx.name() + ); + $init; + } + + fn step(&$slf, $ctx: &mut $crate::command::CommandContext) -> $crate::command::CommandResult { + use $crate::command::GetCommandInfo; + debug!( + "{}({}) step", + stringify!($command), + $ctx.name() + ); + $step + } + + fn finish(&$slf, $ctx: &mut $crate::command::CommandContext) { + use $crate::command::GetCommandInfo; + debug!( + "{}({}) finish({:?})", + stringify!($command), + $ctx.name(), + $ctx.status(), + ); + $finish; + } + } + pub type $command = $crate::command::GenericCommand<$tconfig>; + }; + ( + $command:ident<$tconfig:ty,$tstate:ty>; + $slf:ident => $init:block; + $step:block; + $finish:block + ) => { + command_impl! { $command<$tconfig,$tstate>; + $init_self, cts => $init; $step; $finish } + }; +} diff --git a/src/command/mod.rs b/src/command/mod.rs new file mode 100644 index 0000000..16a2491 --- /dev/null +++ b/src/command/mod.rs @@ -0,0 +1,14 @@ +#[macro_use] +mod macros; +pub mod command; +pub mod command_group; +pub mod generic_command; +pub mod scheduler; + +pub use self::command::{ + Command, CommandId, CommandInfo, CommandParent, CommandRef, CommandStatus, GetCommandInfo, + WithCommandInfo, +}; +pub use self::command_group::{CommandGroup, CommandGroupConfig}; +pub use self::generic_command::{CommandContext, CommandImp, CommandResult, GenericCommand}; +pub use self::scheduler::Scheduler; diff --git a/src/command/scheduler.rs b/src/command/scheduler.rs new file mode 100644 index 0000000..6c34644 --- /dev/null +++ b/src/command/scheduler.rs @@ -0,0 +1,108 @@ +use super::{Command, CommandId, CommandParent, CommandRef, CommandStatus}; + +struct SchedulerCommandParent { + start_queue: Vec, +} + +impl SchedulerCommandParent { + fn new() -> Self { + Self { + start_queue: Vec::new(), + } + } +} + +impl CommandParent for SchedulerCommandParent { + fn start_command(&mut self, command: CommandRef) -> bool { + self.start_queue.push(command); + true + } +} + +pub struct Scheduler { + running_commands: Vec, +} + +impl Scheduler { + pub fn new() -> Self { + Self { + running_commands: Vec::new(), + } + } + + fn get_command(&self, id: T) -> Option<&CommandRef> { + let id = id.command_id(); + for command in &self.running_commands { + if command.borrow().command_info().command_id() == id { + return Some(command); + } + } + None + } + + fn get_command_mut(&mut self, id: T) -> Option<&mut CommandRef> { + let id = id.command_id(); + for command in &mut self.running_commands { + if command.borrow().command_info().command_id() == id { + return Some(command); + } + } + None + } + + pub fn start_command(&mut self, command: CommandRef) -> bool { + let command_id = command.borrow().command_info().command_id(); + if self.is_running(command_id) { + false + } else { + self.running_commands.push(command); + true + } + } + + pub fn is_running(&self, id: T) -> bool { + self.get_command(id).is_some() + } + + pub fn cancel_command(&mut self, id: T) -> bool { + self.get_command_mut(id).map_or(false, |command| { + command.borrow_mut().cancel(); + true + }) + } + + pub fn execute(&mut self) { + use self::CommandStatus::*; + let mut parent = SchedulerCommandParent::new(); + let exec_command = + |command: &mut Command, parent: &mut CommandParent| match command.execute(parent) { + Ended | Cancelled => { + command.reset(); + true + } + Stopped | Running | Cancelling => false, + }; + self.running_commands.drain_filter(|command| { + let mut command = command.borrow_mut(); + exec_command(&mut *command, &mut parent) + }); + while !parent.start_queue.is_empty() { + let mut new_parent = SchedulerCommandParent::new(); + { + let more_commands = parent.start_queue.into_iter().filter_map(|command_ref| { + let done = { + let mut command = command_ref.borrow_mut(); + exec_command(&mut *command, &mut new_parent) + }; + if !done { + Some(command_ref) + } else { + None + } + }); + self.running_commands.extend(more_commands); + } + parent = new_parent; + } + } +}