Skip to content

Commit f4247cc

Browse files
Merge pull request #12 from NiklasJohansen/dev-0.7.0
0.7.0 - Improved scene and asset API, bug fixes, shader reloading and more
2 parents 0366604 + 9941e28 commit f4247cc

71 files changed

Lines changed: 2254 additions & 1370 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

build.gradle.kts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ plugins {
44
java
55
`maven-publish`
66
kotlin("jvm") version "1.6.10"
7+
id("me.champeau.jmh") version "0.6.6"
78
}
89

910
val version: String by project
@@ -59,16 +60,24 @@ dependencies {
5960
implementation("net.sf.trove4j:trove4j:3.0.3")
6061
implementation("de.undercouch:bson4jackson:2.13.1")
6162
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.3")
63+
64+
// Java Microbenchmark Harness
65+
jmhAnnotationProcessor("org.openjdk.jmh:jmh-generator-annprocess:1.35")
66+
jmh("org.openjdk.jmh:jmh-core:1.35")
67+
jmh("org.openjdk.jmh:jmh-generator-annprocess:1.34")
68+
jmh("com.github.biboudis:jmh-profilers:0.1.4")
6269
}
6370

6471
val sourcesJar by tasks.creating(Jar::class) {
6572
group = JavaBasePlugin.DOCUMENTATION_GROUP
73+
duplicatesStrategy = DuplicatesStrategy.INCLUDE
6674
archiveClassifier.set("sources")
6775
exclude("*.png", "*.jpg", "*.ttf", "*.ogg", "*.txt")
6876
from(sourceSets.main.get().allSource)
6977
}
7078

7179
val jar by tasks.getting(Jar::class) {
80+
duplicatesStrategy = DuplicatesStrategy.INCLUDE
7281
manifest {
7382
attributes["Main-Class"] = mainClass
7483
}
@@ -105,4 +114,11 @@ publishing {
105114
// url = uri("$buildDir/repository")
106115
// }
107116
}
117+
}
118+
119+
jmh {
120+
duplicateClassesStrategy.set(DuplicatesStrategy.INCLUDE)
121+
warmupIterations.set(2)
122+
iterations.set(2)
123+
fork.set(2)
108124
}

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version=0.6.3
1+
version=0.7.0-SNAPSHOT
22
group=no.njoh
33
artifact=pulse-engine
44
lwjglVersion=3.3.1

gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#Wed Jul 01 20:06:20 CEST 2020
2-
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip
2+
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
33
distributionBase=GRADLE_USER_HOME
44
distributionPath=wrapper/dists
55
zipStorePath=wrapper/dists
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package benchmarks
2+
3+
import no.njoh.pulseengine.core.shared.utils.Extensions.forEachFast
4+
import org.openjdk.jmh.annotations.Benchmark
5+
import org.openjdk.jmh.annotations.Scope
6+
import org.openjdk.jmh.annotations.State
7+
import org.openjdk.jmh.infra.Blackhole
8+
9+
@State(Scope.Benchmark)
10+
open class ForEachBenchmark
11+
{
12+
private var numbers = (0 .. 1000).shuffled()
13+
14+
@Benchmark
15+
fun benchmarkForEachFast(bh: Blackhole)
16+
{
17+
var sum = 0
18+
numbers.forEachFast { sum += it }
19+
bh.consume(sum)
20+
}
21+
22+
@Benchmark
23+
fun benchmarkForEach(bh: Blackhole)
24+
{
25+
var sum = 0
26+
numbers.forEach { sum += it }
27+
bh.consume(sum)
28+
}
29+
}

