Files
gamedev-the-steward/simulation/state/WorldEventDailyRollupRecord.gd
T
2026-08-12 22:59:59 +02:00

245 lines
6.5 KiB
GDScript

class_name WorldEventDailyRollupRecord
extends RefCounted
const SCHEMA_VERSION := 1
const RECORD_FIELDS := [
"schema_version",
"day_index",
"world_id",
"location_id",
"event_type",
"item_id",
"event_count",
"total_amount",
"minimum_tick",
"maximum_tick",
"minimum_event_id",
"maximum_event_id",
"source_event_ids_checksum",
]
var _day_index := -1
var _world_id: StringName
var _location_id: StringName
var _event_type: StringName
var _item_id: StringName
var _event_count := 0
var _total_amount := 0.0
var _minimum_tick := -1
var _maximum_tick := -1
var _minimum_event_id := -1
var _maximum_event_id := -1
var _source_event_ids_checksum := ""
static func create(
day_index: int,
world_id: StringName,
location_id: StringName,
event_type: StringName,
item_id: StringName,
source_event_ids: Array[int],
total_amount: float,
minimum_tick: int,
maximum_tick: int
) -> WorldEventDailyRollupRecord:
var normalized_ids: Variant = _normalize_event_ids(source_event_ids)
if normalized_ids == null or normalized_ids.is_empty():
return null
var record := WorldEventDailyRollupRecord.new()
record._day_index = day_index
record._world_id = world_id
record._location_id = location_id
record._event_type = event_type
record._item_id = item_id
record._event_count = normalized_ids.size()
record._total_amount = total_amount
record._minimum_tick = minimum_tick
record._maximum_tick = maximum_tick
record._minimum_event_id = int(normalized_ids.front())
record._maximum_event_id = int(normalized_ids.back())
record._source_event_ids_checksum = JSON.stringify(normalized_ids).sha256_text()
return record if record.is_valid() else null
static func from_dictionary(record_data: Dictionary) -> WorldEventDailyRollupRecord:
if not _has_exact_fields(record_data, RECORD_FIELDS):
return null
if (
not record_data["schema_version"] is int
or int(record_data["schema_version"]) != SCHEMA_VERSION
or not record_data["day_index"] is int
or not record_data["world_id"] is String
or not record_data["location_id"] is String
or not record_data["event_type"] is String
or not record_data["item_id"] is String
or not record_data["event_count"] is int
or (not record_data["total_amount"] is int and not record_data["total_amount"] is float)
or not record_data["minimum_tick"] is int
or not record_data["maximum_tick"] is int
or not record_data["minimum_event_id"] is int
or not record_data["maximum_event_id"] is int
or not record_data["source_event_ids_checksum"] is String
):
return null
var record := WorldEventDailyRollupRecord.new()
record._day_index = int(record_data["day_index"])
record._world_id = StringName(record_data["world_id"])
record._location_id = StringName(record_data["location_id"])
record._event_type = StringName(record_data["event_type"])
record._item_id = StringName(record_data["item_id"])
record._event_count = int(record_data["event_count"])
record._total_amount = float(record_data["total_amount"])
record._minimum_tick = int(record_data["minimum_tick"])
record._maximum_tick = int(record_data["maximum_tick"])
record._minimum_event_id = int(record_data["minimum_event_id"])
record._maximum_event_id = int(record_data["maximum_event_id"])
record._source_event_ids_checksum = String(record_data["source_event_ids_checksum"])
return record if record.is_valid() else null
func is_valid() -> bool:
return (
_day_index >= 0
and not _world_id.is_empty()
and not _location_id.is_empty()
and not _event_type.is_empty()
and not _item_id.is_empty()
and _event_count > 0
and is_finite(_total_amount)
and _total_amount > 0.0
and _minimum_tick >= 0
and _maximum_tick >= _minimum_tick
and _minimum_event_id >= 0
and _maximum_event_id >= _minimum_event_id
and _is_sha256(_source_event_ids_checksum)
)
func get_day_index() -> int:
return _day_index
func get_world_id() -> StringName:
return _world_id
func get_location_id() -> StringName:
return _location_id
func get_event_type() -> StringName:
return _event_type
func get_item_id() -> StringName:
return _item_id
func get_event_count() -> int:
return _event_count
func get_total_amount() -> float:
return _total_amount
func get_minimum_tick() -> int:
return _minimum_tick
func get_maximum_tick() -> int:
return _maximum_tick
func get_minimum_event_id() -> int:
return _minimum_event_id
func get_maximum_event_id() -> int:
return _maximum_event_id
func get_source_event_ids_checksum() -> String:
return _source_event_ids_checksum
func index_key() -> String:
return index_key_for(_day_index, _world_id, _location_id, _event_type, _item_id)
func location_index_key() -> String:
return location_key_for(_world_id, _location_id)
func get_reason_trace() -> Array[StringName]:
return [
&"daily_transaction_rollup",
StringName("source_event_count_%d" % _event_count),
StringName("source_event_ids_sha256_%s" % _source_event_ids_checksum),
]
func to_dictionary() -> Dictionary:
return {
"schema_version": SCHEMA_VERSION,
"day_index": _day_index,
"world_id": String(_world_id),
"location_id": String(_location_id),
"event_type": String(_event_type),
"item_id": String(_item_id),
"event_count": _event_count,
"total_amount": _total_amount,
"minimum_tick": _minimum_tick,
"maximum_tick": _maximum_tick,
"minimum_event_id": _minimum_event_id,
"maximum_event_id": _maximum_event_id,
"source_event_ids_checksum": _source_event_ids_checksum,
}
static func index_key_for(
day_index: int,
world_id: StringName,
location_id: StringName,
event_type: StringName,
item_id: StringName
) -> String:
return JSON.stringify(
[day_index, String(world_id), String(location_id), String(event_type), String(item_id)]
)
static func location_key_for(world_id: StringName, location_id: StringName) -> String:
return JSON.stringify([String(world_id), String(location_id)])
static func _normalize_event_ids(values: Array) -> Variant:
var seen: Dictionary = {}
var normalized: Array[int] = []
for value: Variant in values:
if not value is int or int(value) < 0 or seen.has(int(value)):
return null
seen[int(value)] = true
normalized.append(int(value))
normalized.sort()
return normalized
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
static func _is_sha256(value: String) -> bool:
if value.length() != 64:
return false
for character_index in range(value.length()):
if value[character_index] not in "0123456789abcdef":
return false
return true