Important information regarding cancelling creature spawns

Discussion in 'Resources' started by bergerkiller, May 12, 2013.

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

    bergerkiller

    Very important warning

    In case anyone out there thought: "I don't want spoiders in my lands, let's cancel the event" - this is for you. Cancelling the creature spawn event persistently will result in a SEVERE memory leak.

    The issue

    The issue is that a new Entity instance, together with a lot of data for AI, path finding, properties, and what not, is created BEFORE Creature_Spawn is fired. So, even if you cancel that event, it is still going to create a lot of waste, because of that Entity instance that was created beforehand.

    Now this issue was not that bad...but on 1.5.2...it has gotten worse for some reason. Or it happened before and it's only now that I noticed it.

    Anyway, cancelling the event persistently can easily lead to over 1000 of Entity instances being created per tick. Before you know it, you will have 100K Entity instances reduced to garbage. This results in the used memory increasing rapidly, and in severe cases, results in the garbage collector running non-stop. The difference is noticeable: before I fixed it (about that later), it was 15 MB/s increasing. Now it went back to 2 MB/s.

    This also results in a noticeable tick rate issue on the server. Don't think initializing and allocating all those entities is cheap! Especially the path finding/tracker contains a lot of data and needs some initialization, where time is lost. Not to forget that the creature spawn event is spammed a lot...

    The solution (until Bukkit adds something we can use)

    The solution, sadly, requires a lot of hacking into NMS. To keep things running stable across my plugins, I put it in BKCommonLib under a separate injected class. For that reason it also contains other things, such as timings-related logic.

    https://github.com/bergerkiller/BKC...mon/internal/ChunkProviderServerHook.java#L51

    The solution is fairly straightforward: simply override the getMobsFor method in the CPS, and remove the entities you do not want to spawn at this time. I added a 'MobPreSpawn' listener so that other plugins can handle it. But be aware: this method is called an insane amount of times. Do not do complicated logic in there, and if you must, don't make it that accurate. A Bukkit event is a bit problematic for that reason - getMobsFor is called, maybe, 100 times per tick. Only a listener will do...

    Another solution

    An easier solution is to alter mob spawning so that it spawns in 'waves'. Every 50 ticks, for example, you set all animal/monster/etc. world spawn limits to the normal values, let the server spawn some, and then you set them back to 0. This works, somewhat, but you can easily conflict with other plugins this way. Plus, it does affect how and when mobs spawn.

    Conclusion

    Creature spawn is nice, but it should not be abused for enforcing mob spawning 'blacklists' or 'whitelists'. You will severely ruin the server's performance this way. There are ways around it, but they are rather complicated/bloated/unstable.
     
  2. turn of monster spawning and handle the spawning code by yourself, that way you don't need to create entities when they aren't needed (but requires a lot of code)
     
  3. Offline

    Cybermaxke

    You can also remove the entity from all the biomes to prevent spawning.
     
    bergerkiller likes this.
  4. Offline

    bergerkiller

    Cybermaxke
    Yep, the List fields in the biomes allow this. Downside is that you can no longer configure it per world then, which I needed.
     
  5. Offline

    MrMag518

    What about removing the entity right after it spawned, calling entity.remove()?
     
  6. Offline

    Garris0n

    I think this would cause mobs to flash then disappear(like peaceful mode) but I'm not positive.
     
  7. Offline

    MrMag518

    Yes, but I mean, will it be a temporary "solution"?
     
  8. Offline

    Garris0n

  9. Offline

    MrMag518

    I have no idea how to test like bergerkiller does, so I'll wait and see if bergerkiller responds.
    bergerkiller
     
  10. Offline

    bergerkiller

    MrMag518
    As of right now, there is an event to deal with it: CreaturePreSpawnEvent in BKCommonLib which I fire manually.

    https://github.com/bergerkiller/BKC...kit/common/events/CommonEventFactory.java#L67

    Re-uses the event to avoid memory pollution and what not. Had to optimize a lot of things to reduce the lag, mainly I had to add a StringMapCaseInsensitive because String.toLowerCase was lagging too much. It really is called a lot...

    And no, calling .remove() would make the problem worse. The issue is that you need to avoid the creation of the Entity instance, which Bukkit does not offer at all. Your best option is to either alter the mob spawn settings or to do what I did.
     
  11. Offline

    MrMag518

    Ah ok I see, thanks for providing this information and the custom event in BKCommonLib.
    Hope Bukkit will fix this.
     
  12. Offline

    bergerkiller

    MrMag518
    AFAIK they offer additional entity spawn limits you can configure, but the root issue of creature spawn event is not fixed.
     
  13. Offline

    MTN

    Is it fixed by now?
     
  14. Offline

    bergerkiller

    MTN
    Nope, and probably never will be considering it has to be done efficiently.
     
  15. Offline

    Kainzo

    I can confirm that cancelling spawn events is a very bad idea and will have bad effects to your server.
     
    CubieX and Ultimate_n00b like this.
  16. Offline

    coaster3000

    This is quite a severe issue. Glad someone found it.....
     
  17. Offline

    rsod

    soo... all servers using WorldGuard's deny-spawn flag are potentially screwed?
     
  18. Offline

    bergerkiller

    rsod
    Depends on whether WorldGuard also sets the spawning limits (in Bukkit) to 0 in addition to cancelling the event. For denying 'all' mobs or all animals/monsters the solution is simple: set the spawn limit to 0 for said mobs. For more specific limits such as 'Spider' this does not apply, since you can't set per-entity type limits on the server.
     
  19. Offline

    xxNightlordx

    I have created a plugin which adds a new event that MIGHT fix this problem. It doesn't use any NMS code though. This is the link.
     
  20. Offline

    bergerkiller

    xxNightlordx
    I looked at what it did, there are two issues with it:
    • When a plugin toggles spawning of a creature on again, the event won't be fired any more. One cancel means one spawn limit decrease.
    • Clients will still receive spawn messages for the Entity (and after some time they will lag out because of 1000s of spawn packets), and the server will still try to tick the Entity. There are ways to fix that though. (but it requires NMS)
    A better solution would be a plugin that does not provide an API, but simply alters the spawn limits (MonsterSpawnLimit, AnimalSpawnLimit, WaterSpawnLimit) so that the creature_spawn event isn't fired as often. For example, for 20 ticks you set all limits to 0, then for 1 or 2 ticks you turn it to the original value again, rinse and repeat.
     
  21. Offline

    xxNightlordx

    Thanks for the feedback. The reason the clients still receive spawn messages is because I made it so that if the CreatureSpawnAltEvent was cancelled, then the creature would be immediately teleported to the void and get its health set to 0 (In case it's a blaze or ghast) I guess killing the mob and removing the drops isn't the best way of cancelling the event .-.
     
  22. Offline

    bergerkiller

    xxNightlordx
    Well it is 'a' solution, preferably the solution would be in the server though. It shouldn't call entity spawning logic as often as it does, then all of these issues would be fixed. I'm fairly sure Spigot already addressed this problem.

    That said, your solution does fix it, but I don't think it is needed to add a separate event for it. You could handle the event in MONITOR and cancel it there, providing your own 'cancel' logic.

    Anyway, to improve your plugin, you can untrack the entity in the entity tracker of the world. NMS.WorldServer has an 'EntityTracker' field, which you can get and call untrack(entityHandle) on. That solves the spawn messages issue.

    To fix the spawn disabling going on...perhaps just flush all entities every now and then? Allow them to respawn every minute or something.
     
  23. Offline

    AstramG

    I'm a bit confused with this, does anyone know how to cancel the mob spawning if its anything except a zombie. Then spawn a zombie where the mob is supposed to spawn? I'm using this for a plugin that I'm currently working on.
     
  24. Offline

    bergerkiller

    AstramG
    Spawning a different mob in it's place is a possible solution (I think) as it then eventually caps the spawn count as well. As long as you don't spawn an animal in a monster's place it's fine.
     
  25. Offline

    epicfacecreeper

  26. Offline

    bob7

    Well.. I decided to do some testing of my own.. Keep in mind, this test server only has ONE player on who is FROZEN in place. I'm canceling ONLY monster spawns and using the latest DEV craftbukkit from 8/22/2013.


    As you can see bukkit attempts to spawn an entity near the player every tick. Now, imagine 100+ players moving around as the plugin cancels the event!

    There probably isn't any none-NMS (Besides world spawn options!) code that could despawn an entity without this occurring. Just in case i tested: Killing the entity, Removing the entity, teleporting the entity, setting the health of the entity.
     
    Ultimate_n00b likes this.
  27. Offline

    Ultimate_n00b

    I would say it's still pretty relevant.
     
  28. Offline

    epicfacecreeper

    Yes, but does bukkit destroy the entity if the event is cancelled?
     
  29. Offline

    bergerkiller

    epicfacecreeper
    Yes. Well, not destroyed, just 'ignored' and left to be garbage collected later on.
     
  30. Offline

    craftik7

    So instead of canceling event we remove the entity.

    Code:java
    1. e.getEntity().remove();


    right ?
     
Thread Status:
Not open for further replies.

Share This Page