StackOverflowError optimization help?

Discussion in 'Plugin Development' started by kyle1320, Aug 23, 2012.

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

    kyle1320

    Hey guys, I'm getting a "StackOverflowError" on this code:

    Code:
    private void replaceWater(Block block, Material mat) {
            if (block.getTypeId() == 8 || block.getTypeId() == 9){
             
                Block up = block.getRelative(0, 1, 0);
                Block down = block.getRelative(0, -1, 0);
                Block north = block.getRelative(0, 0, -1);
                Block south = block.getRelative(0, 0, 1);
                Block east = block.getRelative(1, 0, 0);
                Block west = block.getRelative(-1, 0, 0);
             
                if (mat.isBlock()) {
                    block.setType(mat);
                }else {
                    block.setType(Material.AIR);
                }
             
                if (up.getTypeId() == 8 || up.getTypeId() == 9) {
                    this.replaceWater(up, mat);
                }
                if (down.getTypeId() == 8 || down.getTypeId() == 9) {
                    this.replaceWater(down, mat);
                }
                if (north.getTypeId() == 8 || north.getTypeId() == 9) {
                    this.replaceWater(north, mat);
                }
                if (south.getTypeId() == 8 || south.getTypeId() == 9) {
                    this.replaceWater(south, mat);
                }
                if (east.getTypeId() == 8 || east.getTypeId() == 9) {
                    this.replaceWater(east, mat);
                }
                if (west.getTypeId() == 8 || west.getTypeId() == 9) {
                    this.replaceWater(west, mat);
                }
            }
        }
    Here's (part of) the stack trace:
    Code:
    >20:35:00 [SEVERE] null
    org.bukkit.command.CommandException: Unhandled exception executing command 'replwater' in plugin ModTests v0.1
        at org.bukkit.command.PluginCommand.execute(PluginCommand.java:42)
        at org.bukkit.command.SimpleCommandMap.dispatch(SimpleCommandMap.java:168)
        at org.bukkit.craftbukkit.CraftServer.dispatchCommand(CraftServer.java:492)
        at net.minecraft.server.NetServerHandler.handleCommand(NetServerHandler.java:878)
        at net.minecraft.server.NetServerHandler.chat(NetServerHandler.java:825)
        at net.minecraft.server.NetServerHandler.a(NetServerHandler.java:807)
        at net.minecraft.server.Packet3Chat.handle(Packet3Chat.java:44)
        at net.minecraft.server.NetworkManager.b(NetworkManager.java:276)
        at net.minecraft.server.NetServerHandler.d(NetServerHandler.java:109)
        at net.minecraft.server.ServerConnection.b(SourceFile:35)
        at net.minecraft.server.DedicatedServerConnection.b(SourceFile:30)
        at net.minecraft.server.MinecraftServer.q(MinecraftServer.java:581)
        at net.minecraft.server.DedicatedServer.q(DedicatedServer.java:212)
        at net.minecraft.server.MinecraftServer.p(MinecraftServer.java:474)
        at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:406)
        at net.minecraft.server.ThreadServerApplication.run(SourceFile:539)
    Caused by: java.lang.StackOverflowError
        at org.bukkit.craftbukkit.util.LongHash.containsKey(LongHash.java:17)
        at net.minecraft.server.ChunkProviderServer.isChunkLoaded(ChunkProviderServer.java:40)
        at net.minecraft.server.World.isChunkLoaded(World.java:212)
        at net.minecraft.server.World.c(World.java:199)
        at net.minecraft.server.WorldServer.a(WorldServer.java:392)
        at net.minecraft.server.BlockFlowing.onPlace(BlockFlowing.java:309)
        at net.minecraft.server.Chunk.a(Chunk.java:423)
        at net.minecraft.server.World.setRawTypeIdAndData(World.java:246)
        at net.minecraft.server.World.setRawTypeIdAndData(World.java:235)
        at net.minecraft.server.BlockStationary.l(BlockStationary.java:35)
        at net.minecraft.server.BlockStationary.doPhysics(BlockStationary.java:27)
        at net.minecraft.server.World.m(World.java:438)
        at net.minecraft.server.World.applyPhysics(World.java:414)
        at net.minecraft.server.World.update(World.java:371)
        at net.minecraft.server.World.setTypeId(World.java:343)
        at org.bukkit.craftbukkit.block.CraftBlock.setTypeId(CraftBlock.java:92)
        at org.bukkit.craftbukkit.block.CraftBlock.setType(CraftBlock.java:88)
        at me.kyle1320.ModTests.ModTests.replaceWater(ModTests.java:187)
        at me.kyle1320.ModTests.ModTests.replaceWater(ModTests.java:199)
        at me.kyle1320.ModTests.ModTests.replaceWater(ModTests.java:199)
    It continues on with probably hundreds of lines something like:
    Code:
     at me.kyle1320.ModTests.ModTests.replaceWater(ModTests.java:199)
    The lines that these are pointing to are the lines that go:
    Code:
    this.replaceWater(up, mat);
    I realize it's trying to do a ton of stuff, (this only happens when trying to change a large body of water) so I was wondering:

    What kind of optimizations could I make to my code to help prevent this? / How else can I prevent this error?

    Thanks :)
     
  2. Offline

    The_Coder

    unloaded chucks maybe
     
  3. Offline

    kyle1320

    Thought about this, what would be the best way to fix it? Preferably if it could load the chunks somehow and continue?

    EDIT: Okay I added
    Code:
    if (!block.getChunk().isLoaded()) {
        block.getChunk().load(true);
    }
    to the very beginning, but it's still giving me the same error. :/
     
  4. Offline

    The_Coder

    if statements checking if the next chunk is loaded
     
  5. Offline

    kyle1320

    See my last post again
     
  6. Offline

    Jnorr44

    Your not supposed to call the method from inside itself, thats what causes stackoverflowerrors.
     
  7. Offline

    kyle1320

    Ah, didn't know that. How would I fix it then? Call another method to call this one? :p
     
  8. Offline

    Jnorr44

    No, you need to make 2 seperate methods for this one... one to figure out which variables to use for this method (the bottom part), and one to do the rest.
     
  9. Offline

    kyle1320

    Jnorr44
    Changed it to this:
    Code:
    private void replaceWater(Block block, Material mat) {
           
            if (!block.getChunk().isLoaded()) {
                block.getChunk().load(true);
            }
           
            if (block.getTypeId() == 8 || block.getTypeId() == 9){
               
                if (mat.isBlock()) {
                    block.setType(mat);
                }else {
                    block.setType(Material.AIR);
                }
               
                this.getAdjacentWater(block, mat);
            }
        }
       
        private void getAdjacentWater(Block block, Material mat) {
           
            Block up = block.getRelative(0, 1, 0);
            Block down = block.getRelative(0, -1, 0);
            Block north = block.getRelative(0, 0, -1);
            Block south = block.getRelative(0, 0, 1);
            Block east = block.getRelative(1, 0, 0);
            Block west = block.getRelative(-1, 0, 0);
           
            if (up.getTypeId() == 8 || up.getTypeId() == 9) {
                this.replaceWater(up, mat);
            }
            if (down.getTypeId() == 8 || down.getTypeId() == 9) {
                this.replaceWater(down, mat);
            }
            if (north.getTypeId() == 8 || north.getTypeId() == 9) {
                this.replaceWater(north, mat);
            }
            if (south.getTypeId() == 8 || south.getTypeId() == 9) {
                this.replaceWater(south, mat);
            }
            if (east.getTypeId() == 8 || east.getTypeId() == 9) {
                this.replaceWater(east, mat);
            }
            if (west.getTypeId() == 8 || west.getTypeId() == 9) {
                this.replaceWater(west, mat);
            }
        }
    But it's still coming up with the same error, just alternating
    Code:
    at me.kyle1320.ModTests.ModTests.replaceWater(ModTests.java:199)
        at me.kyle1320.ModTests.ModTests.getAdjacentWater(ModTests.java:222)
        at me.kyle1320.ModTests.ModTests.replaceWater(ModTests.java:199)
        at me.kyle1320.ModTests.ModTests.getAdjacentWater(ModTests.java:216)
    instead. :/
     
  10. Offline

    Jnorr44

    thats because in replaceWater your calling getAdjacentWater. The reason you get stackoverflow is because they are looping through eachother constantly, calling the same methods over, and over. You CANNOT do that. There is NO possible way to do this, and it is not recommended no matter what (although a scheduled task MAY work), but I wouldn't even attempt that. Event if you get this working with a scheduled task, it will lag like CRAZY, unless your calling this stuff once every 2 hours XD.
     
  11. Offline

    kyle1320

    Jnorr44
    Is there some way to clear them from the stack once one finishes? I'm determined :p
    Also just read up on this so now I understand it all better and should be slightly less of a pain to deal with, hopefully.
     
  12. Offline

    Jnorr44

    You arent saving any data to a hashmap/arraylist/set/anything... so there is no way to clear data. What exactly may I ask are you trying to do?
     
  13. Offline

    kyle1320

    Literally just messing around, wanted to see if I could make something to turn oceans to dirt or whatever.. Messing around / answering questions on here seems to be very good practice for learning Bukkit / Java, so it's really all I've been doing for the past few weeks.
     
  14. Offline

    Jnorr44

    If you want to learn java fast, you need to jump right into a major project. Take a look at my plugins... I started writing code 10 weeks ago, wtih NO experience at all with any programming language. I started with Flow, and progressively got better, and still am. Now I am actually learning C, and writing 4 plugins (1 unannounced, 2 released but not finished, 1 team plugin that should have a beta soon).
     
  15. Offline

    kyle1320

    Doesn't have to be fast, honestly I'm having fun like this. I'm taking a programming class second semester so I just thought to try some stuff out, and what better way than Minecraft? :) Only started about 3 weeks ago from essentially no experience, so I'm making some pretty good progress by my own summer time standards. Next up to try is file processing :p
     
  16. Offline

    desht

    Actually, "no possible way" is a bit of an overstatement (but perhaps only a bit :) ).

    kyle1320 recursion needs to be approached with care, and you need to have guard conditions to ensure you don't end up with stack overflows, but it's certainly possible to use for a limited size water body. The way I'd do it (untested code):

    PHP:
            public void replaceWater(Block blockMaterial matint max) {
                    
    replaceWater(blockmat, new HashSet<Block>(), max);
            }
     
            private 
    void replaceWater(Block blockMaterial matSet<Blockseenint max) {
                    if (
    seen.contains(block))
                            return;
                    if (
    seen.size() >= max)
                            return;
                    if (
    block.getType() != Material.WATER && block.getType() != Material.STATIONARY_WATER)
                            return;
     
                    
    seen.add(block);
                    
    block.setTypeIdAndData(mat.getId(), mat.getData().getData(), false);
     
                    
    replaceWater(block.getRelative(BlockFace.UP), matseenmax);
                    
    replaceWater(block.getRelative(BlockFace.DOWN), matseenmax);
                    
    replaceWater(block.getRelative(BlockFace.NORTH), matseenmax);
                    
    replaceWater(block.getRelative(BlockFace.SOUTH), matseenmax);
                    
    replaceWater(block.getRelative(BlockFace.EAST), matseenmax);
                    
    replaceWater(block.getRelative(BlockFace.WEST), matseenmax);
            }
    Notes:
    • I've added a max parameter which you can use to limit the number of blocks which will be replaced. You can tune this to avoid/minimise lag.
    • I've added a seen Set<Block>, which stores the blocks already processed. This ensures that blocks don't get processed more than once.
    • I've used block.setTypeIdAndData() with an applyPhysics parameter of false - this will reduce the overhead of block changing a little.
    However, this is still expensive to do - the real killer is the block changing, which forces lighting recalculation for every block change. The only way around this is to use low-level NMS calls to change the chunk data directly, and then manually send chunk updates to nearby players when you're done. This isn't simple, but there's some discussion here: http://forums.bukkit.org/threads/massive-block-changes.73988/
     
Thread Status:
Not open for further replies.

Share This Page