Skip to content

Commit

Permalink
Implement CHA-RC3a (only fetch each channel once)
Browse files Browse the repository at this point in the history
Based on spec at 28d09cc.

Resolves #132.
  • Loading branch information
lawrence-forooghian committed Nov 20, 2024
1 parent 00bb2e1 commit f29dc7b
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 24 deletions.
69 changes: 45 additions & 24 deletions Sources/AblyChat/Room.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,36 +131,57 @@ internal actor DefaultRoom<LifecycleManagerFactory: RoomLifecycleManagerFactory>
internal var contributor: DefaultRoomLifecycleContributor
}

private static func createFeatureChannelPartialDependencies(roomID: String, roomOptions _: RoomOptions, realtime: RealtimeClient) -> [RoomFeature: FeatureChannelPartialDependencies] {
.init(uniqueKeysWithValues: [
RoomFeature.messages,
RoomFeature.reactions,
RoomFeature.presence,
RoomFeature.occupancy,
].map { feature in
/// The returned dictionary is guaranteed to have an entry for each element of `features`.
private static func createChannelsForFeatures(_ features: [RoomFeature], roomID: String, roomOptions _: RoomOptions, realtime: RealtimeClient) -> [RoomFeature: RealtimeChannelProtocol] {
// CHA-RC3a

// Multiple features can share a realtime channel. We fetch each realtime channel exactly once, merging the channel options for the various features that use this channel.

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

let pairsOfFeatureAndChannel = featuresGroupedByChannelName.flatMap { channelName, features in
var channelOptions = RealtimeChannelOptions()

// channel setup for presence and occupancy
if feature == .presence {
// TODO: Restore this code once we understand weird Realtime behaviour and spec points (https://github.com/ably-labs/ably-chat-swift/issues/133)
/*
let presenceOptions = roomOptions.presence

if presenceOptions?.enter ?? false {
channelOptions.modes.insert(.presence)
}

if presenceOptions?.subscribe ?? false {
channelOptions.modes.insert(.presenceSubscribe)
}
*/
} else if feature == .occupancy {
channelOptions.params = ["occupancy": "metrics"]
for feature in features {
if feature == .presence {
// TODO: Restore this code once we understand weird Realtime behaviour and spec points (https://github.com/ably-labs/ably-chat-swift/issues/133)
/*
let presenceOptions = roomOptions.presence

if presenceOptions?.enter ?? false {
channelOptions.modes.insert(.presence)
}

if presenceOptions?.subscribe ?? false {
channelOptions.modes.insert(.presenceSubscribe)
}
*/
} else if feature == .occupancy {
var params: [String: String] = channelOptions.params ?? [:]
params["occupancy"] = "metrics"
channelOptions.params = params
}
}

let channel = realtime.getChannel(feature.channelNameForRoomID(roomID), opts: channelOptions)
let contributor = DefaultRoomLifecycleContributor(channel: .init(underlyingChannel: channel), feature: feature)
let channel = realtime.getChannel(channelName, opts: channelOptions)
return features.map { ($0, channel) }
}

return Dictionary(uniqueKeysWithValues: pairsOfFeatureAndChannel)
}

private static func createFeatureChannelPartialDependencies(roomID: String, roomOptions: RoomOptions, realtime: RealtimeClient) -> [RoomFeature: FeatureChannelPartialDependencies] {
let features: [RoomFeature] = [
.messages,
.reactions,
.presence,
.occupancy,
]
let channelsByFeature = createChannelsForFeatures(features, roomID: roomID, roomOptions: roomOptions, realtime: realtime)

return .init(uniqueKeysWithValues: channelsByFeature.map { feature, channel in
let contributor = DefaultRoomLifecycleContributor(channel: .init(underlyingChannel: channel), feature: feature)
return (feature, .init(channel: channel, contributor: contributor))
})
}
Expand Down
23 changes: 23 additions & 0 deletions Tests/AblyChatTests/DefaultRoomTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,29 @@ struct DefaultRoomTests {
#expect(channelsGetArguments.allSatisfy { $0.options.attachOnSubscribe == false })
}

// @spec CHA-RC3a
@Test
func fetchesEachChannelOnce() async throws {
// Given: A DefaultRoom instance, configured to use presence and occupancy
let channelsList = [
MockRealtimeChannel(name: "basketball::$chat::$chatMessages", attachResult: .success),
MockRealtimeChannel(name: "basketball::$chat::$reactions", attachResult: .success),
]
let channels = MockChannels(channels: channelsList)
let realtime = MockRealtime.create(channels: channels)
let roomOptions = RoomOptions(presence: PresenceOptions(), occupancy: OccupancyOptions())
_ = try await DefaultRoom(realtime: realtime, chatAPI: ChatAPI(realtime: realtime), roomID: "basketball", options: roomOptions, logger: TestLogger(), lifecycleManagerFactory: MockRoomLifecycleManagerFactory())

// Then: It fetches the …$chatMessages channel (which is used by messages, presence, and occupancy) only once, and the options with which it does so are the result of merging the options used by the presence feature and those used by the occupancy feature
let channelsGetArguments = channels.getArguments
#expect(channelsGetArguments.map(\.name).sorted() == ["basketball::$chat::$chatMessages", "basketball::$chat::$reactions"])

let chatMessagesChannelGetOptions = try #require(channelsGetArguments.first { $0.name == "basketball::$chat::$chatMessages" }?.options)
#expect(chatMessagesChannelGetOptions.params?["occupancy"] == "metrics")
// TODO: Restore this code once we understand weird Realtime behaviour and spec points (https://github.com/ably-labs/ably-chat-swift/issues/133)
// #expect(chatMessagesChannelGetOptions.modes == [.presence, .presenceSubscribe])
}

// MARK: - Features

// @spec CHA-M1
Expand Down

0 comments on commit f29dc7b

Please sign in to comment.