Rust状态机实现源码

22-10-25 banq

物品可以展示,可以下订单购买,提供购买详
细信息以验证订单,最后,物品被运送。发货前可以取消订单。下面示意性地描述了该状态机:


完整的源代码可以在这里找到

状态机接受的消息:

enum Message {
  PlaceOrder,
  Verify(Verify),
  FinalConfirmation,
  Cancel,
}
enum Verify {
 Address,
 PaymentDetails,
}


接下来,描述状态;为了示范性的目的,我选择了Placed状态,因为它需要一些输入才能被输入。

struct Placed {
  is_address_present: bool,
  is_payment_ok: bool,
  is_canceled: bool,
}
impl Placed {
  fn new() -> Self {
    Self {
      is_address_present: false,
      is_payment_ok: false,
      is_canceled: false,
    }
  }
}
impl State<Types> for Placed {
  fn deliver(
    &mut self,
    message: Message,
  ) -> DeliveryStatus<Message, <Types as StateTypes>::Err> {
    match message {
      Message::Cancel => self.is_canceled = true,
      Message::Verify(Verify::Address) => self.is_address_present = true,
      Message::Verify(Verify::PaymentDetails) => self.is_payment_ok = true,
      _ => return DeliveryStatus::Unexpected(message),
    }
    DeliveryStatus::Delivered
  }
  fn advance(&self) -> Result<Transition<Types>, <Types as StateTypes>::Err> {
    if self.is_canceled {
      return Ok(Transition::Next(Box::new(Canceled)));
    }
    Ok(if self.is_payment_ok && self.is_address_present {
        Transition::Next(Box::new(Verified::new()))
      } else {
        Transition::Same
      }
    )
  }
}


现在很清楚,deliver更新了相应的字段,而advance则根据其内部值动态地选择下一个状态。
所有其他的状态都有类似的描述,可以在测试代码中查找。

为了把所有的东西连接在一起,我们需要创建一个有时间限制的运行器,把它启动,然后给它一些消息,使状态机在各个状态中进展。观察一下。

// Initial state.
let on_display = OnDisplay::new();
// Fake a feed of un-ordered messages.
let mut feed = VecDeque::from(vec![
  Message::Verify(Verify::Address),
  Message::PlaceOrder,
  Message::FinalConfirmation,
  Message::Verify(Verify::PaymentDetails),
]);
let mut feeding_interval = time::interval(Duration::from_millis(100));
feeding_interval.tick().await;
let mut state_machine_runner = TimeBoundStateMachineRunner::new(
  “Order”.to_string(),
  Box::new(on_display),
  Duration::from_secs(5), // time budget
);
let (state_machine_tx, mut state_machine_rx) =    
  mpsc::unbounded_channel();                                     state_machine_runner.run(state_machine_tx);
                                                                   let res: TimeBoundStateMachineResult<Types> = loop {                                          
  select! {                                              
    Some(Either::Result{ result, ..} ) = 
      state_machine_rx.recv() => { break result; }                                               
    _ = feeding_interval.tick() => {                                                 
      // feed a message if present.                                                 
      if let Some(msg) = feed.pop_front() {                                                     
        let _ = state_machine_runner.deliver(msg);                                                      
      }
    }
  }
};
let order = res.unwrap_or_else(
  |_| panic!(“State machine did not complete in time”)
);
assert!(order.is::<Shipped>());


代码是不言自明的:我们从正在显示的项目开始,鉴于消息的顺序,我们希望有时间限制的状态机运行器在5秒内完成,终端状态为已发货。

这个例子完成了所提出的功能必须被使用的基本场景。