From 711fdd25afa4813829d7b46934843482554a7429 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Fri, 18 Dec 2015 15:13:59 +0000 Subject: [PATCH] Improve perf of refreshing room list. Show spinner when loading left rooms. When the JS SDK encounters a new room it will emit a flurry of events for things like state and room members. Refreshing the room list on each event is bad for performance. This is okay initially because the room list is only shown after the first sync, but when getting archived rooms it locks up for 15-30s as it thrashes. Add a 1s cap to refreshRoomList() which means that it will refresh *AT MOST* once every second. If it has been >1s since the last refresh it will immediately refresh. If it has been <1s it will wait the difference. --- src/components/views/rooms/RoomList.js | 55 +++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index 758d3500dd..af20b25421 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -38,6 +38,7 @@ module.exports = React.createClass({ getInitialState: function() { return { activityMap: null, + isLoadingLeftRooms: false, lists: {}, } }, @@ -89,20 +90,24 @@ module.exports = React.createClass({ }, onRoom: function(room) { - this.refreshRoomList(); + this._delayedRefreshRoomList(); }, onDeleteRoom: function(roomId) { - this.refreshRoomList(); + this._delayedRefreshRoomList(); }, onArchivedHeaderClick: function(isHidden) { if (!isHidden) { + var self = this; + this.setState({ isLoadingLeftRooms: true }); // we don't care about the response since it comes down via "Room" // events. MatrixClientPeg.get().syncLeftRooms().catch(function(err) { console.error("Failed to sync left rooms: %s", err); console.error(err); + }).finally(function() { + self.setState({ isLoadingLeftRooms: false }); }); } }, @@ -143,22 +148,57 @@ module.exports = React.createClass({ }, onRoomName: function(room) { - this.refreshRoomList(); + this._delayedRefreshRoomList(); }, onRoomTags: function(event, room) { - this.refreshRoomList(); + this._delayedRefreshRoomList(); }, onRoomStateEvents: function(ev, state) { - setTimeout(this.refreshRoomList, 0); + this._delayedRefreshRoomList(); }, onRoomMemberName: function(ev, member) { - setTimeout(this.refreshRoomList, 0); + this._delayedRefreshRoomList(); + }, + + _delayedRefreshRoomList: function() { + // There can be 1000s of JS SDK events when rooms are initially synced; + // we don't want to do lots of work rendering until things have settled. + // Therefore, keep a 1s refresh buffer which will refresh the room list + // at MOST once every 1s to prevent thrashing. + var MAX_REFRESH_INTERVAL_MS = 1000; + var self = this; + + if (!self._lastRefreshRoomListTs) { + self.refreshRoomList(); // first refresh evar + } + else { + var timeWaitedMs = Date.now() - self._lastRefreshRoomListTs; + if (timeWaitedMs > MAX_REFRESH_INTERVAL_MS) { + clearTimeout(self._refreshRoomListTimerId); + self._refreshRoomListTimerId = null; + self.refreshRoomList(); // refreshed more than MAX_REFRESH_INTERVAL_MS ago + } + else { + // refreshed less than MAX_REFRESH_INTERVAL_MS ago, wait the difference + // if we aren't already waiting. If we are waiting then NOP, it will + // fire soon, promise! + if (!self._refreshRoomListTimerId) { + self._refreshRoomListTimerId = setTimeout(function() { + self.refreshRoomList(); + }, 10 + MAX_REFRESH_INTERVAL_MS - timeWaitedMs); // 10 is a buffer amount + } + } + } }, refreshRoomList: function() { + // console.log("DEBUG: Refresh room list delta=%s ms", + // (!this._lastRefreshRoomListTs ? "-" : (Date.now() - this._lastRefreshRoomListTs)) + // ); + // TODO: rather than bluntly regenerating and re-sorting everything // every time we see any kind of room change from the JS SDK // we could do incremental updates on our copy of the state @@ -166,6 +206,7 @@ module.exports = React.createClass({ // us re-rendering all the sublists every time anything changes anywhere // in the state of the client. this.setState(this.getRoomLists()); + this._lastRefreshRoomListTs = Date.now(); }, getRoomLists: function() { @@ -320,6 +361,8 @@ module.exports = React.createClass({ selectedRoom={ self.props.selectedRoom } collapsed={ self.props.collapsed } alwaysShowHeader={ true } + startAsHidden={ true } + showSpinner={ self.state.isLoadingLeftRooms } onHeaderClick= { self.onArchivedHeaderClick } />