Files
TheTree_mock/scripts/core/domain_event_stream.gd
T
Takeshi 5dde0c5ec2 Record original-alignment implementation and traceability checkpoint
Capture current source, content data, tests, asset licenses, and original-specification traceability. Original implementation remains incomplete. The accepted matching UI generation passed 154 suites and 12,253 checks, with eight rendered staff-contract screens reviewed. Latest native Windows and full-original acceptance remain outstanding.
2026-09-30 23:52:24 +09:00

150 lines
5.0 KiB
GDScript

class_name DomainEventStream
extends RefCounted
const SNAPSHOT_VERSION := 1
const WILDCARD_EVENT_TYPE: StringName = &"*"
const RECENT_EVENT_LIMIT := 256
const MAX_BATCH_SIZE := 4_096
var _subscribers: Dictionary = {}
var _published_ids: Dictionary = {}
var _snapshot_ids: Array[String] = []
var _snapshot_ids_dirty := true
var _recent_events: Array[SimulationDomainEvent] = []
var _pending_deliveries: Array[SimulationDomainEvent] = []
var _delivery_active := false
func subscribe(event_type: StringName, subscriber_id: StringName, callback: Callable) -> bool:
if event_type.is_empty() or subscriber_id.is_empty() or not callback.is_valid():
return false
if not _subscribers.has(event_type):
_subscribers[event_type] = {}
var subscribers: Dictionary = _subscribers[event_type]
if subscribers.has(subscriber_id):
return false
subscribers[subscriber_id] = callback
return true
func unsubscribe(event_type: StringName, subscriber_id: StringName) -> void:
if _subscribers.has(event_type):
var subscribers: Dictionary = _subscribers[event_type]
subscribers.erase(subscriber_id)
func publish(event: SimulationDomainEvent) -> bool:
return publish_batch([event])
## Validate and commit the entire event set before the first subscriber runs.
## Reentrant publications join the end of the delivery queue, so callbacks can
## never observe or deliver only a prefix of the committed batch.
func publish_batch(events: Array) -> bool:
if events.size() > MAX_BATCH_SIZE:
return false
var committed: Array[SimulationDomainEvent] = []
var batch_ids: Dictionary = {}
for event_value: Variant in events:
if not event_value is SimulationDomainEvent:
return false
var event: SimulationDomainEvent = event_value
if event == null or not event.is_valid() \
or _published_ids.has(event.event_id) or batch_ids.has(event.event_id):
return false
batch_ids[event.event_id] = true
committed.append(event.copy())
if not committed.is_empty():
_snapshot_ids_dirty = true
for event: SimulationDomainEvent in committed:
_published_ids[event.event_id] = true
_recent_events.append(event.copy())
if _recent_events.size() > RECENT_EVENT_LIMIT:
_recent_events.pop_front()
_pending_deliveries.append(event.copy())
_drain_deliveries()
return true
func _drain_deliveries() -> void:
if _delivery_active:
return
_delivery_active = true
while not _pending_deliveries.is_empty():
var event: SimulationDomainEvent = _pending_deliveries.pop_front()
_deliver(event)
_delivery_active = false
func _deliver(event: SimulationDomainEvent) -> void:
var callbacks := {}
_collect_callbacks(event.event_type, callbacks)
_collect_callbacks(WILDCARD_EVENT_TYPE, callbacks)
var subscriber_ids: Array = callbacks.keys()
subscriber_ids.sort()
for subscriber_id: Variant in subscriber_ids:
var callback: Callable = callbacks[subscriber_id]
if callback.is_valid():
callback.call(event.copy())
func has_published(event_id: StringName) -> bool:
return _published_ids.has(event_id)
func recent_events() -> Array[SimulationDomainEvent]:
var result: Array[SimulationDomainEvent] = []
for event in _recent_events:
result.append(event.copy())
return result
func create_snapshot() -> Dictionary:
if _snapshot_ids_dirty:
_snapshot_ids.clear()
for event_id: Variant in _published_ids:
_snapshot_ids.append(String(event_id))
_snapshot_ids.sort()
_snapshot_ids_dirty = false
var saved_ids: Array[String] = _snapshot_ids.duplicate()
var saved_recent: Array[Dictionary] = []
for event in _recent_events:
saved_recent.append(event.to_dictionary())
return {
"version": SNAPSHOT_VERSION,
"published_ids": saved_ids,
"recent_events": saved_recent,
}
func restore_snapshot(snapshot: Dictionary) -> bool:
if _delivery_active or not _pending_deliveries.is_empty():
return false
if int(snapshot.get("version", -1)) != SNAPSHOT_VERSION:
return false
if not snapshot.get("published_ids", null) is Array \
or not snapshot.get("recent_events", null) is Array:
return false
var restored_ids := {}
for event_id: Variant in snapshot["published_ids"]:
var normalized_id := StringName(event_id)
if normalized_id.is_empty() or restored_ids.has(normalized_id):
return false
restored_ids[normalized_id] = true
var restored_recent: Array[SimulationDomainEvent] = []
for event_data: Variant in snapshot["recent_events"]:
if not event_data is Dictionary:
return false
var event := SimulationDomainEvent.from_dictionary(event_data)
if not event.is_valid() or not restored_ids.has(event.event_id):
return false
restored_recent.append(event)
if restored_recent.size() > RECENT_EVENT_LIMIT:
return false
_published_ids = restored_ids
_snapshot_ids_dirty = true
_recent_events = restored_recent
_pending_deliveries.clear()
return true
func _collect_callbacks(event_type: StringName, target: Dictionary) -> void:
if not _subscribers.has(event_type):
return
var source: Dictionary = _subscribers[event_type]
for subscriber_id: Variant in source:
if not target.has(subscriber_id):
target[subscriber_id] = source[subscriber_id]