feat: plan bounded event history rollups
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
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
|
||||
Reference in New Issue
Block a user