How can I prevent the server, from being overloaded?

Discussion in 'Plugin Development' started by LukeSFT, Oct 23, 2012.

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

    LukeSFT

    I start the server with my plugin and the server gets overloaded.
    Here is my Plugin:
    Code:
    package de.LukeSFT.SnowPile;
     
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Random;
     
    import org.bukkit.Chunk;
    import org.bukkit.Material;
    import org.bukkit.World;
    import org.bukkit.block.Biome;
    import org.bukkit.block.Block;
    import org.bukkit.plugin.java.JavaPlugin;
     
    public class Snows extends JavaPlugin {
       
        Chunk[] loadedChunks;
        List<Biome> snowBiomes = new ArrayList<Biome>();
        boolean on = true;
        public void onEnable(){
            this.saveDefaultConfig();
            boolean conf = false;
            while(!conf){conf = config();}
            snow();
            //System.out.println(dur())
            System.out.println("[SnowPile] was enabled.");
           
        }
       
        public void onDisable(){
            on = false;
            System.out.println("[SnowPile] was disabled.");
        }
        public boolean config(){
            if(this.getConfig().getBoolean("biomes.taiga")){
                snowBiomes.add(Biome.TAIGA);
            }
            if(this.getConfig().getBoolean("biomes.frozen_ocean")){
                snowBiomes.add(Biome.FROZEN_OCEAN);
            }
            if(this.getConfig().getBoolean("biomes.frozen_river")){
                snowBiomes.add(Biome.FROZEN_RIVER);
            }
            if(this.getConfig().getBoolean("biomes.ice_mountains")){
                snowBiomes.add(Biome.ICE_MOUNTAINS);
            }
            if(this.getConfig().getBoolean("biomes.ice_plains")){
                snowBiomes.add(Biome.ICE_PLAINS);
            }
            if(this.getConfig().getBoolean("biomes.taiga_hills")){
                snowBiomes.add(Biome.TAIGA_HILLS);
            }
            return true;
        }
        public boolean dur(){
            World w = getServer().getWorld(this.getConfig().getString("world"));
            System.out.println("storm:" + w.hasStorm());
            return w.isThundering();
        }
        public void snow(){
            World w = getServer().getWorld(this.getConfig().getString("world"));
            System.out.println("act");
            if(w.hasStorm()){
                //System.out.println("thunder");
                Random random = new Random();
                loadedChunks = w.getLoadedChunks();
                for(Chunk ch : loadedChunks){
                    int x = 0;
                    while(x != 16){
                        int z = 0;
                        while(z != 16){
                            int y = 127;
                            while(y != -1){
                                Block b = ch.getBlock(x, y, z);
                                if(b.getTypeId() != 0){
                                    if( snowBiomes.contains(b.getBiome()) ){
                                        if (random.nextInt(200) == 1){
                                            b.setType(Material.DIAMOND_BLOCK);
                                            //System.out.println("s");
                                            y = 0;
                                        }
                                    }
                                }
                                y--;
                            }
                            z++;
                            this.getServer().getScheduler().scheduleSyncDelayedTask(this, new Runnable() {public void run() {}}, 20L);
                        }
                        x++;
                    }
                }
            }
            if(on){
                this.getServer().getScheduler().scheduleSyncDelayedTask(this, new Runnable() {public void run() { snow(); }}, 20L);
            }
        }
    }
    
     
  2. Offline

    skore87

    Put it in a separate thread/scheduler and it should speed up a little. Also you're starting a scheduler with no code to run?

    Lastly, you're telling it to continually do a random number until it is equal to 1. So of course that is going to be slow.
     
  3. Offline

    LukeSFT

    skore87:
    The Scheduler without code in it is to wait a little bit, so that it isn't too fas

    Is there a better way to do that what I did?

    What do you mean by:
     
  4. posiable infinity loop:
    while(!conf){conf = config();}
     
  5. Offline

    LukeSFT

  6. that on that spot an infinity loop can occur
     
  7. Offline

    LukeSFT

    No.
    look at hte function, named config.
     
  8. Offline

    rmb938

    That will slow down the whole server since it is on the main thread. If you don't want the code to run to fast then use some sort of timer system like a sync repeating task or something

    He can't do that because he is updating blocks which is not thread safe.
     
  9. Offline

    skore87

    They really just need to make some of these methods synchronized so it would be still beneficial to use threads with those methods. All it takes is one modifier "synchronized" on the method.

    Also, if you're trying to slow down the execution, putting it in a separate thread and then calling Thread.sleep(time) would work.
     
  10. Offline

    LukeSFT

    skore87: only Thread.sleep(10);?
    That doesn't work.
     
  11. If you use an SyncDelayedTask or SyncRepeatingTask it is thread safe.
     
  12. Offline

    Ewe Loon

    ok, firstlt, there is no infinate loop, the config function always returns true,
    but the point is, in the onenable function, there is no need to even check if it has been called

    point 2,the problem isnt in the onenable its in the snow function

    point 3 , last time i read the docs, they said "Dont call api functions from async tasks", the loaded chunk functions are as API as it gets, so Asynck tast is out

    point 4 NEVER cause delays in Sync tasks, it stops the server

    point 5, calling a sync delayed task as is coded will not cause a delay of any significance, and in that case is unnecessary, and should be removed

    How to fix, easy , be smarter with the code

    Im typing this directly, so expect a few errors
    since there approximatly 50% of the blocks below 127 are air that means each column contains about 64 solid blocks
    that means each chunk contains about 16000 solid blocks, since about 0.5% of them will be changed that makes about 80 being changed
    so, rather than checking all the blocks why not just check 160 blocks in random positions 50% being solis will give about the same result

    Code:
    public void snow(){
            World w = getServer().getWorld(this.getConfig().getString("world"));
            System.out.println("act");
            if(w.hasStorm()){
                //System.out.println("thunder");
                Random random = new Random();
                loadedChunks = w.getLoadedChunks();
                for(Chunk ch : loadedChunks){
                    for(int n=0;n<160;n++){
                        int x=random.nextInt(16);
                        int z=random.nextInt(16);
                        // add 1 to y to avoid removing the bottom layer of bedrock
                        // if you want to replace the bottom layer of bedrock use int y=random.nextInt(127);
                        int y=random.nextInt(126)+1;
                        Block b = ch.getBlock(x, y, z);
                        if(b.getTypeId() != 0){
                            if( snowBiomes.contains(b.getBiome()) ){
                                b.setType(Material.DIAMOND_BLOCK);
                          }
                      }
                }
             
            }
       if(on){
            this.getServer().getScheduler().scheduleSyncDelayedTask(this, new Runnable() {public void run() { snow(); }}, 20L);
        }
        }
     
  13. Offline

    LukeSFT

    hmm
    sounds nice
    I'll think about this
    Thanks.
     
  14. Offline

    Ewe Loon

    another thing that might help is "Spreding the load"
    call snow more often, but not process all the chunks
    for example rather than calling snow every 20 cycles, call it every 2 and only process 10% of the chunks each time
     
  15. Offline

    stoneminer02

    Just edit in bukkit.yml
    Code:
    warn-on-overload:true
    To
    Code:
    warn-on-overload:false
     
  16. Offline

    skore87

    In the idea of spreading the load as Loon mentions, you can recursively call the function in a sync delayed task that then creates a delayed task again if there is any remaining chunks to process.
     
  17. Offline

    LukeSFT

    I changed it with the things Loon said to this:
    Class Snows (open)
    Code:
    package de.LukeSFT.SnowPile;
     
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Random;
     
    import org.bukkit.Chunk;
    import org.bukkit.Material;
    import org.bukkit.World;
    import org.bukkit.block.Biome;
    import org.bukkit.block.Block;
    import org.bukkit.plugin.java.JavaPlugin;
     
    public class Snows extends JavaPlugin{
     
        Chunk[] loadedChunks;
        List<Biome> snowBiomes = new ArrayList<Biome>();
        World w;
        boolean on = true;
        public void onEnable(){
            this.saveDefaultConfig();
            boolean conf = false;
            while(!conf){conf = config();}
            World w = getServer().getWorld(this.getConfig().getString("world"));
            if(w == null){
                System.out.println("w = null");
            }
            SnowThread snowthr = new SnowThread(this , w , snowBiomes);
            //snow();
            System.out.println("[SnowPile] was enabled.");
     
        }
     
        public void onDisable(){
            SnowThread.on = false;
            System.out.println("[SnowPile] was disabled.");
        }
        public boolean config(){
            if(this.getConfig().getBoolean("biomes.taiga")){
                snowBiomes.add(Biome.TAIGA);
            }
            if(this.getConfig().getBoolean("biomes.frozen_ocean")){
                snowBiomes.add(Biome.FROZEN_OCEAN);
            }
            if(this.getConfig().getBoolean("biomes.frozen_river")){
                snowBiomes.add(Biome.FROZEN_RIVER);
            }
            if(this.getConfig().getBoolean("biomes.ice_mountains")){
                snowBiomes.add(Biome.ICE_MOUNTAINS);
            }
            if(this.getConfig().getBoolean("biomes.ice_plains")){
                snowBiomes.add(Biome.ICE_PLAINS);
            }
            if(this.getConfig().getBoolean("biomes.taiga_hills")){
                snowBiomes.add(Biome.TAIGA_HILLS);
            }
            return true;
        }
    }

    and this:
    Class SnowThread (open)
    Code:
    package de.LukeSFT.SnowPile;
     
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Random;
     
    import org.bukkit.Chunk;
    import org.bukkit.Material;
    import org.bukkit.World;
    import org.bukkit.block.Biome;
    import org.bukkit.block.Block;
     
    public class SnowThread extends Thread{
        Snows plugin;
        World w;
        Chunk[] loadedChunks;
        List<Biome> snowBiomes = new ArrayList<Biome>();
        static boolean on = true;
        public SnowThread(Snows instance , World world , List<Biome> snowB ){
            plugin = instance;
            w = world;
            snowBiomes = snowB;
            snow();
        }
        public void snow(){
            while(on){
                System.out.println("act");
                if(w.hasStorm()){
                    //System.out.println("thunder");
                    Random random = new Random();
                    loadedChunks = w.getLoadedChunks();
                    for(Chunk ch : loadedChunks){
                        if(random.nextInt(9) == 1){
                        int x = 0;
                        while(x != 16){
                            if(random.nextInt(10) == 1 ){
                            int z = 0;
                            while(z != 16){
                                if(random.nextInt(5) == 1){
                                int y = 127;
                                while(y != -1){
                                    Block b = ch.getBlock(x, y, z);
                                    if(b.getTypeId() != 0){
                                        if( snowBiomes.contains(b.getBiome()) ){
                                            if (random.nextInt(20) == 1){
                                                b.setType(Material.DIAMOND_BLOCK);
                                                System.out.println("s");
                                                y = 0;
                                            }
                                        }
                                    }
                                    y--;
                                }
                                z++;
                                try {
                                    sleep(10);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }
                            }
                            x++;
                        }
                        }
                    }
                    }
                }
                /*if(on){
                    this.getServer().getScheduler().scheduleSyncDelayedTask(this, new Runnable() {public void run() { snow(); }}, 20L);
                }*/
                try {
                    sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
     
    
     
  18. Offline

    desht

    Or use a sync repeating task, cancelling when all chunks are processed.

    The important thing is not to spend too much time in each individual call, or you will cause server lag. Each tick is 50ms, in which time all plugins have to complete their event handlers & scheduled tasks (in addition to the main server overhead). So you need to minimise time spent in each call, which usually means breaking up the work you need to do into chunks.

    Another complication is that the set of loaded chunks is likely to change regularly as players log in and move around. Just calling your snow() method when the plugin starts and getting the chunks loaded at that time is only likely to affect a few chunks. It's also going to end up processing the same chunks multiple times (every time the server starts up) so you'll eventually end up with a very high density of diamond blocks. Is that really what you want?
     
  19. Offline

    LukeSFT

    Is this right what i posted above?
     
  20. Offline

    desht

    What you've posted above is guaranteed to severely lag your server. You're iterating through thousands of blocks (~32,000 per loaded chunk) every time the task is called. You really need to go back and look at what Ewe Loon posted - it's a much better way to do it.

    But far worse, you're calling sleep() from a sync task. Your server will grind to halt when that happens. You must not call sleep() from the main thread.
     
    Milkywayz and kroltan like this.
  21. Offline

    LukeSFT

    desht: this is not the main thread. This is antoher class, where I extended Thread.
    Also I can't use what Ewe Loon posted, because I don't want to replace some random solid blocks. I want to replace some random solid blocks, that are the highest solid blocks with this x and z coordinate.
     
  22. Offline

    desht

    a) That isn't how you extend Thread (there are plenty of docs out there on using threads in Java), and b) You must not call Bukkit/CraftBukkit methods from another thread anyway, unless you and your players enjoy random exceptions and/or world corruption.

    Seriously. Go back and read what Ewe Loon posted.
     
  23. Offline

    LukeSFT

    desht: to a) Oh I thought that was right, because I read this from a java documentation .
    to b) I didn't know that.
    What Ewe Loon posted is no alternative, because it doesn't do what I want to do, as I said in the post from above.
    Do you have a solution?
     
  24. Offline

    Ewe Loon

    It most definatly is the main thread, Its called from onenable() which is the main thread
    if you dont beleive me, put an infinate loop in it and see what happen

    basics on tasks
    all sync tasks are the main thread
    anything you call from a sync task is the same task, and therefore the main thread
    all async tasks are not the main thread
    if you schedule a sync-task from an async task(not main thread) it becomes the main thread
    if an event does not have an isAsynchronous() function then it is the main task
    if an event has an isAsynchronous() function then call this function to find out if it is the main task (false = main task)
    Never access API stuff like chunks from an async task (it could unload while you are accessing it)
    Never do anything in the main task that will cause a delay, eg. network access, sleeping the task

    more info can be found here http://wiki.bukkit.org/Scheduler_Programming


    Code:
        public void snow(){
            World w = getServer().getWorld(this.getConfig().getString("world"));
            System.out.println("act");
            if(w.hasStorm()){
                //System.out.println("thunder");
                Random random = new Random();
                loadedChunks = w.getLoadedChunks();
                for(Chunk ch : loadedChunks){
                    int x = 0;
                    while(x != 16){
                        int z = 0;
                        while(z != 16){
                            if( snowBiomes.contains(b.getBiome()) ){ //move this up since the whole column is the same biome
                                int y = 127;
                                while(y != -1){
                                    Block b = ch.getBlock(x, y, z);
                                    if(b.getTypeId() != 0){
                                        if (random.nextInt(200) == 1){
                                            b.setType(Material.DIAMOND_BLOCK);
                                            //System.out.println("s");
                                            // y = 0; this is in the wrong place , will only call when a block is changed not when one is found
                                        }
                                        y=0; // block is not air so no further search
                                    }
                                    y--;  // moved up because biome check moved
                                }
                            }
                            z++;
                        }
                        x++;
                    }
                }
            }
            if(on){
                this.getServer().getScheduler().scheduleSyncDelayedTask(this, new Runnable() {public void run() { snow(); }}, 20L);
            }
        }
    }
    
    the changes i have made to the above code will help, but it will probibly still cause problems if the server has a high population
    you might also try using the Chunk.getChunkSnapshot().getHighestBlockYAt(x,z) to find the highest block, it might prove to be faster than searching yourself


    if you have worldedit I suggest you do a block count on the areas that your code will have been run on, there will be diamond block under the ground because you had the "y=0" in the wrong place

    after thinking about it , it would be better to do the if (random.nextInt(200) == 1){ before the search for the top block
    another-words, nest the y loop inside the if (random ...

    another option would be to randomly select 1 x/z column per chunk to change as that would be 1/256 which isnt far from 1/200 that you currently have

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 29, 2016
    ferrybig likes this.
  25. Offline

    LukeSFT

    Ewe Loon: good idea.
    Thanks.
    But what is the sense of a ChunkSnapshot?
     
  26. Offline

    Ewe Loon

    as far as i know ChunkSnapshot is supposed to be faster for reading , but not writable
    it also has a getHighestBlockYAt(x,z) function that might be of help to you
     
  27. Offline

    LukeSFT

  28. Offline

    Courier

    ChunkSnapshots are for creating thread-safe read-only copies of a chunk. It creates a copy of the entire chunk, so it is not particularly fast.
    World has the same method.
     
  29. Offline

    nisovin

    It looks like you're trying to spawn snow on 1/200 columns in each chunk. That ends up being about 1 per chunk (a bit more). It makes a lot more sense to just generate two random numbers between 0 and 15 for each chunk, and use those as x and z coordinates for the column. Then you use the getHighestBlockYAt() method to find the surface block.

    This completely removes the need to iterate through the entire chunk, and achieves roughly the same result.
     
  30. Offline

    Icyene

    No offense, that code is the VERY inefficient way to get random blocks in chunks, and will inherently lead to overload warnings.. Here is an alternative:

    Code:Java
    1.  
    2.  
    3. /*
    4. * @author Icyene
    5. */
    6. public class BlockTickSelector {
    7.  
    8. private WorldServer world;
    9. private Method recheckGaps;
    10. private int chan;
    11. private final Random rand = new Random();
    12.  
    13. public BlockTickSelector(World world, int selChance)
    14.  
    15. this.world = ((CraftWorld) world).getHandle();
    16.  
    17. double version;
    18. String vers = Bukkit.getServer().getVersion();
    19. if (vers.contains("1.2.")) {
    20. version = 1.2;
    21. } else {
    22. if (vers.contains("1.3.")) {
    23. version = 1.3;
    24. } else {
    25. //Bad things may happen...
    26. }
    27. }
    28.  
    29. recheckGaps = Chunk.class.getDeclaredMethod(version == 1.3 ? "k" : "o"); //If 1.3.X, method is named "k", else "o".
    30. recheckGaps.setAccessible(true); //Is private by default
    31. }
    32.  
    33. public ArrayList<ChunkCoordIntPair> getRandomTickedChunks() throws InvocationTargetException, IllegalAccessException {
    34.  
    35. ArrayList<ChunkCoordIntPair> doTick = new ArrayList<ChunkCoordIntPair>();
    36.  
    37. if (world.players.isEmpty()) {
    38. return doTick;
    39. }
    40.  
    41. List<org.bukkit.Chunk> loadedChunks = Arrays.asList(world.getWorld().getLoadedChunks());
    42.  
    43. for (Object ob : world.players) {
    44.  
    45. EntityHuman entityhuman = (EntityHuman) ob;
    46. int eX = (int) Math.floor(entityhuman.locX / 16), eZ = (int) Math.floor(entityhuman.locZ / 16);
    47.  
    48. for (int x = -7; x <= 7; x++) {
    49. for (int z = -7; z <= 7; z++) {
    50. if (loadedChunks.contains(world.getChunkAt(x + eX, z + eZ).bukkitChunk)) { //Check if the bukkit chunk exists
    51. doTick.add(new ChunkCoordIntPair(x + eX, z + eZ));
    52. }
    53. }
    54. }
    55. }
    56. return doTick; //Empty list = failure
    57. }
    58.  
    59. public ArrayList<Block> getRandomTickedBlocks()
    60.  
    61. ArrayList<Block> doTick = new ArrayList<Block>();
    62. ArrayList<ChunkCoordIntPair> ticked = getRandomTickedChunks();
    63.  
    64. if (ticked.isEmpty()) {
    65. return doTick;
    66. }
    67.  
    68. for (ChunkCoordIntPair pair : ticked) {
    69.  
    70. int xOffset = pair.x << 4, zOffset = pair.z << 4; //Multiply by 4, obviously.
    71. Chunk chunk = world.getChunkAt(pair.x, pair.z);
    72.  
    73. recheckGaps.invoke(chunk); //Recheck gaps
    74.  
    75. for (int i = 0; i != 3; i++) {
    76. if (rand.nextInt(100) <= chan) {
    77. int x = rand.nextInt(16), z = rand.nextInt(16);
    78. doTick.add(world.getWorld().getBlockAt(x + xOffset, world.g(x + xOffset, z + zOffset), z + zOffset));
    79. }
    80. }
    81. }
    82. return doTick;
    83. }
    84. }
    85.  


    Works on the same basis that MineCraft default snowing works. MC 1.2/1.3 compatible. Generates large lists of blocks in very little time and resources. Basically, gets a list of air blocks just one block above ground level.

    Hope you find the above snippet useful!
     
    LukeSFT likes this.
Thread Status:
Not open for further replies.

Share This Page