From ec8bcdb5ed97c437e0983acec572baaa979f4fbe Mon Sep 17 00:00:00 2001 From: Jason Hilder Date: Thu, 2 Jul 2026 10:20:35 +0200 Subject: [PATCH] Fighting the wasm build... --- src/machine/machine.odin | 27 ++-- src/main_desktop/main.odin | 4 +- src/main_web/main_web.odin | 17 ++- src/simulator/gui.odin | 12 +- src/simulator/gui_control_bar.odin | 8 +- src/simulator/gui_file_loader.odin | 15 +-- src/simulator/gui_file_loader_web.odin | 174 +++++++++++++++++++++++++ src/simulator/simulator.odin | 11 +- 8 files changed, 225 insertions(+), 43 deletions(-) create mode 100644 src/simulator/gui_file_loader_web.odin diff --git a/src/machine/machine.odin b/src/machine/machine.odin index b342400..114faea 100644 --- a/src/machine/machine.odin +++ b/src/machine/machine.odin @@ -1,7 +1,5 @@ package machine -import "core:log" - // System struct, init, constants, fontset System :: struct { @@ -48,7 +46,7 @@ FONT_SET := [80]u8 { } init :: proc() -> System { - log.info("Booting chip 8 cpu") + // log.info("Booting chip 8 cpu") // Structs are zero initialized so timers, sp etc are good. s := System { pc = 0x200 } @@ -69,17 +67,20 @@ run_machine :: proc(s: ^System, cycles: int) { } } -new_machine :: proc() -> System { - s: System - s.pc = 0x200 - s.current_key = -1 - // load fonts into the memory +reset_machine :: proc(s: ^System) { + s.memory = {} + s.v = {} + s.stack = {} + s.sp = 0 + s.i = 0 + s.pc = 0x200 + s.display = {} + s.keypad = {} + s.current_key = -1 + s.delay_timer = 0 + s.sound_timer = 0 + for v, i in FONT_SET { s.memory[i] = v } - return s -} - -reset_machine :: proc(s: ^System) { - s^ = new_machine() } diff --git a/src/main_desktop/main.odin b/src/main_desktop/main.odin index 693700d..958d63b 100644 --- a/src/main_desktop/main.odin +++ b/src/main_desktop/main.odin @@ -34,10 +34,10 @@ main :: proc() { sim.init(&s) for sim.should_run() { - sim.update(&s) + sim.update() } - sim.shutdown(&s) + sim.shutdown() when DEV { if len(track.allocation_map) > 0 { diff --git a/src/main_web/main_web.odin b/src/main_web/main_web.odin index d5605e3..76cb330 100644 --- a/src/main_web/main_web.odin +++ b/src/main_web/main_web.odin @@ -21,7 +21,7 @@ main_start :: proc "c" () { // emscripten. There is some kind of conflict with how the manage memory. // So this sets up an allocator that uses emscripten's malloc. context.allocator = emscripten_allocator() - runtime.init_global_temporary_allocator(1*mem.Megabyte) + runtime.init_global_temporary_allocator(4*mem.Megabyte) // Since we now use js_wasm32 we should be able to remove this and use // context.logger = log.create_console_logger(). However, that one produces @@ -30,25 +30,25 @@ main_start :: proc "c" () { web_context = context - // Init the emu 8 "cpu" - system := emu.init() - s := sim.Simulator { - machine = &system, + system := new(emu.System) + system^ = emu.init() + + s := new(sim.Simulator) + s^ = sim.Simulator { + machine = system, rom_loaded = false, paused = true, step = false, cpu_hz = 700, disasm_follow = true, } - - sim.init(&s) + sim.init(s) } @export main_update :: proc "c" () -> bool { context = web_context - // TODO sim.update() return sim.should_run() } @@ -56,7 +56,6 @@ main_update :: proc "c" () -> bool { @export main_end :: proc "c" () { context = web_context - // TODO sim.shutdown() } diff --git a/src/simulator/gui.odin b/src/simulator/gui.odin index da4d655..3478442 100644 --- a/src/simulator/gui.odin +++ b/src/simulator/gui.odin @@ -3,6 +3,8 @@ package simulator import emu "../machine" import rl "vendor:raylib" +sim_run: bool + // Window WINDOW_WIDTH :: 1920 WINDOW_HEIGHT :: 1080 @@ -40,6 +42,7 @@ Layout :: struct { init :: proc(sim: ^Simulator) { _sim = sim + sim_run = true // desktop when ODIN_OS != .JS { @@ -49,7 +52,7 @@ init :: proc(sim: ^Simulator) { // web when ODIN_OS == .JS { - rl.SetConfigFlags({.VSYNC_HINT}) + // rl.SetConfigFlags({.VSYNC_HINT}) } rl.InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Octal Cookie - Chip 8 Simulator") @@ -86,8 +89,8 @@ update :: proc() { rl.BeginDrawing() rl.ClearBackground(rl.Color{0x18, 0x18, 0x18, 0xFF}) - cycles := int(sim.cpu_hz / SIM_FPS) if (!sim.paused) { + cycles := int(sim.cpu_hz / SIM_FPS) // Cycle the machine to update memory etc emu.run_machine(sim.machine, cycles) tick_timers(sim) @@ -106,7 +109,7 @@ update :: proc() { // Left // ------------------------------------------ - when ODIN_OS != .JS { gui_file_loader(layout.file_loader, sim)} + gui_file_loader(layout.file_loader, sim) gui_key_pad(layout.keypad, sim.machine.keypad, sim.font) // Center @@ -142,7 +145,8 @@ should_run :: proc() -> bool { when ODIN_OS != .JS { return !rl.WindowShouldClose() } - return true // web loop is controlled by JS requestAnimationFrame + // web loop is controlled by JS requestAnimationFrame + return true } tick_timers :: proc(sim: ^Simulator) { diff --git a/src/simulator/gui_control_bar.odin b/src/simulator/gui_control_bar.odin index fb6385a..908f02a 100644 --- a/src/simulator/gui_control_bar.odin +++ b/src/simulator/gui_control_bar.odin @@ -1,9 +1,7 @@ package simulator import "core:fmt" -import "core:log" -import emu "../machine" import rl "vendor:raylib" gui_control_bar :: proc(rect: rl.Rectangle, sim: ^Simulator) { @@ -19,7 +17,7 @@ gui_control_bar :: proc(rect: rl.Rectangle, sim: ^Simulator) { if sim.rom_loaded { sim.paused = false } else { - log.info("no rom selected, can't run") + // log.info("no rom selected, can't run") } } @@ -32,9 +30,7 @@ gui_control_bar :: proc(rect: rl.Rectangle, sim: ^Simulator) { } if btn(&cursor, rect, BUTTON_HEIGHT, BUTTON_WIDTH, PADDING_X, "RESET") { - sim.paused = true - sim.disasm_count = 0 - emu.reset_machine(sim.machine) + reset_sim(sim) } slider_rect := rl.Rectangle{ diff --git a/src/simulator/gui_file_loader.odin b/src/simulator/gui_file_loader.odin index 3ca825d..a5f232b 100644 --- a/src/simulator/gui_file_loader.odin +++ b/src/simulator/gui_file_loader.odin @@ -3,7 +3,6 @@ package simulator import "core:strings" import "core:fmt" -import "core:log" import rl "vendor:raylib" import emu "../machine" @@ -60,10 +59,10 @@ gui_file_loader :: proc(rect: rl.Rectangle, sim: ^Simulator) { mouse := rl.GetMousePosition() if rl.CheckCollisionPointRec(mouse, drop_zone) { - path_str := string(dropped_file.paths[0]) - log.info("file dropped: ", path_str) + // path_str := string(dropped_file.paths[0]) + // log.info("file dropped: ", path_str) } else { - log.info("File dropped outside drop zone, ignoring") + // log.info("File dropped outside drop zone, ignoring") } } rl.UnloadDroppedFiles(dropped_file) @@ -136,22 +135,22 @@ gui_file_loader :: proc(rect: rl.Rectangle, sim: ^Simulator) { } load_selected_rom :: proc(sim: ^Simulator, rom_path: string) { - emu.reset_machine(sim.machine) + reset_sim(sim) rom_size, err := emu.load_rom(sim.machine, rom_path) if err != nil { // @TODO: update status bar here panic("failed to load rom!") } + sim.rom_loaded = true - sim.paused = true sim.disasm_count = disassemble_rom_n(sim.machine.memory[:], 0x200, 0x200 + u16(rom_size), sim.disasm[:]) } load_embedded_rom :: proc(sim: ^Simulator, path: string, data: []byte) { - emu.reset_machine(sim.machine) + reset_sim(sim) + copy(sim.machine.memory[0x200:], data) sim.rom_loaded = true - sim.paused = true sim.disasm_count = disassemble_rom_n(sim.machine.memory[:], 0x200, 0x200 + u16(len(data)), sim.disasm[:]) } diff --git a/src/simulator/gui_file_loader_web.odin b/src/simulator/gui_file_loader_web.odin new file mode 100644 index 0000000..2ac8156 --- /dev/null +++ b/src/simulator/gui_file_loader_web.odin @@ -0,0 +1,174 @@ +#+build js +package simulator + +import "core:strings" +import "core:fmt" +import rl "vendor:raylib" + +import emu "../machine" + +gui_file_loader :: proc(rect: rl.Rectangle, sim: ^Simulator) { + rl.DrawRectangleLinesEx(rect, 1, rl.GRAY) + + // Embedded Rom Section: + // --------------------------------------------- + bottom_bounds := rl.Rectangle { + x = rect.x, + y = rect.y, + width = rect.width - (PADDING_X * 2), + height = rect.height - (PADDING_X * 2) + } + rl.GuiPanel(bottom_bounds, "ROM List") + + panel_rect := rl.Rectangle { + x = bottom_bounds.x, + y = bottom_bounds.y + PANEL_HEADER, + width = rect.width - (PADDING_X * 2), + height = rect.height - (PADDING_X * 2) - PANEL_HEADER + } + + content_rect := rl.Rectangle { + x = panel_rect.x, + y = panel_rect.y - PANEL_HEADER, + width = panel_rect.width - 15, + height = f32(len(roms)) * LINE_HEIGHT + } + + view: rl.Rectangle + rl.GuiScrollPanel(panel_rect, nil, content_rect, &sim.rompick_scroll, &view) + + rl.BeginScissorMode(i32(view.x), i32(view.y), i32(view.width), i32(view.height)) + defer rl.EndScissorMode() + + for path, index in roms { + y_pos := panel_rect.y + f32(index * LINE_HEIGHT) + sim.rompick_scroll.y + + row_rect := rl.Rectangle { + x = view.x, + y = y_pos, + width = view.width, + height = LINE_HEIGHT, + } + + // make selected visible + if path.name == sim.selected_rom { + rl.DrawRectangleRec(row_rect, rl.ColorAlpha(rl.SKYBLUE, 0.25)) + } + + // hover rows + mouse := rl.GetMousePosition() + if rl.CheckCollisionPointRec(mouse, row_rect) { + rl.DrawRectangleRec(row_rect, rl.ColorAlpha(rl.WHITE, 0.08)) + if rl.IsMouseButtonPressed(.LEFT) { + sim.selected_rom = path.name + load_embedded_rom(sim, path.name, roms[index].data) + } + } + + txt := fmt.tprintf("%v: %v", (index + 1), path.name) + rl.DrawTextEx( + sim.font, + strings.clone_to_cstring(txt, context.temp_allocator), + {panel_rect.x + PADDING_X + sim.rompick_scroll.x, y_pos + (LINE_HEIGHT - 18) * 0.5}, + 18, 1, + rl.WHITE + ) + } +} + +load_embedded_rom :: proc(sim: ^Simulator, path: string, data: []byte) { + reset_sim(sim) + copy(sim.machine.memory[0x200:], data) + sim.rom_loaded = true + sim.disasm_count = disassemble_rom_n(sim.machine.memory[:], 0x200, 0x200 + u16(len(data)), sim.disasm[:]) +} + +disassemble_rom_n :: proc(memory: []u8, start_addr: u16, end_addr: u16, out: []Instruction) -> int { + count := 0 + i := start_addr + + for i < end_addr { + if int(i) + 1 >= len(memory) do break + if count >= len(out) do break + + addr := i + high_byte := memory[i] + low_byte := memory[i + 1] + i += 2 + + // Get full opcode from the 2 bytes + opcode := (u16(high_byte) << 8) | u16(low_byte) + + // Pre-decode common components of opcodes + first_nibble := (opcode & 0xF000) >> 12 + vx := (opcode & 0x0F00) >> 8 // A 4-bit value, the lower 4 bits of the high byte of the instruction + vy := (opcode & 0x00F0) >> 4 // A 4-bit value, the upper 4 bits of the low byte of the instruction + last_nibble := (opcode & 0x000F) // A 4-bit value, the lowest 4 bits of the instruction + kk := u8(opcode & 0x00FF) // An 8-bit value, the lowest 8 bits of the instruction + nnn := (opcode & 0x0FFF) // A 12-bit value, the lowest 12 bits of the instruction + + + instruction := &out[count] + instruction.address = addr + instruction.raw = opcode + + switch first_nibble { + case 0x0: + switch opcode { + //case 0x00E0: str = "CLS" + case 0x00E0: fmt.bprintf(instruction.str[:], "CLS") + case 0x00EE: fmt.bprintf(instruction.str[:], "RET") + case: fmt.bprintf(instruction.str[:], "DATA 0x%04X", opcode) + } + case 0x1: fmt.bprintf(instruction.str[:], "JP 0x%03X", nnn) + case 0x2: fmt.bprintf(instruction.str[:], "CALL 0x%03X", nnn) + case 0x3: fmt.bprintf(instruction.str[:], "SE V%X, 0x%02X", vx, kk) + case 0x4: fmt.bprintf(instruction.str[:], "SNE V%X, 0x%02X", vx, kk) + case 0x5: fmt.bprintf(instruction.str[:], "SE V%X, V%X", vx, vy) + case 0x6: fmt.bprintf(instruction.str[:], "LD V%X, 0x%02X", vx, kk) + case 0x7: fmt.bprintf(instruction.str[:], "ADD V%X, 0x%02X", vx, kk) + case 0x8: + switch last_nibble { + case 0x0: fmt.bprintf(instruction.str[:], "LD V%X, V%X", vx, vy) + case 0x1: fmt.bprintf(instruction.str[:], "OR V%X, V%X", vx, vy) + case 0x2: fmt.bprintf(instruction.str[:], "AND V%X, V%X", vx, vy) + case 0x3: fmt.bprintf(instruction.str[:], "XOR V%X, V%X", vx, vy) + case 0x4: fmt.bprintf(instruction.str[:], "ADD V%X, V%X", vx, vy) + case 0x5: fmt.bprintf(instruction.str[:], "SUB V%X, V%X", vx, vy) + case 0x6: fmt.bprintf(instruction.str[:], "SHR V%X", vx) + case 0x7: fmt.bprintf(instruction.str[:], "SUBN V%X, V%X", vx, vy) + case 0xE: fmt.bprintf(instruction.str[:], "SHL V%X", vx) + case: fmt.bprintf(instruction.str[:], "DATA 0x%04X", opcode) + } + case 0x9: fmt.bprintf(instruction.str[:], "SNE V%X, V%X", vx, vy) + case 0xA: fmt.bprintf(instruction.str[:], "LD I, 0x%03X", nnn) + case 0xB: fmt.bprintf(instruction.str[:], "JP V0, 0x%03X", nnn) + case 0xC: fmt.bprintf(instruction.str[:], "RND V%X, 0x%02X", vx, kk) + case 0xD: fmt.bprintf(instruction.str[:], "DRW V%X, V%X, %X", vx, vy, last_nibble) + case 0xE: + switch kk { + case 0x9E: fmt.bprintf(instruction.str[:], "SKP V%X", vx) + case 0xA1: fmt.bprintf(instruction.str[:], "SKNP V%X", vx) + case: fmt.bprintf(instruction.str[:], "DATA 0x%04X", opcode) + } + case 0xF: + switch kk { + case 0x07: fmt.bprintf(instruction.str[:], "LD V%X, DT", vx) + case 0x0A: fmt.bprintf(instruction.str[:], "LD V%X, K", vx) + case 0x15: fmt.bprintf(instruction.str[:], "LD DT, V%X", vx) + case 0x18: fmt.bprintf(instruction.str[:], "LD ST, V%X", vx) + case 0x1E: fmt.bprintf(instruction.str[:], "ADD I, V%X", vx) + case 0x29: fmt.bprintf(instruction.str[:], "LD F, V%X", vx) + case 0x33: fmt.bprintf(instruction.str[:], "LD B, V%X", vx) + case 0x55: fmt.bprintf(instruction.str[:], "LD [I], V%X", vx) + case 0x65: fmt.bprintf(instruction.str[:], "LD V%X, [I]", vx) + case: fmt.bprintf(instruction.str[:], "DATA 0x%04X", opcode) + } + case: fmt.bprintf(instruction.str[:], "DATA 0x%04X", opcode) + } + + count += 1 + } + + return count +} diff --git a/src/simulator/simulator.odin b/src/simulator/simulator.odin index 1cd8bb7..441686c 100644 --- a/src/simulator/simulator.odin +++ b/src/simulator/simulator.odin @@ -34,7 +34,6 @@ Simulator :: struct { // GUI sound: rl.Sound, font: rl.Font, - active_tab: i32, mem_scroll: rl.Vector2, rompick_scroll: rl.Vector2, selected_rom: string, @@ -46,3 +45,13 @@ Simulator :: struct { disasm_count: int, disasm_scroll: rl.Vector2, } + +reset_sim :: proc(sim: ^Simulator) { + sim.paused = true + sim.disasm_count = 0 + sim.rom_loaded = false + sim.selected_rom = "" + + emu.reset_machine(sim.machine) +} +