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.
131 lines
7.1 KiB
GDScript
131 lines
7.1 KiB
GDScript
extends SceneTree
|
|
## Paired old/new cohort timing on separate generated large diagnostic saves.
|
|
const E = preload("res://world/owniverse/math/exact.gd")
|
|
var cycles: int = 50
|
|
var repeats: int = 2
|
|
var artifact_prefix: String = "large_star_cohorts"
|
|
|
|
func _initialize(): call_deferred("run")
|
|
|
|
func restore_json_integers(value):
|
|
if value is float and value == floorf(value) and absf(value) <= 9007199254740991: return int(value)
|
|
if value is Dictionary:
|
|
for key in value: value[key] = restore_json_integers(value[key])
|
|
elif value is Array:
|
|
for i in value.size(): value[i] = restore_json_integers(value[i])
|
|
return value
|
|
|
|
func dispose(world):
|
|
for entity in world.catalog.entities.values(): entity.free()
|
|
world.free()
|
|
|
|
func world_from(state: Dictionary, use_cycles: bool):
|
|
var world = load("res://world/owniverse/owniverse.gd").new()
|
|
world.catalog.load_definitions(root.get_node("Global").OwniverseEntities)
|
|
world.state = state.duplicate(true)
|
|
world.speed = 6
|
|
world.batches.cycle_stars = use_cycles
|
|
return world
|
|
|
|
func cohort_count(world) -> int:
|
|
var total: int = 0
|
|
for id in world.state.batches:
|
|
if world.catalog.entities[id].group_as == "Star": total += world.state.batches[id].size()
|
|
return total
|
|
|
|
func make_state(initial: Dictionary, use_cycles: bool) -> Dictionary:
|
|
var world = world_from(initial, use_cycles)
|
|
var anchor: Variant = E.of("1e12")
|
|
var shift: Variant = E.subtract(anchor, world.state.time.datetime)
|
|
for id in world.state.batches:
|
|
for batch: Dictionary in world.state.batches[id]:
|
|
for field in ["created_from", "created_to", "start_year", "end_year", "processed_until"]:
|
|
if batch.has(field): batch[field] = E.add(batch[field], shift)
|
|
for id in world.state.batch_origins:
|
|
world.state.batch_origins[id] = E.add(world.state.batch_origins[id], shift)
|
|
world.state.time = {"datetime": anchor, "year": anchor}
|
|
var physical: Dictionary = world.ledger.physical_amounts(world.state)
|
|
var populations: Dictionary = {}
|
|
for id in world.catalog.entities:
|
|
var entity = world.catalog.entities[id]
|
|
if entity.group_as != "Star" or E.le(entity.lifespan, 0) or E.le(physical.get(id, 0), 0): continue
|
|
world.state.batches[id] = []
|
|
world.state.batch_origins.erase(id)
|
|
var lifespan: Variant = world.batches.effective_lifespan(world.state, id)
|
|
var birth_from: Variant = E.subtract(anchor, lifespan)
|
|
var duration: Variant = E.minimum(lifespan, 2000000)
|
|
world.batches.append_batch(world.state, id, physical[id], birth_from, E.add(birth_from, duration))
|
|
populations[id] = E.fixed(physical[id])
|
|
var payload: Dictionary = {"format_version": 6, "app_version": "0.7.0", "ui": {"current_page": 0}, "state": world.state,
|
|
"simulation": {"active_clock": {}, "requested_clock": {}, "productions": [], "speed": 6}}
|
|
var error: String = root.get_node("SaveManager").validate_data(payload)
|
|
assert(error.is_empty(), error)
|
|
var suffix: String = "cycle" if use_cycles else "legacy"
|
|
var path: String = "res://tests/artifacts/%s_%s.adventure.json" % [artifact_prefix, suffix]
|
|
var output = FileAccess.open(path, FileAccess.WRITE)
|
|
assert(output != null)
|
|
output.store_string(JSON.stringify(E.encode(payload), " ", true, true))
|
|
output.close()
|
|
print("Generated %s: star_types=%d stellar_cohorts=%d populations=%s" % [suffix, populations.size(), cohort_count(world), str(populations)])
|
|
var restored: Dictionary = E.decode(restore_json_integers(JSON.parse_string(FileAccess.get_file_as_string(path))))
|
|
assert(E.eq(restored.state, world.state), "Diagnostic file roundtrip changed initial state")
|
|
dispose(world)
|
|
return restored.state
|
|
|
|
func measure(initial: Dictionary, use_cycles: bool, run_index: int) -> Dictionary:
|
|
var world = world_from(initial, use_cycles)
|
|
world.profile_enabled = true
|
|
var samples: Array = []
|
|
var total_usec: int = 0
|
|
var initial_year: Variant = world.state.time.datetime
|
|
var peak_cohorts: int = cohort_count(world)
|
|
for cycle in cycles:
|
|
var started: int = Time.get_ticks_usec()
|
|
world.evolute(0.2)
|
|
var elapsed: int = Time.get_ticks_usec() - started
|
|
total_usec += elapsed
|
|
samples.append(elapsed / 1000.0)
|
|
peak_cohorts = maxi(peak_cohorts, cohort_count(world))
|
|
var applied: Variant = E.subtract(world.state.time.datetime, initial_year)
|
|
assert(E.eq(applied, cycles * 20000))
|
|
var error: String = root.get_node("SaveManager").validate_data({"format_version": 6, "app_version": "0.7.0", "ui": {"current_page": 0}, "state": world.state})
|
|
assert(error.is_empty(), error)
|
|
samples.sort()
|
|
var result: Dictionary = {"model": "cycle" if use_cycles else "legacy", "repeat": run_index, "cycles": cycles,
|
|
"mean_ms": total_usec / (1000.0 * cycles), "min_ms": samples.front(), "max_ms": samples.back(), "p95_ms": samples[int(ceil(samples.size() * 0.95)) - 1],
|
|
"cycles_per_second": cycles * 1000000.0 / total_usec, "throughput": cycles * 200000.0 / total_usec,
|
|
"game_years": E.fixed(applied), "peak_stellar_cohorts": peak_cohorts, "final_stellar_cohorts": cohort_count(world), "profile": world.profile.duplicate(true)}
|
|
print("PAIRED_RESULT ", JSON.stringify(result))
|
|
dispose(world)
|
|
return result
|
|
|
|
func run():
|
|
for arg in OS.get_cmdline_user_args():
|
|
if arg.begins_with("cycles="): cycles = maxi(1, arg.trim_prefix("cycles=").to_int())
|
|
if arg.begins_with("repeats="): repeats = maxi(1, arg.trim_prefix("repeats=").to_int())
|
|
var raw = JSON.parse_string(FileAccess.get_file_as_string("res://tests/artifacts/all_objects_speed6.adventure.json"))
|
|
var payload: Dictionary = E.decode(restore_json_integers(raw))
|
|
assert(root.get_node("SaveManager").validate_data(payload).is_empty())
|
|
var legacy: Dictionary = make_state(payload.state, false)
|
|
var current: Dictionary = make_state(payload.state, true)
|
|
var reference = world_from(legacy, false)
|
|
var candidate = world_from(current, true)
|
|
assert(E.eq(reference.ledger.physical_amounts(reference.state), candidate.ledger.physical_amounts(candidate.state)))
|
|
assert(E.eq(reference.state.ethics, candidate.state.ethics))
|
|
assert(cohort_count(candidate) <= cohort_count(reference), "Dynamic widening increased the fixture cohort count")
|
|
dispose(reference)
|
|
dispose(candidate)
|
|
if OS.get_cmdline_user_args().has("generate_only"):
|
|
print("Generated states validate and survive disk roundtrip; physical amounts and ethics match")
|
|
quit()
|
|
return
|
|
var results: Array = []
|
|
for repeat_index in repeats:
|
|
var order: Array = [false, true] if repeat_index % 2 == 0 else [true, false]
|
|
for use_cycles in order:
|
|
results.append(measure(current if use_cycles else legacy, use_cycles, repeat_index + 1))
|
|
var output = FileAccess.open("res://tests/artifacts/%s_results.json" % artifact_prefix, FileAccess.WRITE)
|
|
output.store_string(JSON.stringify({"delta_seconds": 0.2, "speed": 6, "cycles_per_run": cycles, "repeats": repeats, "profile_enabled": true, "runs": results}, " "))
|
|
output.close()
|
|
quit()
|