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 future; + + /** + * Creates a new instance + * + * @param event + * the event + * @param future + * the future + */ + public EventContainer(WorldEvent event, + WorldEventFutureImpl 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); +}