02c9843e1b
- Implemented runtime-only adaptive quality controller to optimize simulation performance based on CPU time. - Added adaptive quality thresholds and recovery cycles to dynamically adjust simulation fidelity. - Introduced single-step solving method for civilization transitions to improve performance under high loads. - Updated benchmark scripts to include speed parameter for testing various simulation speeds. - Added tests for adaptive quality adjustments and their effects on civilization flow calculations. - Documented results of optimization attempts and their impact on performance metrics.
179 lines
7.7 KiB
GDScript
179 lines
7.7 KiB
GDScript
extends Node
|
|
class_name Owniverse
|
|
const E = preload("res://world/owniverse/math/exact.gd")
|
|
## Owns the state and modules. Rules live in the modules below.
|
|
const SimulationTime = preload("res://world/owniverse/simulation_time.gd")
|
|
const WorldParameters = preload("res://world/owniverse/world_parameters.gd")
|
|
var catalog = preload("res://world/owniverse/entity_catalog.gd").new()
|
|
var presentation = preload("res://world/owniverse/item_presentation.gd").new()
|
|
var batches = preload("res://world/owniverse/object_batches.gd").new(self)
|
|
var ledger = preload("res://world/owniverse/object_ledger.gd").new(self)
|
|
var production = preload("res://world/owniverse/manual_production.gd").new(self)
|
|
var automatic = preload("res://world/owniverse/automatic_production.gd").new(self)
|
|
var stellar = preload("res://world/owniverse/stellar_rules.gd").new(self)
|
|
var life = preload("res://world/owniverse/life_evolution.gd").new(self)
|
|
var environment = preload("res://world/owniverse/universe_environment.gd").new(self)
|
|
var evolution = preload("res://world/owniverse/evolution_coordinator.gd").new(self)
|
|
var civilization_flow = preload("res://world/owniverse/civilization_flow.gd").new(self)
|
|
var profile_enabled: bool = false
|
|
var profile: Dictionary = {}
|
|
## Runtime-only quality controller. It is deliberately not serialized.
|
|
var adaptive_quality: int = 0
|
|
var adaptive_cycle_ema_ms: float = 0.0
|
|
var adaptive_recovery_cycles: int = 0
|
|
var adaptive_generation_phase: int = 0
|
|
|
|
func update_adaptive_quality(cycle_usec: int) -> void:
|
|
var cycle_ms: float = float(cycle_usec) / 1000.0
|
|
adaptive_cycle_ema_ms = cycle_ms if adaptive_cycle_ema_ms <= 0.0 else adaptive_cycle_ema_ms * 0.8 + cycle_ms * 0.2
|
|
if adaptive_cycle_ema_ms > 300.0:
|
|
adaptive_quality = 3
|
|
adaptive_recovery_cycles = 0
|
|
elif adaptive_quality >= 2 and adaptive_cycle_ema_ms > 200.0:
|
|
adaptive_quality = 3
|
|
adaptive_recovery_cycles = 0
|
|
elif adaptive_cycle_ema_ms > 220.0:
|
|
adaptive_quality = maxi(adaptive_quality, 2)
|
|
adaptive_recovery_cycles = 0
|
|
elif adaptive_cycle_ema_ms > 200.0:
|
|
adaptive_quality = maxi(adaptive_quality, 1)
|
|
adaptive_recovery_cycles = 0
|
|
elif adaptive_cycle_ema_ms < 140.0:
|
|
adaptive_recovery_cycles += 1
|
|
if adaptive_recovery_cycles >= 30:
|
|
adaptive_quality = maxi(0, adaptive_quality - 1)
|
|
adaptive_recovery_cycles = 0
|
|
else:
|
|
adaptive_recovery_cycles = 0
|
|
if adaptive_quality >= 3:
|
|
adaptive_generation_phase = 1 - adaptive_generation_phase
|
|
|
|
func adaptive_timer_usec() -> int:
|
|
return Time.get_ticks_usec() - (frame_budget.waiting_usec if frame_busy and frame_budget != null else 0)
|
|
|
|
func profile_start() -> int:
|
|
if not profile_enabled: return 0
|
|
return Time.get_ticks_usec() - (frame_budget.waiting_usec if frame_busy and frame_budget != null else 0)
|
|
|
|
func profile_end(section: String, started: int) -> void:
|
|
if not profile_enabled: return
|
|
var sample: Array = profile.get(section, [0, 0])
|
|
sample[0] += profile_start() - started
|
|
sample[1] += 1
|
|
profile[section] = sample
|
|
|
|
var state: Dictionary = {
|
|
'version': SimulationTime.STATE_VERSION,
|
|
'time': {'datetime': 0, 'year': 0},
|
|
'speed': 1,
|
|
'speed_in_year': 1,
|
|
'energy': WorldParameters.INITIAL_ENERGY,
|
|
'world_parameters': WorldParameters.defaults(),
|
|
'trends': WorldParameters.trend_defaults(),
|
|
'batches': {},
|
|
'batch_origins': {},
|
|
'binds': {},
|
|
'amount': {},
|
|
'distribution': {},
|
|
'auto_resources': {},
|
|
'auto_star_deficits': {},
|
|
'production_remainders': {},
|
|
'resource_remainders': {'basic': {}, 'automatic': {}},
|
|
'evolution_remainders': {},
|
|
'life_remainders': {},
|
|
'planet_remainders': {},
|
|
'blast_remainders': {},
|
|
'destruction_remainders': {},
|
|
'civilization_remainders': {},
|
|
'civilization_stats': {},
|
|
'civilization_homes': {},
|
|
'ethics': {},
|
|
'stats': {'total_amount': {}},
|
|
}
|
|
|
|
var frame_busy: bool = false
|
|
var frame_commits: int = 0
|
|
var frame_clock: Dictionary = {}
|
|
var frame_budget: RefCounted
|
|
var _requested_speed: int = 0
|
|
var _queued_productions: Array = []
|
|
var frame_active_clock: Dictionary = {}
|
|
var _resume_clock: Dictionary = {}
|
|
var frame_elapsed_seconds: float = 0.0
|
|
const frame_step_seconds: float = 0.2
|
|
|
|
## Keep the requested clock advancing while a transaction spans render frames.
|
|
func evolute_frame(delta: float, tree: SceneTree) -> void:
|
|
if not is_finite(delta) or delta < 0.0 or not is_finite(delta * pow(10.0, speed - 1)): return
|
|
if frame_clock.is_empty(): frame_clock = state.time.duplicate()
|
|
frame_clock = SimulationTime.advance(frame_clock, delta, speed)
|
|
frame_elapsed_seconds += delta
|
|
if frame_busy: return
|
|
var next_clock: Dictionary = _resume_clock
|
|
if next_clock.is_empty():
|
|
# Cadence limits starts; each transaction consumes the whole pending interval.
|
|
if frame_elapsed_seconds + 1e-12 < frame_step_seconds: return
|
|
next_clock = frame_clock.duplicate()
|
|
frame_elapsed_seconds = 0.0
|
|
frame_busy = true
|
|
frame_budget = preload("res://world/owniverse/frame_budget.gd").new(tree)
|
|
frame_active_clock = next_clock
|
|
_resume_clock = {}
|
|
evolution.run_frame(frame_active_clock, frame_budget)
|
|
|
|
func request_production(id: String, amount: Variant, resource: bool = false) -> void:
|
|
if frame_busy:
|
|
_queued_productions.append({"id": id, "amount": amount, "resource": resource})
|
|
elif resource:
|
|
production.produce_hs(id, amount)
|
|
else:
|
|
production.produce(id, amount)
|
|
|
|
## Persist work requests, never the partially calculated transaction.
|
|
func frame_checkpoint() -> Dictionary:
|
|
return {"requested_clock": frame_clock, "active_clock": frame_active_clock if frame_busy else {}, "speed": speed, "productions": _queued_productions, "elapsed_seconds": frame_elapsed_seconds}
|
|
|
|
func restore_frame_checkpoint(checkpoint: Dictionary) -> void:
|
|
frame_clock = checkpoint.get("requested_clock", {})
|
|
_resume_clock = checkpoint.get("active_clock", {})
|
|
var reserved_clock: Dictionary = _resume_clock if not _resume_clock.is_empty() else state.time
|
|
var pending_years: Variant = E.maximum(0, E.subtract(frame_clock.get("datetime", reserved_clock.datetime), reserved_clock.datetime))
|
|
frame_elapsed_seconds = float(checkpoint.get("elapsed_seconds", E.approx(pending_years) / pow(10.0, int(checkpoint.get("speed", state.speed)) - 1)))
|
|
_queued_productions = checkpoint.get("productions", [])
|
|
if _resume_clock.is_empty():
|
|
speed = checkpoint.get("speed", state.speed)
|
|
else:
|
|
_requested_speed = checkpoint.get("speed", state.speed)
|
|
|
|
func finish_frame() -> void:
|
|
frame_busy = false
|
|
frame_commits += 1
|
|
if _requested_speed != 0:
|
|
speed = _requested_speed
|
|
_requested_speed = 0
|
|
var productions: Array = _queued_productions
|
|
_queued_productions = []
|
|
for command in productions:
|
|
request_production(command.id, command.amount, command.resource)
|
|
|
|
func _notification(what: int) -> void:
|
|
if what == NOTIFICATION_PREDELETE and frame_budget != null:
|
|
frame_budget.cancelled = true
|
|
|
|
var speed: int :
|
|
get:
|
|
return _requested_speed if _requested_speed != 0 else state.speed
|
|
set(value):
|
|
if frame_busy:
|
|
_requested_speed = clampi(value, 1, Global.SPEED_LIMIT)
|
|
return
|
|
state.speed = clampi(value, 1, Global.SPEED_LIMIT)
|
|
state.speed_in_year = E.power(10, E.subtract(state.speed, 1))
|
|
|
|
func evolute(delta: Variant) -> void:
|
|
if not E.finite(delta) or E.approx(delta) <= 0.0 or E.approx(delta) * E.approx(state.speed_in_year) >= 1e27:
|
|
return
|
|
var clock: Dictionary = SimulationTime.advance(state.time, E.approx(delta), speed)
|
|
if not E.finite(clock.datetime): return
|
|
evolution.evolute_to_datetime(clock.datetime, clock)
|