From e27fc1a5c836354a4639ffdfd03ae448f18259fd Mon Sep 17 00:00:00 2001 From: Rijad Zuzo Date: Wed, 12 Aug 2026 20:14:57 +0200 Subject: [PATCH] perf: index simulation event history --- simulation/events/SimulationEventLog.gd | 80 ++++++++++++++++++++----- tests/unit/test_simulation_services.gd | 11 ++++ 2 files changed, 77 insertions(+), 14 deletions(-) diff --git a/simulation/events/SimulationEventLog.gd b/simulation/events/SimulationEventLog.gd index 3a97689..f3d8b71 100644 --- a/simulation/events/SimulationEventLog.gd +++ b/simulation/events/SimulationEventLog.gd @@ -7,6 +7,12 @@ const DEFAULT_TICKS_PER_DAY := 200.0 var events: Array[EconomicEventRecord] = [] var next_event_id := 0 +var world_events := WorldEventStore.new() + +var _events_by_id: Dictionary = {} +var _event_ids_by_actor: Dictionary = {} +var _event_ids_by_type: Dictionary = {} +var _event_ids_by_tick: Dictionary = {} func record_economic( @@ -65,14 +71,14 @@ func record_narrative( func get_for_actor(actor_id: int, max_count: int = 8) -> Array[EconomicEventRecord]: var results: Array[EconomicEventRecord] = [] - for index in range(events.size() - 1, -1, -1): - var event := events[index] - if int(event.data["actor_id"]) != actor_id: + var event_ids: Array = _event_ids_by_actor.get(actor_id, []) + for index in range(event_ids.size() - 1, -1, -1): + var event := get_by_id(int(event_ids[index])) + if event == null: continue - results.append(event) + results.push_front(event) if results.size() >= max_count: break - results.reverse() return results @@ -85,10 +91,7 @@ func get_recent(max_count: int = 5) -> Array[EconomicEventRecord]: func get_by_id(event_id: int) -> EconomicEventRecord: - for event in events: - if int(event.data["event_id"]) == event_id: - return event - return null + return _events_by_id.get(event_id) as EconomicEventRecord func get_by_ids(event_ids: Array[int]) -> Array[EconomicEventRecord]: @@ -100,6 +103,30 @@ func get_by_ids(event_ids: Array[int]) -> Array[EconomicEventRecord]: return results +func get_for_type(event_type: StringName, max_count: int = 0) -> Array[EconomicEventRecord]: + var results: Array[EconomicEventRecord] = [] + var event_ids: Array = _event_ids_by_type.get(event_type, []) + var start := 0 if max_count <= 0 else maxi(event_ids.size() - max_count, 0) + for index in range(start, event_ids.size()): + var event := get_by_id(int(event_ids[index])) + if event != null: + results.append(event) + return results + + +func get_for_tick(tick: int) -> Array[EconomicEventRecord]: + var results: Array[EconomicEventRecord] = [] + for event_id in _event_ids_by_tick.get(tick, []): + var event := get_by_id(int(event_id)) + if event != null: + results.append(event) + return results + + +func get_world_event(event_id: int) -> WorldEventRecord: + return world_events.get_by_id(event_id) + + func get_consumption_rates( current_tick: int, window_ticks: int = DEFAULT_RATE_WINDOW_TICKS, @@ -110,11 +137,9 @@ func get_consumption_rates( SimulationIds.RESOURCE_WOOD: 0.0, } var first_tick := maxi(current_tick - window_ticks, 0) - for event in events: + for event in get_for_type(SimulationIds.EVENT_ITEM_CONSUMED): if int(event.data["tick"]) < first_tick: continue - if StringName(event.data["event_type"]) != SimulationIds.EVENT_ITEM_CONSUMED: - continue var item_id := StringName(event.data["item_id"]) if consumed.has(item_id): consumed[item_id] = float(consumed[item_id]) + float(event.data["amount"]) @@ -126,11 +151,38 @@ func get_consumption_rates( func restore(restored_events: Array[EconomicEventRecord], restored_next_id: int) -> void: - events = restored_events.duplicate() + events.clear() + _events_by_id.clear() + _event_ids_by_actor.clear() + _event_ids_by_type.clear() + _event_ids_by_tick.clear() + world_events = WorldEventStore.new() + for event in restored_events: + _index(event) next_event_id = restored_next_id func _append(event: EconomicEventRecord) -> void: next_event_id += 1 - events.append(event) + _index(event) event_recorded.emit(event) + + +func _index(event: EconomicEventRecord) -> void: + if event == null: + return + var event_id := int(event.data["event_id"]) + if _events_by_id.has(event_id): + return + events.append(event) + _events_by_id[event_id] = event + _append_index(_event_ids_by_actor, int(event.data["actor_id"]), event_id) + _append_index(_event_ids_by_type, StringName(event.data["event_type"]), event_id) + _append_index(_event_ids_by_tick, int(event.data["tick"]), event_id) + world_events.append_economic(event, SimulationIds.WORLD_CORE, SimulationIds.LOCATION_JAJCE) + + +static func _append_index(index: Dictionary, key: Variant, event_id: int) -> void: + var event_ids: Array = index.get(key, []) + event_ids.append(event_id) + index[key] = event_ids diff --git a/tests/unit/test_simulation_services.gd b/tests/unit/test_simulation_services.gd index 6f3e802..c2cfec2 100644 --- a/tests/unit/test_simulation_services.gd +++ b/tests/unit/test_simulation_services.gd @@ -147,6 +147,10 @@ func test_event_log_preserves_order_and_derives_consumption_rate() -> void: assert_eq(int(event_log.events[0].data["event_id"]), 0) assert_eq(int(event_log.events[1].data["event_id"]), 1) assert_eq(event_log.get_for_actor(4).size(), 2) + assert_eq(event_log.get_by_id(1), event_log.events[1]) + assert_eq(event_log.get_for_type(SimulationIds.EVENT_ITEM_CONSUMED), [event_log.events[1]]) + assert_eq(event_log.get_for_tick(20), [event_log.events[1]]) + assert_eq(event_log.get_world_event(1).get_event_type(), SimulationIds.EVENT_ITEM_CONSUMED) assert_eq(float(event_log.get_consumption_rates(200)["food_per_day"]), 1.0) var legacy_event_data := event_log.events[1].to_dictionary() legacy_event_data["schema_version"] = EconomicEventRecord.LEGACY_SCHEMA_VERSION @@ -155,6 +159,13 @@ func test_event_log_preserves_order_and_derives_consumption_rate() -> void: assert_not_null(migrated_event) assert_eq(migrated_event.get_world_position(), Vector3.ZERO) + var restored_log := SimulationEventLogScript.new() + restored_log.restore(event_log.events, event_log.next_event_id) + assert_eq(restored_log.get_by_id(0), event_log.events[0]) + assert_eq(restored_log.get_for_actor(4).size(), 2) + assert_eq(restored_log.get_for_type(SimulationIds.EVENT_TASK_STARTED).size(), 1) + assert_eq(restored_log.get_world_event(1).get_event_id(), 1) + func test_food_aid_builds_directed_trust_once_per_event() -> void: var contributor := SimNPC.new(0, "Amina", SimulationIds.PROFESSION_FARMER, 5.0, 5.0)