如何制作一个仅用一个线程即可改变状态的事件处理程序?

问题描述 投票:0回答:1

TL;DR:如何创建一个请求-响应系统,该系统具有多个结构,仅用一个线程以可变方式相互通信?

我正在为业余爱好项目开发 NES 模拟器。我想在项目的层次结构和类型中表示硬件结构。所以我有代表每个芯片的结构。

在真实的硬件中,它们将通过电线进行通信并对信号做出反应,从而改变它们自己的状态。然而,这意味着共享可变状态。

目前的struct层次结构是这样的。

NES
拥有以下所有内容:

NES:
- CPU6502
- SystemBus
- PPU
- Memory
- CatridgeConnector
- some other chips
Rc怎么样

使用

Rc<RefCell<T>>
共享可变引用是第一种方法,它确实有效。但它引入了如此多的样板代码,我认为这是瓶颈的一部分(尽管只是预感)。这并不能很好地反映硬件结构。每个芯片不应该关心它连接到什么。我想避免在结构字段中提及其他芯片的类型。

来回借用和归还所有权怎么样?

所以想法是这样的:例如,当

CPU6502
想要与
Memory
通信时,它会返回消息,表示它想要访问特定位置的内存。然而,这需要大量的重构。 在当前层次结构中,
NES
将调用
CPU6502
中的函数来执行循环。假设它返回消息给
NES
,那么
NES
就会命令
Memory
做它的事情,依此类推。

但是

CPU6502
可以在单个函数调用中多次从内存中读取。每次需要与其他芯片通信时都返回
NES
是不可能的。这种行为在真实硬件中并不是 100% 准确,但现在就是这样。

频道?事件(或消息)系统?

我当前尝试的方法是使用bi Direction_channel。如果

CPU6502
Memory
发送请求,它应该以
u8
进行响应。但是我只有一个线程,没有任何东西可以处理请求。我可以使用多个线程,但这似乎有点矫枉过正。它只是从字节缓冲区读取特定索引处的单个值。处理多个线程,即使它像 tokio 一样轻量级,与仅执行此操作的小型操作相比也会更昂贵:
self.buffer[address - start]

那只是阅读。

CPU6502
也需要写入
Memory
,因此可变性也是必要的。

以上只是示例。每个芯片之间的所有通信都需要非常小的操作,例如设置单个u16,对u8进行按位操作。所以我认为多线程不是一个好主意。

rust ownership
1个回答
0
投票

您可以通过回调拥有类似通道的功能:通过类型擦除,您可以避免让每个组件了解其他组件,并且通过将组件访问集中在

Nes
下,您可以避免
Rc
RefCell
更难摆脱。

use std::cell::RefCell;

pub struct Requester<Params, Ret> {
    responder: fn(&Nes, Params) -> Ret,
}

impl<Params, Ret> Requester<Params, Ret> {
    pub fn new(responder: fn(&Nes, Params) -> Ret) -> Self {
        Self { responder }
    }

    pub fn request(&self, nes: &Nes, params: Params) -> Ret {
        (self.responder)(nes, params)
    }
}

pub struct Address(pub u8);

pub struct Cpu6502 {
    memory_requester: Requester<Address, u8>,
}

impl Cpu6502 {
    pub fn new(memory_requester: Requester<Address, u8>) -> Self {
        Self { memory_requester }
    }

    pub fn execute_cycle(&mut self, nes: &Nes) {
        let data = self.memory_requester.request(nes, Address(0));
    }
}

pub struct Memory {}

impl Memory {
    pub fn new() -> Self {
        Self {}
    }

    pub fn request_address(&self, nes: &Nes, addr: Address) -> u8 {
        unimplemented!()
    }
}

pub struct Nes {
    cpu6502: RefCell<Cpu6502>,
    memory: RefCell<Memory>,
}

impl Nes {
    pub fn new() -> Self {
        let cpu6502 = RefCell::new(Cpu6502::new(Requester::new(|nes, addr| {
            nes.memory.borrow().request_address(nes, addr)
        })));
        let memory = RefCell::new(Memory::new());
        Self { cpu6502, memory }
    }

    pub fn execute_cycle(&self) {
        self.cpu6502.borrow_mut().execute_cycle(self);
    }
}

如果组件仅需要对其所有操作共享引用,则可以避免将其包装在

RefCell
中。

© www.soinside.com 2019 - 2024. All rights reserved.