Skip to content

Commit f5c9b16

Browse files
committed
refactored the way we're preparing event schedules, by distinguishing cache durations for current day and other days, and by being a little bit more aggressive on caching
1 parent 825d475 commit f5c9b16

File tree

6 files changed

+125
-109
lines changed

6 files changed

+125
-109
lines changed

mobile/src/models/VoxxrinTalk.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@ export type VoxxrinDetailedTalk = Replace<VoxxrinTalk, {
2929
}> & Replace<Omit<DetailedTalk, (keyof Talk) | "summary">, {
3030
}>
3131

32+
export function removeTalkOverflowsAndDuplicates(talks: VoxxrinTalk[]) {
33+
const talksById = talks.reduce((talksById, talk) => {
34+
if(!talk.isOverflow) {
35+
talksById.set(talk.id.value, talk);
36+
}
37+
return talksById;
38+
}, new Map<string, VoxxrinTalk>())
39+
40+
return Array.from(talksById.values());
41+
}
42+
3243
export function createVoxxrinTalkFromFirestore(event: VoxxrinConferenceDescriptor, firestoreTalk: Talk) {
3344
const format = findTalkFormat(event, new TalkFormatId(firestoreTalk.format.id));
3445
const track = findTrack(event, new TrackId(firestoreTalk.track.id));

mobile/src/state/useConferenceDescriptor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ export function useOfflineEventPreparation(
121121
// stopping watcher as soon as possible
122122
watchCleaner();
123123

124-
await checkCache(`useOfflineEventPreparation(eventId=${confDescriptor.id.value})`, Temporal.Duration.from({ hours: 6 }), async () => {
124+
await checkCache(`useOfflineEventPreparation(eventId=${confDescriptor.id.value})`, Temporal.Duration.from({ hours: 1 }), async () => {
125125
return new Promise(async schedulePreparationResolved => {
126126
const otherDayIds = availableDays.filter(availableDay => !availableDay.id.isSameThan(currentSchedule.day)).map(d => d.id);
127127
LOGGER.info(() => `Preparing schedule data for other days than currently selected one (${otherDayIds.map(id => id.value).join(", ")})`)

mobile/src/state/useEventTalk.ts

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {
2-
createVoxxrinDetailedTalkFromFirestore,
3-
TalkId,
2+
createVoxxrinDetailedTalkFromFirestore,
3+
TalkId, VoxxrinTalk,
44
} from "@/models/VoxxrinTalk";
55
import {computed, Ref, unref, watch} from "vue";
66
import {DetailedTalk} from "../../../shared/daily-schedule.firestore";
@@ -12,10 +12,10 @@ import {
1212
} from "@/views/vue-utils";
1313
import {createSharedComposable} from "@vueuse/core";
1414
import {EventId} from "@/models/VoxxrinEvent";
15-
import {PERF_LOGGER} from "@/services/Logger";
15+
import {Logger, PERF_LOGGER} from "@/services/Logger";
1616
import {DayId} from "@/models/VoxxrinDay";
1717
import {Temporal} from "temporal-polyfill";
18-
import {checkCache} from "@/services/Cachings";
18+
import {checkCache, preloadPicture} from "@/services/Cachings";
1919
import {CompletablePromiseQueue} from "@/models/utils";
2020

2121

@@ -57,25 +57,49 @@ export function useEventTalk(
5757
};
5858
}
5959

60-
export async function prepareEventTalks(
60+
export async function prepareEventTalk(
6161
conferenceDescriptor: VoxxrinConferenceDescriptor,
6262
dayId: DayId,
63-
talkIds: Array<TalkId>,
64-
promisesQueue: CompletablePromiseQueue
63+
talk: VoxxrinTalk,
64+
promisesQueue: CompletablePromiseQueue,
65+
queuePriority: number
6566
) {
66-
return checkCache(`eventTalksPreparation(eventId=${conferenceDescriptor.id.value}, dayId=${dayId.value})`, Temporal.Duration.from({ hours: 6 }), async () => {
67-
PERF_LOGGER.debug(`prepareEventTalks(eventId=${conferenceDescriptor.id.value}, talkIds=${JSON.stringify(talkIds.map(id => id.value))})`)
68-
69-
promisesQueue.addAll(talkIds.map(talkId => {
70-
return async () => {
71-
const talkDetailsRef = getTalkDetailsRef(conferenceDescriptor.id, talkId);
72-
if(talkDetailsRef) {
73-
await getDoc(talkDetailsRef);
74-
PERF_LOGGER.debug(`getDoc(${talkDetailsRef.path})`)
75-
}
67+
68+
const talkDetailsRef = getTalkDetailsRef(conferenceDescriptor.id, talk.id);
69+
if(talkDetailsRef) {
70+
await getDoc(talkDetailsRef);
71+
PERF_LOGGER.debug(`getDoc(${talkDetailsRef.path})`)
72+
}
73+
74+
await checkCache(`eventTalkPreparation(eventId=${conferenceDescriptor.id.value}, dayId=${dayId.value}, talkId=${talk.id.value})(speaker pictures)`,
75+
Temporal.Duration.from({ hours: 24 }), // No need to have frequent refreshes for speaker urls...
76+
async () => {
77+
PERF_LOGGER.debug(`eventTalkPreparation(eventId=${conferenceDescriptor.id.value}, dayId=${dayId.value}, talkId=${talk.id.value})(speaker pictures)`)
78+
79+
promisesQueue.addAll(talk.speakers.map(speaker => () => {
80+
if(speaker.photoUrl) {
81+
return loadSpeakerUrl(talk, speaker.photoUrl);
7682
}
77-
}), { priority: 100 })
83+
}), {priority: queuePriority });
7884
});
7985
}
8086

87+
const IN_MEMORY_SPEAKER_URL_PRELOADINGS = new Set<string>();
88+
async function loadSpeakerUrl(talk: VoxxrinTalk, speakerUrl: string) {
89+
const LOGGER = Logger.named(`loadTalkSpeakerUrl(${talk.id.value}): ${speakerUrl}`);
90+
91+
return new Promise(async resolve => {
92+
if(IN_MEMORY_SPEAKER_URL_PRELOADINGS.has(speakerUrl)) {
93+
LOGGER.debug(`Speaker url already preloaded, skipping: ${speakerUrl}`)
94+
resolve(null);
95+
} else {
96+
IN_MEMORY_SPEAKER_URL_PRELOADINGS.add(speakerUrl);
97+
98+
// TODO: handle picture loading error here maybe ??
99+
await preloadPicture(speakerUrl)
100+
resolve(null);
101+
}
102+
})
103+
}
104+
81105
export const useSharedEventTalk = createSharedComposable(useEventTalk);

mobile/src/state/useEventTalkStats.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {EventId} from "@/models/VoxxrinEvent";
22
import {DayId} from "@/models/VoxxrinDay";
3-
import {TalkId} from "@/models/VoxxrinTalk";
3+
import {TalkId, VoxxrinTalk} from "@/models/VoxxrinTalk";
44
import {computed, Ref, toValue, unref, watch, watchEffect} from "vue";
55
import {
66
deferredVuefireUseCollection,
@@ -152,15 +152,15 @@ export function useEventTalkStats(eventIdRef: Ref<EventId|undefined>, talkIdsRef
152152
export async function prepareTalkStats(
153153
eventId: EventId,
154154
dayId: DayId,
155-
talkIds: Array<TalkId>,
155+
talks: Array<VoxxrinTalk>,
156156
promisesQueue: CompletablePromiseQueue
157157
) {
158158
return checkCache(`talkStatsPreparation(eventId=${eventId.value}, dayId=${dayId.value})`, Temporal.Duration.from({ hours: 2 }), async () => {
159-
PERF_LOGGER.debug(`prepareTalkStats(eventId=${eventId.value}, talkIds=${JSON.stringify(talkIds.map(talkId => talkId.value))})`)
159+
PERF_LOGGER.debug(`prepareTalkStats(eventId=${eventId.value}, talkIds=${JSON.stringify(talks.map(talk => talk.id.value))})`)
160160

161-
promisesQueue.addAll(talkIds.map(talkId => {
161+
promisesQueue.addAll(talks.map(talk => {
162162
return async () => {
163-
const talksStatsRef = getTalksStatsRef(eventId, talkId);
163+
const talksStatsRef = getTalksStatsRef(eventId, talk.id);
164164
if(talksStatsRef) {
165165
await getDoc(talksStatsRef)
166166
PERF_LOGGER.debug(`getDoc(${talksStatsRef.path})`)

mobile/src/state/useSchedule.ts

Lines changed: 61 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,22 @@ import {DayId} from "@/models/VoxxrinDay";
1111
import {VoxxrinConferenceDescriptor} from "@/models/VoxxrinConferenceDescriptor";
1212
import {DocumentReference, doc, collection, getDoc} from "firebase/firestore";
1313
import {db} from "@/state/firebase";
14-
import {prepareEventTalks} from "@/state/useEventTalk";
14+
import {prepareEventTalk} from "@/state/useEventTalk";
1515
import {prepareTalkStats} from "@/state/useEventTalkStats";
1616
import {prepareUserTalkNotes} from "@/state/useUserTalkNotes";
17-
import {TalkId, VoxxrinTalk} from "@/models/VoxxrinTalk";
17+
import {
18+
createVoxxrinTalkFromFirestore,
19+
removeTalkOverflowsAndDuplicates,
20+
VoxxrinTalk
21+
} from "@/models/VoxxrinTalk";
1822
import {VoxxrinTimeslotFeedback} from "@/models/VoxxrinFeedback";
1923
import {UserDailyFeedbacks} from "../../../shared/feedbacks.firestore";
20-
import {Logger, PERF_LOGGER} from "@/services/Logger";
24+
import {PERF_LOGGER} from "@/services/Logger";
2125
import { User } from 'firebase/auth';
2226
import {CompletablePromiseQueue} from "@/models/utils";
23-
import {match} from "ts-pattern";
24-
import {preloadPicture} from "@/services/Cachings";
27+
import {match, P} from "ts-pattern";
28+
import {checkCache} from "@/services/Cachings";
29+
import {Temporal} from "temporal-polyfill";
2530

2631
export function useSchedule(
2732
conferenceDescriptorRef: Ref<VoxxrinConferenceDescriptor | undefined>,
@@ -61,33 +66,12 @@ export function dailyScheduleDocument(eventDescriptor: VoxxrinConferenceDescript
6166
return doc(collection(doc(collection(db, 'events'), eventDescriptor.id.value), 'days'), dayId.value) as DocumentReference<DailySchedule>;
6267
}
6368

64-
const IN_MEMORY_SPEAKER_URL_PRELOADINGS = new Set<string>();
65-
async function loadTalkSpeakerUrls(
66-
talk: { speakers: Array<{ photoUrl?: VoxxrinTalk['speakers'][number]['photoUrl'] }> },
67-
promisesQueue: CompletablePromiseQueue
68-
) {
69-
const LOGGER = Logger.named("loadTalkSpeakerUrls");
70-
71-
promisesQueue.addAll(talk.speakers.map(speaker => {
72-
return async () => new Promise(async resolve => {
73-
if (speaker.photoUrl) {
74-
if(IN_MEMORY_SPEAKER_URL_PRELOADINGS.has(speaker.photoUrl)) {
75-
LOGGER.debug(`Speaker url already preloaded, skipping: ${speaker.photoUrl}`)
76-
resolve(null);
77-
} else {
78-
IN_MEMORY_SPEAKER_URL_PRELOADINGS.add(speaker.photoUrl);
79-
80-
// TODO: handle picture loading error here maybe ??
81-
await preloadPicture(speaker.photoUrl)
82-
resolve(null);
83-
}
84-
} else {
85-
resolve(null);
86-
}
87-
});
88-
}), { priority: 1 }); // Low priority because if we're not getting speaker image, that's not dramatic
89-
}
69+
export async function prepareDailySchedule(conferenceDescriptor: VoxxrinConferenceDescriptor, day: DayId, dailyTalks: VoxxrinTalk[], promisesQueue: CompletablePromiseQueue, queuePriority: number) {
70+
// Removing talk duplicates (for instance, overflows)
71+
const dedupedDailyTalks = removeTalkOverflowsAndDuplicates(dailyTalks);
9072

73+
promisesQueue.addAll(dedupedDailyTalks.map(talk => () => prepareEventTalk(conferenceDescriptor, day, talk, promisesQueue, queuePriority)))
74+
}
9175

9276
export async function
9377
prepareSchedules(
@@ -98,55 +82,52 @@ prepareSchedules(
9882
otherDayIds: Array<DayId>,
9983
promisesQueue: CompletablePromiseQueue
10084
) {
101-
PERF_LOGGER.debug(() => `prepareSchedules(userId=${user.uid}, eventId=${conferenceDescriptor.id.value}, currentDayId=${currentDayId.value}, currentTalkIds=${JSON.stringify(currentTalks.map(talk => talk.id.value))}, otherDayIds=${JSON.stringify(otherDayIds.map(id => id.value))})`);
102-
103-
promisesQueue.addAll([currentDayId, ...otherDayIds].map((dayId) => {
104-
return async () => {
105-
const talkIds: TalkId[]|undefined = await match(dayId === currentDayId)
106-
.with(true, async () => {
107-
currentTalks.forEach(talk => {
108-
loadTalkSpeakerUrls(talk, promisesQueue);
109-
})
110-
111-
return currentTalks.map(talk => talk.id);
112-
}).otherwise(async () => {
113-
const dailyScheduleDoc = dailyScheduleDocument(conferenceDescriptor, dayId)
114-
115-
if(navigator.onLine && dailyScheduleDoc) {
116-
const dailyScheduleSnapshot = await getDoc(dailyScheduleDoc);
117-
PERF_LOGGER.debug(`getDoc(${dailyScheduleDoc.path})`)
118-
119-
return dailyScheduleSnapshot.data()?.timeSlots.reduce((talkIds, timeslot) => {
120-
if (timeslot.type === 'talks') {
121-
timeslot.talks.forEach(talk => {
122-
loadTalkSpeakerUrls(talk, promisesQueue);
123-
124-
talkIds.push(new TalkId(talk.id))
125-
})
126-
}
127-
128-
return talkIds;
129-
}, [] as Array<TalkId>) || [];
130-
} else {
131-
return undefined;
132-
}
133-
})
134-
135-
if(navigator.onLine && talkIds) {
136-
// Removing talk ids duplicates (for instance, on overflows)
137-
const uniqueTalkIds = Array.from(
138-
new Set(talkIds.map(t => t.value))
139-
).map(rawTalkId => new TalkId(rawTalkId));
140-
141-
if(dayId !== currentDayId) {
142-
promisesQueue.add(() => prepareTalkStats(conferenceDescriptor.id, dayId, uniqueTalkIds, promisesQueue), { priority: 100 })
143-
promisesQueue.add(() => prepareUserTalkNotes(user, conferenceDescriptor.id, dayId, uniqueTalkIds, promisesQueue), { priority: 100 });
144-
}
145-
146-
promisesQueue.add(() => prepareEventTalks(conferenceDescriptor, dayId, uniqueTalkIds, promisesQueue), { priority: 100 });
147-
}
148-
}
149-
}), { priority: 1000 });
85+
promisesQueue.add(() =>
86+
checkCache(`offlineSchedulePrep(eventId=${conferenceDescriptor.id.value})(currentDay (${currentDayId.value}))`,
87+
Temporal.Duration.from({ hours: 6 }), // Cache should be lower for current day than for other days
88+
async () => {
89+
PERF_LOGGER.debug(() => `offlineSchedulePrep(eventId=${conferenceDescriptor.id.value})(currentDay (${currentDayId.value}))`)
90+
await prepareDailySchedule(conferenceDescriptor, currentDayId, currentTalks, promisesQueue, 1000);
91+
}),
92+
{priority: 1000}
93+
);
94+
95+
promisesQueue.addAll(otherDayIds.map(otherDayId => () =>
96+
checkCache(`offlineSchedulePrep(eventId=${conferenceDescriptor.id.value})(otherDay=${currentDayId.value})`,
97+
Temporal.Duration.from({ hours: 24 }), // Cache should be higher for other days than for current day
98+
async () => {
99+
PERF_LOGGER.debug(() => `offlineSchedulePrep(eventId=${conferenceDescriptor.id.value})(otherDay=${currentDayId.value})`)
100+
101+
const maybeDailyScheduleDoc = dailyScheduleDocument(conferenceDescriptor, otherDayId)
102+
103+
const otherDayTalks = await match([navigator.onLine, maybeDailyScheduleDoc])
104+
.with([true, P.nonNullable], async ([_, dailyScheduleDoc ]) => {
105+
const dailyScheduleSnapshot = await getDoc(dailyScheduleDoc);
106+
PERF_LOGGER.debug(`getDoc(${dailyScheduleDoc.path})`)
107+
108+
return dailyScheduleSnapshot.data()?.timeSlots.reduce((talks, timeslot) => {
109+
if (timeslot.type === 'talks') {
110+
timeslot.talks.forEach(talk => {
111+
const voxxrinTalk = createVoxxrinTalkFromFirestore(conferenceDescriptor, talk)
112+
talks.push(voxxrinTalk);
113+
})
114+
}
115+
116+
return talks;
117+
}, [] as Array<VoxxrinTalk>) || [];
118+
}).otherwise(async () => [] as VoxxrinTalk[]);
119+
120+
const dedupedOtherDayTalks = removeTalkOverflowsAndDuplicates(otherDayTalks)
121+
122+
// For other days, we also need to prepare talks stats & notes given that it won't have been de-facto
123+
// pre-loaded by user navigation
124+
promisesQueue.add(() => prepareTalkStats(conferenceDescriptor.id, otherDayId, dedupedOtherDayTalks, promisesQueue), { priority: 100 })
125+
promisesQueue.add(() => prepareUserTalkNotes(user, conferenceDescriptor.id, otherDayId, dedupedOtherDayTalks, promisesQueue), { priority: 100 });
126+
127+
await prepareDailySchedule(conferenceDescriptor, otherDayId, otherDayTalks, promisesQueue, 100);
128+
})),
129+
{priority: 100}
130+
);
150131
}
151132

152133
export type LabelledTimeslotWithFeedback = VoxxrinScheduleTimeSlot & {

mobile/src/state/useUserTalkNotes.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {EventId} from "@/models/VoxxrinEvent";
22
import {DayId} from "@/models/VoxxrinDay";
3-
import {TalkId} from "@/models/VoxxrinTalk";
3+
import {TalkId, VoxxrinTalk} from "@/models/VoxxrinTalk";
44
import {Ref, toValue, unref} from "vue";
55
import {useCurrentUser} from "@/state/useCurrentUser";
66
import {
@@ -281,14 +281,14 @@ export async function prepareUserTalkNotes(
281281
user: User,
282282
eventId: EventId,
283283
dayId: DayId,
284-
talkIds: Array<TalkId>,
284+
talks: Array<VoxxrinTalk>,
285285
promisesQueue: CompletablePromiseQueue
286286
) {
287287
return checkCache(`prepareUserTalkNotes(eventId=${eventId.value}, dayId=${dayId.value})`, Temporal.Duration.from({ hours: 24 }), async () => {
288-
PERF_LOGGER.debug(`prepareUserTalkNotes(user=${user.uid}, eventId=${eventId.value}, talkIds=${JSON.stringify(talkIds.map(talkId => talkId.value))})`)
289-
promisesQueue.addAll(talkIds.map(talkId => {
288+
PERF_LOGGER.debug(`prepareUserTalkNotes(user=${user.uid}, eventId=${eventId.value}, talkIds=${JSON.stringify(talks.map(talk => talk.id.value))})`)
289+
promisesQueue.addAll(talks.map(talk => {
290290
return async () => {
291-
const talkNotesRef = getTalkNotesRef(user, eventId, talkId);
291+
const talkNotesRef = getTalkNotesRef(user, eventId, talk.id);
292292
if(talkNotesRef) {
293293
await getDoc(talkNotesRef)
294294
PERF_LOGGER.debug(`getDoc(${talkNotesRef.path})`)

0 commit comments

Comments
 (0)