From a00acd90cae1ddaeacff9fec453e8b4a5b66e4c0 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Thu, 12 Mar 2026 15:57:48 -0700 Subject: [PATCH 01/12] Update swift-collections package version range --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 64ebe29fe..480447df5 100644 --- a/Package.swift +++ b/Package.swift @@ -22,7 +22,7 @@ let package = Package( .package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "144.7559.01"), .package(url: "https://github.com/livekit/livekit-uniffi-xcframework.git", exact: "0.0.5"), .package(url: "https://github.com/apple/swift-protobuf.git", from: "1.31.0"), - .package(url: "https://github.com/apple/swift-collections.git", "1.1.0" ..< "1.3.0"), + .package(url: "https://github.com/apple/swift-collections.git", "1.1.0" ..< "1.5.0"), // Only used for DocC generation .package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.3.0"), ], From 70d1562c5c12c1f133f05cd50ffedd128fd6e255 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Thu, 12 Mar 2026 15:58:55 -0700 Subject: [PATCH 02/12] Apply suggestion from @dfed --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 480447df5..17b904c52 100644 --- a/Package.swift +++ b/Package.swift @@ -22,7 +22,7 @@ let package = Package( .package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "144.7559.01"), .package(url: "https://github.com/livekit/livekit-uniffi-xcframework.git", exact: "0.0.5"), .package(url: "https://github.com/apple/swift-protobuf.git", from: "1.31.0"), - .package(url: "https://github.com/apple/swift-collections.git", "1.1.0" ..< "1.5.0"), + .package(url: "https://github.com/apple/swift-collections.git", "1.1.0" ..< "2.0.0"), // Only used for DocC generation .package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.3.0"), ], From f4afb6dab2cde6f2c2de6697d16b05dd8d0eac81 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Thu, 12 Mar 2026 16:01:56 -0700 Subject: [PATCH 03/12] Package@swift-6.0.swift too --- Package@swift-6.0.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index debe7873a..dfe189aea 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -22,7 +22,7 @@ let package = Package( .package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "144.7559.01"), .package(url: "https://github.com/livekit/livekit-uniffi-xcframework.git", exact: "0.0.5"), .package(url: "https://github.com/apple/swift-protobuf.git", from: "1.31.0"), - .package(url: "https://github.com/apple/swift-collections.git", "1.1.0" ..< "1.3.0"), + .package(url: "https://github.com/apple/swift-collections.git", "1.1.0" ..< "2.0.0"), // Only used for DocC generation .package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.3.0"), ], From 2e5aa8d791c95421b6f389055171496a25cfdd66 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Tue, 17 Mar 2026 10:56:17 -0700 Subject: [PATCH 04/12] Remove swift-collections dependency Replace DequeModule and OrderedCollections with minimal internal implementations covering only the APIs actually used (~14 call sites). This unblocks consumers from being pinned to old swift-collections versions due to library evolution mode compatibility constraints. Co-Authored-By: Claude Opus 4.6 (1M context) --- AGENTS.md | 2 +- LiveKitClient.podspec | 2 - Package.swift | 3 - Package@swift-6.0.swift | 3 - Sources/LiveKit/Agent/Session.swift | 1 - Sources/LiveKit/Core/DataChannelPair.swift | 1 - .../Support/DataStructures/Deque.swift | 44 +++++++++ .../DataStructures/OrderedDictionary.swift | 94 +++++++++++++++++++ .../Support/DataStructures/OrderedSet.swift | 41 ++++++++ .../Track/Metrics/MetricsManager.swift | 1 - .../Agent/TranscriptionTests.swift | 1 - 11 files changed, 180 insertions(+), 13 deletions(-) create mode 100644 Sources/LiveKit/Support/DataStructures/Deque.swift create mode 100644 Sources/LiveKit/Support/DataStructures/OrderedDictionary.swift create mode 100644 Sources/LiveKit/Support/DataStructures/OrderedSet.swift diff --git a/AGENTS.md b/AGENTS.md index 5ca5a4c28..cd819cc5c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -52,7 +52,7 @@ Key components: - `StateSync` - thread-safe state container with `@dynamicMemberLookup`; triggers `onDidMutate` callbacks - `MulticastDelegate` - weak-reference delegate collection for event broadcasting -Dependencies: LiveKitWebRTC, LiveKitUniFFI, SwiftProtobuf, swift-collections. +Dependencies: LiveKitWebRTC, LiveKitUniFFI, SwiftProtobuf. ## WebRTC diff --git a/LiveKitClient.podspec b/LiveKitClient.podspec index d731224a4..b42ccc746 100644 --- a/LiveKitClient.podspec +++ b/LiveKitClient.podspec @@ -17,8 +17,6 @@ Pod::Spec.new do |spec| spec.dependency("LiveKitWebRTC", "= 144.7559.01") spec.dependency("LiveKitUniFFI", "= 0.0.5") spec.dependency("SwiftProtobuf") - spec.dependency("DequeModule", "= 1.1.4") - spec.dependency("OrderedCollections", " = 1.1.4") spec.resource_bundles = {"Privacy" => ["Sources/LiveKit/PrivacyInfo.xcprivacy"]} diff --git a/Package.swift b/Package.swift index 17b904c52..6d1deb55c 100644 --- a/Package.swift +++ b/Package.swift @@ -22,7 +22,6 @@ let package = Package( .package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "144.7559.01"), .package(url: "https://github.com/livekit/livekit-uniffi-xcframework.git", exact: "0.0.5"), .package(url: "https://github.com/apple/swift-protobuf.git", from: "1.31.0"), - .package(url: "https://github.com/apple/swift-collections.git", "1.1.0" ..< "2.0.0"), // Only used for DocC generation .package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.3.0"), ], @@ -37,8 +36,6 @@ let package = Package( .product(name: "LiveKitWebRTC", package: "webrtc-xcframework"), .product(name: "LiveKitUniFFI", package: "livekit-uniffi-xcframework"), .product(name: "SwiftProtobuf", package: "swift-protobuf"), - .product(name: "DequeModule", package: "swift-collections"), - .product(name: "OrderedCollections", package: "swift-collections"), "LKObjCHelpers", ], exclude: [ diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index dfe189aea..2e9d7dbfc 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -22,7 +22,6 @@ let package = Package( .package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "144.7559.01"), .package(url: "https://github.com/livekit/livekit-uniffi-xcframework.git", exact: "0.0.5"), .package(url: "https://github.com/apple/swift-protobuf.git", from: "1.31.0"), - .package(url: "https://github.com/apple/swift-collections.git", "1.1.0" ..< "2.0.0"), // Only used for DocC generation .package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.3.0"), ], @@ -37,8 +36,6 @@ let package = Package( .product(name: "LiveKitWebRTC", package: "webrtc-xcframework"), .product(name: "LiveKitUniFFI", package: "livekit-uniffi-xcframework"), .product(name: "SwiftProtobuf", package: "swift-protobuf"), - .product(name: "DequeModule", package: "swift-collections"), - .product(name: "OrderedCollections", package: "swift-collections"), "LKObjCHelpers", ], exclude: [ diff --git a/Sources/LiveKit/Agent/Session.swift b/Sources/LiveKit/Agent/Session.swift index 5c8d5c48b..f21c4972a 100644 --- a/Sources/LiveKit/Agent/Session.swift +++ b/Sources/LiveKit/Agent/Session.swift @@ -16,7 +16,6 @@ import Combine import Foundation -import OrderedCollections /// A ``Session`` represents a connection to a LiveKit Room that can contain an ``Agent``. /// diff --git a/Sources/LiveKit/Core/DataChannelPair.swift b/Sources/LiveKit/Core/DataChannelPair.swift index 4e78c1dc4..d143b2f8f 100644 --- a/Sources/LiveKit/Core/DataChannelPair.swift +++ b/Sources/LiveKit/Core/DataChannelPair.swift @@ -16,7 +16,6 @@ // swiftlint:disable file_length -import DequeModule import Foundation internal import LiveKitWebRTC diff --git a/Sources/LiveKit/Support/DataStructures/Deque.swift b/Sources/LiveKit/Support/DataStructures/Deque.swift new file mode 100644 index 000000000..09d804f1b --- /dev/null +++ b/Sources/LiveKit/Support/DataStructures/Deque.swift @@ -0,0 +1,44 @@ +/* + * Copyright 2026 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// API inspired by swift-collections by Apple Inc. +// https://github.com/apple/swift-collections +// Licensed under Apache License 2.0 with Runtime Library Exception. + +struct Deque: ExpressibleByArrayLiteral { + private var storage: [Element] = [] + + init() {} + + init(arrayLiteral elements: Element...) { + storage = elements + } + + var isEmpty: Bool { storage.isEmpty } + + var first: Element? { storage.first } + + mutating func append(_ element: Element) { + storage.append(element) + } + + @discardableResult + mutating func removeFirst() -> Element { + storage.removeFirst() + } +} + +extension Deque: Sendable where Element: Sendable {} diff --git a/Sources/LiveKit/Support/DataStructures/OrderedDictionary.swift b/Sources/LiveKit/Support/DataStructures/OrderedDictionary.swift new file mode 100644 index 000000000..ece7fc347 --- /dev/null +++ b/Sources/LiveKit/Support/DataStructures/OrderedDictionary.swift @@ -0,0 +1,94 @@ +/* + * Copyright 2026 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// API inspired by swift-collections by Apple Inc. +// https://github.com/apple/swift-collections +// Licensed under Apache License 2.0 with Runtime Library Exception. + +struct OrderedDictionary: ExpressibleByDictionaryLiteral { + private var pairs: [(key: Key, value: Value)] = [] + private var index: [Key: Int] = [:] + + init() {} + + init(dictionaryLiteral elements: (Key, Value)...) { + for (key, value) in elements { + pairs.append((key, value)) + index[key] = pairs.count - 1 + } + } + + init(uniqueKeysWithValues keysAndValues: some Sequence<(Key, Value)>) { + for (key, value) in keysAndValues { + pairs.append((key, value)) + index[key] = pairs.count - 1 + } + } + + var count: Int { pairs.count } + + var keys: Keys { Keys(pairs: pairs) } + var values: Values { Values(pairs: pairs) } + + subscript(key: Key) -> Value? { + get { + guard let i = index[key] else { return nil } + return pairs[i].value + } + set { + if let newValue { + if let i = index[key] { + pairs[i] = (key, newValue) + } else { + index[key] = pairs.count + pairs.append((key, newValue)) + } + } else if let i = index.removeValue(forKey: key) { + pairs.remove(at: i) + for j in i ..< pairs.count { + index[pairs[j].key] = j + } + } + } + } + + @discardableResult + mutating func updateValue(_ value: Value, forKey key: Key) -> Value? { + if let i = index[key] { + let old = pairs[i].value + pairs[i] = (key, value) + return old + } + index[key] = pairs.count + pairs.append((key, value)) + return nil + } + + struct Keys { + fileprivate let pairs: [(key: Key, value: Value)] + subscript(position: Int) -> Key { pairs[position].key } + } + + struct Values { + fileprivate let pairs: [(key: Key, value: Value)] + var elements: [Value] { pairs.map(\.value) } + subscript(position: Int) -> Value { pairs[position].value } + } +} + +extension OrderedDictionary: Sendable where Key: Sendable, Value: Sendable {} +extension OrderedDictionary.Keys: Sendable where Key: Sendable, Value: Sendable {} +extension OrderedDictionary.Values: Sendable where Key: Sendable, Value: Sendable {} diff --git a/Sources/LiveKit/Support/DataStructures/OrderedSet.swift b/Sources/LiveKit/Support/DataStructures/OrderedSet.swift new file mode 100644 index 000000000..d89ecba86 --- /dev/null +++ b/Sources/LiveKit/Support/DataStructures/OrderedSet.swift @@ -0,0 +1,41 @@ +/* + * Copyright 2026 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// API inspired by swift-collections by Apple Inc. +// https://github.com/apple/swift-collections +// Licensed under Apache License 2.0 with Runtime Library Exception. + +struct OrderedSet { + private var array: [Element] = [] + private var set: [Element: Int] = [:] + + init() {} + + var elements: [Element] { array } + + @discardableResult + mutating func append(_ element: Element) -> (inserted: Bool, index: Int) { + if let existing = set[element] { + return (inserted: false, index: existing) + } + let index = array.count + array.append(element) + set[element] = index + return (inserted: true, index: index) + } +} + +extension OrderedSet: Sendable where Element: Sendable {} diff --git a/Sources/LiveKit/Track/Metrics/MetricsManager.swift b/Sources/LiveKit/Track/Metrics/MetricsManager.swift index 562e3cf3b..de7c7c6e1 100644 --- a/Sources/LiveKit/Track/Metrics/MetricsManager.swift +++ b/Sources/LiveKit/Track/Metrics/MetricsManager.swift @@ -15,7 +15,6 @@ */ import Foundation -import OrderedCollections // MARK: - Triggers diff --git a/Tests/LiveKitCoreTests/Agent/TranscriptionTests.swift b/Tests/LiveKitCoreTests/Agent/TranscriptionTests.swift index 6a1f81610..683493a78 100644 --- a/Tests/LiveKitCoreTests/Agent/TranscriptionTests.swift +++ b/Tests/LiveKitCoreTests/Agent/TranscriptionTests.swift @@ -15,7 +15,6 @@ */ @testable import LiveKit -import OrderedCollections #if canImport(LiveKitTestSupport) import LiveKitTestSupport #endif From 11dce9d4864e157235145d652c59297424abfcb1 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Tue, 17 Mar 2026 10:59:43 -0700 Subject: [PATCH 05/12] Add .changes entry for swift-collections removal Co-Authored-By: Claude Opus 4.6 (1M context) --- .changes/remove-swift-collections | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changes/remove-swift-collections diff --git a/.changes/remove-swift-collections b/.changes/remove-swift-collections new file mode 100644 index 000000000..083b0382e --- /dev/null +++ b/.changes/remove-swift-collections @@ -0,0 +1 @@ +patch type="changed" "Remove swift-collections dependency by replacing with minimal internal implementations" From 8ba93635a964dccd77cbea48d32a385516b43821 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Tue, 17 Mar 2026 11:11:36 -0700 Subject: [PATCH 06/12] Remove redundant license line from attribution comments Co-Authored-By: Claude Opus 4.6 (1M context) --- Sources/LiveKit/Support/DataStructures/Deque.swift | 1 - Sources/LiveKit/Support/DataStructures/OrderedDictionary.swift | 1 - Sources/LiveKit/Support/DataStructures/OrderedSet.swift | 1 - 3 files changed, 3 deletions(-) diff --git a/Sources/LiveKit/Support/DataStructures/Deque.swift b/Sources/LiveKit/Support/DataStructures/Deque.swift index 09d804f1b..e5b38228e 100644 --- a/Sources/LiveKit/Support/DataStructures/Deque.swift +++ b/Sources/LiveKit/Support/DataStructures/Deque.swift @@ -16,7 +16,6 @@ // API inspired by swift-collections by Apple Inc. // https://github.com/apple/swift-collections -// Licensed under Apache License 2.0 with Runtime Library Exception. struct Deque: ExpressibleByArrayLiteral { private var storage: [Element] = [] diff --git a/Sources/LiveKit/Support/DataStructures/OrderedDictionary.swift b/Sources/LiveKit/Support/DataStructures/OrderedDictionary.swift index ece7fc347..538fa539c 100644 --- a/Sources/LiveKit/Support/DataStructures/OrderedDictionary.swift +++ b/Sources/LiveKit/Support/DataStructures/OrderedDictionary.swift @@ -16,7 +16,6 @@ // API inspired by swift-collections by Apple Inc. // https://github.com/apple/swift-collections -// Licensed under Apache License 2.0 with Runtime Library Exception. struct OrderedDictionary: ExpressibleByDictionaryLiteral { private var pairs: [(key: Key, value: Value)] = [] diff --git a/Sources/LiveKit/Support/DataStructures/OrderedSet.swift b/Sources/LiveKit/Support/DataStructures/OrderedSet.swift index d89ecba86..da1cd46ed 100644 --- a/Sources/LiveKit/Support/DataStructures/OrderedSet.swift +++ b/Sources/LiveKit/Support/DataStructures/OrderedSet.swift @@ -16,7 +16,6 @@ // API inspired by swift-collections by Apple Inc. // https://github.com/apple/swift-collections -// Licensed under Apache License 2.0 with Runtime Library Exception. struct OrderedSet { private var array: [Element] = [] From 1b63c055cee7f48b13a344e00d51937034965c32 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Tue, 17 Mar 2026 15:31:42 -0700 Subject: [PATCH 07/12] Use circular buffer for Deque to ensure O(1) removeFirst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Array.removeFirst() is O(n), making the processSendQueue drain loop O(n²). Switch to a circular buffer backed by [Element?] with head pointer tracking for O(1) amortized append and removeFirst. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Support/DataStructures/Deque.swift | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/Sources/LiveKit/Support/DataStructures/Deque.swift b/Sources/LiveKit/Support/DataStructures/Deque.swift index e5b38228e..361290c87 100644 --- a/Sources/LiveKit/Support/DataStructures/Deque.swift +++ b/Sources/LiveKit/Support/DataStructures/Deque.swift @@ -17,26 +17,56 @@ // API inspired by swift-collections by Apple Inc. // https://github.com/apple/swift-collections +/// A double-ended queue backed by a circular buffer. +/// Provides O(1) amortized `append` and `removeFirst`. struct Deque: ExpressibleByArrayLiteral { - private var storage: [Element] = [] + private var buffer: [Element?] = [] + private var head = 0 + private var count_ = 0 init() {} init(arrayLiteral elements: Element...) { - storage = elements + buffer = elements.map { $0 } + count_ = elements.count } - var isEmpty: Bool { storage.isEmpty } + var isEmpty: Bool { count_ == 0 } - var first: Element? { storage.first } + var first: Element? { + guard count_ > 0 else { return nil } + return buffer[head] + } mutating func append(_ element: Element) { - storage.append(element) + if count_ == buffer.count { + grow() + } + let tail = (head + count_) % buffer.count + buffer[tail] = element + count_ += 1 } @discardableResult mutating func removeFirst() -> Element { - storage.removeFirst() + guard count_ > 0 else { + fatalError("Cannot removeFirst from an empty Deque") + } + let element = buffer[head]! + buffer[head] = nil + head = (head + 1) % buffer.count + count_ -= 1 + return element + } + + private mutating func grow() { + let newCapacity = max(buffer.count * 2, 4) + var newBuffer = [Element?](repeating: nil, count: newCapacity) + for i in 0 ..< count_ { + newBuffer[i] = buffer[(head + i) % buffer.count] + } + buffer = newBuffer + head = 0 } } From 6f144a8b4097baa0523c57907840f3e7b5eed600 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Sat, 28 Mar 2026 13:49:42 -0700 Subject: [PATCH 08/12] Apply review feedback from pblazej - Use bitwise AND instead of modulo in Deque (power-of-two optimization) - Change fatalError to preconditionFailure to match Array behavior - Handle duplicate keys in OrderedDictionary init(uniqueKeysWithValues:) Co-Authored-By: Claude Opus 4.6 (1M context) --- Sources/LiveKit/Support/DataStructures/Deque.swift | 9 +++++---- .../Support/DataStructures/OrderedDictionary.swift | 8 ++++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Sources/LiveKit/Support/DataStructures/Deque.swift b/Sources/LiveKit/Support/DataStructures/Deque.swift index 361290c87..d523c922b 100644 --- a/Sources/LiveKit/Support/DataStructures/Deque.swift +++ b/Sources/LiveKit/Support/DataStructures/Deque.swift @@ -42,7 +42,7 @@ struct Deque: ExpressibleByArrayLiteral { if count_ == buffer.count { grow() } - let tail = (head + count_) % buffer.count + let tail = (head + count_) & (buffer.count - 1) buffer[tail] = element count_ += 1 } @@ -50,11 +50,11 @@ struct Deque: ExpressibleByArrayLiteral { @discardableResult mutating func removeFirst() -> Element { guard count_ > 0 else { - fatalError("Cannot removeFirst from an empty Deque") + preconditionFailure("Cannot removeFirst from an empty Deque") } let element = buffer[head]! buffer[head] = nil - head = (head + 1) % buffer.count + head = (head + 1) & (buffer.count - 1) count_ -= 1 return element } @@ -62,8 +62,9 @@ struct Deque: ExpressibleByArrayLiteral { private mutating func grow() { let newCapacity = max(buffer.count * 2, 4) var newBuffer = [Element?](repeating: nil, count: newCapacity) + let mask = buffer.count - 1 for i in 0 ..< count_ { - newBuffer[i] = buffer[(head + i) % buffer.count] + newBuffer[i] = buffer[(head + i) & mask] } buffer = newBuffer head = 0 diff --git a/Sources/LiveKit/Support/DataStructures/OrderedDictionary.swift b/Sources/LiveKit/Support/DataStructures/OrderedDictionary.swift index 538fa539c..58227104e 100644 --- a/Sources/LiveKit/Support/DataStructures/OrderedDictionary.swift +++ b/Sources/LiveKit/Support/DataStructures/OrderedDictionary.swift @@ -32,8 +32,12 @@ struct OrderedDictionary: ExpressibleByDictionaryLiteral { init(uniqueKeysWithValues keysAndValues: some Sequence<(Key, Value)>) { for (key, value) in keysAndValues { - pairs.append((key, value)) - index[key] = pairs.count - 1 + if let i = index[key] { + pairs[i] = (key, value) + } else { + index[key] = pairs.count + pairs.append((key, value)) + } } } From d5da9f11f22eed3e2426343227ed2998510c0f87 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Sat, 28 Mar 2026 13:59:30 -0700 Subject: [PATCH 09/12] Remove swift-collections from Package@swift-6.2.swift Co-Authored-By: Claude Opus 4.6 (1M context) --- Package@swift-6.2.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Package@swift-6.2.swift b/Package@swift-6.2.swift index a3797d98f..24b2d1a9c 100644 --- a/Package@swift-6.2.swift +++ b/Package@swift-6.2.swift @@ -23,7 +23,6 @@ let package = Package( .package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "144.7559.01"), .package(url: "https://github.com/livekit/livekit-uniffi-xcframework.git", exact: "0.0.5"), .package(url: "https://github.com/apple/swift-protobuf.git", from: "1.31.0"), - .package(url: "https://github.com/apple/swift-collections.git", "1.1.0" ..< "1.3.0"), // Only used for DocC generation .package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.3.0"), ], @@ -38,8 +37,6 @@ let package = Package( .product(name: "LiveKitWebRTC", package: "webrtc-xcframework"), .product(name: "LiveKitUniFFI", package: "livekit-uniffi-xcframework"), .product(name: "SwiftProtobuf", package: "swift-protobuf"), - .product(name: "DequeModule", package: "swift-collections"), - .product(name: "OrderedCollections", package: "swift-collections"), "LKObjCHelpers", ], exclude: [ From df498fe9aa2b549db75bab4b645698d67ac376ef Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Sat, 28 Mar 2026 14:45:55 -0700 Subject: [PATCH 10/12] Run swiftformat to fix redundantSendable and sortImports lint Co-Authored-By: Claude Opus 4.6 (1M context) --- Sources/LiveKit/Broadcast/IPC/BroadcastImageCodec.swift | 2 +- Sources/LiveKit/Core/DataChannelPair.swift | 8 ++++---- Sources/LiveKit/Core/RPC.swift | 2 +- Sources/LiveKit/Core/RegionManager.swift | 2 +- Sources/LiveKit/Core/Room.swift | 2 +- Sources/LiveKit/Core/SignalClient.swift | 2 +- .../DataStream/Incoming/IncomingStreamManager.swift | 2 +- Sources/LiveKit/LiveKit.swift | 4 ++-- Sources/LiveKit/Participant/Participant.swift | 2 +- Sources/LiveKit/Support/Async/AsyncTimer.swift | 2 +- Sources/LiveKit/Support/Logger.swift | 4 ++-- Sources/LiveKit/Support/Schedulers/QueueActor.swift | 2 +- Sources/LiveKit/Token/TokenSource.swift | 4 ++-- Sources/LiveKit/Track/Track.swift | 2 +- Sources/LiveKit/Types/Attributes/AttributeTypings.swift | 8 ++++---- Sources/LiveKit/Types/IceCandidate.swift | 2 +- Sources/LiveKit/Types/TrackSettings.swift | 2 +- Sources/LiveKit/Views/VideoView.swift | 2 +- Tests/LiveKitTestSupport/MockURLProtocol.swift | 2 +- 19 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Sources/LiveKit/Broadcast/IPC/BroadcastImageCodec.swift b/Sources/LiveKit/Broadcast/IPC/BroadcastImageCodec.swift index 750ed1a34..243ba7fc1 100644 --- a/Sources/LiveKit/Broadcast/IPC/BroadcastImageCodec.swift +++ b/Sources/LiveKit/Broadcast/IPC/BroadcastImageCodec.swift @@ -20,7 +20,7 @@ import AVFoundation @preconcurrency import CoreImage /// Encode and decode image samples for transport. -struct BroadcastImageCodec: Sendable { +struct BroadcastImageCodec { struct Metadata: Codable { let width: Int let height: Int diff --git a/Sources/LiveKit/Core/DataChannelPair.swift b/Sources/LiveKit/Core/DataChannelPair.swift index d143b2f8f..85710f8e1 100644 --- a/Sources/LiveKit/Core/DataChannelPair.swift +++ b/Sources/LiveKit/Core/DataChannelPair.swift @@ -53,7 +53,7 @@ class DataChannelPair: NSObject, @unchecked Sendable, Loggable { } } - private struct Buffers: Sendable { + private struct Buffers { var lossyBuffer = SendBuffer() var reliableBuffer = SendBuffer() var reliableRetryBuffer = RetryBuffer(minAmount: DataChannelPair.reliableRetryAmount) @@ -118,7 +118,7 @@ class DataChannelPair: NSObject, @unchecked Sendable, Loggable { } } - private struct PublishDataRequest: Sendable { + private struct PublishDataRequest { let data: LKRTCDataBuffer let sequence: UInt32 let continuation: CheckedContinuation? @@ -128,11 +128,11 @@ class DataChannelPair: NSObject, @unchecked Sendable, Loggable { } } - private struct ChannelEvent: Sendable { + private struct ChannelEvent { let channelKind: ChannelKind let detail: Detail - enum Detail: Sendable { + enum Detail { case publishData(PublishDataRequest) case publishedData(PublishDataRequest) case bufferedAmountChanged(UInt64) diff --git a/Sources/LiveKit/Core/RPC.swift b/Sources/LiveKit/Core/RPC.swift index 40bed62f7..e5f40fd0b 100644 --- a/Sources/LiveKit/Core/RPC.swift +++ b/Sources/LiveKit/Core/RPC.swift @@ -136,7 +136,7 @@ public struct RpcInvocationData { public let responseTimeout: TimeInterval } -struct PendingRpcResponse: Sendable { +struct PendingRpcResponse { let participantIdentity: Participant.Identity let onResolve: @Sendable (_ payload: String?, _ error: RpcError?) -> Void } diff --git a/Sources/LiveKit/Core/RegionManager.swift b/Sources/LiveKit/Core/RegionManager.swift index e4f5c51f3..e0d94a831 100644 --- a/Sources/LiveKit/Core/RegionManager.swift +++ b/Sources/LiveKit/Core/RegionManager.swift @@ -19,7 +19,7 @@ import Foundation // MARK: - RegionManager actor RegionManager: Loggable { - struct State: Sendable { + struct State { var lastRequested: Date? var all: [RegionInfo] = [] var remaining: [RegionInfo] = [] diff --git a/Sources/LiveKit/Core/Room.swift b/Sources/LiveKit/Core/Room.swift index fd66145e4..66f8b4a5a 100644 --- a/Sources/LiveKit/Core/Room.swift +++ b/Sources/LiveKit/Core/Room.swift @@ -134,7 +134,7 @@ public class Room: NSObject, @unchecked Sendable, ObservableObject, Loggable { // MARK: - State - struct State: Equatable, Sendable { + struct State: Equatable { // Options var connectOptions: ConnectOptions var roomOptions: RoomOptions diff --git a/Sources/LiveKit/Core/SignalClient.swift b/Sources/LiveKit/Core/SignalClient.swift index 33b46bbb5..9a7bece82 100644 --- a/Sources/LiveKit/Core/SignalClient.swift +++ b/Sources/LiveKit/Core/SignalClient.swift @@ -25,7 +25,7 @@ actor SignalClient: Loggable { typealias AddTrackRequestPopulator = @Sendable (inout Livekit_AddTrackRequest) throws -> Void - enum ConnectResponse: Sendable { + enum ConnectResponse { case join(Livekit_JoinResponse) case reconnect(Livekit_ReconnectResponse) diff --git a/Sources/LiveKit/DataStream/Incoming/IncomingStreamManager.swift b/Sources/LiveKit/DataStream/Incoming/IncomingStreamManager.swift index 2bb68a620..6969861a8 100644 --- a/Sources/LiveKit/DataStream/Incoming/IncomingStreamManager.swift +++ b/Sources/LiveKit/DataStream/Incoming/IncomingStreamManager.swift @@ -36,7 +36,7 @@ actor IncomingStreamManager: Loggable { private var textStreamHandlers: [String: TextStreamHandler] = [:] /// Events are processed in a serial (FIFO) order - enum StreamEvent: Sendable { + enum StreamEvent { case header(Livekit_DataStream.Header, String, EncryptionType) case chunk(Livekit_DataStream.Chunk, EncryptionType) case trailer(Livekit_DataStream.Trailer, EncryptionType) diff --git a/Sources/LiveKit/LiveKit.swift b/Sources/LiveKit/LiveKit.swift index 87e3d5b4a..b5d1449ce 100644 --- a/Sources/LiveKit/LiveKit.swift +++ b/Sources/LiveKit/LiveKit.swift @@ -14,9 +14,9 @@ * limitations under the License. */ -import Foundation -internal import LiveKitWebRTC internal import LiveKitUniFFI +internal import LiveKitWebRTC +import Foundation /// The open source platform for real-time communication. /// diff --git a/Sources/LiveKit/Participant/Participant.swift b/Sources/LiveKit/Participant/Participant.swift index 53e40baff..404a18413 100644 --- a/Sources/LiveKit/Participant/Participant.swift +++ b/Sources/LiveKit/Participant/Participant.swift @@ -69,7 +69,7 @@ public class Participant: NSObject, @unchecked Sendable, ObservableObject, Logga // MARK: - Internal - struct State: Equatable, Hashable, Sendable { + struct State: Equatable, Hashable { var sid: Sid? var identity: Identity? var name: String? diff --git a/Sources/LiveKit/Support/Async/AsyncTimer.swift b/Sources/LiveKit/Support/Async/AsyncTimer.swift index ffd78d894..c99c6194d 100644 --- a/Sources/LiveKit/Support/Async/AsyncTimer.swift +++ b/Sources/LiveKit/Support/Async/AsyncTimer.swift @@ -23,7 +23,7 @@ final class AsyncTimer: Sendable, Loggable { // MARK: - Private - struct State: Sendable { + struct State { var isStarted: Bool = false var interval: TimeInterval var task: AnyTaskCancellable? diff --git a/Sources/LiveKit/Support/Logger.swift b/Sources/LiveKit/Support/Logger.swift index 4794f1927..ccb5f96ea 100644 --- a/Sources/LiveKit/Support/Logger.swift +++ b/Sources/LiveKit/Support/Logger.swift @@ -14,9 +14,9 @@ * limitations under the License. */ -import OSLog -internal import LiveKitWebRTC internal import LiveKitUniFFI +internal import LiveKitWebRTC +import OSLog // MARK: - Logger diff --git a/Sources/LiveKit/Support/Schedulers/QueueActor.swift b/Sources/LiveKit/Support/Schedulers/QueueActor.swift index ffed9eaa2..02c5d0cbb 100644 --- a/Sources/LiveKit/Support/Schedulers/QueueActor.swift +++ b/Sources/LiveKit/Support/Schedulers/QueueActor.swift @@ -21,7 +21,7 @@ actor QueueActor: Loggable { // MARK: - Public - enum State: Sendable { + enum State { case resumed case suspended } diff --git a/Sources/LiveKit/Token/TokenSource.swift b/Sources/LiveKit/Token/TokenSource.swift index 395768bdc..d18822078 100644 --- a/Sources/LiveKit/Token/TokenSource.swift +++ b/Sources/LiveKit/Token/TokenSource.swift @@ -14,8 +14,8 @@ * limitations under the License. */ -import Foundation internal import LiveKitUniFFI +import Foundation // MARK: - Source @@ -97,7 +97,7 @@ public struct TokenRequestOptions: Sendable, Equatable { } } -struct TokenSourceRequest: Sendable, Encodable { +struct TokenSourceRequest: Encodable { let roomName: String? let participantName: String? let participantIdentity: String? diff --git a/Sources/LiveKit/Track/Track.swift b/Sources/LiveKit/Track/Track.swift index 6c8bbe54e..afd7ff666 100644 --- a/Sources/LiveKit/Track/Track.swift +++ b/Sources/LiveKit/Track/Track.swift @@ -92,7 +92,7 @@ public class Track: NSObject, @unchecked Sendable, Loggable { let mediaTrack: LKRTCMediaStreamTrack - struct State: Sendable { + struct State { let name: String let kind: Kind let source: Source diff --git a/Sources/LiveKit/Types/Attributes/AttributeTypings.swift b/Sources/LiveKit/Types/Attributes/AttributeTypings.swift index adb214111..8b90b7b93 100644 --- a/Sources/LiveKit/Types/Attributes/AttributeTypings.swift +++ b/Sources/LiveKit/Types/Attributes/AttributeTypings.swift @@ -51,7 +51,7 @@ extension TranscriptionAttributes { // MARK: - AgentAttributes -struct AgentAttributes: Codable, Sendable { +struct AgentAttributes: Codable { let lkAgentInputs: [AgentInput]? let lkAgentOutputs: [AgentOutput]? let lkAgentState: AgentState? @@ -65,13 +65,13 @@ struct AgentAttributes: Codable, Sendable { } } -enum AgentInput: String, Codable, Sendable { +enum AgentInput: String, Codable { case audio case text case video } -enum AgentOutput: String, Codable, Sendable { +enum AgentOutput: String, Codable { case audio case transcription } @@ -87,7 +87,7 @@ public enum AgentState: String, Codable, Sendable { // MARK: - TranscriptionAttributes /// Schema for transcription-related attributes -struct TranscriptionAttributes: Codable, Sendable { +struct TranscriptionAttributes: Codable { /// The segment id of the transcription let lkSegmentID: String? /// The associated track id of the transcription diff --git a/Sources/LiveKit/Types/IceCandidate.swift b/Sources/LiveKit/Types/IceCandidate.swift index 7eb0ebc26..3e7903a5b 100644 --- a/Sources/LiveKit/Types/IceCandidate.swift +++ b/Sources/LiveKit/Types/IceCandidate.swift @@ -16,7 +16,7 @@ internal import LiveKitWebRTC -struct IceCandidate: Codable, Sendable { +struct IceCandidate: Codable { let sdp: String let sdpMLineIndex: Int32 let sdpMid: String? diff --git a/Sources/LiveKit/Types/TrackSettings.swift b/Sources/LiveKit/Types/TrackSettings.swift index 6f58d0de6..6d0700960 100644 --- a/Sources/LiveKit/Types/TrackSettings.swift +++ b/Sources/LiveKit/Types/TrackSettings.swift @@ -16,7 +16,7 @@ import Foundation -struct TrackSettings: Equatable, Hashable, Sendable { +struct TrackSettings: Equatable, Hashable { let isEnabled: Bool let dimensions: Dimensions let videoQuality: VideoQuality diff --git a/Sources/LiveKit/Views/VideoView.swift b/Sources/LiveKit/Views/VideoView.swift index fc59cff81..cba5379a1 100644 --- a/Sources/LiveKit/Views/VideoView.swift +++ b/Sources/LiveKit/Views/VideoView.swift @@ -202,7 +202,7 @@ public class VideoView: NativeView, Loggable { case secondary } - struct State: Sendable { + struct State { weak var track: Track? var isEnabled: Bool = true var isHidden: Bool = false diff --git a/Tests/LiveKitTestSupport/MockURLProtocol.swift b/Tests/LiveKitTestSupport/MockURLProtocol.swift index fa6fbac00..e96bc2257 100644 --- a/Tests/LiveKitTestSupport/MockURLProtocol.swift +++ b/Tests/LiveKitTestSupport/MockURLProtocol.swift @@ -30,7 +30,7 @@ public final class MockURLProtocol: URLProtocol { } } - private struct State: Sendable { + private struct State { var allowedHosts = Set() var allowedPaths = Set() var requestHandler: (@Sendable (URLRequest) throws -> Response)? From 7ee3b985a078851affc8baa560db99d0384fcfd5 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Sat, 28 Mar 2026 14:46:50 -0700 Subject: [PATCH 11/12] Regenerate protobuf files with latest swift-protobuf Co-Authored-By: Claude Opus 4.6 (1M context) --- Sources/LiveKit/Protos/livekit_models.pb.swift | 4 ++++ Sources/LiveKit/Protos/livekit_rtc.pb.swift | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Sources/LiveKit/Protos/livekit_models.pb.swift b/Sources/LiveKit/Protos/livekit_models.pb.swift index 68ca32ff5..431595d05 100644 --- a/Sources/LiveKit/Protos/livekit_models.pb.swift +++ b/Sources/LiveKit/Protos/livekit_models.pb.swift @@ -22,7 +22,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(FoundationEssentials) +import FoundationEssentials +#else import Foundation +#endif import SwiftProtobuf // If the compiler emits an error on this type, it is because this file diff --git a/Sources/LiveKit/Protos/livekit_rtc.pb.swift b/Sources/LiveKit/Protos/livekit_rtc.pb.swift index cdeeaadad..4f43cd755 100644 --- a/Sources/LiveKit/Protos/livekit_rtc.pb.swift +++ b/Sources/LiveKit/Protos/livekit_rtc.pb.swift @@ -22,7 +22,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(FoundationEssentials) +import FoundationEssentials +#else import Foundation +#endif import SwiftProtobuf // If the compiler emits an error on this type, it is because this file From 552d4e84a3445ce934cee44920a7c63e443e2240 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Sat, 28 Mar 2026 19:24:00 -0700 Subject: [PATCH 12/12] Trigger CI re-run Co-Authored-By: Claude Opus 4.6 (1M context)