Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 52 additions & 8 deletions src/viewer/PlayService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
combineLatest as observableCombineLatest,
empty as observableEmpty,
from as observableFrom,
merge as observableMerge,
of as observableOf,
zip as observableZip,
Observable,
Expand Down Expand Up @@ -160,9 +161,8 @@ export class PlayService {
return observableOf<[Sequence, NavigationDirection]>([undefined, direction]);
}

const sequence$: Observable<Sequence> = (mode === GraphMode.Sequence ?
this._graphService.cacheSequenceImages$(sequenceId, imageId) :
this._graphService.cacheSequence$(sequenceId)).pipe(
const sequence$: Observable<Sequence> = this._graphService
.cacheSequence$(sequenceId).pipe(
retry(3),
catchError(
(error: Error): Observable<Sequence> => {
Expand All @@ -171,8 +171,29 @@ export class PlayService {
return observableOf(undefined);
}));

// Caching all images of the sequence is a batching
// optimization for the sequence graph mode. It must
// run alongside image caching instead of gating it:
// it only completes once every batch has been
// retrieved, which for long sequences takes long
// enough to starve playback of images while running.
const sequenceImages$: Observable<Sequence> =
mode === GraphMode.Sequence ?
this._graphService
.cacheSequenceImages$(sequenceId, imageId)
.pipe(
retry(3),
catchError(
(error: Error): Observable<Sequence> => {
console.error(error);

return observableEmpty();
})) :
observableEmpty();

return observableCombineLatest(
sequence$,
observableMerge(sequence$, sequenceImages$).pipe(
distinctUntilChanged()),
observableOf(direction));
}),
switchMap(
Expand All @@ -186,6 +207,21 @@ export class PlayService {
imageIds.reverse();
}

// Positions are resolved on every animation frame,
// linear lookups would scale with the sequence length.
const imageIndices: Map<string, number> =
new Map<string, number>();
for (let i: number = 0; i < imageIds.length; i++) {
imageIndices.set(imageIds[i], i);
}

const indexOf: (id: string) => number =
(id: string): number => {
const index: number = imageIndices.get(id);

return index === undefined ? -1 : index;
};

return this._stateService.currentState$.pipe(
map(
(frame: AnimationFrame): [string, number] => {
Expand All @@ -197,17 +233,25 @@ export class PlayService {
[lastTrajectoryKey, imagesAhead]: [string, number]):
[string, string[]] => {

if (lastRequestKey === undefined) {
lastRequestKey = lastTrajectoryKey;
// The trajectory reaches beyond the
// sequence when traversing into another
// one, there is nothing to request here.
const current: number = indexOf(lastTrajectoryKey);
if (current === -1) {
return [lastRequestKey, []];
}

const lastIndex: number = imageIds.length - 1;
if (imagesAhead >= this._imagesAhead || imageIds[lastIndex] === lastRequestKey) {
return [lastRequestKey, []];
}

const current: number = imageIds.indexOf(lastTrajectoryKey);
const start: number = imageIds.indexOf(lastRequestKey) + 1;
const lastRequestIndex: number =
lastRequestKey === undefined ?
-1 : indexOf(lastRequestKey);
const start: number =
(lastRequestIndex === -1 ?
current : lastRequestIndex) + 1;
const end: number = Math.min(lastIndex, current + this._imagesAhead - imagesAhead) + 1;

if (end <= start) {
Expand Down
Loading
Loading