mre / mos6502

MOS 6502 emulator written in Rust
BSD 3-Clause "New" or "Revised" License
77 stars 15 forks source link

Add function to Variant for constructing CPU #100

Closed krazy-j closed 1 month ago

krazy-j commented 1 month ago

I noticed the needless_pass_by_value on the CPU::new method, and I wanted to suggest adding a method to Variant.

pub trait Variant {
    fn new_cpu<M: crate::memory::Bus>(memory: M) -> crate::cpu::CPU<M, Self>
    where
        Self: Sized
    {
        // The `variant` field is currently private.
        // It could be made `pub(crate)`, or a modified version
        // of `CPU::new` without the variant argument could be added.
        crate::cpu::CPU {
            registers: crate::registers::Registers::new(),
            memory,
            variant: core::marker::PhantomData::<Self>,
        }
    }
}
// using CPU
CPU::new(Memory::new(), Nmos6502);
// using Variant
Nmos6502::new_cpu(Memory::new());

This would allow constructing CPU without the need to use a type parameter or pass the variant as a value. I could create a pull request if that would be helpful.

mre commented 1 month ago

Thanks for raising the issue. I can see where you're coming from, and avoiding the type parameter is definitely compelling. That said, I'm still on the fence on this, because logically the CPU is the main entrypoint into the emulator and I like that it's always the same "interface" and you just have to specify the variant as a parameter (Nmos6502 in this case). I also like that the Variant trait only has a single method at the moment. The less methods Rust traits have, the more powerful the abstraction.

Perhaps we could add some helper methods like CPU::new_nmos(Memory::new()) or CPU::nmos(Memory::new()) as a compromise? I'm a little worried that the number of constructors might go out of hand, though.

@omarandlorraine, any thoughts?

omarandlorraine commented 1 month ago

I also like that the Variant trait only has a single method at the moment. The less methods Rust traits have, the more powerful the abstraction.

This is a good point, but it depends on if we can envisage:

this may be necessary for disassemblers or something in the future

omarandlorraine commented 1 month ago

What about a module containing functions for instantiating the most common combinations?

mod new_cpus {
    fn typical_nmos<M: crate::memory::Bus>(memory: M) -> crate::cpu::CPU<M, Nmos> {
        ...
    }

    fn nmos_thing_with_commodore64_bankswitching<M: crate::memory::FancyBus>(memory: M) -> crate::cpu::CPU<M, Nmos> {
        ...
    }

    fn sally<M: crate::memory::AtariBus>(memory: M) -> crate::cpu::CPU<M, Nmos> {
        ...
    }

    fn typical_cmos<M: crate::memory::Bus>(memory: M) -> crate::cpu::CPU<M, Cmos> {
        ...
    }
}

just an idea

mre commented 1 month ago

The longer I think about it, the more I'm of the opinion that the original syntax that we support right now is fine. I mean, except for the needless_pass_by_value I don't see any big issues. We can revisit that in the future if the number of variants explodes or once we know more about how we want to alter the trait. Sounds acceptable? 😅

krazy-j commented 1 month ago

That's entirely fair. It was just a suggestion, after all.

mre commented 1 month ago

Thanks for the understanding and also thanks for raising the question. :)