Files

140 lines
7.6 KiB
GDScript
Raw Permalink Normal View History

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(0.2, self)
check(sliced.frame_busy, "Heavy transaction yields before publishing")
check(E.eq(sliced.state.time.year, 0), "Published clock stays at completed transaction")
check(E.eq(sliced.display_clock().year, Clock.advance({"datetime": 0, "year": 0}, 0.2, sliced.speed).year), "Display clock advances independently of the heavy transaction")
check(sliced.request_production("H", 1, true), "Production requested during a frame is queued")
check(sliced.frame_checkpoint().productions.size() == 1, "Queued production remains part of the save checkpoint")
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 or sliced.production_queue_busy: await process_frame
direct.evolute(0.2)
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)
check(not sliced.frame_busy, "Speed change does not bypass real-time cadence")
sliced.evolute_frame(0.189, self)
var second_clock: Dictionary = Clock.advance(Clock.advance(requested, 0.001, 3), 0.189, 3)
var active: Dictionary = sliced.frame_active_clock.duplicate()
while sliced.frame_busy or sliced.production_queue_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(0.2, 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 or resumed.production_queue_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.2, 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)
for selected_speed in range(1, 7):
var scheduled = fixture()
scheduled.speed = selected_speed
scheduled.evolute_frame(0.1, self)
check(not scheduled.frame_busy and scheduled.frame_commits == 0, "Wait for a full step at speed %d" % selected_speed)
scheduled.evolute_frame(0.1, self)
while scheduled.frame_busy: await process_frame
var cadence_clock: Dictionary = Clock.advance({"datetime": 0, "year": 0}, 0.2, selected_speed)
check(scheduled.frame_commits == 1 and E.eq(scheduled.state.time, cadence_clock), "One complete 0.2 second cycle at speed %d" % selected_speed)
scheduled.evolute_frame(0.0, self)
check(not scheduled.frame_busy and scheduled.frame_commits == 1, "No extra cycle without elapsed time")
dispose(scheduled)
var behind = fixture()
behind.evolute_frame(1.0, self)
while behind.frame_busy: await process_frame
check(E.eq(behind.state.time.datetime, 100000) and E.eq(behind.state.time, behind.frame_clock), "Delayed start consumes all elapsed time in one cycle")
behind.evolute_frame(0.0, self)
check(behind.frame_commits == 1 and not behind.frame_busy, "Consumed interval cannot be processed twice")
var next_interval: float = behind.frame_step_seconds + 0.03
behind.evolute_frame(next_interval, self)
var remained_busy: bool = behind.frame_busy
if remained_busy: behind.evolute_frame(0.53, self)
var pending_clock: Dictionary = behind.frame_clock.duplicate()
if remained_busy:
var expected_active: Dictionary = Clock.advance({"datetime": 100000, "year": 100000}, next_interval, behind.speed)
check(E.eq(behind.frame_active_clock, expected_active), "Busy transaction reserves its original target")
while behind.frame_busy: await process_frame
if remained_busy:
check(E.eq(behind.state.time, behind.frame_active_clock), "Busy transaction keeps its original target")
behind.evolute_frame(0.0, self)
while behind.frame_busy: await process_frame
check(E.eq(behind.state.time, pending_clock), "Next cycle consumes all time accumulated during computation")
else:
check(E.eq(behind.state.time, pending_clock), "Completed transaction publishes the full requested interval")
dispose(behind)
print("Frame evolution: %d checks, %d failures" % [checks, failures])
quit(1 if failures else 0)