mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 20:31:51 +09:00
no more reading entire savefiles onto the memory upon the booting
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user