Linked repository source
fixtures/native_components/reload.gd
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
@tool
extends EditorPlugin
var nodes: Array[Node] = []
var resources: Array[Resource] = []
var identities := PackedInt64Array()
var retained_strings: Array[String] = []
var last_revision := 0
var signals_seen := 0
var last_signal := 0
var layout := 0
var root: Node2D
var started := false
# Phase 15 baseline consumers: a baked-asset-shaped grid, a per-revision bulk
# input, staged/classify expectations, and the background verification state.
var baked_grid := PackedByteArray()
var baked_digest := 0
const BAKED_WIDTH := 64
const BAKED_HEIGHT := 48
const CELL := 16.0
var bg_pending_id := 0
var bg_pending_ticks := 0
func _enter_tree() -> void:
call_deferred("start")
func require(condition: bool, message: String) -> void:
if not condition:
push_error("GODODIN_RELOAD_LIFETIME_FAIL " + message)
get_tree().quit(1)
assert(condition, message)
# The GDScript oracle for the native mask summary: FNV-1a folded exactly as
# signed 64-bit, so the basis is written in its signed form and the multiply
# wraps modulo 2^64 like Odin's u64 arithmetic.
func mask_digest(bytes: PackedByteArray) -> int:
var accumulator := -3750763034362895579 # 0xcbf29ce484222325
for byte in bytes:
accumulator = (accumulator ^ byte) * 0x100000001b3
return accumulator & 0x3fffffffffffffff
func start() -> void:
await get_tree().create_timer(3.0).timeout
root = Node2D.new()
root.name = "UnsavedReloadScene"
EditorInterface.add_root_node(root)
var count := int(OS.get_environment("GODODIN_RELOAD_TEST_COUNT"))
for index in count:
var node: Node = ClassDB.instantiate("ReloadNode")
var settings: Resource = ClassDB.instantiate("ReloadSettings")
require(node != null and settings != null, "native classes unavailable")
root.add_child(node)
node.owner = root
node.set("seed", 400 + index)
node.set("label", "node-%d" % index)
settings.set("seed", 800 + index)
settings.set("label", "resource-%d" % index)
node.connect("pulse", on_pulse)
nodes.append(node)
resources.append(settings)
identities.append(node.get_instance_id())
identities.append(settings.get_instance_id())
# The baked-asset consumer: one immutable grid whose digest the native bulk
# input must reproduce every revision, the way an exported app consumes a
# committed baked asset.
baked_grid.resize(BAKED_WIDTH * BAKED_HEIGHT)
for y in BAKED_HEIGHT:
for x in BAKED_WIDTH:
baked_grid[y * BAKED_WIDTH + x] = 255 if ((x * 13 + y * 29) % 97) < 40 else 0
baked_digest = mask_digest(baked_grid)
EditorInterface.mark_scene_as_unsaved()
started = true
set_process(true)
func on_pulse(revision: int) -> void:
signals_seen += 1
last_signal = revision
func _process(_delta: float) -> void:
if not started or nodes.is_empty():
return
if FileAccess.file_exists(OS.get_environment("GODODIN_RELOAD_TEST_STOP")):
set_process(false)
# Free through Godot while the current native image is still active.
for node in nodes:
node.free()
nodes.clear()
resources.clear()
get_tree().quit()
return
# A submitted background computation completes on a later tick; finishing
# it here leaves one unconsumed record in the handoff, which the next
# reload's teardown must drain.
if bg_pending_id != 0:
bg_pending_ticks += 1
var status := int(nodes[0].call("bg_status"))
if status == 2:
var fingerprint: int = nodes[0].call("bg_poll")
require(fingerprint != 0, "a completed background record polled empty")
# Leave a fresh unconsumed submission outstanding across the next
# reload; its teardown cancels, joins, and drains it.
var outstanding: int = nodes[0].call("bg_submit", 0x5eed + last_revision, 250_000_000)
require(outstanding > 0, "the outstanding background submit was rejected")
bg_pending_id = 0
elif status == 3 or status == 4 or bg_pending_ticks > 3000:
require(false, "the background computation never completed (status %d)" % status)
return
if not nodes[0].has_method("revision"):
require(false, "native class was not reconstructed")
set_process(false)
return
var revision := int(nodes[0].call("revision"))
if revision == last_revision:
return
require(revision == last_revision + 1, "a superseded build reached native activation")
for index in nodes.size():
var node := nodes[index]
var settings := resources[index]
require(node.get_instance_id() == identities[index*2] and settings.get_instance_id() == identities[index*2+1], "Godot identity changed")
require(node.get("seed") == 400 + index and settings.get("seed") == 800 + index, "stored values changed")
require(node.get("label") == "node-%d" % index and settings.get("label") == "resource-%d" % index, "owned string changed")
require(node.call("private_value") == 1000 + revision, "private state was not reconstructed")
require(node.call("cache") == "cache-%d" % revision, "allocator-backed state was not reconstructed")
require(node.call("literal") == "literal-%d" % revision, "literal came from old image")
var previous_signals := signals_seen
require(node.call("ping") and signals_seen == previous_signals + 1 and last_signal == revision, "native signal connection is stale")
if layout == 0:
layout = node.call("layout")
require(node.call("layout") == layout, "layout fixture changed size/alignment")
for index in retained_strings.size():
require(retained_strings[index] == "literal-%d" % (index + 1), "a surviving Godot String retained image storage")
retained_strings.append(nodes[0].call("literal"))
require(root.scene_file_path.is_empty() and EditorInterface.get_edited_scene_root() == root, "unsaved scene changed")
# --- Phase 15 consumers, verified against GDScript oracles every revision ---
var probe := nodes[0]
# The reloaded image's own worker code answers, never a stale entry.
require(int(probe.call("bg_version")) == revision, "the background worker came from an old image")
# A previous reload with an outstanding submission drained it: the fresh
# instance starts idle with an empty handoff.
require(int(probe.call("bg_status")) == 0 and int(probe.call("bg_poll")) == 0,
"pre-reload background residue survived the reload")
# Baked-asset consumer: the immutable grid digests identically.
require(int(probe.call("mask_summary", baked_grid, BAKED_WIDTH, BAKED_HEIGHT)) == baked_digest,
"the baked grid digest diverged")
# Bulk input, revision-derived.
var bulk := PackedByteArray()
bulk.resize(32 * 24)
for index in bulk.size():
bulk[index] = (index * 5 + revision * 3) & 0xff
require(int(probe.call("mask_summary", bulk, 32, 24)) == mask_digest(bulk),
"the borrowed packed input diverged")
# Staged bulk result, copied by the Host from the instance's own buffer.
var staged: PackedByteArray = probe.call("stage_flags", 512)
require(staged.size() == 512, "the staged result carried the wrong count")
for index in staged.size():
require(staged[index] == ((index * 7 + 11) & 0xff) ^ (revision & 0xff),
"the staged result diverged at %d" % index)
# Typed-vector consumer over borrowed packed arrays.
var positions := PackedVector3Array()
var radii := PackedFloat32Array()
for index in 16:
positions.append(Vector3(float((index * 17) % 128), float(index * 3), float((index * 31) % 96)))
radii.append(4.0 + index)
var flags: PackedByteArray = probe.call("classify", positions, radii, Vector3.ZERO, CELL)
require(flags.size() == 16, "the classify result carried the wrong count")
for index in 16:
var pair := int(positions[index].x / CELL) + int(positions[index].z / CELL)
require(flags[index] == (255 if pair % 2 == 0 else 0), "the classify result diverged at %d" % index)
last_revision = revision
var observation := {"revision": revision, "editor_pid": OS.get_process_id(), "nodes": nodes.size(), "resources": resources.size(), "layout": layout, "signals_seen": signals_seen, "host": OS.get_environment("GODODIN_NATIVE_ACTIVE_HOST")}
var file := FileAccess.open(OS.get_environment("GODODIN_RELOAD_TEST_OBSERVATION"), FileAccess.WRITE)
file.store_string(JSON.stringify(observation))
file.close()
print("GODODIN_RELOAD_LIFETIME_OBSERVED " + JSON.stringify(observation))
# Kick the background verification for this revision; completion (and the
# next reload's drain) is handled on later ticks at the top of _process.
var submitted: int = probe.call("bg_submit", 0x51b5 + revision, 30_000_000)
require(submitted > 0, "the background submit was rejected")
bg_pending_id = submitted
bg_pending_ticks = 0