Skip to content

Commit 821cc54

Browse files
Add support for debugging cyclic wisps with HexDebug (#188)
2 parents 3410bc7 + 4674cd9 commit 821cc54

File tree

15 files changed

+251
-37
lines changed

15 files changed

+251
-37
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ jobs:
7575
fabric-api: 0.85.0
7676
# NOTE: these must be quoted and formatted exactly like this, since they'll be used as a bash array
7777
dependencies: >-
78-
'https://cdn.modrinth.com/data/Ha28R6CL/versions/ADg3gvlr/fabric-language-kotlin-1.9.5%2Bkotlin.1.8.22.jar'
78+
'https://cdn.modrinth.com/data/Ha28R6CL/versions/vlhvI5Li/fabric-language-kotlin-1.10.18%2Bkotlin.1.9.22.jar'
7979
'https://cdn.modrinth.com/data/K01OU20C/versions/qW85eawp/cardinal-components-api-5.2.2.jar'
8080
'https://cdn.modrinth.com/data/nU0bVIaL/versions/PKvFvHeb/Patchouli-1.20.1-80-FABRIC.jar'
8181
'https://cdn.modrinth.com/data/9s6osm5g/versions/s7VTKfLA/cloth-config-11.1.106-fabric.jar'

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

7+
## [UNRELEASED]
8+
9+
### Added
10+
11+
- Added support for debugging cyclic wisps with [HexDebug](https://modrinth.com/mod/hexdebug)!
12+
13+
### Changed
14+
15+
- Increased minimum dependency versions:
16+
- Fabric Loader: `0.16`
17+
- Fabric Language Kotlin: `1.10.18+kotlin.1.9.22`
18+
719
## `0.3.1` - 2025-10-30
820

921
### Changed

Common/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ dependencies {
3737
compileOnly "ram.talia.moreiotas:moreiotas-common-$minecraftVersion:$moreIotasVersion"
3838
compileOnly "vazkii.patchouli:Patchouli-xplat:$minecraftVersion-$patchouliVersion-SNAPSHOT"
3939
compileOnly "software.bernie.geckolib:geckolib-forge-$minecraftVersion:$geckolibVersion"
40+
compileOnly "gay.object.hexdebug:hexdebug-core-common-mojmap:$hexdebugVersion+$minecraftVersion"
41+
compileOnly "gay.object.hexdebug:hexdebug-common-mojmap:$hexdebugVersion+$minecraftVersion"
4042

4143
testImplementation "at.petra-k.paucal:paucal-common-$minecraftVersion:$paucalVersion"
4244
testImplementation "at.petra-k.hexcasting:hexcasting-common-$minecraftVersion:$hexcastingVersion"

Common/src/main/java/ram/talia/hexal/api/casting/wisp/WispCastingManager.kt

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import at.petrak.hexcasting.api.casting.iota.Iota
77
import at.petrak.hexcasting.api.casting.iota.IotaType.isTooLargeToSerialize
88
import at.petrak.hexcasting.api.utils.asCompound
99
import at.petrak.hexcasting.api.utils.putCompound
10+
import gay.`object`.hexdebug.core.api.HexDebugCoreAPI
11+
import gay.`object`.hexdebug.core.api.exceptions.DebugException
1012
import net.minecraft.nbt.CompoundTag
1113
import net.minecraft.nbt.ListTag
1214
import net.minecraft.server.MinecraftServer
@@ -16,6 +18,7 @@ import ram.talia.hexal.api.HexalAPI
1618
import ram.talia.hexal.api.casting.eval.env.WispCastEnv
1719
import ram.talia.hexal.api.nbt.SerialisedIotaList
1820
import ram.talia.hexal.common.entities.BaseCastingWisp
21+
import ram.talia.hexal.common.entities.TickingWisp
1922
import java.util.*
2023

2124
class WispCastingManager(private val casterUUID: UUID, private var cachedServer: MinecraftServer?) {
@@ -130,22 +133,28 @@ class WispCastingManager(private val casterUUID: UUID, private var cachedServer:
130133
userData = userData
131134
)
132135

133-
val harness = CastingVM(image, ctx)
136+
val hex = cast.hex.getIotas(ctx.world)
134137

135-
val info = harness.queueExecuteAndWrapIotas(cast.hex.getIotas(ctx.world), wisp.level() as ServerLevel)
138+
// if we're debugging this wisp, delegate to the debugger
139+
if (wisp is TickingWisp && wisp.isDebugging) {
140+
wisp.getDebugEnv()?.let { debugEnv ->
141+
debugEnv.isPaused = true
142+
try {
143+
HexDebugCoreAPI.INSTANCE.startDebuggingIotas(debugEnv, ctx, hex, image)
144+
} catch (e: DebugException) {
145+
HexalAPI.LOGGER.warn("Failed to start debugging wisp hex", e)
146+
}
147+
}
148+
return WispCastResult(wisp, succeeded = true, image = image, cancelled = true)
149+
}
136150

137-
// TODO: Make this a mishap
138-
// Clear stack if it gets too large
139-
var endStack = harness.image.stack
140-
if (isTooLargeToSerialize(endStack)) {
141-
endStack = mutableListOf()
142-
}
151+
val harness = CastingVM(image, ctx)
143152

144-
val endRavenmind = harness.image.userData.getCompound(HexAPI.RAVENMIND_USERDATA)
153+
val info = harness.queueExecuteAndWrapIotas(hex, wisp.level() as ServerLevel)
145154

146155
// the wisp will have things it wants to do once the cast is successful, so a callback on it is called to let it know that happened, and what the end state of the
147156
// stack and ravenmind is. This is returned and added to a list that [executeCasts] will loop over to hopefully prevent concurrent modification problems.
148-
return WispCastResult(wisp, info.resolutionType.success, endStack, endRavenmind)
157+
return WispCastResult(wisp, info.resolutionType.success, harness.image)
149158
}
150159

151160
fun readFromNbt(tag: CompoundTag?, level: ServerLevel) {
@@ -248,6 +257,21 @@ class WispCastingManager(private val casterUUID: UUID, private var cachedServer:
248257
* the result passed back to the Wisp after its cast is successfully executed.
249258
*/
250259
data class WispCastResult(val wisp: BaseCastingWisp, val succeeded: Boolean, val endStack: List<Iota>, val endRavenmind: CompoundTag, val cancelled: Boolean = false) {
260+
constructor(
261+
wisp: BaseCastingWisp,
262+
succeeded: Boolean,
263+
image: CastingImage,
264+
cancelled: Boolean = false,
265+
) : this(
266+
wisp = wisp,
267+
succeeded = succeeded,
268+
// TODO: Make this a mishap
269+
// Clear stack if it gets too large
270+
endStack = if (isTooLargeToSerialize(image.stack)) mutableListOf() else image.stack,
271+
endRavenmind = image.userData.getCompound(HexAPI.RAVENMIND_USERDATA),
272+
cancelled = cancelled,
273+
)
274+
251275
fun callback() { wisp.castCallback(this) }
252276
}
253277

@@ -266,4 +290,4 @@ class WispCastingManager(private val casterUUID: UUID, private var cachedServer:
266290
specialHandlers.add { _, cast -> cast.wisp?.seon == true }
267291
}
268292
}
269-
}
293+
}

Common/src/main/java/ram/talia/hexal/common/casting/actions/spells/wisp/OpSummonWisp.kt

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,26 @@
22
package ram.talia.hexal.common.casting.actions.spells.wisp
33

44
import at.petrak.hexcasting.api.HexAPI
5-
import at.petrak.hexcasting.api.misc.MediaConstants
65
import at.petrak.hexcasting.api.casting.*
76
import at.petrak.hexcasting.api.casting.castables.SpellAction
87
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
98
import at.petrak.hexcasting.api.casting.iota.Iota
109
import at.petrak.hexcasting.api.casting.iota.IotaType
1110
import at.petrak.hexcasting.api.casting.iota.NullIota
11+
import at.petrak.hexcasting.api.misc.MediaConstants
12+
import gay.`object`.hexdebug.core.api.HexDebugCoreAPI
13+
import gay.`object`.hexdebug.core.api.exceptions.DebugException
1214
import net.minecraft.nbt.CompoundTag
13-
import net.minecraft.world.entity.player.Player
15+
import net.minecraft.server.level.ServerPlayer
1416
import net.minecraft.world.phys.Vec3
17+
import ram.talia.hexal.api.HexalAPI
1518
import ram.talia.hexal.api.addBounded
1619
import ram.talia.hexal.api.casting.eval.env.WispCastEnv
17-
import ram.talia.hexal.api.config.HexalConfig
1820
import ram.talia.hexal.api.casting.mishaps.MishapExcessiveReproduction
21+
import ram.talia.hexal.api.config.HexalConfig
1922
import ram.talia.hexal.common.entities.ProjectileWisp
2023
import ram.talia.hexal.common.entities.TickingWisp
24+
import ram.talia.hexal.interop.hexdebug.WispDebugEnv
2125
import kotlin.math.max
2226

2327
class OpSummonWisp(val ticking: Boolean) : SpellAction {
@@ -72,15 +76,30 @@ class OpSummonWisp(val ticking: Boolean) : SpellAction {
7276
if (env is WispCastEnv)
7377
env.wisp.summonedChildThisCast = true
7478

79+
val player = env.castingEntity as? ServerPlayer
80+
7581
val pigment = env.pigment
7682
val wisp = when (ticking) {
77-
true -> TickingWisp(env.world, pos, env.castingEntity as? Player, media)
78-
false -> ProjectileWisp(env.world, pos, vel, env.castingEntity as? Player, media)
83+
true -> TickingWisp(env.world, pos, player, media)
84+
false -> ProjectileWisp(env.world, pos, vel, player, media)
7985
}
8086
wisp.setPigment(pigment)
8187
wisp.setHex(hex.toMutableList())
8288
wisp.setRavenmind(ravenmind)
8389
env.world.addFreshEntity(wisp)
90+
91+
// if the current cast is being debugged, try to spawn the wisp in debug mode too
92+
if (HexDebugCoreAPI.INSTANCE.getDebugEnv(env) != null && player != null && wisp is TickingWisp) {
93+
val debugEnv = WispDebugEnv(player, wisp.uuid, ravenmind)
94+
try {
95+
HexDebugCoreAPI.INSTANCE.createDebugThread(debugEnv, null)
96+
} catch (e: DebugException) {
97+
// if there are no threads available, just spawn the wisp in normal mode
98+
HexalAPI.LOGGER.debug("Not starting wisp in debug mode", e)
99+
return
100+
}
101+
wisp.setDebugEnv(debugEnv)
102+
}
84103
}
85104
}
86-
}
105+
}

