Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ECO-5015] Implement public API changes of CHADR-062 #92

Merged
merged 5 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Example/AblyChatExample/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ struct ContentView: View {
}

func showRoomStatus() async throws {
for await status in try await room().status.onChange(bufferingPolicy: .unbounded) {
for await status in try await room().onStatusChange(bufferingPolicy: .unbounded) {
withAnimation {
if status.current.isAttaching {
statusInfo = "\(status.current)...".capitalized
Expand Down
44 changes: 15 additions & 29 deletions Example/AblyChatExample/Mocks/MockClients.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ actor MockRoom: Room {

nonisolated lazy var occupancy: any Occupancy = MockOccupancy(clientID: clientID, roomID: roomID)

nonisolated lazy var status: any RoomStatus = MockRoomStatus(clientID: clientID, roomID: roomID)
var status: RoomStatus = .initialized

private var mockSubscriptions: [MockSubscription<RoomStatusChange>] = []
lawrence-forooghian marked this conversation as resolved.
Show resolved Hide resolved

func attach() async throws {
fatalError("Not yet implemented")
Expand All @@ -73,6 +75,18 @@ actor MockRoom: Room {
func detach() async throws {
fatalError("Not yet implemented")
}

private func createSubscription() -> MockSubscription<RoomStatusChange> {
let subscription = MockSubscription<RoomStatusChange>(randomElement: {
RoomStatusChange(current: [.attached, .attached, .attached, .attached, .attaching(error: nil), .attaching(error: nil), .suspended(error: .createUnknownError())].randomElement()!, previous: .attaching(error: nil))
}, interval: 8)
mockSubscriptions.append(subscription)
return subscription
}
lawrence-forooghian marked this conversation as resolved.
Show resolved Hide resolved

func onStatusChange(bufferingPolicy _: BufferingPolicy) async -> Subscription<RoomStatusChange> {
.init(mockAsyncSequence: createSubscription())
}
}

actor MockMessages: Messages {
Expand Down Expand Up @@ -394,31 +408,3 @@ actor MockOccupancy: Occupancy {
fatalError("Not yet implemented")
}
}

actor MockRoomStatus: RoomStatus {
let clientID: String
let roomID: String

var current: RoomLifecycle
var error: ARTErrorInfo?

private var mockSubscriptions: [MockSubscription<RoomStatusChange>] = []

init(clientID: String, roomID: String) {
self.clientID = clientID
self.roomID = roomID
current = .initialized
}

private func createSubscription() -> MockSubscription<RoomStatusChange> {
let subscription = MockSubscription<RoomStatusChange>(randomElement: {
RoomStatusChange(current: [.attached, .attached, .attached, .attached, .attaching(error: nil), .attaching(error: nil), .suspended(error: .createUnknownError())].randomElement()!, previous: .attaching(error: nil))
}, interval: 8)
mockSubscriptions.append(subscription)
return subscription
}

func onChange(bufferingPolicy _: BufferingPolicy) async -> Subscription<RoomStatusChange> {
.init(mockAsyncSequence: createSubscription())
}
}
16 changes: 6 additions & 10 deletions Sources/AblyChat/Connection.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import Ably

public protocol Connection: AnyObject, Sendable {
var status: any ConnectionStatus { get }
}

public protocol ConnectionStatus: AnyObject, Sendable {
var current: ConnectionLifecycle { get }
var status: ConnectionStatus { get }
// TODO: (https://github.com/ably-labs/ably-chat-swift/issues/12): consider how to avoid the need for an unwrap
var error: ARTErrorInfo? { get }
func onChange(bufferingPolicy: BufferingPolicy) -> Subscription<ConnectionStatusChange>
func onStatusChange(bufferingPolicy: BufferingPolicy) -> Subscription<ConnectionStatusChange>
}

public enum ConnectionLifecycle: Sendable {
public enum ConnectionStatus: Sendable {
case initialized
case connecting
case connected
Expand All @@ -21,13 +17,13 @@ public enum ConnectionLifecycle: Sendable {
}

public struct ConnectionStatusChange: Sendable {
public var current: ConnectionLifecycle
public var previous: ConnectionLifecycle
public var current: ConnectionStatus
public var previous: ConnectionStatus
// TODO: (https://github.com/ably-labs/ably-chat-swift/issues/12): consider how to avoid the need for an unwrap
public var error: ARTErrorInfo?
public var retryIn: TimeInterval

public init(current: ConnectionLifecycle, previous: ConnectionLifecycle, error: ARTErrorInfo? = nil, retryIn: TimeInterval) {
public init(current: ConnectionStatus, previous: ConnectionStatus, error: ARTErrorInfo? = nil, retryIn: TimeInterval) {
self.current = current
self.previous = previous
self.error = error
Expand Down
45 changes: 36 additions & 9 deletions Sources/AblyChat/Room.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,24 @@ public protocol Room: AnyObject, Sendable {
var typing: any Typing { get }
// To access this property if occupancy is not enabled for the room is a programmer error, and will lead to `fatalError` being called.
var occupancy: any Occupancy { get }
var status: any RoomStatus { get }
// TODO: change to `status`
var status: RoomStatus { get async }
func onStatusChange(bufferingPolicy: BufferingPolicy) async -> Subscription<RoomStatusChange>
func attach() async throws
func detach() async throws
var options: RoomOptions { get }
}

public struct RoomStatusChange: Sendable {
public var current: RoomStatus
public var previous: RoomStatus

public init(current: RoomStatus, previous: RoomStatus) {
self.current = current
self.previous = previous
}
}

internal actor DefaultRoom: Room {
internal nonisolated let roomID: String
internal nonisolated let options: RoomOptions
Expand All @@ -33,15 +45,16 @@ internal actor DefaultRoom: Room {
}
#endif

private let _status: DefaultRoomStatus
internal private(set) var status: RoomStatus = .initialized
// TODO: clean up old subscriptions (https://github.com/ably-labs/ably-chat-swift/issues/36)
private var statusSubscriptions: [Subscription<RoomStatusChange>] = []
private let logger: InternalLogger

internal init(realtime: RealtimeClient, chatAPI: ChatAPI, roomID: String, options: RoomOptions, logger: InternalLogger) async throws {
self.realtime = realtime
self.roomID = roomID
self.options = options
self.logger = logger
_status = .init(logger: logger)
self.chatAPI = chatAPI

guard let clientId = realtime.clientId else {
Expand Down Expand Up @@ -71,10 +84,6 @@ internal actor DefaultRoom: Room {
fatalError("Not yet implemented")
}

internal nonisolated var status: any RoomStatus {
_status
}

/// Fetches the channels that contribute to this room.
private func channels() -> [any RealtimeChannelProtocol] {
[
Expand All @@ -95,7 +104,7 @@ internal actor DefaultRoom: Room {
throw error
}
}
await _status.transition(to: .attached)
transition(to: .attached)
}

public func detach() async throws {
Expand All @@ -107,6 +116,24 @@ internal actor DefaultRoom: Room {
throw error
}
}
await _status.transition(to: .detached)
transition(to: .detached)
}

// MARK: - Room status

internal func onStatusChange(bufferingPolicy: BufferingPolicy) -> Subscription<RoomStatusChange> {
let subscription: Subscription<RoomStatusChange> = .init(bufferingPolicy: bufferingPolicy)
statusSubscriptions.append(subscription)
return subscription
}
lawrence-forooghian marked this conversation as resolved.
Show resolved Hide resolved

/// Sets ``status`` to the given status, and emits a status change to all subscribers added via ``onStatusChange(bufferingPolicy:)``.
internal func transition(to newStatus: RoomStatus) {
logger.log(message: "Transitioning to \(newStatus)", level: .debug)
let statusChange = RoomStatusChange(current: newStatus, previous: status)
status = newStatus
for subscription in statusSubscriptions {
subscription.emit(statusChange)
}
}
}
10 changes: 5 additions & 5 deletions Sources/AblyChat/RoomLifecycleManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ internal actor RoomLifecycleManager<Contributor: RoomLifecycleContributor> {
case releasing(releaseOperationID: UUID)
case released

internal var toRoomLifecycle: RoomLifecycle {
internal var toRoomStatus: RoomStatus {
switch self {
case .initialized:
.initialized
Expand Down Expand Up @@ -264,8 +264,8 @@ internal actor RoomLifecycleManager<Contributor: RoomLifecycleContributor> {

// MARK: - Room status and its changes

internal var current: RoomLifecycle {
status.toRoomLifecycle
internal var roomStatus: RoomStatus {
status.toRoomStatus
}

internal func onChange(bufferingPolicy: BufferingPolicy) -> Subscription<RoomStatusChange> {
Expand All @@ -279,7 +279,7 @@ internal actor RoomLifecycleManager<Contributor: RoomLifecycleContributor> {
logger.log(message: "Transitioning from \(status) to \(new)", level: .info)
let previous = status
status = new
let statusChange = RoomStatusChange(current: status.toRoomLifecycle, previous: previous.toRoomLifecycle)
let statusChange = RoomStatusChange(current: status.toRoomStatus, previous: previous.toRoomStatus)
emitStatusChange(statusChange)
}

Expand Down Expand Up @@ -779,7 +779,7 @@ internal actor RoomLifecycleManager<Contributor: RoomLifecycleContributor> {
}

// This check is CHA-RL2h2
if !status.toRoomLifecycle.isFailed {
if !status.toRoomStatus.isFailed {
changeStatus(to: .failed(error: error))
}
default:
Expand Down
50 changes: 3 additions & 47 deletions Sources/AblyChat/RoomStatus.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import Ably

public protocol RoomStatus: AnyObject, Sendable {
var current: RoomLifecycle { get async }
func onChange(bufferingPolicy: BufferingPolicy) async -> Subscription<RoomStatusChange>
}

public enum RoomLifecycle: Sendable, Equatable {
// TODO: rename
public enum RoomStatus: Sendable, Equatable {
case initialized
case attaching(error: ARTErrorInfo?)
case attached
Expand All @@ -16,7 +12,7 @@ public enum RoomLifecycle: Sendable, Equatable {
case releasing
case released

// Helpers to allow us to test whether a `RoomLifecycle` value has a certain case, without caring about the associated value. These are useful for in contexts where we want to use a `Bool` to communicate a case. For example:
// Helpers to allow us to test whether a `RoomStatus` value has a certain case, without caring about the associated value. These are useful for in contexts where we want to use a `Bool` to communicate a case. For example:
//
// 1. testing (e.g. `#expect(status.isFailed)`)
// 2. testing that a status does _not_ have a particular case (e.g. if !status.isFailed), which a `case` statement cannot succinctly express
Expand Down Expand Up @@ -45,43 +41,3 @@ public enum RoomLifecycle: Sendable, Equatable {
}
}
}

public struct RoomStatusChange: Sendable {
public var current: RoomLifecycle
public var previous: RoomLifecycle

public init(current: RoomLifecycle, previous: RoomLifecycle) {
self.current = current
self.previous = previous
}
}

internal actor DefaultRoomStatus: RoomStatus {
internal private(set) var current: RoomLifecycle = .initialized
internal private(set) var error: ARTErrorInfo?

private let logger: InternalLogger

internal init(logger: InternalLogger) {
self.logger = logger
}

// TODO: clean up old subscriptions (https://github.com/ably-labs/ably-chat-swift/issues/36)
private var subscriptions: [Subscription<RoomStatusChange>] = []

internal func onChange(bufferingPolicy: BufferingPolicy) -> Subscription<RoomStatusChange> {
let subscription: Subscription<RoomStatusChange> = .init(bufferingPolicy: bufferingPolicy)
subscriptions.append(subscription)
return subscription
}

/// Sets ``current`` to the given state, and emits a status change to all subscribers added via ``onChange(bufferingPolicy:)``.
internal func transition(to newState: RoomLifecycle) {
logger.log(message: "Transitioning to \(newState)", level: .debug)
let statusChange = RoomStatusChange(current: newState, previous: current)
current = newState
for subscription in subscriptions {
subscription.emit(statusChange)
}
}
}
41 changes: 0 additions & 41 deletions Tests/AblyChatTests/DefaultRoomStatusTests.swift

This file was deleted.

Loading