Hello friends. I’m working on a 6502 emulator as a hobby project. Due to how old it is, most of the devices that use a 6502 have direct memory mapped I/O. This means that, since I’d like to be able to resuse the emulation code for different devices (i.e. in a NES, in an Apple II etc), it would be advantageous to be able to have different functions for writing/reading to the device memory (since the location and behaviour of the memory mapped I/O portions will differ from each device to the next).
Is this a valid usecase for function bodies? Here’s some basic pseudocode that demonstrates how I more or less understand it would work. To be clear, the read and write functions won’t change at runtime and will always be comptime known.
main.zig
const STDT = @import("things.zig");
const struct_that_does_thing: STDT = .{.read = read, .write = write};
var storage: [5]u8 = @splat(0);
struct_that_does_thing.doThings();
....
fn read(idx: u8) u8 {
//do some stuff if idx is a certain value
//...
return storage[idx];
}
fn write(idx: u8, val: u8) void {
//do some stuff if idx is a certain value
//...
storage[idx] = val;
}
Is this a correct usage of function bodies, or would I be better of with a pointer? I’m also not sure if it matters whether storage is a stack variable or a global or a variable in some struct that might also have a STDT field.
(In case you’re wondering, the reason I’d like to avoid function pointers here is because the 6502 runs at 1.5Mhz and every single one of those cycles either reads or writes.)
it shouldn’t since you are copying to/from. I assume you meant the stack of a function higher up in the call stack.
I think you’re justified in using function bodies here. Though, my first thought was just to give each component a slice of memory it can do what it wants with. But I think that would be harder to make work well, and I’m not familiar with 6502.
…that way the optimizer should be able to do its thing, even inline the read/write calls.
PS: my chips emulators here are also heavily configured via generics, however I use a different approach for glueing chips in a system together (via in/out pin bitmasks - this is a very ‘clean design’ but may be slower than traditional methods: chipz/src/chips at main · floooh/chipz · GitHub - also see: Zig and Emulators)
After thinking on it for a bit, it seems that my pointer-less design would only really work if I had a single instance of the emulator running at a time (since each instance would need a separate slice for their own RAM and therefore read and write would basically need a self pointer etc) - and since I do want to at least have the option for multiple instances running at once, I guess pointers can’t be avoided. That being so, I think I’ll explore the generics option. Still, it’s nice to know I have the option of function bodies in the future for other projects.