Common/src/main/java/ram/talia/hexal/common/entities/BaseCastingWisp.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ abstract class BaseCastingWisp(entityType: EntityType<out BaseCastingWisp>, worl
316316
/**
317317
* Returns true if there are no triggers limiting when the wisp can cast, false otherwise
318318
*/
319-
fun canScheduleCast(): Boolean {
319+
open fun canScheduleCast(): Boolean {
320320
// HexalAPI.LOGGER.info("active trigger is $activeTrigger, shouldRemove: ${activeTrigger?.shouldRemoveTrigger(this)}, shouldTrigger: ${activeTrigger?.shouldTrigger(this)}")
321321
if (activeTrigger?.shouldRemoveTrigger(this) == true)
322322
activeTrigger = null

Common/src/main/java/ram/talia/hexal/common/entities/TickingWisp.kt

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import at.petrak.hexcasting.api.casting.iota.EntityIota
55
import at.petrak.hexcasting.api.casting.iota.Iota
66
import at.petrak.hexcasting.api.utils.hasByte
77
import at.petrak.hexcasting.api.utils.hasFloat
8+
import gay.`object`.hexdebug.core.api.HexDebugCoreAPI
89
import net.minecraft.nbt.CompoundTag
910
import net.minecraft.nbt.ListTag
1011
import net.minecraft.network.chat.Component
1112
import net.minecraft.network.syncher.EntityDataAccessor
1213
import net.minecraft.network.syncher.EntityDataSerializers
1314
import net.minecraft.network.syncher.SynchedEntityData
1415
import net.minecraft.server.level.ServerLevel
16+
import net.minecraft.server.level.ServerPlayer
1517
import net.minecraft.world.entity.EntityType
1618
import net.minecraft.world.entity.player.Player
1719
import net.minecraft.world.level.Level
@@ -23,7 +25,9 @@ import ram.talia.hexal.api.nbt.SerialisedIotaList
2325
import ram.talia.hexal.api.plus
2426
import ram.talia.hexal.api.times
2527
import ram.talia.hexal.common.lib.HexalEntities
28+
import ram.talia.hexal.interop.hexdebug.WispDebugEnv
2629
import java.lang.Double.min
30+
import java.util.UUID
2731

2832
class TickingWisp : BaseCastingWisp {
2933
override val shouldComplainNotEnoughMedia = false
@@ -73,6 +77,24 @@ class TickingWisp : BaseCastingWisp {
7377
val maximumMoveMultiplier: Float
7478
get() = entityData.get(MAXIMUM_MOVE_MULTIPLIER)
7579

80+
private var debugSessionId: UUID? = null
81+
82+
val isDebugging: Boolean
83+
get() = debugSessionId != null
84+
85+
private val isDebuggingAndPaused: Boolean
86+
get() = isDebugging && getDebugEnv()?.isPaused != false
87+
88+
fun getDebugEnv(): WispDebugEnv? {
89+
val debugSessionId = debugSessionId ?: return null
90+
val caster = caster as? ServerPlayer ?: return null
91+
return HexDebugCoreAPI.INSTANCE.getDebugEnv(caster, debugSessionId) as? WispDebugEnv
92+
}
93+
94+
fun setDebugEnv(debugEnv: WispDebugEnv) {
95+
debugSessionId = debugEnv.sessionId
96+
}
97+
7698
constructor(entityType: EntityType<out BaseCastingWisp>, world: Level) : super(entityType, world)
7799
constructor(
78100
entityType: EntityType<out TickingWisp>,
@@ -121,6 +143,12 @@ class TickingWisp : BaseCastingWisp {
121143
}
122144
}
123145

146+
// if the debug session has ended, destroy the wisp
147+
// TODO: this can result in a zombie session when the player dies (https://github.com/object-Object/HexDebug/issues/64)
148+
if (isDebugging && caster != null && getDebugEnv() == null) {
149+
discard()
150+
}
151+
124152
super.tick()
125153
}
126154

@@ -143,7 +171,7 @@ class TickingWisp : BaseCastingWisp {
143171
}
144172

145173
override fun move() {
146-
if (reachedTargetPos()) // also checks if within close enough distance of target.
174+
if (reachedTargetPos() || isDebuggingAndPaused) // also checks if within close enough distance of target.
147175
return
148176

149177
val currentTarget = getTargetMovePosRaw()
@@ -163,10 +191,16 @@ class TickingWisp : BaseCastingWisp {
163191
// Seon wisps have the same max range as the caster.
164192
override fun maxSqrCastingDistance() = if (seon) { PlayerBasedCastEnv.AMBIT_RADIUS * PlayerBasedCastEnv.AMBIT_RADIUS } else { CASTING_RADIUS * CASTING_RADIUS }
165193

194+
override fun canScheduleCast(): Boolean {
195+
return super.canScheduleCast() && !isDebuggingAndPaused
196+
}
197+
166198
override fun castCallback(result: WispCastingManager.WispCastResult) {
167199
// HexalAPI.LOGGER.info("ticking wisp $uuid had a cast successfully completed!")
168-
setStack(result.endStack)
169-
setRavenmind(result.endRavenmind)
200+
if (!result.cancelled) {
201+
setStack(result.endStack)
202+
setRavenmind(result.endRavenmind)
203+
}
170204

171205
super.castCallback(result)
172206
}
@@ -184,25 +218,40 @@ class TickingWisp : BaseCastingWisp {
184218
entityData.set(TARGET_MOVE_POS_Z, pos.z.toFloat())
185219
}
186220

221+
fun clearTargetMovePos() {
222+
entityData.set(HAS_TARGET_MOVE_POS, false)
223+
}
224+
187225
fun reachedTargetPos(): Boolean {
188226
return if (!entityData.get(HAS_TARGET_MOVE_POS)) {
189227
true
190228
} else if ((getTargetMovePosRaw() - position()).lengthSqr() < 0.01) {
191229
setPos(getTargetMovePosRaw())
192-
entityData.set(HAS_TARGET_MOVE_POS, false)
230+
clearTargetMovePos()
193231
true
194232
} else {
195233
false
196234
}
197235
}
198236

237+
override fun remove(reason: RemovalReason) {
238+
if (reason.shouldDestroy()) {
239+
getDebugEnv()?.let { HexDebugCoreAPI.INSTANCE.removeDebugThread(it) }
240+
}
241+
super.remove(reason)
242+
}
243+
199244
override fun readAdditionalSaveData(compound: CompoundTag) {
200245
super.readAdditionalSaveData(compound)
201246

202247
when (val stackTag = compound.get(TAG_STACK)) {
203248
null -> serStack.set(mutableListOf())
204249
else -> serStack.set(stackTag as ListTag)
205250
}
251+
debugSessionId = when (compound.hasUUID(TAG_DEBUG_SESSION_ID)) {
252+
true -> compound.getUUID(TAG_DEBUG_SESSION_ID)
253+
false -> null
254+
}
206255
entityData.set(HAS_TARGET_MOVE_POS, when(compound.hasByte(TAG_HAS_TARGET_MOVE_POS)) {
207256
true -> compound.getBoolean(TAG_HAS_TARGET_MOVE_POS)
208257
false -> false
@@ -233,6 +282,7 @@ class TickingWisp : BaseCastingWisp {
233282
super.addAdditionalSaveData(compound)
234283

235284
compound.put(TAG_STACK, serStack.getTag())
285+
debugSessionId?.let { compound.putUUID(TAG_DEBUG_SESSION_ID, it) }
236286
compound.putBoolean(TAG_HAS_TARGET_MOVE_POS, entityData.get(HAS_TARGET_MOVE_POS))
237287
compound.putFloat(TAG_TARGET_MOVE_POS_X, entityData.get(TARGET_MOVE_POS_X))
238288
compound.putFloat(TAG_TARGET_MOVE_POS_Y, entityData.get(TARGET_MOVE_POS_Y))
@@ -261,6 +311,7 @@ class TickingWisp : BaseCastingWisp {
261311
val MAXIMUM_MOVE_MULTIPLIER: EntityDataAccessor<Float> = SynchedEntityData.defineId(TickingWisp::class.java, EntityDataSerializers.FLOAT)
262312

263313
const val TAG_STACK = "stack"
314+
const val TAG_DEBUG_SESSION_ID = "debug_session_id"
264315
const val TAG_HAS_TARGET_MOVE_POS = "has_target_move_pos"
265316
const val TAG_TARGET_MOVE_POS_X = "target_move_pos_x"
266317
const val TAG_TARGET_MOVE_POS_Y = "target_move_pos_y"
@@ -274,4 +325,4 @@ class TickingWisp : BaseCastingWisp {
274325
const val BASE_MAX_SPEED_PER_TICK = 6.0 / 20
275326
const val SCALE = 0.2
276327
}
277-
}
328+
}

0 commit comments

Comments
 (0)