From f9d48271029142695d29e065c3449c57ea7c7ded Mon Sep 17 00:00:00 2001 From: t895 Date: Sun, 10 Dec 2023 20:54:00 -0500 Subject: [PATCH] android: Fix games list loading thread safety Previously we relied on a stateflow for reloading state. Now we use an atomic boolean. --- .../org/yuzu/yuzu_emu/model/GamesViewModel.kt | 74 ++++++++++--------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt index eaec09b246..d19f20dc29 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt @@ -20,8 +20,8 @@ import kotlinx.serialization.json.Json import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.utils.GameHelper -import org.yuzu.yuzu_emu.utils.GameMetadata import org.yuzu.yuzu_emu.utils.NativeConfig +import java.util.concurrent.atomic.AtomicBoolean class GamesViewModel : ViewModel() { val games: StateFlow> get() = _games @@ -33,6 +33,8 @@ class GamesViewModel : ViewModel() { val isReloading: StateFlow get() = _isReloading private val _isReloading = MutableStateFlow(false) + private val reloading = AtomicBoolean(false) + val shouldSwapData: StateFlow get() = _shouldSwapData private val _shouldSwapData = MutableStateFlow(false) @@ -49,38 +51,8 @@ class GamesViewModel : ViewModel() { // Ensure keys are loaded so that ROM metadata can be decrypted. NativeLibrary.reloadKeys() - // Retrieve list of cached games - val storedGames = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) - .getStringSet(GameHelper.KEY_GAMES, emptySet()) - - viewModelScope.launch { - withContext(Dispatchers.IO) { - getGameDirs() - if (storedGames!!.isNotEmpty()) { - val deserializedGames = mutableSetOf() - storedGames.forEach { - val game: Game - try { - game = Json.decodeFromString(it) - } catch (e: Exception) { - // We don't care about any errors related to parsing the game cache - return@forEach - } - - val gameExists = - DocumentFile.fromSingleUri( - YuzuApplication.appContext, - Uri.parse(game.path) - )?.exists() - if (gameExists == true) { - deserializedGames.add(game) - } - } - setGames(deserializedGames.toList()) - } - reloadGames(false) - } - } + getGameDirs() + reloadGames(directoriesChanged = false, firstStartup = true) } fun setGames(games: List) { @@ -110,16 +82,46 @@ class GamesViewModel : ViewModel() { _searchFocused.value = searchFocused } - fun reloadGames(directoriesChanged: Boolean) { - if (isReloading.value) { + fun reloadGames(directoriesChanged: Boolean, firstStartup: Boolean = false) { + if (reloading.get()) { return } + reloading.set(true) _isReloading.value = true viewModelScope.launch { withContext(Dispatchers.IO) { - GameMetadata.resetMetadata() + if (firstStartup) { + // Retrieve list of cached games + val storedGames = + PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) + .getStringSet(GameHelper.KEY_GAMES, emptySet()) + if (storedGames!!.isNotEmpty()) { + val deserializedGames = mutableSetOf() + storedGames.forEach { + val game: Game + try { + game = Json.decodeFromString(it) + } catch (e: Exception) { + // We don't care about any errors related to parsing the game cache + return@forEach + } + + val gameExists = + DocumentFile.fromSingleUri( + YuzuApplication.appContext, + Uri.parse(game.path) + )?.exists() + if (gameExists == true) { + deserializedGames.add(game) + } + } + setGames(deserializedGames.toList()) + } + } + setGames(GameHelper.getGames()) + reloading.set(false) _isReloading.value = false if (directoriesChanged) {