class_name WorldEventStore extends RefCounted signal event_recorded(event: WorldEventRecord) const SCHEMA_VERSION := 1 var _events_by_id: Dictionary = {} var _ordered_event_ids: Array[int] = [] var _event_type_index: Dictionary = {} var _participant_index: Dictionary = {} var _location_index: Dictionary = {} var _tick_index: Dictionary = {} var _ordered_ticks: Array[int] = [] static func from_dictionary(store_data: Dictionary) -> WorldEventStore: var store := WorldEventStore.new() return store if store.restore_from_dictionary(store_data) else null func size() -> int: return _events_by_id.size() func is_empty() -> bool: return _events_by_id.is_empty() func has_event(event_id: int) -> bool: return _events_by_id.has(event_id) func append(event: WorldEventRecord) -> bool: return _append(event, true) func append_economic( economic_event: EconomicEventRecord, world_id: StringName = &"", location_id: StringName = &"" ) -> WorldEventRecord: var world_event := WorldEventRecord.from_economic_event(economic_event, world_id, location_id) if world_event == null or not append(world_event): return null return get_by_id(world_event.get_event_id()) func get_by_id(event_id: int) -> WorldEventRecord: return _events_by_id.get(event_id) as WorldEventRecord func get_by_ids(event_ids: Array[int]) -> Array[WorldEventRecord]: var results: Array[WorldEventRecord] = [] for event_id in event_ids: var event := get_by_id(event_id) if event != null: results.append(event) return results func get_all() -> Array[WorldEventRecord]: return _records_for_ids(_ordered_event_ids) func get_for_type(event_type: StringName) -> Array[WorldEventRecord]: return _records_for_ids(_event_type_index.get(event_type, [])) func get_for_participant(participant: WorldEntityRef) -> Array[WorldEventRecord]: if participant == null or not participant.is_valid(): return [] return _records_for_ids(_participant_index.get(participant.index_key(), [])) func get_for_location(location: SpatialAddress) -> Array[WorldEventRecord]: if location == null or not location.is_valid(): return [] return _records_for_ids(_location_index.get(location.index_key(), [])) func get_for_tick(tick: int) -> Array[WorldEventRecord]: return _records_for_ids(_tick_index.get(tick, [])) func get_between_ticks(first_tick: int, last_tick: int) -> Array[WorldEventRecord]: var results: Array[WorldEventRecord] = [] if first_tick < 0 or last_tick < first_tick: return results for tick in _ordered_ticks: if tick < first_tick: continue if tick > last_tick: break results.append_array(_records_for_ids(_tick_index[tick])) return results func restore(restored_events: Array[WorldEventRecord]) -> bool: var candidate := WorldEventStore.new() for event in restored_events: if not candidate._append(event, false): return false _adopt_indexes(candidate) return true func restore_from_dictionary(store_data: Dictionary) -> bool: if int(store_data.get("schema_version", -1)) != SCHEMA_VERSION: return false var saved_events = store_data.get("events") if not saved_events is Array: return false var restored_events: Array[WorldEventRecord] = [] for saved_event in saved_events: if not saved_event is Dictionary: return false var event := WorldEventRecord.from_dictionary(saved_event) if event == null: return false restored_events.append(event) return restore(restored_events) func to_dictionary() -> Dictionary: var serialized_events: Array[Dictionary] = [] for event in get_all(): serialized_events.append(event.to_dictionary()) return { "schema_version": SCHEMA_VERSION, "events": serialized_events, } func _append(event: WorldEventRecord, emit_signal: bool) -> bool: if event == null or not event.is_valid() or _events_by_id.has(event.get_event_id()): return false var stored_event := WorldEventRecord.from_dictionary(event.to_dictionary()) if stored_event == null: return false var event_id := stored_event.get_event_id() _events_by_id[event_id] = stored_event _insert_event_id(_ordered_event_ids, event_id) _index_event_id(_event_type_index, stored_event.get_event_type(), event_id) for participant_key in stored_event.get_participant_index_keys(): _index_event_id(_participant_index, participant_key, event_id) if stored_event.has_location(): _index_event_id(_location_index, stored_event.get_location().index_key(), event_id) _index_event_id(_tick_index, stored_event.get_tick(), event_id) _insert_tick(stored_event.get_tick()) if emit_signal: event_recorded.emit(stored_event) return true func _index_event_id(index: Dictionary, key: Variant, event_id: int) -> void: if not index.has(key): index[key] = [] var indexed_ids: Array = index[key] _insert_event_id(indexed_ids, event_id) func _insert_event_id(event_ids: Array, event_id: int) -> void: var low := 0 var high := event_ids.size() while low < high: var middle := (low + high) / 2 if _event_precedes(event_id, int(event_ids[middle])): high = middle else: low = middle + 1 event_ids.insert(low, event_id) func _event_precedes(left_id: int, right_id: int) -> bool: var left := get_by_id(left_id) var right := get_by_id(right_id) if left.get_tick() != right.get_tick(): return left.get_tick() < right.get_tick() return left_id < right_id func _insert_tick(tick: int) -> void: var insert_at := _ordered_ticks.bsearch(tick) if insert_at < _ordered_ticks.size() and _ordered_ticks[insert_at] == tick: return _ordered_ticks.insert(insert_at, tick) func _records_for_ids(event_ids: Array) -> Array[WorldEventRecord]: var results: Array[WorldEventRecord] = [] for event_id in event_ids: var event := get_by_id(int(event_id)) if event != null: results.append(event) return results func _adopt_indexes(source: WorldEventStore) -> void: _events_by_id = source._events_by_id _ordered_event_ids = source._ordered_event_ids _event_type_index = source._event_type_index _participant_index = source._participant_index _location_index = source._location_index _tick_index = source._tick_index _ordered_ticks = source._ordered_ticks