Skip to content

Commit

Permalink
Merge pull request #134 from ably-labs/132-fetch-each-channel-once
Browse files Browse the repository at this point in the history
[ECO-5128] Fetch each channel once
  • Loading branch information
lawrence-forooghian authored Nov 21, 2024
2 parents 5344d7f + f29dc7b commit de96fd8
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 21 deletions.
65 changes: 44 additions & 21 deletions Sources/AblyChat/Room.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,34 +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
var channelOptions = RealtimeChannelOptions()
/// 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

// channel setup for presence and occupancy
if feature == .presence {
let channelOptions = ARTRealtimeChannelOptions()
let presenceOptions = roomOptions.presence
// 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.

if presenceOptions?.enter ?? false {
channelOptions.modes.insert(.presence)
}
let featuresGroupedByChannelName = Dictionary(grouping: features) { $0.channelNameForRoomID(roomID) }

if presenceOptions?.subscribe ?? false {
channelOptions.modes.insert(.presenceSubscribe)
let pairsOfFeatureAndChannel = featuresGroupedByChannelName.flatMap { channelName, features in
var channelOptions = RealtimeChannelOptions()

// channel setup for presence and occupancy
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
}
} else if feature == .occupancy {
channelOptions.params = ["occupancy": "metrics"]
}

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 de96fd8

Please sign in to comment.