单线程下多结构体可变通信的请求响应系统方案咨询
单线程下NES模拟器多芯片结构体的可变通信方案
核心问题
TL;DR:如何在单线程环境下实现多结构体(对应NES各硬件芯片)间的可变请求响应式通信?
场景背景
开发NES模拟器时,用层级结构体对应硬件架构:
NES: - CPU6502 - SystemBus - PPU - Memory - CatridgeConnector - 其他芯片
需要模拟真实硬件通过导线通信并修改状态的逻辑,实现共享可变状态,但希望避免芯片结构体直接引用其他芯片类型,同时减少样板代码、控制性能开销。
现有方案的痛点
1. Rc<RefCell<T>>的问题
可行但存在明显缺陷:
- 引入大量样板代码,导致冗余
- 虽未实测,但单线程下的运行时检查可能带来性能损耗
- 芯片结构体需要持有其他芯片的引用,不符合真实硬件逻辑(芯片无需知晓连接对象)
2. 消息返回式的所有权借取
思路是CPU6502执行周期时返回内存访问消息,由NES顶层协调Memory操作,但存在问题:
- CPU单次周期可能多次访问内存,每次返回消息会导致逻辑碎片化,实现成本极高
- 与当前实现逻辑差异大,需要大量重构
3. 通道/事件系统的不足
尝试过双向通道,但适配性差:
- 单线程下缺乏自动请求处理机制,手动处理会增加复杂度
- 多线程方案(如tokio)的开销远大于内存读写这类微小操作,完全没必要
- 无法高效支持可变写操作
推荐解决方案
方案1:基于系统总线的回调/闭包机制
模拟真实硬件的总线通信逻辑,让所有芯片通过SystemBus交互,无需直接引用彼此:
- 定义总线请求类型,包含读写操作、地址、数据等信息:
enum BusRequest { Read(u16, Box<dyn FnOnce(u8)>), // 地址 + 读取完成后的回调 Write(u16, u8), // 地址 + 数据 }
SystemBus作为中央协调者,持有所有需要响应总线请求的组件(Memory、PPU、Cartridge等)的可变引用- CPU6502结构体持有
&mut SystemBus(或在执行周期时传入),发起请求时:- 读操作:传入地址和回调函数,总线处理后调用回调返回数据
- 写操作:直接传入地址和数据,总线分发到对应组件处理
- 总线的处理逻辑在单线程同步执行,所有操作即时完成,无额外线程开销
优势:
- 完全符合硬件总线的通信逻辑,架构清晰
- 芯片无需知道其他芯片类型,解耦彻底
- 单线程同步处理,性能开销极小
- 支持单次周期内多次内存访问,回调机制避免了频繁返回消息的问题
方案2:全局状态+访问 trait(谨慎使用)
定义一个全局的NES状态结构体,所有芯片通过实现特定trait来访问状态:
- 定义
HardwareComponenttrait,包含访问系统状态的方法:
trait HardwareComponent { fn tick(&mut self, state: &mut NESState); }
NESState包含所有硬件组件的可变状态(Memory、PPU等)- 顶层NES循环依次调用每个组件的
tick方法,并传入&mut NESState - 组件在
tick过程中直接通过状态访问其他硬件的资源
注意:
- 需严格控制状态访问的顺序,避免可变引用冲突
- 适合逻辑相对简单的场景,复杂场景下可能导致状态管理混乱
方案3:基于消息队列的同步调度
实现一个单线程消息队列,所有芯片的请求都放入队列,顶层循环统一处理:
- 定义消息类型,包含发送者标识、请求类型、数据、回调(如果需要返回)
- 每个芯片持有消息队列的发送端,发起请求时将消息入队
- 顶层NES循环在每个周期内先处理所有队列中的消息,再执行各芯片的逻辑
优势:
- 解耦芯片间的直接依赖
- 可以灵活控制请求处理顺序
- 单线程执行,无额外开销
适合场景:需要严格控制通信时序的硬件模拟场景
总结
优先推荐基于系统总线的回调/闭包机制,既符合真实硬件架构,又能高效实现单线程下的可变通信,同时避免了Rc<RefCell<T>>的样板代码和性能顾虑。如果场景简单,全局状态+trait的方案实现成本更低,但要注意状态访问的安全性。
内容的提问来源于stack exchange,提问作者tmvkrpxl0
相关产品推荐
相关产品推荐

