Skip to content

Commit 38bd041

Browse files
committed
Implement circle debugging!
This is only barely tested, and needs some cleanup and tweaks, but it does seem to actually work.
1 parent d01accd commit 38bd041

24 files changed

+673
-118
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package gay.object.hexdebug.mixin;
2+
3+
import at.petrak.hexcasting.api.block.HexBlockEntity;
4+
import at.petrak.hexcasting.api.casting.circles.BlockEntityAbstractImpetus;
5+
import at.petrak.hexcasting.api.casting.circles.CircleExecutionState;
6+
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
7+
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
8+
import gay.object.hexdebug.core.api.HexDebugCoreAPI;
9+
import gay.object.hexdebug.core.api.debugging.DebugOutputCategory;
10+
import gay.object.hexdebug.core.api.exceptions.DebugException;
11+
import gay.object.hexdebug.debugger.circles.CircleDebugEnv;
12+
import gay.object.hexdebug.debugger.circles.IMixinBlockEntityAbstractImpetus;
13+
import gay.object.hexdebug.debugger.circles.IMixinCircleExecutionState;
14+
import net.minecraft.core.BlockPos;
15+
import net.minecraft.network.chat.Component;
16+
import net.minecraft.server.level.ServerPlayer;
17+
import net.minecraft.world.InteractionResult;
18+
import net.minecraft.world.item.ItemStack;
19+
import net.minecraft.world.level.block.entity.BlockEntityType;
20+
import net.minecraft.world.level.block.state.BlockState;
21+
import org.jetbrains.annotations.NotNull;
22+
import org.jetbrains.annotations.Nullable;
23+
import org.spongepowered.asm.mixin.Mixin;
24+
import org.spongepowered.asm.mixin.Shadow;
25+
import org.spongepowered.asm.mixin.injection.At;
26+
import org.spongepowered.asm.mixin.injection.Inject;
27+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
28+
29+
@Mixin(BlockEntityAbstractImpetus.class)
30+
public abstract class MixinBlockEntityAbstractImpetus
31+
extends HexBlockEntity implements IMixinBlockEntityAbstractImpetus
32+
{
33+
@Shadow(remap = false)
34+
@Nullable
35+
protected CircleExecutionState executionState;
36+
37+
public MixinBlockEntityAbstractImpetus(BlockEntityType<?> pType, BlockPos pWorldPosition, BlockState pBlockState) {
38+
super(pType, pWorldPosition, pBlockState);
39+
}
40+
41+
@Shadow
42+
public abstract void startExecution(@Nullable ServerPlayer player);
43+
44+
@SuppressWarnings("AddedMixinMembersNamePattern")
45+
@Override
46+
@NotNull
47+
public InteractionResult startDebugging(@NotNull ServerPlayer caster, int threadId) {
48+
if (executionState != null) return InteractionResult.PASS;
49+
50+
var debugEnv = new CircleDebugEnv(caster, getBlockPos());
51+
try {
52+
HexDebugCoreAPI.INSTANCE.createDebugThread(debugEnv, threadId);
53+
} catch (DebugException ignored) {
54+
return InteractionResult.PASS;
55+
}
56+
57+
startExecution(caster);
58+
((IMixinCircleExecutionState) executionState).setDebugEnv$hexdebug(debugEnv);
59+
60+
return InteractionResult.CONSUME;
61+
}
62+
63+
@Override
64+
public void hexdebug$clearExecutionState() {
65+
executionState = null;
66+
}
67+
68+
@Inject(method = "postPrint", at = @At("HEAD"))
69+
private void hexdebug$printDebugMessage(Component printDisplay, CallbackInfo ci) {
70+
if (executionState != null) {
71+
var debugEnv = ((IMixinCircleExecutionState) executionState).getDebugEnv$hexdebug();
72+
if (debugEnv != null) {
73+
debugEnv.printDebugMessage(printDisplay);
74+
}
75+
}
76+
}
77+
78+
@Inject(method = "postMishap", at = @At("HEAD"))
79+
private void hexdebug$printDebugMishap(Component mishapDisplay, CallbackInfo ci) {
80+
if (executionState != null) {
81+
var debugEnv = ((IMixinCircleExecutionState) executionState).getDebugEnv$hexdebug();
82+
if (debugEnv != null) {
83+
debugEnv.printDebugMessage(mishapDisplay, DebugOutputCategory.STDERR);
84+
}
85+
}
86+
}
87+
88+
@WrapOperation(
89+
method = "postNoExits",
90+
at = @At(
91+
value = "INVOKE",
92+
target = "Lat/petrak/hexcasting/api/casting/circles/BlockEntityAbstractImpetus;postDisplay(Lnet/minecraft/network/chat/Component;Lnet/minecraft/world/item/ItemStack;)V"
93+
)
94+
)
95+
private void hexdebug$printDebugNoExits(
96+
BlockEntityAbstractImpetus instance,
97+
Component error,
98+
ItemStack display,
99+
Operation<Void> original
100+
) {
101+
if (executionState != null) {
102+
var debugEnv = ((IMixinCircleExecutionState) executionState).getDebugEnv$hexdebug();
103+
if (debugEnv != null) {
104+
debugEnv.printDebugMessage(error, DebugOutputCategory.STDERR, false);
105+
}
106+
}
107+
original.call(instance, error, display);
108+
}
109+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package gay.object.hexdebug.mixin;
2+
3+
import at.petrak.hexcasting.api.block.circle.BlockCircleComponent;
4+
import at.petrak.hexcasting.api.casting.eval.ExecutionClientView;
5+
import at.petrak.hexcasting.api.casting.eval.env.CircleCastEnv;
6+
import at.petrak.hexcasting.api.casting.eval.vm.CastingImage;
7+
import at.petrak.hexcasting.api.casting.eval.vm.CastingVM;
8+
import at.petrak.hexcasting.api.casting.iota.Iota;
9+
import at.petrak.hexcasting.api.casting.iota.PatternIota;
10+
import at.petrak.hexcasting.common.blocks.circles.BlockEntitySlate;
11+
import at.petrak.hexcasting.common.blocks.circles.BlockSlate;
12+
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
13+
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
14+
import gay.object.hexdebug.core.api.HexDebugCoreAPI;
15+
import gay.object.hexdebug.core.api.debugging.BaseCircleDebugEnv;
16+
import gay.object.hexdebug.core.api.debugging.DebuggableCircleComponent;
17+
import gay.object.hexdebug.core.api.exceptions.IllegalDebugSessionException;
18+
import net.minecraft.core.BlockPos;
19+
import net.minecraft.core.Direction;
20+
import net.minecraft.server.level.ServerLevel;
21+
import net.minecraft.server.level.ServerPlayer;
22+
import net.minecraft.world.level.block.EntityBlock;
23+
import net.minecraft.world.level.block.SimpleWaterloggedBlock;
24+
import net.minecraft.world.level.block.state.BlockState;
25+
import org.spongepowered.asm.mixin.Mixin;
26+
import org.spongepowered.asm.mixin.injection.At;
27+
28+
import java.util.Collections;
29+
import java.util.List;
30+
31+
@Mixin(BlockSlate.class)
32+
public abstract class MixinBlockSlate
33+
extends BlockCircleComponent
34+
implements EntityBlock, SimpleWaterloggedBlock, DebuggableCircleComponent
35+
{
36+
public MixinBlockSlate(Properties p_49795_) {
37+
super(p_49795_);
38+
}
39+
40+
@SuppressWarnings("AddedMixinMembersNamePattern")
41+
@Override
42+
public void acceptDebugControlFlow(
43+
ServerPlayer caster,
44+
BaseCircleDebugEnv debugEnv,
45+
CastingImage imageIn,
46+
CircleCastEnv env,
47+
Direction enterDir,
48+
BlockPos pos,
49+
BlockState bs
50+
) {
51+
if (!(caster.serverLevel().getBlockEntity(pos) instanceof BlockEntitySlate tile)) return;
52+
53+
List<Iota> iotas;
54+
if (tile.pattern == null) {
55+
iotas = Collections.emptyList();
56+
} else {
57+
iotas = Collections.singletonList(new PatternIota(tile.pattern));
58+
}
59+
60+
// TODO: maybe startExecuting should return something to tell us if we can continue
61+
try {
62+
HexDebugCoreAPI.INSTANCE.startExecuting(debugEnv, env, iotas, imageIn);
63+
} catch (IllegalDebugSessionException ignored) {}
64+
}
65+
66+
@WrapOperation(
67+
method = "acceptControlFlow",
68+
at = @At(
69+
value = "INVOKE",
70+
target = "Lat/petrak/hexcasting/api/casting/eval/vm/CastingVM;queueExecuteAndWrapIota(Lat/petrak/hexcasting/api/casting/iota/Iota;Lnet/minecraft/server/level/ServerLevel;)Lat/petrak/hexcasting/api/casting/eval/ExecutionClientView;"
71+
)
72+
)
73+
private ExecutionClientView hexdebug$skipExecuteIfDebugging(
74+
CastingVM vm,
75+
Iota iota,
76+
ServerLevel world,
77+
Operation<ExecutionClientView> original
78+
) {
79+
var debugEnv = HexDebugCoreAPI.INSTANCE.getDebugEnv(vm.getEnv());
80+
if (debugEnv instanceof BaseCircleDebugEnv circleDebugEnv) {
81+
if (circleDebugEnv.getNewImage() != null) {
82+
vm.setImage(circleDebugEnv.getNewImage());
83+
circleDebugEnv.setNewImage(null);
84+
}
85+
// FIXME: hack
86+
return vm.queueExecuteAndWrapIotas(Collections.emptyList(), world);
87+
}
88+
return original.call(vm, iota, world);
89+
}
90+
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package gay.object.hexdebug.mixin;
2+
3+
import at.petrak.hexcasting.api.casting.circles.BlockEntityAbstractImpetus;
4+
import at.petrak.hexcasting.api.casting.circles.CircleExecutionState;
5+
import at.petrak.hexcasting.api.casting.eval.env.CircleCastEnv;
6+
import at.petrak.hexcasting.api.casting.eval.vm.CastingImage;
7+
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
8+
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
9+
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
10+
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
11+
import gay.object.hexdebug.core.api.HexDebugCoreAPI;
12+
import gay.object.hexdebug.core.api.debugging.DebuggableCircleComponent;
13+
import gay.object.hexdebug.debugger.circles.CircleDebugEnv;
14+
import gay.object.hexdebug.debugger.circles.IMixinCircleExecutionState;
15+
import gay.object.hexdebug.impl.IDebugEnvAccessor;
16+
import net.minecraft.core.BlockPos;
17+
import net.minecraft.core.Direction;
18+
import net.minecraft.nbt.CompoundTag;
19+
import net.minecraft.server.level.ServerLevel;
20+
import net.minecraft.server.level.ServerPlayer;
21+
import org.jetbrains.annotations.Nullable;
22+
import org.spongepowered.asm.mixin.Final;
23+
import org.spongepowered.asm.mixin.Mixin;
24+
import org.spongepowered.asm.mixin.Shadow;
25+
import org.spongepowered.asm.mixin.Unique;
26+
import org.spongepowered.asm.mixin.injection.At;
27+
import org.spongepowered.asm.mixin.injection.Inject;
28+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
29+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
30+
31+
import java.util.List;
32+
33+
@Mixin(CircleExecutionState.class)
34+
public abstract class MixinCircleExecutionState implements IMixinCircleExecutionState {
35+
@Shadow(remap = false)
36+
@Final
37+
public List<BlockPos> reachedPositions;
38+
@Shadow
39+
public BlockPos currentPos;
40+
@Shadow
41+
public Direction enteredFrom;
42+
@Shadow(remap = false)
43+
public CastingImage currentImage;
44+
45+
@Unique
46+
@Nullable
47+
private CircleDebugEnv debugEnv$hexdebug;
48+
49+
@Shadow
50+
@Nullable
51+
public abstract ServerPlayer getCaster(ServerLevel world);
52+
53+
@Nullable
54+
@Override
55+
public CircleDebugEnv getDebugEnv$hexdebug() {
56+
return debugEnv$hexdebug;
57+
}
58+
59+
@Override
60+
public void setDebugEnv$hexdebug(@Nullable CircleDebugEnv debugEnv) {
61+
debugEnv$hexdebug = debugEnv;
62+
}
63+
64+
@SuppressWarnings("UnreachableCode")
65+
@Inject(method = "tick", at = @At("HEAD"), cancellable = true, remap = false)
66+
private void hexdebug$debugTick(BlockEntityAbstractImpetus impetus, CallbackInfoReturnable<Boolean> cir) {
67+
if (debugEnv$hexdebug == null) return;
68+
69+
var world = (ServerLevel) impetus.getLevel();
70+
if (world == null) return;
71+
72+
var bs = world.getBlockState(currentPos);
73+
if (!(bs.getBlock() instanceof DebuggableCircleComponent debuggable)) return;
74+
75+
var caster = getCaster(world);
76+
if (caster == null) return;
77+
78+
// we'll be executing this many times, so only energize etc the first time
79+
if (!debuggable.isEnergized(currentPos, bs, world)) {
80+
bs = debuggable.startEnergized(currentPos, bs, world);
81+
reachedPositions.add(currentPos);
82+
83+
debugEnv$hexdebug.setPaused(true);
84+
85+
var env = new CircleCastEnv(world, (CircleExecutionState) (Object) this);
86+
debuggable.acceptDebugControlFlow(caster, debugEnv$hexdebug, currentImage, env, enteredFrom, currentPos, bs);
87+
}
88+
89+
// if we stopped on entry or a breakpoint, continue ticking but skip the normal logic
90+
// if we got terminated, stop now
91+
// otherwise, do the regular tick logic
92+
if (debugEnv$hexdebug.isPaused()) {
93+
cir.setReturnValue(true);
94+
} else if (HexDebugCoreAPI.INSTANCE.getDebugEnv(caster, debugEnv$hexdebug.getSessionId()) == null) {
95+
cir.setReturnValue(false);
96+
}
97+
}
98+
99+
@WrapOperation(
100+
method = "tick",
101+
at = @At(
102+
value = "INVOKE",
103+
target = "Ljava/util/List;add(Ljava/lang/Object;)Z",
104+
ordinal = 0
105+
),
106+
require = 0,
107+
remap = false
108+
)
109+
private boolean hexdebug$maybeSkipAddingToReachedPositions(
110+
List<Object> instance,
111+
Object pos,
112+
Operation<Boolean> original
113+
) {
114+
if (
115+
pos instanceof BlockPos
116+
&& !reachedPositions.isEmpty()
117+
&& reachedPositions.get(reachedPositions.size() - 1) == pos
118+
) {
119+
return true;
120+
}
121+
return original.call(instance, pos);
122+
}
123+
124+
@ModifyExpressionValue(
125+
method = "tick",
126+
at = @At(
127+
value = "NEW",
128+
target = "(Lnet/minecraft/server/level/ServerLevel;Lat/petrak/hexcasting/api/casting/circles/CircleExecutionState;)Lat/petrak/hexcasting/api/casting/eval/env/CircleCastEnv;"
129+
)
130+
)
131+
private CircleCastEnv hexdebug$setDebugEnv(CircleCastEnv env) {
132+
((IDebugEnvAccessor) env).setDebugEnv$hexdebug(debugEnv$hexdebug);
133+
return env;
134+
}
135+
136+
@Inject(method = "endExecution", at = @At("HEAD"), remap = false)
137+
private void hexdebug$stopDebugging(BlockEntityAbstractImpetus impetus, CallbackInfo ci) {
138+
if (debugEnv$hexdebug != null) {
139+
HexDebugCoreAPI.INSTANCE.removeDebugThread(debugEnv$hexdebug);
140+
debugEnv$hexdebug = null;
141+
}
142+
}
143+
144+
@ModifyReturnValue(method = "save", at = @At("RETURN"))
145+
private CompoundTag hexdebug$saveDebugEnvSessionId(CompoundTag out) {
146+
if (debugEnv$hexdebug != null) {
147+
out.putUUID(TAG_HEXDEBUG_SESSION_ID, debugEnv$hexdebug.getSessionId());
148+
}
149+
return out;
150+
}
151+
152+
@ModifyReturnValue(method = "load", at = @At("RETURN"))
153+
private static CircleExecutionState hexdebug$loadDebugEnv(CircleExecutionState state, CompoundTag nbt, ServerLevel level) {
154+
var caster = state.getCaster(level);
155+
if (caster != null && nbt.contains(TAG_HEXDEBUG_SESSION_ID)) {
156+
var sessionId = nbt.getUUID(TAG_HEXDEBUG_SESSION_ID);
157+
var debugEnv = HexDebugCoreAPI.INSTANCE.getDebugEnv(caster, sessionId);
158+
if (debugEnv instanceof CircleDebugEnv circleEnv && circleEnv.getPos() == state.impetusPos) {
159+
((MixinCircleExecutionState) (Object) state).debugEnv$hexdebug = circleEnv;
160+
}
161+
}
162+
return state;
163+
}
164+
165+
@Unique
166+
private static final String TAG_HEXDEBUG_SESSION_ID = "hexdebug:session_id";
167+
}

0 commit comments

Comments
 (0)