robotrs/src/command/generic_command.rs
2019-01-19 23:42:27 -08:00

158 lines
4.1 KiB
Rust

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,
};
}
}