diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/LoadingLog.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/LoadingLog.kt new file mode 100644 index 00000000..8f89a810 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/LoadingLog.kt @@ -0,0 +1,71 @@ +package ru.dbotthepony.kstarbound + +import it.unimi.dsi.fastutil.ints.IntIterators +import it.unimi.dsi.fastutil.objects.ObjectIterators + +interface ILoadingLog : Iterable { + interface ILine { + var text: String + } + + fun line(text: String): ILine + + companion object : ILoadingLog, ILine { + override var text: String + get() = "" + set(value) {} + + override fun line(text: String): ILine { + return this + } + + override fun iterator(): Iterator { + return ObjectIterators.emptyIterator() + } + } +} + +class LoadingLog : ILoadingLog { + private val lines = arrayOfNulls(128) + private var index = 0 + private val lock = Any() + private var size = 0 + var lastActivity: Long = System.nanoTime() + private set + + override fun line(text: String): ILoadingLog.ILine { + return Line(text) + } + + inner class Line(text: String) : ILoadingLog.ILine { + override var text: String = text + set(value) { + field = value + lastActivity = System.nanoTime() + } + + init { + lastActivity = System.nanoTime() + + synchronized(lock) { + lines[index++ and 127] = this + size = (size + 1).coerceAtMost(127) + } + } + } + + override fun iterator(): Iterator { + return object : Iterator { + private val index = (this@LoadingLog.index - 1) and 127 + private val parent = IntIterators.fromTo(0, size) + + override fun hasNext(): Boolean { + return parent.hasNext() + } + + override fun next(): String { + return lines[(index - parent.nextInt()) and 127]!!.text + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index 285d9dae..03d45657 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -52,9 +52,7 @@ fun main() { //Starbound.addPakPath(File("packed.pak")) - Starbound.initializeGame { finished, replaceStatus, status -> - client.putDebugLog(status, replaceStatus) - } + Starbound.initializeGame(client.loadingLog) client.onTermination { Starbound.terminateLoading = true @@ -159,7 +157,8 @@ fun main() { client.font.render("${ent.position}", y = 100f, scale = 0.25f) client.font.render("${ent.movement.velocity}", y = 120f, scale = 0.25f) client.font.render("Camera: ${client.camera.pos} ${client.settings.zoom}", y = 140f, scale = 0.25f) - client.font.render("World chunk: ${client.world!!.chunkFromCell(client.camera.pos)}", y = 160f, scale = 0.25f) + client.font.render("Cursor: ${client.mouseCoordinates} -> ${client.screenToWorld(client.mouseCoordinates)}", y = 160f, scale = 0.25f) + client.font.render("World chunk: ${client.world!!.chunkFromCell(client.camera.pos)}", y = 180f, scale = 0.25f) } client.onPreDrawWorld { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/RecipeRegistry.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/RecipeRegistry.kt index b479847f..8b0952bf 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/RecipeRegistry.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/RecipeRegistry.kt @@ -7,14 +7,15 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import ru.dbotthepony.kstarbound.api.IStarboundFile import ru.dbotthepony.kstarbound.defs.player.RecipeDefinition import java.util.Collections +import java.util.LinkedList class RecipeRegistry { private val recipesInternal = ArrayList>() - private val group2recipesInternal = Object2ObjectOpenHashMap>>() + private val group2recipesInternal = Object2ObjectOpenHashMap>>() private val group2recipesBacking = Object2ObjectOpenHashMap>>() - private val output2recipesInternal = Object2ObjectOpenHashMap>>() + private val output2recipesInternal = Object2ObjectOpenHashMap>>() private val output2recipesBacking = Object2ObjectOpenHashMap>>() - private val input2recipesInternal = Object2ObjectOpenHashMap>>() + private val input2recipesInternal = Object2ObjectOpenHashMap>>() private val input2recipesBacking = Object2ObjectOpenHashMap>>() val recipes: List> = Collections.unmodifiableList(recipesInternal) @@ -28,21 +29,21 @@ class RecipeRegistry { for (group in value.groups) { group2recipesInternal.computeIfAbsent(group, Object2ObjectFunction { p -> - ArrayList>().also { + LinkedList>().also { group2recipesBacking[p as String] = Collections.unmodifiableList(it) } }).add(recipe) } output2recipesInternal.computeIfAbsent(value.output.item.name, Object2ObjectFunction { p -> - ArrayList>().also { + LinkedList>().also { output2recipesBacking[p as String] = Collections.unmodifiableList(it) } }).add(recipe) for (input in value.input) { input2recipesInternal.computeIfAbsent(input.item.name, Object2ObjectFunction { p -> - ArrayList>().also { + LinkedList>().also { input2recipesBacking[p as String] = Collections.unmodifiableList(it) } }).add(recipe) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index 09b4fbe0..3a305150 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -85,6 +85,8 @@ import ru.dbotthepony.kstarbound.util.set import ru.dbotthepony.kstarbound.util.traverseJsonPath import java.io.* import java.text.DateFormat +import java.util.concurrent.ForkJoinPool +import java.util.concurrent.ForkJoinTask import java.util.function.BiConsumer import java.util.function.BinaryOperator import java.util.function.Function @@ -901,7 +903,7 @@ object Starbound : ISBFileLocator { private set private fun loadStage( - callback: (Boolean, Boolean, String) -> Unit, + log: ILoadingLog, loader: ((String) -> Unit) -> Unit, name: String, ) { @@ -909,27 +911,25 @@ object Starbound : ISBFileLocator { return val time = System.currentTimeMillis() - callback(false, false, "Loading $name...") - logger.info("Loading $name...") + val line = log.line("Loading $name...".also(logger::info)) loader { if (terminateLoading) { throw InterruptedException("Game is terminating") } - callback(false, true, it) + line.text = it } - callback(false, true, "Loaded $name in ${System.currentTimeMillis() - time}ms") - logger.info("Loaded $name in ${System.currentTimeMillis() - time}ms") + line.text = ("Loaded $name in ${System.currentTimeMillis() - time}ms".also(logger::info)) } private fun loadStage( - callback: (Boolean, Boolean, String) -> Unit, + log: ILoadingLog, registry: ObjectRegistry, files: List, ) { - loadStage(callback, loader = { + loadStage(log, loader = { for (listedFile in files) { try { it("Loading $listedFile") @@ -945,24 +945,24 @@ object Starbound : ISBFileLocator { }, registry.name) } - private fun doInitialize(callback: (finished: Boolean, replaceStatus: Boolean, status: String) -> Unit) { + private fun doInitialize(log: ILoadingLog) { var time = System.currentTimeMillis() if (archivePaths.isNotEmpty()) { - callback(false, false, "Searching for pak archives...".also(logger::info)) + log.line("Searching for pak archives...".also(logger::info)) for (path in archivePaths) { - callback(false, false, "Reading index of ${path}...".also(logger::info)) + val line = log.line("Reading index of ${path}...".also(logger::info)) addPak(StarboundPak(path) { _, status -> - callback(false, true, "${path.parent}/${path.name}: $status") + line.text = ("${path.parent}/${path.name}: $status") }) } } - callback(false, false, "Finished reading pak archives in ${System.currentTimeMillis() - time}ms".also(logger::info)) + log.line("Finished reading pak archives in ${System.currentTimeMillis() - time}ms".also(logger::info)) time = System.currentTimeMillis() - callback(false, false, "Building file index...".also(logger::info)) + log.line("Building file index...".also(logger::info)) val ext2files = fileSystems.parallelStream() .flatMap { it.explore() } @@ -998,39 +998,45 @@ object Starbound : ISBFileLocator { } }) - callback(false, false, "Finished building file index in ${System.currentTimeMillis() - time}ms".also(logger::info)) + log.line("Finished building file index in ${System.currentTimeMillis() - time}ms".also(logger::info)) - loadStage(callback, { loadItemDefinitions(it, ext2files) }, "item definitions") - loadStage(callback, { loadJsonFunctions(it, ext2files["functions"] ?: listOf()) }, "json functions") - loadStage(callback, { loadJson2Functions(it, ext2files["2functions"] ?: listOf()) }, "json 2functions") - loadStage(callback, { loadRecipes(it, ext2files["recipe"] ?: listOf()) }, "recipes") - loadStage(callback, { loadTreasurePools(it, ext2files["treasurepools"] ?: listOf()) }, "treasure pools") + val pool = ForkJoinPool.commonPool() + val tasks = ArrayList>() - loadStage(callback, _tiles, ext2files["material"] ?: listOf()) - loadStage(callback, _tileModifiers, ext2files["matmod"] ?: listOf()) - loadStage(callback, _liquid, ext2files["liquid"] ?: listOf()) - loadStage(callback, _worldObjects, ext2files["object"] ?: listOf()) - loadStage(callback, _statusEffects, ext2files["statuseffect"] ?: listOf()) - loadStage(callback, _species, ext2files["species"] ?: listOf()) - loadStage(callback, _particles, ext2files["particle"] ?: listOf()) - loadStage(callback, _questTemplates, ext2files["questtemplate"] ?: listOf()) - loadStage(callback, _techs, ext2files["tech"] ?: listOf()) - loadStage(callback, _npcTypes, ext2files["npctype"] ?: listOf()) - //loadStage(callback, _projectiles, ext2files["projectile"] ?: listOf()) - //loadStage(callback, _tenants, ext2files["tenant"] ?: listOf()) - loadStage(callback, _monsterSkills, ext2files["monsterskill"] ?: listOf()) - //loadStage(callback, _monsterTypes, ext2files["monstertype"] ?: listOf()) + tasks.add(pool.submit { loadStage(log, { loadItemDefinitions(it, ext2files) }, "item definitions") }) + + tasks.add(pool.submit { loadStage(log, { loadJsonFunctions(it, ext2files["functions"] ?: listOf()) }, "json functions") }) + tasks.add(pool.submit { loadStage(log, { loadJson2Functions(it, ext2files["2functions"] ?: listOf()) }, "json 2functions") }) + tasks.add(pool.submit { loadStage(log, { loadRecipes(it, ext2files["recipe"] ?: listOf()) }, "recipes") }) + tasks.add(pool.submit { loadStage(log, { loadTreasurePools(it, ext2files["treasurepools"] ?: listOf()) }, "treasure pools") }) + + tasks.add(pool.submit { loadStage(log, _tiles, ext2files["material"] ?: listOf()) }) + tasks.add(pool.submit { loadStage(log, _tileModifiers, ext2files["matmod"] ?: listOf()) }) + tasks.add(pool.submit { loadStage(log, _liquid, ext2files["liquid"] ?: listOf()) }) + tasks.add(pool.submit { loadStage(log, _worldObjects, ext2files["object"] ?: listOf()) }) + tasks.add(pool.submit { loadStage(log, _statusEffects, ext2files["statuseffect"] ?: listOf()) }) + tasks.add(pool.submit { loadStage(log, _species, ext2files["species"] ?: listOf()) }) + tasks.add(pool.submit { loadStage(log, _particles, ext2files["particle"] ?: listOf()) }) + tasks.add(pool.submit { loadStage(log, _questTemplates, ext2files["questtemplate"] ?: listOf()) }) + tasks.add(pool.submit { loadStage(log, _techs, ext2files["tech"] ?: listOf()) }) + tasks.add(pool.submit { loadStage(log, _npcTypes, ext2files["npctype"] ?: listOf()) }) + // tasks.add(pool.submit { loadStage(log, _projectiles, ext2files["projectile"] ?: listOf()) }) + // tasks.add(pool.submit { loadStage(log, _tenants, ext2files["tenant"] ?: listOf()) }) + tasks.add(pool.submit { loadStage(log, _monsterSkills, ext2files["monsterskill"] ?: listOf()) }) + // tasks.add(pool.submit { loadStage(log, _monsterTypes, ext2files["monstertype"] ?: listOf()) }) AssetPathStack.block("/") { //playerDefinition = gson.fromJson(locate("/player.config").reader(), PlayerDefinition::class.java) } + tasks.forEach { it.join() } + initializing = false initialized = true - callback(true, false, "Finished loading in ${System.currentTimeMillis() - time}ms") + log.line("Finished loading in ${System.currentTimeMillis() - time}ms") } - fun initializeGame(callback: (finished: Boolean, replaceStatus: Boolean, status: String) -> Unit) { + fun initializeGame(log: ILoadingLog) { if (initializing) { throw IllegalStateException("Already initializing!") } @@ -1040,7 +1046,7 @@ object Starbound : ISBFileLocator { } initializing = true - Thread({ doInitialize(callback) }, "Asset Loader").also { + Thread({ doInitialize(log) }, "Asset Loader").also { it.isDaemon = true it.start() } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt index 549fd14d..0f538795 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt @@ -12,6 +12,7 @@ import org.lwjgl.opengl.GL45.* import org.lwjgl.opengl.GLCapabilities import org.lwjgl.system.MemoryStack import org.lwjgl.system.MemoryUtil +import ru.dbotthepony.kstarbound.LoadingLog import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNIT import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.Starbound @@ -109,7 +110,7 @@ class StarboundClient : Closeable { var viewportTopRight = Vector2d() private set - var fullbright = true + var fullbright = false var clientTerminated = false private set @@ -133,8 +134,7 @@ class StarboundClient : Closeable { private val onPostDrawWorldOnce = ArrayList<(LayeredRenderer) -> Unit>() private val onViewportChanged = ArrayList<(width: Int, height: Int) -> Unit>() private val terminateCallbacks = ArrayList<() -> Unit>() - private val startupTextList = ArrayList() - private var finishStartupRendering = System.currentTimeMillis() + 4000L + val loadingLog = LoadingLog() private val cleaner = Cleaner.create { r -> val thread = Thread(r, "OpenGL Cleaner for '${thread.name}'") @@ -180,7 +180,7 @@ class StarboundClient : Closeable { window = GLFW.glfwCreateWindow(800, 600, "KStarbound", MemoryUtil.NULL, MemoryUtil.NULL) require(window != MemoryUtil.NULL) { "Unable to create GLFW window" } - startupTextList.add("Created GLFW window") + loadingLog.line("Created GLFW window") input.installCallback(window) @@ -235,7 +235,7 @@ class StarboundClient : Closeable { GLFW.glfwSwapInterval(0) GLFW.glfwShowWindow(window) - putDebugLog("Initialized GLFW window") + loadingLog.line("Initialized GLFW window") } val maxTextureBlocks = glGetInteger(GL_MAX_TEXTURE_IMAGE_UNITS) @@ -521,6 +521,20 @@ class StarboundClient : Closeable { builder.draw(GL_LINES) } + inline fun lines(color: RGBAColor = RGBAColor.WHITE, lambda: (VertexBuilder) -> Unit) { + val builder = programs.position.builder + + builder.builder.begin(GeometryType.LINES) + lambda.invoke(builder.builder) + builder.upload() + + programs.position.use() + programs.position.colorMultiplier = color + programs.position.modelMatrix = stack.last() + + builder.draw(GL_LINES) + } + fun vertex(file: File) = GLShader(file, GL_VERTEX_SHADER) fun fragment(file: File) = GLShader(file, GL_FRAGMENT_SHADER) @@ -531,20 +545,6 @@ class StarboundClient : Closeable { fun internalFragment(file: String) = GLShader(readInternal(file), GL_FRAGMENT_SHADER) fun internalGeometry(file: String) = GLShader(readInternal(file), GL_GEOMETRY_SHADER) - fun putDebugLog(text: String, replace: Boolean = false) { - if (replace) { - if (startupTextList.isEmpty()) { - startupTextList.add(text) - } else { - startupTextList[startupTextList.size - 1] = text - } - } else { - startupTextList.add(text) - } - - finishStartupRendering = System.currentTimeMillis() + 4000L - } - private fun isMe(state: StarboundClient?) { if (state != null && state != this) { throw InvalidArgumentException("Provided object does not belong to $this state tracker (belongs to $state)") @@ -569,7 +569,7 @@ class StarboundClient : Closeable { xMousePos.position(0) yMousePos.position(0) - return Vector2d(xMousePos.get(), yMousePos.get()) + return Vector2d(xMousePos.get(), viewportHeight - yMousePos.get()) } val mouseCoordinatesF: Vector2f get() { @@ -579,7 +579,7 @@ class StarboundClient : Closeable { xMousePos.position(0) yMousePos.position(0) - return Vector2f(xMousePos.get().toFloat(), yMousePos.get().toFloat()) + return Vector2f(xMousePos.get().toFloat(), viewportHeight - yMousePos.get().toFloat()) } fun screenToWorld(x: Double, y: Double): Vector2d { @@ -600,7 +600,6 @@ class StarboundClient : Closeable { var world: ClientWorld? = ClientWorld(this, 0L, Vector2i(3000, 2000), true) init { - putDebugLog("Initialized OpenGL context") clearColor = RGBAColor.SLATE_GRAY blend = true @@ -798,6 +797,10 @@ class StarboundClient : Closeable { size = viewportRectangle) if (viewportLightingMem != null && !fullbright) { + val spos = screenToWorld(mouseCoordinates) + + viewportLighting.addPointLight(roundTowardsPositiveInfinity(spos.x - viewportCellX), roundTowardsPositiveInfinity(spos.y - viewportCellY), 1f, 1f, 1f) + viewportLightingMem.position(0) BufferUtils.zeroBuffer(viewportLightingMem) viewportLightingMem.position(0) @@ -844,21 +847,19 @@ class StarboundClient : Closeable { uberShaderPrograms.forEachValid { it.viewMatrix = viewportMatrixScreen } fontShaderPrograms.forEachValid { it.viewMatrix = viewportMatrixScreen } - val thisTime = System.currentTimeMillis() - - if (startupTextList.isNotEmpty() && thisTime <= finishStartupRendering) { + if (System.nanoTime() - loadingLog.lastActivity <= 4_000_000_000L) { var alpha = 1f - if (finishStartupRendering - thisTime < 1000L) { - alpha = (finishStartupRendering - thisTime) / 1000f + if (System.nanoTime() - loadingLog.lastActivity >= 3_000_000_000L) { + alpha = 1f - (System.nanoTime() - loadingLog.lastActivity - 3_000_000_000L) / 1_000_000_000f } stack.push() stack.last().translate(y = viewportHeight.toFloat()) var shade = 255 - for (i in startupTextList.size - 1 downTo 0) { - val size = font.render(startupTextList[i], alignY = TextAlignY.BOTTOM, scale = 0.4f, color = RGBAColor(shade / 255f, shade / 255f, shade / 255f, alpha)) + for (line in loadingLog) { + val size = font.render(line, alignY = TextAlignY.BOTTOM, scale = 0.4f, color = RGBAColor(shade / 255f, shade / 255f, shade / 255f, alpha)) stack.last().translate(y = -size.height * 1.2f) if (shade > 120) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RenderLayers.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RenderLayer.kt similarity index 56% rename from src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RenderLayers.kt rename to src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RenderLayer.kt index 2dc4c6cf..f00c1e3d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RenderLayers.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RenderLayer.kt @@ -2,6 +2,8 @@ package ru.dbotthepony.kstarbound.client.render import com.google.common.collect.ImmutableMap import org.apache.logging.log4j.LogManager +import ru.dbotthepony.kstarbound.world.api.ITileState +import ru.dbotthepony.kstarbound.world.api.TileColor enum class RenderLayer { BackgroundOverlay, @@ -29,33 +31,49 @@ enum class RenderLayer { FrontParticle, Overlay; - val base = Point(this) + private val base = Point(this) - fun point(offset: Long = 0L): Point { - return if (offset == 0L) + fun point(offset: Long = 0L, index: Long = 0L, hueShift: Float = 0f, colorVariant: TileColor = TileColor.DEFAULT): Point { + return if (offset == 0L && index == 0L && hueShift == 0f && colorVariant === TileColor.DEFAULT) base else - Point(this, offset) + Point(this, offset, index, hueShift, colorVariant) } - data class Point(val base: RenderLayer, val offset: Long = 0L) : Comparable { + fun point(): Point { + return base + } + + data class Point(val base: RenderLayer, val offset: Long = 0L, val index: Long = 0L, val hueShift: Float = 0f, val colorVariant: TileColor = TileColor.DEFAULT) : Comparable { override fun compareTo(other: Point): Int { + if (this === other) return 0 var cmp = base.compareTo(other.base) if (cmp == 0) cmp = offset.compareTo(other.offset) + if (cmp == 0) cmp = index.compareTo(other.index) + if (cmp == 0) cmp = hueShift.compareTo(other.hueShift) + if (cmp == 0) cmp = colorVariant.compareTo(other.colorVariant) return cmp } } companion object { - fun tileLayer(isBackground: Boolean, isModifier: Boolean, offset: Long = 0L): Point { + fun tileLayer(isBackground: Boolean, isModifier: Boolean, offset: Long = 0L, index: Long = 0L, hueShift: Float = 0f, colorVariant: TileColor = TileColor.DEFAULT): Point { if (isBackground && isModifier) { - return BackgroundTileMod.point(offset) + return BackgroundTileMod.point(offset, index, hueShift, colorVariant) } else if (isBackground) { - return BackgroundTile.point(offset) + return BackgroundTile.point(offset, index, hueShift, colorVariant) } else if (isModifier) { - return ForegroundTileMod.point(offset) + return ForegroundTileMod.point(offset, index, hueShift, colorVariant) } else { - return ForegroundTile.point(offset) + return ForegroundTile.point(offset, index, hueShift, colorVariant) + } + } + + fun tileLayer(isBackground: Boolean, isModifier: Boolean, tile: ITileState): Point { + if (isModifier) { + return tileLayer(isBackground, true, tile.modifier?.renderParameters?.zLevel ?: 0L, tile.modifier?.modId?.toLong() ?: 0L, tile.modifierHueShift) + } else { + return tileLayer(isBackground, false, tile.material.renderParameters.zLevel, tile.material.materialId.toLong(), tile.hueShift) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt index e20c1f1e..fedd6304 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt @@ -1,5 +1,8 @@ package ru.dbotthepony.kstarbound.client.render +import com.github.benmanes.caffeine.cache.Cache +import com.github.benmanes.caffeine.cache.Caffeine +import com.github.benmanes.caffeine.cache.Scheduler import org.apache.logging.log4j.LogManager import org.lwjgl.opengl.GL45.* import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf @@ -15,6 +18,7 @@ import ru.dbotthepony.kstarbound.world.api.TileColor import ru.dbotthepony.kvector.arrays.Matrix3f import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.Vector2i +import java.time.Duration import kotlin.collections.HashMap /** @@ -23,22 +27,37 @@ import kotlin.collections.HashMap * Создаётся единожды как потомок [Graphics] */ class TileRenderers(val client: StarboundClient) { - private val foreground = HashMap() - private val background = HashMap() - private val matCache = HashMap() - private val modCache = HashMap() + private val foreground: Cache = Caffeine.newBuilder() + .expireAfterAccess(Duration.ofMinutes(5)) + .scheduler(Scheduler.systemScheduler()) + .build() + + private val background: Cache = Caffeine.newBuilder() + .expireAfterAccess(Duration.ofMinutes(5)) + .scheduler(Scheduler.systemScheduler()) + .build() + + private val matCache: Cache = Caffeine.newBuilder() + .expireAfterAccess(Duration.ofMinutes(5)) + .scheduler(Scheduler.systemScheduler()) + .build() + + private val modCache: Cache = Caffeine.newBuilder() + .expireAfterAccess(Duration.ofMinutes(5)) + .scheduler(Scheduler.systemScheduler()) + .build() fun getMaterialRenderer(defName: String): TileRenderer { - return matCache.computeIfAbsent(defName) { + return matCache.get(defName) { val def = Starbound.tiles[defName] // TODO: Пустой рендерер - return@computeIfAbsent TileRenderer(this, def!!.value) + TileRenderer(this, def!!.value) } } fun getModifierRenderer(defName: String): TileRenderer { - return modCache.computeIfAbsent(defName) { + return modCache.get(defName) { val def = Starbound.tileModifiers[defName] // TODO: Пустой рендерер - return@computeIfAbsent TileRenderer(this, def!!.value) + TileRenderer(this, def!!.value) } } @@ -62,11 +81,11 @@ class TileRenderers(val client: StarboundClient) { } fun foreground(texture: GLTexture2D): RenderConfig { - return foreground.computeIfAbsent(texture) { Config(it, FOREGROUND_COLOR) } + return foreground.get(texture) { Config(it, FOREGROUND_COLOR) } } fun background(texture: GLTexture2D): RenderConfig { - return background.computeIfAbsent(texture) { Config(it, BACKGROUND_COLOR) } + return background.get(texture) { Config(it, BACKGROUND_COLOR) } } companion object { @@ -175,7 +194,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) { tesselateAt( self, renderPiece.piece, getter, - meshBuilder.getBuilder(RenderLayer.tileLayer(isBackground, isModifier, def.renderParameters.zLevel), program).mode(GeometryType.QUADS), + meshBuilder.getBuilder(RenderLayer.tileLayer(isBackground, isModifier, self), program).mode(GeometryType.QUADS), pos, renderPiece.offset, isModifier) } else { tesselateAt(self, renderPiece.piece, getter, thisBuilder, pos, renderPiece.offset, isModifier) @@ -217,7 +236,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) { val template = def.renderTemplate.value ?: return val vertexBuilder = meshBuilder - .getBuilder(RenderLayer.tileLayer(isBackground, isModifier, def.renderParameters.zLevel), if (isBackground) bakedBackgroundProgramState!! else bakedProgramState!!) + .getBuilder(RenderLayer.tileLayer(isBackground, isModifier, self), if (isBackground) bakedBackgroundProgramState!! else bakedProgramState!!) .mode(GeometryType.QUADS) for ((_, matcher) in template.matches) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientWorld.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientWorld.kt index a1dde6bd..50c3fdda 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientWorld.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientWorld.kt @@ -16,6 +16,8 @@ import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity import ru.dbotthepony.kstarbound.world.CHUNK_SIZE import ru.dbotthepony.kstarbound.world.ChunkPos +import ru.dbotthepony.kstarbound.world.NonSolidRayFilter +import ru.dbotthepony.kstarbound.world.SolidRayFilter import ru.dbotthepony.kstarbound.world.World import ru.dbotthepony.kstarbound.world.api.ITileAccess import ru.dbotthepony.kstarbound.world.api.OffsetCellAccess @@ -24,8 +26,12 @@ import ru.dbotthepony.kstarbound.world.positiveModulo import ru.dbotthepony.kvector.api.IStruct2i import ru.dbotthepony.kvector.util2d.AABB import ru.dbotthepony.kvector.vector.RGBAColor +import ru.dbotthepony.kvector.vector.Vector2d import ru.dbotthepony.kvector.vector.Vector2f import ru.dbotthepony.kvector.vector.Vector2i +import kotlin.math.PI +import kotlin.math.cos +import kotlin.math.sin class ClientWorld( val client: StarboundClient, @@ -282,26 +288,42 @@ class ClientWorld( } } - /*layers.add(-999999) { + /*layers.add(RenderLayer.Overlay.base) { val rayFan = ArrayList() + val pos = client.screenToWorld(client.mouseCoordinates) - for (i in 0 .. 359) { - rayFan.add(Vector2d(cos(i / 180.0 * PI), sin(i / 180.0 * PI))) + //for (i in 0 .. 359) { + // rayFan.add(Vector2d(cos(i / 180.0 * PI), sin(i / 180.0 * PI))) + //} + + rayFan.add(Vector2d(0.5, 0.7).unitVector) + + client.quadWireframe(RGBAColor(1f, 1f, 1f, 0.4f)) { + for (x in -20 .. 20) { + for (y in -20 .. 20) { + it.vertex(pos.x.toInt().toFloat() + x, pos.y.toInt().toFloat() + y) + it.vertex(pos.x.toInt().toFloat() + x + 1f, pos.y.toInt().toFloat() + y) + it.vertex(pos.x.toInt().toFloat() + x + 1f, pos.y.toInt().toFloat() + 1f + y) + it.vertex(pos.x.toInt().toFloat() + x, pos.y.toInt().toFloat() + 1f + y) + } + } } - for (ray in rayFan) { - val trace = castRayNaive(pos, ray, 16.0) + client.lines { + for (ray in rayFan) { + val trace = castRayExact(pos, ray, 16.0, NonSolidRayFilter) - client.gl.quadWireframe { - for ((tpos, tile) in trace.traversedTiles) { - if (tile.foreground.material != null) - it.quad( - tpos.x.toFloat(), - tpos.y.toFloat(), - tpos.x + 1f, - tpos.y + 1f - ) - } + it.vertex(pos.x.toFloat(), pos.y.toFloat()) + it.vertex(pos.x.toFloat() + ray.x.toFloat() * trace.fraction.toFloat() * 16f, pos.y.toFloat() + ray.y.toFloat() * trace.fraction.toFloat() * 16f) + + /*for ((tpos, tile) in trace.traversedTiles) { + if (!tile.foreground.material.renderParameters.lightTransparent) { + it.vertex(tpos.x.toFloat(), tpos.y.toFloat()) + it.vertex(tpos.x.toFloat() + 1f, tpos.y.toFloat()) + it.vertex(tpos.x.toFloat() + 1f, tpos.y.toFloat() + 1f) + it.vertex(tpos.x.toFloat(), tpos.y.toFloat() + 1f) + } + }*/ } } }*/ diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt index 694efd56..b8c30d66 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt @@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.defs.image import com.github.benmanes.caffeine.cache.Cache import com.github.benmanes.caffeine.cache.Caffeine +import com.github.benmanes.caffeine.cache.Scheduler import com.google.common.collect.ImmutableList import com.google.gson.JsonArray import com.google.gson.JsonNull @@ -274,10 +275,10 @@ class Image private constructor( private val cleaner = Cleaner.create { Thread(it, "STB Image Cleaner") } private val dataCache: Cache = Caffeine.newBuilder() - .softValues() - .expireAfterAccess(Duration.ofMinutes(5)) + .expireAfterAccess(Duration.ofMinutes(1)) .weigher { key, value -> value.capacity() } .maximumWeight(1_024L * 1_024L * 256L /* 256 МиБ */) + .scheduler(Scheduler.systemScheduler()) .build() @JvmStatic diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/StarboundPak.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/StarboundPak.kt index 6a0e64b2..ae38a808 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/StarboundPak.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/StarboundPak.kt @@ -97,9 +97,11 @@ class StarboundPak(val path: File, callback: (finished: Boolean, status: String) return -1 } - reader.seek(innerOffset + offset) - innerOffset++ - return reader.read() + synchronized(lock) { + reader.seek(innerOffset + offset) + innerOffset++ + return reader.read() + } } override fun readNBytes(len: Int): ByteArray { @@ -110,15 +112,13 @@ class StarboundPak(val path: File, callback: (finished: Boolean, status: String) if (readMax == 0) return ByteArray(0) - val b = ByteArray(readMax) - reader.seek(innerOffset + offset) - val readBytes = reader.read(b) - - if (readBytes != readMax) - throw IOError(RuntimeException("Reading $readMax bytes returned only $readBytes bytes")) - - innerOffset += readBytes - return b + synchronized(lock) { + val b = ByteArray(readMax) + reader.seek(innerOffset + offset) + reader.readFully(b) + innerOffset += readMax + return b + } } override fun read(b: ByteArray, off: Int, len: Int): Int { @@ -133,14 +133,16 @@ class StarboundPak(val path: File, callback: (finished: Boolean, status: String) if (readMax <= 0) return -1 - reader.seek(innerOffset + offset) - val readBytes = reader.read(b, off, readMax) + synchronized(lock) { + reader.seek(innerOffset + offset) + val readBytes = reader.read(b, off, readMax) - if (readBytes == -1) - throw RuntimeException("Unexpected EOF, want to read $readMax bytes from starting $offset in $path") + if (readBytes == -1) + throw RuntimeException("Unexpected EOF, want to read $readMax bytes from starting $offset in $path") - innerOffset += readBytes - return readBytes + innerOffset += readBytes + return readBytes + } } } } @@ -150,7 +152,8 @@ class StarboundPak(val path: File, callback: (finished: Boolean, status: String) } } - val reader = RandomAccessFile(path, "r") + private val reader = RandomAccessFile(path, "r") + private val lock = Any() init { readHeader(reader, 0x53) // S