From 161d19f263b2dfa2a94d24997cf288ae6833d0a3 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Sun, 5 May 2024 22:53:31 +0700 Subject: [PATCH] Little faster big dungeon placement --- .../defs/dungeon/DungeonDefinition.kt | 10 ++++++++-- .../kstarbound/defs/dungeon/DungeonWorld.kt | 17 +++++++++-------- 2 files changed, 17 insertions(+), 10 deletions(-) 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 80a9e81d..8575d147 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonDefinition.kt @@ -21,6 +21,8 @@ import ru.dbotthepony.kstarbound.util.AssetPathStack import ru.dbotthepony.kstarbound.util.random.random import java.util.concurrent.CompletableFuture import java.util.random.RandomGenerator +import kotlin.math.roundToInt +import kotlin.math.sqrt // Dungeons in Starbound are separated into two categories: // A. Dungeons described using specific tileset (palette, defined through JSON) and corresponding image maps (chunks) @@ -98,6 +100,10 @@ data class DungeonDefinition( metadata.anchor.stream().map { anchor -> actualParts.first { it.name == anchor } }.collect(ImmutableList.toImmutableList()) } + private val expectedSize by lazy { + (partMap.values.sumOf { it.reader.size.x.toDouble() * it.reader.size.y.toDouble() } / sqrt(partMap.size.toDouble())).roundToInt() + } + private fun connectableParts(connector: DungeonPart.JigsawConnector): List { val result = ArrayList() @@ -217,7 +223,7 @@ data class DungeonDefinition( scope: CoroutineScope = Starbound.GLOBAL_SCOPE ): 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) + val dungeonWorld = DungeonWorld(world, random, if (markSurfaceAndTerrain) y else null, terrainSurfaceSpaceExtends, expectedSize = expectedSize) val validAnchors = anchorParts.filter { world.template.threatLevel in it.minimumThreatLevel .. it.maximumThreatLevel } @@ -243,7 +249,7 @@ data class DungeonDefinition( 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 { require(anchor in anchorParts) { "$anchor does not belong to $name" } - val dungeonWorld = DungeonWorld(world, random, if (markSurfaceAndTerrain) y else null, terrainSurfaceSpaceExtends) + val dungeonWorld = DungeonWorld(world, random, if (markSurfaceAndTerrain) y else null, terrainSurfaceSpaceExtends, expectedSize = expectedSize) 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 183bb5e6..ac4610ef 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonWorld.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonWorld.kt @@ -40,11 +40,12 @@ import java.util.concurrent.CompletableFuture import java.util.function.Consumer import java.util.function.Supplier import java.util.random.RandomGenerator +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) { +class DungeonWorld(val parent: ServerWorld, val random: RandomGenerator, val markSurfaceLevel: Int? = null, val terrainSurfaceSpaceExtends: Int = 0, expectedSize: Int = 0) { val geometry = parent.geometry data class Material( @@ -80,11 +81,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(8192, 0.5f) - private val foregroundMaterial = HashMap(8192, 0.5f) - private val foregroundModifier = HashMap(8192, 0.5f) - private val backgroundMaterial = HashMap(8192, 0.5f) - private val backgroundModifier = HashMap(8192, 0.5f) + 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) // for entity spaces which should be considered empty if they // are occupied by tile entity @@ -93,8 +94,8 @@ class DungeonWorld(val parent: ServerWorld, val random: RandomGenerator, val mar // entities themselves to be removed private val tileEntitiesToRemove = HashSet(2048, 0.5f) - val touchedTiles = HashSet(16384, 0.5f) - val protectTile = HashSet(16384, 0.5f) + val touchedTiles = HashSet(max(16384, expectedSize), 0.5f) + val protectTile = HashSet(max(16384, expectedSize), 0.5f) private val boundingBoxes = ArrayList()