3fcc7211ce
- Added `civilization_flow.gd` to manage continuous expected flows and resource allocation within civilizations. - Introduced `allocate_profiled` and `whole_quotas` functions in `quantity_math.gd` for precise stock allocation based on float shares. - Enhanced `credit` logic in `object_ledger.gd` to handle production remainders more effectively. - Updated `append_batch` in `object_batches.gd` to prevent batch creation for invalid IDs. - Introduced `ratio_approx` function in `exact.gd` for better handling of float profiles. - Added a benchmark script `benchmark_civilization_flows.gd` to evaluate the performance of civilization flow mechanics. - Refactored various functions to improve clarity and efficiency in handling resource flows and allocations.
122 lines
6.1 KiB
GDScript
122 lines
6.1 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(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")
|
|
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(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)
|
|
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(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: 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, 20000) and E.eq(behind.frame_clock.datetime, 100000), "Debt keeps the fixed step and preserves all requested time")
|
|
for cycle in 4:
|
|
behind.evolute_frame(0.0, self)
|
|
while behind.frame_busy: await process_frame
|
|
check(behind.frame_commits == 5 and E.eq(behind.state.time, behind.frame_clock), "One second is processed in exactly five full cycles")
|
|
dispose(behind)
|
|
print("Frame evolution: %d checks, %d failures" % [checks, failures])
|
|
quit(1 if failures else 0)
|