Generating a circle without lag

Discussion in 'Plugin Development' started by libraryaddict, Sep 13, 2012.

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

    libraryaddict

    So I looked at worldedits code for solid cylinder generation.
    Modified it a little.
    Run it.

    Generating a circle = fine.
    Knowing where to place the blocks = fine.
    Placing the blocks themselves = NOT FINE

    It lags to pieces.

    I have a goal of making a Hunger Games style plugin precisely like the book except for a few details and so I need to have a central area where they spawn in.
    Problem being this circle needs to be 1/3 larger then the platform circle.
    And at my test of 120 players this gets a bit of lag.

    Anyone wondering why I have no bukkit dev page or anything up on this.
    I found I don't like having people get psyched over something I might drop.

    Basically.
    Is there any fast way to create blocks in the world.
    Else I'm going to have to limit how much of the circle generates per tick.

    Perhaps call it a "countdown" before the game starts.
     
  2. Offline

    desht

    Yes, but not with plain Bukkit. The problem with Bukkit's block.setType() and all related methods is that lighting recalculations are redone every time a block is changed, and that's the main source of lag. The Bukkit methods are fine for setting one block (or a few), but not suitable at all for mass block changes.

    I encountered this in ChessCraft, which often needs to update hundreds of blocks at a time (and sometimes thousands or even millions). But thanks to bergerkiller, I learned a much faster way, using net.minecraft.server (NMS) calls. Here's an example:
    PHP:
            public static boolean setBlockFast(Block bint typeIdbyte data) {
                    
    Chunk c b.getChunk();
                    
    net.minecraft.server.Chunk chunk = ((CraftChunkc).getHandle();
                    return 
    chunk.a(b.getX() & 15b.getY(), b.getZ() & 15typeIddata);
            }
    Nice and easy, and very very fast :) But, it's not that simple. You also need to ensure players see the changes (and you may need to do some explicit re-lighting). Here's where it gets a bit more complex...

    There is a Minecraft packet for pushing chunk changes to clients: Packet51MapChunk. You could just send this to all clients, but you don't want to do that - you can cause some severe server lag doing this. A better way is to add the co-ordinates of any altered chunks to the NMS EntityPlayer chunkCoordIntPairQueue field. This field lists the chunks that need sending to the player, and the server will process those in its own time - meaning no lag.

    For an example, see https://github.com/desht/ChessCraft.../me/desht/chesscraft/regions/Cuboid.java#L678 of my Cuboid class - I get the chunks covered by the cuboid object, find all players within viewing distance (no point in sending changes to players too far away to see the changes), and queue the chunk co-ordinates for those players.
    For explicit re-lighting, you can use the (NMS) Chunk initLighting() method. See https://github.com/desht/ChessCraft.../me/desht/chesscraft/regions/Cuboid.java#L646 for how I call it.

    So... not simple, but it works, and it's much faster than just using Bukkit calls. Some unscientific estimates and tests suggest you can update on the order of 1,000,000 blocks per second doing it this way.
     
  3. Offline

    libraryaddict

    Holy.

    Thanks!

    desht Any idea why it says that CraftChunk can't be resolved to a type?

    I'm using CraftBukkit as the jar file which I'm pretty sure is the correct jar.
    Its 1.3.1 R2

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 28, 2016
  4. Offline

    desht

    libraryaddict You'll need to include craftbukkit.jar in your build dependencies too.
     
  5. Offline

    libraryaddict

    That's the thing.
    I have.

    Its the same way you add Bukkit correct?

    Ohh.
    I had to import it, But the import option wasn't showing ^^

    Code:
    public void setBlockFast(Block b, int typeId, byte data) {
        Chunk c = b.getChunk();
        net.minecraft.server.Chunk chunk = ((CraftChunk) c).getHandle();
        chunk.a(b.getX() & 15, b.getY(), b.getZ() & 15, typeId, data);
      }
    
      public void makeCylinder(Location loc, Integer block, int r) {
        List<Integer> num = new ArrayList<Integer>();
        List<Location> set = new ArrayList<Location>();
        int x = 0;
        int z = 0;
        int y = (int) loc.getY();
        Collection<Chunk> chunks = new HashSet();
        for (double i = 0.0; i < 360.0; i += 0.1) {
          double angle = i * Math.PI / 180;
          x = (int) (loc.getX() + r * Math.cos(angle));
          z = (int) (loc.getZ() + r * Math.sin(angle));
          Location loa = loc.getWorld().getBlockAt(x, y, z).getLocation();
          if (x < loc.getX() && !num.contains((int)loa.getZ())) {
            num.add((int) loa.getZ());
            int mid = (int) (loc.getX() - loa.getX());
            for (int ai = (int) loa.getX(); ai <= loc.getX() + mid; ai++) {
              Location lc = new Location(loa.getWorld(), ai, y, loa.getZ());
              setBlockFast(lc.getBlock(), block, (byte) 0);
            }
          }
        }
      }
    
    Can you see any problems with my code?
    When using craftbukkit way it takes 68seconds.
    When using block.setTypeId() it takes 72 seconds.

    I think I'm doing something wrong.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 28, 2016
  6. Offline

    desht

    Uh, yeah... that's an awful lot of trigonometry (3600 calls to Math.cos() and Math.sin()), and some unnecessary creation of Location objects and probably worst of all, you're repeatedly running .contains() on an ever-growing ArrayList. How about:

    PHP:
    public void cylinder(Location locint matIdint r) {
            
    int cx loc.getBlockX();
            
    int cy loc.getBlockY();
            
    int cz loc.getBlockZ();
            
    World w loc.getWorld();
            
    int rSquared r;
     
            for (
    int x cx r<= cx +rx++) {
                    for (
    int z cz r<= cz +rz++) {
                            if ((
    cx x) * (cx -x) + (cz z) * (cz z) <= rSquared) {
                                    
    setBlockFast(w.getBlockAt(xcyz), matId, (byte0);
                            }
                    }
            }
    }
    No need to use trig here - just scan the square that contains your cylinder, and set those blocks which are within <r> distance of the centre (Pythagoras' theorem). All done with nice fast integer arithmetic. A further optimisation might be to only scan one quarter of the containing square, and use symmetry to set the other three quarters, reducing the number of comparisons you need to do. Not sure how much time that would really save, though.

    Given that using setBlockFast() only saved you about 5% of your time suggests the bottleneck wasn't there, so it's debatable whether you really need to do that at all - what's the typical radius for your cylinders?
     
    hawkfalcon likes this.
  7. Offline

    Jnorr44

    desht That's really smart, but wouldn't Chunk.a break on update?
     
  8. Offline

    libraryaddict

    Apparently. I am not a clever man.

    And current radius is 48 with 21 seconds of lag.
     
  9. Offline

    desht

    There is that risk, yes - that comes with using any NMS calls. But there isn't a fast way to do it with Bukkit.

    (As it happens, Chunk.a() hasn't changed in the last several updates, but that doesn't mean it won't).

    Everyone starts somewhere :)

    That works out to ~1800 blocks (times whatever vertical height your cylinder will be), so "fast" block updates may well be worthwhile.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 28, 2016
  10. Offline

    libraryaddict

    Saying that its the best I'm going to get?
     
  11. Offline

    Jnorr44

    No, I started writing in java 13 weeks ago, and I am already a fairly decent dev. Java was my first ever programming language.

    Meaning: You'll get there!
     
  12. Offline

    libraryaddict

    I meant lag wise.
    And 4-5 weeks here.

    I just had a dumb moment with the radius thingy ^^
     
  13. Offline

    desht

    libraryaddict

    You should be able to get 1800 blocks updated in a few milliseconds, so 21 seconds is definitely cause for concern...
     
Thread Status:
Not open for further replies.

Share This Page