Skip to content

Commit d137242

Browse files
committed
Improve boot time by implementing sliding sync
1 parent c9598cf commit d137242

File tree

7 files changed

+204
-36
lines changed

7 files changed

+204
-36
lines changed

packages/host/app/components/ai-assistant/panel.gts

+2-1
Original file line numberDiff line numberDiff line change
@@ -657,7 +657,8 @@ export default class AiAssistantPanel extends Component<Signature> {
657657
return Boolean(
658658
this.matrixService.currentRoomId &&
659659
this.maybeMonacoSDK &&
660-
this.doCreateRoom.isIdle,
660+
this.doCreateRoom.isIdle &&
661+
!this.matrixService.isLoadingTimeline,
661662
);
662663
}
663664
}

packages/host/app/services/matrix-service.ts

+189-27
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { debounce } from '@ember/runloop';
44
import Service, { service } from '@ember/service';
55
import { cached, tracked } from '@glimmer/tracking';
66

7-
import { task } from 'ember-concurrency';
7+
import { task, timeout, restartableTask } from 'ember-concurrency';
88
import window from 'ember-window-mock';
99
import { cloneDeep } from 'lodash';
1010
import {
@@ -14,6 +14,12 @@ import {
1414
type EmittedEvents,
1515
type ISendEventResponse,
1616
} from 'matrix-js-sdk';
17+
import {
18+
SlidingSync,
19+
MSC3575List,
20+
SlidingSyncEvent,
21+
} from 'matrix-js-sdk/lib/sliding-sync';
22+
import { SlidingSyncState } from 'matrix-js-sdk/lib/sliding-sync';
1723
import stringify from 'safe-stable-stringify';
1824
import { md5 } from 'super-fast-md5';
1925
import { TrackedMap } from 'tracked-built-ins';
@@ -115,6 +121,10 @@ import type * as MatrixSDK from 'matrix-js-sdk';
115121
const { matrixURL } = ENV;
116122
const MAX_CARD_SIZE_KB = 60;
117123
const STATE_EVENTS_OF_INTEREST = ['m.room.create', 'm.room.name'];
124+
const SLIDING_SYNC_AI_ROOM_LIST_NAME = 'ai-room';
125+
const SLIDING_SYNC_AUTH_ROOM_LIST_NAME = 'auth-room';
126+
const SLIDING_SYNC_LIST_RANGE_SIZE = 2;
127+
const SLIDING_SYNC_LIST_TIMELINE_LIMIT = 1;
118128

119129
const realmEventsLogger = logger('realm:events');
120130

@@ -139,6 +149,12 @@ export default class MatrixService extends Service {
139149
@tracked private _isNewUser = false;
140150
@tracked private postLoginCompleted = false;
141151
@tracked private _currentRoomId: string | undefined;
152+
@tracked private timelineLoadingState: Map<string, boolean> =
153+
new TrackedMap();
154+
@tracked private currentAiRoomListRange = SLIDING_SYNC_LIST_RANGE_SIZE;
155+
@tracked private totalAiRooms?: number;
156+
@tracked private totalAuthRooms?: number;
157+
@tracked private currentAuthRoomListRange = SLIDING_SYNC_LIST_RANGE_SIZE;
142158

143159
profile = getMatrixProfile(this, () => this.userId);
144160

@@ -164,6 +180,7 @@ export default class MatrixService extends Service {
164180
new TrackedMap();
165181
private cardHashes: Map<string, string> = new Map(); // hashes <> event id
166182
private skillCardHashes: Map<string, string> = new Map(); // hashes <> event id
183+
private slidingSync: SlidingSync | undefined;
167184

168185
constructor(owner: Owner) {
169186
super(owner);
@@ -181,6 +198,7 @@ export default class MatrixService extends Service {
181198
set currentRoomId(value: string | undefined) {
182199
this._currentRoomId = value;
183200
if (value) {
201+
this.loadAllTimelineEvents(value);
184202
window.localStorage.setItem(CurrentRoomIdPersistenceKey, value);
185203
} else {
186204
window.localStorage.removeItem(CurrentRoomIdPersistenceKey);
@@ -475,8 +493,9 @@ export default class MatrixService extends Service {
475493
this.bindEventListeners();
476494

477495
try {
478-
await this._client.startClient();
479-
let accountDataContent = await this._client.getAccountDataFromServer<{
496+
this.initSlidingSync();
497+
await this.client.startClient({ slidingSync: this.slidingSync });
498+
let accountDataContent = await this.client.getAccountDataFromServer<{
480499
realms: string[];
481500
}>(APP_BOXEL_REALMS_EVENT_TYPE);
482501
await this.realmServer.setAvailableRealmURLs(
@@ -498,6 +517,126 @@ export default class MatrixService extends Service {
498517
}
499518
}
500519

520+
private initSlidingSync() {
521+
let lists: Map<string, MSC3575List> = new Map();
522+
lists.set(SLIDING_SYNC_AI_ROOM_LIST_NAME, {
523+
ranges: [[0, SLIDING_SYNC_LIST_RANGE_SIZE]],
524+
filters: {
525+
is_dm: false,
526+
},
527+
timeline_limit: SLIDING_SYNC_LIST_TIMELINE_LIMIT,
528+
required_state: [['*', '*']],
529+
});
530+
lists.set(SLIDING_SYNC_AUTH_ROOM_LIST_NAME, {
531+
ranges: [[0, SLIDING_SYNC_LIST_RANGE_SIZE]],
532+
filters: {
533+
is_dm: true,
534+
},
535+
timeline_limit: 1,
536+
required_state: [['*', '*']],
537+
});
538+
this.slidingSync = new SlidingSync(
539+
this.client.baseUrl,
540+
lists,
541+
{
542+
timeline_limit: SLIDING_SYNC_LIST_TIMELINE_LIMIT,
543+
},
544+
this.client as any,
545+
3000,
546+
);
547+
548+
this.slidingSync.on(SlidingSyncEvent.Lifecycle, (state, response) => {
549+
if (state !== SlidingSyncState.Complete || !response) {
550+
return;
551+
}
552+
553+
// Handle AI rooms
554+
if (
555+
response.lists[SLIDING_SYNC_AI_ROOM_LIST_NAME]?.ops?.[0].op === 'SYNC'
556+
) {
557+
if (
558+
response.lists?.[SLIDING_SYNC_AI_ROOM_LIST_NAME]?.count !== undefined
559+
) {
560+
this.totalAiRooms =
561+
response.lists[SLIDING_SYNC_AI_ROOM_LIST_NAME].count;
562+
const currentRange =
563+
response.lists[SLIDING_SYNC_AI_ROOM_LIST_NAME].ops?.[0]?.range;
564+
565+
if (currentRange) {
566+
this.currentAiRoomListRange = currentRange[1];
567+
this.maybeLoadMoreAiRooms.perform();
568+
}
569+
}
570+
}
571+
572+
// Handle Auth rooms
573+
if (
574+
response.lists[SLIDING_SYNC_AUTH_ROOM_LIST_NAME]?.ops?.[0].op === 'SYNC'
575+
) {
576+
if (
577+
response.lists?.[SLIDING_SYNC_AUTH_ROOM_LIST_NAME]?.count !==
578+
undefined
579+
) {
580+
this.totalAuthRooms =
581+
response.lists[SLIDING_SYNC_AUTH_ROOM_LIST_NAME].count;
582+
const currentAuthRange =
583+
response.lists[SLIDING_SYNC_AUTH_ROOM_LIST_NAME].ops?.[0]?.range;
584+
585+
if (currentAuthRange) {
586+
this.currentAuthRoomListRange = currentAuthRange[1];
587+
this.maybeLoadMoreAuthRooms.perform();
588+
}
589+
}
590+
}
591+
});
592+
593+
return this.slidingSync;
594+
}
595+
596+
private maybeLoadMoreAiRooms = restartableTask(async () => {
597+
if (!this.totalAiRooms || !this.slidingSync) {
598+
return;
599+
}
600+
601+
while (this.currentAiRoomListRange < this.totalAiRooms) {
602+
const nextRange = Math.min(
603+
this.currentAiRoomListRange + 2,
604+
this.totalAiRooms,
605+
);
606+
607+
try {
608+
await this.slidingSync.setListRanges('ai-room', [[0, nextRange]]);
609+
this.currentAiRoomListRange = nextRange;
610+
await timeout(500);
611+
} catch (error) {
612+
console.error('Error expanding room range:', error);
613+
break;
614+
}
615+
}
616+
});
617+
618+
private maybeLoadMoreAuthRooms = restartableTask(async () => {
619+
if (!this.totalAuthRooms || !this.slidingSync) {
620+
return;
621+
}
622+
623+
while (this.currentAuthRoomListRange < this.totalAuthRooms) {
624+
const nextRange = Math.min(
625+
this.currentAuthRoomListRange + 2,
626+
this.totalAuthRooms,
627+
);
628+
629+
try {
630+
await this.slidingSync.setListRanges('auth-room', [[0, nextRange]]);
631+
this.currentAuthRoomListRange = nextRange;
632+
await timeout(500);
633+
} catch (error) {
634+
console.error('Error expanding auth room range:', error);
635+
break;
636+
}
637+
}
638+
});
639+
501640
private async loginToRealms() {
502641
// This is where we would actually load user-specific choices out of the
503642
// user's profile based on this.client.getUserId();
@@ -534,7 +673,7 @@ export default class MatrixService extends Service {
534673
| CommandResultWithOutputContent
535674
| CommandDefinitionsContent,
536675
) {
537-
let roomData = await this.ensureRoomData(roomId);
676+
let roomData = this.ensureRoomData(roomId);
538677
return roomData.mutex.dispatch(async () => {
539678
if ('data' in content) {
540679
const encodedContent = {
@@ -1008,7 +1147,7 @@ export default class MatrixService extends Service {
10081147
}
10091148

10101149
async setPowerLevel(roomId: string, userId: string, powerLevel: number) {
1011-
let roomData = await this.ensureRoomData(roomId);
1150+
let roomData = this.ensureRoomData(roomId);
10121151
await roomData.mutex.dispatch(async () => {
10131152
return this.client.setPowerLevel(roomId, userId, powerLevel);
10141153
});
@@ -1045,7 +1184,7 @@ export default class MatrixService extends Service {
10451184
content: Record<string, any>,
10461185
stateKey: string = '',
10471186
) {
1048-
let roomData = await this.ensureRoomData(roomId);
1187+
let roomData = this.ensureRoomData(roomId);
10491188
await roomData.mutex.dispatch(async () => {
10501189
return this.client.sendStateEvent(roomId, eventType, content, stateKey);
10511190
});
@@ -1059,7 +1198,7 @@ export default class MatrixService extends Service {
10591198
content: Record<string, any>,
10601199
) => Promise<Record<string, any>>,
10611200
) {
1062-
let roomData = await this.ensureRoomData(roomId);
1201+
let roomData = this.ensureRoomData(roomId);
10631202
await roomData.mutex.dispatch(async () => {
10641203
let currentContent = await this.getStateEventSafe(
10651204
roomId,
@@ -1077,21 +1216,21 @@ export default class MatrixService extends Service {
10771216
}
10781217

10791218
async leave(roomId: string) {
1080-
let roomData = await this.ensureRoomData(roomId);
1219+
let roomData = this.ensureRoomData(roomId);
10811220
await roomData.mutex.dispatch(async () => {
10821221
return this.client.leave(roomId);
10831222
});
10841223
}
10851224

10861225
async forget(roomId: string) {
1087-
let roomData = await this.ensureRoomData(roomId);
1226+
let roomData = this.ensureRoomData(roomId);
10881227
await roomData.mutex.dispatch(async () => {
10891228
return this.client.forget(roomId);
10901229
});
10911230
}
10921231

10931232
async setRoomName(roomId: string, name: string) {
1094-
let roomData = await this.ensureRoomData(roomId);
1233+
let roomData = this.ensureRoomData(roomId);
10951234
await roomData.mutex.dispatch(async () => {
10961235
return this.client.setRoomName(roomId, name);
10971236
});
@@ -1131,11 +1270,43 @@ export default class MatrixService extends Service {
11311270
return await this.client.isUsernameAvailable(username);
11321271
}
11331272

1134-
async getRoomState(roomId: string) {
1135-
return this.client
1136-
.getRoom(roomId)
1137-
?.getLiveTimeline()
1138-
.getState('f' as MatrixSDK.Direction);
1273+
private async loadAllTimelineEvents(roomId: string) {
1274+
let roomData = this.ensureRoomData(roomId);
1275+
let room = this.client.getRoom(roomId);
1276+
1277+
if (!room) {
1278+
throw new Error(`Cannot find room with id ${roomId}`);
1279+
}
1280+
1281+
this.timelineLoadingState.set(roomId, true);
1282+
try {
1283+
while (room.oldState.paginationToken != null) {
1284+
await this.client.scrollback(room);
1285+
await timeout(1000);
1286+
let rs = room.getLiveTimeline().getState('f' as MatrixSDK.Direction);
1287+
if (rs) {
1288+
roomData.notifyRoomStateUpdated(rs);
1289+
}
1290+
}
1291+
1292+
const timeline = room.getLiveTimeline();
1293+
const events = timeline.getEvents();
1294+
for (let event of events) {
1295+
await this.processDecryptedEvent(this.buildEventForProcessing(event));
1296+
}
1297+
} catch (error) {
1298+
console.error('Error loading timeline events:', error);
1299+
throw error;
1300+
} finally {
1301+
this.timelineLoadingState.set(roomId, false);
1302+
}
1303+
}
1304+
1305+
get isLoadingTimeline() {
1306+
if (!this.currentRoomId) {
1307+
return false;
1308+
}
1309+
return this.timelineLoadingState.get(this.currentRoomId) ?? false;
11391310
}
11401311

11411312
async sendActiveLLMEvent(roomId: string, model: string) {
@@ -1152,18 +1323,14 @@ export default class MatrixService extends Service {
11521323
`bug: roomId is undefined for event ${JSON.stringify(event, null, 2)}`,
11531324
);
11541325
}
1155-
let roomData = await this.ensureRoomData(roomId);
1326+
let roomData = this.ensureRoomData(roomId);
11561327
roomData.addEvent(event, oldEventId);
11571328
}
11581329

1159-
private async ensureRoomData(roomId: string) {
1330+
private ensureRoomData(roomId: string) {
11601331
let roomData = this.getRoomData(roomId);
11611332
if (!roomData) {
11621333
roomData = new Room(roomId);
1163-
let rs = await this.getRoomState(roomId);
1164-
if (rs) {
1165-
roomData.notifyRoomStateUpdated(rs);
1166-
}
11671334
this.setRoomData(roomId, roomData);
11681335
}
11691336
return roomData;
@@ -1282,7 +1449,7 @@ export default class MatrixService extends Service {
12821449
}
12831450
roomStates = Array.from(roomStateMap.values());
12841451
for (let rs of roomStates) {
1285-
let roomData = await this.ensureRoomData(rs.roomId);
1452+
let roomData = this.ensureRoomData(rs.roomId);
12861453
roomData.notifyRoomStateUpdated(rs);
12871454
}
12881455
roomStateUpdatesDrained!();
@@ -1482,11 +1649,6 @@ export default class MatrixService extends Service {
14821649
) {
14831650
this.commandService.executeCommandEventsIfNeeded(event);
14841651
}
1485-
1486-
if (room.oldState.paginationToken != null) {
1487-
// we need to scroll back to capture any room events fired before this one
1488-
await this.client?.scrollback(room);
1489-
}
14901652
}
14911653

14921654
async activateCodingSkill() {

packages/host/ember-cli-build.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@ module.exports = function (defaults) {
8686
process: false,
8787
},
8888
alias: {
89-
'matrix-js-sdk': 'matrix-js-sdk/src/browser-index.ts', // Consume matrix-js-sdk via Typescript ESM so that code splitting works to exlcude massive matrix-sdk-crypto-wasm from the main bundle
89+
'matrix-js-sdk$': 'matrix-js-sdk/src/browser-index.ts', // Consume matrix-js-sdk via Typescript ESM so that code splitting works to exlcude massive matrix-sdk-crypto-wasm from the main bundle
90+
'matrix-js-sdk/src/sliding-sync':
91+
'matrix-js-sdk/src/sliding-sync.ts',
9092
},
9193
},
9294
node: {

packages/matrix/docker/synapse/dev/homeserver.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,6 @@ email:
9999

100100
templates:
101101
custom_template_directory: "/custom/templates/"
102+
103+
experimental_features:
104+
msc3575_enabled: true

packages/matrix/docker/synapse/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ export async function synapseStart(
126126
);
127127
await dockerCreateNetwork({ networkName: 'boxel' });
128128
const synapseId = await dockerRun({
129-
image: 'matrixdotorg/synapse:v1.99.0rc1',
129+
image: 'matrixdotorg/synapse:v1.126.0',
130130
containerName: 'boxel-synapse',
131131
dockerParams: [
132132
'--rm',

patches/matrix-js-sdk@31.0.0.patch

2.63 KB
Binary file not shown.

0 commit comments

Comments
 (0)