[Tutorial] Making Your Plugin Faster By Not Overkilling Event Handling [WINDOWS]

Discussion in 'Resources' started by Icyene, Jul 5, 2012.

?

Was This Post In Any Way, Shape, or Form Usefull to you?

Poll closed Jul 19, 2012.
  1. Yes

    71.4%
  2. No

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

    Icyene

    You've either had this problem before, or will have it later. Making a plugin that requires alot of modification of the game.

    Lets take this example: you want to make a plugin that allows redstone to be waterproof.

    Being resourceful, you look through the JavaDocs, and come across the BlockFromToEvent. You decide you will just check if the target block is redstone, and if so, cancel the event. You then write a simple function to do just that. In essence, it would look like this.

    Code:
    public void onBlockFromTo(BlockFromToEvent event) {
    if (event.getToBlock().getType().equals(Material.REDSTONE_WIRE)) {
    event.setCancelled(true);
    }
    }
    You compile it, and test it on your server. Works fine. However, there are a couple of things that you should know, that you probably didn't think about. The BlockFromToEvent is called at a rate of about 5/10 seconds. So 0.5/second. Just for ONE water block. It causes unnecessary memory consumption on a production server, where around 30 of these events would be called per second (depending on amount of players). Also keep in mind, these events happen regardless of whether the player who made a ring of redstone around his water is online or not.

    The reason why I wrote this article is to show a different method that alot of people overlook, and in this case, will waterproof the redstone in a more realistic way.

    First, use you unzipper of choice to unzip craftbukkit.jar (NOT BukkitAPI.jar).

    Browse to net>minecraft>server.

    Extract all the class files there into a folder outside the jar file.

    Now, we need to decompile these class files. Decompiling is fun. To do this, you need a decompiler. The one generally used by most people is JAD.

    To use jad, open up command prompt, and type in the following:

    Code:
    cd DIRECTORY_OF_CLASS_FILES
    jad.exe *.class
    ren *.jad *.java
    
    You now have the DECOMPILED Minecraft files, all conveniently named by the Bukkit staff.

    The best place to start for anything involving blocks is Block.java, so open it up in your text editor of choice.

    We want to modify MOVING water, as stationary water poses no threat to circuits.

    Scrolling through the file, you will find a line that says

    Code:
    WATER = (new BlockFlowing(8, Material.WATER)).c(100F).f(3).a("water").s().j();
    From that one line, we now know EVERYTHING we need to make water not destroy redstone. The first thing we need is to open up BlockFlowing.java.

    Scrolling through it, we look for things that might be useful, such as references to other exceptions. When I first did this, I was looking for an exception for a ladder. I found it, at

    Code:
    private boolean k(net.minecraft.server.World world, int i, int j, int k)
        {
            int l = world.getTypeId(i, j, k);
            if(l != Block.WOODEN_DOOR.id && l != Block.IRON_DOOR_BLOCK.id && l != Block.SIGN_POST.id && l != Block.LADDER.id && l != Block.SUGAR_CANE_BLOCK.id)
            {...
    So we can logically infer that if we added Block.REDSTONE_WIRE.id into there, we can make redstone waterproof.

    Alas, the backdraw of object-oriented programming. boolean k (obviously obfuscated) is private, meaning that we cannot access it from our own class.

    To work around this, create a class that EXTENDS BlockFlowing, like so:

    Code:
    public class CustomFlowing extends BlockFluids 
    Now, copy the boolean k into CustomFlowing, and change the 'private' to 'public', so we can access it later. Also, add
    Code:
     l!=Block.REDSTONE_WIRE.id
    into the first if statement.

    However, in our water declaration, there are other methods that need to be copied either from Block.java or BlockFlowing.java.

    I will now skip about 10 minutes of copy pasting functions, when our class now looks like this:

    Code:
    protected CrimsonFlowing(int i, Material material) {
            super(i, material);
            a = 0;
            b = new boolean[4];
            c = new int[4];
    
        }
    
        public CrimsonFlowing f(int i) {
            lightBlock[id] = i;
            return this;
        }
    
        public CrimsonFlowing c(float f) {
            strength = f;
            if (durability < f * 5F)
                durability = f * 5F;
            return this;
        }
    
        public CrimsonFlowing s() {
            bS = false;
            return this;
        }
    
        public CrimsonFlowing j() {
            r[id] = true;
            return this;
        }
    
        public Block a(float f) {
            lightEmission[id] = (int) (15F * f);
            return this;
        }
    
        public boolean k(net.minecraft.server.World world, int i, int j, int k) {
            int l = world.getTypeId(i, j, k);
            if (!Waterproof.waterproofBlocks.contains(l)
                    && l != Block.WOODEN_DOOR.id && l != Block.IRON_DOOR_BLOCK.id
                    && l != Block.SIGN_POST.id && l != Block.LADDER.id
                    && l != Block.SUGAR_CANE_BLOCK.id) {
                if (l == 0) {
                    return false;
                } else {
                    Material material = Block.byId[l].material;
                    return material != Material.PORTAL ? material.isSolid() : true;
                }
            } else {
                return true;
            }
        }
    
        int a;
        boolean b[];
        int c[];
    
    Note that you will have to change some of the 'Block's to 'CustomFlowing', if Eclipse or your IDE complains.

    Good job getting this far! Almost finished!

    Create a new class, that will control everything. Using standard Bukkit procedures, you will create an onEnable function. In it is the perfect place to mod the water.

    Import your CustomFlowing, and in the onEnable(), add the following code:

    Code:
    Block.byId[Block.WATER.id] = null;
            Block.byId[Block.WATER.id] = ((CustomFlowing) (new CustomFlowing(8,
                    Material.WATER)).c(100F).f(3).a("water")).s().j();
    Now, why did we do that? Let me explain. We first set the default water to null (the first line), and then we initialized it as our water, in the same way as Block.java did.

    I'll let you think why we had to cast it to CustomFlowing, however.

    In conclusion:

    With this code, we have modified water ONCE, and canceled no events. At least in my opinion, while it is harder to do then simply event.setCancelled(true), it does pay off in terms of performance. We only had to mod it ONCE, on startup. Not 30 times a second.

    Hope you learned something new!

    Extending:

    With the current code, there is a backdraw. The water will remain modded even after your plugin is deleted. To get past this, store the Block.REDSTONE_WIRE.id in a variable. onDisable(), set that variable to null, and run all the code in onEnable() again.
     
    H2NCH2COOH, -_Husky_- and hawkfalcon like this.
  2. Good tutorial, really helped me! Didn't think of this before
     
  3. Offline

    RyanTheLeach

    This wouldn't be version safe would it?
    additionally it seems dependent on craftbukkit and wouldn't support other implementations of bukkit,
    But please correct me if i'm wrong.
     
  4. Offline

    Icyene

    That's exactly the point of doing it like this (aside from making it faster + more realistic). It is not dependent in the slightest, as it mods the native MINECRAFT code, not Bukkit's WRAPPER around it. So basically, if Bukkit shut down tomorrow (which obviously won't happen) the only thing from my code that needs to be changed is to put the modification function somewhere other then the onEnable(), as that's all Bukkit.

    Backdraw is not the safety, as its safe. The problem is that if everyone used the Block.byId[##]=null, people would be overriding each others custom blocks. This can be averted more using reflection. I will write a tutorial about that later.

    P.S. Minecraft hasn't changed it water classes in a LONG time, while Bukkit has updated alot in that time. In other words, this requires less updating.
     
  5. Hmm interesting, I didn't know you could just overwrite block classes like that xD

    However, how would I proceed to block the BlockFormEvent for snow by using this method ?
     
  6. Offline

    Icyene

    I just took a quick look into the NMS code, and found this in BlockSnow.java:

    Code:
     public boolean canPlace(World world, int i, int j, int k)
        {
            int l = world.getTypeId(i, j - 1, k);
            return l == 0 || l != Block.LEAVES.id && !Block.byId[l].a() ? false : world.getMaterial(i, j - 1, k).isSolid();
        }
    You would add a check in there for it not to place on your blocks. Alternatively, if you dont want snow at all, youd just do:
    Code:
     public boolean canPlace(World world, int i, int j, int k)
        {   return false
        }
    Hope I helped!
     
  7. Yes but... it extends Block which methods c() and I dunno what other are private... I would need to copy that too ? Seems like alot of copying... is there a way to use reflection to only replace the canPlace() method ? :}
     
  8. Offline

    Icyene

    Digi You would have to copy it. You could alternatively use BCEL, ASM, AspectJ or Groovy to achieve this, but you'd most likely end up with more code then you started with. That said, it would lower compatibility issues. I will write a tutorial on using BCEL and Groovy at a later time. P.S. The standard Java reflection does not support method swapping.

    Also, the Eclipse IDE tells you which methods are "not visible". You need to copy those out, change their visibility to public, and you will also have to cast your block like I did.

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

    Dark_Balor

    In term of performance I'm completely agree with your way to do it.

    But in term of interaction with other plugins ... you just break it. I mean, in the case of other plugins are having some "special behavior" when water touch redstone, you break it, since the event won't be triggered.

    Of course you "just" need to put a warning on your Plugin Page to explain it, but users don't read, and you'll have some whining about "You plugin is sh*t it's breaking this that I NEEEEEEDDDD !!!!!!!!".

    The other annoyance, is the need to, at each version of minecraft, look closely again to the source code, since ... if they add a new method, all the code could have been changed (thx obfuscator ...)

    But still I like the way you achieve it.

    Edit : by the way I'm surprised that Block.WATER.id is not final ...
     
  10. Offline

    Icyene

    Dark_Balor The BlockFromTo event is still triggered (Its extending BlockFlowing, not Block). Or is that not what you meant? Also, even if Block.WATER.id was final, final isn't that final with reflection :]. The looking at the code is a pretty big annoyance, especially for large plugins that use that method, like my CrimsonStone. Every update I have to rework 20 classes. Then again, this prevent users going like "This plugin 5uc5!!11!!11 Ist so fcking slow!!!111111111". The bright side is that the method bodies really aren't changed much every update, so its relatively easy. In one hour, I can update more then 20 classes, so it basically the same amount of time it takes for you to figure out how to use a new method Bukkit provides (that is admittedly more rare, but still. Performance does come first, at least in my opinion"). If you were to use AspectJ to block calls to the method you need and swap them with a method call to your method, that really wouldn't break any plugins, except those who use AspectJ. Then again, its a pain in the arse to do so; I'm still stuck logging all method calls with it.
     
  11. Offline

    Dark_Balor

    Okay I see.

    I thought if the method k return false, bukkit don't trigger the event.
     
  12. Offline

    Icyene

    Dark_Balor the actual event is called by the flow() method, which is inherited.
     
  13. Offline

    seifpic

  14. That wouldn't include things like the actual minecraft server source which is added to CraftBukkit when it is compiled.
     
  15. Offline

    Icyene

    seifpic
    Simply because not all classes are in there, e.g. BlockFluids, which is essential to the example.
     
  16. Offline

    seifpic

    But you can import the files you want from https://github.com/Bukkit/mc-dev/ it's a bit more work but you don't have to decompile code. Anyway, I don't think it matters much.
     
  17. Offline

    Icyene

    seifpic
    True, yet think to yourself: aside from the original coding, when would you need to update this? When MC updates. When MC updates, the mc-dev repo goes private for indeterminate periods of time, in which one can simply decompile the jar in under 2 minutes and make their users happy with an early release.
     
  18. Offline

    Debels

    Didn't know you could do this, This is really helpful.
     
Thread Status:
Not open for further replies.

Share This Page