Files
Valere Fedronic fb49f5b83b Add experimental native calls behind a developer flag (#6159)
Add the element-call-ios dependency and a developer option for native calls

The package carries the call UI and the matrix-rust-rtc media stack, pinned by
exact version the way the SDK is, with the local path kept commented out for
working against a checkout. Its media xcframework has no Intel simulator slice
and a package cannot say so for the projects that consume it, hence the
excluded architecture at the project level.

The developer option is off by default and nothing reads it yet. It is placed
next to the Element Call URL override, which only applies to the web view that
native calls replace.
2026-09-17 19:42:18 +03:00

158 lines
5.6 KiB
Swift

//
// Copyright 2026 Element Creations Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
import Synchronization
/// A clock whose sleeps only finish when the test advances it.
final nonisolated class ManualClock: Clock {
struct Instant: InstantProtocol {
let offset: Duration
func advanced(by duration: Duration) -> Self {
Self(offset: offset + duration)
}
func duration(to other: Self) -> Duration {
other.offset - offset
}
static func < (lhs: Self, rhs: Self) -> Bool {
lhs.offset < rhs.offset
}
}
private struct Sleep {
let deadline: Instant
let continuation: CheckedContinuation<Void, any Error>
}
private struct State {
var now = Instant(offset: .zero)
/// A test steps through one timer at a time, so a single sleep can be pending.
var sleep: Sleep?
var sleepObserver: CheckedContinuation<Duration, Never>?
}
private let state = Mutex(State())
let minimumResolution: Duration = .zero
var now: Instant {
state.withLock { $0.now }
}
/// Whether a sleep is waiting on ``advance(by:)``, which is how a test asserts that a timer has
/// been cancelled rather than advancing the clock and watching for what it doesn't do.
var hasPendingSleep: Bool {
state.withLock { $0.sleep != nil }
}
/// Suspends until ``advance(by:)`` moves the clock to `deadline`, or the calling task is
/// cancelled, in which case this throws a `CancellationError`. Deadlines that have already
/// passed return without suspending. `tolerance` is ignored.
func sleep(until deadline: Instant, tolerance: Duration? = nil) async throws {
try await withTaskCancellationHandler {
try Task.checkCancellation()
try await withCheckedThrowingContinuation { continuation in
switch schedule(deadline: deadline, continuation: continuation) {
case .elapsed:
continuation.resume()
case .scheduled(let duration, let observer):
observer?.resume(returning: duration)
// Cancellation that landed while scheduling found nothing to cancel.
if Task.isCancelled {
cancelSleep()
}
}
}
} onCancel: {
cancelSleep()
}
}
/// Suspends until a sleep is pending and returns how long it has left to run. A sleep that is
/// already pending returns straight away, so a scheduled sleep can't be missed.
///
/// Await this before advancing a timer that runs in an unstructured task — advancing the clock
/// before the sleep has been scheduled would leave it sleeping past the new deadline.
@discardableResult
func waitForScheduledSleep() async -> Duration {
await withCheckedContinuation { continuation in
let duration = state.withLock { state -> Duration? in
guard let sleep = state.sleep else {
state.sleepObserver = continuation
return nil
}
return state.now.duration(to: sleep.deadline)
}
if let duration {
continuation.resume(returning: duration)
}
}
}
/// Moves ``now`` forward, resuming the pending sleep if it has come due. Resuming is all this
/// does — wait on the work the sleep unblocks rather than assuming it has run by the time this
/// returns.
func advance(by duration: Duration) {
let dueSleep = state.withLock { state -> Sleep? in
state.now = state.now.advanced(by: duration)
guard let sleep = state.sleep, sleep.deadline <= state.now else {
return nil
}
state.sleep = nil
return sleep
}
dueSleep?.continuation.resume()
}
// MARK: - Private
private enum Scheduling {
/// The deadline had already passed, the sleep doesn't suspend.
case elapsed
/// The sleep is now pending, along with the ``waitForScheduledSleep()`` caller to notify.
case scheduled(Duration, observer: CheckedContinuation<Duration, Never>?)
}
/// Stores the sleep's continuation, handing the caller whatever needs resuming so that it
/// happens outside of the lock.
private func schedule(deadline: Instant, continuation: CheckedContinuation<Void, any Error>) -> Scheduling {
state.withLock { state in
guard deadline > state.now else {
return .elapsed
}
precondition(state.sleep == nil, "The clock only supports a single pending sleep.")
state.sleep = Sleep(deadline: deadline, continuation: continuation)
let observer = state.sleepObserver
state.sleepObserver = nil
return .scheduled(state.now.duration(to: deadline), observer: observer)
}
}
private func cancelSleep() {
let sleep = state.withLock { state -> Sleep? in
let sleep = state.sleep
state.sleep = nil
return sleep
}
sleep?.continuation.resume(throwing: CancellationError())
}
}