refactor(M03): extract M2 unique placement registry
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
extends SceneTree
|
||||
|
||||
const STREAMING_WORLD_LOADER := preload("res://src/scenes/streaming/streaming_world_loader.gd")
|
||||
const M2_UNIQUE_PLACEMENT_REGISTRY := preload(
|
||||
"res://src/render/m2/m2_unique_placement_registry.gd"
|
||||
)
|
||||
|
||||
var _failures := 0
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
var loader = STREAMING_WORLD_LOADER.new()
|
||||
var registry: RefCounted = M2_UNIQUE_PLACEMENT_REGISTRY.new()
|
||||
var placement := {
|
||||
"name_id": 0,
|
||||
"unique_id": 11785,
|
||||
@@ -15,30 +17,24 @@ func _initialize() -> void:
|
||||
"scale": 1.0,
|
||||
}
|
||||
|
||||
var first: Dictionary = loader.call("_reserve_tile_m2_placements", "30_49", [placement])
|
||||
var first: Dictionary = registry.call("reserve", "30_49", [placement])
|
||||
_expect((first.get("placements", []) as Array).size() == 1, "first tile should reserve unique M2 placement")
|
||||
_expect((first.get("unique_keys", []) as Array).has("uid:11785"), "first tile should own uid:11785")
|
||||
_expect((first.get("skipped_unique_keys", []) as Array).is_empty(), "first tile should not skip uid:11785")
|
||||
|
||||
var duplicate: Dictionary = loader.call("_reserve_tile_m2_placements", "31_49", [placement])
|
||||
var duplicate: Dictionary = registry.call("reserve", "31_49", [placement])
|
||||
_expect((duplicate.get("placements", []) as Array).is_empty(), "second tile should skip duplicate uid:11785")
|
||||
_expect((duplicate.get("skipped_unique_keys", []) as Array).has("uid:11785"), "second tile should remember skipped uid:11785")
|
||||
|
||||
loader.call("_release_tile_m2_unique_keys", {
|
||||
"key": "30_49",
|
||||
"m2_unique_keys": ["uid:11785"],
|
||||
"m2_skipped_unique_keys": [],
|
||||
}, false)
|
||||
var after_release: Dictionary = loader.call("_reserve_tile_m2_placements", "31_49", [placement])
|
||||
registry.call("release", "30_49", ["uid:11785"])
|
||||
var after_release: Dictionary = registry.call("reserve", "31_49", [placement])
|
||||
_expect((after_release.get("placements", []) as Array).size() == 1, "second tile should reserve uid:11785 after owner release")
|
||||
_expect((after_release.get("unique_keys", []) as Array).has("uid:11785"), "second tile should own uid:11785 after release")
|
||||
|
||||
var same_tile_loader = STREAMING_WORLD_LOADER.new()
|
||||
var same_tile_duplicate: Dictionary = same_tile_loader.call("_reserve_tile_m2_placements", "31_49", [placement, placement])
|
||||
var same_tile_registry: RefCounted = M2_UNIQUE_PLACEMENT_REGISTRY.new()
|
||||
var same_tile_duplicate: Dictionary = same_tile_registry.call("reserve", "31_49", [placement, placement])
|
||||
_expect((same_tile_duplicate.get("placements", []) as Array).size() == 1, "same tile should keep only one copy of duplicate uid:11785")
|
||||
|
||||
loader.free()
|
||||
same_tile_loader.free()
|
||||
if _failures > 0:
|
||||
push_error("M2 unique dedupe verification failed: %d issue(s)" % _failures)
|
||||
quit(1)
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
extends SceneTree
|
||||
|
||||
## Asset-free ownership, lifecycle, dependency and timing regression for M2 IDs.
|
||||
|
||||
const REGISTRY_SCRIPT := preload("res://src/render/m2/m2_unique_placement_registry.gd")
|
||||
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
var failures: Array[String] = []
|
||||
_verify_unkeyed_and_invalid_placements(failures)
|
||||
_verify_same_tile_and_cross_tile_deduplication(failures)
|
||||
_verify_owner_checked_release_and_retry(failures)
|
||||
_verify_clear_and_detached_diagnostics(failures)
|
||||
_verify_loader_boundary(failures)
|
||||
var elapsed_milliseconds := _verify_bounded_timing(failures)
|
||||
if not failures.is_empty():
|
||||
for failure in failures:
|
||||
push_error("M2_UNIQUE_PLACEMENT_REGISTRY: %s" % failure)
|
||||
quit(1)
|
||||
return
|
||||
print(
|
||||
"M2_UNIQUE_PLACEMENT_REGISTRY PASS cases=10 iterations=100 elapsed_ms=%.3f"
|
||||
% elapsed_milliseconds
|
||||
)
|
||||
quit(0)
|
||||
|
||||
|
||||
func _verify_unkeyed_and_invalid_placements(failures: Array[String]) -> void:
|
||||
var registry: RefCounted = REGISTRY_SCRIPT.new()
|
||||
var result: Dictionary = registry.call("reserve", "tile-a", [
|
||||
{"unique_id": -1},
|
||||
{"unique_id": 0},
|
||||
{"name_id": 7},
|
||||
"invalid",
|
||||
])
|
||||
_expect_equal((result["placements"] as Array).size(), 3, "unkeyed placements pass", failures)
|
||||
_expect_true((result["unique_keys"] as Array).is_empty(), "unkeyed placements not owned", failures)
|
||||
_expect_equal(int(registry.call("active_count")), 0, "unkeyed active count", failures)
|
||||
|
||||
|
||||
func _verify_same_tile_and_cross_tile_deduplication(failures: Array[String]) -> void:
|
||||
var registry: RefCounted = REGISTRY_SCRIPT.new()
|
||||
var placement := {"unique_id": 11785, "name_id": 0}
|
||||
var first: Dictionary = registry.call("reserve", "30_49", [placement, placement])
|
||||
_expect_equal((first["placements"] as Array).size(), 1, "same call duplicate filtered", failures)
|
||||
_expect_string_array((first["unique_keys"] as Array), ["uid:11785"], "first owner key", failures)
|
||||
var same_owner_retry: Dictionary = registry.call("reserve", "30_49", [placement])
|
||||
_expect_equal((same_owner_retry["placements"] as Array).size(), 1, "same owner later call passes", failures)
|
||||
var competing: Dictionary = registry.call("reserve", "31_49", [placement, placement])
|
||||
_expect_true((competing["placements"] as Array).is_empty(), "competing tile filtered", failures)
|
||||
_expect_string_array(
|
||||
competing["skipped_unique_keys"] as Array,
|
||||
["uid:11785"],
|
||||
"competing skip recorded once",
|
||||
failures
|
||||
)
|
||||
|
||||
|
||||
func _verify_owner_checked_release_and_retry(failures: Array[String]) -> void:
|
||||
var registry: RefCounted = REGISTRY_SCRIPT.new()
|
||||
var placement := {"unique_id": 44}
|
||||
registry.call("reserve", "owner", [placement])
|
||||
_expect_true(
|
||||
(registry.call("release", "other", ["uid:44"]) as Array).is_empty(),
|
||||
"non-owner release ignored",
|
||||
failures
|
||||
)
|
||||
_expect_equal(int(registry.call("active_count")), 1, "non-owner keeps reservation", failures)
|
||||
var released: Array = registry.call("release", "owner", ["", "uid:44", "uid:44"])
|
||||
_expect_string_array(released, ["uid:44"], "owner release order and dedupe", failures)
|
||||
var retried: Dictionary = registry.call("reserve", "other", [placement])
|
||||
_expect_equal((retried["placements"] as Array).size(), 1, "waiting tile reserves after release", failures)
|
||||
|
||||
|
||||
func _verify_clear_and_detached_diagnostics(failures: Array[String]) -> void:
|
||||
var registry: RefCounted = REGISTRY_SCRIPT.new()
|
||||
registry.call("reserve", "tile-b", [{"unique_id": 20}])
|
||||
registry.call("reserve", "tile-a", [{"unique_id": 10}])
|
||||
var snapshot: Dictionary = registry.call("diagnostic_snapshot")
|
||||
var owners: Array = snapshot["owners"]
|
||||
_expect_equal(int(snapshot["active_count"]), 2, "diagnostic count", failures)
|
||||
_expect_string_equal(String(owners[0]["unique_key"]), "uid:10", "diagnostic sorted", failures)
|
||||
owners[0]["tile_key"] = "mutated"
|
||||
var fresh_snapshot: Dictionary = registry.call("diagnostic_snapshot")
|
||||
_expect_string_equal(
|
||||
String((fresh_snapshot["owners"] as Array)[0]["tile_key"]),
|
||||
"tile-a",
|
||||
"diagnostics detached",
|
||||
failures
|
||||
)
|
||||
registry.call("clear")
|
||||
registry.call("clear")
|
||||
_expect_equal(int(registry.call("active_count")), 0, "clear idempotent", failures)
|
||||
|
||||
|
||||
func _verify_loader_boundary(failures: Array[String]) -> void:
|
||||
var source := _read_text(LOADER_PATH, failures)
|
||||
_expect_true(source.contains("M2_UNIQUE_PLACEMENT_REGISTRY_SCRIPT.new()"), "loader composes registry", failures)
|
||||
_expect_true(source.contains("_m2_unique_placement_registry.reserve("), "loader delegates reserve", failures)
|
||||
_expect_true(source.contains("_m2_unique_placement_registry.release("), "loader delegates release", failures)
|
||||
_expect_true(source.contains("_m2_unique_placement_registry.clear()"), "loader delegates clear", failures)
|
||||
_expect_true(not source.contains("var _m2_unique_registry"), "loader omits registry dictionary", failures)
|
||||
_expect_true(not source.contains("func _m2_unique_key("), "loader omits key formula", failures)
|
||||
|
||||
|
||||
func _verify_bounded_timing(failures: Array[String]) -> float:
|
||||
var placements: Array = []
|
||||
for placement_index in range(256):
|
||||
placements.append({"unique_id": placement_index + 1})
|
||||
var registry: RefCounted = REGISTRY_SCRIPT.new()
|
||||
var started_microseconds := Time.get_ticks_usec()
|
||||
for _iteration in range(100):
|
||||
var reservation: Dictionary = registry.call("reserve", "owner", placements)
|
||||
registry.call("release", "owner", reservation["unique_keys"])
|
||||
var elapsed_milliseconds := float(Time.get_ticks_usec() - started_microseconds) / 1000.0
|
||||
_expect_true(elapsed_milliseconds < 1000.0, "100 reserve/release cycles remain bounded", failures)
|
||||
return elapsed_milliseconds
|
||||
|
||||
|
||||
func _read_text(path: String, failures: Array[String]) -> String:
|
||||
var file := FileAccess.open(path, FileAccess.READ)
|
||||
if file == null:
|
||||
failures.append("cannot open %s" % path)
|
||||
return ""
|
||||
return file.get_as_text()
|
||||
|
||||
|
||||
func _expect_equal(actual_value: int, expected_value: int, label: String, failures: Array[String]) -> void:
|
||||
if actual_value != expected_value:
|
||||
failures.append("%s expected %d, got %d" % [label, expected_value, actual_value])
|
||||
|
||||
|
||||
func _expect_string_equal(
|
||||
actual_value: String,
|
||||
expected_value: String,
|
||||
label: String,
|
||||
failures: Array[String]
|
||||
) -> void:
|
||||
if actual_value != expected_value:
|
||||
failures.append("%s expected %s, got %s" % [label, expected_value, actual_value])
|
||||
|
||||
|
||||
func _expect_string_array(
|
||||
actual_values: Array,
|
||||
expected_values: Array,
|
||||
label: String,
|
||||
failures: Array[String]
|
||||
) -> void:
|
||||
if actual_values != expected_values:
|
||||
failures.append("%s expected %s, got %s" % [label, expected_values, actual_values])
|
||||
|
||||
|
||||
func _expect_true(actual_value: bool, label: String, failures: Array[String]) -> void:
|
||||
if not actual_value:
|
||||
failures.append("%s expected true" % label)
|
||||
@@ -0,0 +1 @@
|
||||
uid://inw4fft20be0
|
||||
Reference in New Issue
Block a user