有限状态机之Rust语言实现浅学随笔(五)

内容分享3天前发布
0 0 0

继续我的学习笔记,不同的思路,不同的启发,抛砖引玉。

查表法:

通过数组、元组、迭代器等实现查表法, 下面的状态转换表,是一个数组,每个数组元素是一个三元素元组,分别定义了:当前状态、事件、下一个转态;以:当前状态和事件为key, 可以查询出对应的下一个状态;当然也可以定义为四元素元组,增加一个函数指针或闭包,用于处理对应状态下的事件;状态和事件类型采用了字符串类型别名,为的是简单明了,易于配置, 但效率不高, 列如可以替换成整数编码或enum枚举类型,这样看着紧凑、清晰、效率高一些!

// 状态和事件定义
type State = &'static str;
type Event = &'static str;

// 状态转移表(当前状态, 事件)→ 下一状态
const TRANSITIONS: &[(State, Event, State)] = &[
("Red", "Timer", "Green"),
("Green", "Timer", "Yellow"),
("Yellow", "Timer", "Red"),
];

struct TrafficLight {
    current: State,
}

impl TrafficLight {
    fn new() -> Self { Self { current: "Red" }
}

    fn handle_event(&mut self, event: Event) {
        //do action
        println!("handle_event: State:{}->Event:{}", self.current, event);
        
        // 查表获取下一状态
        self.current = TRANSITIONS
        .iter()
        .find(|(s, e, _)| *s == self.current && *e == event)
        .map(|(_, _, next)| *next)
        .unwrap_or(self.current); // 无匹配时保持当前状态
        
    }
}

fn main(){
    let mut sm = TrafficLight::new();
    
    sm.handle_event("Timer");
}

总结:这个实现适合状态和事件总数比较少的简单情况,其中通过数组遍历查找下一个状态的方法效率不高, 可以通过HashMap来改善,由于Rust HashMap 本身就支持多元素的元组作为key,无需手动实现什么Trait,但是前提是:元组中的每个元素都分别实现了Eq Trait和Hash Trait; 当然元组的长度是有限制的,小于等于12; 详细详细信息请查阅Rust官方文档:

https://doc.rust-lang.org/std/primitive.tuple.html

https://doc.rust-lang.org/std/collections/struct.HashMap.html

use std::collections::HashMap;

fn main(){
    let mut map:HashMap<(i32, &str,bool), &str> = HashMap::new();
    map.insert((1, "rust", true), "good language");
    map.insert((2, "python", true), "good language");

    println!("{:?}", map.get(&(1, "rust", true)));
}

事件驱动型

// 1. 定义事件类型
//此枚举默认为复制语义.
#[derive(Debug, PartialEq)]
enum PlayerEvent {
	Play,
	Pause,
	Stop,
}

// 2. 定义状态类型
//由于有String, 所以此枚举默认为移动语义.
#[derive(Debug, Clone, PartialEq)]
enum PlayerState {
	Stopped,
	Playing(String), // 关联数据:当前播放曲目
	Paused(String),  // 关联数据:暂停时的曲目
}

// 3. 状态机主体
struct PlayerStateMachine {
    current_state: PlayerState,
}

impl PlayerStateMachine {
    fn new() -> Self {
        Self {
        current_state: PlayerState::Stopped,
        }
    }
// 接收事件,返回新状态(也可直接修改内部状态)
    fn handle_event(&mut self, event: PlayerEvent) -> &PlayerState {
        self.current_state = match (&self.current_state, event) {
        	// 停止状态下,收到Play事件→转为状态Playing(默认曲目)
            (PlayerState::Stopped, PlayerEvent::Play) => PlayerState::Playing("默认曲目".to_string()),
        
        	// 播放状态下,收到Pause事件→转为状态Paused;收到Stop事件→转为状态Stopped
            (PlayerState::Playing(track), PlayerEvent::Pause) => PlayerState::Paused(track.clone()),
            (PlayerState::Playing(_), PlayerEvent::Stop) => PlayerState::Stopped,
        
        	// 暂停状态下,收到Play事件→转为状态Playing;收到Stop事件→转为状态Sopped
            (PlayerState::Paused(track), PlayerEvent::Play) => PlayerState::Playing(track.clone()),
            (PlayerState::Paused(_), PlayerEvent::Stop) => PlayerState::Stopped,
        
        	// 其他情况保持原状态
          //注意此处:由于&mut self, 这个可变引用, 所以state不允许被move出去,也不允许被共享引用,
          //由于Rust规则,读写互斥, 所以此处选择clone,避免编译错误。
            (state, _) => state.clone(),
        };
        &self.current_state
    }

}
// 测试
fn main() {
    let mut player = PlayerStateMachine::new();
    println!("当前状态: {:?}", player.current_state); // Stopped
    player.handle_event(PlayerEvent::Play);
    println!("Play事件: {:?}", player.current_state); // Playing("默认曲目")

    player.handle_event(PlayerEvent::Pause);
    println!("Pause事件: {:?}", player.current_state); // Paused("默认曲目")

    player.handle_event(PlayerEvent::Stop);
    println!("Stop事件: {:?}", player.current_state); // Stopped

}

其他实现方法也许还有许多种,列如通过宏定义来减少重复代码,异步状态机,泛型状态机等等,精力有限,点到为止,抛砖引玉。

代码编译环境:

Rust playground stable 2024 1.90

后记:

我是一个普通的c++老码农,Rust语言爱好者,如今已四十不惑,青丝白发生,人谈不上机智,然多年来敏而好学,不敢懈怠, 随处学随处记,笔耕不辍,坚信好脑瓜不如烂笔头!如今赋闲家中,翻腾出来这些粗鄙之文,分享出来,抛砖引玉!不过本人水平有限,许多时候也是不求甚解,匆促行文,故而文中难免存有谬误,望诸君海涵指正!

帮忙点个赞吧!鼓励我坚持写下去!谢谢啦!

© 版权声明

相关文章

暂无评论

none
暂无评论...