mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-04 07:17:50 +07:00
Improved MultiplayerScreen performance (#5527)
* Added MultiplayerGameInfo * Added usage of MultiplayerGameInfo * Integrate MultiplayerGamInfo into GameInfoPreview * Replaced MultiplayerGameInfo with GameInfoPreview * Correction in function docs * PR cleanup * Added currentTurnStartTime from merge * Fixed resign not propagating to preview
This commit is contained in:
@ -121,6 +121,7 @@ class GameInfo {
|
||||
* @throws NoSuchElementException if no civ of than name is in the game (alive or dead)! */
|
||||
fun getCivilization(civName: String) = civilizations.first { it.civName == civName }
|
||||
fun getCurrentPlayerCivilization() = currentPlayerCiv
|
||||
fun getCivilizationsAsPreviews() = civilizations.map { it.asPreview() }.toMutableList()
|
||||
/** Get barbarian civ
|
||||
* @throws NoSuchElementException in no-barbarians games! */
|
||||
fun getBarbarianCivilization() = getCivilization(Constants.barbarians)
|
||||
@ -367,15 +368,51 @@ class GameInfo {
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
fun asPreview() = GameInfoPreview(this)
|
||||
}
|
||||
|
||||
// reduced variant only for load preview
|
||||
class GameInfoPreview {
|
||||
/**
|
||||
* Reduced variant of GameInfo used for load preview and multiplayer saves.
|
||||
* Contains additional data for multiplayer settings.
|
||||
*/
|
||||
class GameInfoPreview() {
|
||||
var civilizations = mutableListOf<CivilizationInfoPreview>()
|
||||
var difficulty = "Chieftain"
|
||||
var gameParameters = GameParameters()
|
||||
var turns = 0
|
||||
var gameId = ""
|
||||
var currentPlayer = ""
|
||||
var currentTurnStartTime = 0L
|
||||
var turnNotification = true //used as setting in the MultiplayerScreen
|
||||
|
||||
/**
|
||||
* Converts a GameInfo object (can be uninitialized) into a GameInfoPreview object.
|
||||
* Sets all multiplayer settings to default.
|
||||
*/
|
||||
constructor(gameInfo: GameInfo) : this() {
|
||||
civilizations = gameInfo.getCivilizationsAsPreviews()
|
||||
difficulty = gameInfo.difficulty
|
||||
gameParameters = gameInfo.gameParameters
|
||||
turns = gameInfo.turns
|
||||
gameId = gameInfo.gameId
|
||||
currentPlayer = gameInfo.currentPlayer
|
||||
currentTurnStartTime = gameInfo.currentTurnStartTime
|
||||
}
|
||||
|
||||
fun getCivilization(civName: String) = civilizations.first { it.civName == civName }
|
||||
|
||||
/**
|
||||
* Updates the current player and turn information in the GameInfoPreview object with the help of a
|
||||
* GameInfo object (can be uninitialized).
|
||||
*/
|
||||
fun updateCurrentTurn(gameInfo: GameInfo) : GameInfoPreview {
|
||||
currentPlayer = gameInfo.currentPlayer
|
||||
turns = gameInfo.turns
|
||||
currentTurnStartTime = gameInfo.currentTurnStartTime
|
||||
//We update the civilizations in case someone is removed from the game (resign/kick)
|
||||
civilizations = gameInfo.getCivilizationsAsPreviews()
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
@ -38,9 +38,21 @@ object GameSaver {
|
||||
return localSaves + Gdx.files.absolute(externalFilesDirForAndroid + "/${getSubfolder(multiplayer)}").list().asSequence()
|
||||
}
|
||||
|
||||
fun saveGame(game: GameInfo, GameName: String, multiplayer: Boolean = false, saveCompletionCallback: ((Exception?) -> Unit)? = null) {
|
||||
fun saveGame(game: GameInfo, GameName: String, saveCompletionCallback: ((Exception?) -> Unit)? = null) {
|
||||
try {
|
||||
json().toJson(game, getSave(GameName, multiplayer))
|
||||
json().toJson(game, getSave(GameName))
|
||||
saveCompletionCallback?.invoke(null)
|
||||
} catch (ex: Exception) {
|
||||
saveCompletionCallback?.invoke(ex)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload of function saveGame to save a GameInfoPreview in the MultiplayerGames folder
|
||||
*/
|
||||
fun saveGame(game: GameInfoPreview, GameName: String, saveCompletionCallback: ((Exception?) -> Unit)? = null) {
|
||||
try {
|
||||
json().toJson(game, getSave(GameName, true))
|
||||
saveCompletionCallback?.invoke(null)
|
||||
} catch (ex: Exception) {
|
||||
saveCompletionCallback?.invoke(ex)
|
||||
|
@ -1212,14 +1212,27 @@ class CivilizationInfo {
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
fun asPreview() = CivilizationInfoPreview(this)
|
||||
}
|
||||
|
||||
// reduced variant only for load preview
|
||||
class CivilizationInfoPreview {
|
||||
/**
|
||||
* Reduced variant of CivilizationInfo used for load preview.
|
||||
*/
|
||||
class CivilizationInfoPreview() {
|
||||
var civName = ""
|
||||
var playerType = PlayerType.AI
|
||||
var playerId = ""
|
||||
fun isPlayerCivilization() = playerType == PlayerType.Human
|
||||
|
||||
/**
|
||||
* Converts a CivilizationInfo object (can be uninitialized) into a CivilizationInfoPreview object.
|
||||
*/
|
||||
constructor(civilizationInfo: CivilizationInfo) : this() {
|
||||
civName = civilizationInfo.civName
|
||||
playerType = civilizationInfo.playerType
|
||||
playerId = civilizationInfo.playerId
|
||||
}
|
||||
}
|
||||
|
||||
enum class CivFlags {
|
||||
|
@ -3,7 +3,7 @@ package com.unciv.ui
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.TextField
|
||||
import com.unciv.logic.GameInfo
|
||||
import com.unciv.logic.GameInfoPreview
|
||||
import com.unciv.logic.GameSaver
|
||||
import com.unciv.logic.civilization.PlayerType
|
||||
import com.unciv.models.translations.tr
|
||||
@ -14,7 +14,7 @@ import kotlin.concurrent.thread
|
||||
|
||||
/** Subscreen of MultiplayerScreen to edit and delete saves
|
||||
* backScreen is used for getting back to the MultiplayerScreen so it doesn't have to be created over and over again */
|
||||
class EditMultiplayerGameInfoScreen(game: GameInfo?, gameName: String, backScreen: MultiplayerScreen): PickerScreen(){
|
||||
class EditMultiplayerGameInfoScreen(val gameInfo: GameInfoPreview?, gameName: String, backScreen: MultiplayerScreen): PickerScreen(){
|
||||
init {
|
||||
val textField = TextField(gameName, skin)
|
||||
|
||||
@ -24,7 +24,7 @@ class EditMultiplayerGameInfoScreen(game: GameInfo?, gameName: String, backScree
|
||||
val deleteButton = "Delete save".toTextButton()
|
||||
deleteButton.onClick {
|
||||
val askPopup = YesNoPopup("Are you sure you want to delete this map?", {
|
||||
backScreen.removeMultiplayerGame(game, gameName)
|
||||
backScreen.removeMultiplayerGame(gameInfo, gameName)
|
||||
backScreen.game.setScreen(backScreen)
|
||||
backScreen.reloadGameListUI()
|
||||
}, this)
|
||||
@ -34,7 +34,7 @@ class EditMultiplayerGameInfoScreen(game: GameInfo?, gameName: String, backScree
|
||||
val giveUpButton = "Resign".toTextButton()
|
||||
giveUpButton.onClick {
|
||||
val askPopup = YesNoPopup("Are you sure you want to resign?", {
|
||||
resign(game!!.gameId, gameName, backScreen)
|
||||
resign(gameInfo!!.gameId, gameName, backScreen)
|
||||
}, this)
|
||||
askPopup.open()
|
||||
}
|
||||
@ -55,14 +55,14 @@ class EditMultiplayerGameInfoScreen(game: GameInfo?, gameName: String, backScree
|
||||
rightSideButton.onClick {
|
||||
rightSideButton.setText("Saving...".tr())
|
||||
//remove the old game file
|
||||
backScreen.removeMultiplayerGame(game, gameName)
|
||||
backScreen.removeMultiplayerGame(gameInfo, gameName)
|
||||
//using addMultiplayerGame will download the game from Dropbox so the descriptionLabel displays the right things
|
||||
backScreen.addMultiplayerGame(game!!.gameId, textField.text)
|
||||
backScreen.addMultiplayerGame(gameInfo!!.gameId, textField.text)
|
||||
backScreen.game.setScreen(backScreen)
|
||||
backScreen.reloadGameListUI()
|
||||
}
|
||||
|
||||
if (game == null){
|
||||
if (gameInfo == null){
|
||||
textField.isDisabled = true
|
||||
textField.color = Color.GRAY
|
||||
rightSideButton.disable()
|
||||
@ -102,8 +102,9 @@ class EditMultiplayerGameInfoScreen(game: GameInfo?, gameName: String, backScree
|
||||
civ.addNotification("[${playerCiv.civName}] resigned and is now controlled by AI", playerCiv.civName)
|
||||
}
|
||||
|
||||
//save game so multiplayer list stays up to date
|
||||
GameSaver.saveGame(gameInfo, gameName, true)
|
||||
//save game so multiplayer list stays up to date but do not override multiplayer settings
|
||||
val updatedSave = this.gameInfo!!.updateCurrentTurn(gameInfo)
|
||||
GameSaver.saveGame(updatedSave, gameName)
|
||||
OnlineMultiplayer().tryUploadGame(gameInfo)
|
||||
Gdx.app.postRunnable {
|
||||
popup.close()
|
||||
|
@ -3,10 +3,7 @@ package com.unciv.ui
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.files.FileHandle
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.*
|
||||
import com.unciv.logic.GameInfo
|
||||
import com.unciv.logic.GameSaver
|
||||
import com.unciv.logic.IdChecker
|
||||
import com.unciv.logic.UncivShowableException
|
||||
import com.unciv.logic.*
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.pickerscreens.PickerScreen
|
||||
import com.unciv.ui.utils.*
|
||||
@ -21,7 +18,7 @@ class MultiplayerScreen(previousScreen: CameraStageBaseScreen) : PickerScreen()
|
||||
private lateinit var selectedGameFile: FileHandle
|
||||
|
||||
// Concurrent because we can get concurrent modification errors if we change things around while running redownloadAllGames() in another thread
|
||||
private var multiplayerGames = ConcurrentHashMap<FileHandle, GameInfo>()
|
||||
private var multiplayerGames = ConcurrentHashMap<FileHandle, GameInfoPreview>()
|
||||
private val rightSideTable = Table()
|
||||
private val leftSideTable = Table()
|
||||
|
||||
@ -117,7 +114,7 @@ class MultiplayerScreen(previousScreen: CameraStageBaseScreen) : PickerScreen()
|
||||
//RightSideButton Setup
|
||||
rightSideButton.setText("Join game".tr())
|
||||
rightSideButton.onClick {
|
||||
joinMultiplaerGame()
|
||||
joinMultiplayerGame()
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,11 +144,11 @@ class MultiplayerScreen(previousScreen: CameraStageBaseScreen) : PickerScreen()
|
||||
try {
|
||||
// The tryDownload can take more than 500ms. Therefore, to avoid ANRs,
|
||||
// we need to run it in a different thread.
|
||||
val game = OnlineMultiplayer().tryDownloadGame(gameId.trim())
|
||||
val gamePreview = OnlineMultiplayer().tryDownloadGame(gameId.trim()).asPreview()
|
||||
if (gameName == "")
|
||||
GameSaver.saveGame(game, game.gameId, true)
|
||||
GameSaver.saveGame(gamePreview, gamePreview.gameId)
|
||||
else
|
||||
GameSaver.saveGame(game, gameName, true)
|
||||
GameSaver.saveGame(gamePreview, gameName)
|
||||
|
||||
Gdx.app.postRunnable { reloadGameListUI() }
|
||||
} catch (ex: Exception) {
|
||||
@ -170,12 +167,21 @@ class MultiplayerScreen(previousScreen: CameraStageBaseScreen) : PickerScreen()
|
||||
}
|
||||
}
|
||||
|
||||
//just loads the game from savefile
|
||||
//the game will be downloaded opon joining it anyway
|
||||
private fun joinMultiplaerGame() {
|
||||
//Download game and use the popup to cover ANRs
|
||||
private fun joinMultiplayerGame() {
|
||||
val loadingGamePopup = Popup(this)
|
||||
loadingGamePopup.add("Loading latest game state...".tr())
|
||||
loadingGamePopup.open()
|
||||
|
||||
try {
|
||||
game.loadGame(multiplayerGames[selectedGameFile]!!)
|
||||
// For whatever reason, the only way to show the popup before the ANRs started was to
|
||||
// call the loadGame explicitly with a runnable on the main thread.
|
||||
// Maybe this adds just enough lag for the popup to show up
|
||||
Gdx.app.postRunnable {
|
||||
game.loadGame(OnlineMultiplayer().tryDownloadGame((multiplayerGames[selectedGameFile]!!.gameId)))
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
loadingGamePopup.close()
|
||||
val errorPopup = Popup(this)
|
||||
errorPopup.addGoodSizedLabel("Could not download game!")
|
||||
errorPopup.row()
|
||||
@ -255,7 +261,7 @@ class MultiplayerScreen(previousScreen: CameraStageBaseScreen) : PickerScreen()
|
||||
|
||||
thread(name = "loadGameFile") {
|
||||
try {
|
||||
val game = gameSaver.loadGameFromFile(gameSaveFile)
|
||||
val game = gameSaver.loadGamePreviewFromFile(gameSaveFile)
|
||||
|
||||
//Add games to list so saves don't have to be loaded as Files so often
|
||||
if (!gameIsAlreadySavedAsMultiplayer(game.gameId)) {
|
||||
@ -265,7 +271,7 @@ class MultiplayerScreen(previousScreen: CameraStageBaseScreen) : PickerScreen()
|
||||
Gdx.app.postRunnable {
|
||||
turnIndicator.clear()
|
||||
if (isUsersTurn(game)) {
|
||||
turnIndicator.add(ImageGetter.getNationIndicator(game.currentPlayerCiv.nation, 50f))
|
||||
turnIndicator.add(ImageGetter.getImage("OtherIcons/ExclamationMark")).size(50f)
|
||||
}
|
||||
//set variable so it can be displayed when gameButton.onClick gets called
|
||||
currentTurnUser = game.currentPlayer
|
||||
@ -307,8 +313,8 @@ class MultiplayerScreen(previousScreen: CameraStageBaseScreen) : PickerScreen()
|
||||
thread(name = "multiplayerGameDownload") {
|
||||
for ((fileHandle, gameInfo) in multiplayerGames) {
|
||||
try {
|
||||
val game = OnlineMultiplayer().tryDownloadGame(gameInfo.gameId)
|
||||
GameSaver.saveGame(game, fileHandle.name(), true)
|
||||
val game = gameInfo.updateCurrentTurn(OnlineMultiplayer().tryDownloadGame(gameInfo.gameId))
|
||||
GameSaver.saveGame(game, fileHandle.name())
|
||||
multiplayerGames[fileHandle] = game
|
||||
} catch (ex: Exception) {
|
||||
//skipping one is not fatal
|
||||
@ -342,7 +348,7 @@ class MultiplayerScreen(previousScreen: CameraStageBaseScreen) : PickerScreen()
|
||||
if (gameIsAlreadySavedAsMultiplayer(currentlyRunningGame.gameId))
|
||||
return@onClick
|
||||
try {
|
||||
GameSaver.saveGame(currentlyRunningGame, currentlyRunningGame.gameId, true)
|
||||
GameSaver.saveGame(currentlyRunningGame, currentlyRunningGame.gameId)
|
||||
reloadGameListUI()
|
||||
} catch (ex: Exception) {
|
||||
val errorPopup = Popup(this)
|
||||
@ -366,9 +372,9 @@ class MultiplayerScreen(previousScreen: CameraStageBaseScreen) : PickerScreen()
|
||||
}
|
||||
|
||||
//check if its the users turn
|
||||
private fun isUsersTurn(gameInfo: GameInfo) = gameInfo.currentPlayerCiv.playerId == game.settings.userId
|
||||
private fun isUsersTurn(gameInfo: GameInfoPreview) = gameInfo.getCivilization(gameInfo.currentPlayer).playerId == game.settings.userId
|
||||
|
||||
fun removeMultiplayerGame(gameInfo: GameInfo?, gameName: String) {
|
||||
fun removeMultiplayerGame(gameInfo: GameInfoPreview?, gameName: String) {
|
||||
val games = multiplayerGames.filterValues { it == gameInfo }.keys
|
||||
try {
|
||||
GameSaver.deleteSave(gameName, true)
|
||||
|
@ -207,7 +207,8 @@ class NewGameScreen(
|
||||
GameSaver.autoSave(newGame!!) {}
|
||||
|
||||
// Saved as Multiplayer game to show up in the session browser
|
||||
GameSaver.saveGame(newGame!!, newGame!!.gameId, true)
|
||||
val newGamePreview = newGame!!.asPreview()
|
||||
GameSaver.saveGame(newGamePreview, newGamePreview.gameId)
|
||||
} catch (ex: Exception) {
|
||||
Gdx.app.postRunnable {
|
||||
Popup(this).apply {
|
||||
|
Reference in New Issue
Block a user