From 566751b77b740c5063c3942e56017ef574296b9f Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Mon, 6 May 2024 02:12:01 +0700 Subject: [PATCH] Immediate dungeon placement --- .../luna/exec/DirectCallExecutor.java | 2 +- .../kstarbound/defs/dungeon/DungeonBrush.kt | 33 ++-- .../defs/dungeon/DungeonDefinition.kt | 21 ++- .../kstarbound/defs/dungeon/DungeonWorld.kt | 154 +++++++++++++++++- .../ru/dbotthepony/kstarbound/math/AABBi.kt | 8 + .../kstarbound/server/world/ServerWorld.kt | 24 ++- .../kstarbound/world/api/MutableTileState.kt | 13 ++ 7 files changed, 219 insertions(+), 36 deletions(-) diff --git a/luna/src/main/java/org/classdump/luna/exec/DirectCallExecutor.java b/luna/src/main/java/org/classdump/luna/exec/DirectCallExecutor.java index d1cb3438..bc165482 100644 --- a/luna/src/main/java/org/classdump/luna/exec/DirectCallExecutor.java +++ b/luna/src/main/java/org/classdump/luna/exec/DirectCallExecutor.java @@ -259,7 +259,7 @@ public class DirectCallExecutor { */ public Object[] resume(Continuation continuation) throws CallException, CallPausedException, InterruptedException { - return execute(continuation, schedulingContextFactory.newInstance(), performJavaConversions); + return execute(continuation, schedulingContextFactory.newInstance(), false); } private static class Result implements CallEventHandler { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonBrush.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonBrush.kt index da3abfeb..7f0239b6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonBrush.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonBrush.kt @@ -29,6 +29,8 @@ import ru.dbotthepony.kstarbound.json.builder.JsonIgnore import ru.dbotthepony.kstarbound.util.random.random import ru.dbotthepony.kstarbound.world.Direction import ru.dbotthepony.kstarbound.world.api.AbstractLiquidState +import ru.dbotthepony.kstarbound.world.api.AbstractTileState +import ru.dbotthepony.kstarbound.world.api.ImmutableTileState import ru.dbotthepony.kstarbound.world.api.MutableLiquidState import ru.dbotthepony.kstarbound.world.api.TileColor @@ -90,13 +92,7 @@ abstract class DungeonBrush { return // TODO: delete objects too? - world.setLiquid(x, y, AbstractLiquidState.EMPTY) - world.setForeground(x, y, BuiltinMetaMaterials.EMPTY) - world.setForeground(x, y, BuiltinMetaMaterials.EMPTY_MOD) - world.setBackground(x, y, BuiltinMetaMaterials.EMPTY) - world.setBackground(x, y, BuiltinMetaMaterials.EMPTY_MOD) - - world.setDungeonID(x, y) + world.clearTile(x, y) } override fun toString(): String { @@ -145,24 +141,19 @@ abstract class DungeonBrush { val colorVariant: String = "0", // HOLY FUCKING SHIT ) + private val state by lazy { + ImmutableTileState(material.orEmptyTile, modifier.orEmptyModifier, color, hueShift, modHueShift) + } + override fun execute(x: Int, y: Int, phase: Phase, world: DungeonWorld) { if (phase !== Phase.PLACE_WALLS) return - if (isBackground) - world.setBackground(x, y, material.orEmptyTile, hueShift, color) - else - world.setForeground(x, y, material.orEmptyTile, hueShift, color) - - if (!isBackground && material.orEmptyTile.value.collisionKind.isSolidCollision) { - world.setLiquid(x, y, AbstractLiquidState.EMPTY) - } - - if (modifier.isRealModifier) { - if (isBackground) - world.setBackground(x, y, modifier.orEmptyModifier, modHueShift) - else - world.setForeground(x, y, modifier.orEmptyModifier, modHueShift) + if (isBackground) { + world.setCombined(x, y, background = state, foreground = null, liquid = null, setBackgroundModifier = modifier.isRealModifier) + } else { + val liquid = if (material.orEmptyTile.value.collisionKind.isSolidCollision) AbstractLiquidState.EMPTY else null + world.setCombined(x, y, background = null, foreground = state, liquid = liquid, setForegroundModifier = modifier.isRealModifier) } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonDefinition.kt index 8575d147..d6f00975 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonDefinition.kt @@ -220,10 +220,11 @@ data class DungeonDefinition( dungeonID: Int = 0, terrainSurfaceSpaceExtends: Int = 0, commit: Boolean = true, - scope: CoroutineScope = Starbound.GLOBAL_SCOPE + scope: CoroutineScope = Starbound.GLOBAL_SCOPE, + immediate: Boolean = false, ): CompletableFuture { require(dungeonID in 0 .. NO_DUNGEON_ID) { "Dungeon ID out of range: $dungeonID" } - val dungeonWorld = DungeonWorld(world, random, if (markSurfaceAndTerrain) y else null, terrainSurfaceSpaceExtends, expectedSize = expectedSize) + val dungeonWorld = DungeonWorld(world, random, if (markSurfaceAndTerrain) y else null, terrainSurfaceSpaceExtends, expectedSize = expectedSize, immediate = immediate) val validAnchors = anchorParts.filter { world.template.threatLevel in it.minimumThreatLevel .. it.maximumThreatLevel } @@ -247,9 +248,21 @@ data class DungeonDefinition( }.asCompletableFuture() } - fun build(anchor: DungeonPart, world: ServerWorld, random: RandomGenerator, x: Int, y: Int, dungeonID: Int = NO_DUNGEON_ID, markSurfaceAndTerrain: Boolean = false, forcePlacement: Boolean = false, terrainSurfaceSpaceExtends: Int = 0, commit: Boolean = true, scope: CoroutineScope = Starbound.GLOBAL_SCOPE): CompletableFuture { + fun build( + anchor: DungeonPart, + world: ServerWorld, + random: RandomGenerator, + x: Int, y: Int, + dungeonID: Int = NO_DUNGEON_ID, + markSurfaceAndTerrain: Boolean = false, + forcePlacement: Boolean = false, + terrainSurfaceSpaceExtends: Int = 0, + commit: Boolean = true, + scope: CoroutineScope = Starbound.GLOBAL_SCOPE, + immediate: Boolean = false + ): CompletableFuture { require(anchor in anchorParts) { "$anchor does not belong to $name" } - val dungeonWorld = DungeonWorld(world, random, if (markSurfaceAndTerrain) y else null, terrainSurfaceSpaceExtends, expectedSize = expectedSize) + val dungeonWorld = DungeonWorld(world, random, if (markSurfaceAndTerrain) y else null, terrainSurfaceSpaceExtends, expectedSize = expectedSize, immediate = immediate) return scope.async { generate0(anchor, dungeonWorld, x, y, forcePlacement, dungeonID) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonWorld.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonWorld.kt index ac4610ef..4747b777 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonWorld.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonWorld.kt @@ -27,7 +27,9 @@ import ru.dbotthepony.kstarbound.util.random.random import ru.dbotthepony.kstarbound.world.ChunkPos import ru.dbotthepony.kstarbound.world.ChunkState import ru.dbotthepony.kstarbound.world.Direction +import ru.dbotthepony.kstarbound.world.api.AbstractCell import ru.dbotthepony.kstarbound.world.api.AbstractLiquidState +import ru.dbotthepony.kstarbound.world.api.AbstractTileState import ru.dbotthepony.kstarbound.world.api.MutableTileState import ru.dbotthepony.kstarbound.world.api.TileColor import ru.dbotthepony.kstarbound.world.entities.tile.TileEntity @@ -45,7 +47,20 @@ import kotlin.math.max // Facade world for generating dungeons, so generation can be performed without affecting world state, // and if error occurs, won't require world's rollback, as well allowing dungeon to be generated mostly // off world's thread. -class DungeonWorld(val parent: ServerWorld, val random: RandomGenerator, val markSurfaceLevel: Int? = null, val terrainSurfaceSpaceExtends: Int = 0, expectedSize: Int = 0) { +class DungeonWorld( + val parent: ServerWorld, + val random: RandomGenerator, + val markSurfaceLevel: Int? = null, + val terrainSurfaceSpaceExtends: Int = 0, + expectedSize: Int = 0, + + /** + * Whenever tile changes should be passthrough, if possible + * (improved generation speed and memory usage, but expects world chunks to be already + * loaded into memory AND no concurrent chunkmap operations are performed) + */ + val immediate: Boolean = false, +) { val geometry = parent.geometry data class Material( @@ -81,11 +96,11 @@ class DungeonWorld(val parent: ServerWorld, val random: RandomGenerator, val mar val targetChunkState = if (parent.template.worldParameters is FloatingDungeonWorldParameters) ChunkState.FULL else ChunkState.TERRAIN - private val liquid = HashMap(max(8192, expectedSize), 0.5f) - private val foregroundMaterial = HashMap(max(8192, expectedSize), 0.5f) - private val foregroundModifier = HashMap(max(8192, expectedSize), 0.5f) - private val backgroundMaterial = HashMap(max(8192, expectedSize), 0.5f) - private val backgroundModifier = HashMap(max(8192, expectedSize), 0.5f) + private val liquid = HashMap(if (immediate) 2048 else max(8192, expectedSize), 0.5f) + private val foregroundMaterial = HashMap(if (immediate) 2048 else max(8192, expectedSize), 0.5f) + private val foregroundModifier = HashMap(if (immediate) 2048 else max(8192, expectedSize), 0.5f) + private val backgroundMaterial = HashMap(if (immediate) 2048 else max(8192, expectedSize), 0.5f) + private val backgroundModifier = HashMap(if (immediate) 2048 else max(8192, expectedSize), 0.5f) // for entity spaces which should be considered empty if they // are occupied by tile entity @@ -117,8 +132,6 @@ class DungeonWorld(val parent: ServerWorld, val random: RandomGenerator, val mar private var currentBoundingBox: AABBi? = null - fun touched(): Set = Collections.unmodifiableSet(touchedTiles) - fun touch(x: Int, y: Int) { val wrapped = geometry.wrap(Vector2i(x, y)) touchedTiles.add(wrapped) @@ -260,6 +273,131 @@ class DungeonWorld(val parent: ServerWorld, val random: RandomGenerator, val mar setBackground(x, y, Modifier(modifier, hueShift)) } + private val emptyMaterial = Material(AbstractTileState.EMPTY.material, AbstractTileState.EMPTY.hueShift, AbstractTileState.EMPTY.color) + private val emptyModifier = Modifier(AbstractTileState.EMPTY.modifier, AbstractTileState.EMPTY.modifierHueShift) + + private fun setCombinedFallback( + x: Int, y: Int, + foreground: AbstractTileState?, + background: AbstractTileState?, + liquid: AbstractLiquidState?, + setForegroundMaterial: Boolean, + setForegroundModifier: Boolean, + setBackgroundMaterial: Boolean, + setBackgroundModifier: Boolean, + ) { + val pos = geometry.wrap(Vector2i(x, y)) + + if (foreground != null) { + if (setForegroundMaterial) + this.foregroundMaterial[pos] = Material(foreground.material, foreground.hueShift, foreground.color) + + if (setForegroundModifier) + this.foregroundModifier[pos] = Modifier(foreground.modifier, foreground.modifierHueShift) + } + + if (background != null) { + if (setBackgroundMaterial) + this.backgroundMaterial[pos] = Material(background.material, background.hueShift, background.color) + + if (setBackgroundModifier) + this.backgroundModifier[pos] = Modifier(background.modifier, background.modifierHueShift) + } + + if (liquid != null) this.liquid[pos] = liquid + } + + fun setCombined( + x: Int, y: Int, + foreground: AbstractTileState?, + background: AbstractTileState?, + liquid: AbstractLiquidState?, + dungeonId: Int? = null, + setForegroundMaterial: Boolean = true, + setForegroundModifier: Boolean = true, + setBackgroundMaterial: Boolean = true, + setBackgroundModifier: Boolean = true, + ) { + if (immediate) { + val cell = parent.getCell(x, y).mutable() + if (foreground != null) cell.foreground.from(foreground, setForegroundMaterial, setForegroundModifier) + if (background != null) cell.background.from(background, setBackgroundMaterial, setBackgroundModifier) + if (liquid != null) cell.liquid.from(liquid) + + cell.dungeonId = dungeonId ?: cell.dungeonId + + if (!parent.setCell(x, y, cell.immutable())) { + LOGGER.debug("Cell at $x, $y is out of preloaded bounds for immediate dungeon generator... While this won't affect dungeon generation, it will degrade performance and signify there is a bug somewhere.") + + setCombinedFallback( + x = x, + y = y, + foreground = foreground, + background = background, + liquid = liquid, + setForegroundMaterial = setForegroundMaterial, + setForegroundModifier = setForegroundModifier, + setBackgroundMaterial = setBackgroundMaterial, + setBackgroundModifier = setBackgroundModifier, + ) + } else { + val pos = geometry.wrap(Vector2i(x, y)) + + if (foreground != null) { + if (setForegroundMaterial) this.foregroundMaterial.remove(pos) + if (setForegroundModifier) this.foregroundModifier.remove(pos) + } + + if (background != null) { + if (setBackgroundMaterial) this.backgroundMaterial.remove(pos) + if (setBackgroundModifier) this.backgroundModifier.remove(pos) + } + + if (liquid != null) this.liquid.remove(pos) + } + } else { + setCombinedFallback( + x = x, + y = y, + foreground = foreground, + background = background, + liquid = liquid, + setForegroundMaterial = setForegroundMaterial, + setForegroundModifier = setForegroundModifier, + setBackgroundMaterial = setBackgroundMaterial, + setBackgroundModifier = setBackgroundModifier, + ) + } + } + + fun clearTile(x: Int, y: Int) { + val pos = geometry.wrap(Vector2i(x, y)) + + if (immediate) { + if (parent.setCell(x, y, AbstractCell.EMPTY)) { + this.foregroundMaterial.remove(pos) + this.foregroundModifier.remove(pos) + this.backgroundMaterial.remove(pos) + this.backgroundModifier.remove(pos) + this.liquid.remove(pos) + } else { + this.foregroundMaterial[pos] = emptyMaterial + this.foregroundModifier[pos] = emptyModifier + this.backgroundMaterial[pos] = emptyMaterial + this.backgroundModifier[pos] = emptyModifier + this.liquid[pos] = AbstractLiquidState.EMPTY + } + } else { + this.foregroundMaterial[pos] = emptyMaterial + this.foregroundModifier[pos] = emptyModifier + this.backgroundMaterial[pos] = emptyMaterial + this.backgroundModifier[pos] = emptyModifier + this.liquid[pos] = AbstractLiquidState.EMPTY + } + + dungeonIDs.remove(pos) + } + fun needsForegroundBiomeMod(x: Int, y: Int): Boolean { val pos = geometry.wrap(Vector2i(x, y)) val material = foregroundMaterial[pos] ?: return false diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/math/AABBi.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/math/AABBi.kt index 897f17b8..9a42d6e7 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/math/AABBi.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/math/AABBi.kt @@ -142,6 +142,14 @@ data class AABBi(val mins: Vector2i, val maxs: Vector2i) { ) } + /** + * Rectangle with given [width] * 2 and [height] * 2 + */ + fun withSide(pos: IStruct2i, size: IStruct2i): AABBi { + val (width, height) = size + return withSide(pos, width, height) + } + @JvmField val ZERO = AABBi(Vector2i.ZERO, Vector2i.ZERO) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt index bf6bc243..0a8d65d3 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt @@ -565,13 +565,33 @@ class ServerWorld private constructor( x = geometry.x.cell(x) - LOGGER.info("Trying to place dungeon ${dungeon.dungeon.key} at $x, ${dungeon.baseHeight}...") + val tickets = ArrayList() val dungeonWorld = try { - dungeon.dungeon.value.generate(this, random(random.nextLong()), x, dungeon.baseHeight, dungeon.blendWithTerrain, dungeon.forcePlacement, dungeonID = currentDungeonID).await() + // if immediate placement will cause issues it can be safely toggled off + var immediate = false + + if (dungeon.dungeon.value.anchorParts.size == 1 && dungeon.dungeon.value.partMap.size == 1) { + immediate = true + LOGGER.info("Trying to immediately place dungeon ${dungeon.dungeon.key} at $x, ${dungeon.baseHeight}...") + tickets.addAll(permanentChunkTicket(AABBi.withSide(Vector2i(x, dungeon.baseHeight), dungeon.dungeon.value.anchorParts.first().reader.size), ChunkState.TERRAIN).await()) + tickets.forEach { it.chunk.await() } + } else { + LOGGER.info("Trying to place dungeon ${dungeon.dungeon.key} at $x, ${dungeon.baseHeight}...") + } + + dungeon.dungeon.value.generate( + this, + random(random.nextLong()), + x, dungeon.baseHeight, + dungeon.blendWithTerrain, + dungeon.forcePlacement, + dungeonID = currentDungeonID, + immediate = immediate).await() } catch (err: Throwable) { LOGGER.error("Exception while placing dungeon ${dungeon.dungeon.key} at $x, ${dungeon.baseHeight}", err) // continue + tickets.forEach { it.cancel() } break } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/MutableTileState.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/MutableTileState.kt index 871d3740..f0a491c5 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/MutableTileState.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/MutableTileState.kt @@ -39,6 +39,19 @@ data class MutableTileState( modifierHueShift = 0f } + fun from(other: AbstractTileState, setMaterial: Boolean = true, setModifer: Boolean = true) { + if (setMaterial) { + material = other.material + modifier = other.modifier + color = other.color + } + + if (setModifer) { + hueShift = other.hueShift + modifierHueShift = other.modifierHueShift + } + } + fun setHueShift(value: Int): MutableTileState { if (value < 0) { hueShift = 0f