diff --git a/src/ecs.odin b/src/ecs.odin index 2732323..2ee115b 100644 --- a/src/ecs.odin +++ b/src/ecs.odin @@ -1,19 +1,7 @@ package shoal -import "core:strings" -import "core:thread" -import ml "vendor:microui" import rl "vendor:raylib" -update_pool: thread.Pool - -@(private) -sparse_entity_limit: int - -get_entity_limit :: proc() -> int { - return sparse_entity_limit -} - App :: struct { systems: map[int][dynamic]proc(), plugins: [dynamic]proc(app: ^App), diff --git a/src/signals.odin b/src/signals.odin index 675058c..218bf9d 100644 --- a/src/signals.odin +++ b/src/signals.odin @@ -1,25 +1,85 @@ package shoal -@(private) -signal_buffer_b: [dynamic]string -@(private) -signal_buffer_a: [dynamic]string - -write :: proc(name: string) { - append(&signal_buffer_b, name) +// per-type signal bus, created on first use. +// one system writes a signal with data, any number of other systems read it +// after. same-frame reads only work until signal_system clears the buses at +// POST_UPDATE — see read/signal_system below for the details. +Bus :: struct($T: typeid) { + pending: [dynamic]T, } -read :: proc(name: string) -> bool { - for s in signal_buffer_a { - if s == name {return true} - } - for s in signal_buffer_b { - if s == name {return true} - } - return false +@(private) +Bus_Entry :: struct { + bus: rawptr, + clear: proc(b: rawptr), } +@(private) +buses: map[typeid]Bus_Entry + +@(private) +get_bus :: proc($T: typeid) -> ^Bus(T) { + if entry, exists := buses[T]; exists { + return (^Bus(T))(entry.bus) + } + bus := new(Bus(T)) + buses[T] = { + bus = bus, + clear = clear_bus(T), + } + return bus +} + +// called once per payload type, hands signal_system a rawptr clear for that bus +@(private) +clear_bus :: proc($T: typeid) -> proc(b: rawptr) { + return proc(b: rawptr) { + bus := (^Bus(T))(b) + clear(&bus.pending) + } +} + +// queue a signal, readable until signal_system clears it at POST_UPDATE. +// note POST_UPDATE runs before PRE_DRAW/DRAW, so a reader sees exactly what's +// been written since the last POST_UPDATE: UPDATE systems see same-frame +// writes, but a DRAW system only sees writes made after this frame's +// POST_UPDATE (by PRE_DRAW or earlier DRAW systems) +// queue an empty payload by passing the bare type: write(MySignal) +write :: proc { + write_empty, + write_with_data, +} + +write_empty :: proc($T: typeid) { + bus := get_bus(T) + append(&bus.pending, T{}) +} + +write_with_data :: proc(data: $T) { + bus := get_bus(T) + append(&bus.pending, data) +} + +// every signal of type T written since the last POST_UPDATE, non-consuming, +// empty if none. iterate it: for s in read(MySignal) +// copied into context.temp_allocator so write() during iteration can't +// reallocate the pending buffer out from under you — but that means the result +// dies with the frame's temp arena, don't hold onto it past that +read :: proc($T: typeid) -> []T { + bus := get_bus(T) + if len(bus.pending) == 0 {return nil} + result := make([]T, len(bus.pending), context.temp_allocator) + copy(result, bus.pending[:]) + return result +} + +// runs at POST_UPDATE, clears every bus so the next frame starts fresh. +// writes made after it (e.g. in DRAW) survive until the *next* frame's +// POST_UPDATE, so the rest of this frame and the next frame's +// PRE_UPDATE/UPDATE systems still read them — but the next frame's DRAW +// never does, POST_UPDATE gets there first signal_system :: proc() { - signal_buffer_a = signal_buffer_b - clear(&signal_buffer_b) + for _, e in buses { + e.clear(e.bus) + } } diff --git a/src/systems.odin b/src/systems.odin index 2855967..668c1a0 100644 --- a/src/systems.odin +++ b/src/systems.odin @@ -6,7 +6,7 @@ import rl "vendor:raylib" DefaultPlugin :: proc(app: ^App) { add_system(app, STARTUP, startup_window) add_system(app, DRAW, draw_sprite) - add_system(app, UPDATE, signal_system) + add_system(app, POST_UPDATE, signal_system) add_system(app, UPDATE, run_timers) add_system(app, UPDATE, update_cursor_position) add_system(app, PRE_DRAW, proc() {rl.BeginDrawing()