class_name FacilityService extends RefCounted signal instance_registered(instance: FacilityInstance) signal instance_unregistered(instance_id: StringName) signal facility_state_changed(instance_id: StringName, old_state: int, new_state: int, reasons: Array[StringName]) signal request_enqueued(request: ServiceRequest) signal request_left_queue(request_id: StringName, reason: StringName) signal service_started(service: ServiceInstance) signal service_interrupted(service: ServiceInstance, reason: StringName) signal service_completed(service: ServiceInstance) signal service_failed(service: ServiceInstance, reason: StringName) const FacilityInstanceType = preload("res://scripts/facility/facility_instance.gd") const ServiceRequestType = preload("res://scripts/facility/service_request.gd") const ServiceInstanceType = preload("res://scripts/facility/service_instance.gd") const CommandResultType = preload("res://scripts/core/command_result.gd") const FacilityPricingType = preload("res://scripts/facility/facility_pricing.gd") const SNAPSHOT_VERSION := 1 const STAFF_REPLACEMENT_GRACE_MINUTES := 5 const INTERRUPTION_PLAYER_CLOSED := &"PLAYER_CLOSED" const INTERRUPTION_NO_POWER := &"NO_POWER" const INTERRUPTION_MANA_STOP := &"MANA_STOP" const INTERRUPTION_STAFF_UNAVAILABLE := &"STAFF_UNAVAILABLE" const INTERRUPTION_EMERGENCY := &"EMERGENCY" const INTERRUPTION_FIRE := &"FIRE" const INTERRUPTION_RIOT := &"RIOT" const INTERRUPTION_GUEST_LEFT := &"GUEST_LEFT" const INTERRUPTION_BROKEN := &"BROKEN" var current_minute := 0 var last_error := "" var _instances: Dictionary = {} var _requests: Dictionary = {} var _known_instance_ids: Dictionary = {} var _known_request_ids: Dictionary = {} var _known_service_ids: Dictionary = {} var _service_history: Dictionary = {} var _next_service_sequence := 1 var _start_validator := Callable() var _completion_callback := Callable() func configure_callbacks( start_validator: Callable = Callable(), completion_callback: Callable = Callable() ) -> FacilityService: _start_validator = start_validator _completion_callback = completion_callback return self func set_start_validator(callback: Callable) -> void: _start_validator = callback func set_completion_callback(callback: Callable) -> void: _completion_callback = callback func register_instance(instance: Variant) -> CommandResult: if instance == null or not instance is FacilityInstance: return _invalid("Facility instance is missing or has an invalid type.") var facility: FacilityInstance = instance if not facility.is_valid(): return _invalid("Facility instance is invalid.", {&"instance_id": facility.instance_id}) if _known_instance_ids.has(facility.instance_id): return _duplicate("Facility instance", facility.instance_id) if not facility.queue_entries.is_empty() or not facility.active_services.is_empty(): return _invalid( "A new facility must be registered before it owns requests or services.", {&"instance_id": facility.instance_id} ) _instances[facility.instance_id] = facility _known_instance_ids[facility.instance_id] = true facility.evaluate_state(current_minute) instance_registered.emit(facility) last_error = "" return _success({&"instance_id": facility.instance_id}) func create_instance(configuration: Dictionary) -> CommandResult: var instance := FacilityInstanceType.new().configure( StringName(_value(configuration, &"instance_id", &"")), StringName(_value(configuration, &"definition_id", &"")), configuration ) var result := register_instance(instance) if result.is_success(): result.details[&"instance"] = instance return result func has_instance(instance_id: StringName) -> bool: return _instances.has(instance_id) func get_instance(instance_id: StringName) -> FacilityInstance: return _instances.get(instance_id) as FacilityInstance func get_facility(instance_id: StringName) -> FacilityInstance: return get_instance(instance_id) func get_instances() -> Array[FacilityInstance]: var result: Array[FacilityInstance] = [] for instance_id: StringName in _sorted_keys(_instances): result.append(_instances[instance_id]) return result func unregister_instance(instance_id: StringName) -> CommandResult: var instance := get_instance(instance_id) if instance == null: return _not_found("Facility instance", instance_id) if not instance.queue_entries.is_empty() or not instance.active_services.is_empty(): return _invalid( "A facility with queued or active services cannot be unregistered.", {&"instance_id": instance_id} ) _instances.erase(instance_id) refresh_operating_dependencies(current_minute) instance_unregistered.emit(instance_id) last_error = "" return _success({&"instance_id": instance_id}) func set_operational_status( instance_id: StringName, conditions: Dictionary, absolute_minute: int = -1 ) -> CommandResult: var instance := get_instance(instance_id) if instance == null: return _not_found("Facility instance", instance_id) var old_state := instance.state if _has_value(conditions, &"related_facility_id"): var raw_related: Variant = _value(conditions, &"related_facility_id", null) if typeof(raw_related) not in [TYPE_STRING, TYPE_STRING_NAME]: return _invalid("Related facility must be one facility ID.") var related := StringName(raw_related) if related == instance_id or (not related.is_empty() and not has_instance(related)): return _invalid("Related facility is missing or refers to itself.") if instance.requires_operating_kitchen() and not related.is_empty(): var kitchen := get_instance(related) if kitchen.definition_id != &"kitchen" or kitchen.origin.z != WorldService.LAYER_BACK: return _invalid("Dining facilities require one existing BACK kitchen.", {&"reason": &"INVALID_RELATED_KITCHEN"}) if not _required_related_placement_matches(instance, kitchen): return _invalid("The BACK kitchen must lie inside this FRONT dining facility's XY footprint.", {&"reason": &"INVALID_RELATED_KITCHEN_PLACEMENT"}) var seen := {instance_id: true} var cursor := related while not cursor.is_empty() and has_instance(cursor): if seen.has(cursor): return _invalid("Related facilities cannot form a cycle.") seen[cursor] = true cursor = get_instance(cursor).related_facility_id if _has_value(conditions, &"related_facility_operating") or _has_value(conditions, &"operation_state"): return _invalid("Operating flags are computed from actual facility conditions.") if _has_value(conditions, &"suspension_reason") and (typeof(_value(conditions, &"suspension_reason", null)) not in [TYPE_STRING, TYPE_STRING_NAME] or StringName(_value(conditions, &"suspension_reason", &"")) not in [&"DESTROYED", &"RAIDED"]): return _invalid("Suspension recovery is not specified.") var minute := _resolve_minute(absolute_minute) instance.set_operational_conditions(conditions, minute) if old_state != instance.state or _has_value(conditions, &"related_facility_id"): refresh_operating_dependencies(minute) _emit_state_change(instance, old_state) last_error = "" return _success({ &"instance_id": instance_id, &"state": FacilityInstanceType.State.keys()[instance.state], &"state_reasons": instance.state_reasons.duplicate(), }) func set_opening_hours( instance_id: StringName, open_minute_of_day: int, close_minute_of_day: int, absolute_minute: int = -1 ) -> CommandResult: var instance := get_instance(instance_id) if instance == null: return _not_found("Facility instance", instance_id) if not instance.set_opening_hours(open_minute_of_day, close_minute_of_day): return _invalid("Opening hours must be valid minute-of-day values.") var old_state := instance.state instance.evaluate_state(_resolve_minute(absolute_minute)) refresh_operating_dependencies(current_minute) _emit_state_change(instance, old_state) last_error = "" return _success({&"instance_id": instance_id}) func set_policy( instance_id: StringName, patch: Dictionary, effective_at: int = -1 ) -> CommandResult: var instance := get_instance(instance_id) if instance == null: return _not_found("Facility instance", instance_id) if patch.is_empty(): return _invalid("Policy patch cannot be empty.") var validation := FacilityPricingType.validate_patch(patch) if not bool(validation[&"valid"]): return _invalid(String(validation[&"message"]), {&"reason": validation[&"reason"]}) var normalized_patch: Dictionary = validation[&"patch"] if effective_at >= 0 and effective_at > current_minute: instance.schedule_policy_patch(normalized_patch, effective_at) last_error = "" return _success({ &"instance_id": instance_id, &"pending_revision": instance.policy_pending_revision, &"effective_at": effective_at, }) var changed := instance.apply_policy_patch(normalized_patch) var old_state := instance.state instance.evaluate_state(current_minute) refresh_operating_dependencies(current_minute) _emit_state_change(instance, old_state) last_error = "" return _success({ &"instance_id": instance_id, &"policy_revision": instance.policy_revision, &"changed": changed, }) func create_request( request_id: StringName, facility_id: StringName, guest_ids: Array[StringName], service_type: StringName, created_at: int, options: Dictionary = {} ) -> ServiceRequest: var instance := get_instance(facility_id) if instance == null: return null var quotation := options.duplicate(true) if not _has_value(quotation, &"quoted_price"): quotation[&"quoted_price"] = FacilityPricingType.quote(instance.base_price, instance.policy_current) if not _has_value(quotation, &"expected_quality"): quotation[&"expected_quality"] = instance.base_quality if not _has_value(quotation, &"service_minutes"): quotation[&"service_minutes"] = instance.base_service_minutes quotation[&"policy_snapshot"] = instance.policy_current.duplicate(true) quotation[&"policy_revision"] = instance.policy_revision return ServiceRequestType.new().configure( request_id, facility_id, guest_ids, service_type, created_at, quotation ) func enqueue_request(request: Variant, absolute_minute: int = -1, commit_clock: bool = true) -> CommandResult: if request == null or not request is ServiceRequest: return _invalid("Service request is missing or has an invalid type.") var queued_request: ServiceRequest = request if not queued_request.is_valid(): return _invalid("Service request is invalid.") if _known_request_ids.has(queued_request.request_id): return _duplicate("Service request", queued_request.request_id) var instance := get_instance(queued_request.facility_id) if instance == null: return _not_found("Facility instance", queued_request.facility_id) if not instance.has_queue_space(): return _invalid( "Facility queue is full.", {&"instance_id": instance.instance_id, &"queue_capacity": instance.queue_capacity} ) # Queue registration can precede the Clock increment in APPLY_COMMANDS. # Preserve the request effective time without claiming participant advancement. if not commit_clock and absolute_minute != current_minute + 1: return _invalid("Deferred queue registration must target the next uncommitted minute.") var minute := _resolve_minute(absolute_minute) if commit_clock else absolute_minute queued_request.enqueued_at = minute _requests[queued_request.request_id] = queued_request _known_request_ids[queued_request.request_id] = true instance.queue_entries.append(queued_request.request_id) _sort_queue(instance) queued_request.expected_wait = _estimate_wait_before(instance, queued_request.request_id) _increment_stat(instance.statistics_today, &"requests_enqueued") _increment_stat(instance.lifetime_statistics, &"requests_enqueued") request_enqueued.emit(queued_request) last_error = "" return _success({ &"request_id": queued_request.request_id, &"expected_wait": queued_request.expected_wait, &"queue_position": instance.queue_entries.find(queued_request.request_id), }) func submit_request(request: Variant, absolute_minute: int = -1) -> CommandResult: return enqueue_request(request, absolute_minute) func get_request(request_id: StringName) -> ServiceRequest: return _requests.get(request_id) as ServiceRequest func get_queue(instance_id: StringName) -> Array[ServiceRequest]: var queued: Array[ServiceRequest] = [] var instance := get_instance(instance_id) if instance == null: return queued for request_id: StringName in instance.queue_entries: var request := get_request(request_id) if request != null: queued.append(request) return queued func peek_next_request(instance_id: StringName) -> ServiceRequest: var instance := get_instance(instance_id) if instance == null or instance.queue_entries.is_empty(): return null return get_request(instance.queue_entries[0]) func leave_queue( instance_id: StringName, request_id: StringName, reason: StringName = &"GUEST_LEFT" ) -> CommandResult: var instance := get_instance(instance_id) if instance == null: return _not_found("Facility instance", instance_id) var queue_index := instance.queue_entries.find(request_id) if queue_index < 0: return _not_found("Queued service request", request_id) instance.queue_entries.remove_at(queue_index) _requests.erase(request_id) _increment_stat(instance.statistics_today, &"queue_departures") _increment_stat(instance.lifetime_statistics, &"queue_departures") request_left_queue.emit(request_id, reason) last_error = "" return _success({&"request_id": request_id, &"reason": reason}) func cancel_request(request_id: StringName, reason: StringName = &"GUEST_LEFT") -> CommandResult: for instance_id: StringName in _sorted_keys(_instances): var instance: FacilityInstance = _instances[instance_id] if instance.queue_entries.has(request_id): return leave_queue(instance_id, request_id, reason) return _not_found("Queued service request", request_id) func _required_related_kind_matches(instance: FacilityInstance) -> bool: if not instance.requires_operating_kitchen(): return true var kitchen := get_instance(instance.related_facility_id) return kitchen != null and kitchen.definition_id == &"kitchen" and kitchen.origin.z == WorldService.LAYER_BACK # Original 2.2 and 8.3 put a BACK kitchen behind the FRONT dining venue. # XY containment is Root's operational interpretation, not a width/origin-x # equality rule. Use actual occupied cells; do not synthesize missing geometry, # rewrite old IDs, or persist this derived predicate. func _required_related_placement_matches(instance: FacilityInstance, proposed_kitchen: FacilityInstance = null) -> bool: if not instance.requires_operating_kitchen(): return true var kitchen: FacilityInstance = proposed_kitchen if proposed_kitchen != null else get_instance(instance.related_facility_id) if kitchen == null or kitchen.definition_id != &"kitchen" \ or instance.origin.z != WorldService.LAYER_FRONT or kitchen.origin.z != WorldService.LAYER_BACK \ or instance.occupied_cells.is_empty() or kitchen.occupied_cells.is_empty(): return false var front_xy: Dictionary = {} for cell: Vector3i in instance.occupied_cells: if cell.z != WorldService.LAYER_FRONT: return false front_xy[Vector2i(cell.x, cell.y)] = true for cell: Vector3i in kitchen.occupied_cells: if cell.z != WorldService.LAYER_BACK or not front_xy.has(Vector2i(cell.x, cell.y)): return false return true # Read-only derived eligibility. Legacy cached state/IDs remain untouched until # actual gameplay evaluates a facility or advances one minute. func get_required_kitchen_report(instance_id: StringName) -> Dictionary: var instance := get_instance(instance_id) if instance == null: return {} var required := instance.requires_operating_kitchen() var related := instance.related_facility_id var kitchen := get_instance(related) var valid_kind := _required_related_kind_matches(instance) var valid_placement := _required_related_placement_matches(instance) var operating := not required or (valid_placement and _restored_dependency_operates(related, {})) var reason: StringName = &"" if required: if related.is_empty(): reason = &"KITCHEN_NOT_LINKED" elif kitchen == null: reason = &"KITCHEN_REMOVED" elif not valid_kind: reason = &"INVALID_RELATED_KITCHEN" elif not valid_placement: reason = &"INVALID_RELATED_KITCHEN_PLACEMENT" elif not kitchen.suspension_reason.is_empty(): reason = &"KITCHEN_SUSPENDED" elif not operating: reason = &"KITCHEN_INACTIVE" return {&"required": required, &"related_facility_id": related, &"exists": kitchen != null, &"valid_kind": valid_kind, &"operating": operating, &"reason": reason} # Read-only eligibility of a proposed link. Does not select a kitchen, alter an # existing related ID, evaluate state, advance time, or clear persistent suspension. func get_kitchen_placement_report(instance_id: StringName, kitchen_id: StringName) -> Dictionary: var instance := get_instance(instance_id) var kitchen := get_instance(kitchen_id) var required := instance != null and instance.requires_operating_kitchen() var valid_kind := kitchen != null and kitchen.definition_id == &"kitchen" and kitchen.origin.z == WorldService.LAYER_BACK var valid_placement := required and valid_kind and _required_related_placement_matches(instance, kitchen) var reason: StringName = &"" if instance == null: reason = &"FACILITY_NOT_FOUND" elif not required: reason = &"KITCHEN_NOT_REQUIRED" elif kitchen == null: reason = &"KITCHEN_REMOVED" elif not valid_kind: reason = &"INVALID_RELATED_KITCHEN" elif not valid_placement: reason = &"INVALID_RELATED_KITCHEN_PLACEMENT" return {&"facility_id": instance_id, &"kitchen_id": kitchen_id, &"required": required, &"exists": kitchen != null, &"valid_kind": valid_kind, &"valid_placement": valid_placement, &"reason": reason} func refresh_operating_dependencies(minute: int) -> void: var resolved: Dictionary = {} for instance_id: StringName in _sorted_keys(_instances): _resolve_operating_dependency(instance_id, minute, resolved, {}) func _resolve_operating_dependency(id: StringName, minute: int, resolved: Dictionary, visiting: Dictionary) -> bool: if resolved.has(id): return bool(resolved[id]) if visiting.has(id) or not has_instance(id): return false visiting[id] = true var instance := get_instance(id) instance.apply_pending_policy(minute) var related := instance.related_facility_id instance.related_facility_operating = _required_related_placement_matches(instance) and (related.is_empty() or _resolve_operating_dependency(related, minute, resolved, visiting)) var old_state := instance.state instance.evaluate_state(minute) _emit_state_change(instance, old_state) visiting.erase(id) resolved[id] = instance.operation_state == FacilityInstanceType.OperationState.OPERATING return bool(resolved[id]) func start_next_service( instance_id: StringName, absolute_minute: int = -1, validator: Callable = Callable() ) -> CommandResult: var instance := get_instance(instance_id) if instance == null: return _not_found("Facility instance", instance_id) var minute := _resolve_minute(absolute_minute) _resolve_operating_dependency(instance_id, minute, {}, {}) instance.apply_pending_policy(minute) var old_state := instance.state instance.evaluate_state(minute) _emit_state_change(instance, old_state) if not instance.can_start_service(minute): return _invalid( "Facility cannot start a service in its current state.", { &"instance_id": instance_id, &"state": FacilityInstanceType.State.keys()[instance.state], &"reasons": instance.state_reasons.duplicate(), } ) _cleanup_missing_queue_requests(instance) if instance.queue_entries.is_empty(): return _not_found("Queued service request", &"") var request := get_request(instance.queue_entries[0]) if request == null: return _invalid("The next queue entry is corrupt.") if not instance.can_accept_customers(request.guest_ids): return _invalid("Simultaneous customer limit reached.", {&"reason": &"CUSTOMER_CAPACITY"}) var effective_validator := validator if validator.is_valid() else _start_validator var callback_failure := _run_boundary_callback( effective_validator, [request, instance, minute], "Service start validation failed.", CommandResultType.Code.VALIDATION_FAILED ) if callback_failure != null: callback_failure.details[&"request_id"] = request.request_id callback_failure.details[&"instance_id"] = instance_id return callback_failure var service_id := _next_service_id() var service := ServiceInstanceType.new().configure( service_id, request.request_id, instance_id, minute, request.service_minutes if request.service_minutes > 0 else instance.base_service_minutes, request.guest_ids, instance.active_staff_ids, { &"quality_snapshot": request.expected_quality, &"price_snapshot": request.quoted_price, &"policy_snapshot": request.policy_snapshot, &"inventory_reservations": request.reserved_inventory_ids, } ) instance.queue_entries.remove_at(0) # Report this start's service index, independent of customer headcount and callbacks. var slot := instance.active_services.size() instance.active_services[service_id] = service _known_service_ids[service_id] = true _increment_stat(instance.statistics_today, &"services_started") _increment_stat(instance.lifetime_statistics, &"services_started") service_started.emit(service) last_error = "" return _success({ &"instance_id": instance_id, &"request_id": request.request_id, &"service_id": service_id, &"slot": slot, }) func fill_available_slots( instance_id: StringName, absolute_minute: int = -1, validator: Callable = Callable() ) -> Array[CommandResult]: var results: Array[CommandResult] = [] var instance := get_instance(instance_id) if instance == null: results.append(_not_found("Facility instance", instance_id)) return results var minute := _resolve_minute(absolute_minute) while instance.available_slots() > 0 and not instance.queue_entries.is_empty(): var result := start_next_service(instance_id, minute, validator) results.append(result) if not result.is_success(): break return results func advance_one_minute(absolute_minute: int = -1) -> Array[CommandResult]: if absolute_minute < 0: current_minute += 1 elif absolute_minute < current_minute: return [_invalid("FacilityService cannot advance backwards in time.")] else: current_minute = absolute_minute refresh_operating_dependencies(current_minute) var results: Array[CommandResult] = [] for instance_id: StringName in _sorted_keys(_instances): var instance: FacilityInstance = _instances[instance_id] instance.apply_pending_policy(current_minute) var old_state := instance.state instance.evaluate_state(current_minute) _emit_state_change(instance, old_state) var active_ids := _sorted_keys(instance.active_services) for service_id: StringName in active_ids: if not instance.active_services.has(service_id): continue var service: ServiceInstance = instance.active_services[service_id] var preparation := _prepare_service_for_tick(instance, service) if preparation != null: results.append(preparation) if not instance.active_services.has(service_id): continue if service.state != ServiceInstanceType.State.ACTIVE: continue var ready_to_complete := service.elapsed_minutes >= service.duration_minutes if not ready_to_complete: ready_to_complete = service.advance_one_minute() if ready_to_complete: results.append(_complete_service(instance, service)) var starts := fill_available_slots(instance_id, current_minute) results.append_array(starts) last_error = "" return results func interrupt_service( service_id: StringName, reason: StringName, absolute_minute: int = -1 ) -> CommandResult: var located := _find_active_service(service_id) if located.is_empty(): return _not_found("Active service", service_id) var instance: FacilityInstance = located[&"instance"] var service: ServiceInstance = located[&"service"] var minute := _resolve_minute(absolute_minute) match reason: INTERRUPTION_PLAYER_CLOSED: return _success({&"service_id": service_id, &"continues": true}) INTERRUPTION_NO_POWER, INTERRUPTION_MANA_STOP: service.pause(INTERRUPTION_NO_POWER, minute) INTERRUPTION_STAFF_UNAVAILABLE: service.wait_for_staff(reason, minute, STAFF_REPLACEMENT_GRACE_MINUTES) INTERRUPTION_BROKEN, INTERRUPTION_EMERGENCY, INTERRUPTION_FIRE, INTERRUPTION_RIOT, INTERRUPTION_GUEST_LEFT: _fail_service(instance, service, reason, minute) return _success({&"service_id": service_id, &"failed": true, &"reason": reason}) _: return _invalid("Unknown service interruption reason.", {&"reason": reason}) service_interrupted.emit(service, reason) last_error = "" return _success({ &"service_id": service_id, &"state": ServiceInstanceType.State.keys()[service.state], &"reason": reason, }) func resume_service(service_id: StringName) -> CommandResult: var located := _find_active_service(service_id) if located.is_empty(): return _not_found("Active service", service_id) var service: ServiceInstance = located[&"service"] if not service.resume(): return _invalid("Only an interrupted service can resume.") last_error = "" return _success({&"service_id": service_id}) func replace_service_staff(service_id: StringName, staff_ids: Array[StringName]) -> CommandResult: var located := _find_active_service(service_id) if located.is_empty(): return _not_found("Active service", service_id) if staff_ids.is_empty(): return _invalid("Replacement staff list cannot be empty.") var service: ServiceInstance = located[&"service"] service.staff_ids.assign(staff_ids) if service.state == ServiceInstanceType.State.WAITING_FOR_STAFF: service.resume() last_error = "" return _success({&"service_id": service_id}) func get_service(service_id: StringName) -> ServiceInstance: var located := _find_active_service(service_id) if not located.is_empty(): return located[&"service"] as ServiceInstance return _service_history.get(service_id) as ServiceInstance func get_active_services(instance_id: StringName) -> Array[ServiceInstance]: var services: Array[ServiceInstance] = [] var instance := get_instance(instance_id) if instance == null: return services for service_id: StringName in _sorted_keys(instance.active_services): services.append(instance.active_services[service_id]) return services func create_snapshot() -> Dictionary: var saved_instances: Array[Dictionary] = [] for instance_id: StringName in _sorted_keys(_instances): var instance: FacilityInstance = _instances[instance_id] saved_instances.append(instance.create_snapshot()) var saved_requests: Array[Dictionary] = [] for request_id: StringName in _sorted_keys(_requests): var request: ServiceRequest = _requests[request_id] saved_requests.append(request.create_snapshot()) var saved_history: Array[Dictionary] = [] for service_id: StringName in _sorted_keys(_service_history): var service: ServiceInstance = _service_history[service_id] saved_history.append(service.create_snapshot()) return { &"schema_version": SNAPSHOT_VERSION, &"current_minute": current_minute, &"next_service_sequence": _next_service_sequence, &"known_instance_ids": _keys_to_strings(_known_instance_ids), &"known_request_ids": _keys_to_strings(_known_request_ids), &"known_service_ids": _keys_to_strings(_known_service_ids), &"instances": saved_instances, &"requests": saved_requests, &"service_history": saved_history, } func restore_snapshot(snapshot: Dictionary) -> bool: if int(_value(snapshot, &"schema_version", 0)) != SNAPSHOT_VERSION: last_error = "Unsupported FacilityService snapshot version." return false var array_fields := [ &"known_instance_ids", &"known_request_ids", &"known_service_ids", &"instances", &"requests", &"service_history", ] for field: StringName in array_fields: if not _value(snapshot, field, null) is Array: last_error = "FacilityService snapshot field '%s' must be an Array." % field return false var restored_instances: Dictionary = {} for saved_instance: Variant in _value(snapshot, &"instances", []): if not saved_instance is Dictionary: last_error = "Facility snapshot entry must be a Dictionary." return false var instance := FacilityInstanceType.from_snapshot(saved_instance) if instance == null or restored_instances.has(instance.instance_id): last_error = "Facility snapshot contains an invalid or duplicate instance." return false if not _saved_policy_is_valid(instance.policy_current) or not _saved_policy_is_valid(instance.policy_pending): return false for active: ServiceInstance in instance.active_services.values(): if not _saved_policy_is_valid(active.policy_snapshot): return false restored_instances[instance.instance_id] = instance var restored_requests: Dictionary = {} for saved_request: Variant in _value(snapshot, &"requests", []): if not saved_request is Dictionary: last_error = "Request snapshot entry must be a Dictionary." return false var request := ServiceRequestType.from_snapshot(saved_request) if request == null or restored_requests.has(request.request_id): last_error = "Facility snapshot contains an invalid or duplicate request." return false if not _saved_policy_is_valid(request.policy_snapshot): return false restored_requests[request.request_id] = request var restored_history: Dictionary = {} for saved_service: Variant in _value(snapshot, &"service_history", []): if not saved_service is Dictionary: last_error = "Service history entry must be a Dictionary." return false var service := ServiceInstanceType.from_snapshot(saved_service) if service == null or not service.is_terminal() or restored_history.has(service.service_id): last_error = "Facility snapshot contains invalid service history." return false if not _saved_policy_is_valid(service.policy_snapshot): return false restored_history[service.service_id] = service var restored_known_instances: Variant = _id_set(_value(snapshot, &"known_instance_ids", [])) var restored_known_requests: Variant = _id_set(_value(snapshot, &"known_request_ids", [])) var restored_known_services: Variant = _id_set(_value(snapshot, &"known_service_ids", [])) if restored_known_instances == null or restored_known_requests == null or restored_known_services == null: last_error = "Known ID lists contain an empty or duplicate ID." return false if not _validate_restored_graph( restored_instances, restored_requests, restored_history, restored_known_instances, restored_known_requests, restored_known_services ): return false var restored_next_sequence := int(_value(snapshot, &"next_service_sequence", 0)) var restored_minute := int(_value(snapshot, &"current_minute", -1)) if restored_next_sequence < 1 or restored_minute < 0: last_error = "FacilityService counters are invalid." return false # Commit only after every object and cross-reference has validated. _instances = restored_instances _requests = restored_requests _service_history = restored_history _known_instance_ids = restored_known_instances _known_request_ids = restored_known_requests _known_service_ids = restored_known_services _next_service_sequence = restored_next_sequence current_minute = restored_minute for instance: FacilityInstance in _instances.values(): instance.related_facility_operating = _required_related_placement_matches(instance) and _restored_dependency_operates(instance.related_facility_id, {}) instance.operation_state = FacilityInstanceType.OperationState.SUSPENDED if not instance.suspension_reason.is_empty() else (FacilityInstanceType.OperationState.OPERATING if instance.state in [FacilityInstanceType.State.OPEN, FacilityInstanceType.State.DEGRADED] and instance.related_facility_operating else FacilityInstanceType.OperationState.INACTIVE) last_error = "" return true func _restored_dependency_operates(id: StringName, seen: Dictionary) -> bool: if id.is_empty(): return true if seen.has(id) or not has_instance(id): return false seen[id] = true var instance := get_instance(id) return _required_related_placement_matches(instance) and instance.suspension_reason.is_empty() and instance.state in [FacilityInstanceType.State.OPEN, FacilityInstanceType.State.DEGRADED] and _restored_dependency_operates(instance.related_facility_id, seen) func _saved_policy_is_valid(policy: Dictionary) -> bool: var validation := FacilityPricingType.validate_patch(policy) if not bool(validation[&"valid"]): last_error = "Facility snapshot contains an invalid policy: %s" % validation[&"reason"] return false return true func _validate_restored_graph( instances: Dictionary, requests: Dictionary, history: Dictionary, known_instances: Dictionary, known_requests: Dictionary, known_services: Dictionary ) -> bool: var referenced_requests: Dictionary = {} var active_service_ids: Dictionary = {} for instance_id: StringName in _sorted_keys(instances): var instance: FacilityInstance = instances[instance_id] var dependency_seen: Dictionary = {instance_id: true} var dependency := instance.related_facility_id while not dependency.is_empty() and instances.has(dependency): if dependency_seen.has(dependency): last_error = "Related facility cycle in snapshot." return false dependency_seen[dependency] = true dependency = (instances[dependency] as FacilityInstance).related_facility_id if not known_instances.has(instance_id): last_error = "Active facility ID is absent from known IDs." return false for request_id: StringName in instance.queue_entries: if referenced_requests.has(request_id) or not requests.has(request_id): last_error = "Queue contains a duplicate or missing request." return false var request: ServiceRequest = requests[request_id] if request.facility_id != instance_id: last_error = "Queued request targets another facility." return false referenced_requests[request_id] = true for service_id: StringName in _sorted_keys(instance.active_services): var service: ServiceInstance = instance.active_services[service_id] if active_service_ids.has(service_id) or history.has(service_id): last_error = "Service ID occurs more than once." return false if not known_services.has(service_id) or not requests.has(service.request_id): last_error = "Active service has a missing known ID or request." return false if referenced_requests.has(service.request_id): last_error = "Request is referenced by multiple queue/service entries." return false var request: ServiceRequest = requests[service.request_id] if request.facility_id != instance_id: last_error = "Active service request targets another facility." return false referenced_requests[service.request_id] = true active_service_ids[service_id] = true for request_id: StringName in _sorted_keys(requests): if not known_requests.has(request_id) or not referenced_requests.has(request_id): last_error = "Live request is orphaned or absent from known IDs." return false for service_id: StringName in _sorted_keys(history): if not known_services.has(service_id): last_error = "Historical service is absent from known IDs." return false return true func _prepare_service_for_tick( instance: FacilityInstance, service: ServiceInstance ) -> CommandResult: if instance.state == FacilityInstanceType.State.BROKEN: _fail_service(instance, service, INTERRUPTION_BROKEN, current_minute) return _success({&"service_id": service.service_id, &"failed": true}) if not instance.related_facility_operating: if service.state == ServiceInstanceType.State.ACTIVE: service.pause(&"RELATED_FACILITY_INACTIVE", current_minute) service_interrupted.emit(service, &"RELATED_FACILITY_INACTIVE") return null if service.state == ServiceInstanceType.State.PAUSED and service.interruption_reason == &"RELATED_FACILITY_INACTIVE": service.resume() if not instance.power_available: if service.state == ServiceInstanceType.State.ACTIVE: service.pause(INTERRUPTION_NO_POWER, current_minute) service_interrupted.emit(service, INTERRUPTION_NO_POWER) return null if service.state == ServiceInstanceType.State.PAUSED \ and service.interruption_reason == INTERRUPTION_NO_POWER: service.resume() var has_required_staff := instance.active_staff_ids.size() >= instance.staff_min if not has_required_staff: if service.state in [ServiceInstanceType.State.ACTIVE, ServiceInstanceType.State.PAUSED]: service.wait_for_staff( INTERRUPTION_STAFF_UNAVAILABLE, current_minute, STAFF_REPLACEMENT_GRACE_MINUTES ) service_interrupted.emit(service, INTERRUPTION_STAFF_UNAVAILABLE) elif instance.staff_min > 0 \ and service.state == ServiceInstanceType.State.WAITING_FOR_STAFF \ and service.interruption_reason == INTERRUPTION_STAFF_UNAVAILABLE: service.resume() if service.state == ServiceInstanceType.State.WAITING_FOR_STAFF: if service.staff_grace_deadline >= 0 and current_minute >= service.staff_grace_deadline: _fail_service(instance, service, INTERRUPTION_STAFF_UNAVAILABLE, current_minute) return _success({&"service_id": service.service_id, &"failed": true}) return null func _complete_service( instance: FacilityInstance, service: ServiceInstance, callback: Callable = Callable() ) -> CommandResult: var request := get_request(service.request_id) if request == null: _fail_service(instance, service, &"MISSING_REQUEST", current_minute) return _invalid( "Active service lost its request and was failed safely.", {&"service_id": service.service_id} ) var effective_callback := callback if callback.is_valid() else _completion_callback var callback_failure := _run_boundary_callback( effective_callback, [service, request, instance, current_minute], "Service completion transaction failed.", CommandResultType.Code.TRANSACTION_APPLY_FAILED ) if callback_failure != null: callback_failure.details[&"service_id"] = service.service_id callback_failure.details[&"request_id"] = request.request_id return callback_failure service.mark_completed(current_minute) instance.active_services.erase(service.service_id) _requests.erase(request.request_id) _service_history[service.service_id] = service _increment_stat(instance.statistics_today, &"services_completed") _increment_stat(instance.lifetime_statistics, &"services_completed") service_completed.emit(service) return _success({ &"instance_id": instance.instance_id, &"request_id": request.request_id, &"service_id": service.service_id, &"completed": true, }) func _fail_service( instance: FacilityInstance, service: ServiceInstance, reason: StringName, absolute_minute: int ) -> void: service.mark_failed(reason, absolute_minute) instance.active_services.erase(service.service_id) _requests.erase(service.request_id) _service_history[service.service_id] = service _increment_stat(instance.statistics_today, &"services_failed") _increment_stat(instance.lifetime_statistics, &"services_failed") service_failed.emit(service, reason) func _run_boundary_callback( callback: Callable, arguments: Array, fallback_message: String, failure_code: CommandResult.Code ) -> CommandResult: if not callback.is_valid(): return null var argument_count := callback.get_argument_count() var effective_arguments := arguments if argument_count >= 0 and argument_count < arguments.size(): effective_arguments = arguments.slice(0, argument_count) var returned: Variant = callback.callv(effective_arguments) if returned == null or (returned is bool and returned): return null if returned is CommandResult: var command_result: CommandResult = returned return null if command_result.is_success() else command_result.copy() if returned is Dictionary: if bool(returned.get(&"ok", returned.get("ok", false))): return null var dictionary_message := String( returned.get(&"message", returned.get("message", fallback_message)) ) return CommandResultType.failed(failure_code, 0, dictionary_message, returned) var message := String(returned) if returned is String and not returned.is_empty() else fallback_message return CommandResultType.failed(failure_code, 0, message) func _estimate_wait_before(instance: FacilityInstance, target_request_id: StringName) -> int: var outstanding_minutes := 0 for service_id: StringName in _sorted_keys(instance.active_services): var service: ServiceInstance = instance.active_services[service_id] outstanding_minutes += service.remaining_minutes() for request_id: StringName in instance.queue_entries: if request_id == target_request_id: break var request := get_request(request_id) if request != null: outstanding_minutes += request.service_minutes return ceili(float(outstanding_minutes) / float(maxi(instance.capacity, 1))) func _cleanup_missing_queue_requests(instance: FacilityInstance) -> void: var valid_ids: Array[StringName] = [] for request_id: StringName in instance.queue_entries: if _requests.has(request_id): valid_ids.append(request_id) else: push_warning( "Removed corrupt FacilityService queue entry '%s' from '%s'." \ % [request_id, instance.instance_id] ) instance.queue_entries = valid_ids _sort_queue(instance) func _sort_queue(instance: FacilityInstance) -> void: instance.queue_entries.sort_custom(Callable(self, "_queue_entry_less")) func _queue_entry_less(left_id: StringName, right_id: StringName) -> bool: var left := get_request(left_id) var right := get_request(right_id) if left == null: return false if right == null: return true if left.priority != right.priority: return left.priority > right.priority if left.enqueued_at != right.enqueued_at: return left.enqueued_at < right.enqueued_at return String(left.request_id) < String(right.request_id) func _find_active_service(service_id: StringName) -> Dictionary: for instance_id: StringName in _sorted_keys(_instances): var instance: FacilityInstance = _instances[instance_id] if instance.active_services.has(service_id): return { &"instance": instance, &"service": instance.active_services[service_id], } return {} func _next_service_id() -> StringName: while true: var candidate := StringName("service_%08d" % _next_service_sequence) _next_service_sequence += 1 if not _known_service_ids.has(candidate): return candidate return &"" func _resolve_minute(absolute_minute: int) -> int: if absolute_minute >= 0: current_minute = maxi(current_minute, absolute_minute) return current_minute func _emit_state_change(instance: FacilityInstance, old_state: int) -> void: if old_state != instance.state: facility_state_changed.emit( instance.instance_id, old_state, instance.state, instance.state_reasons.duplicate() ) func _success(details: Dictionary = {}) -> CommandResult: return CommandResultType.succeeded(0, details) func _invalid(message: String, details: Dictionary = {}) -> CommandResult: last_error = message return CommandResultType.failed( CommandResultType.Code.VALIDATION_FAILED, 0, message, details ) func _not_found(entity_name: String, entity_id: StringName) -> CommandResult: var message := "%s '%s' was not found." % [entity_name, entity_id] last_error = message return CommandResultType.failed( CommandResultType.Code.NOT_FOUND, 0, message, {&"id": entity_id} ) func _duplicate(entity_name: String, entity_id: StringName) -> CommandResult: var message := "%s '%s' already exists." % [entity_name, entity_id] last_error = message return CommandResultType.failed( CommandResultType.Code.DUPLICATE_COMMAND_ID, 0, message, {&"id": entity_id} ) static func _increment_stat(statistics: Dictionary, key: StringName) -> void: statistics[key] = int(statistics.get(key, 0)) + 1 static func _value(source: Dictionary, key: StringName, fallback: Variant) -> Variant: return source.get(key, source.get(String(key), fallback)) static func _has_value(source: Dictionary, key: StringName) -> bool: return source.has(key) or source.has(String(key)) static func _sorted_keys(source: Dictionary) -> Array[StringName]: var values: Array[StringName] = [] for value: Variant in source.keys(): values.append(StringName(value)) values.sort_custom(func(a: StringName, b: StringName) -> bool: return String(a) < String(b)) return values static func _keys_to_strings(source: Dictionary) -> Array[String]: var values: Array[String] = [] for key: StringName in _sorted_keys(source): values.append(String(key)) return values static func _id_set(source: Variant) -> Variant: if not source is Array: return null var ids: Dictionary = {} for raw_id: Variant in source: var id := StringName(raw_id) if id.is_empty() or ids.has(id): return null ids[id] = true return ids