Skip to content
This repository was archived by the owner on Nov 28, 2025. It is now read-only.

Commit 3b10469

Browse files
authored
Merge pull request #185 from DockyardMC/feature/hot-reload-support
Add better hot reloading support
2 parents 4b1f650 + c7e3547 commit 3b10469

File tree

28 files changed

+355
-123
lines changed

28 files changed

+355
-123
lines changed

.idea/misc.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/runConfigurations/MainKt.xml

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ dependencies {
4444
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
4545
implementation("com.akuleshov7:ktoml-core:0.5.1")
4646
implementation("com.akuleshov7:ktoml-file:0.5.1")
47+
implementation("net.bytebuddy:byte-buddy-agent:1.14.12")
4748

4849
api("io.github.dockyardmc:bytesocks-client-java:1.0-SNAPSHOT") {
4950
exclude(module = "slf4j-api")
@@ -73,7 +74,7 @@ dependencies {
7374
implementation("com.google.guava:guava:33.3.1-jre")
7475
implementation("com.google.code.gson:gson:2.10.1")
7576
api("it.unimi.dsi:fastutil:8.5.13")
76-
api("cz.lukynka:kotlin-bindables:2.1")
77+
api("cz.lukynka:kotlin-bindables:2.2")
7778

7879
api("io.github.dockyardmc:spark-api:1.12-SNAPSHOT")
7980
api("io.github.dockyardmc:spark-common:1.12-SNAPSHOT")

src/main/kotlin/Main.kt

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import io.github.dockyardmc.DockyardServer
2-
import io.github.dockyardmc.commands.CommandException
32
import io.github.dockyardmc.commands.Commands
43
import io.github.dockyardmc.events.Events
54
import io.github.dockyardmc.events.PlayerJoinEvent
5+
import io.github.dockyardmc.events.PlayerRightClickWithItemEvent
66
import io.github.dockyardmc.inventory.give
7+
import io.github.dockyardmc.maths.vectors.Vector3d
78
import io.github.dockyardmc.player.systems.GameMode
89
import io.github.dockyardmc.registry.Items
910
import io.github.dockyardmc.ui.TestScreen
10-
import io.github.dockyardmc.ui.snapshot.InventorySnapshot
1111
import io.github.dockyardmc.utils.DebugSidebar
1212

1313
fun main() {
@@ -26,6 +26,10 @@ fun main() {
2626
player.give(Items.OAK_LOG)
2727
}
2828

29+
Events.on<PlayerRightClickWithItemEvent> { event ->
30+
event.player.setVelocity(Vector3d(0, 20.5, 0))
31+
}
32+
2933
Commands.add("/ui") {
3034
execute { ctx ->
3135
val player = ctx.getPlayerOrThrow()
@@ -34,25 +38,5 @@ fun main() {
3438
}
3539
}
3640

37-
var snapshot: InventorySnapshot? = null
38-
Commands.add("/snapshot") {
39-
addSubcommand("take") {
40-
execute { ctx ->
41-
val player = ctx.getPlayerOrThrow()
42-
snapshot = InventorySnapshot(player)
43-
player.sendMessage("<orange>taken inventory snapshot")
44-
}
45-
}
46-
47-
addSubcommand("restore") {
48-
execute { ctx ->
49-
val player = ctx.getPlayerOrThrow()
50-
if(snapshot == null) throw CommandException("There is not inventory snapshot taken")
51-
snapshot!!.restore()
52-
player.sendMessage("<lime>Restored inventory snapshot from ${snapshot!!.created.toEpochMilliseconds()}")
53-
}
54-
}
55-
}
56-
5741
server.start()
5842
}

src/main/kotlin/io/github/dockyardmc/DockyardServer.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.github.dockyardmc
22

3+
import io.github.dockyardmc.utils.InstrumentationUtils
34
import cz.lukynka.prettylog.LogType
45
import cz.lukynka.prettylog.log
56
import io.github.dockyardmc.config.Config
@@ -87,6 +88,12 @@ class DockyardServer(configBuilder: Config.() -> Unit) {
8788

8889
Events.dispatch(ServerFinishLoadEvent(this))
8990
if (ConfigManager.config.updateChecker) UpdateChecker()
91+
92+
if (InstrumentationUtils.isDebuggerAttached()) {
93+
profiler("Setup hot reload detection") {
94+
InstrumentationUtils.setupHotReloadDetection()
95+
}
96+
}
9097
}
9198
}
9299

src/main/kotlin/io/github/dockyardmc/entity/Entity.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import cz.lukynka.bindables.Bindable
44
import cz.lukynka.bindables.BindableList
55
import cz.lukynka.bindables.BindableMap
66
import cz.lukynka.bindables.BindablePool
7-
import cz.lukynka.prettylog.LogType
8-
import cz.lukynka.prettylog.log
97
import io.github.dockyardmc.config.ConfigManager
108
import io.github.dockyardmc.entity.EntityManager.despawnEntity
119
import io.github.dockyardmc.entity.handlers.*
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package io.github.dockyardmc.events
2+
3+
import kotlin.reflect.KClass
4+
5+
data class InstrumentationHotReloadEvent(val kclass: KClass<*>) : Event {
6+
override val context: Event.Context = Event.Context(isGlobalEvent = true)
7+
}

src/main/kotlin/io/github/dockyardmc/events/system/EventSystem.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,11 +163,12 @@ abstract class EventSystem : Disposable {
163163
fun debugTree(indent: Int = 1): String {
164164
return buildString {
165165
append(name)
166-
children.forEach {
166+
append(" <gray>(${eventList().size} listeners)")
167+
children.forEach { child ->
167168
appendLine()
168169
append(" ".repeat(indent))
169170
append("<gray>⇒ <aqua>")
170-
append(it.debugTree(indent + 1))
171+
append(child.debugTree(indent + 1))
171172
}
172173
}
173174
}

src/main/kotlin/io/github/dockyardmc/implementations/commands/DebugCommand.kt

Lines changed: 0 additions & 38 deletions
This file was deleted.
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package io.github.dockyardmc.implementations.commands
2+
3+
import io.github.dockyardmc.DockyardServer
4+
import io.github.dockyardmc.commands.Commands
5+
import io.github.dockyardmc.commands.WorldArgument
6+
import io.github.dockyardmc.events.Events
7+
import io.github.dockyardmc.extentions.truncate
8+
import io.github.dockyardmc.player.PlayerManager
9+
import io.github.dockyardmc.profiler.profiler
10+
import io.github.dockyardmc.server.ServerMetrics
11+
import io.github.dockyardmc.utils.DataSizeCounter
12+
import io.github.dockyardmc.world.World
13+
14+
object DebugCommands {
15+
16+
fun register() {
17+
18+
Commands.add("/debug_world") {
19+
addOptionalArgument("world", WorldArgument())
20+
execute { ctx ->
21+
val world = getArgumentOrNull<World>("world") ?: ctx.getPlayerOrThrow().world
22+
23+
ctx.sendMessage(" ")
24+
ctx.sendMessage(" <gray>Entities:")
25+
ctx.sendMessage(" <dark_gray>◾ <gray>Total: <white>${world.entities.size}")
26+
ctx.sendMessage(" <dark_gray>◾ <gray>Tickable: <white>${world.entities.filter { entity -> entity.tickable }.size}")
27+
ctx.sendMessage(" <dark_gray>◾ <gray>Non-tickable: <white>${world.entities.filter { entity -> !entity.tickable }.size}")
28+
ctx.sendMessage(" <dark_gray>◾ <gray>AutoViewable: <white>${world.entities.filter { entity -> entity.autoViewable }.size}")
29+
ctx.sendMessage(" <dark_gray>◾ <gray>Non-AutoViewable: <white>${world.entities.filter { entity -> !entity.autoViewable }.size}")
30+
ctx.sendMessage(" ")
31+
ctx.sendMessage(" <gray>Chunks:")
32+
ctx.sendMessage(" <dark_gray>◾ <gray>Total: <white>${world.chunks.size}")
33+
ctx.sendMessage(" <dark_gray>◾ <gray>Visible: <white>${world.chunks.filter { chunk -> chunk.value.viewers.isNotEmpty() }.size}")
34+
ctx.sendMessage(" ")
35+
ctx.sendMessage(" <gray>Scheduler:")
36+
ctx.sendMessage(" <dark_gray>◾ <gray>Running: <white>${!world.scheduler.paused.value}")
37+
ctx.sendMessage(" <dark_gray>◾ <gray>Tickrate: <white>${world.scheduler.tickRate.value.inWholeMilliseconds}ms <gray>(${world.scheduler.mspt} mspt)")
38+
ctx.sendMessage(" <dark_gray>◾ <gray>Tasks: <white>${world.scheduler.taskSize}")
39+
ctx.sendMessage(" ")
40+
ctx.sendMessage(" <gray>World:")
41+
ctx.sendMessage(" <dark_gray>◾ <gray>Event pool size: <white>${world.eventPool.eventList().size}")
42+
ctx.sendMessage(" <dark_gray>◾ <gray>Bindable pool size: <white>${world.bindablePool.size}")
43+
ctx.sendMessage(" <dark_gray>◾ <gray>Loaded: <white>${world.isLoaded.value}")
44+
ctx.sendMessage(" <dark_gray>◾ <gray>Player Queue: <white>${world.playerJoinQueue.size}")
45+
ctx.sendMessage(" <dark_gray>◾ <gray>Data Blocks: <white>${world.customDataBlocks.size}")
46+
ctx.sendMessage(" ")
47+
}
48+
}
49+
50+
Commands.add("/debug_events") {
51+
execute { ctx ->
52+
ctx.sendMessage(" ")
53+
ctx.sendMessage(Events.debugTree())
54+
ctx.sendMessage(" ")
55+
}
56+
}
57+
58+
Commands.add("/forcegc") {
59+
execute { ctx ->
60+
val ms = profiler("Force Collect GC") {
61+
System.gc()
62+
}
63+
ctx.sendMessage("<gray>Ran GC in <white>${ms}<gray>ms")
64+
}
65+
}
66+
67+
Commands.add("/debug_network") {
68+
execute { ctx ->
69+
val dockyard = DockyardServer.instance.nettyServer
70+
val nettyThreadBoss = dockyard.bossGroup.executorCount()
71+
val nettyThreadWorker = dockyard.workerGroup.executorCount()
72+
73+
ctx.sendMessage(" ")
74+
ctx.sendMessage(" <gray>Netty:")
75+
ctx.sendMessage(" <dark_gray>◾ <gray>Boss: <white>$nettyThreadBoss")
76+
ctx.sendMessage(" <dark_gray>◾ <gray>Worker: <white>$nettyThreadWorker")
77+
ctx.sendMessage(" <dark_gray>◾ <gray>Connections: <white>${PlayerManager.players.size}")
78+
ctx.sendMessage(" ")
79+
ctx.sendMessage(" <gray>Network")
80+
ctx.sendMessage(" <dark_gray>◾ <gray>Packets/Sec: <white>↑${ServerMetrics.packetsSentAverage}${ServerMetrics.packetsReceivedAverage}")
81+
ctx.sendMessage(" <dark_gray>◾ <gray>Bandwidth: <white>↑${ServerMetrics.outboundBandwidth.getSize(DataSizeCounter.Type.MEGABYTE)}mb ↓${ServerMetrics.inboundBandwidth.getSize(DataSizeCounter.Type.MEGABYTE)}mb")
82+
ctx.sendMessage(" ")
83+
}
84+
}
85+
86+
Commands.add("/debug_memory") {
87+
execute { ctx ->
88+
val runtime = Runtime.getRuntime()
89+
val message = buildString {
90+
append("\n")
91+
appendLine(" <gray>Memory Stats:")
92+
appendLine(" <dark_gray>◾ <gray>Max memory: <white>${runtime.maxMemory() / 1024 / 1024}MB")
93+
appendLine(" <dark_gray>◾ <gray>Total memory: <white>${runtime.totalMemory() / 1024 / 1024}MB")
94+
appendLine(" <dark_gray>◾ <gray>Free memory: <white>${runtime.freeMemory() / 1024 / 1024}MB")
95+
appendLine(" <dark_gray>◾ <gray>Used memory: <white>${(runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024}MB")
96+
appendLine("")
97+
appendLine(" <gray>GC Stats:")
98+
appendLine(" <dark_gray>◾ <gray>Available processors: <white>${runtime.availableProcessors()}")
99+
appendLine(" <dark_gray>◾ <gray>Memory usage: <white>${ServerMetrics.memoryUsagePercent.truncate(1)}%")
100+
}
101+
ctx.sendMessage(message)
102+
}
103+
}
104+
105+
Commands.add("/reloadchunks") {
106+
execute { ctx ->
107+
val player = ctx.getPlayerOrThrow()
108+
player.chunkViewSystem.lock.lock()
109+
player.chunkViewSystem.resendChunks()
110+
player.chunkViewSystem.lock.unlock()
111+
player.chunkViewSystem.update()
112+
player.sendMessage("<gray>Reloaded your chunks!")
113+
}
114+
}
115+
116+
Commands.add("/reloadviewers") {
117+
execute { ctx ->
118+
val player = ctx.getPlayerOrThrow()
119+
player.entityViewSystem.lock.lock()
120+
player.entityViewSystem.clear()
121+
player.entityViewSystem.lock.unlock()
122+
player.entityViewSystem.tick()
123+
player.sendMessage("<gray>Reloaded your viewers!")
124+
}
125+
}
126+
}
127+
}

0 commit comments

Comments
 (0)