no more reading entire savefiles onto the memory upon the booting

This commit is contained in:
minjaesong
2021-10-01 10:07:23 +09:00
parent aaa8a80324
commit b720c12c4e
9 changed files with 92 additions and 38 deletions

View File

@@ -16,6 +16,7 @@ import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.JsonValue;
import com.github.strikerx3.jxinput.XInputDevice;
import kotlin.Pair;
import net.torvald.gdx.graphics.PixmapIO2;
import net.torvald.getcpuname.GetCpuName;
import net.torvald.terrarum.concurrent.ThreadExecutor;
@@ -31,6 +32,7 @@ import net.torvald.terrarum.modulebasegame.IngameRenderer;
import net.torvald.terrarum.modulebasegame.TerrarumIngame;
import net.torvald.terrarum.modulebasegame.ui.ItemSlotImageFactory;
import net.torvald.terrarum.serialise.WriteConfig;
import net.torvald.terrarum.serialise.WriteMeta;
import net.torvald.terrarum.tvda.VirtualDisk;
import net.torvald.terrarum.utils.JsonFetcher;
import net.torvald.terrarum.worlddrawer.CreateTileAtlas;
@@ -194,7 +196,7 @@ public class App implements ApplicationListener {
/**
* Sorted by the lastplaytime, in reverse order (index 0 is the most recent game played)
*/
public static ArrayList<VirtualDisk> savegames = new ArrayList<>();
public static ArrayList<Pair<File, WriteMeta.WorldMeta>> savegames = new ArrayList<>();
public static void updateListOfSavegames() {
AppUpdateListOfSavegames();

View File

@@ -17,7 +17,8 @@ object DefaultConfig {
"atlastexsize" to 2048,
"language" to App.getSysLang(),
"notificationshowuptime" to 4000,
"notificationshowuptime" to 4096, // 4s
"autosaveinterval" to 262144, // 4m22s
"multithread" to true,
"showhealthmessageonstartup" to true,

View File

@@ -26,6 +26,8 @@ import net.torvald.terrarum.gameworld.fmod
import net.torvald.terrarum.itemproperties.ItemCodex
import net.torvald.terrarum.itemproperties.MaterialCodex
import net.torvald.terrarum.serialise.Common
import net.torvald.terrarum.serialise.ReadMeta
import net.torvald.terrarum.tvda.DiskSkimmer
import net.torvald.terrarum.tvda.EntryFile
import net.torvald.terrarum.tvda.VDUtil
import net.torvald.terrarum.tvda.VirtualDisk
@@ -685,24 +687,29 @@ fun AppUpdateListOfSavegames() {
App.savegames.clear()
File(App.defaultSaveDir).listFiles().filter { !it.isDirectory && !it.name.contains('.') }.map { file ->
try {
VDUtil.readDiskArchive(file, Level.INFO) {
printdbgerr("Terrarum", "Possibly corrupted savefile '${file.absolutePath}':\n$it")
DiskSkimmer(file, Common.CHARSET) { it.containsKey(-1) }.requestFile(-1)?.let {
file to ReadMeta.fromDiskEntry(it)
}
}
catch (e: Throwable) {
System.err.println("Unable to load a savefile ${file.absolutePath}")
e.printStackTrace()
null
}
}.filter { it != null && it.entries.contains(-1) }
.sortedByDescending { (it as VirtualDisk).entries[-1]!!.modificationDate }.forEach {
}.filter { it != null }.sortedByDescending { it!!.second.lastplay_t }.forEach {
App.savegames.add(it!!)
}
}
fun checkForSavegameDamage(disk: VirtualDisk): Boolean {
/**
* @param skimmer loaded with the savefile
*/
fun checkForSavegameDamage(skimmer: DiskSkimmer): Boolean {
// # check if The Player is there
val player = disk.entries[PLAYER_REF_ID.toLong().and(0xFFFFFFFFL)] ?: return true
// # check if the world The Player is at actually exists
val player = skimmer.requestFile(PLAYER_REF_ID.toLong().and(0xFFFFFFFFL))?.contents ?: return true
// # check if:
// the world The Player is at actually exists
// all the actors for the world actually exists
val currentWorld = (player as EntryFile).bytes.let {
val maxsize = 1 shl 30
val worldIndexRegex = Regex("""worldIndex: ?([0-9]+)""")
@@ -711,6 +718,8 @@ fun checkForSavegameDamage(disk: VirtualDisk): Boolean {
// todo
}
// skimmer.requestFile(367228416) ?: return true
return false
}

View File

@@ -128,7 +128,7 @@ open class GameWorld() : Disposable {
// preliminary spawn points
this.spawnX = width / 2
this.spawnY = 200
this.spawnY = 150
layerTerrain = BlockLayer(width, height)
layerWall = BlockLayer(width, height)

View File

@@ -285,8 +285,14 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) {
/** Load rest of the game with GL context */
private fun postInitForLoadFromSave(codices: Codices) {
codices.actors.forEach {
val actor = ReadActor(LoadSavegame.getFileReader(codices.disk, it.toLong()))
addNewActor(actor)
try {
val actor = ReadActor(LoadSavegame.getFileReader(codices.disk, it.toLong()))
addNewActor(actor)
}
catch (e: NullPointerException) {
System.err.println("Could not read the actor ${it} from the disk")
// throw e // TODO don't rethrow -- let players play the corrupted world if it loads, they'll be able to cope with their losses even though there will be buncha lone actorblocks lying around...
}
}
// by doing this, whatever the "possession" the player had will be broken by the game load

View File

@@ -14,13 +14,14 @@ import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.serialise.Common
import net.torvald.terrarum.serialise.LoadSavegame
import net.torvald.terrarum.serialise.ReadMeta
import net.torvald.terrarum.tvda.ByteArray64InputStream
import net.torvald.terrarum.tvda.VDUtil
import net.torvald.terrarum.tvda.VirtualDisk
import net.torvald.terrarum.serialise.WriteMeta
import net.torvald.terrarum.tvda.*
import net.torvald.terrarum.ui.*
import java.io.File
import java.time.Instant
import java.time.format.DateTimeFormatter
import java.util.*
import java.util.logging.Level
import java.util.zip.GZIPInputStream
import kotlin.math.roundToInt
@@ -74,10 +75,10 @@ class UILoadDemoSavefiles : UICanvas() {
// read savegames
init {
App.savegames.forEachIndexed { index, disk ->
App.savegames.forEachIndexed { index, fileMetaPair ->
val x = uiX
val y = titleTopGradEnd + cellInterval * index
addUIitem(UIItemDemoSaveCells(this, x, y, disk as VirtualDisk))
addUIitem(UIItemDemoSaveCells(this, x, y, fileMetaPair.first, fileMetaPair.second))
}
}
@@ -275,7 +276,9 @@ class UIItemDemoSaveCells(
parent: UILoadDemoSavefiles,
initialX: Int,
initialY: Int,
val disk: VirtualDisk) : UIItem(parent, initialX, initialY) {
val diskfile: File, val meta: WriteMeta.WorldMeta) : UIItem(parent, initialX, initialY) {
private val skimmer = DiskSkimmer(diskfile, Common.CHARSET)
companion object {
const val WIDTH = 480
@@ -289,7 +292,7 @@ class UIItemDemoSaveCells(
private var thumb: TextureRegion? = null
private val grad = CommonResourcePool.getAsTexture("title_halfgrad")
private val meta = ReadMeta(disk)
private var saveDamaged = checkForSavegameDamage(skimmer)
private fun parseDuration(seconds: Long): String {
val s = seconds % 60
@@ -302,6 +305,9 @@ class UIItemDemoSaveCells(
"${d}d${h.toString().padStart(2,'0')}h${m.toString().padStart(2,'0')}m${s.toString().padStart(2,'0')}s"
}
private val saveName = skimmer.getDiskName(Common.CHARSET)
private val saveMode = skimmer.getSaveMode()
private val lastPlayedTimestamp = Instant.ofEpochSecond(meta.lastplay_t)
.atZone(TimeZone.getDefault().toZoneId())
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) +
@@ -310,7 +316,7 @@ class UIItemDemoSaveCells(
init {
try {
// load a thumbnail
val zippedTga = VDUtil.getAsNormalFile(disk, -2).getContent()
val zippedTga = (skimmer.requestFile(-2)!!.contents as EntryFile).bytes
val gzin = GZIPInputStream(ByteArray64InputStream(zippedTga))
val tgaFileContents = gzin.readAllBytes(); gzin.close()
@@ -328,7 +334,7 @@ class UIItemDemoSaveCells(
}
override var clickOnceListener: ((Int, Int, Int) -> Unit)? = { _: Int, _: Int, _: Int ->
LoadSavegame(disk)
LoadSavegame(VDUtil.readDiskArchive(diskfile, Level.INFO))
}
override fun render(batch: SpriteBatch, camera: Camera) {
@@ -357,10 +363,10 @@ class UIItemDemoSaveCells(
val tlen = App.fontSmallNumbers.getWidth(lastPlayedTimestamp)
App.fontSmallNumbers.draw(batch, lastPlayedTimestamp, x + (width - tlen) - 3f, y + height - 16f)
// file size
App.fontSmallNumbers.draw(batch, "${disk.usedBytes.ushr(10)} KiB", x + 3f, y + height - 16f)
App.fontSmallNumbers.draw(batch, "${diskfile.length().ushr(10)} KiB", x + 3f, y + height - 16f)
// savegame name
val diskName = disk.getDiskNameString(Common.CHARSET)
App.fontGame.draw(batch, diskName + "${if (disk.saveMode % 2 == 1) "*" else ""}", x + 3f, y + 1f)
if (saveDamaged) batch.color = Color.RED
App.fontGame.draw(batch, saveName + "${if (saveMode % 2 == 1) "*" else ""}", x + 3f, y + 1f)
super.render(batch, camera)
batch.color = Color.WHITE

View File

@@ -5,7 +5,9 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch
import net.torvald.terrarum.App
import net.torvald.terrarum.Second
import net.torvald.terrarum.serialise.LoadSavegame
import net.torvald.terrarum.tvda.VDUtil
import net.torvald.terrarum.ui.UICanvas
import java.util.logging.Level
/**
* Created by minjaesong on 2021-09-13.
@@ -30,7 +32,9 @@ class UIProxyLoadLatestSave : UICanvas() {
override fun endOpening(delta: Float) {
if (App.savegames.size > 0) {
LoadSavegame(App.savegames[0])
LoadSavegame(VDUtil.readDiskArchive(App.savegames[0].first, Level.INFO) {
System.err.println("Possibly damaged savefile ${App.savegames[0].first.absolutePath}:\n$it")
})
}
}

View File

@@ -5,10 +5,7 @@ import net.torvald.terrarum.ModMgr
import net.torvald.terrarum.gameactors.ActorID
import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.modulebasegame.worldgenerator.RoguelikeRandomiser
import net.torvald.terrarum.tvda.ByteArray64
import net.torvald.terrarum.tvda.ByteArray64Reader
import net.torvald.terrarum.tvda.EntryFile
import net.torvald.terrarum.tvda.VirtualDisk
import net.torvald.terrarum.tvda.*
import net.torvald.terrarum.weather.WeatherMixer
/**
@@ -82,4 +79,9 @@ object ReadMeta {
return Common.jsoner.fromJson(WriteMeta.WorldMeta::class.java, metaReader)
}
fun fromDiskEntry(metaFile: DiskEntry): WriteMeta.WorldMeta {
val metaReader = ByteArray64Reader((metaFile.contents as EntryFile).getContent(), Common.CHARSET)
return Common.jsoner.fromJson(WriteMeta.WorldMeta::class.java, metaReader)
}
}

View File

@@ -17,7 +17,11 @@ import kotlin.experimental.and
*
* Created by minjaesong on 2017-11-17.
*/
class DiskSkimmer(private val diskFile: File, val charset: Charset = Charset.defaultCharset()) {
class DiskSkimmer(
private val diskFile: File,
val charset: Charset = Charset.defaultCharset(),
private val skimmingStopFunction: (HashMap<EntryID, Long>) -> Boolean = { false }
) {
/*
@@ -63,6 +67,10 @@ removefile:
val fa = RandomAccessFile(diskFile, "rw")
private fun debugPrintln(s: Any) {
if (false) println(s.toString())
}
init {
val fis = FileInputStream(diskFile)
@@ -141,11 +149,13 @@ removefile:
if (typeFlag > 0) {
entryToOffsetTable[entryID] = offset
println("[DiskSkimmer] successfully read the entry $entryID at offset $offset (name: ${diskIDtoReadableFilename(entryID)})")
debugPrintln("[DiskSkimmer] ... successfully read the entry $entryID at offset $offset (name: ${diskIDtoReadableFilename(entryID)})")
}
else {
println("[DiskSkimmer] discarding entry $entryID at offset $offset (name: ${diskIDtoReadableFilename(entryID)})")
debugPrintln("[DiskSkimmer] ... discarding entry $entryID at offset $offset (name: ${diskIDtoReadableFilename(entryID)})")
}
if (skimmingStopFunction(entryToOffsetTable)) break
}
}
@@ -162,7 +172,7 @@ removefile:
fun requestFile(entryID: EntryID): DiskEntry? {
entryToOffsetTable[entryID].let { offset ->
if (offset == null) {
println("[DiskSkimmer.requestFile] entry $entryID does not exist on the table")
debugPrintln("[DiskSkimmer.requestFile] entry $entryID does not exist on the table")
return null
}
else {
@@ -231,17 +241,17 @@ removefile:
// fixme pretty much untested
val path = path.split(dirDelim)
//println(path)
//debugPrintln(path)
// bunch-of-io-access approach (for reading)
var traversedDir = 0L // entry ID
var dirFile: DiskEntry? = null
path.forEachIndexed { index, dirName ->
println("[DiskSkimmer.requestFile] $index\t$dirName, traversedDir = $traversedDir")
debugPrintln("[DiskSkimmer.requestFile] $index\t$dirName, traversedDir = $traversedDir")
dirFile = requestFile(traversedDir)
if (dirFile == null) {
println("[DiskSkimmer.requestFile] requestFile($traversedDir) came up null")
debugPrintln("[DiskSkimmer.requestFile] requestFile($traversedDir) came up null")
return null
} // outright null
if (dirFile!!.contents !is EntryDirectory && index < path.lastIndex) { // unexpectedly encountered non-directory
@@ -257,7 +267,7 @@ removefile:
// get name of the file
val childDirFile = requestFile(it)!!
if (childDirFile.filename.toCanonicalString(charset) == dirName) {
//println("[DiskSkimmer] found, $traversedDir -> $it")
//debugPrintln("[DiskSkimmer] found, $traversedDir -> $it")
dirGotcha = true
traversedDir = it
}
@@ -306,6 +316,20 @@ removefile:
fa.writeByte(bits)
}
fun getSaveMode(): Int {
fa.seek(49L)
return fa.read()
}
fun getDiskName(charset: Charset): String {
val bytes = ByteArray(268)
fa.seek(10L)
fa.read(bytes, 0, 32)
fa.seek(60L)
fa.read(bytes, 32, 236)
return bytes.toCanonicalString(charset)
}
///////////////////////////////////////////////////////
// THESE ARE METHODS TO SUPPORT ON-LINE MODIFICATION //
///////////////////////////////////////////////////////
@@ -409,7 +433,7 @@ removefile:
val HEADER_SIZE = DiskEntry.HEADER_SIZE
println("[DiskSkimmer.getEntryBlockSize] offset for entry $id = $offset")
debugPrintln("[DiskSkimmer.getEntryBlockSize] offset for entry $id = $offset")
val fis = FileInputStream(diskFile)
fis.skip(offset + 8)