Which will be less "laggy": Timers, or event check?

Discussion in 'Plugin Development' started by Colby l, Feb 16, 2014.

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

    Colby l

    Currently, I need to create 13 timers for my plugin. One timer would be running every second, to check a player's flight and such, and the other 12 would range from every second to every fifteen or so seconds.

    I'm trying to find the best way to handle this issue, and the issue the 12 timers are solving is checking if a player can fly there. The timer would iterate through arrays of players, and the arrays would have a total of over 100 people.

    With events, I'd check when a player moved from or to a chunk, and check if he/she can fly in the chunk. But, this would be for every player's chunk move event, which would also be with over 100 people.

    So, which way do you think would be best, performance wise?

    Thanks for the help!
  2. Offline


    Colby l
    I'm leaning towards a repeating task, but if you optimize enough, PlayerMoveEvent may be a bit better.
  3. Offline


    Assuming you're not referring to java.util.Timer, which you cannot safely use with the Bukkit API, why do you need to construct 12 separate repeatable tasks? Wouldn't one task set to a reasonable delay be sufficient? Splitting up work that can be achieved by a single PlayerMoveEvent handler sounds like premature optimization to me.

    You should always be wary of PlayerMoveEvent, especially when you're dealing with a potentially large player base. It's fired extremely frequently, so you're pretty much forced to check for crossing of chunk boundaries before doing your own processing (in this case, checking flight status):
    1. public class ExampleMod extends JavaPlugin implements Listener {
    2. private static class ChunkCoordinate {
    3. public final int x;
    4. public final int z;
    6. public ChunkCoordinate(int x, int z) {
    7. this.x = x;
    8. this.z = z;
    9. }
    10. }
    12. // We have to store the previous chunk somehow
    13. private Map<Player, ChunkCoordinate> currentChunk = Maps.newHashMap();
    14. private Stopwatch watch = new Stopwatch();
    16. @Override
    17. public void onEnable() {
    18. getServer().getPluginManager().registerEvents(this, this);
    19. }
    21. @EventHandler
    22. public void onPlayerMove(PlayerMoveEvent e) {
    23. //watch.start();
    24. ChunkCoordinate old = currentChunk.get(e.getPlayer());
    26. // New chunk coordinate
    27. Location location = e.getTo();
    28. int chunkX = location.getBlockX() >> 4;
    29. int chunkZ = location.getBlockZ() >> 4;
    31. // Only do our thing if the chunk has changed
    32. if (old == null || old.x != chunkX || old.z != chunkZ) {
    33. System.out.println("New chunk for " + e.getPlayer());
    35. currentChunk.put(e.getPlayer(), new ChunkCoordinate(chunkX, chunkZ));
    36. }
    37. //watch.stop();
    38. }
    40. @EventHandler
    41. public void onPlayerChangedWorldEvent(PlayerChangedWorldEvent e) {
    42. // Clear the chunk - it no longer refers to the correct world
    43. currentChunk.remove(e.getPlayer());
    44. }
    46. @EventHandler
    47. public void onPlayerQuitEvent(PlayerQuitEvent e) {
    48. // Prevent memory leaks
    49. currentChunk.remove(e.getPlayer());
    51. System.out.println("Time consumed by handler: " + watch);
    52. }
    53. }

    But is this actually faster than a scheduled tasks? To find out, I installed NoLagg on a private server (CB 1.7.2) along with the test plugin above, logged in (with no other players online) and executed nolagg examine while running in a straight direction. That produced a 25 second timing report for every event handler by every plugin:
    Total duration: 6.347 ms / 500 ticks
    Average duration: 0.013 ms/tick
    Event count: 1
    Task count: 0
    Plugin: ExamplePlugin
    Extrapolating to 100 players, that would be over 0.013 ms/tick * 100 = 1.3 ms/tick, which is 2.6% of the time in a single tick (50 ms). That's a pretty significant overhead.

    If you uncomment watch.start() and watch.stop(), you can also get a sense of of the time consumed by the method handler alone, excluding the inherent overhead of invoking an event method through reflection:
    Total duration: 9.254 ms / 500 ticks
    Average duration: 0.019 ms/tick
    Event count: 1
    Task count: 0
    Plugin: ExamplePlugin
    [01:04:50] [Server thread/INFO]: Time consumed by handler: 9,644 ms
    Though it appears reflection has very little to do with it. It's the chunk checking that is time consuming.

    Compare that to a simple repeatable task, set to 1 second:
    1. public class ExampleMod extends JavaPlugin implements Listener {
    2. @Override
    3. public void onEnable() {
    4. getServer().getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {
    5. @Override
    6. public void run() {
    7. for (Player player : Bukkit.getServer().getOnlinePlayers()) {
    8. // Do whatever
    9. }
    10. }
    11. }, 20, 20);
    12. }
    13. }

    The numbers are startling:
    Total duration: 0.291 ms / 500 ticks
    Average duration: 0.001 ms/tick
    Event count: 0
    Task count: 1
    Plugin: ExamplePlugin
    That's at least an order of magnitude less overhead than the PlayerMoveEvent solution. Of course, the repeatable task might miss the chunk change by a second or two, but is that kind of accuracy really necessary?

    Still, don't forget, circumstances might change when you scale things up to 100 players. I recommend doing tests yourself, with the flight check included.
  4. Offline

    Colby l

    Very interesting report, thanks for the long, and nicely detailed response. The reasoning behind 12 timers was each timer had to be different, because each one would remove items from player's inventory as they fly. For example, a guest may have an item removed every second for flying, when a VIP+ may get an item removed only every 20 seconds.

    Thanks again for the response!
Thread Status:
Not open for further replies.

Share This Page