216 lines
6.4 KiB
GDScript
216 lines
6.4 KiB
GDScript
class_name WorldEventDailyRollupStore
|
|
extends RefCounted
|
|
|
|
const SCHEMA_VERSION := 1
|
|
const RECORD_FIELDS := ["schema_version", "rollups"]
|
|
|
|
var _records_by_key: Dictionary = {}
|
|
var _ordered_keys: Array[String] = []
|
|
var _day_index: Dictionary = {}
|
|
var _location_index: Dictionary = {}
|
|
var _event_type_index: Dictionary = {}
|
|
var _item_index: Dictionary = {}
|
|
|
|
|
|
static func from_dictionary(store_data: Dictionary) -> WorldEventDailyRollupStore:
|
|
var store := WorldEventDailyRollupStore.new()
|
|
return store if store.restore_from_dictionary(store_data) else null
|
|
|
|
|
|
func size() -> int:
|
|
return _records_by_key.size()
|
|
|
|
|
|
func is_empty() -> bool:
|
|
return _records_by_key.is_empty()
|
|
|
|
|
|
func has_rollup(
|
|
day_index: int,
|
|
world_id: StringName,
|
|
location_id: StringName,
|
|
event_type: StringName,
|
|
item_id: StringName
|
|
) -> bool:
|
|
return _records_by_key.has(
|
|
WorldEventDailyRollupRecord.index_key_for(
|
|
day_index, world_id, location_id, event_type, item_id
|
|
)
|
|
)
|
|
|
|
|
|
func append(record: WorldEventDailyRollupRecord) -> bool:
|
|
return _append(record)
|
|
|
|
|
|
func get_by_key(
|
|
day_index: int,
|
|
world_id: StringName,
|
|
location_id: StringName,
|
|
event_type: StringName,
|
|
item_id: StringName
|
|
) -> WorldEventDailyRollupRecord:
|
|
var key := WorldEventDailyRollupRecord.index_key_for(
|
|
day_index, world_id, location_id, event_type, item_id
|
|
)
|
|
return _records_by_key.get(key) as WorldEventDailyRollupRecord
|
|
|
|
|
|
func get_all() -> Array[WorldEventDailyRollupRecord]:
|
|
return _records_for_keys(_ordered_keys)
|
|
|
|
|
|
func get_for_day(day_index: int) -> Array[WorldEventDailyRollupRecord]:
|
|
return _records_for_keys(_day_index.get(day_index, []))
|
|
|
|
|
|
func get_for_location(
|
|
world_id: StringName, location_id: StringName
|
|
) -> Array[WorldEventDailyRollupRecord]:
|
|
return _records_for_keys(
|
|
_location_index.get(WorldEventDailyRollupRecord.location_key_for(world_id, location_id), [])
|
|
)
|
|
|
|
|
|
func get_for_event_type(event_type: StringName) -> Array[WorldEventDailyRollupRecord]:
|
|
return _records_for_keys(_event_type_index.get(event_type, []))
|
|
|
|
|
|
func get_for_item(item_id: StringName) -> Array[WorldEventDailyRollupRecord]:
|
|
return _records_for_keys(_item_index.get(item_id, []))
|
|
|
|
|
|
func restore(records: Array[WorldEventDailyRollupRecord]) -> bool:
|
|
var candidate := WorldEventDailyRollupStore.new()
|
|
for record: WorldEventDailyRollupRecord in records:
|
|
if not candidate._append(record):
|
|
return false
|
|
_adopt(candidate)
|
|
return true
|
|
|
|
|
|
func restore_from_dictionary(store_data: Dictionary) -> bool:
|
|
if not _has_exact_fields(store_data, RECORD_FIELDS):
|
|
return false
|
|
if (
|
|
not store_data["schema_version"] is int
|
|
or int(store_data["schema_version"]) != SCHEMA_VERSION
|
|
or not store_data["rollups"] is Array
|
|
):
|
|
return false
|
|
var records: Array[WorldEventDailyRollupRecord] = []
|
|
for raw_record: Variant in store_data["rollups"]:
|
|
if not raw_record is Dictionary:
|
|
return false
|
|
var record := WorldEventDailyRollupRecord.from_dictionary(raw_record)
|
|
if record == null:
|
|
return false
|
|
records.append(record)
|
|
if not _records_are_canonical(records):
|
|
return false
|
|
return restore(records)
|
|
|
|
|
|
func to_dictionary() -> Dictionary:
|
|
var serialized: Array[Dictionary] = []
|
|
for record: WorldEventDailyRollupRecord in get_all():
|
|
serialized.append(record.to_dictionary())
|
|
return {
|
|
"schema_version": SCHEMA_VERSION,
|
|
"rollups": serialized,
|
|
}
|
|
|
|
|
|
func checksum() -> String:
|
|
return JSON.stringify(to_dictionary()).sha256_text()
|
|
|
|
|
|
func _append(record: WorldEventDailyRollupRecord) -> bool:
|
|
if record == null or not record.is_valid() or _records_by_key.has(record.index_key()):
|
|
return false
|
|
var stored := WorldEventDailyRollupRecord.from_dictionary(record.to_dictionary())
|
|
if stored == null:
|
|
return false
|
|
var key := stored.index_key()
|
|
_records_by_key[key] = stored
|
|
_insert_key(_ordered_keys, key)
|
|
_index_key(_day_index, stored.get_day_index(), key)
|
|
_index_key(_location_index, stored.location_index_key(), key)
|
|
_index_key(_event_type_index, stored.get_event_type(), key)
|
|
_index_key(_item_index, stored.get_item_id(), key)
|
|
return true
|
|
|
|
|
|
func _index_key(index: Dictionary, index_value: Variant, record_key: String) -> void:
|
|
if not index.has(index_value):
|
|
index[index_value] = []
|
|
var keys: Array = index[index_value]
|
|
_insert_key(keys, record_key)
|
|
|
|
|
|
func _insert_key(keys: Array, record_key: String) -> void:
|
|
var low := 0
|
|
var high := keys.size()
|
|
while low < high:
|
|
var middle := (low + high) / 2
|
|
if _record_precedes(record_key, String(keys[middle])):
|
|
high = middle
|
|
else:
|
|
low = middle + 1
|
|
keys.insert(low, record_key)
|
|
|
|
|
|
func _record_precedes(first_key: String, second_key: String) -> bool:
|
|
var first := _records_by_key[first_key] as WorldEventDailyRollupRecord
|
|
var second := _records_by_key[second_key] as WorldEventDailyRollupRecord
|
|
if first.get_day_index() != second.get_day_index():
|
|
return first.get_day_index() < second.get_day_index()
|
|
if first.get_world_id() != second.get_world_id():
|
|
return String(first.get_world_id()) < String(second.get_world_id())
|
|
if first.get_location_id() != second.get_location_id():
|
|
return String(first.get_location_id()) < String(second.get_location_id())
|
|
if first.get_event_type() != second.get_event_type():
|
|
return String(first.get_event_type()) < String(second.get_event_type())
|
|
return String(first.get_item_id()) < String(second.get_item_id())
|
|
|
|
|
|
func _records_for_keys(keys: Array) -> Array[WorldEventDailyRollupRecord]:
|
|
var records: Array[WorldEventDailyRollupRecord] = []
|
|
for key: Variant in keys:
|
|
var record := _records_by_key.get(String(key)) as WorldEventDailyRollupRecord
|
|
if record != null:
|
|
records.append(record)
|
|
return records
|
|
|
|
|
|
func _records_are_canonical(records: Array[WorldEventDailyRollupRecord]) -> bool:
|
|
var candidate := WorldEventDailyRollupStore.new()
|
|
for record: WorldEventDailyRollupRecord in records:
|
|
if not candidate._append(record):
|
|
return false
|
|
var canonical_records := candidate.get_all()
|
|
if canonical_records.size() != records.size():
|
|
return false
|
|
for index in range(records.size()):
|
|
if canonical_records[index].index_key() != records[index].index_key():
|
|
return false
|
|
return true
|
|
|
|
|
|
func _adopt(source: WorldEventDailyRollupStore) -> void:
|
|
_records_by_key = source._records_by_key
|
|
_ordered_keys = source._ordered_keys
|
|
_day_index = source._day_index
|
|
_location_index = source._location_index
|
|
_event_type_index = source._event_type_index
|
|
_item_index = source._item_index
|
|
|
|
|
|
static func _has_exact_fields(record_data: Dictionary, fields: Array) -> bool:
|
|
if record_data.size() != fields.size():
|
|
return false
|
|
for field: String in fields:
|
|
if not record_data.has(field):
|
|
return false
|
|
return true
|