feat: add emergent world foundations

This commit is contained in:
Rijad Zuzo
2026-08-12 20:06:51 +02:00
parent fa04137a04
commit ea00f60165
44 changed files with 2665 additions and 7 deletions
+204
View File
@@ -0,0 +1,204 @@
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
+1
View File
@@ -0,0 +1 @@
uid://dss5liteqydyd