feat: add deterministic regional scheduler
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
class_name ScheduledJobRecord
|
||||
extends RefCounted
|
||||
|
||||
const SCHEMA_VERSION := 1
|
||||
const NO_REPEAT := 0
|
||||
|
||||
var data: Dictionary
|
||||
|
||||
|
||||
func _init(record_data: Dictionary = {}) -> void:
|
||||
data = record_data.duplicate(true)
|
||||
|
||||
|
||||
static func create(
|
||||
job_id: StringName,
|
||||
job_type: StringName,
|
||||
due_tick: int,
|
||||
phase: int,
|
||||
entity_id: StringName,
|
||||
stable_sequence: int,
|
||||
payload: Dictionary = {},
|
||||
dedupe_key: StringName = &"",
|
||||
repeat_interval: int = NO_REPEAT,
|
||||
occurrence: int = 0
|
||||
) -> ScheduledJobRecord:
|
||||
var payload_status := [true]
|
||||
var normalized_payload: Variant = _normalize_serializable(payload, payload_status)
|
||||
if not payload_status[0] or not normalized_payload is Dictionary:
|
||||
return null
|
||||
var record := (
|
||||
ScheduledJobRecord
|
||||
. new(
|
||||
{
|
||||
"schema_version": SCHEMA_VERSION,
|
||||
"job_id": String(job_id),
|
||||
"job_type": String(job_type),
|
||||
"due_tick": due_tick,
|
||||
"phase": phase,
|
||||
"entity_id": String(entity_id),
|
||||
"stable_sequence": stable_sequence,
|
||||
"dedupe_key": String(dedupe_key),
|
||||
"repeat_interval": repeat_interval,
|
||||
"occurrence": occurrence,
|
||||
"payload": normalized_payload,
|
||||
}
|
||||
)
|
||||
)
|
||||
return record if record.is_valid() else null
|
||||
|
||||
|
||||
static func from_dictionary(record_data: Dictionary) -> ScheduledJobRecord:
|
||||
if int(record_data.get("schema_version", -1)) != SCHEMA_VERSION:
|
||||
return null
|
||||
if not (
|
||||
record_data
|
||||
. has_all(
|
||||
[
|
||||
"job_id",
|
||||
"job_type",
|
||||
"due_tick",
|
||||
"phase",
|
||||
"entity_id",
|
||||
"stable_sequence",
|
||||
"dedupe_key",
|
||||
"repeat_interval",
|
||||
"occurrence",
|
||||
"payload",
|
||||
]
|
||||
)
|
||||
):
|
||||
return null
|
||||
if not record_data["payload"] is Dictionary:
|
||||
return null
|
||||
return create(
|
||||
StringName(record_data["job_id"]),
|
||||
StringName(record_data["job_type"]),
|
||||
int(record_data["due_tick"]),
|
||||
int(record_data["phase"]),
|
||||
StringName(record_data["entity_id"]),
|
||||
int(record_data["stable_sequence"]),
|
||||
record_data["payload"],
|
||||
StringName(record_data["dedupe_key"]),
|
||||
int(record_data["repeat_interval"]),
|
||||
int(record_data["occurrence"])
|
||||
)
|
||||
|
||||
|
||||
func is_valid() -> bool:
|
||||
return (
|
||||
not get_job_id().is_empty()
|
||||
and not get_job_type().is_empty()
|
||||
and get_due_tick() >= 0
|
||||
and get_phase() >= 0
|
||||
and not get_entity_id().is_empty()
|
||||
and get_stable_sequence() >= 0
|
||||
and get_repeat_interval() >= NO_REPEAT
|
||||
and get_occurrence() >= 0
|
||||
and data["payload"] is Dictionary
|
||||
)
|
||||
|
||||
|
||||
func get_job_id() -> StringName:
|
||||
return StringName(data["job_id"])
|
||||
|
||||
|
||||
func get_job_type() -> StringName:
|
||||
return StringName(data["job_type"])
|
||||
|
||||
|
||||
func get_due_tick() -> int:
|
||||
return int(data["due_tick"])
|
||||
|
||||
|
||||
func get_phase() -> int:
|
||||
return int(data["phase"])
|
||||
|
||||
|
||||
func get_entity_id() -> StringName:
|
||||
return StringName(data["entity_id"])
|
||||
|
||||
|
||||
func get_stable_sequence() -> int:
|
||||
return int(data["stable_sequence"])
|
||||
|
||||
|
||||
func get_dedupe_key() -> StringName:
|
||||
return StringName(data["dedupe_key"])
|
||||
|
||||
|
||||
func get_repeat_interval() -> int:
|
||||
return int(data["repeat_interval"])
|
||||
|
||||
|
||||
func get_occurrence() -> int:
|
||||
return int(data["occurrence"])
|
||||
|
||||
|
||||
func get_payload() -> Dictionary:
|
||||
return data["payload"].duplicate(true)
|
||||
|
||||
|
||||
func is_repeating() -> bool:
|
||||
return get_repeat_interval() > NO_REPEAT
|
||||
|
||||
|
||||
func create_next_occurrence(next_stable_sequence: int = -1) -> ScheduledJobRecord:
|
||||
if not is_repeating():
|
||||
return null
|
||||
var sequence := get_stable_sequence() if next_stable_sequence < 0 else next_stable_sequence
|
||||
var next_occurrence := get_occurrence() + 1
|
||||
return create(
|
||||
&"%s#%d" % [get_job_id().get_slice("#", 0), next_occurrence],
|
||||
get_job_type(),
|
||||
get_due_tick() + get_repeat_interval(),
|
||||
get_phase(),
|
||||
get_entity_id(),
|
||||
sequence,
|
||||
get_payload(),
|
||||
get_dedupe_key(),
|
||||
get_repeat_interval(),
|
||||
next_occurrence
|
||||
)
|
||||
|
||||
|
||||
func to_dictionary() -> Dictionary:
|
||||
return data.duplicate(true)
|
||||
|
||||
|
||||
static func compare(first: ScheduledJobRecord, second: ScheduledJobRecord) -> bool:
|
||||
if first.get_due_tick() != second.get_due_tick():
|
||||
return first.get_due_tick() < second.get_due_tick()
|
||||
if first.get_phase() != second.get_phase():
|
||||
return first.get_phase() < second.get_phase()
|
||||
if first.get_entity_id() != second.get_entity_id():
|
||||
return String(first.get_entity_id()) < String(second.get_entity_id())
|
||||
return first.get_stable_sequence() < second.get_stable_sequence()
|
||||
|
||||
|
||||
static func _normalize_serializable(value: Variant, status: Array) -> Variant:
|
||||
match typeof(value):
|
||||
TYPE_NIL, TYPE_BOOL, TYPE_INT, TYPE_STRING:
|
||||
return value
|
||||
TYPE_STRING_NAME:
|
||||
return String(value)
|
||||
TYPE_FLOAT:
|
||||
if not is_finite(float(value)):
|
||||
status[0] = false
|
||||
return value
|
||||
TYPE_ARRAY:
|
||||
var normalized_array: Array = []
|
||||
for item in value:
|
||||
normalized_array.append(_normalize_serializable(item, status))
|
||||
return normalized_array
|
||||
TYPE_DICTIONARY:
|
||||
return _normalize_dictionary(value, status)
|
||||
status[0] = false
|
||||
return null
|
||||
|
||||
|
||||
static func _normalize_dictionary(value: Dictionary, status: Array) -> Dictionary:
|
||||
var source_keys: Dictionary = {}
|
||||
for raw_key in value:
|
||||
if not raw_key is String and not raw_key is StringName:
|
||||
status[0] = false
|
||||
return {}
|
||||
var key := String(raw_key)
|
||||
if source_keys.has(key):
|
||||
status[0] = false
|
||||
return {}
|
||||
source_keys[key] = raw_key
|
||||
var keys: Array = source_keys.keys()
|
||||
keys.sort()
|
||||
var normalized: Dictionary = {}
|
||||
for key in keys:
|
||||
normalized[key] = _normalize_serializable(value[source_keys[key]], status)
|
||||
return normalized
|
||||
@@ -0,0 +1 @@
|
||||
uid://dnvfgloigjgmy
|
||||
Reference in New Issue
Block a user