Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow Device to attach Serial/Link Cable #21

Open
greenfox1505 opened this issue Apr 19, 2024 · 7 comments
Open

Allow Device to attach Serial/Link Cable #21

greenfox1505 opened this issue Apr 19, 2024 · 7 comments

Comments

@greenfox1505
Copy link
Contributor

I'd like to be able to connect Serial/Link Cables.

I'm imagining something like syncing this similarly to how the audio system works (you impl SerialConnection for LinkCable {?)

I can imagine if you don't have both of the Device's do_cycle's in sync this can cause some problems, since there doesn't seem to be a "buffer" system in the system.

At minimum, I'd like to make the printer output data buffers instead of to files. Device->Device connections might be too hard to be worth the time.

@mvdnes
Copy link
Owner

mvdnes commented Apr 21, 2024

This can be done by hooking a SerialCallback to the serial module. Both sides transfer a byte at the same time.

In the current implementation, the transfer of data happens instantly. In a real gameboy, it would take several clock cycles. When implementing such a function, this might also be useful since you can keep the emulator running while waiting for the network.

@greenfox1505
Copy link
Contributor Author

I'm using rboy in Godot. My gameboys are pickup-able object and they update with they physics update. But they update 60 times per sec in order, not at the same time. I should re-implement that as simultaneous updates. The challenge will be multi threading that to be somewhat lockstep (so that no one Device gets too far ahead or behind any other Device it's connected to.

My object currently uses Device, and Device does not currently expose a pub read-to or write-to serial function. I suppose I could re-implement some of Device and give my struct a CPU instead.

@greenfox1505
Copy link
Contributor Author

mod cpu; is private. Device does not implement a way to interface with the Serial connection.

@greenfox1505
Copy link
Contributor Author

greenfox1505 commented Apr 23, 2024

I think this is right? I'm not great with lifetimes yet, this will work, but I'm not sure it's the best way to handle this. And I'm only 60% that read address is correct.

impl Device {
...
    pub fn attach_serial_callback(&mut self, a:SerialCallback<'static>) {
        self.cpu.mmu.serial.set_callback(a);
    }

    pub fn write_serial_data(&mut self,data:u8)  {
        self.cpu.mmu.serial.wb(0xFF01,data)
    }

Let me know if there is anything I should change or if I can PR that as-is.

@mvdnes
Copy link
Owner

mvdnes commented Apr 25, 2024

I do not think that the devices need to be in perfect sync. Normal gameboys probably would not be either. They do sync a clock for transferring data, but that is handled by the callback mechanism.

Using attach_serial_callback is a good idea. This does only work for callbacks with a 'static lifetime though. If that is enough then I am fine with this, otherwise we may need to rethink the Device::new function. With all the options currently a builder would maybe be a better idea.

I do not think write_serial_data should be exposed. In your current implementation, this will set the data to be transferred from the local Device, to the remote. I think you want it the other way around, which the callback is for.

@greenfox1505
Copy link
Contributor Author

You're right that they don't need to be "perfectly" in sync, but they should definitely be more in sync than I'm doing. I'm only updating them in order 60x per sec. Every single frame, they'll go one or two frame, 70k-140k cycles, out of sync before catching up. I think I need move my cycler to a separate thread that pushes all the Devices together.

The callback is reading from the Device, so the the other function should write to the Device, no? I want Device->Device communication, right? The callback doesn't include writeback or any kind of return, does it?

This is what I spun up for audio using mpsc::channel()s. I think 'static would be fine for serial callbacks.

struct GodotBoyAudio {
    pipe_in: Sender<Vec<(f32, f32)>>,
}
impl GodotBoyAudio {
    fn new(pipe_in: Sender<Vec<(f32, f32)>>) -> Self {
        Self { pipe_in }
    }
}

impl AudioPlayer for GodotBoyAudio {
    fn play(&mut self, left_channel: &[f32], right_channel: &[f32]) {
        let g: Vec<(f32, f32)> = left_channel
            .iter()
            .zip(right_channel.iter())
            .map(|(l, r)| (l.clone(), r.clone()))
            .collect();
        self.pipe_in.send(g).unwrap();
    }

    fn samples_rate(&self) -> u32 {
        44100
    }

    fn underflowed(&self) -> bool {
        true
    }
}

@mvdnes
Copy link
Owner

mvdnes commented Apr 27, 2024

The callback is a function is a function that has an u8 argument that returns an Option.
The value put in the argument is the value that is send from the Device to the outside. If the return value from the function is a Some(u8), then that u8 is the value that will be sent from the outside to the Device.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants