command refactor
This commit is contained in:
parent
aafd678977
commit
118e5af851
489
src/command.rs
489
src/command.rs
@ -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
Normal file
80
src/command/command.rs
Normal file
@ -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
Normal file
82
src/command/command_group.rs
Normal file
@ -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
Normal file
157
src/command/generic_command.rs
Normal file
@ -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
Normal file
67
src/command/macros.rs
Normal file
@ -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
Normal file
14
src/command/mod.rs
Normal file
@ -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
Normal file
108
src/command/scheduler.rs
Normal file
@ -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…
x
Reference in New Issue
Block a user