Browse Source

various performance fixes

master
James Fenn 5 months ago
parent
commit
5925ccb382
16 changed files with 433 additions and 54 deletions
  1. +3
    -0
      build.gradle
  2. +2
    -2
      dungeon/build.gradle
  3. +1
    -1
      dungeon/src/commonMain/kotlin/me/jfenn/dungeon/api/env/Config.kt
  4. +3
    -3
      dungeon/src/commonMain/kotlin/me/jfenn/dungeon/api/graphics/GraphicsManager.kt
  5. +12
    -12
      dungeon/src/commonMain/kotlin/me/jfenn/dungeon/api/graphics/shader/ExhaustiveShader.kt
  6. +5
    -5
      dungeon/src/commonMain/kotlin/me/jfenn/dungeon/api/graphics/shader/Shader.kt
  7. +2
    -0
      dungeon/src/commonMain/kotlin/me/jfenn/dungeon/api/view/ViewEventListener.kt
  8. +2
    -2
      dungeon/src/commonMain/kotlin/me/jfenn/dungeon/core/graphics/Luminance.kt
  9. +2
    -2
      dungeon/src/commonMain/kotlin/me/jfenn/dungeon/core/spatial/SpatialCollection.kt
  10. +23
    -27
      dungeon/src/commonMain/kotlin/me/jfenn/dungeon/core/spatial/SpatialMap.kt
  11. +141
    -0
      dungeon/src/commonMain/kotlin/me/jfenn/dungeon/core/spatial/SpatialMapChunked.kt
  12. +153
    -0
      dungeon/src/commonMain/kotlin/me/jfenn/dungeon/core/spatial/SpatialMapFast.kt
  13. +32
    -0
      dungeon/src/commonMain/kotlin/me/jfenn/dungeon/core/vector/Vector.kt
  14. +44
    -0
      dungeon/src/commonMain/kotlin/me/jfenn/dungeon/core/vector/area/LineArea.kt
  15. +4
    -0
      dungeon/src/jvmMain/kotlin/me/jfenn/dungeon/api/view/JDungeonCanvas.kt
  16. +4
    -0
      example-swing/src/main/java/me/jfenn/example/dungeonswing/snake/Background.kt

+ 3
- 0
build.gradle View File

