// C callbacks use an unretained `user` context. Client destruction joins callback threads, // and transient event pointers are copied before the callback returns. import VoiceCatC import Foundation /// Internal: builds the `vc_callbacks` struct wired to VoiceCatClient's C function pointers. /// The `user` context is an Unmanaged-passUnretained pointer to the client — resolved back /// to the client inside `onEvent`/`onLevel` below. internal enum Callbacks { /// The `on_event` C function pointer. Non-capturing @convention(c) closure — resolves /// the VoiceCatClient from `user` and enqueues a safe copy of the event. static let onEvent: @convention(c) ( UnsafeMutableRawPointer?, UnsafePointer? ) -> Void = { user, ev in guard let user, let ev else { return } let client = Unmanaged.fromOpaque(user).takeUnretainedValue() // Copy the event (including text) to a Swift value NOW — the raw vc_event is // invalid after this callback returns. client.enqueueEvent(VoiceCatEvent.from(ev.pointee)) } /// The `on_level` C function pointer. Coalesces to "latest sample per stream_id" /// (intermediate values are visually irrelevant — same as C#'s ConcurrentDictionary). static let onLevel: @convention(c) ( UnsafeMutableRawPointer?, UInt32, Float ) -> Void = { user, streamId, rms in guard let user else { return } let client = Unmanaged.fromOpaque(user).takeUnretainedValue() client.enqueueLevel(streamId, rms) } /// Construct the vc_callbacks struct for a given client. static func make(user: UnsafeMutableRawPointer) -> vc_callbacks { vc_callbacks(on_event: onEvent, on_level: onLevel, user: user) } }