c4e7302567
- Introduced `civilization_benchmark.gd` and `legacy_quantity_math.gd` for benchmarking and legacy quantity calculations. - Created `test_dynamic_math.gd`, `test_frame_evolution.gd`, and `test_sparse_batches.gd` to validate mathematical operations and frame evolution logic. - Implemented `frame_budget.gd` to manage cooperative work on the main thread, ensuring no partially calculated state is published. - Added UID files for all new scripts to maintain unique identifiers.
101 lines
4.8 KiB
GDScript
101 lines
4.8 KiB
GDScript
extends SceneTree
|
|
const E = preload("res://world/owniverse/math/exact.gd")
|
|
const Clock = preload("res://world/owniverse/simulation_time.gd")
|
|
const Fixture = preload("res://tests/fixtures/civilization_benchmark.gd")
|
|
var checks: int = 0
|
|
var failures: int = 0
|
|
|
|
func _initialize(): call_deferred("run")
|
|
func check(condition: bool, message: String):
|
|
checks += 1
|
|
if not condition:
|
|
failures += 1
|
|
push_error(message)
|
|
func fixture():
|
|
var world = load("res://world/owniverse/owniverse.gd").new()
|
|
world.catalog.load_definitions(root.get_node("Global").OwniverseEntities)
|
|
Fixture.seed(world, root.get_node("Global"))
|
|
return world
|
|
func dispose(world):
|
|
for entity in world.catalog.entities.values(): entity.free()
|
|
world.free()
|
|
|
|
func run():
|
|
Engine.max_fps = 0
|
|
var direct = fixture()
|
|
var sliced = fixture()
|
|
for delta in [1.0 / 60.0, 0.133333333333]:
|
|
direct.evolute(delta)
|
|
var clock: Dictionary = Clock.advance(sliced.state.time, delta, sliced.speed)
|
|
var budget = load("res://world/owniverse/frame_budget.gd").new(self, 1)
|
|
await sliced.evolution.evolute_to_datetime(clock.datetime, clock, budget)
|
|
check(E.eq(direct.state, sliced.state), "Sliced transaction exactly matches uninterrupted evolution")
|
|
dispose(direct)
|
|
dispose(sliced)
|
|
direct = fixture()
|
|
sliced = fixture()
|
|
var initial_h: Variant = sliced.state.amount.H
|
|
sliced.evolute_frame(1.0 / 60.0, self)
|
|
check(sliced.frame_busy, "Heavy transaction yields before publishing")
|
|
check(E.eq(sliced.state.time.year, 0), "Published clock stays at completed transaction")
|
|
sliced.request_production("H", 1, true)
|
|
check(E.eq(sliced.state.amount.H, initial_h), "Player action waits for transaction commit")
|
|
sliced.evolute_frame(0.01, self)
|
|
var requested: Dictionary = sliced.frame_clock.duplicate()
|
|
sliced.speed = 3
|
|
check(sliced.speed == 3 and sliced.state.speed == 6, "Speed change is visible and cannot mutate an unfinished transaction")
|
|
while sliced.frame_busy: await process_frame
|
|
direct.evolute(1.0 / 60.0)
|
|
direct.production.produce_hs("H", 1)
|
|
direct.speed = 3
|
|
check(E.eq(direct.state, sliced.state), "Queued action and speed change survive publication")
|
|
sliced.evolute_frame(0.001, self)
|
|
var second_clock: Dictionary = Clock.advance(requested, 0.001, 3)
|
|
var active: Dictionary = sliced.frame_active_clock.duplicate()
|
|
while sliced.frame_busy: await process_frame
|
|
direct.evolution.evolute_to_datetime(active.datetime, active)
|
|
check(E.eq(direct.state, sliced.state), "Bounded frame transaction matches its exact target clock")
|
|
check(E.eq(sliced.frame_clock, second_clock), "All elapsed time during a busy frame remains in the requested clock")
|
|
check(sliced.frame_commits == 2, "Only complete simulation states are committed")
|
|
dispose(direct)
|
|
dispose(sliced)
|
|
var original = fixture()
|
|
original.evolute_frame(1.0 / 60.0, self)
|
|
original.request_production("H", 123, true)
|
|
original.evolute_frame(0.01, self)
|
|
original.speed = 3
|
|
var checkpoint: Dictionary = E.decode(E.encode(original.frame_checkpoint()))
|
|
var published: Dictionary = E.decode(E.encode(original.state))
|
|
var expected_clock: Dictionary = original.frame_active_clock.duplicate()
|
|
dispose(original)
|
|
for frame in 3: await process_frame
|
|
var resumed = fixture()
|
|
var expected = fixture()
|
|
resumed.state = published.duplicate(true)
|
|
expected.state = published.duplicate(true)
|
|
resumed.restore_frame_checkpoint(checkpoint)
|
|
resumed.evolute_frame(0.0, self)
|
|
while resumed.frame_busy: await process_frame
|
|
expected.evolution.evolute_to_datetime(expected_clock.datetime, expected_clock)
|
|
expected.production.produce_hs("H", 123)
|
|
expected.speed = 3
|
|
check(E.eq(expected.state, resumed.state), "Checkpoint recomputes interrupted transaction and preserves queued production")
|
|
check(E.eq(resumed.frame_clock, checkpoint.requested_clock), "Checkpoint preserves all requested elapsed time")
|
|
dispose(resumed)
|
|
dispose(expected)
|
|
var cancelled = fixture()
|
|
cancelled.evolute_frame(0.133333333333, self)
|
|
check(cancelled.frame_busy, "Cancellation fixture has suspended work")
|
|
var budget_ref: RefCounted = cancelled.frame_budget
|
|
dispose(cancelled)
|
|
for frame in 3: await process_frame
|
|
check(budget_ref.cancelled, "Resetting the world cancels suspended work without publishing")
|
|
var invalid = fixture()
|
|
var before: Dictionary = invalid.state.duplicate(true)
|
|
invalid.evolute_frame(1e308, self)
|
|
invalid.evolute(1e308)
|
|
check(not invalid.frame_busy and invalid.frame_clock.is_empty() and E.eq(before, invalid.state), "Overflowing elapsed float cannot enter either clock API")
|
|
dispose(invalid)
|
|
print("Frame evolution: %d checks, %d failures" % [checks, failures])
|
|
quit(1 if failures else 0)
|