Zum Inhalt

apple/PhysarumApp/Sources/Remote/AmbientHost.swift

import Foundation
import PhysarumEngine

/// The ATV-side application of decoded remote messages onto the model. A pure mapping (no
/// Network.framework) so the message→effect is host-testable; the NWListener (added per-platform)
/// feeds decoded messages through `apply`. The model (AppState/AmbientController) is main-only, like
/// the existing tick path — so the network layer MUST hop each delivery to the main queue before
/// calling in. The `dispatchPrecondition` enforces that contract: a stray off-main delivery would
/// race the SwiftUI render/tick, and this fails it loudly in dev/test instead of corrupting silently.
enum AmbientHost {
    /// Apply one decoded message to the same AmbientController sink the Siri Remote drives. Must run
    /// on the main queue.
    static func apply(_ message: RemoteMessage, to controller: AmbientController) {
        dispatchPrecondition(condition: .onQueue(.main))
        switch message {
        case .hello(let state):
            controller.adoptRemote(selectedID: state.selectedID, params: state.params, cycling: state.cycling)
        case .intent(let intent):
            controller.apply(intent)
        case .params(let p):
            controller.setLiveParams(p)   // transition-aware: a live edit survives an in-flight crossfade
        case .audioFactors(let frame):
            controller.receiveAudioFactors(frame)   // T2b: phone-streamed mic reactivity
        }
    }

    /// On disconnect (user action or network loss) the ATV resumes autonomous T1 cycling. Main queue.
    static func handleDisconnect(_ controller: AmbientController) {
        dispatchPrecondition(condition: .onQueue(.main))
        controller.resumeAutonomousCycle()
    }
}