Browse Source

command refactor

master
Alex Mikhalev 6 years ago
parent
commit
118e5af851
  1. 489
      src/command.rs
  2. 80
      src/command/command.rs
  3. 82
      src/command/command_group.rs
  4. 157
      src/command/generic_command.rs
  5. 67
      src/command/macros.rs
  6. 14
      src/command/mod.rs
  7. 108
      src/command/scheduler.rs

489
src/command.rs

@ -1,489 +0,0 @@ @@ -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<TName: Into<String>>(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<T: WithCommandInfo> CommandId for T {
fn command_id(&self) -> u64 {
self.command_info().command_id
}
}
impl<T: WithCommandInfo> 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<T>
where
T: CommandImp,
{
info: CommandInfo,
status: CommandStatus,
parent: Option<*mut CommandParent>,
state: <T as CommandImp>::State,
}
impl<T: CommandImp> CommandContext<T> {
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<T: CommandImp> WithCommandInfo for CommandContext<T> {
fn command_info(&self) -> &CommandInfo {
&self.info
}
}
pub trait CommandImp {
type State: Default;
fn init(&self, ctx: &mut CommandContext<Self>)
where
Self: Sized;
fn step(&self, ctx: &mut CommandContext<Self>) -> CommandResult
where
Self: Sized;
fn finish(&self, ctx: &mut CommandContext<Self>)
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<T: CommandImp> {
context: CommandContext<T>,
pub imp: T,
}
impl<T: CommandImp + 'static> GenericCommand<T> {
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<Self> {
Rc::new(RefCell::new(Self::new(info, imp)))
}
}
impl<T: CommandImp> WithCommandInfo for GenericCommand<T> {
fn command_info(&self) -> &CommandInfo {
&self.context.info
}
}
impl<T: CommandImp> Command for GenericCommand<T> {
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<Self>) {
use $crate::command::GetCommandInfo;
debug!(
"{}({}) init",
stringify!($command),
$ctx.name()
);
$init;
}
fn step(&$slf, $ctx: &mut $crate::command::CommandContext<Self>) -> $crate::command::CommandResult {
use $crate::command::GetCommandInfo;
debug!(
"{}({}) step",
stringify!($command),
$ctx.name()
);
$step
}
fn finish(&$slf, $ctx: &mut $crate::command::CommandContext<Self>) {
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<T = dyn Command> = Rc<RefCell<T>>;
pub struct CommandGroupConfig {
pub commands: Vec<CommandRef>,
}
impl CommandGroupConfig {
pub fn new(commands: Vec<CommandRef>) -> 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<CommandGroupConfig>;
impl CommandImp for CommandGroupConfig {
type State = CommandGroupState;
fn init(&self, ctx: &mut CommandContext<Self>) {
*ctx.state() = Default::default();
debug!("CommandGroup {} init", ctx.name());
}
fn step(&self, ctx: &mut CommandContext<Self>) -> 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<Self>) {
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<CommandRef>,
}
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<CommandRef>,
}
impl Scheduler {
pub fn new() -> Self {
Self {
running_commands: Vec::new(),
}
}
fn get_command<T: CommandId>(&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<T: CommandId>(&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<T: CommandId>(&self, id: T) -> bool {
self.get_command(id).is_some()
}
pub fn cancel_command<T: CommandId>(&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;
}
}
}

80
src/command/command.rs

@ -0,0 +1,80 @@ @@ -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<TName: Into<String>>(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<T: WithCommandInfo> CommandId for T {
fn command_id(&self) -> u64 {
self.command_info().command_id
}
}
impl<T: WithCommandInfo> 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<T = dyn Command> = Rc<RefCell<T>>;

82
src/command/command_group.rs

@ -0,0 +1,82 @@ @@ -0,0 +1,82 @@
use super::{CommandContext, CommandImp, CommandRef, CommandResult, GetCommandInfo, CommandStatus, GenericCommand};
pub struct CommandGroupConfig {
pub commands: Vec<CommandRef>,
}
impl CommandGroupConfig {
pub fn new(commands: Vec<CommandRef>) -> 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<CommandGroupConfig>;
impl CommandImp for CommandGroupConfig {
type State = CommandGroupState;
fn init(&self, ctx: &mut CommandContext<Self>) {
*ctx.state() = Default::default();
debug!("CommandGroup {} init", ctx.name());
}
fn step(&self, ctx: &mut CommandContext<Self>) -> 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<Self>) {
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();
}
}
}
}

157
src/command/generic_command.rs

@ -0,0 +1,157 @@ @@ -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<T>
where
T: CommandImp,
{
info: CommandInfo,
status: CommandStatus,
parent: Option<*mut CommandParent>,
state: <T as CommandImp>::State,
}
impl<T: CommandImp> CommandContext<T> {
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<T: CommandImp> WithCommandInfo for CommandContext<T> {
fn command_info(&self) -> &CommandInfo {
&self.info
}
}
pub trait CommandImp {
type State: Default;
fn init(&self, ctx: &mut CommandContext<Self>)
where
Self: Sized;
fn step(&self, ctx: &mut CommandContext<Self>) -> CommandResult
where
Self: Sized;
fn finish(&self, ctx: &mut CommandContext<Self>)
where
Self: Sized;
}
pub struct GenericCommand<T: CommandImp> {
context: CommandContext<T>,
pub imp: T,
}
impl<T: CommandImp + 'static> GenericCommand<T> {
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<Self> {
Rc::new(RefCell::new(Self::new(info, imp)))
}
}
impl<T: CommandImp> WithCommandInfo for GenericCommand<T> {
fn command_info(&self) -> &CommandInfo {
&self.context.info
}
}
impl<T: CommandImp> Command for GenericCommand<T> {
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,
};
}
}

67
src/command/macros.rs

@ -0,0 +1,67 @@ @@ -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<Self>) {
use $crate::command::GetCommandInfo;
debug!(
"{}({}) init",
stringify!($command),
$ctx.name()
);
$init;
}
fn step(&$slf, $ctx: &mut $crate::command::CommandContext<Self>) -> $crate::command::CommandResult {
use $crate::command::GetCommandInfo;
debug!(
"{}({}) step",
stringify!($command),
$ctx.name()
);
$step
}
fn finish(&$slf, $ctx: &mut $crate::command::CommandContext<Self>) {
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 }
};
}

14
src/command/mod.rs

@ -0,0 +1,14 @@ @@ -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;

108
src/command/scheduler.rs

@ -0,0 +1,108 @@ @@ -0,0 +1,108 @@
use super::{Command, CommandId, CommandParent, CommandRef, CommandStatus};
struct SchedulerCommandParent {
start_queue: Vec<CommandRef>,
}
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<CommandRef>,
}
impl Scheduler {
pub fn new() -> Self {
Self {
running_commands: Vec::new(),
}
}
fn get_command<T: CommandId>(&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<T: CommandId>(&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<T: CommandId>(&self, id: T) -> bool {
self.get_command(id).is_some()
}
pub fn cancel_command<T: CommandId>(&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;
}
}
}
Loading…
Cancel
Save