summaryrefslogtreecommitdiff
path: root/lib/simulation.odin
diff options
context:
space:
mode:
authorJoel Stålnacke <joel@saker.fi>2026-03-23 19:50:18 +0200
committerJoel Stålnacke <joel@saker.fi>2026-03-23 19:50:18 +0200
commit3a1f9fe6800ef5fe7bbf1c0a5976720383ba84fe (patch)
treef1218eb4085a654f6bb70671b41b4a74e646c052 /lib/simulation.odin
parent3870982f640260822bf605e693782ce9f5d79cf6 (diff)
Rewrite native code in Odin and use ECSodin
Diffstat (limited to 'lib/simulation.odin')
-rw-r--r--lib/simulation.odin261
1 files changed, 261 insertions, 0 deletions
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, ~{})
+}