2323import com .sk89q .worldedit .entity .BaseEntity ;
2424import com .sk89q .worldedit .entity .Entity ;
2525import com .sk89q .worldedit .event .extent .EditSessionEvent ;
26+ import com .sk89q .worldedit .extension .platform .Actor ;
2627import com .sk89q .worldedit .extension .platform .Capability ;
2728import com .sk89q .worldedit .extension .platform .Watchdog ;
2829import com .sk89q .worldedit .extent .ChangeSetExtent ;
2930import com .sk89q .worldedit .extent .Extent ;
3031import com .sk89q .worldedit .extent .MaskingExtent ;
3132import com .sk89q .worldedit .extent .NullExtent ;
33+ import com .sk89q .worldedit .extent .TracingExtent ;
3234import com .sk89q .worldedit .extent .buffer .ForgetfulExtentBuffer ;
3335import com .sk89q .worldedit .extent .cache .LastAccessExtentCache ;
3436import com .sk89q .worldedit .extent .inventory .BlockBag ;
107109import com .sk89q .worldedit .util .Direction ;
108110import com .sk89q .worldedit .util .SideEffectSet ;
109111import com .sk89q .worldedit .util .TreeGenerator ;
112+ import com .sk89q .worldedit .util .collection .BlockMap ;
110113import com .sk89q .worldedit .util .collection .DoubleArrayList ;
111114import com .sk89q .worldedit .util .eventbus .EventBus ;
115+ import com .sk89q .worldedit .util .formatting .text .TextComponent ;
116+ import com .sk89q .worldedit .util .formatting .text .TranslatableComponent ;
112117import com .sk89q .worldedit .world .NullWorld ;
113118import com .sk89q .worldedit .world .World ;
114119import com .sk89q .worldedit .world .biome .BiomeType ;
122127import org .slf4j .LoggerFactory ;
123128
124129import java .util .ArrayList ;
130+ import java .util .Collections ;
125131import java .util .HashMap ;
126132import java .util .HashSet ;
133+ import java .util .LinkedHashSet ;
127134import java .util .LinkedList ;
128135import java .util .List ;
129136import java .util .Map ;
130137import java .util .Set ;
138+ import java .util .stream .Collectors ;
131139import javax .annotation .Nullable ;
132140
133141import 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