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.Disposable;
import com.badlogic.gdx.utils.JsonValue; import com.badlogic.gdx.utils.JsonValue;
import com.github.strikerx3.jxinput.XInputDevice; import com.github.strikerx3.jxinput.XInputDevice;
import kotlin.Pair;
import net.torvald.gdx.graphics.PixmapIO2; import net.torvald.gdx.graphics.PixmapIO2;
import net.torvald.getcpuname.GetCpuName; import net.torvald.getcpuname.GetCpuName;
import net.torvald.terrarum.concurrent.ThreadExecutor; 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.TerrarumIngame;
import net.torvald.terrarum.modulebasegame.ui.ItemSlotImageFactory; import net.torvald.terrarum.modulebasegame.ui.ItemSlotImageFactory;
import net.torvald.terrarum.serialise.WriteConfig; import net.torvald.terrarum.serialise.WriteConfig;
import net.torvald.terrarum.serialise.WriteMeta;
import net.torvald.terrarum.tvda.VirtualDisk; import net.torvald.terrarum.tvda.VirtualDisk;
import net.torvald.terrarum.utils.JsonFetcher; import net.torvald.terrarum.utils.JsonFetcher;
import net.torvald.terrarum.worlddrawer.CreateTileAtlas; 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) * 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() { public static void updateListOfSavegames() {
AppUpdateListOfSavegames(); AppUpdateListOfSavegames();

View File

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

View File

@@ -128,7 +128,7 @@ open class GameWorld() : Disposable {
// preliminary spawn points // preliminary spawn points
this.spawnX = width / 2 this.spawnX = width / 2
this.spawnY = 200 this.spawnY = 150
layerTerrain = BlockLayer(width, height) layerTerrain = BlockLayer(width, height)
layerWall = 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 */ /** Load rest of the game with GL context */
private fun postInitForLoadFromSave(codices: Codices) { private fun postInitForLoadFromSave(codices: Codices) {
codices.actors.forEach { codices.actors.forEach {
val actor = ReadActor(LoadSavegame.getFileReader(codices.disk, it.toLong())) try {
addNewActor(actor) 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 // 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.Common
import net.torvald.terrarum.serialise.LoadSavegame import net.torvald.terrarum.serialise.LoadSavegame
import net.torvald.terrarum.serialise.ReadMeta import net.torvald.terrarum.serialise.ReadMeta
import net.torvald.terrarum.tvda.ByteArray64InputStream import net.torvald.terrarum.serialise.WriteMeta
import net.torvald.terrarum.tvda.VDUtil import net.torvald.terrarum.tvda.*
import net.torvald.terrarum.tvda.VirtualDisk
import net.torvald.terrarum.ui.* import net.torvald.terrarum.ui.*
import java.io.File
import java.time.Instant import java.time.Instant
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.* import java.util.*
import java.util.logging.Level
import java.util.zip.GZIPInputStream import java.util.zip.GZIPInputStream
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -74,10 +75,10 @@ class UILoadDemoSavefiles : UICanvas() {
// read savegames // read savegames
init { init {
App.savegames.forEachIndexed { index, disk -> App.savegames.forEachIndexed { index, fileMetaPair ->
val x = uiX val x = uiX
val y = titleTopGradEnd + cellInterval * index 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, parent: UILoadDemoSavefiles,
initialX: Int, initialX: Int,
initialY: 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 { companion object {
const val WIDTH = 480 const val WIDTH = 480
@@ -289,7 +292,7 @@ class UIItemDemoSaveCells(
private var thumb: TextureRegion? = null private var thumb: TextureRegion? = null
private val grad = CommonResourcePool.getAsTexture("title_halfgrad") private val grad = CommonResourcePool.getAsTexture("title_halfgrad")
private val meta = ReadMeta(disk) private var saveDamaged = checkForSavegameDamage(skimmer)
private fun parseDuration(seconds: Long): String { private fun parseDuration(seconds: Long): String {
val s = seconds % 60 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" "${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) private val lastPlayedTimestamp = Instant.ofEpochSecond(meta.lastplay_t)
.atZone(TimeZone.getDefault().toZoneId()) .atZone(TimeZone.getDefault().toZoneId())
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) +
@@ -310,7 +316,7 @@ class UIItemDemoSaveCells(
init { init {
try { try {
// load a thumbnail // 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 gzin = GZIPInputStream(ByteArray64InputStream(zippedTga))
val tgaFileContents = gzin.readAllBytes(); gzin.close() val tgaFileContents = gzin.readAllBytes(); gzin.close()
@@ -328,7 +334,7 @@ class UIItemDemoSaveCells(
} }
override var clickOnceListener: ((Int, Int, Int) -> Unit)? = { _: Int, _: Int, _: Int -> 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) { override fun render(batch: SpriteBatch, camera: Camera) {
@@ -357,10 +363,10 @@ class UIItemDemoSaveCells(
val tlen = App.fontSmallNumbers.getWidth(lastPlayedTimestamp) val tlen = App.fontSmallNumbers.getWidth(lastPlayedTimestamp)
App.fontSmallNumbers.draw(batch, lastPlayedTimestamp, x + (width - tlen) - 3f, y + height - 16f) App.fontSmallNumbers.draw(batch, lastPlayedTimestamp, x + (width - tlen) - 3f, y + height - 16f)
// file size // 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 // savegame name
val diskName = disk.getDiskNameString(Common.CHARSET) if (saveDamaged) batch.color = Color.RED
App.fontGame.draw(batch, diskName + "${if (disk.saveMode % 2 == 1) "*" else ""}", x + 3f, y + 1f) App.fontGame.draw(batch, saveName + "${if (saveMode % 2 == 1) "*" else ""}", x + 3f, y + 1f)
super.render(batch, camera) super.render(batch, camera)
batch.color = Color.WHITE 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.App
import net.torvald.terrarum.Second import net.torvald.terrarum.Second
import net.torvald.terrarum.serialise.LoadSavegame import net.torvald.terrarum.serialise.LoadSavegame
import net.torvald.terrarum.tvda.VDUtil
import net.torvald.terrarum.ui.UICanvas import net.torvald.terrarum.ui.UICanvas
import java.util.logging.Level
/** /**
* Created by minjaesong on 2021-09-13. * Created by minjaesong on 2021-09-13.
@@ -30,7 +32,9 @@ class UIProxyLoadLatestSave : UICanvas() {
override fun endOpening(delta: Float) { override fun endOpening(delta: Float) {
if (App.savegames.size > 0) { 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.gameactors.ActorID
import net.torvald.terrarum.modulebasegame.TerrarumIngame import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.modulebasegame.worldgenerator.RoguelikeRandomiser import net.torvald.terrarum.modulebasegame.worldgenerator.RoguelikeRandomiser
import net.torvald.terrarum.tvda.ByteArray64 import net.torvald.terrarum.tvda.*
import net.torvald.terrarum.tvda.ByteArray64Reader
import net.torvald.terrarum.tvda.EntryFile
import net.torvald.terrarum.tvda.VirtualDisk
import net.torvald.terrarum.weather.WeatherMixer import net.torvald.terrarum.weather.WeatherMixer
/** /**
@@ -82,4 +79,9 @@ object ReadMeta {
return Common.jsoner.fromJson(WriteMeta.WorldMeta::class.java, metaReader) 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. * 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") val fa = RandomAccessFile(diskFile, "rw")
private fun debugPrintln(s: Any) {
if (false) println(s.toString())
}
init { init {
val fis = FileInputStream(diskFile) val fis = FileInputStream(diskFile)
@@ -141,11 +149,13 @@ removefile:
if (typeFlag > 0) { if (typeFlag > 0) {
entryToOffsetTable[entryID] = offset 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 { 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? { fun requestFile(entryID: EntryID): DiskEntry? {
entryToOffsetTable[entryID].let { offset -> entryToOffsetTable[entryID].let { offset ->
if (offset == null) { 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 return null
} }
else { else {
@@ -231,17 +241,17 @@ removefile:
// fixme pretty much untested // fixme pretty much untested
val path = path.split(dirDelim) val path = path.split(dirDelim)
//println(path) //debugPrintln(path)
// bunch-of-io-access approach (for reading) // bunch-of-io-access approach (for reading)
var traversedDir = 0L // entry ID var traversedDir = 0L // entry ID
var dirFile: DiskEntry? = null var dirFile: DiskEntry? = null
path.forEachIndexed { index, dirName -> path.forEachIndexed { index, dirName ->
println("[DiskSkimmer.requestFile] $index\t$dirName, traversedDir = $traversedDir") debugPrintln("[DiskSkimmer.requestFile] $index\t$dirName, traversedDir = $traversedDir")
dirFile = requestFile(traversedDir) dirFile = requestFile(traversedDir)
if (dirFile == null) { if (dirFile == null) {
println("[DiskSkimmer.requestFile] requestFile($traversedDir) came up null") debugPrintln("[DiskSkimmer.requestFile] requestFile($traversedDir) came up null")
return null return null
} // outright null } // outright null
if (dirFile!!.contents !is EntryDirectory && index < path.lastIndex) { // unexpectedly encountered non-directory if (dirFile!!.contents !is EntryDirectory && index < path.lastIndex) { // unexpectedly encountered non-directory
@@ -257,7 +267,7 @@ removefile:
// get name of the file // get name of the file
val childDirFile = requestFile(it)!! val childDirFile = requestFile(it)!!
if (childDirFile.filename.toCanonicalString(charset) == dirName) { if (childDirFile.filename.toCanonicalString(charset) == dirName) {
//println("[DiskSkimmer] found, $traversedDir -> $it") //debugPrintln("[DiskSkimmer] found, $traversedDir -> $it")
dirGotcha = true dirGotcha = true
traversedDir = it traversedDir = it
} }
@@ -306,6 +316,20 @@ removefile:
fa.writeByte(bits) 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 // // THESE ARE METHODS TO SUPPORT ON-LINE MODIFICATION //
/////////////////////////////////////////////////////// ///////////////////////////////////////////////////////
@@ -409,7 +433,7 @@ removefile:
val HEADER_SIZE = DiskEntry.HEADER_SIZE 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) val fis = FileInputStream(diskFile)
fis.skip(offset + 8) fis.skip(offset + 8)