class_name RoomLogicEngine extends RefCounted signal event_received( source_id: StringName, event_id: StringName ) signal rule_triggered( rule_id: StringName, source_id: StringName, event_id: StringName, target_id: StringName, action_id: StringName ) signal action_requested( target_id: StringName, action_id: StringName, rule_id: StringName ) signal trace_entry_created( entry: Dictionary ) var room: RoomDefinition var _pending_actions: Array[Dictionary] = [] var _simulation_time: float = 0.0 func configure( room_definition: RoomDefinition ) -> void: room = room_definition reset() func reset() -> void: _pending_actions.clear() _simulation_time = 0.0 func get_simulation_time() -> float: return _simulation_time func emit_event( source_id: StringName, event_id: StringName ) -> void: if room == null: return event_received.emit( source_id, event_id ) _create_trace_entry( &"event", { "source_id": source_id, "event_id": event_id, } ) for rule in room.logic_rules: if rule == null: continue if rule.source_id != source_id: continue if rule.event_id != event_id: continue _trigger_rule( rule ) func advance( delta_seconds: float ) -> void: if delta_seconds <= 0.0: return _simulation_time += delta_seconds _process_pending_actions() func _trigger_rule( rule: RoomLogicRule ) -> void: rule_triggered.emit( rule.rule_id, rule.source_id, rule.event_id, rule.target_id, rule.action_id ) _create_trace_entry( &"rule", { "rule_id": rule.rule_id, "source_id": rule.source_id, "event_id": rule.event_id, "target_id": rule.target_id, "action_id": rule.action_id, "delay_seconds": rule.delay_seconds, } ) if rule.delay_seconds <= 0.0: _request_action( rule.target_id, rule.action_id, rule.rule_id ) return _pending_actions.append( { "execute_at": ( _simulation_time + rule.delay_seconds ), "target_id": ( rule.target_id ), "action_id": ( rule.action_id ), "rule_id": ( rule.rule_id ), } ) func _process_pending_actions() -> void: if _pending_actions.is_empty(): return var ready_actions: Array[Dictionary] = [] var remaining_actions: Array[Dictionary] = [] for pending in _pending_actions: var execute_at: float = ( pending.get( "execute_at", 0.0 ) ) if execute_at <= _simulation_time: ready_actions.append( pending ) else: remaining_actions.append( pending ) _pending_actions = remaining_actions for pending in ready_actions: _request_action( pending.get( "target_id", &"" ), pending.get( "action_id", &"" ), pending.get( "rule_id", &"" ) ) func _request_action( target_id: StringName, action_id: StringName, rule_id: StringName ) -> void: _create_trace_entry( &"action", { "target_id": target_id, "action_id": action_id, "rule_id": rule_id, } ) action_requested.emit( target_id, action_id, rule_id ) func _create_trace_entry( entry_type: StringName, data: Dictionary ) -> void: var entry := data.duplicate() entry["type"] = entry_type entry["time"] = _simulation_time trace_entry_created.emit( entry ) func has_pending_actions() -> bool: return not _pending_actions.is_empty()