Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce time spent on the splash screen #1552

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
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
108 changes: 51 additions & 57 deletions src/components/mixins/SplashMixin.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import Vue from 'vue';
import Component from 'vue-class-component';

import R2Error from '../../model/errors/R2Error';
import RequestItem from '../../model/requests/RequestItem';
import type { PackageListChunks, PackageListIndex } from '../../store/modules/TsModsModule';

Expand All @@ -11,7 +10,6 @@ import type { PackageListChunks, PackageListIndex } from '../../store/modules/Ts
export default class SplashMixin extends Vue {
heroTitle = '';
heroType: string = 'is-info';
isOffline = false;
loadingText = '';
requests: RequestItem[] = [];

Expand Down Expand Up @@ -54,32 +52,49 @@ export default class SplashMixin extends Vue {
await this._getThunderstoreMods();
}

// Get the list of Thunderstore mods from API or local cache.
// Get the list of Thunderstore mods from local cache or API.
async _getThunderstoreMods() {
const packageListIndex = await this.fetchPackageListIndex();
const packageListChunks = await this.fetchPackageListChunksIfUpdated(packageListIndex);
this.getRequestItem('ThunderstoreDownload').setProgress(100);
await this.writeModsToPersistentCacheIfUpdated(packageListIndex, packageListChunks);
const isModListLoaded = await this.readModsToVuex();

// To proceed, the loading of the mod list should result in a non-empty list.
// Empty list is allowed if that's actually what the API returned.
// API wasn't queried at all if we already had the latest index chunk.
const modListHasMods = this.$store.state.tsMods.mods.length;
const apiReturnedEmptyList = packageListChunks && packageListChunks[0].length === 0;
const apiWasNotQueried = packageListIndex && packageListIndex.isLatest;
if (isModListLoaded && (modListHasMods || apiReturnedEmptyList || apiWasNotQueried)) {
await this.moveToNextScreen();
} else {
this.heroTitle = 'Failed to get the list of online mods';
this.loadingText = 'You may still use the manager offline, but some features might be unavailable.';
this.isOffline = true;
const hasPriorCache = await this.doesGameHaveLocalCache();
let hasUpdatedCache = false;

if (!hasPriorCache) {
const packageListIndex = await this.fetchPackageListIndex();
const packageListChunks = await this.fetchPackageListChunksIfUpdated(packageListIndex);
this.getRequestItem('ThunderstoreDownload').setProgress(100);
hasUpdatedCache = await this.writeModsToPersistentCacheIfUpdated(packageListIndex, packageListChunks);
}

if (hasPriorCache || hasUpdatedCache) {
await this.readModsToVuex();
}

await this.moveToNextScreen();
}

/***
* Query IndexedDB for an existing version of package list.
*/
async doesGameHaveLocalCache(): Promise<boolean> {
this.loadingText = 'Checking for mod list in local cache';
let hasCache = false;

try {
hasCache = await this.$store.dispatch('tsMods/gameHasCachedModList');
} catch (e) {
console.error('SplashMixin failed to check mod list in local cache', e);
}

if (hasCache) {
this.getRequestItem('ThunderstoreDownload').setProgress(100);
this.getRequestItem('CacheOperations').setProgress(50);
this.loadingText = 'Processing the mod list from cache';
}

return hasCache;
}

/***
* Query Thunderstore API for the URLs pointing to parts of the package list.
* Fails silently to fallback reading the old values from the IndexedDB cache.
*/
async fetchPackageListIndex(): Promise<PackageListIndex|undefined> {
this.loadingText = 'Checking for mod list updates from Thunderstore';
Expand All @@ -94,12 +109,10 @@ export default class SplashMixin extends Vue {

/***
* Load the package list in chunks pointed out by the packageListIndex.
* This step is skipped if we can't or don't need to load the chunks.
* Fails silently to fallback reading the old values from the IndexedDB cache.
*/
async fetchPackageListChunksIfUpdated(packageListIndex?: PackageListIndex): Promise<PackageListChunks|undefined> {
// Skip loading chunks if loading index failed, or if we already have the latest data.
if (!packageListIndex || packageListIndex.isLatest) {
// Skip loading chunks if loading index failed.
if (!packageListIndex) {
return undefined;
}

Expand All @@ -121,10 +134,12 @@ export default class SplashMixin extends Vue {

/***
* Update a fresh package list to the IndexedDB cache.
* Done only if there was a fresh list to load and it was loaded successfully.
* Fails silently to fallback reading the old values from the IndexedDB cache.
* Done only if a fresh list was loaded successfully from the API.
*/
async writeModsToPersistentCacheIfUpdated(packageListIndex?: PackageListIndex, packageListChunks?: PackageListChunks) {
async writeModsToPersistentCacheIfUpdated(
packageListIndex?: PackageListIndex,
packageListChunks?: PackageListChunks
): Promise<boolean> {
if (packageListIndex && packageListChunks) {
this.loadingText = 'Storing the mod list into local cache';

Expand All @@ -133,52 +148,31 @@ export default class SplashMixin extends Vue {
'tsMods/updatePersistentCache',
{indexHash: packageListIndex.hash, chunks: packageListChunks}
);

this.loadingText = 'Processing the mod list';
this.getRequestItem('CacheOperations').setProgress(50);
return true;
} catch (e) {
console.error('SplashMixin failed to cache mod list locally.', e);
}

this.loadingText = 'Processing the mod list';
} else {
this.loadingText = 'Processing the mod list from cache';
}

this.getRequestItem('CacheOperations').setProgress(50);
return false;
}

/***
* Read mod list from the IndexedDB cache to Vuex so it's kept in memory.
* Always read from the IndexedDB since we don't know if the mod list was
* queried from the API successfully or not. This also handles the type
* casting, since mod manager expects the data to be formatted into objects.
*
* Failure at this point is no longer silently ignored, instead an error
* modal is shown.
*
* Return value is used to tell whether Vuex might contain an empty list
* after calling this because there was an error, or because the package
* list is actually empty.
*/
async readModsToVuex(): Promise<boolean> {
let isModListLoaded = false;

async readModsToVuex(): Promise<void> {
try {
await this.$store.dispatch('tsMods/updateMods');
isModListLoaded = true;
} catch (e) {
this.$store.commit(
'error/handleError',
R2Error.fromThrownValue(e, `${this.loadingText} failed`)
);
console.error('SplashMixin failed to read mods to Vuex.', e);
} finally {
this.getRequestItem('CacheOperations').setProgress(100);
return isModListLoaded;
}
}

async continueOffline() {
await this.moveToNextScreen();
}

async moveToNextScreen() {
this.$router.push({name: 'profiles'});
}
Expand Down
17 changes: 0 additions & 17 deletions src/pages/Splash.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
<p>Game updates may break mods. If a new update has been released, please be patient.</p>
</div>
<progress-bar
v-if="!isOffline"
:max='requests.length * 100'
:value='reduceRequests().getProgress() > 0 ? reduceRequests().getProgress() : undefined'
:className='[reduceRequests().getProgress() > 0 ? "is-info" : ""]' />
Expand All @@ -25,16 +24,6 @@
</aside>
</div>
<div class='column is-three-quarters'>
<div class='container'>
<br />
<nav class='level' v-if='isOffline'>
<div class='level-item'>
<a class='button is-info margin-right margin-right--half-width' @click='continueOffline()'>Continue offline</a>
<a class='button' @click='retryConnection()'>Try to reconnect</a>
</div>
<br /><br />
</nav>
</div>
<div>
<article class='media'>
<div class='media-content'>
Expand Down Expand Up @@ -197,12 +186,6 @@ export default class Splash extends mixins(SplashMixin) {
this.$router.push({name: 'profiles'});
}

retryConnection() {
this.resetRequestProgresses();
this.isOffline = false;
this.checkForUpdates();
}

private async ensureWrapperInGameFolder() {
const wrapperName = process.platform === 'darwin' ? 'macos_proxy' : 'linux_wrapper.sh';
const activeGame: Game = this.$store.state.activeGame;
Expand Down
5 changes: 5 additions & 0 deletions src/store/modules/TsModsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,11 @@ export const TsModsModule = {
return chunks;
},

async gameHasCachedModList({rootState}): Promise<boolean> {
const updated = await PackageDb.getLastPackageListUpdateTime(rootState.activeGame.internalFolderName);
return updated !== undefined;
},

async prewarmCache({getters, rootGetters}) {
const profileMods: ManifestV2[] = rootGetters['profile/modList'];
profileMods.forEach(getters['cachedMod']);
Expand Down
Loading