// Callbacks — the C function pointers passed to `vc_callbacks`. These are the Swift // equivalent of the C# client's `[UnmanagedCallersOnly]` static methods (NativeCallbacks.cs). // // The critical patterns (carried over from the proven C# implementation): // 1. `@convention(c)` closures — plain C function pointers, NOT GC/ARC-managed closures. // A @convention(c) closure cannot capture context, which is why the `user` pointer is // used to resolve back to the VoiceCatClient instance (the C# version uses GCHandle for // the same thing; Swift uses Unmanaged). // 2. `Unmanaged.passUnretained(self).toOpaque()` as the `user` context — a stable raw // pointer to the Swift object WITHOUT incrementing the retain count. This is safe // because `deinit` calls `vc_client_destroy` (which synchronously joins every internal // thread) BEFORE the object's memory is freed — so no callback can fire after the object // is gone. (The C# equivalent: GCHandle.Alloc + GCHandle.Free in Dispose.) // 3. Copy `ev.text` to a Swift `String` INSIDE `onEvent` (via `VoiceCatEvent.from(_:)`) // before returning — the raw pointer is dangling after the callback returns. This is // the #1 lifetime rule from voicecat.h's vc_event doc comment. 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) } }