// VoiceCatEvent — a Swift value type that is safe to hold/queue past the native callback's // return. This is the Swift analog of the C# client's `VoiceCatEvent` record. // // CRITICAL (voicecat.h's vc_event doc comment): the native `vc_event.text` pointer is owned // by the core and valid ONLY for the duration of the `on_event` callback. `from(_:)` copies // it to a Swift `String` immediately — never hold the raw `vc_event` across the callback // boundary, or `text` will be a dangling pointer by the time it's read. This is the #1 // lifetime rule carried over from the Windows client (NativeCallbacks.cs / VoiceCatEvent.cs). import VoiceCatC /// A Swift-safe copy of a `vc_event`. Produced inside the `on_event` callback (see /// Callbacks.swift) — all pointer fields are converted to value types before the callback /// returns. public struct VoiceCatEvent: Sendable, Equatable { public let type: VoiceCatEventType public let connectionState: VoiceCatConnectionState public let result: VoiceCatResult public let userId: UInt32 public let channelId: UInt32 public let streamId: UInt32 public let textScope: VoiceCatTextScope /// Generic small payload, meaning per event type. For `.serverIdentity` this is the /// `VoiceCatTofuStatus`; for `.genericResult` the server error code; for `.talkState` /// talking(0/1). public let u32a: UInt32 /// Copied from the core's `vc_event.text` inside the callback. nil if the core passed NULL. public let text: String? public let timestampUnixMs: UInt64 /// Convenience: the TOFU status, valid when `type == .serverIdentity` (maps `u32a`). public var tofuStatus: VoiceCatTofuStatus? { type == .serverIdentity ? VoiceCatTofuStatus(rawValue: u32a) : nil } /// Copy a native `vc_event` into a safe Swift value. MUST be called inside the callback /// while `ev.text` is still valid — `String(cString:)` copies the bytes here. @inline(__always) public static func from(_ ev: vc_event) -> VoiceCatEvent { let text: String? if let raw = ev.text { text = String(cString: raw) // copies — safe to hold past callback return } else { text = nil } // ev.result is int32_t (not vc_result) per voicecat.h — bridge via bitPattern. // ev.u32a is uint32_t — matches VoiceCatTofuStatus's UInt32 raw value directly. return VoiceCatEvent( type: VoiceCatEventType(ev.type), connectionState: VoiceCatConnectionState(ev.connection_state), result: VoiceCatResult(rawValue: UInt32(bitPattern: ev.result)) ?? .internalError, userId: ev.user_id, channelId: ev.channel_id, streamId: ev.stream_id, textScope: VoiceCatTextScope(ev.text_scope), u32a: ev.u32a, text: text, timestampUnixMs: ev.timestamp_unix_ms ) } }