src/main/kotlin/no/njoh/pulseengine/core/PulseEngineImpl.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import no.njoh.pulseengine.core.asset.AssetManagerImpl
44
import no.njoh.pulseengine.core.asset.AssetManagerInternal
55
import no.njoh.pulseengine.core.asset.types.Font
66
import no.njoh.pulseengine.core.asset.types.Sound
7-
import no.njoh.pulseengine.core.asset.types.Text
87
import no.njoh.pulseengine.core.asset.types.Texture
98
import no.njoh.pulseengine.core.audio.AudioImpl
109
import no.njoh.pulseengine.core.audio.AudioInternal
@@ -21,7 +20,9 @@ import no.njoh.pulseengine.core.input.InputImpl
2120
import no.njoh.pulseengine.core.input.InputInternal
2221
import no.njoh.pulseengine.core.scene.SceneManagerImpl
2322
import no.njoh.pulseengine.core.scene.SceneManagerInternal
23+
import no.njoh.pulseengine.core.shared.utils.Extensions.forEachFast
2424
import no.njoh.pulseengine.core.shared.utils.Extensions.toNowFormatted
25+
import no.njoh.pulseengine.core.shared.utils.FileWatcher
2526
import no.njoh.pulseengine.core.shared.utils.FpsLimiter
2627
import no.njoh.pulseengine.core.shared.utils.Logger
2728
import no.njoh.pulseengine.core.widget.WidgetManagerImpl
@@ -105,7 +106,7 @@ class PulseEngineImpl(
105106

106107
// Reload sound buffers to new OpenAL context
107108
audio.setOnOutputDeviceChanged {
108-
asset.getAll(Sound::class.java).forEach { it.reloadBuffer() }
109+
asset.getAllOfType<Sound>().forEachFast { it.reloadBuffer() }
109110
}
110111

111112
// Notify gfx implementation about loaded textures
@@ -174,6 +175,7 @@ class PulseEngineImpl(
174175
data.measureAndUpdateTimeStats()
175176
{
176177
updateInput()
178+
console.update()
177179
game.onUpdate()
178180
scene.update()
179181
widget.update(this)
@@ -251,6 +253,7 @@ class PulseEngineImpl(
251253

252254
private fun destroy()
253255
{
256+
FileWatcher.shutdown()
254257
scene.cleanUp()
255258
widget.cleanUp(this)
256259
audio.cleanUp()

src/main/kotlin/no/njoh/pulseengine/core/asset/AssetManager.kt

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,69 @@ import kotlin.reflect.KClass
55

66
abstract class AssetManager
77
{
8+
/**
9+
* Adds the [asset] to the [AssetManager] and returns it.
10+
*/
811
abstract fun <T : Asset> add(asset: T): T
9-
abstract fun <T : Asset> get(assetName: String): T
10-
abstract fun <T : Asset> getAll(type: Class<T>): List<T>
1112

12-
inline fun <reified T : Asset> getSafe(assetName: String): T? = getSafe(assetName, T::class)
13-
@PublishedApi internal abstract fun <T : Asset> getSafe(assetName: String, type: KClass<T>): T?
13+
/**
14+
* Removes the [Asset] with given [assetName] and calls its delete function.
15+
*/
16+
abstract fun delete(assetName: String)
1417

18+
/**
19+
* Returns the [Asset] with name [assetName] and type [T] or null if not found.
20+
*/
21+
inline fun <reified T : Asset> getOrNull(assetName: String): T? = getOrNull(assetName, T::class)
22+
23+
/**
24+
* Returns a list of all [Asset]s with given type [T].
25+
*/
26+
inline fun <reified T : Asset> getAllOfType(): List<T> = getAllOfType(T::class)
27+
28+
/**
29+
* Loads all [Texture]s in the given [directory].
30+
*/
1531
abstract fun loadAllTextures(directory: String)
32+
33+
/**
34+
* Loads the file with given [fileName] and ads it to the [AssetManager] as a [Texture].
35+
*/
1636
abstract fun loadTexture(fileName: String, assetName: String): Texture
37+
38+
/**
39+
* Loads the file with given [fileName] and ads it to the [AssetManager] as a [SpriteSheet].
40+
*/
1741
abstract fun loadSpriteSheet(fileName: String, assetName: String, horizontalCells: Int, verticalCells: Int): SpriteSheet
42+
43+
/**
44+
* Loads the file with given [fileName] and ads it to the [AssetManager] as a [Font].
45+
*/
1846
abstract fun loadFont(fileName: String, assetName: String, fontSizes: FloatArray): Font
47+
48+
/**
49+
* Loads the file with given [fileName] and ads it to the [AssetManager] as a [Cursor].
50+
*/
1951
abstract fun loadCursor(fileName: String, assetName: String, xHotSpot: Int, yHotSpot: Int): Cursor
52+
53+
/**
54+
* Loads the file with given [fileName] and ads it to the [AssetManager] as a [Sound].
55+
*/
2056
abstract fun loadSound(fileName: String, assetName: String): Sound
57+
58+
/**
59+
* Loads the file with given [fileName] and ads it to the [AssetManager] as a [Text].
60+
*/
2161
abstract fun loadText(fileName: String, assetName: String): Text
62+
63+
/**
64+
* Loads the file with given [fileName] and ads it to the [AssetManager] as a [Binary].
65+
*/
2266
abstract fun loadBinary(fileName: String, assetName: String): Binary
67+
68+
// Internal abstract versions of the public inline functions
69+
@PublishedApi internal abstract fun <T : Asset> getAllOfType(type: KClass<T>): List<T>
70+
@PublishedApi internal abstract fun <T : Asset> getOrNull(assetName: String, type: KClass<T>): T?
2371
}
2472

2573
abstract class AssetManagerInternal : AssetManager()

src/main/kotlin/no/njoh/pulseengine/core/asset/AssetManagerImpl.kt

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package no.njoh.pulseengine.core.asset
22

3+
import kotlinx.coroutines.Dispatchers
34
import kotlinx.coroutines.launch
45
import kotlinx.coroutines.runBlocking
56
import no.njoh.pulseengine.core.asset.types.*
67
import no.njoh.pulseengine.core.shared.utils.Logger
78
import no.njoh.pulseengine.core.shared.utils.Extensions.forEachFast
8-
import no.njoh.pulseengine.core.shared.utils.Extensions.forEachFiltered
99
import no.njoh.pulseengine.core.shared.utils.Extensions.loadFileNames
10+
import no.njoh.pulseengine.core.shared.utils.Extensions.toNowFormatted
1011
import kotlin.reflect.KClass
12+
import kotlin.reflect.safeCast
1113

1214
open class AssetManagerImpl : AssetManagerInternal()
1315
{
@@ -17,15 +19,11 @@ open class AssetManagerImpl : AssetManagerInternal()
1719
private var onAssetRemovedCallbacks = mutableListOf<(Asset) -> Unit>()
1820

1921
@Suppress("UNCHECKED_CAST")
20-
override fun <T : Asset> get(assetName: String): T =
21-
assets[assetName]?.let { it as T } ?: throw IllegalArgumentException("No asset loaded with name: $assetName")
22+
override fun <T : Asset> getOrNull(assetName: String, type: KClass<T>): T? =
23+
assets[assetName]?.let { type.safeCast(it) }
2224

23-
@Suppress("UNCHECKED_CAST")
24-
override fun <T : Asset> getSafe(assetName: String, type: KClass<T>): T? =
25-
assets[assetName]?.takeIf { it::class == type }?.let { it as T }
26-
27-
override fun <T : Asset> getAll(type: Class<T>): List<T> =
28-
assets.values.filterIsInstance(type)
25+
override fun <T : Asset> getAllOfType(type: KClass<T>): List<T> =
26+
assets.values.filterIsInstance(type.java)
2927

3028
override fun loadTexture(fileName: String, assetName: String): Texture =
3129
Texture(fileName, assetName).also { add(it) }
@@ -51,16 +49,18 @@ open class AssetManagerImpl : AssetManagerInternal()
5149
override fun loadAllTextures(directory: String) =
5250
directory
5351
.loadFileNames()
54-
.forEachFiltered(
55-
{ fileName -> Texture.SUPPORTED_FORMATS.any { fileName.endsWith(it) } },
56-
{ fileName -> loadTexture(fileName, fileName.substringAfterLast("/").substringBeforeLast(".")) }
57-
)
52+
.filter { fileName -> Texture.SUPPORTED_FORMATS.any { fileName.endsWith(it) } }
53+
.forEachFast { fileName ->
54+
val assetName = fileName.substringAfterLast("/").substringBeforeLast(".")
55+
if (assets.none { it.value.name == assetName })
56+
loadTexture(fileName, assetName)
57+
}
5858

5959
override fun loadInitialAssets()
6060
{
6161
val startTime = System.nanoTime()
6262
add(Font.DEFAULT)
63-
runBlocking()
63+
runBlocking(Dispatchers.IO)
6464
{
6565
assets.values.forEach()
6666
{
@@ -69,23 +69,33 @@ open class AssetManagerImpl : AssetManagerInternal()
6969
}
7070
assets.values.forEach { asset -> onAssetLoadedCallbacks.forEachFast { it.invoke(asset) } }
7171
initialAssetsLoaded = true
72-
Logger.debug("Loaded ${assets.size} assets in ${(System.nanoTime() - startTime) / 1_000_000} ms. [${assets.values.joinToString { it.name }}]")
72+
Logger.debug("Loaded ${assets.size} assets in ${startTime.toNowFormatted()}. [${assets.values.joinToString { it.name }}]")
7373
}
7474

7575
override fun <T: Asset> add(asset: T): T
7676
{
77-
if (assets.containsKey(asset.name))
78-
Logger.warn("Asset with name: ${asset.name} already exists and will be overridden")
77+
val existingAsset = assets[asset.name]
78+
if (existingAsset != null)
79+
{
80+
Logger.warn("Asset with name: ${existingAsset.name} already exists and will be deleted and overridden")
81+
existingAsset.delete()
82+
}
7983

80-
assets[asset.name] = asset
8184
if (initialAssetsLoaded)
8285
{
8386
asset.load()
8487
onAssetLoadedCallbacks.forEachFast { it.invoke(asset) }
8588
}
89+
90+
assets[asset.name] = asset
8691
return asset
8792
}
8893

94+
override fun delete(assetName: String)
95+
{
96+
assets.remove(assetName)?.delete()
97+
}
98+
8999
override fun setOnAssetLoaded(callback: (Asset) -> Unit)
90100
{
91101
onAssetLoadedCallbacks.add(callback)

src/main/kotlin/no/njoh/pulseengine/core/console/CommandRegistry.kt

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@ import no.njoh.pulseengine.core.shared.primitives.Subscription
77
import kotlinx.coroutines.GlobalScope
88
import kotlinx.coroutines.delay
99
import kotlinx.coroutines.launch
10+
import no.njoh.pulseengine.core.console.MessageType.WARN
11+
import no.njoh.pulseengine.core.graphics.api.Shader
12+
import no.njoh.pulseengine.core.graphics.api.Shader.Companion.getShaderFromAbsolutePath
1013
import no.njoh.pulseengine.core.graphics.api.ShaderProgram
1114
import no.njoh.pulseengine.core.scene.SceneManagerInternal
15+
import no.njoh.pulseengine.core.shared.utils.FileWatcher
16+
import no.njoh.pulseengine.core.shared.utils.Logger
1217
import java.io.File
1318
import kotlin.reflect.KMutableProperty
1419
import kotlin.reflect.full.declaredMemberProperties
@@ -243,13 +248,46 @@ object CommandRegistry
243248
CommandResult("Reloaded entity types", showCommand = false)
244249
}
245250

246-
///////////////////////////////////////////// RELOAD POST PROCESSING SHADERS /////////////////////////////////////////////
251+
///////////////////////////////////////////// RELOAD SHADERS /////////////////////////////////////////////
247252

248253
engine.console.registerCommand(
249-
"reloadShaders"
254+
"reloadAllShaders"
250255
) {
256+
Logger.debug("\nReloading all shaders...")
257+
Shader.reloadAll()
251258
ShaderProgram.reloadAll()
252259
CommandResult("Reloaded all shaders", showCommand = false)
253260
}
261+
262+
engine.console.registerCommand(
263+
"reloadShader {fileName:String}"
264+
) {
265+
val fileName = getString("fileName")
266+
val shader = getShaderFromAbsolutePath(fileName)
267+
if (shader != null)
268+
{
269+
val success = shader.reload(fileName)
270+
if (success)
271+
ShaderProgram.reloadAll()
272+
CommandResult("Reloaded shader: $fileName", showCommand = false)
273+
}
274+
else CommandResult("Found no shader with filename: $fileName", showCommand = false, type = WARN)
275+
}
276+
277+
///////////////////////////////////////////// WATCH FILE CHANGES /////////////////////////////////////////////
278+
279+
engine.console.registerCommand(
280+
"watchFileChange {path:String} {triggerCommand:String} {interval:Int?} {fileTypes:String?} {maxDepth:Int?}"
281+
) {
282+
val path = getString("path")
283+
val command = getString("triggerCommand")
284+
val interval = getOptionalInt("interval") ?: 10
285+
val fileTypes = getOptionalString("fileTypes")?.split(";") ?: emptyList()
286+
val maxDepth = getOptionalInt("maxDepth") ?: 5
287+
FileWatcher.setOnFileChanged(path, fileTypes, maxDepth, interval) { fileName ->
288+
engine.console.runLater("$command \"$fileName\"", showCommand = true)
289+
}
290+
CommandResult("Watching for file changes every $interval seconds in $path", showCommand = false)
291+
}
254292
}
255293
}

src/main/kotlin/no/njoh/pulseengine/core/console/Console.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ interface Console
66
{
77
fun log(text: String, type: MessageType = MessageType.INFO)
88
fun run(commandString: String, showCommand: Boolean = true): List<CommandResult>
9+
fun runLater(commandString: String, showCommand: Boolean = true)
910
fun runScript(filename: String)
1011
fun registerCommand(template: String, description: String = "", isAlias: Boolean = false, block: CommandArguments.() -> CommandResult)
1112
fun getHistory(index: Int, type: MessageType): ConsoleEntry?
@@ -17,6 +18,7 @@ interface Console
1718
interface ConsoleInternal : Console
1819
{
1920
fun init(engine: PulseEngine)
21+
fun update()
2022
}
2123

2224
data class Command(

0 commit comments

Comments
 (0)