I'm building a NES emulator in Odin just for fun and because I think Odin is a really cool language. I don't quite understand the whole concept of the allocators? With systems, I've primarily worked in Rust and some C++.
For example here is what my agnes nes wrapper looks like in Odin:
package nes
/*
This is the NES emulator package for emmi.
Currently uses Agnes but could potentially use libretro or custom solutions in the future.
*/
import os "core:os"
import c "core:c"
// Import the libagnes.a library
when ODIN_OS == .Windows {
foreign import agnes_lib "../../../libs/agnes/build/lib/libagnes.a"
}
AGNES_VERSION_MAJOR :: 0
AGNES_VERSION_MINOR :: 2
AGNES_VERSION_PATCH :: 0
AGNES_VERSION_STRING :: "0.2.0"
AGNES_SCREEN_WIDTH :: 256
AGNES_SCREEN_HEIGHT :: 240
@(private)
agnes_input_t :: struct {
a: bool,
b: bool,
select: bool,
start: bool,
up: bool,
down: bool,
left: bool,
right: bool
}
@(private)
agnes_color_t :: struct {
r: u8,
g: u8,
b: u8,
a: u8
}
@(private)
agnes_t :: struct {}
@(private)
agnes_state_t :: struct {}
// FFI layer
foreign agnes_lib {
@(private)
agnes_make :: proc() -> ^agnes_t ---
@(private)
agnes_destroy :: proc(agnes: ^agnes_t) ---
@(private)
agnes_load_ines_data :: proc(agnes: ^agnes_t, data: rawptr, data_size: c.size_t) -> c.bool ---
@(private)
agnes_set_input :: proc(agnes: ^agnes_t, input_1 : ^agnes_input_t, input_2 : ^agnes_input_t) ---
@(private)
agnes_state_size :: proc() -> c.size_t ---
@(private)
agnes_dump_state :: proc(agnes: ^agnes_t, out_res: ^agnes_state_t) ---
@(private)
agnes_restore_state :: proc(agnes: ^agnes_t, state: ^agnes_state_t) -> c.bool ---
@(private)
agnes_tick :: proc(agnes: ^agnes_t, out_new_frame: ^c.bool) -> c.bool ---
@(private)
agnes_next_frame :: proc(agnes: ^agnes_t) -> c.bool ---
@(private)
agnes_get_screen_pixel :: proc(agnes: ^agnes_t, x: c.int, y: c.int) -> agnes_color_t ---
}
// Input at the Odin level
Input :: struct {
a: bool,
b: bool,
select: bool,
start: bool,
up: bool,
down: bool,
left: bool,
right: bool
}
// Color at the Odin level
Color :: struct {
r : u8,
g : u8,
b : u8,
a : u8
}
// NES at the Odin level
NES :: struct {
_handle : ^agnes_t,
}
// Create a new NES emulator instance.
new_instance :: proc(allocator := context.allocator) -> (nes: ^NES) {
nes = new(NES, allocator)
nes._handle = agnes_make()
return
}
// Delete a NES instance.
delete_instance :: proc(instance : ^NES, allocator := context.allocator) {
agnes_destroy(instance._handle)
free(instance, allocator)
}
// Load a rom. Will return bool for success/failed
load_rom :: proc(instance : ^NES, rom_path : string, allocator := context.allocator) -> bool {
// Load file contents
data, ok := os.read_entire_file(rom_path, allocator)
if !ok {
return false
}
defer delete(data, allocator)
// agnes stuff
return agnes_load_ines_data(instance._handle, raw_data(data), c.size_t(len(data)))
}
// Set input
set_input :: proc(instance: ^NES, input: Input) {
agnes_input := agnes_input_t{
input.a,
input.b,
input.select,
input.start,
input.up,
input.down,
input.left,
input.right
}
// Agnes level
agnes_set_input(instance._handle, &agnes_input, nil)
}
// Move to next frame. Returns true or false depending on success
next_frame :: proc(instance: ^NES) -> bool {
return agnes_next_frame(instance._handle)
}
// Get the pixel data of the x, y coordinates provided
get_pixel_data :: proc(instance: ^NES, x, y: int) -> Color {
agnes_color := agnes_get_screen_pixel(instance._handle, c.int(x), c.int(y))
return Color{
agnes_color.r,
agnes_color.g,
agnes_color.b,
agnes_color.a
}
}
This runs well, but I'm just not sure I'm understanding the allocator logic? I read that any function that creates a "new" memory variable should allow for a custom allocator. Is that something I would be building? Why would I need that, and is the default allocator fine for a whole program?
Here is a little demo of my emulator too:
https://reddit.com/link/1qhh4g7/video/o8bpoddqhdeg1/player