Solved Efficient way of getting all blocks in a chunk?

Discussion in 'Plugin Development' started by iTristan, Apr 22, 2013.

Thread Status:
Not open for further replies.
  1. Offline

    iTristan

    Hello,

    I am currently developing a security plugin of the famous anarchy server 2B2T. This plugin will be used to remove illegal items that have been obtained through various glitches/hacks/exploits from the past 3 years.

    To do this, I would like the plugin to hook into the chunkLoadEvent and check for tile entities (chests/dispensers/furnaces/droppers etc.) on the loaded chunks.

    I am currently thinking of running an asynchronous task too check for chests with getTileEntities() (No Idea if that works).

    Could anyone tell me if this is right or if I'm doing something wrong/something I can do to make the code run faster.

    -Tristan
     
  2. Offline

    Tzeentchful

    I've done something similar before for remove items from chests here is the code i used.
    Code:
            getServer().getScheduler().runTaskAsynchronously(this, new Runnable() {
     
                @Override
                public void run() {
     
                    int count = 0;
                    for(Chunk chunk : Bukkit.getServer().getWorlds().get(0).getLoadedChunks())
                    {
                        for(BlockState e : chunk.getTileEntities())
                        {
                            if(e instanceof Chest)
                            {
                                Chest chest = (Chest) e;
     
     
                                ListIterator<ItemStack> it = chest.getInventory().iterator();
                                while(it.hasNext()){
                                    ItemStack is = it.next();
                                    if(is != null && is.getType() == Material.CHAINMAIL_CHESTPLATE){//searh for chainmail chestplate
                                        chest.getInventory().removeItem(is);
                                        count++;
                                    }
                                }
                            }
                        }
                    }
     
                    player.sendMessage(ChatColor.DARK_AQUA + "Found and removed " + ChatColor.DARK_GREEN + count + " items");
     
                }       
            });
     
  3. Offline

    iTristan

    Thanks a lot! I will try the code out.
     
  4. Offline

    Comphenix

    I would recommend against using asynchronous threads in this case. You cannot safely call the Bukkit API on a different thread without risking a crash (ConcurrentModificationException) or worse - corrupt data. Unfortunately, these errors are not necessarily guaranteed to occur on every run, so you may think the asynchronous code above works just fine. It doesn't.

    Instead, you will have to split up the work into several parts, and execute a small portion of it every tick (download):
    Code:java
    1. package com.comphenix.example;
    2.  
    3. import java.util.ArrayDeque;
    4. import java.util.Arrays;
    5. import java.util.Queue;
    6. import java.util.concurrent.TimeUnit;
    7.  
    8. import org.bukkit.Bukkit;
    9. import org.bukkit.Chunk;
    10. import org.bukkit.block.BlockState;
    11. import org.bukkit.plugin.Plugin;
    12. import org.bukkit.scheduler.BukkitScheduler;
    13. import org.bukkit.scheduler.BukkitTask;
    14.  
    15. import com.google.common.base.Preconditions;
    16.  
    17. public abstract class TileEntityProcessor {
    18. private static final int TICK_REPEAT_DELAY = 1;
    19. private static final int TICK_DELAY = 1;
    20.  
    21. private Queue<Chunk> chunks;
    22. private Queue<BlockState> tileEntities;
    23.  
    24. private final long tickProcessingTimeNano;
    25. private BukkitTask task;
    26.  
    27. public TileEntityProcessor(long tickProcessingTime, TimeUnit tickProcessingUnit) {
    28. Preconditions.checkNotNull(tickProcessingUnit, "Tick processing unit cannot be NULL.");
    29.  
    30. this.chunks = new ArrayDeque<Chunk>();
    31. this.tickProcessingTimeNano = tickProcessingUnit.toNanos(tickProcessingTime);
    32. }
    33.  
    34. /**
    35.   * Process a given tile entity.
    36.   * @param state - the entity to process.
    37.   */
    38. protected abstract void processTileEntity(BlockState state);
    39.  
    40. /**
    41.   * Determine if the entity processor is running.
    42.   * @return TRUE if it is, FALSE otherwise.
    43.   */
    44. public final boolean isRunning() {
    45. return task != null;
    46. }
    47.  
    48. /**
    49.   * Begin processing tile entities.
    50.   * @param plugin - the current plugin.
    51.   */
    52. public final void start(Plugin plugin) {
    53. start(Bukkit.getScheduler(), plugin);
    54. }
    55.  
    56. /**
    57.   * Begin processing tile entities.
    58.   * @param scheduler - the current Bukkit scehduler.
    59.   * @param plugin - the current plugin.
    60.   */
    61. public final void start(BukkitScheduler scheduler, Plugin plugin) {
    62. Preconditions.checkState(!isRunning(), "Cannot start a processor twice.");
    63. Preconditions.checkNotNull(scheduler, "Scheduler cannot be NULL.");
    64. Preconditions.checkNotNull(plugin, "Plugin cannot be NULL.");
    65.  
    66. scheduler.runTaskTimer(plugin, getRunnable(), TICK_DELAY, TICK_REPEAT_DELAY);
    67. }
    68.  
    69. /**
    70.   * Stop processing tile entities.
    71.   *
    72.   */
    73. public final void stop() {
    74. Preconditions.checkState(isRunning(), "Cannot stop a stopped processor.");
    75.  
    76. task.cancel();
    77. task = null;
    78. }
    79.  
    80. private Runnable getRunnable() {
    81. return new Runnable() {
    82. @Override
    83. public void run() {
    84. long start = System.nanoTime();
    85.  
    86. while (true) {
    87. if (increment()) {
    88. // Handle timeout
    89. if (!processTileEntities(start))
    90. break;
    91.  
    92. } else {
    93. // Don't repeat
    94. stop();
    95. break;
    96. }
    97. }
    98. }
    99. };
    100. }
    101.  
    102. private boolean processTileEntities(long start) {
    103. while (!tileEntities.isEmpty()) {
    104. // Timeout?
    105. if (System.nanoTime() > start + tickProcessingTimeNano)
    106. return false;
    107.  
    108. BlockState state = tileEntities.poll();
    109. processTileEntity(state);
    110. }
    111.  
    112. // Process more!
    113. return true;
    114. }
    115.  
    116. private boolean increment() {
    117. if (isEmpty(tileEntities)) {
    118. // Process next chunk
    119. if (!isEmpty(chunks)) {
    120. tileEntities = new ArrayDeque<BlockState>(
    121. Arrays.asList(chunks.poll().getTileEntities())
    122. );
    123. return true;
    124. }
    125. }
    126. return false;
    127. }
    128.  
    129. private boolean isEmpty(Queue<?> queue) {
    130. return queue == null || queue.isEmpty();
    131. }
    132. }

    To use it, decide on the amount of work to perform every tick (maximum 50 milliseconds) and start it:
    Code:java
    1. public class ExampleMod extends JavaPlugin {
    2. @Override
    3. public void onEnable() {
    4. TileEntityProcessor processor = new TileEntityProcessor(10, TimeUnit.MILLISECONDS) {
    5. @Override
    6. protected void processTileEntity(BlockState state) {
    7. // Whatever you need to do per tile entity!
    8. if (state instanceof Chest) {
    9. Chest chest = (Chest) state;
    10. Inventory inventory = chest.getInventory();
    11.  
    12. for (ItemStack stack : inventory.getContents()) {
    13. if (stack != null && stack.getType() == Material.CHAINMAIL_CHESTPLATE) {
    14. inventory.remove(stack);
    15. }
    16. }
    17. }
    18. }
    19. };
    20. processor.start(this);
    21. }
    22. }

    EDIT: Changed the code to be much more organized. The tile entity processing is now moved to a (inner) sub-class of TileEntityProcessor.
     
    iTristan and desht like this.
  5. Offline

    iTristan

    Thanks I will try the code.
     
Thread Status:
Not open for further replies.

Share This Page