diff --git a/pom.xml b/pom.xml
index e64ba5ddc..9d16dae49 100644
--- a/pom.xml
+++ b/pom.xml
@@ -120,6 +120,13 @@
jar
runtime
+
+ com.google.guava
+ guava
+ r09
+ jar
+ runtime
+
diff --git a/src/main/java/com/l2jserver/service/game/world/CachedWorldIDService.java b/src/main/java/com/l2jserver/service/game/world/CachedWorldIDService.java
index 3efab71d3..9ff81ae84 100644
--- a/src/main/java/com/l2jserver/service/game/world/CachedWorldIDService.java
+++ b/src/main/java/com/l2jserver/service/game/world/CachedWorldIDService.java
@@ -102,6 +102,11 @@ public class CachedWorldIDService extends AbstractService implements
loaded = true;
}
+ @Override
+ public void unload() {
+ cache.removeAll();
+ }
+
/**
* Load the pre-existing ids
*
diff --git a/src/main/java/com/l2jserver/service/game/world/WorldIDService.java b/src/main/java/com/l2jserver/service/game/world/WorldIDService.java
index 623586670..5a69b1fbc 100644
--- a/src/main/java/com/l2jserver/service/game/world/WorldIDService.java
+++ b/src/main/java/com/l2jserver/service/game/world/WorldIDService.java
@@ -35,6 +35,11 @@ public interface WorldIDService extends Service {
*/
void load();
+ /**
+ * Unload all loaded {@link ObjectID}
+ */
+ void unload();
+
/**
* Tries to resolve an ID based on its raw value
*
diff --git a/src/main/java/com/l2jserver/service/game/world/WorldServiceImpl.java b/src/main/java/com/l2jserver/service/game/world/WorldServiceImpl.java
index dce14a452..f26741104 100644
--- a/src/main/java/com/l2jserver/service/game/world/WorldServiceImpl.java
+++ b/src/main/java/com/l2jserver/service/game/world/WorldServiceImpl.java
@@ -82,6 +82,7 @@ public class WorldServiceImpl extends AbstractService implements WorldService {
protected void doStart() throws ServiceStartException {
objects.clear();
idService.load();
+ dispatcher.start();
}
@Override
@@ -172,5 +173,7 @@ public class WorldServiceImpl extends AbstractService implements WorldService {
@Override
protected void doStop() throws ServiceStopException {
objects.clear();
+ idService.unload();
+ dispatcher.stop();
}
}
diff --git a/src/main/java/com/l2jserver/service/game/world/event/WorldEventDispatcher.java b/src/main/java/com/l2jserver/service/game/world/event/WorldEventDispatcher.java
index 0322c6cb8..381a3660c 100644
--- a/src/main/java/com/l2jserver/service/game/world/event/WorldEventDispatcher.java
+++ b/src/main/java/com/l2jserver/service/game/world/event/WorldEventDispatcher.java
@@ -31,10 +31,14 @@ public interface WorldEventDispatcher {
* need to invoke listeners immediately. Dispatching can occur
* concurrently.
*
+ * @param
+ * the event type
* @param event
* the event
+ * @return the future. The future can be used to be notified once the event
+ * has been dispatched to all listeners.
*/
- void dispatch(WorldEvent event);
+ WorldEventFuture dispatch(E event);
/**
* Adds a new global listener
diff --git a/src/main/java/com/l2jserver/service/game/world/event/WorldEventDispatcherImpl.java b/src/main/java/com/l2jserver/service/game/world/event/WorldEventDispatcherImpl.java
index 620bcd5b6..63729d4e3 100644
--- a/src/main/java/com/l2jserver/service/game/world/event/WorldEventDispatcherImpl.java
+++ b/src/main/java/com/l2jserver/service/game/world/event/WorldEventDispatcherImpl.java
@@ -21,10 +21,14 @@ import java.util.Queue;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.util.concurrent.AbstractFuture;
import com.l2jserver.model.id.ObjectID;
import com.l2jserver.model.world.WorldObject;
import com.l2jserver.util.factory.CollectionFactory;
@@ -35,28 +39,51 @@ import com.l2jserver.util.factory.CollectionFactory;
* @author Rogiel
*/
public class WorldEventDispatcherImpl implements WorldEventDispatcher {
+ /**
+ * The logger
+ */
private static final Logger log = LoggerFactory
.getLogger(WorldEventDispatcherImpl.class);
- private final Timer timer = new Timer();
+ /**
+ * The execution thread
+ */
+ private Timer timer;
+ /**
+ * The list of all global listeners
+ */
private Set globalListeners = CollectionFactory.newSet();
+ /**
+ * The {@link Map} containing all listeners for every object
+ */
private Map, Set> listeners = CollectionFactory
.newMap();
- // private Queue listeners = CollectionFactory
- // .newConcurrentQueue(ListenerIDPair.class);
- private Queue events = CollectionFactory.newConcurrentQueue();
+ /**
+ * The events pending dispatch
+ */
+ private Queue events = CollectionFactory
+ .newConcurrentQueue();
- public WorldEventDispatcherImpl() {
+ public void start() {
+ timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
- final WorldEvent event = events.poll();
+ final EventContainer event = events.poll();
if (event == null)
return;
try {
- doDispatch(event);
+ // set state
+ event.future.running = true;
+ event.future.complete = false;
+
+ // dispatch
+ if (doDispatch(event))
+ // the set will update state
+ event.future.set(event.event);
} catch (Throwable t) {
+ event.future.setException(t);
log.warn("Exception in WorldEventDispatcher thread", t);
}
}
@@ -64,21 +91,32 @@ public class WorldEventDispatcherImpl implements WorldEventDispatcher {
}
@Override
- public void dispatch(WorldEvent event) {
+ public WorldEventFuture dispatch(E event) {
log.debug("Queing dispatch for event {}", event);
- events.add(event);
+ final WorldEventFutureImpl future = new WorldEventFutureImpl();
+ events.add(new EventContainer(event, future));
+ return future;
}
- public void doDispatch(WorldEvent event) {
+ /**
+ * Do the dispatching
+ *
+ * @param event
+ * the event
+ * @return true if dispatch was not canceled
+ */
+ public boolean doDispatch(EventContainer event) {
log.debug("Dispatching event {}", event);
- final ObjectID>[] objects = event.getDispatchableObjects();
+ final ObjectID>[] objects = event.event.getDispatchableObjects();
for (ObjectID> obj : objects) {
if (obj == null)
continue;
final Set listeners = getListeners(obj);
for (final WorldListener listener : listeners) {
+ if (event.future.isCancelled())
+ return false;
try {
- if (!listener.dispatch(event))
+ if (!listener.dispatch(event.event))
// remove listener if return value is false
listeners.remove(listener);
} catch (ClassCastException e) {
@@ -89,6 +127,7 @@ public class WorldEventDispatcherImpl implements WorldEventDispatcher {
}
}
}
+ return true;
}
@Override
@@ -134,6 +173,14 @@ public class WorldEventDispatcherImpl implements WorldEventDispatcher {
listeners.remove(id);
}
+ /**
+ * Get the {@link Set} of listeners for an given object. Creates a new one
+ * if does not exists.
+ *
+ * @param id
+ * the object id
+ * @return the {@link Set}. Never null.
+ */
private Set getListeners(ObjectID> id) {
Set set = listeners.get(id);
if (set == null) {
@@ -142,4 +189,117 @@ public class WorldEventDispatcherImpl implements WorldEventDispatcher {
}
return set;
}
+
+ public void stop() {
+ timer.cancel();
+ timer = null;
+ }
+
+ /**
+ * {@link WorldEventFuture} implementation
+ *
+ * @param
+ * the event type
+ * @author Rogiel
+ */
+ private static class WorldEventFutureImpl extends
+ AbstractFuture implements WorldEventFuture {
+ private boolean running = false;
+ private boolean complete = false;
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected boolean set(WorldEvent value) {
+ running = false;
+ complete = true;
+ return super.set((E) value);
+ }
+
+ @Override
+ protected boolean setException(Throwable throwable) {
+ return super.setException(throwable);
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ if (!mayInterruptIfRunning && running)
+ return false;
+ if (complete)
+ return false;
+ return cancel();
+ }
+
+ @Override
+ public void await() throws InterruptedException {
+ try {
+ super.get();
+ } catch (ExecutionException e) {
+ }
+ }
+
+ @Override
+ public void await(long timeout, TimeUnit unit)
+ throws InterruptedException, TimeoutException {
+ try {
+ super.get(timeout, unit);
+ } catch (ExecutionException e) {
+ }
+ }
+
+ @Override
+ public boolean awaitUninterruptibly() {
+ try {
+ super.get();
+ return true;
+ } catch (InterruptedException e) {
+ return false;
+ } catch (ExecutionException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean awaitUninterruptibly(long timeout, TimeUnit unit) {
+ try {
+ super.get(timeout, unit);
+ return true;
+ } catch (InterruptedException e) {
+ return false;
+ } catch (ExecutionException e) {
+ return false;
+ } catch (TimeoutException e) {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Simple container that contains an event and a future
+ *
+ * @author Rogiel
+ */
+ private static class EventContainer {
+ /**
+ * The event
+ */
+ private final WorldEvent event;
+ /**
+ * The future
+ */
+ private final WorldEventFutureImpl extends WorldEvent> future;
+
+ /**
+ * Creates a new instance
+ *
+ * @param event
+ * the event
+ * @param future
+ * the future
+ */
+ public EventContainer(WorldEvent event,
+ WorldEventFutureImpl extends WorldEvent> future) {
+ this.event = event;
+ this.future = future;
+ }
+ }
}
diff --git a/src/main/java/com/l2jserver/service/game/world/event/WorldEventFuture.java b/src/main/java/com/l2jserver/service/game/world/event/WorldEventFuture.java
new file mode 100644
index 000000000..4ab0541b8
--- /dev/null
+++ b/src/main/java/com/l2jserver/service/game/world/event/WorldEventFuture.java
@@ -0,0 +1,74 @@
+/*
+ * This file is part of l2jserver .
+ *
+ * l2jserver is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * l2jserver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with l2jserver. If not, see .
+ */
+package com.l2jserver.service.game.world.event;
+
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * This is an {@link Future} for {@link WorldEvent}. This {@link Future} can be
+ * used to receive notifications once an event has been dispatched to all
+ * listeners.
+ *
+ * @author Rogiel
+ */
+public interface WorldEventFuture extends Future {
+ /**
+ * Waits until the event is dispatched to all listeners
+ *
+ * @throws InterruptedException
+ * if the thread has been interrupted while waiting
+ */
+ void await() throws InterruptedException;
+
+ /**
+ * Waits until the event is dispatched to all listeners
+ *
+ * @param timeout
+ * the timeout
+ * @param unit
+ * the timeout unit
+ *
+ * @throws InterruptedException
+ * if the thread has been interrupted while waiting
+ * @throws TimeoutException
+ * if timeout was exceeded
+ */
+ void await(long timeout, TimeUnit unit) throws InterruptedException,
+ TimeoutException;
+
+ /**
+ * Waits until the event is dispatched to all listeners
+ *
+ * @return true if execution ended with no error, false otherwise
+ */
+ boolean awaitUninterruptibly();
+
+ /**
+ * Waits until the event is dispatched to all listeners
+ *
+ * @param timeout
+ * the timeout
+ * @param unit
+ * the timeout unit
+ *
+ * @return true if execution ended with no error, false otherwise. Please
+ * note that false will be returned if the timeout has expired too!
+ */
+ boolean awaitUninterruptibly(long timeout, TimeUnit unit);
+}