Various sliding sync tweaks (#446)

* Refactor SS configuration and add (unfinished) support for adding views dynamically.

* Implement pop and clear support on the room summary provider

* Register views against sliding sync

* Read invalidated vislbeRoomsSummaryProvider from the allRoomSummaryProvider

* Switch SS window range setting from the ScrollViewAdapter to a publisher debounce

* Tweak allRoomsView addition: switch from listening the visibleRoomsView's state to when it publishes the first diff update

* Cleanup client delegate and sliding sync observers and lifecycle

* Bump the RustSDK to 1.0.30-alpha

* Reuse startSync within restartSync
This commit is contained in:
Stefan Ceriu
2023-01-13 17:09:37 +02:00
committed by GitHub
parent c383029203
commit 83e6ba8f6b
13 changed files with 215 additions and 137 deletions
@@ -46,9 +46,6 @@ private class WeakClientProxyWrapper: ClientDelegate, SlidingSyncObserver {
}
class ClientProxy: ClientProxyProtocol {
/// The maximum number of timeline events required during a sync request.
static let syncLimit: UInt16 = 50
private let client: ClientProtocol
private let backgroundTaskService: BackgroundTaskServiceProtocol
private var sessionVerificationControllerProxy: SessionVerificationControllerProxy?
@@ -58,10 +55,15 @@ class ClientProxy: ClientProxyProtocol {
private var slidingSyncObserverToken: StoppableSpawn?
private var slidingSync: SlidingSync?
var visibleRoomsSlidingSyncView: SlidingSyncView?
var visibleRoomsSummaryProvider: RoomSummaryProviderProtocol?
var allRoomsSlidingSyncView: SlidingSyncView?
var allRoomsSummaryProvider: RoomSummaryProviderProtocol?
private var cancellables = Set<AnyCancellable>()
private var visibleRoomsViewProxyStateObservationToken: AnyCancellable?
deinit {
// These need to be inlined instead of using stopSync()
// as we can't call async methods safely from deinit
@@ -72,77 +74,16 @@ class ClientProxy: ClientProxyProtocol {
let callbacks = PassthroughSubject<ClientProxyCallback, Never>()
// swiftlint:disable:next function_body_length
init(client: ClientProtocol,
backgroundTaskService: BackgroundTaskServiceProtocol) async {
init(client: ClientProtocol, backgroundTaskService: BackgroundTaskServiceProtocol) async {
self.client = client
self.backgroundTaskService = backgroundTaskService
clientQueue = .init(label: "ClientProxyQueue",
attributes: .concurrent)
mediaProxy = MediaProxy(client: client,
clientQueue: clientQueue)
clientQueue = .init(label: "ClientProxyQueue", attributes: .concurrent)
mediaProxy = MediaProxy(client: client, clientQueue: clientQueue)
await Task.dispatch(on: clientQueue) {
do {
let slidingSyncBuilder = try client.slidingSync().homeserver(url: ServiceLocator.shared.settings.slidingSyncProxyBaseURLString)
let requiredState = [RequiredState(key: "m.room.avatar", value: ""),
RequiredState(key: "m.room.encryption", value: "")]
let filters = SlidingSyncRequestListFilters(isDm: nil,
spaces: [],
isEncrypted: nil,
isInvite: false,
isTombstoned: false,
roomTypes: [],
notRoomTypes: ["m.space"],
roomNameLike: nil,
tags: [],
notTags: [])
let visibleRoomsView = try SlidingSyncViewBuilder()
.timelineLimit(limit: 20)
.requiredState(requiredState: requiredState)
.filters(filters: filters)
.name(name: "CurrentlyVisibleRooms")
.syncMode(mode: .selective)
.addRange(from: 0, to: 20)
.build()
let allRoomsView = try SlidingSyncViewBuilder()
.noTimelineLimit()
.requiredState(requiredState: requiredState)
.filters(filters: filters)
.name(name: "AllRooms")
.syncMode(mode: .growingFullSync)
.batchSize(batchSize: 100)
.roomLimit(limit: 500)
.build()
let slidingSync = try slidingSyncBuilder
.addView(v: visibleRoomsView)
.addView(v: allRoomsView)
.withCommonExtensions()
.coldCache(name: "ElementX")
.build()
let visibleRoomsViewProxy = SlidingSyncViewProxy(clientProxy: self, slidingSync: slidingSync, slidingSyncView: visibleRoomsView)
let allRoomsViewProxy = SlidingSyncViewProxy(clientProxy: self, slidingSync: slidingSync, slidingSyncView: allRoomsView)
self.visibleRoomsSummaryProvider = RoomSummaryProvider(slidingSyncViewProxy: visibleRoomsViewProxy,
roomMessageFactory: RoomMessageFactory())
self.allRoomsSummaryProvider = RoomSummaryProvider(slidingSyncViewProxy: allRoomsViewProxy,
roomMessageFactory: RoomMessageFactory())
self.slidingSync = slidingSync
} catch {
MXLog.error("Failed configuring sliding sync with error: \(error)")
}
}
client.setDelegate(delegate: WeakClientProxyWrapper(clientProxy: self))
configureSlidingSync()
}
var userIdentifier: String {
@@ -181,24 +122,16 @@ class ClientProxy: ClientProxyProtocol {
}
func startSync() {
guard !client.isSoftLogout() else {
guard !client.isSoftLogout(), slidingSyncObserverToken == nil else {
return
}
slidingSync?.setObserver(observer: WeakClientProxyWrapper(clientProxy: self))
slidingSyncObserverToken = slidingSync?.sync()
}
func stopSync() {
client.setDelegate(delegate: nil)
slidingSyncObserverToken?.cancel()
slidingSync?.setObserver(observer: nil)
}
func restartSync() {
slidingSyncObserverToken?.cancel()
slidingSyncObserverToken = slidingSync?.sync()
slidingSyncObserverToken = nil
}
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol? {
@@ -297,6 +230,125 @@ class ClientProxy: ClientProxyProtocol {
}
// MARK: Private
private func restartSync() {
stopSync()
startSync()
}
private func configureSlidingSync() {
guard slidingSync == nil else {
fatalError("This shouldn't be called more than once")
}
do {
let slidingSyncBuilder = try client.slidingSync().homeserver(url: ServiceLocator.shared.settings.slidingSyncProxyBaseURLString)
let slidingSync = try slidingSyncBuilder
.withCommonExtensions()
.coldCache(name: "ElementX")
.build()
slidingSync.setObserver(observer: WeakClientProxyWrapper(clientProxy: self))
self.slidingSync = slidingSync
configureSlidingSyncViews(slidingSync: slidingSync)
guard let visibleRoomsSlidingSyncView else {
MXLog.error("Visible rooms sliding sync view unavailable")
return
}
registerSlidingSyncView(visibleRoomsSlidingSyncView)
} catch {
MXLog.error("Failed building sliding sync with error: \(error)")
}
}
// swiftlint:disable:next function_body_length
private func configureSlidingSyncViews(slidingSync: SlidingSyncProtocol) {
guard visibleRoomsSlidingSyncView == nil,
allRoomsSlidingSyncView == nil else {
fatalError("This shouldn't be called more than once")
}
let requiredState = [RequiredState(key: "m.room.avatar", value: ""),
RequiredState(key: "m.room.encryption", value: "")]
let filters = SlidingSyncRequestListFilters(isDm: nil,
spaces: [],
isEncrypted: nil,
isInvite: false,
isTombstoned: false,
roomTypes: [],
notRoomTypes: ["m.space"],
roomNameLike: nil,
tags: [],
notTags: [])
do {
let visibleRoomsView = try SlidingSyncViewBuilder()
.timelineLimit(limit: 20)
.requiredState(requiredState: requiredState)
.filters(filters: filters)
.name(name: "CurrentlyVisibleRooms")
.syncMode(mode: .selective)
.addRange(from: 0, to: 20)
.build()
let allRoomsView = try SlidingSyncViewBuilder()
.noTimelineLimit()
.requiredState(requiredState: requiredState)
.filters(filters: filters)
.name(name: "AllRooms")
.syncMode(mode: .growingFullSync)
.batchSize(batchSize: 100)
.roomLimit(limit: 500)
.build()
let visibleRoomsViewProxy = SlidingSyncViewProxy(slidingSync: slidingSync, slidingSyncView: visibleRoomsView)
let allRoomsViewProxy = SlidingSyncViewProxy(slidingSync: slidingSync, slidingSyncView: allRoomsView)
visibleRoomsSummaryProvider = RoomSummaryProvider(slidingSyncViewProxy: visibleRoomsViewProxy,
roomMessageFactory: RoomMessageFactory())
allRoomsSummaryProvider = RoomSummaryProvider(slidingSyncViewProxy: allRoomsViewProxy,
roomMessageFactory: RoomMessageFactory())
visibleRoomsViewProxy.visibleRangeUpdatePublisher.sink { [weak self] in
self?.restartSync()
}
.store(in: &cancellables)
visibleRoomsViewProxyStateObservationToken = visibleRoomsViewProxy.diffPublisher.sink { [weak self] _ in
self?.registerAllRoomSlidingSyncView()
self?.visibleRoomsViewProxyStateObservationToken = nil
}
visibleRoomsSlidingSyncView = visibleRoomsView
allRoomsSlidingSyncView = allRoomsView
} catch {
MXLog.error("Failed building sliding sync views with error: \(error)")
}
}
private func registerAllRoomSlidingSyncView() {
guard let allRoomsSlidingSyncView else {
MXLog.error("All rooms sliding sync view unavailable")
return
}
registerSlidingSyncView(allRoomsSlidingSyncView)
}
private func registerSlidingSyncView(_ view: SlidingSyncView) {
_ = slidingSync?.addView(view: view)
restartSync()
}
private func roomTupleForIdentifier(_ identifier: String) -> (SlidingSyncRoom?, Room?) {
do {
@@ -79,8 +79,6 @@ protocol ClientProxyProtocol: AnyObject, MediaProxyProtocol {
func stopSync()
func restartSync()
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol?
func loadUserDisplayName() async -> Result<String, ClientProxyError>
@@ -40,8 +40,6 @@ class MockClientProxy: ClientProxyProtocol {
func stopSync() { }
func restartSync() { }
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol? {
guard let room = visibleRoomsSummaryProvider?.roomListPublisher.value.first(where: { $0.id == identifier }) else {
return nil
@@ -51,7 +51,6 @@ private class SlidingSyncViewObserver: SlidingSyncViewRoomListObserver, SlidingS
}
class SlidingSyncViewProxy {
private weak var clientProxy: ClientProxyProtocol?
private let slidingSync: SlidingSyncProtocol
private let slidingSyncView: SlidingSyncViewProtocol
@@ -64,6 +63,7 @@ class SlidingSyncViewProxy {
let diffPublisher = PassthroughSubject<SlidingSyncViewRoomsListDiff, Never>()
let statePublisher = PassthroughSubject<SlidingSyncState, Never>()
let countPublisher = PassthroughSubject<UInt, Never>()
let visibleRangeUpdatePublisher = PassthroughSubject<Void, Never>()
deinit {
listUpdateObserverToken?.cancel()
@@ -71,8 +71,7 @@ class SlidingSyncViewProxy {
countUpdateObserverToken?.cancel()
}
init(clientProxy: ClientProxyProtocol, slidingSync: SlidingSyncProtocol, slidingSyncView: SlidingSyncViewProtocol) {
self.clientProxy = clientProxy
init(slidingSync: SlidingSyncProtocol, slidingSyncView: SlidingSyncViewProtocol) {
self.slidingSync = slidingSync
self.slidingSyncView = slidingSyncView
@@ -103,11 +102,11 @@ class SlidingSyncViewProxy {
try slidingSync.getRoom(roomId: identifier)
}
func updateVisibleRange(_ range: ClosedRange<Int>) {
func updateVisibleRange(_ range: Range<Int>) {
MXLog.info("Setting sliding sync view range to \(range)")
slidingSyncView.setRange(start: UInt32(range.lowerBound), end: UInt32(range.upperBound))
clientProxy?.restartSync()
visibleRangeUpdatePublisher.send(())
}
}
@@ -46,7 +46,7 @@ class MockRoomSummaryProvider: RoomSummaryProviderProtocol {
func updateRoomsWithIdentifiers(_ identifiers: [String]) { }
func updateVisibleRange(_ range: ClosedRange<Int>) { }
func updateVisibleRange(_ range: Range<Int>) { }
// MARK: - Private
@@ -68,7 +68,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
}
}
func updateVisibleRange(_ range: ClosedRange<Int>) {
func updateVisibleRange(_ range: Range<Int>) {
slidingSyncViewProxy.updateVisibleRange(range)
}
@@ -181,6 +181,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
}
}
// swiftlint:disable:next cyclomatic_complexity
private func buildDiff(from diff: SlidingSyncViewRoomsListDiff, on rooms: [RoomSummary]) -> CollectionDifference<RoomSummary>? {
var changes = [CollectionDifference<RoomSummary>.Change]()
@@ -216,6 +217,18 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
for (index, value) in values.enumerated() {
changes.append(.insert(offset: index, element: buildSummaryForRoomListEntry(value), associatedWith: nil))
}
case .clear:
MXLog.verbose("Clear all items, current total count: \(rooms.count)")
for (index, value) in rooms.enumerated() {
changes.append(.remove(offset: index, element: value, associatedWith: nil))
}
case .pop:
MXLog.verbose("Pop, current total count: \(rooms.count)")
guard let value = rooms.last else {
fatalError()
}
changes.append(.remove(offset: rooms.count - 1, element: value, associatedWith: nil))
}
return CollectionDifference(changes)
@@ -55,5 +55,5 @@ protocol RoomSummaryProviderProtocol {
/// - Parameter identifiers: the identifiers for the rooms that have changed
func updateRoomsWithIdentifiers(_ identifiers: [String])
func updateVisibleRange(_ range: ClosedRange<Int>)
func updateVisibleRange(_ range: Range<Int>)
}