From 3a1f9fe6800ef5fe7bbf1c0a5976720383ba84fe Mon Sep 17 00:00:00 2001 From: Joel Stålnacke Date: Mon, 23 Mar 2026 19:50:18 +0200 Subject: Rewrite native code in Odin and use ECS --- lib/simulation.odin | 261 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 lib/simulation.odin (limited to 'lib/simulation.odin') diff --git a/lib/simulation.odin b/lib/simulation.odin new file mode 100644 index 0000000..0ac8e89 --- /dev/null +++ b/lib/simulation.odin @@ -0,0 +1,261 @@ +package djup + +import "base:runtime" +import "core:log" + +#assert(#config(DJUP_CLIENT, false) || #config(DJUP_SERVER, false)) +#assert(!(#config(DJUP_CLIENT, false) && #config(DJUP_SERVER, false))) + +SimulationContext :: struct { + snapshots: []Snapshot, + ctx: runtime.Context, + assert_func: AssertFunc, +} + +Metadata :: Components + +entity_id :: distinct i32 + +Snapshot :: struct { + active: bool, + tick: u32, + metadata: []Metadata, + balls: []Ball, + player_inputs: []PlayerInput, + ctx: ^runtime.Context, +} + +@(export = true) +sim_create :: proc "c" (snapshots, entities: i32) -> ^SimulationContext { + if snapshots <= 0 || entities <= 0 { + return nil + } + + context = runtime.default_context() + context.logger = log.create_console_logger() + ctx := new(SimulationContext) + ctx.ctx = context + ctx.ctx.user_ptr = ctx + + snapshots, err := make([]Snapshot, snapshots) + if err != .None { + free(ctx) + return nil + } + ctx.snapshots = snapshots + + error := false + for &s in ctx.snapshots { + s.ctx = &ctx.ctx + if !snapshot_init(&s, entities) { + error = true + } + } + + if error { + for &s in ctx.snapshots { + snapshot_deinit(&s) + } + delete(ctx.snapshots) + free(ctx) + return nil + } + + log.destroy_console_logger(ctx.ctx.logger) + ctx.ctx.logger = log.nil_logger() + + return ctx +} + +@(export = true) +sim_free :: proc "c" (ctx: ^SimulationContext) { + if ctx == nil { + return + } + context = ctx.ctx + + for &s in ctx.snapshots { + snapshot_deinit(&s) + } + delete(ctx.snapshots) + free(ctx) +} + +snapshot_init :: proc(s: ^Snapshot, entities: i32) -> bool { + make_components :: proc($T: typeid, len: i32, error: ^bool) -> []T { + res, err := make([]T, len) + if err != .None { + log.errorf("Failed to allocate component array: %s", err) + error^ = true + return nil + } + return res + } + + error := false + components: Components = ~{} + for c in components { + switch c { + case .Exists: + s.metadata = make_components(Metadata, entities, &error) + case .Ball: + s.balls = make_components(Ball, entities, &error) + case .PlayerInput: + s.player_inputs = make_components(PlayerInput, entities, &error) + } + } + return !error +} + +snapshot_deinit :: proc(s: ^Snapshot) -> bool { + delete_components :: proc($T: typeid, slice: []T, error: ^bool) { + if slice == nil { + return + } + err := delete(slice) + if err != .None && error != nil { + log.errorf("Failed to delete component array: %s", err) + error^ = true + } + } + + error := false + components: Components = ~{} + for c in components { + switch c { + case .Exists: + delete_components(Metadata, s.metadata, &error) + case .Ball: + delete_components(Ball, s.balls, &error) + case .PlayerInput: + delete_components(PlayerInput, s.player_inputs, &error) + } + } + return !error +} + +@(export = true) +sim_get_snapshot :: proc "c" (ctx: ^SimulationContext) -> ^Snapshot { + if ctx == nil { + return nil + } + + for &s in ctx.snapshots { + if s.active { + continue + } + s.active = true + return &s + } + return nil +} + +@(export = true) +sim_return_snapshot :: proc "c" (ctx: ^SimulationContext, s: ^Snapshot) { + if ctx == nil || s == nil { + return + } + + s.active = false +} + +@(export = true) +snapshot_create_entity :: proc "c" (s: ^Snapshot) -> entity_id { + if s == nil { + return -1 + } + i := 0 + for i < len(s.metadata) { + if .Exists not_in s.metadata[i] { + s.metadata[i] = {.Exists} + return entity_id(i) + } + } + return -1 +} + +ComponentAddError :: enum u32 { + None, + InvalidArg, + InvalidId, + EntityNotFound, +} + +@(export = true) +snapshot_add_component :: proc "c" ( + s: ^Snapshot, + id: entity_id, + component: Component, + init: rawptr, +) -> ComponentAddError { + if s == nil { + return .InvalidArg + } + if (id < 0 && id >= entity_id(len(s.metadata))) { + return .InvalidId + } + + if .Exists not_in s.metadata[id] { + return .EntityNotFound + } + + add :: proc "contextless" (s: ^Snapshot, id: entity_id, c: Components, slice: []$T, init: ^T) { + s.metadata[id] += c + if init != nil { + slice[id] = init^ + } + } + + switch component { + case .Exists: + add(s, id, {.Exists}, s.metadata, nil) + case .Ball: + add(s, id, {.Ball}, s.balls, cast(^Ball)init) + case .PlayerInput: + add(s, id, {.PlayerInput}, s.player_inputs, cast(^PlayerInput)init) + } + return .None +} + +ComponentRemoveError :: enum u32 { + None, + InvalidArg, + InvalidId, + EntityNotFound, +} + +@(export = true) +snapshot_remove_components :: proc "c" ( + s: ^Snapshot, + id: entity_id, + components: Components, +) -> ComponentRemoveError { + if s == nil { + return .InvalidArg + } + if (id < 0 && id >= entity_id(len(s.metadata))) { + return .InvalidId + } + + if .Exists not_in s.metadata[id] { + return .EntityNotFound + } + + for c in components { + switch c { + case .Exists: + s.metadata[id] = {} + case .Ball: + s.balls[id] = Ball{} + case .PlayerInput: + s.player_inputs[id] = PlayerInput{} + } + } + + return .None +} + +@(export = true) +snapshot_reset_entity :: proc "c" (s: ^Snapshot, id: entity_id) -> ComponentRemoveError { + return snapshot_remove_components(s, id, ~{}) +} -- cgit v1.2.3