Skip to content

Commit ffaf8e0

Browse files
authored
Add tracing feature to EditSessions (#1301)
* Add tracing feature to EditSessions This helps track down what plugin (or more specifically, extent) is blocking actions from happening. * Set a trace hook instead of a one-off command * Correct the logic for determining tracing active * Fix extra newline in AbstractPlayerActor * Fix checkstyle * Improve messaging of trace mode
1 parent a4d45b0 commit ffaf8e0

File tree

16 files changed

+549
-76
lines changed

16 files changed

+549
-76
lines changed

worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -396,8 +396,11 @@ public EditSession createEditSession(Player player) {
396396
LocalSession session = WorldEdit.getInstance().getSessionManager().get(wePlayer);
397397
BlockBag blockBag = session.getBlockBag(wePlayer);
398398

399-
EditSession editSession = WorldEdit.getInstance().getEditSessionFactory()
400-
.getEditSession(wePlayer.getWorld(), session.getBlockChangeLimit(), blockBag, wePlayer);
399+
EditSession editSession = WorldEdit.getInstance().newEditSessionBuilder()
400+
.locatableActor(wePlayer)
401+
.maxBlocks(session.getBlockChangeLimit())
402+
.blockBag(blockBag)
403+
.build();
401404
editSession.enableStandardMode();
402405

403406
return editSession;
@@ -414,7 +417,7 @@ public void remember(Player player, EditSession editSession) {
414417
LocalSession session = WorldEdit.getInstance().getSessionManager().get(wePlayer);
415418

416419
session.remember(editSession);
417-
editSession.flushSession();
420+
editSession.close();
418421

419422
WorldEdit.getInstance().flushBlockBag(wePlayer, editSession);
420423
}

worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java

Lines changed: 134 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@
2323
import com.sk89q.worldedit.entity.BaseEntity;
2424
import com.sk89q.worldedit.entity.Entity;
2525
import com.sk89q.worldedit.event.extent.EditSessionEvent;
26+
import com.sk89q.worldedit.extension.platform.Actor;
2627
import com.sk89q.worldedit.extension.platform.Capability;
2728
import com.sk89q.worldedit.extension.platform.Watchdog;
2829
import com.sk89q.worldedit.extent.ChangeSetExtent;
2930
import com.sk89q.worldedit.extent.Extent;
3031
import com.sk89q.worldedit.extent.MaskingExtent;
3132
import com.sk89q.worldedit.extent.NullExtent;
33+
import com.sk89q.worldedit.extent.TracingExtent;
3234
import com.sk89q.worldedit.extent.buffer.ForgetfulExtentBuffer;
3335
import com.sk89q.worldedit.extent.cache.LastAccessExtentCache;
3436
import com.sk89q.worldedit.extent.inventory.BlockBag;
@@ -107,8 +109,11 @@
107109
import com.sk89q.worldedit.util.Direction;
108110
import com.sk89q.worldedit.util.SideEffectSet;
109111
import com.sk89q.worldedit.util.TreeGenerator;
112+
import com.sk89q.worldedit.util.collection.BlockMap;
110113
import com.sk89q.worldedit.util.collection.DoubleArrayList;
111114
import com.sk89q.worldedit.util.eventbus.EventBus;
115+
import com.sk89q.worldedit.util.formatting.text.TextComponent;
116+
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
112117
import com.sk89q.worldedit.world.NullWorld;
113118
import com.sk89q.worldedit.world.World;
114119
import com.sk89q.worldedit.world.biome.BiomeType;
@@ -122,12 +127,15 @@
122127
import org.slf4j.LoggerFactory;
123128

124129
import java.util.ArrayList;
130+
import java.util.Collections;
125131
import java.util.HashMap;
126132
import java.util.HashSet;
133+
import java.util.LinkedHashSet;
127134
import java.util.LinkedList;
128135
import java.util.List;
129136
import java.util.Map;
130137
import java.util.Set;
138+
import java.util.stream.Collectors;
131139
import javax.annotation.Nullable;
132140

133141
import static com.google.common.base.Preconditions.checkArgument;
@@ -186,6 +194,7 @@ public String getDisplayName() {
186194

187195
@SuppressWarnings("ProtectedField")
188196
protected final World world;
197+
private final @Nullable Actor actor;
189198
private final ChangeSet changeSet = new BlockOptimizedHistory();
190199

191200
private @Nullable SideEffectExtent sideEffectExtent;
@@ -201,6 +210,8 @@ public String getDisplayName() {
201210
private final Extent bypassHistory;
202211
private final Extent bypassNone;
203212

213+
private final @Nullable List<TracingExtent> tracingExtents;
214+
204215
private ReorderMode reorderMode = ReorderMode.MULTI_STAGE;
205216

206217
private Mask oldMask;
@@ -212,63 +223,74 @@ public String getDisplayName() {
212223
* @param world the world
213224
* @param maxBlocks the maximum number of blocks that can be changed, or -1 to use no limit
214225
* @param blockBag an optional {@link BlockBag} to use, otherwise null
215-
* @param event the event to call with the extent
226+
* @param actor the actor that owns the session
227+
* @param tracing if tracing is enabled. An actor is required if this is {@code true}
216228
*/
217-
EditSession(EventBus eventBus, World world, int maxBlocks, @Nullable BlockBag blockBag, EditSessionEvent event) {
229+
EditSession(EventBus eventBus, World world, int maxBlocks, @Nullable BlockBag blockBag,
230+
@Nullable Actor actor,
231+
boolean tracing) {
218232
checkNotNull(eventBus);
219233
checkArgument(maxBlocks >= -1, "maxBlocks >= -1 required");
220-
checkNotNull(event);
234+
235+
if (tracing) {
236+
this.tracingExtents = new ArrayList<>();
237+
checkNotNull(actor, "An actor is required while tracing");
238+
} else {
239+
this.tracingExtents = null;
240+
}
221241

222242
this.world = world;
243+
this.actor = actor;
223244

224245
if (world != null) {
246+
EditSessionEvent event = new EditSessionEvent(world, actor, maxBlocks, null);
225247
Watchdog watchdog = WorldEdit.getInstance().getPlatformManager()
226248
.queryCapability(Capability.GAME_HOOKS).getWatchdog();
227249
Extent extent;
228250

229251
// These extents are ALWAYS used
230-
extent = sideEffectExtent = new SideEffectExtent(world);
252+
extent = traceIfNeeded(sideEffectExtent = new SideEffectExtent(world));
231253
if (watchdog != null) {
232254
// Reset watchdog before world placement
233255
WatchdogTickingExtent watchdogExtent = new WatchdogTickingExtent(extent, watchdog);
234-
extent = watchdogExtent;
256+
extent = traceIfNeeded(watchdogExtent);
235257
watchdogExtents.add(watchdogExtent);
236258
}
237-
extent = survivalExtent = new SurvivalModeExtent(extent, world);
238-
extent = new BlockQuirkExtent(extent, world);
239-
extent = new BiomeQuirkExtent(extent);
240-
extent = new ChunkLoadingExtent(extent, world);
241-
extent = new LastAccessExtentCache(extent);
242-
extent = blockBagExtent = new BlockBagExtent(extent, blockBag);
259+
extent = traceIfNeeded(survivalExtent = new SurvivalModeExtent(extent, world));
260+
extent = traceIfNeeded(new BlockQuirkExtent(extent, world));
261+
extent = traceIfNeeded(new BiomeQuirkExtent(extent));
262+
extent = traceIfNeeded(new ChunkLoadingExtent(extent, world));
263+
extent = traceIfNeeded(new LastAccessExtentCache(extent));
264+
extent = traceIfNeeded(blockBagExtent = new BlockBagExtent(extent, blockBag));
243265
extent = wrapExtent(extent, eventBus, event, Stage.BEFORE_CHANGE);
244-
this.bypassReorderHistory = new DataValidatorExtent(extent, world);
266+
this.bypassReorderHistory = traceIfNeeded(new DataValidatorExtent(extent, world));
245267

246268
// This extent can be skipped by calling rawSetBlock()
247-
extent = reorderExtent = new MultiStageReorder(extent, false);
248-
extent = chunkBatchingExtent = new ChunkBatchingExtent(extent);
269+
extent = traceIfNeeded(reorderExtent = new MultiStageReorder(extent, false));
270+
extent = traceIfNeeded(chunkBatchingExtent = new ChunkBatchingExtent(extent));
249271
extent = wrapExtent(extent, eventBus, event, Stage.BEFORE_REORDER);
250272
if (watchdog != null) {
251273
// reset before buffering extents, since they may buffer all changes
252274
// before the world-placement reset can happen, and still cause halts
253275
WatchdogTickingExtent watchdogExtent = new WatchdogTickingExtent(extent, watchdog);
254-
extent = watchdogExtent;
276+
extent = traceIfNeeded(watchdogExtent);
255277
watchdogExtents.add(watchdogExtent);
256278
}
257-
this.bypassHistory = new DataValidatorExtent(extent, world);
279+
this.bypassHistory = traceIfNeeded(new DataValidatorExtent(extent, world));
258280

259281
// These extents can be skipped by calling smartSetBlock()
260-
extent = new ChangeSetExtent(extent, changeSet);
261-
extent = maskingExtent = new MaskingExtent(extent, Masks.alwaysTrue());
262-
extent = changeLimiter = new BlockChangeLimiter(extent, maxBlocks);
282+
extent = traceIfNeeded(new ChangeSetExtent(extent, changeSet));
283+
extent = traceIfNeeded(maskingExtent = new MaskingExtent(extent, Masks.alwaysTrue()));
284+
extent = traceIfNeeded(changeLimiter = new BlockChangeLimiter(extent, maxBlocks));
263285
extent = wrapExtent(extent, eventBus, event, Stage.BEFORE_HISTORY);
264-
this.bypassNone = new DataValidatorExtent(extent, world);
286+
this.bypassNone = traceIfNeeded(new DataValidatorExtent(extent, world));
265287
} else {
266288
Extent extent = new NullExtent();
267-
extent = survivalExtent = new SurvivalModeExtent(extent, NullWorld.getInstance());
268-
extent = blockBagExtent = new BlockBagExtent(extent, blockBag);
269-
extent = reorderExtent = new MultiStageReorder(extent, false);
270-
extent = maskingExtent = new MaskingExtent(extent, Masks.alwaysTrue());
271-
extent = changeLimiter = new BlockChangeLimiter(extent, maxBlocks);
289+
extent = traceIfNeeded(survivalExtent = new SurvivalModeExtent(extent, NullWorld.getInstance()));
290+
extent = traceIfNeeded(blockBagExtent = new BlockBagExtent(extent, blockBag));
291+
extent = traceIfNeeded(reorderExtent = new MultiStageReorder(extent, false));
292+
extent = traceIfNeeded(maskingExtent = new MaskingExtent(extent, Masks.alwaysTrue()));
293+
extent = traceIfNeeded(changeLimiter = new BlockChangeLimiter(extent, maxBlocks));
272294
this.bypassReorderHistory = extent;
273295
this.bypassHistory = extent;
274296
this.bypassNone = extent;
@@ -277,10 +299,26 @@ public String getDisplayName() {
277299
setReorderMode(this.reorderMode);
278300
}
279301

302+
private Extent traceIfNeeded(Extent input) {
303+
Extent output = input;
304+
if (tracingExtents != null) {
305+
TracingExtent newExtent = new TracingExtent(input);
306+
output = newExtent;
307+
tracingExtents.add(newExtent);
308+
}
309+
return output;
310+
}
311+
280312
private Extent wrapExtent(Extent extent, EventBus eventBus, EditSessionEvent event, Stage stage) {
313+
// NB: the event does its own tracing
281314
event = event.clone(stage);
282315
event.setExtent(extent);
316+
boolean tracing = tracingExtents != null;
317+
event.setTracing(tracing);
283318
eventBus.post(event);
319+
if (tracing) {
320+
tracingExtents.addAll(event.getTracingExtents());
321+
}
284322
return event.getExtent();
285323
}
286324

@@ -298,6 +336,18 @@ boolean commitRequired() {
298336
return false;
299337
}
300338

339+
/**
340+
* Get the current list of active tracing extents.
341+
*/
342+
private List<TracingExtent> getActiveTracingExtents() {
343+
if (tracingExtents == null) {
344+
return ImmutableList.of();
345+
}
346+
return tracingExtents.stream()
347+
.filter(TracingExtent::isActive)
348+
.collect(Collectors.toList());
349+
}
350+
301351
/**
302352
* Turns on specific features for a normal WorldEdit session, such as
303353
* {@link #setBatchingChunks(boolean)
@@ -322,7 +372,7 @@ public void setReorderMode(ReorderMode reorderMode) {
322372
throw new IllegalArgumentException("An EditSession without a reorder extent tried to use it for reordering!");
323373
}
324374
if (commitRequired()) {
325-
flushSession();
375+
internalFlushSession();
326376
}
327377

328378
this.reorderMode = reorderMode;
@@ -420,14 +470,14 @@ public void enableQueue() {
420470
}
421471

422472
/**
423-
* Disable the queue. This will {@linkplain #flushSession() flush the session}.
473+
* Disable the queue. This will flush the session.
424474
*
425475
* @deprecated Use {@link EditSession#setReorderMode(ReorderMode)} with another mode instead.
426476
*/
427477
@Deprecated
428478
public void disableQueue() {
429479
if (isQueueEnabled()) {
430-
flushSession();
480+
internalFlushSession();
431481
}
432482
setReorderMode(ReorderMode.NONE);
433483
}
@@ -548,8 +598,7 @@ public boolean isBatchingChunks() {
548598
}
549599

550600
/**
551-
* Enable or disable chunk batching. Disabling will
552-
* {@linkplain #flushSession() flush the session}.
601+
* Enable or disable chunk batching. Disabling will flush the session.
553602
*
554603
* @param batchingChunks {@code true} to enable, {@code false} to disable
555604
*/
@@ -561,7 +610,7 @@ public void setBatchingChunks(boolean batchingChunks) {
561610
return;
562611
}
563612
if (!batchingChunks && isBatchingChunks()) {
564-
flushSession();
613+
internalFlushSession();
565614
}
566615
chunkBatchingExtent.setEnabled(batchingChunks);
567616
}
@@ -575,7 +624,7 @@ public void setBatchingChunks(boolean batchingChunks) {
575624
public void disableBuffering() {
576625
// We optimize here to avoid repeated calls to flushSession.
577626
if (commitRequired()) {
578-
flushSession();
627+
internalFlushSession();
579628
}
580629
setReorderMode(ReorderMode.NONE);
581630
if (chunkBatchingExtent != null) {
@@ -780,7 +829,7 @@ public void undo(EditSession editSession) {
780829
UndoContext context = new UndoContext();
781830
context.setExtent(editSession.bypassHistory);
782831
Operations.completeBlindly(ChangeSetExecutor.createUndo(changeSet, context));
783-
editSession.flushSession();
832+
editSession.internalFlushSession();
784833
}
785834

786835
/**
@@ -792,7 +841,7 @@ public void redo(EditSession editSession) {
792841
UndoContext context = new UndoContext();
793842
context.setExtent(editSession.bypassHistory);
794843
Operations.completeBlindly(ChangeSetExecutor.createRedo(changeSet, context));
795-
editSession.flushSession();
844+
editSession.internalFlushSession();
796845
}
797846

798847
/**
@@ -825,18 +874,67 @@ public List<? extends Entity> getEntities() {
825874
}
826875

827876
/**
828-
* Closing an EditSession {@linkplain #flushSession() flushes its buffers}.
877+
* Closing an EditSession flushes its buffers to the world, and performs other
878+
* cleanup tasks.
829879
*/
830880
@Override
831881
public void close() {
832-
flushSession();
882+
internalFlushSession();
883+
dumpTracingInformation();
884+
}
885+
886+
private void dumpTracingInformation() {
887+
if (this.tracingExtents == null) {
888+
return;
889+
}
890+
List<TracingExtent> tracingExtents = getActiveTracingExtents();
891+
assert actor != null;
892+
if (tracingExtents.isEmpty()) {
893+
actor.printError(TranslatableComponent.of("worldedit.trace.no-tracing-extents"));
894+
return;
895+
}
896+
// find the common stacks
897+
Set<List<TracingExtent>> stacks = new LinkedHashSet<>();
898+
Map<List<TracingExtent>, BlockVector3> stackToPosition = new HashMap<>();
899+
Set<BlockVector3> touchedLocations = Collections.newSetFromMap(BlockMap.create());
900+
for (TracingExtent tracingExtent : tracingExtents) {
901+
touchedLocations.addAll(tracingExtent.getTouchedLocations());
902+
}
903+
for (BlockVector3 loc : touchedLocations) {
904+
List<TracingExtent> stack = tracingExtents.stream()
905+
.filter(it -> it.getTouchedLocations().contains(loc))
906+
.collect(Collectors.toList());
907+
boolean anyFailed = stack.stream()
908+
.anyMatch(it -> it.getFailedActions().containsKey(loc));
909+
if (anyFailed && stacks.add(stack)) {
910+
stackToPosition.put(stack, loc);
911+
}
912+
}
913+
stackToPosition.forEach((stack, position) -> {
914+
// stack can never be empty, something has to have touched the position
915+
TracingExtent failure = stack.get(0);
916+
actor.printDebug(TranslatableComponent.builder("worldedit.trace.action-failed")
917+
.args(
918+
TextComponent.of(failure.getFailedActions().get(position).toString()),
919+
TextComponent.of(position.toString()),
920+
TextComponent.of(failure.getExtent().getClass().getName())
921+
)
922+
.build());
923+
});
833924
}
834925

835926
/**
836927
* Communicate to the EditSession that all block changes are complete,
837928
* and that it should apply them to the world.
929+
*
930+
* @deprecated Replace with {@link #close()} for proper cleanup behavior.
838931
*/
932+
@Deprecated
839933
public void flushSession() {
934+
internalFlushSession();
935+
}
936+
937+
private void internalFlushSession() {
840938
Operations.completeBlindly(commit());
841939
}
842940

0 commit comments

Comments
 (0)