Skip to content

Commit

Permalink
Merge pull request #180 from ably/153-lifecycle-contributor-precedence
Browse files Browse the repository at this point in the history
[ECO-5149] Implement lifecycle contributor precedence
  • Loading branch information
lawrence-forooghian authored Dec 5, 2024
2 parents bc1f56c + 90f436b commit ee6194b
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 7 deletions.
10 changes: 5 additions & 5 deletions Sources/AblyChat/Room.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ internal actor DefaultRoom<LifecycleManagerFactory: RoomLifecycleManagerFactory>
}
}

/// The features are returned in CHA-RC2e order.
static func fromRoomOptions(_ roomOptions: RoomOptions) -> [Self] {
var result: [Self] = [.messages]

Expand Down Expand Up @@ -214,8 +213,6 @@ internal actor DefaultRoom<LifecycleManagerFactory: RoomLifecycleManagerFactory>
}

/// Each feature in `featuresWithOptions` is guaranteed to appear in the `features` member of precisely one of the returned array’s values.
///
/// The elements of `featuresWithOptions` must be in CHA-RC2e order.
private static func createFeatureChannelPartialDependencies(roomID: String, featuresWithOptions: [RoomFeatureWithOptions], realtime: RealtimeClient) -> [(features: [RoomFeature], featureChannelPartialDependencies: FeatureChannelPartialDependencies)] {
// CHA-RC3a

Expand All @@ -225,7 +222,7 @@ internal actor DefaultRoom<LifecycleManagerFactory: RoomLifecycleManagerFactory>

let featuresGroupedByChannelName = Dictionary(grouping: featuresWithOptions) { $0.toRoomFeature.channelNameForRoomID(roomID) }

return featuresGroupedByChannelName.map { channelName, features in
let unorderedResult = featuresGroupedByChannelName.map { channelName, features in
var channelOptions = RealtimeChannelOptions()

// channel setup for presence and occupancy
Expand All @@ -251,13 +248,16 @@ internal actor DefaultRoom<LifecycleManagerFactory: RoomLifecycleManagerFactory>
let channel = realtime.getChannel(channelName, opts: channelOptions)

// Give the contributor the first of the enabled features that correspond to this channel, using CHA-RC2e ordering. This will determine which feature is used for atttachment and detachment errors.
let contributorFeature = features[0].toRoomFeature
let contributorFeature = features.map(\.toRoomFeature).sorted { RoomFeature.areInPrecedenceListOrder($0, $1) }[0]

let contributor = DefaultRoomLifecycleContributor(channel: .init(underlyingChannel: channel), feature: contributorFeature)
let featureChannelPartialDependencies = FeatureChannelPartialDependencies(channel: channel, contributor: contributor)

return (features.map(\.toRoomFeature), featureChannelPartialDependencies)
}

// Sort the result in CHA-RC2e order
return unorderedResult.sorted { RoomFeature.areInPrecedenceListOrder($0.1.contributor.feature, $1.1.contributor.feature) }
}

private static func createFeatureChannels(partialDependencies: [(features: [RoomFeature], featureChannelPartialDependencies: FeatureChannelPartialDependencies)], lifecycleManager: RoomLifecycleManager) -> [RoomFeature: DefaultFeatureChannel] {
Expand Down
13 changes: 11 additions & 2 deletions Sources/AblyChat/RoomFeature.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import Ably

/// The features offered by a chat room.
internal enum RoomFeature {
internal enum RoomFeature: CaseIterable {
// This list MUST be kept in the same order as the list in CHA-RC2e, in order for the implementation of `areInPrecedenceListOrder` to work.
case messages
case presence
case typing
case reactions
case occupancy
case typing

internal func channelNameForRoomID(_ roomID: String) -> String {
"\(roomID)::$chat::$\(channelNameSuffix)"
Expand All @@ -27,6 +28,14 @@ internal enum RoomFeature {
"typingIndicators"
}
}

/// Returns a `Bool` indicating whether `first` and `second` are in the same order as the list given in CHA-RC2e.
internal static func areInPrecedenceListOrder(_ first: Self, _ second: Self) -> Bool {
let allCases = Self.allCases
let indexOfFirst = allCases.firstIndex(of: first)!
let indexOfSecond = allCases.firstIndex(of: second)!
return indexOfFirst < indexOfSecond
}
}

/// Provides all of the channel-related functionality that a room feature (e.g. an implementation of ``Messages``) needs.
Expand Down
24 changes: 24 additions & 0 deletions Tests/AblyChatTests/DefaultRoomTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,30 @@ struct DefaultRoomTests {
#expect(Set(channelsGetArguments.map(\.name)) == Set(expectedFetchedChannelNames))
}

// @spec CHA-RC2e
// @spec CHA-RL10
@Test
func lifecycleContributorOrder() async throws {
// Given: a DefaultRoom, instance, with all room features enabled
let channelsList = [
MockRealtimeChannel(name: "basketball::$chat::$chatMessages"),
MockRealtimeChannel(name: "basketball::$chat::$reactions"),
MockRealtimeChannel(name: "basketball::$chat::$typingIndicators"),
]
let channels = MockChannels(channels: channelsList)
let realtime = MockRealtime.create(channels: channels)
let lifecycleManagerFactory = MockRoomLifecycleManagerFactory()
_ = try await DefaultRoom(realtime: realtime, chatAPI: ChatAPI(realtime: realtime), roomID: "basketball", options: .allFeaturesEnabled, logger: TestLogger(), lifecycleManagerFactory: lifecycleManagerFactory)

// Then: The array of contributors with which it initializes the RoomLifecycleManager are in the same order as the following list:
//
// messages, presence, typing, reactions, occupancy
//
// (note that we do not say that it is the _same_ list, because we combine multiple features into a single contributor)
let lifecycleManagerCreationArguments = try #require(await lifecycleManagerFactory.createManagerArguments.first)
#expect(lifecycleManagerCreationArguments.contributors.map(\.feature) == [.messages, .typing, .reactions])
}

// @specUntested CHA-RC2b - We chose to implement this failure with an idiomatic fatalError instead of throwing, but we can’t test this.

// This is just a basic sense check to make sure the room getters are working as expected, since we don’t have unit tests for some of the features at the moment.
Expand Down

0 comments on commit ee6194b

Please sign in to comment.