Gododin Gododin Documentation

Linked repository source

examples/logistics_post/declarations/logistics_post.odin

This read-only view comes from the file in the Gododin repository. It retains its design, example or evidence context.

Download the original file · Return to the guide

// Package declarations is the authored native class package for
// logistics_post (DX-8). One Node owns the pure-Odin depot-logistics post:
// the scene imports its authored map once, submits jobs with stable ids, and
// drives fixed ticks; the kernel owns assignment, routing, inventory, and the
// worker state, and the scene reads one bounded batch snapshot per frame for
// presentation. The job_completed signal is the only event — one emission per
// delivered job, never per agent per tick.
package declarations

import "core:mem"

import gd "gododin:bindings/typed/call"

import logistics "../subsystem"

// Logistics_Settings is the native settings Resource the Inspector edits and
// the saved scene retains: worker count and simulation seed. The scene's
// configure reads both; interactive tick pacing stays in the scene's UI.
@(gododin_class="Resource")
Logistics_Settings :: struct {
	worker_count: i64 `godot:"property"`,
	seed:         i64 `godot:"property"`,
}

// Job_Event is the payload of the job_completed signal: the stable job id.
Job_Event :: struct {
	job_id: i64,
}

@(gododin_class="Node2D")
Logistics_Post :: struct {
	allocator:  mem.Allocator,
	post:       ^logistics.Post,
	width:      i64,
	height:     i64,
	last_error: i64,
	ticks:      i64,
	deliveries: i64,
	// The scene-owned settings Resource and the single coarse event.
	settings:       gd.Ref(Logistics_Settings) `godot:"property"`,
	job_completed:  gd.Signal(Job_Event),
}

logistics_post_init :: proc(self: ^Logistics_Post, allocator: mem.Allocator) -> bool {
	// Field assignment only: the bridge has already written this slot's
	// signal token, and a whole-struct zeroing would wipe it.
	self.allocator = allocator
	self.post = nil
	self.last_error = 0
	self.ticks = 0
	self.deliveries = 0
	return true
}

logistics_post_deinit :: proc(self: ^Logistics_Post) {
	if self.post != nil {
		_ = logistics.post_destroy(&self.post)
	}
}

// configure imports the authored map (one bounded whole-grid snapshot the
// kernel copies) and spawns the workers. Reconfiguration destroys the
// previous post first; a failed import leaves the old one live.
//
// Returns 0, -1 call validation, -2 already configured + shutdown refused,
// -3 rejected configuration (bounds, kind bytes, worker count, or spawn room).
@(gododin)
logistics_post_configure :: proc(
	self:    ^Logistics_Post,
	call:    gd.Main_Call,
	width, height: i64,
	cells:   []u8,
	worker_count, seed: i64,
) -> i64 {
	if gd.validate_main_call(call) != .None { return -1 }
	created, created_ok := logistics.post_create(
		i32(width), i32(height), cells, i32(worker_count), u32(seed & 0xffffffff), self.allocator)
	if !created_ok {
		self.last_error = -3
		return -3
	}
	if self.post != nil {
		if !logistics.post_destroy(&self.post) {
			// Keep the failed retirement reachable and own the new post too.
			self.last_error = -2
			self.post = created
			return -2
		}
	}
	self.post = created
	self.width = width
	self.height = height
	self.last_error = 0
	self.ticks = 0
	self.deliveries = 0
	return 0
}

// submit_job registers one source→destination delivery and returns its
// stable id (replays identically for the same job order).
@(gododin)
logistics_post_submit_job :: proc(self: ^Logistics_Post, call: gd.Main_Call, source, dest: i64) -> i64 {
	if gd.validate_main_call(call) != .None { return -1 }
	if self.post == nil { self.last_error = -2; return -2 }
	id := logistics.post_submit_job(self.post, i32(source), i32(dest))
	if id < 0 { self.last_error = -3 }
	return id
}

// tick advances one fixed simulation step and emits job_completed once per
// job delivered this tick. Ticks are explicit — the scene owns pacing.
@(gododin)
logistics_post_tick :: proc(self: ^Logistics_Post, call: gd.Main_Call) -> i64 {
	if gd.validate_main_call(call) != .None { return -1 }
	if self.post == nil { self.last_error = -2; return -2 }
	delivered := logistics.post_tick(self.post)
	self.ticks = i64(self.post.ticks)
	self.deliveries = i64(self.post.deliveries)
	delivered_ids: [logistics.MAX_WORKERS]i64
	written := logistics.last_delivered_ids(self.post, delivered_ids[:])
	for index in 0..<written {
		payload := Job_Event{job_id = delivered_ids[index]}
		if error := gd.emit(call, self.job_completed, payload); error != .None {
			self.last_error = i64(error)
			return i64(error)
		}
	}
	return delivered
}

// worker_snapshot copies the bounded batch presentation snapshot: five f32s
// per worker (id, cell x, cell y, state, claimed job id). One call per frame
// for the whole scene — never per agent.
@(gododin)
logistics_post_worker_snapshot :: proc(self: ^Logistics_Post, out: []f32) -> i64 {
	if self.post == nil { self.last_error = -2; return -2 }
	count := logistics.worker_snapshot(self.post, out)
	if count < 0 { self.last_error = -3 }
	return count
}

@(gododin)
logistics_post_job_state :: proc(self: ^Logistics_Post, id: i64) -> i64 {
	if self.post == nil { return -1 }
	return logistics.job_state(self.post, id)
}

@(gododin)
logistics_post_get_job_count :: proc(self: ^Logistics_Post) -> i64 {
	if self.post == nil { return 0 }
	return self.post.job_count
}

@(gododin)
logistics_post_get_worker_count :: proc(self: ^Logistics_Post) -> i64 {
	if self.post == nil { return 0 }
	return i64(self.post.worker_count)
}

@(gododin)
logistics_post_get_ticks :: proc(self: ^Logistics_Post) -> i64 {
	return self.ticks
}

@(gododin)
logistics_post_get_deliveries :: proc(self: ^Logistics_Post) -> i64 {
	return self.deliveries
}

@(gododin)
logistics_post_get_digest :: proc(self: ^Logistics_Post) -> i64 {
	if self.post == nil { return 0 }
	return i64(logistics.state_digest(self.post))
}

@(gododin)
logistics_post_get_last_error :: proc(self: ^Logistics_Post) -> i64 {
	return self.last_error
}