Linked repository source
examples/bunny_mark/declarations/bunny_field.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
import "core:mem"
import raw "gododin:bindings/raw"
import godot "gododin:bindings/typed/godot"
import gd "gododin:bindings/typed/call"
import render "gododin:bindings/typed/render"
import bunny "../subsystem"
// Bunny_Field owns one retained float32 staging owner and one pure population.
// The scene owns the child MultiMeshInstance2D, MultiMesh, canvas ArrayMesh and texture.
// The scene driver calls submit_frame once per fixed simulation step, with no
// autonomous callback hidden outside the eval bracket. Packing reads live SoA
// positions; verification independently replays state and reads engine transforms.
// Explicit shutdown runs on every scene exit before authority disappears.
@(gododin_class="Node2D")
Bunny_Field :: struct {
capacity: i64,
population_seed: u32,
frames_submitted: i64,
speed_scale: f32,
gravity: f32,
allocator: mem.Allocator,
// One stream owns the whole submission pipeline for the scene's RID:
// the staged store, the pending retirement, and the write/publish/
// submit sequence. The RID itself stays scene-owned inside it.
stream: render.MultiMesh_Stream,
field: ^bunny.Field,
retired_field: ^bunny.Field,
}
// Bunny_Field_Init records the Host-provided owning allocator once so pure
// population allocation and deallocation stay symmetric across callbacks.
// Every engine callback still establishes its valid Odin context and role.
bunny_field_init :: proc(self: ^Bunny_Field, allocator: mem.Allocator) -> bool {
self.allocator = allocator
return true
}
// Bunny_Field_Deinit is the allocator-symmetric counterpart of init. The pure
// population, if it is still live here, is retired under its own recorded
// allocator. The staged owner is deliberately not touched: retiring it needs
// facade call authority the teardown hook does not have, which is what the
// explicit shutdown operation is for — the same cooperative pattern the
// multimesh_stream fixture froze.
bunny_field_deinit :: proc(self: ^Bunny_Field) {
if self.field != nil {
_ = bunny.field_destroy(&self.field)
}
_ = bunny.field_destroy(&self.retired_field)
}
// Bunny_Field_Setup binds the scene's MultiMesh resource, allocates the
// requested bunny capacity through the engine's own server API, and creates
// the retained staging owner plus the pure population for that capacity. The
// scene's MultiMeshInstance2D is this node's own child 0; its MultiMesh
// resource's server RID is the workload's multimesh, resolved through the
// engine's validated casts and released immediately (the owned reference
// from get_multimesh is unreference()'d — the scene holds the owning one).
// The scene owns the server RID; this class changes its data extent.
//
// Error codes: -1 invalid call token, -2 capacity out of range, -3 staging
// already live, -4 authored object unavailable, -5 not a Node2D, -6 no
// MultiMeshInstance2D child at the scene's declared MMI path, -7 unused
// (merged into -6), -8 not a MultiMeshInstance2D,
// -9 get_multimesh failed or empty, -10 not a Resource, -11 get_rid failed or
// not live, -12 reference release failed, -13 rendering server unavailable,
// -14 multimesh_allocate_data failed, -15 facade unavailable, -16 staging
// owner creation failed, -17 pure population creation failed.
@(gododin)
bunny_field_setup :: proc(self: ^Bunny_Field, call: gd.Main_Call, count: i64, population_seed: i64) -> (code: i64) {
if gd.validate_main_call(call) != .None do return -1
if count <= 0 || count > bunny.MAX_COUNT do return -2
if render.multimesh_stream_owned(&self.stream) || self.field != nil || self.retired_field != nil do return -3
object, ok := gd.self(call)
if !ok do return -4
n2d, cast_ok := godot.cast_Node2D(gd.godot_main_call(call), &object)
if !cast_ok do return -5
// The scene owns its MultiMesh: this node's child named "MMI" (see
// project/main.tscn) is the workload's MultiMeshInstance2D, and its
// MultiMesh resource's RID is what the server API allocates against. The
// name is the scene's explicit binding, so adding or reordering siblings
// cannot silently rebind the workload.
child, child_ok := child_by_name(gd.godot_main_call(call), cast(^godot.Node)n2d, "MMI")
if !child_ok do return -6
scene_object := godot.Object{ptr = child.ptr}
mmi, mmi_ok := godot.cast_MultiMeshInstance2D(gd.godot_main_call(call), &scene_object)
if !mmi_ok do return -8
multimesh, multimesh_error := godot.multi_mesh_instance2d_get_multimesh(gd.godot_main_call(call), mmi)
if multimesh_error != .None do return -9
if !multimesh.live do return -9
// get_multimesh returns an owned reference, not a borrowed one. A typed
// MultiMesh is a RefCounted Resource; release on every subsequent return.
refc := godot.RefCounted{ptr=multimesh.handle.ptr}
defer {
if _, err := godot.ref_counted_unreference(gd.godot_main_call(call), &refc); err != .None {code = -12}
}
resource := godot.Resource{ptr=multimesh.handle.ptr}
handle, rid_error := godot.resource_get_rid(gd.godot_main_call(call), &resource)
if rid_error != .None || !handle.live do return -11
api, facade_ok := gd.main_facade(call)
if !facade_ok do return -15
stream, open_error := render.multimesh_stream_open(
render.main_call(api),
handle.value,
render.MultiMesh_Layout{},
int(count),
)
if open_error != .None do return -14
self.stream = stream // Reachable by shutdown even if population allocation fails.
field := bunny.field_create_with_allocator(count, u32(population_seed & 0xFFFFFFFF), self.allocator)
if field == nil do return -17
self.capacity = count
self.population_seed = u32(population_seed & 0xFFFFFFFF)
self.frames_submitted = 0
self.speed_scale = bunny.CANONICAL_SPEED_SCALE
self.gravity = bunny.CANONICAL_GRAVITY
self.field = field
return 0
}
// Bunny_Field_Submit_Frame is one workload frame: the pure subsystem integrates
// every bunny in one call, the producer writes the staged store in the earned
// 2D layout, the publisher releases it, and one coarse server call submits
// it. Nothing per-instance crosses the boundary, nothing allocates, and the
// configured Safe rendering model consumes on the main/server thread, which
// the scene checks at startup, so the next frame may rewrite the same store. The step always runs at the fixed canonical 1/60 dt so
// canonical-profile runs are replay-verifiable. Custom history is explicit.
//
// Error codes: -1 invalid call token, -2 staging or population not live,
// -3 facade unavailable, -4 staging write view failed, -5 pure step or layout
// write failed, -6 publish failed, -7 unused (owned by submit), -8 submit
// refused (no new publication, zero RID handle, or refused engine call).
@(gododin)
bunny_field_submit_frame :: proc(self: ^Bunny_Field, call: gd.Main_Call) -> i64 {
if gd.validate_main_call(call) != .None do return -1
if !self.stream.staging.live || self.field == nil || self.capacity <= 0 do return -2
api, facade_ok := gd.main_facade(call)
if !facade_ok do return -3
step_ok := bunny.field_step(self.field, bunny.CANONICAL_DT, bunny.CANONICAL_BOUNDS_X, bunny.CANONICAL_BOUNDS_Y,
self.speed_scale, self.gravity)
if !step_ok do return -5
view, write_error := render.multimesh_stream_write(render.main_call(api), &self.stream)
if write_error != .None do return -4
if !bunny.field_write_layout(self.field, view) do return -5
if _, publish_error := render.multimesh_stream_publish(&self.stream, .Main); publish_error != .None do return -6
if submit_error := render.multimesh_stream_submit(render.main_call(api), &self.stream); submit_error != .None do return -8
self.frames_submitted += 1
return 0
}
// Bunny_Field_Set_Count exercises the explicit capacity change: the same
// scene-owned RID is re-allocated through the server at the new count and
// both the staging owner and the pure population are replaced. The new owners
// are created first and the old ones retire only after the whole change
// succeeded. Once the server resize succeeds the new state is authoritative;
// a cleanup error is fatal and does not claim to undo engine storage.
//
// Error codes: -1 invalid call token, -2 count out of range, -3 current state
// not live, -4 facade unavailable, -5 stream resize failed (staged store
// creation, server re-allocation, or a pending retirement that refused to
// die), -6 new population failed, -7/-8 unused (folded into -5), -9 pending
// staging or population could not retire. Once the server resize succeeds,
// replacement state remains adopted even if old cleanup fails.
@(gododin)
bunny_field_set_count :: proc(self: ^Bunny_Field, call: gd.Main_Call, count: i64) -> i64 {
if gd.validate_main_call(call) != .None do return -1
if count <= 0 || count > bunny.MAX_COUNT do return -2
if !self.stream.staging.live || self.field == nil do return -3
api, facade_ok := gd.main_facade(call)
if !facade_ok do return -4
if !retire_previous(self, call) do return -9
self.retired_field = bunny.field_create_with_allocator(count, self.population_seed, self.allocator)
if self.retired_field == nil do return -6
if resize_error := render.multimesh_stream_resize(render.main_call(api), &self.stream, int(count)); resize_error != .None {
_ = retire_previous(self, call)
return -5
}
// Server extent has changed and the stream adopted its new store; adopt
// the matching pending population. The former active owners remain in
// the retirement slots for retry if cleanup fails.
self.field, self.retired_field = self.retired_field, self.field
self.capacity = count
self.frames_submitted = 0
if !retire_previous(self, call) do return -9
return 0
}
// Bunny_Field_Set_Profile retunes the ordinary live speed and gravity controls. The bounds and dt stay canonical. The
// staged oracle only applies at the canonical profile, so after a non-canonical
// retune `verify` reports that verification does not apply until the profile
// returns to canonical and a fresh population is installed.
//
// Returns -1 for an invalid call token and -2 for non-finite values.
@(gododin)
bunny_field_set_profile :: proc(self: ^Bunny_Field, call: gd.Main_Call, speed_scale: f64, gravity: f64) -> i64 {
if gd.validate_main_call(call) != .None do return -1
if !bunny.valid_profile({bunny.CANONICAL_DT,1280,720,f32(speed_scale),f32(gravity)}) do return -2
self.speed_scale = f32(speed_scale)
self.gravity = f32(gravity)
return 0
}
// Bunny_Field_Verify reads the complete packed transform buffer and checks every
// bunny bitwise against independent canonical replay and the submitted staging.
// The bulk getter avoids dev5 Compatibility's individual-getter CPU cache, which
// would retain subsequent transform-only submissions and invalidate staging reuse.
// The engine readback allocates/stalls outside timing; its owner is released before
// returning, so verification can be followed by more submitted frames.
//
// Requires transform-only MultiMesh data, physics interpolation off, and calls
// on the render thread. See dev5 drivers/gles3/storage/mesh_storage.cpp:1965,
// 2092, 2133: bulk readback does not create the per-instance data cache.
// Verification applies only to the canonical profile; a non-canonical retune
// reports -30, meaning healthy but unverified, rather than a state mismatch.
// Error codes: -1 invalid call token, -2 no capacity, -3 nothing submitted,
// -4 rendering server unavailable, -10 bulk getter, -11 readback extent/view,
// -12 readback release, -20 engine transform mismatch, -21 staged payload/state
// mismatch, -22 staging unavailable, -30 non-canonical physics.
@(gododin)
bunny_field_verify :: proc(self: ^Bunny_Field, call: gd.Main_Call) -> i64 {
if gd.validate_main_call(call) != .None do return -1
if self.capacity <= 0 || self.field == nil do return -2
if self.frames_submitted == 0 do return -3
if !self.field.profile_uniform || !bunny.profile_equal(self.field.profile, bunny.canonical_profile()) do return -30
engine_call := gd.godot_main_call(call)
server, server_error := godot.singleton_rendering_server(engine_call)
if server_error != .None do return -4
frames := self.field.frame
api, facade_ok := gd.main_facade(call)
if !facade_ok do return -22
view, view_error := render.multimesh_stream_write(render.main_call(api), &self.stream)
if view_error != .None do return -22
owned, get_error := godot.rendering_server_multimesh_get_buffer(engine_call, &server, self.stream.rid)
if get_error != .None do return -10
// This facade value only borrows `owned`; never destroy both representations.
readback := render.Packed_Float32_Array{value=godot.owned_value_borrow(&owned), live=true}
base, length, read_error := render.packed_float32_array_read_base(api, &readback)
result := i64(0)
if read_error != .None || base == nil || length != int(self.capacity)*bunny.STRIDE {
result = -11
} else {
stored := mem.slice_ptr(base, length)
for representative in 0..<self.capacity {
start := int(representative)*bunny.STRIDE
if !bunny.oracle_instance(view[start:start+bunny.STRIDE], representative, frames, self.population_seed) {
result = -21
break
}
expected := bunny.reference_state(representative, frames, self.population_seed)
actual := [4]f32{self.field.x[representative], self.field.y[representative], self.field.vx[representative], self.field.vy[representative]}
want := [4]f32{expected.x0, expected.y0, expected.vx0, expected.vy0}
for value, i in actual {
if transmute(u32)value != transmute(u32)want[i] {
result = -21
break
}
}
if result != 0 do break
if !bunny.oracle_instance(stored[start:start+bunny.STRIDE], representative, frames, self.population_seed) {
result = -20
break
}
}
}
if godot.owned_value_destroy(engine_call.api, &owned) != .None do return -12
return result
}
// Bunny_Field_Shutdown retires the retained owners explicitly: the staging
// owner and the pure population. The teardown hook has no authority, so
// cooperative shutdown — the compositor's pattern — is the symmetric cleanup
// this owner pair's lifetime contract requires.
//
// Error codes: -1 invalid call token, -2 facade unavailable, -3 staging
// could not retire, -4 population could not retire.
@(gododin)
bunny_field_shutdown :: proc(self: ^Bunny_Field, call: gd.Main_Call) -> i64 {
if gd.validate_main_call(call) != .None do return -1
if !retire_previous(self, call) do return -2
if !render.multimesh_stream_owned(&self.stream) && self.field == nil do return 0
code := i64(0)
if render.multimesh_stream_owned(&self.stream) {
api, facade_ok := gd.main_facade(call)
if !facade_ok do return -2
if close_error := render.multimesh_stream_close(render.main_call(api), &self.stream); close_error != .None {
code = -3
}
}
if self.field != nil {
if !bunny.field_destroy(&self.field) && code == 0 do code = -4
}
if !bunny.field_destroy(&self.retired_field) && code == 0 do code = -4
if code == 0 {self.capacity = 0; self.frames_submitted = 0}
return code
}
@(gododin)
bunny_field_frames_submitted :: proc(self: ^Bunny_Field) -> i64 {
return self.frames_submitted
}
@(gododin)
bunny_field_count :: proc(self: ^Bunny_Field) -> i64 {
return self.capacity
}
@(gododin)
bunny_field_staging_live :: proc(self: ^Bunny_Field) -> bool {
return render.multimesh_stream_owned(&self.stream)
}
// Bunny_Field_Verify_Buffer checks the GDScript arm's actual x/y/vx/vy/scale
// rows against independent replay. It borrows the packed input only for this
// call. Returns -1 for invalid extent/frame count, -2 for a bitwise mismatch.
@(gododin)
bunny_field_verify_buffer :: proc(self: ^Bunny_Field, values: []f32, frames, seed: i64) -> i64 {
_ = self
if len(values)==0 || len(values)%5!=0 || len(values)/5>bunny.MAX_COUNT || frames<0 || frames>100000 do return -1
for i in 0..<len(values)/5 {
p := bunny.reference_state(i64(i),frames,u32(seed & 0xffffffff))
expected := [5]f32{p.x0,p.y0,p.vx0,p.vy0,p.scale}
for v,j in expected {if transmute(u32)v != transmute(u32)values[i*5+j] do return -2}
}
return 0
}
// Bunny_Field_Verifiable reports whether the current history matches the
// canonical seed/frame replay; returning sliders to canonical does not erase
// earlier custom steps. A resize starts a fresh history.
@(gododin)
bunny_field_verifiable :: proc(self: ^Bunny_Field) -> bool {
return self.field != nil && self.field.profile_uniform && bunny.profile_equal(self.field.profile,bunny.canonical_profile())
}
// A failed staging write can invalidate its view while its packed array remains
// owned. Cleanup follows ownership of either state, not only view validity.
// retire_previous keeps pre-adoption and old active populations in instance
// storage while trying their cleanup, so every later shutdown can reach them.
// The stream's pending retirement lives inside the stream owner now, so this
// handles only the pure population.
retire_previous :: proc(self: ^Bunny_Field, call: gd.Main_Call) -> bool {
_ = call
return bunny.field_destroy(&self.retired_field)
}
// child_by_name resolves one direct child of `node` by the scene's declared
// name through the generated surface alone: the interface's own Latin-1
// StringName constructor, the generated String and NodePath constructors, and
// get_node_or_null. Every intermediate engine value is released on every
// path, including the constructor-slot binds when they fail after an
// allocation. This is a bind-time helper, never a hot path. ok is false when
// the call is stale, any engine step refuses, or the path resolves to
// nothing; the caller's class validation distinguishes a wrong-kind target.
child_by_name :: proc(call: godot.Call, node: ^godot.Node, name: cstring) -> (godot.Node, bool) {
if error := godot.validate_call(call); error != .None do return {}, false
api := call.api
name_sn: raw.StringName
api.string_name_new_latin1(
raw.GDExtensionUninitializedStringNamePtr(rawptr(&name_sn)),
transmute(^i8)name,
0,
)
defer api.string_name_destructor(godot.type_pointer(rawptr(&name_sn)))
if godot.bind_construct_string_from_string_name(api) != .None do return {}, false
text, text_error := godot.construct_string_from_string_name(call, name_sn)
if text_error != .None do return {}, false
if godot.bind_construct_node_path_from_string(api) != .None {
_ = godot.owned_string_destroy(api, &text)
return {}, false
}
path, path_error := godot.construct_node_path_from_string(call, text.value)
text_destroy_error := godot.owned_string_destroy(api, &text)
if path_error != .None || text_destroy_error != .None do return {}, false
defer godot.owned_value_destroy(api, &path)
found, found_error := godot.node_get_node_or_null(call, node, path.value)
if found_error != .None do return {}, false
return found, found.ptr != nil
}