@@ -5,6 +5,8 @@ buildscript {
ext.coroutines_version = '1.4.2'
ext.serialization_version = '0.20.0'

ext.kds_version = '2.0.6'

repositories {
google()
jcenter()
@@ -28,6 +30,7 @@ allprojects {
google()
jcenter()
maven { url 'https://jitpack.io' }
maven { url 'https://dl.bintray.com/korlibs/korlibs' }
}
}



+ 2
- 2
dungeon/build.gradle View File

@@ -19,7 +19,7 @@ kotlin {
dependencies {
implementation kotlin('stdlib-common')
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation 'com.github.romainguy:kotlin-math:02ca2b7f0c'
implementation "com.soywiz.korlibs.kds:kds:$kds_version"
}
}

@@ -33,7 +33,7 @@ kotlin {
jvmMain {
dependencies {
implementation kotlin('stdlib-jdk8')
implementation 'org.jogamp.jogl:jogl-all:2.3.2'
implementation "com.soywiz.korlibs.kds:kds-jvm:$kds_version"
}
}



+ 1
- 1
dungeon/src/commonMain/kotlin/me/jfenn/dungeon/api/env/Config.kt View File

@@ -19,7 +19,7 @@ class Config {
var ambientLight: Int = -0x1000000
var shaders: List<Shader> = ArrayList()
var fullscreen: Boolean = true
var fps: Int = 15
var fps: Int = 30

var currentWidth: Int = 0
var currentHeight: Int = 0


+ 3
- 3
dungeon/src/commonMain/kotlin/me/jfenn/dungeon/api/graphics/GraphicsManager.kt View File

@@ -6,7 +6,7 @@ import me.jfenn.dungeon.api.timing.currentTimeMillis
import me.jfenn.dungeon.core.extensions.multiply
import me.jfenn.dungeon.core.graphics.Luminance
import me.jfenn.dungeon.core.graphics.pixel.Pixel
import me.jfenn.dungeon.core.spatial.SpatialMap
import me.jfenn.dungeon.core.spatial.*

class GraphicsManager(
val env: Config
@@ -14,7 +14,7 @@ class GraphicsManager(

val sprites = ArrayList<Sprite>()

val assembledPixels = SpatialMap<Pixel>()
val assembledPixels = SpatialMapChunked<Pixel>()
val assembledLights = SpatialMap<Luminance>()

var renderedPixels = SpatialMap<Int>()
@@ -29,7 +29,7 @@ class GraphicsManager(
sprites.remove(sprite)
}

suspend fun render() : SpatialMap<Int> {
suspend fun render() : SpatialCollection<Int> {
assembledPixels.clear()
assembledLights.clear()



+ 12
- 12
dungeon/src/commonMain/kotlin/me/jfenn/dungeon/api/graphics/shader/ExhaustiveShader.kt View File

@@ -5,29 +5,29 @@ import me.jfenn.dungeon.core.extensions.alpha
import me.jfenn.dungeon.core.extensions.set
import me.jfenn.dungeon.core.graphics.Luminance
import me.jfenn.dungeon.core.graphics.pixel.Pixel
import me.jfenn.dungeon.core.spatial.SpatialMap
import me.jfenn.dungeon.core.spatial.SpatialCollection
import me.jfenn.dungeon.core.spatial.SpatialMapFast
import me.jfenn.dungeon.core.vector.IntVector
import me.jfenn.dungeon.core.vector.area.forLine
import me.jfenn.dungeon.core.vector.area.LineIterator

class ExhaustiveShader(env: Config) : Shader(env) {

override suspend fun drawLightMap(position: IntVector, light: Luminance, pixels: SpatialMap<Pixel>) {
val visited = SpatialMap<Boolean>()
override suspend fun drawLightMap(position: IntVector, light: Luminance, pixels: SpatialCollection<Pixel>) {
val visited = SpatialMapFast<Boolean>()

for (pixelPos in pixels.keys()) {
if (pixelPos.z != position.z || position.distanceTo(pixelPos) > light.getAffectedRadius() || visited[pixelPos] == true)
continue
pixels.forEachRadius(position, light.getAffectedRadius()) { pixelPos, _ ->
if (visited[pixelPos] == true)
return@forEachRadius

var intensity = 1f // start at light source center

// trace line from light source to pixel
forLine(position, pixelPos) { vector ->
val pixel = pixels[vector] ?: return@forLine

//if (intensity < 0.05f) break
for (vector in LineIterator(position, pixelPos)) {
val pixel = pixels[vector] ?: continue
if (intensity < 0.1f) break

// calculate pixel color & insert into list
if (intensity > 0.01f && visited[vector] != true) {
if (visited[vector] != true) {
val color = light.getColor(position.distanceTo(vector).toFloat())
addLight(vector, color.set(alpha = color.alpha() * intensity))
visited[vector] = true


+ 5
- 5
dungeon/src/commonMain/kotlin/me/jfenn/dungeon/api/graphics/shader/Shader.kt View File

@@ -4,18 +4,18 @@ import kotlinx.coroutines.*
import me.jfenn.dungeon.api.env.Config
import me.jfenn.dungeon.core.graphics.Luminance
import me.jfenn.dungeon.core.graphics.pixel.Pixel
import me.jfenn.dungeon.core.spatial.SpatialMap
import me.jfenn.dungeon.core.spatial.SpatialCollection
import me.jfenn.dungeon.core.spatial.SpatialMapFast
import me.jfenn.dungeon.core.vector.IntVector
import kotlin.coroutines.CoroutineContext
import kotlin.jvm.Synchronized

abstract class Shader(
val env: Config
) {

val lightMap = SpatialMap<ArrayList<Int>>()
val lightMap = SpatialMapFast<ArrayList<Int>>()

abstract suspend fun drawLightMap(position: IntVector, light: Luminance, pixels: SpatialMap<Pixel>)
abstract suspend fun drawLightMap(position: IntVector, light: Luminance, pixels: SpatialCollection<Pixel>)

@Synchronized
fun addLight(position: IntVector, light: Int) {
@@ -27,7 +27,7 @@ abstract class Shader(

private val deferred = ArrayList<Deferred<Unit>>()

suspend fun draw(lights: SpatialMap<Luminance>, pixels: SpatialMap<Pixel>) {
suspend fun draw(lights: SpatialCollection<Luminance>, pixels: SpatialCollection<Pixel>) {
lightMap.clear()

lights.forEach { position, light ->


+ 2
- 0
dungeon/src/commonMain/kotlin/me/jfenn/dungeon/api/view/ViewEventListener.kt View File

@@ -12,4 +12,6 @@ interface ViewEventListener {

fun onKeyUp(keyCode: Int) {}

fun onWindowResized(width: Int, height: Int) {}

}

+ 2
- 2
dungeon/src/commonMain/kotlin/me/jfenn/dungeon/core/graphics/Luminance.kt View File

@@ -16,8 +16,8 @@ data class Luminance(
return color.set(alpha = color.alpha() * (1 - ratio).coerceIn(0f, 1f))
}

fun getAffectedRadius(): Int {
return ceil(1.0 / degredation).toInt()
fun getAffectedRadius(): Float {
return ceil(1.0 / degredation).toFloat()
}

}

+ 2
- 2
dungeon/src/commonMain/kotlin/me/jfenn/dungeon/core/spatial/SpatialCollection.kt View File

@@ -19,10 +19,10 @@ interface SpatialCollection<T>: Collection<T>, SpatialObject {

fun clone(): SpatialCollection<T>

fun area(area: Area<IntVector>): SpatialCollection<T>

fun forEach(block: (IntVector, T) -> Unit)

fun forEachRadius(center: IntVector, radius: Float, block: (IntVector, T) -> Unit)

fun keys(): List<IntVector>

}


+ 23
- 27
dungeon/src/commonMain/kotlin/me/jfenn/dungeon/core/spatial/SpatialMap.kt View File

@@ -1,8 +1,8 @@
package me.jfenn.dungeon.core.spatial

import me.jfenn.dungeon.core.extensions.vectorOf
import me.jfenn.dungeon.core.vector.IntVector
import me.jfenn.dungeon.core.vector.MutableIntVector
import me.jfenn.dungeon.core.vector.area.Area
import kotlin.collections.HashMap

class SpatialMap<T>(): SpatialCollection<T> {
@@ -23,21 +23,6 @@ class SpatialMap<T>(): SpatialCollection<T> {
}
}

/**
* Get a subset of the current map that falls inside
* of a given area.
*
* @param area The area that objects must be inside.
* @return A new SpatialMap containing the objects.
*/
override fun area(area: Area<IntVector>): SpatialMap<T> {
val subset = SpatialMap<T>()
for (vector in area)
map[vector.x]?.get(vector.y)?.get(vector.z)?.let { subset[vector] = it }

return subset
}

/**
* Returns the size of the collection.
*/
@@ -63,18 +48,18 @@ class SpatialMap<T>(): SpatialCollection<T> {
size--

if (value != null) {
((map[position.x] ?: run {
HashMap<Int, MutableMap<Int, T>>().also { map[position.x] = it }
((map[position.z] ?: run {
HashMap<Int, MutableMap<Int, T>>().also { map[position.z] = it }
})[position.y] ?: run {
HashMap<Int, T>().also { map[position.x]?.set(position.y, it) }
})[position.z] = value
HashMap<Int, T>().also { map[position.z]?.set(position.y, it) }
})[position.x] = value
} else {
map[position.x]?.get(position.y)?.remove(position.z)
map[position.z]?.get(position.y)?.remove(position.x)
}
}

override operator fun get(position: IntVector): T? {
return map[position.x]?.get(position.y)?.get(position.z)
return map[position.z]?.get(position.y)?.get(position.x)
}

override fun clear() {
@@ -122,15 +107,26 @@ class SpatialMap<T>(): SpatialCollection<T> {
}

override fun forEach(block: (IntVector, T) -> Unit) {
map.forEach { (x, yMap) ->
yMap.forEach { (y, zMap) ->
zMap.forEach { (z, value) ->
map.forEach { (z, yMap) ->
yMap.forEach { (y, xMap) ->
xMap.forEach { (x, value) ->
block(MutableIntVector(x, y, z), value)
}
}
}
}

override fun forEachRadius(center: IntVector, radius: Float, block: (IntVector, T) -> Unit) {
map.forEach { (z, yMap) ->
yMap.forEach { (y, xMap) ->
xMap.forEach { (x, value) ->
if (vectorOf(x, y, z).distanceTo(center) < radius)
block(MutableIntVector(x, y, z), value)
}
}
}
}

override fun keys(): List<IntVector> {
val list = ArrayList<IntVector>(this.size)
this.forEach { i, _ ->
@@ -145,8 +141,8 @@ class SpatialMap<T>(): SpatialCollection<T> {

val iterator = ArrayList<T>().apply {
for (yMap in map.values) {
for (zMap in yMap.values) {
for (value in zMap.values) {
for (xMap in yMap.values) {
for (value in xMap.values) {
add(value)
}
}


+ 141
- 0
dungeon/src/commonMain/kotlin/me/jfenn/dungeon/core/spatial/SpatialMapChunked.kt View File

@@ -0,0 +1,141 @@
package me.jfenn.dungeon.core.spatial

import me.jfenn.dungeon.core.extensions.vectorOf
import me.jfenn.dungeon.core.vector.IntVector
import me.jfenn.dungeon.core.vector.MutableIntVector
import me.jfenn.dungeon.core.vector.area.Area
import kotlin.collections.HashMap

class SpatialMapChunked<T>(
private val chunkSize: Int = 8
): SpatialCollection<T> {

private val map = SpatialMapFast<SpatialMapFast<T>>()

override val position: MutableIntVector
get() = map.position
override val area: MutableIntVector
get() = map.area * chunkSize

constructor(map: Map<IntVector, T>): this() {
for ((k, v) in map)
set(k, v)
}

constructor(collection: SpatialCollection<T>): this() {
collection.forEach { position, item ->
this[position] = item
}
}

/**
* Returns the size of the collection.
*/
override var size: Int = 0

override operator fun set(position: IntVector, value: T?) {
val chunkPosition: IntVector = position / chunkSize
(map[chunkPosition] ?: run {
SpatialMapFast<T>().also { map[chunkPosition] = it }
})[position] = value
}

override operator fun get(position: IntVector): T? {
return map[position / chunkSize]?.get(position)
}

override fun clear() {
size = 0
map.clear()
}

override fun clone(): SpatialCollection<T> {
return SpatialMapChunked(this)
}

/**
* Checks if the specified element is contained in this collection.
*/
override operator fun contains(element: T): Boolean {
for (x in this) {
if (x == element)
return true
}

return false
}

/**
* Checks if all elements in the specified collection are contained in this collection.
*/
override fun containsAll(elements: Collection<T>): Boolean {
for (spatial in elements) {
if (!contains(spatial))
return false
}

return true
}

/**
* Returns `true` if the collection is empty (contains no elements), `false` otherwise.
*/
override fun isEmpty(): Boolean {
return size == 0
}

override fun iterator(): Iterator<T> {
return ChunkIterator(map)
}

override fun forEach(block: (IntVector, T) -> Unit) {
map.forEach { _, chunk ->
chunk.forEach(block)
}
}

override fun forEachRadius(center: IntVector, radius: Float, block: (IntVector, T) -> Unit) {
map.forEach { position, chunk ->
val chunkPosition: IntVector = position * chunkSize
if (center.distanceTo(chunkPosition + (chunkSize / 2)) < radius + (chunkSize * .75))
chunk.forEachRadius(center, radius, block)
}
}

override fun keys(): List<IntVector> {
val list = ArrayList<IntVector>(this.size)
this.forEach { i, _ ->
list.add(i)
}
return list
}

class ChunkIterator<T>(
map: SpatialMapFast<SpatialMapFast<T>>
): Iterator<T> {

val iterator = ArrayList<T>().apply {
for (chunk in map) {
for (item in chunk) {
add(item)
}
}
}.iterator()

/**
* Returns `true` if the iteration has more elements.
*/
override fun hasNext(): Boolean {
return iterator.hasNext()
}

/**
* Returns the next element in the iteration.
*/
override fun next(): T {
return iterator.next()
}

}

}

+ 153
- 0
dungeon/src/commonMain/kotlin/me/jfenn/dungeon/core/spatial/SpatialMapFast.kt View File

@@ -0,0 +1,153 @@
package me.jfenn.dungeon.core.spatial

import com.soywiz.kds.*
import me.jfenn.dungeon.core.extensions.vectorOf
import me.jfenn.dungeon.core.vector.IntVector
import me.jfenn.dungeon.core.vector.MutableIntVector
import kotlin.collections.HashMap

class SpatialMapFast<T>(): SpatialCollection<T> {

override val position: MutableIntVector = MutableIntVector(0, 0, 0)
override val area: MutableIntVector = MutableIntVector(0, 0, 0)

private val map: FastIntMap<Node<T>> = FastIntMap()

constructor(map: Map<IntVector, T>): this() {
for ((k, v) in map)
set(k, v)
}

constructor(collection: SpatialCollection<T>): this() {
collection.forEach { position, item ->
this[position] = item
}
}

/**
* Returns the size of the collection.
*/
override val size: Int
get() = map.size

override operator fun set(position: IntVector, value: T?) {
if (position.x < this.position.x)
this.position.x = position.x
if (position.y < this.position.y)
this.position.y = position.y
if (position.z < this.position.z)
this.position.z = position.z
if (this.area.x < (position.x - this.position.x))
this.area.x = (position.x - this.position.x)
if (this.area.y < (position.y - this.position.y))
this.area.y = (position.y - this.position.y)
if (this.area.z < (position.z - this.position.z))
this.area.z = (position.z - this.position.z)

if (value != null) {
map[position.hashCode()] = Node(position, value)
} else {
map.remove(position.hashCode())
}
}

override operator fun get(position: IntVector): T? {
return map[position.hashCode()]?.value
}

override fun clear() {
map.clear()
}

override fun clone(): SpatialCollection<T> {
return SpatialMapFast(this)
}

/**
* Checks if the specified element is contained in this collection.
*/
override operator fun contains(element: T): Boolean {
for (x in this) {
if (x == element)
return true
}

return false
}

/**
* Checks if all elements in the specified collection are contained in this collection.
*/
override fun containsAll(elements: Collection<T>): Boolean {
for (spatial in elements) {
if (!contains(spatial))
return false
}

return true
}

/**
* Returns `true` if the collection is empty (contains no elements), `false` otherwise.
*/
override fun isEmpty(): Boolean {
return size == 0
}

override fun iterator(): Iterator<T> {
return SpatialIterator(map)
}

override fun forEach(block: (IntVector, T) -> Unit) {
map.fastForEach { _, entry ->
block(entry.position, entry.value)
}
}

override fun forEachRadius(center: IntVector, radius: Float, block: (IntVector, T) -> Unit) {
map.fastForEach { _, entry ->
if (entry.position.distanceTo(center) < radius)
block(entry.position, entry.value)
}
}

override fun keys(): List<IntVector> {
val list = ArrayList<IntVector>(this.size)
this.forEach { i, _ ->
list.add(i)
}
return list
}

inner class Node<T>(
val position: IntVector,
val value: T
)

inner class SpatialIterator<T>(
map: FastIntMap<Node<T>>
): Iterator<T> {

val iterator = ArrayList<T>().apply {
map.fastValueForEachNullable {
it?.let { add(it.value) }
}
}.iterator()

/**
* Returns `true` if the iteration has more elements.
*/
override fun hasNext(): Boolean {
return iterator.hasNext()
}

/**
* Returns the next element in the iteration.
*/
override fun next(): T {
return iterator.next()
}

}

}

+ 32
- 0
dungeon/src/commonMain/kotlin/me/jfenn/dungeon/core/vector/Vector.kt View File

@@ -18,6 +18,14 @@ abstract class Vector<T: Number>: Comparable<Vector<T>>, VectorFactory {
)
}

operator fun <V: Vector<T>> plus(other: T): V {
return create(
x + other,
y + other,
z + other
)
}

operator fun <V: Vector<T>> minus(other: V): V {
return create(
x - other.x,
@@ -26,6 +34,14 @@ abstract class Vector<T: Number>: Comparable<Vector<T>>, VectorFactory {
)
}

operator fun <V: Vector<T>> minus(other: T): V {
return create(
x - other,
y - other,
z - other
)
}

operator fun <V: Vector<T>> times(other: V): V {
return create(
x * other.x,
@@ -34,6 +50,14 @@ abstract class Vector<T: Number>: Comparable<Vector<T>>, VectorFactory {
)
}

operator fun <V: Vector<T>> times(other: T): V {
return create(
x * other,
y * other,
z * other
)
}

operator fun <V: Vector<T>> div(other: V): V {
return create(
x / other.x,
@@ -42,6 +66,14 @@ abstract class Vector<T: Number>: Comparable<Vector<T>>, VectorFactory {
)
}

operator fun <V: Vector<T>> div(other: T): V {
return create(
x / other,
y / other,
z / other
)
}

fun <V: Vector<T>> flatten(): V {
return create(
x, y, typeNum(0, x)


+ 44
- 0
dungeon/src/commonMain/kotlin/me/jfenn/dungeon/core/vector/area/LineArea.kt View File

@@ -4,6 +4,7 @@ import me.jfenn.dungeon.core.extensions.vectorOf
import me.jfenn.dungeon.core.vector.*
import me.jfenn.dungeon.core.vector.iterator.AreaIterator
import kotlin.Exception
import kotlin.math.abs
import kotlin.math.roundToInt

inline fun forLine(start: IntVector, end: IntVector, crossinline block: (IntVector) -> Unit) {
@@ -23,6 +24,49 @@ inline fun forLine(start: IntVector, end: IntVector, crossinline block: (IntVect
}
}

class LineIterator(
private val start: IntVector,
private val end: IntVector
) : Iterator<IntVector> {

private val diff = vectorOf(
abs(start.x - end.x),
abs(start.y - end.y),
abs(start.z - end.z)
)

private val distance = when {
diff.x > diff.y -> diff.x.toFloat()
diff.y > diff.z -> diff.y.toFloat()
diff.z != 0 -> diff.z.toFloat()
else -> start.distanceTo(end).toFloat() * LineArea.SQUARE_MULTIPLIER
}

private val delta = MutableFloatVector(
(end.x - start.x) / distance,
(end.y - start.y) / distance,
(end.z - start.z) / distance
)

private var current = 0

override fun hasNext(): Boolean {
return current < distance
}

override fun next(): IntVector {
val position = vectorOf(
start.x + (delta.x * current).toInt(),
start.y + (delta.y * current).toInt(),
start.z + (delta.z * current).toInt()
)

current++
return position
}

}

class LineArea(
val start: IntVector,
val end: IntVector


+ 4
- 0
dungeon/src/jvmMain/kotlin/me/jfenn/dungeon/api/view/JDungeonCanvas.kt View File

@@ -58,6 +58,10 @@ open class JDungeonCanvas(
env.currentWidth = size.x / pixelSize
env.currentHeight = size.y / pixelSize

env.viewEventListeners.forEach {
it.onWindowResized(env.currentWidth, env.currentHeight)
}

revalidate()
repaint()
}


+ 4
- 0
example-swing/src/main/java/me/jfenn/example/dungeonswing/snake/Background.kt View File

@@ -23,4 +23,8 @@ class Background : Sprite() {
}
}

override fun onWindowResized(width: Int, height: Int) {
invalidate()
}

}

Loading…
Cancel
Save