diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index 079b786c..58a26018 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -65,16 +65,12 @@ fun main() { Starbound.initializeGame(client.loadingLog) - client.onTermination { - Starbound.terminateLoading = true - } - var ply: PlayerEntity? = null - Starbound.onInitialize { + Starbound.mailboxInitialized.submit { ply = PlayerEntity(client.world!!) - ply!!.position = Vector2d(225.0, 745.0) + ply!!.position = Vector2d(225.0, 680.0) ply!!.spawn() //for (chunkX in 17 .. 18) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index 31c0cc92..8b2349bb 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -62,6 +62,7 @@ import ru.dbotthepony.kstarbound.world.physics.Poly import java.io.* import java.lang.ref.Cleaner import java.text.DateFormat +import java.util.concurrent.CompletableFuture import java.util.concurrent.ForkJoinPool import java.util.concurrent.ForkJoinTask import java.util.concurrent.locks.LockSupport @@ -80,6 +81,8 @@ object Starbound : ISBFileLocator { val thread = Thread(::universeThread, "Starbound Universe") val mailbox = MailboxExecutorService(thread) + val mailboxBootstrapped = MailboxExecutorService(thread) + val mailboxInitialized = MailboxExecutorService(thread) init { thread.isDaemon = true @@ -274,11 +277,14 @@ object Starbound : ISBFileLocator { } } - @Volatile var initializing = false private set var initialized = false private set + var bootstrapping = false + private set + var bootstrapped = false + private set @Volatile var terminateLoading = false @@ -370,29 +376,33 @@ object Starbound : ISBFileLocator { archivePaths.add(pak) } - private val initCallbacks = ArrayList<() -> Unit>() - - var playerDefinition: PlayerDefinition by WriteOnce() - private set - - private fun doInitialize(log: ILoadingLog, parallel: Boolean) { - var time = System.currentTimeMillis() - - if (archivePaths.isNotEmpty()) { - log.line("Searching for pak archives...".also(LOGGER::info)) - - for (path in archivePaths) { - val line = log.line("Reading index of ${path}...".also(LOGGER::info)) - - addPak(StarboundPak(path) { _, status -> - line.text = ("${path.parent}/${path.name}: $status") - }) - } + private fun doBootstrap() { + if (!bootstrapped && !bootstrapping) { + bootstrapping = true + } else { + return } - log.line("Finished reading pak archives in ${System.currentTimeMillis() - time}ms".also(LOGGER::info)) - time = System.currentTimeMillis() - log.line("Building file index...".also(LOGGER::info)) + for (path in archivePaths) { + addPak(StarboundPak(path)) + } + + bootstrapped = true + bootstrapping = false + + checkMailbox() + } + + private fun doInitialize(log: ILoadingLog, parallel: Boolean) { + if (!initializing && !initialized) { + initializing = true + } else { + return + } + + doBootstrap() + + val time = System.currentTimeMillis() val ext2files = fileSystems.parallelStream() .flatMap { it.explore() } @@ -428,7 +438,7 @@ object Starbound : ISBFileLocator { } }) - log.line("Finished building file index in ${System.currentTimeMillis() - time}ms".also(LOGGER::info)) + checkMailbox() val tasks = ArrayList>() val pool = if (parallel) ForkJoinPool.commonPool() else ForkJoinPool(1) @@ -437,10 +447,6 @@ object Starbound : ISBFileLocator { tasks.addAll(RecipeRegistry.load(log, ext2files, pool)) tasks.addAll(GlobalDefaults.load(log, pool)) - AssetPathStack.block("/") { - //playerDefinition = gson.fromJson(locate("/player.config").reader(), PlayerDefinition::class.java) - } - tasks.forEach { it.join() } if (!parallel) @@ -450,45 +456,64 @@ object Starbound : ISBFileLocator { initializing = false initialized = true + checkMailbox() log.line("Finished loading in ${System.currentTimeMillis() - time}ms") } fun initializeGame(log: ILoadingLog, parallel: Boolean = true) { - if (initializing) { - throw IllegalStateException("Already initializing!") - } - - if (initialized) { - throw IllegalStateException("Already initialized!") - } - - initializing = true - mailbox.submit { doInitialize(log, parallel) } } - fun onInitialize(callback: () -> Unit) { - if (initialized) { - callback() - } else { - initCallbacks.add(callback) + fun bootstrapGame() { + mailbox.submit { + doBootstrap() } } + private fun checkMailbox() { + mailbox.executeQueuedTasks() + + if (bootstrapped) + mailboxBootstrapped.executeQueuedTasks() + + if (initialized) + mailboxInitialized.executeQueuedTasks() + } + + private var fontPath: File? = null + + fun loadFont(): CompletableFuture { + val fontPath = fontPath + + if (fontPath != null) + return CompletableFuture.completedFuture(fontPath) + + return CompletableFuture.supplyAsync(Supplier { + val fontPath = Starbound.fontPath + + if (fontPath != null) + return@Supplier fontPath + + val file = locate("/hobo.ttf") + + if (!file.exists) + throw FileNotFoundException("Unable to locate font file /hobo.ttf") + else if (!file.isFile) + throw FileNotFoundException("/hobo.ttf is not a file!") + else { + val tempPath = File(System.getProperty("java.io.tmpdir"), "sb-hobo.ttf") + tempPath.writeBytes(file.read().array()) + Starbound.fontPath = tempPath + return@Supplier tempPath + } + }, mailboxBootstrapped) + } + private fun universeThread() { while (true) { - mailbox.executeQueuedTasks() - - if (initialized && initCallbacks.isNotEmpty()) { - for (callback in initCallbacks) { - callback() - } - - initCallbacks.clear() - } - + checkMailbox() LockSupport.park() } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt index 0083091f..cecebb45 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt @@ -78,11 +78,13 @@ import java.util.concurrent.ForkJoinWorkerThread import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.locks.LockSupport import java.util.concurrent.locks.ReentrantLock +import java.util.function.Consumer import java.util.function.IntConsumer import kotlin.collections.ArrayList import kotlin.concurrent.withLock import kotlin.math.absoluteValue import kotlin.math.roundToInt +import kotlin.properties.Delegates class StarboundClient : Closeable { val window: Long @@ -383,7 +385,23 @@ class StarboundClient : Closeable { } val freeType = FreeType() - val font = Font() + + var font: Font by Delegates.notNull() + private set + + private var fontInitialized = false + + init { + Starbound.loadFont().thenAcceptAsync(Consumer { + try { + font = Font(it.canonicalPath) + fontInitialized = true + } catch (err: Throwable) { + LOGGER.fatal("Unable to load font", err) + } + }, mailbox) + } + val programs = GLPrograms() init { @@ -739,6 +757,42 @@ class StarboundClient : Closeable { } private val layers = LayeredRenderer(this) + private var dotsIndex = 0 + private val dotTime = 4 + private var dotInc = 1 + + private fun renderLoadingText() { + var alpha = 1f + + 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 (line in loadingLog) { + if (line.progress in 0.01f ..< 1f) { + quadColor(RGBAColor(0f, shade / 400f * line.progress, 0f, alpha * 0.8f)) { + it.vertex(0f, font.lineHeight * -0.4f) + it.vertex(viewportWidth * line.progress, font.lineHeight * -0.4f) + it.vertex(viewportWidth * line.progress, 0f) + it.vertex(0f, 0f) + } + } + + val size = font.render(line.text, 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) { + shade -= 10 + } + } + + stack.pop() + } fun renderFrame(): Boolean { ensureSameThread() @@ -781,6 +835,71 @@ class StarboundClient : Closeable { return true } + if (!Starbound.initialized || !fontInitialized) { + executeQueuedTasks() + updateViewportParams() + + clearColor = RGBAColor.BLACK + glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) + + val min = viewportHeight.coerceAtMost(viewportWidth) + val size = min * 0.01f + + val program = programs.positionColor + val builder = program.builder + + uberShaderPrograms.forEachValid { it.viewMatrix = viewportMatrixScreen } + fontShaderPrograms.forEachValid { it.viewMatrix = viewportMatrixScreen } + + stack.clear(Matrix3f.identity()) + + program.colorMultiplier = RGBAColor.WHITE + + if (!fontInitialized) { + builder.builder.begin(GeometryType.QUADS) + + dotsIndex += dotInc + + if (dotsIndex < 0) { + dotsIndex = 1 + dotInc = 1 + } else if (dotsIndex > dotTime * 3) { + dotsIndex = dotTime * 3 - 1 + dotInc = -1 + } + + val a = if (dotsIndex / dotTime == 0) RGBAColor.SLATE_GRAY else RGBAColor.WHITE + val b = if (dotsIndex / dotTime == 1) RGBAColor.SLATE_GRAY else RGBAColor.WHITE + val c = if (dotsIndex / dotTime == 2) RGBAColor.SLATE_GRAY else RGBAColor.WHITE + + builder.builder.vertex(viewportWidth * 0.5f - size - size * 4f, viewportHeight * 0.5f - size).color(a) + builder.builder.vertex(viewportWidth * 0.5f + size - size * 4f, viewportHeight * 0.5f - size).color(a) + builder.builder.vertex(viewportWidth * 0.5f + size - size * 4f, viewportHeight * 0.5f + size).color(a) + builder.builder.vertex(viewportWidth * 0.5f - size - size * 4f, viewportHeight * 0.5f + size).color(a) + + builder.builder.vertex(viewportWidth * 0.5f - size, viewportHeight * 0.5f - size).color(b) + builder.builder.vertex(viewportWidth * 0.5f + size, viewportHeight * 0.5f - size).color(b) + builder.builder.vertex(viewportWidth * 0.5f + size, viewportHeight * 0.5f + size).color(b) + builder.builder.vertex(viewportWidth * 0.5f - size, viewportHeight * 0.5f + size).color(b) + + builder.builder.vertex(viewportWidth * 0.5f - size + size * 4f, viewportHeight * 0.5f - size).color(c) + builder.builder.vertex(viewportWidth * 0.5f + size + size * 4f, viewportHeight * 0.5f - size).color(c) + builder.builder.vertex(viewportWidth * 0.5f + size + size * 4f, viewportHeight * 0.5f + size).color(c) + builder.builder.vertex(viewportWidth * 0.5f - size + size * 4f, viewportHeight * 0.5f + size).color(c) + + program.use() + builder.upload() + builder.draw() + } else { + renderLoadingText() + } + + GLFW.glfwSwapBuffers(window) + GLFW.glfwPollEvents() + + return true + } + layers.clear() uberShaderPrograms.forEachValid { @@ -875,36 +994,7 @@ class StarboundClient : Closeable { fontShaderPrograms.forEachValid { it.viewMatrix = viewportMatrixScreen } if (System.nanoTime() - loadingLog.lastActivity <= 4_000_000_000L) { - var alpha = 1f - - 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 (line in loadingLog) { - if (line.progress in 0.01f ..< 1f) { - quadColor(RGBAColor(0f, shade / 400f * line.progress, 0f, alpha * 0.8f)) { - it.vertex(0f, font.lineHeight * -0.4f) - it.vertex(viewportWidth * line.progress, font.lineHeight * -0.4f) - it.vertex(viewportWidth * line.progress, 0f) - it.vertex(0f, 0f) - } - } - - val size = font.render(line.text, 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) { - shade -= 10 - } - } - - stack.pop() + //renderLoadingText() } stack.clear(Matrix3f.identity()) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/PlayerEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/PlayerEntity.kt index 94750c87..316beba8 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/PlayerEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/PlayerEntity.kt @@ -10,10 +10,24 @@ import ru.dbotthepony.kstarbound.world.physics.Poly class PlayerEntity(world: World<*, *>) : Entity(world) { override val movementParameters: PlayerMovementParameters = GlobalDefaults.playerMovementParameters + var isWalking = false + var isRunning = false + var isCrouching = false + var isFlying = false + var isFalling = false + var canJump = false + var isJumping = false + var isGroundMovement = false + var isLiquidMovement = false + init { GlobalDefaults.playerMovementParameters.standingPoly?.let { //hitboxes.add(it) hitboxes.add(Starbound.gson.fromJson("""[ [-0.75, -2.0], [-0.35, -2.5], [0.35, -2.5], [0.75, -2.0], [0.75, 0.65], [0.35, 1.22], [-0.35, 1.22], [-0.75, 0.65] ]""", Poly::class.java)) } } + + override fun move() { + super.move() + } }