diff --git a/api/src/main/java/net/md_5/bungee/api/event/AsyncEvent.java b/api/src/main/java/net/md_5/bungee/api/event/AsyncEvent.java new file mode 100644 index 00000000..cdbca34a --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/event/AsyncEvent.java @@ -0,0 +1,72 @@ +package net.md_5.bungee.api.event; + +import com.google.common.base.Preconditions; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import net.md_5.bungee.api.Callback; +import net.md_5.bungee.api.plugin.Event; +import net.md_5.bungee.api.plugin.Plugin; + +/** + * Represents an event which depends on the result of asynchronous operations. + */ +@Data +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +public class AsyncEvent extends Event +{ + + private final Callback done; + private final Set intents = Collections.newSetFromMap( new ConcurrentHashMap() ); + private final AtomicBoolean fired = new AtomicBoolean(); + private final AtomicInteger latch = new AtomicInteger(); + + @Override + @SuppressWarnings("unchecked") + public void postCall() + { + fired.set( true ); + if ( latch.get() == 0 ) + { + done.done( this, null ); + } + } + + /** + * Register an intent that this plugin will continue to perform work on a + * background task, and wishes to let the event proceed once the registered + * background task has completed. + * + * @param plugin the plugin registering this intent + */ + public void registerIntent(Plugin plugin) + { + Preconditions.checkState( !fired.get(), "Event %s has already been fired", this ); + Preconditions.checkState( !intents.contains( plugin ), "Plugin %s already registered intent for event %s", plugin, this ); + + intents.add( plugin ); + } + + /** + * Notifies this event that this plugin has done all its required processing + * and wishes to let the event proceed. + * + * @param plugin a plugin which has an intent registered for this evemt + */ + @SuppressWarnings("unchecked") + public void completeIntent(Plugin plugin) + { + Preconditions.checkState( intents.contains( plugin ), "Plugin %s has not registered intent for event %s", plugin, this ); + intents.remove( plugin ); + if ( latch.decrementAndGet() == 0 ) + { + done.done( this, null ); + } + } +} diff --git a/api/src/main/java/net/md_5/bungee/api/plugin/Event.java b/api/src/main/java/net/md_5/bungee/api/plugin/Event.java index c164343d..e2e01ecb 100644 --- a/api/src/main/java/net/md_5/bungee/api/plugin/Event.java +++ b/api/src/main/java/net/md_5/bungee/api/plugin/Event.java @@ -5,4 +5,11 @@ package net.md_5.bungee.api.plugin; */ public abstract class Event { + + /** + * Method called after this event has been dispatched to all handlers. + */ + public void postCall() + { + } } diff --git a/api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java b/api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java index 1ca8ef96..e5a86dae 100644 --- a/api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java +++ b/api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java @@ -227,8 +227,11 @@ public class PluginManager */ public T callEvent(T event) { + Preconditions.checkNotNull( event, "event" ); + long start = System.nanoTime(); eventBus.post( event ); + event.postCall(); // TODO: No exceptions! if ( !( event instanceof LoginEvent ) ) { diff --git a/proxy/src/main/java/net/md_5/bungee/tablist/GlobalTabList.java b/proxy/src/main/java/net/md_5/bungee/tablist/GlobalTabList.java index 576f8874..30a17d94 100644 --- a/proxy/src/main/java/net/md_5/bungee/tablist/GlobalTabList.java +++ b/proxy/src/main/java/net/md_5/bungee/tablist/GlobalTabList.java @@ -1,6 +1,7 @@ package net.md_5.bungee.tablist; -import java.util.Map; +import java.util.Collections; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import net.md_5.bungee.BungeeCord; import net.md_5.bungee.UserConnection; @@ -12,9 +13,7 @@ import net.md_5.bungee.packet.PacketC9PlayerListItem; public class GlobalTabList implements TabListHandler { - // Sweet trick to avoid locking, basically reimplement HashSet based on a ConcurrentHashMap - private static final Object PRESENT = new Object(); - private final Map sentPings = new ConcurrentHashMap<>(); + private final Set sentPings = Collections.newSetFromMap( new ConcurrentHashMap() ); @Override public void onConnect(ProxiedPlayer player) @@ -22,7 +21,7 @@ public class GlobalTabList implements TabListHandler UserConnection con = (UserConnection) player; for ( ProxiedPlayer p : ProxyServer.getInstance().getPlayers() ) { - con.sendPacket(new PacketC9PlayerListItem( p.getDisplayName(), true, p.getPing() ) ); + con.sendPacket( new PacketC9PlayerListItem( p.getDisplayName(), true, p.getPing() ) ); } BungeeCord.getInstance().broadcast( new PacketC9PlayerListItem( player.getDisplayName(), true, player.getPing() ) ); } @@ -30,10 +29,10 @@ public class GlobalTabList implements TabListHandler @Override public void onPingChange(ProxiedPlayer player, int ping) { - if ( !sentPings.containsKey( player ) ) + if ( !sentPings.contains( player ) ) { BungeeCord.getInstance().broadcast( new PacketC9PlayerListItem( player.getDisplayName(), true, player.getPing() ) ); - sentPings.put( player, PRESENT ); + sentPings.add( player ); } }