Proper handling for Quest timers concurrency.

This commit is contained in:
MobiusDevelopment 2020-01-30 11:59:53 +00:00
parent 25d3220ce2
commit 99c209f0ba
18 changed files with 1240 additions and 1547 deletions

View File

@ -23,13 +23,12 @@ import java.sql.ResultSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -87,10 +86,7 @@ public class Quest extends AbstractScript implements IIdentifiable
public static final Logger LOGGER = Logger.getLogger(Quest.class.getName()); public static final Logger LOGGER = Logger.getLogger(Quest.class.getName());
/** Map containing lists of timers from the name of the timer. */ /** Map containing lists of timers from the name of the timer. */
private Map<String, List<QuestTimer>> _questTimers = null; private final Map<String, List<QuestTimer>> _questTimers = new HashMap<>();
private final ReentrantReadWriteLock _rwLock = new ReentrantReadWriteLock();
private final WriteLock _writeLock = _rwLock.writeLock();
private final ReadLock _readLock = _rwLock.readLock();
/** Map containing all the start conditions. */ /** Map containing all the start conditions. */
private Set<QuestCondition> _startCondition = null; private Set<QuestCondition> _startCondition = null;
@ -252,7 +248,7 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean) * @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean)
*/ */
@ -267,16 +263,6 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public Map<String, List<QuestTimer>> getQuestTimers() public Map<String, List<QuestTimer>> getQuestTimers()
{ {
if (_questTimers == null)
{
synchronized (this)
{
if (_questTimers == null)
{
_questTimers = new ConcurrentHashMap<>(1);
}
}
}
return _questTimers; return _questTimers;
} }
@ -284,26 +270,21 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @param repeating indicates whether the timer is repeatable or one-time.<br> * @param repeating indicates whether the timer is repeatable or one-time.<br>
* If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped. * If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped.
*/ */
public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating) public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating)
{ {
final List<QuestTimer> timers = getQuestTimers().computeIfAbsent(name, k -> new ArrayList<>(1)); synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.computeIfAbsent(name, k -> new CopyOnWriteArrayList<>());
// If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards. // If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards.
if (getQuestTimer(name, npc, player) == null) if (getQuestTimer(name, npc, player) == null)
{
_writeLock.lock();
try
{ {
timers.add(new QuestTimer(this, name, time, npc, player, repeating)); timers.add(new QuestTimer(this, name, time, npc, player, repeating));
} }
finally
{
_writeLock.unlock();
}
} }
} }
@ -316,17 +297,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player) public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
if (_questTimers == null) if (name == null)
{ {
return null; return null;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_readLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return null;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if ((timer != null) && timer.equals(this, name, npc, player)) if ((timer != null) && timer.equals(this, name, npc, player))
@ -334,14 +317,9 @@ public class Quest extends AbstractScript implements IIdentifiable
return timer; return timer;
} }
} }
}
finally
{
_readLock.unlock();
}
}
return null; return null;
} }
}
/** /**
* Cancel all quest timers with the specified name. * Cancel all quest timers with the specified name.
@ -349,17 +327,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimers(String name) public void cancelQuestTimers(String name)
{ {
if (_questTimers == null) if (name == null)
{ {
return; return;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_writeLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if (timer != null) if (timer != null)
@ -369,11 +349,6 @@ public class Quest extends AbstractScript implements IIdentifiable
} }
timers.clear(); timers.clear();
} }
finally
{
_writeLock.unlock();
}
}
} }
/** /**
@ -384,10 +359,28 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimer(String name, Npc npc, PlayerInstance player) public void cancelQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
final QuestTimer timer = getQuestTimer(name, npc, player); if (name == null)
if (timer != null) {
return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(name);
if ((timers == null) || timers.isEmpty())
{
return;
}
for (QuestTimer timer : timers)
{
if ((timer != null) && timer.equals(this, name, npc, player))
{ {
timer.cancel(); timer.cancel();
timers.remove(timer);
return;
}
}
} }
} }
@ -398,21 +391,18 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void removeQuestTimer(QuestTimer timer) public void removeQuestTimer(QuestTimer timer)
{ {
if ((timer != null) && (_questTimers != null)) if (timer == null)
{ {
final List<QuestTimer> timers = getQuestTimers().get(timer.toString()); return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(timer.toString());
if (timers != null) if (timers != null)
{
_writeLock.lock();
try
{ {
timers.remove(timer); timers.remove(timer);
} }
finally
{
_writeLock.unlock();
}
}
} }
} }
@ -2833,25 +2823,17 @@ public class Quest extends AbstractScript implements IIdentifiable
// cancel all pending timers before reloading. // cancel all pending timers before reloading.
// if timers ought to be restarted, the quest can take care of it // if timers ought to be restarted, the quest can take care of it
// with its code (example: save global data indicating what timer must be restarted). // with its code (example: save global data indicating what timer must be restarted).
if (_questTimers != null) synchronized (_questTimers)
{ {
for (List<QuestTimer> timers : getQuestTimers().values()) for (List<QuestTimer> timers : _questTimers.values())
{
_readLock.lock();
try
{ {
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
timer.cancel(); timer.cancel();
} }
}
finally
{
_readLock.unlock();
}
timers.clear(); timers.clear();
} }
getQuestTimers().clear(); _questTimers.clear();
} }
if (removeFromList) if (removeFromList)

View File

@ -23,13 +23,12 @@ import java.sql.ResultSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -88,10 +87,7 @@ public class Quest extends AbstractScript implements IIdentifiable
public static final Logger LOGGER = Logger.getLogger(Quest.class.getName()); public static final Logger LOGGER = Logger.getLogger(Quest.class.getName());
/** Map containing lists of timers from the name of the timer. */ /** Map containing lists of timers from the name of the timer. */
private Map<String, List<QuestTimer>> _questTimers = null; private final Map<String, List<QuestTimer>> _questTimers = new HashMap<>();
private final ReentrantReadWriteLock _rwLock = new ReentrantReadWriteLock();
private final WriteLock _writeLock = _rwLock.writeLock();
private final ReadLock _readLock = _rwLock.readLock();
/** Map containing all the start conditions. */ /** Map containing all the start conditions. */
private Set<QuestCondition> _startCondition = null; private Set<QuestCondition> _startCondition = null;
@ -254,7 +250,7 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean) * @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean)
*/ */
@ -269,16 +265,6 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public Map<String, List<QuestTimer>> getQuestTimers() public Map<String, List<QuestTimer>> getQuestTimers()
{ {
if (_questTimers == null)
{
synchronized (this)
{
if (_questTimers == null)
{
_questTimers = new ConcurrentHashMap<>(1);
}
}
}
return _questTimers; return _questTimers;
} }
@ -286,26 +272,21 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @param repeating indicates whether the timer is repeatable or one-time.<br> * @param repeating indicates whether the timer is repeatable or one-time.<br>
* If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped. * If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped.
*/ */
public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating) public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating)
{ {
final List<QuestTimer> timers = getQuestTimers().computeIfAbsent(name, k -> new ArrayList<>(1)); synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.computeIfAbsent(name, k -> new CopyOnWriteArrayList<>());
// If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards. // If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards.
if (getQuestTimer(name, npc, player) == null) if (getQuestTimer(name, npc, player) == null)
{
_writeLock.lock();
try
{ {
timers.add(new QuestTimer(this, name, time, npc, player, repeating)); timers.add(new QuestTimer(this, name, time, npc, player, repeating));
} }
finally
{
_writeLock.unlock();
}
} }
} }
@ -318,17 +299,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player) public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
if (_questTimers == null) if (name == null)
{ {
return null; return null;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_readLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return null;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if ((timer != null) && timer.equals(this, name, npc, player)) if ((timer != null) && timer.equals(this, name, npc, player))
@ -336,14 +319,9 @@ public class Quest extends AbstractScript implements IIdentifiable
return timer; return timer;
} }
} }
}
finally
{
_readLock.unlock();
}
}
return null; return null;
} }
}
/** /**
* Cancel all quest timers with the specified name. * Cancel all quest timers with the specified name.
@ -351,17 +329,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimers(String name) public void cancelQuestTimers(String name)
{ {
if (_questTimers == null) if (name == null)
{ {
return; return;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_writeLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if (timer != null) if (timer != null)
@ -371,11 +351,6 @@ public class Quest extends AbstractScript implements IIdentifiable
} }
timers.clear(); timers.clear();
} }
finally
{
_writeLock.unlock();
}
}
} }
/** /**
@ -386,10 +361,28 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimer(String name, Npc npc, PlayerInstance player) public void cancelQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
final QuestTimer timer = getQuestTimer(name, npc, player); if (name == null)
if (timer != null) {
return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(name);
if ((timers == null) || timers.isEmpty())
{
return;
}
for (QuestTimer timer : timers)
{
if ((timer != null) && timer.equals(this, name, npc, player))
{ {
timer.cancel(); timer.cancel();
timers.remove(timer);
return;
}
}
} }
} }
@ -400,21 +393,18 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void removeQuestTimer(QuestTimer timer) public void removeQuestTimer(QuestTimer timer)
{ {
if ((timer != null) && (_questTimers != null)) if (timer == null)
{ {
final List<QuestTimer> timers = getQuestTimers().get(timer.toString()); return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(timer.toString());
if (timers != null) if (timers != null)
{
_writeLock.lock();
try
{ {
timers.remove(timer); timers.remove(timer);
} }
finally
{
_writeLock.unlock();
}
}
} }
} }
@ -2835,25 +2825,17 @@ public class Quest extends AbstractScript implements IIdentifiable
// cancel all pending timers before reloading. // cancel all pending timers before reloading.
// if timers ought to be restarted, the quest can take care of it // if timers ought to be restarted, the quest can take care of it
// with its code (example: save global data indicating what timer must be restarted). // with its code (example: save global data indicating what timer must be restarted).
if (_questTimers != null) synchronized (_questTimers)
{ {
for (List<QuestTimer> timers : getQuestTimers().values()) for (List<QuestTimer> timers : _questTimers.values())
{
_readLock.lock();
try
{ {
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
timer.cancel(); timer.cancel();
} }
}
finally
{
_readLock.unlock();
}
timers.clear(); timers.clear();
} }
getQuestTimers().clear(); _questTimers.clear();
} }
if (removeFromList) if (removeFromList)

View File

@ -23,13 +23,12 @@ import java.sql.ResultSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -89,10 +88,7 @@ public class Quest extends AbstractScript implements IIdentifiable
public static final Logger LOGGER = Logger.getLogger(Quest.class.getName()); public static final Logger LOGGER = Logger.getLogger(Quest.class.getName());
/** Map containing lists of timers from the name of the timer. */ /** Map containing lists of timers from the name of the timer. */
private Map<String, List<QuestTimer>> _questTimers = null; private final Map<String, List<QuestTimer>> _questTimers = new HashMap<>();
private final ReentrantReadWriteLock _rwLock = new ReentrantReadWriteLock();
private final WriteLock _writeLock = _rwLock.writeLock();
private final ReadLock _readLock = _rwLock.readLock();
/** Map containing all the start conditions. */ /** Map containing all the start conditions. */
private Set<QuestCondition> _startCondition = null; private Set<QuestCondition> _startCondition = null;
@ -255,7 +251,7 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean) * @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean)
*/ */
@ -270,16 +266,6 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public Map<String, List<QuestTimer>> getQuestTimers() public Map<String, List<QuestTimer>> getQuestTimers()
{ {
if (_questTimers == null)
{
synchronized (this)
{
if (_questTimers == null)
{
_questTimers = new ConcurrentHashMap<>(1);
}
}
}
return _questTimers; return _questTimers;
} }
@ -287,26 +273,21 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @param repeating indicates whether the timer is repeatable or one-time.<br> * @param repeating indicates whether the timer is repeatable or one-time.<br>
* If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped. * If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped.
*/ */
public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating) public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating)
{ {
final List<QuestTimer> timers = getQuestTimers().computeIfAbsent(name, k -> new ArrayList<>(1)); synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.computeIfAbsent(name, k -> new CopyOnWriteArrayList<>());
// If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards. // If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards.
if (getQuestTimer(name, npc, player) == null) if (getQuestTimer(name, npc, player) == null)
{
_writeLock.lock();
try
{ {
timers.add(new QuestTimer(this, name, time, npc, player, repeating)); timers.add(new QuestTimer(this, name, time, npc, player, repeating));
} }
finally
{
_writeLock.unlock();
}
} }
} }
@ -319,17 +300,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player) public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
if (_questTimers == null) if (name == null)
{ {
return null; return null;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_readLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return null;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if ((timer != null) && timer.equals(this, name, npc, player)) if ((timer != null) && timer.equals(this, name, npc, player))
@ -337,14 +320,9 @@ public class Quest extends AbstractScript implements IIdentifiable
return timer; return timer;
} }
} }
}
finally
{
_readLock.unlock();
}
}
return null; return null;
} }
}
/** /**
* Cancel all quest timers with the specified name. * Cancel all quest timers with the specified name.
@ -352,17 +330,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimers(String name) public void cancelQuestTimers(String name)
{ {
if (_questTimers == null) if (name == null)
{ {
return; return;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_writeLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if (timer != null) if (timer != null)
@ -372,11 +352,6 @@ public class Quest extends AbstractScript implements IIdentifiable
} }
timers.clear(); timers.clear();
} }
finally
{
_writeLock.unlock();
}
}
} }
/** /**
@ -387,10 +362,28 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimer(String name, Npc npc, PlayerInstance player) public void cancelQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
final QuestTimer timer = getQuestTimer(name, npc, player); if (name == null)
if (timer != null) {
return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(name);
if ((timers == null) || timers.isEmpty())
{
return;
}
for (QuestTimer timer : timers)
{
if ((timer != null) && timer.equals(this, name, npc, player))
{ {
timer.cancel(); timer.cancel();
timers.remove(timer);
return;
}
}
} }
} }
@ -401,21 +394,18 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void removeQuestTimer(QuestTimer timer) public void removeQuestTimer(QuestTimer timer)
{ {
if ((timer != null) && (_questTimers != null)) if (timer == null)
{ {
final List<QuestTimer> timers = getQuestTimers().get(timer.toString()); return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(timer.toString());
if (timers != null) if (timers != null)
{
_writeLock.lock();
try
{ {
timers.remove(timer); timers.remove(timer);
} }
finally
{
_writeLock.unlock();
}
}
} }
} }
@ -2836,25 +2826,17 @@ public class Quest extends AbstractScript implements IIdentifiable
// cancel all pending timers before reloading. // cancel all pending timers before reloading.
// if timers ought to be restarted, the quest can take care of it // if timers ought to be restarted, the quest can take care of it
// with its code (example: save global data indicating what timer must be restarted). // with its code (example: save global data indicating what timer must be restarted).
if (_questTimers != null) synchronized (_questTimers)
{ {
for (List<QuestTimer> timers : getQuestTimers().values()) for (List<QuestTimer> timers : _questTimers.values())
{
_readLock.lock();
try
{ {
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
timer.cancel(); timer.cancel();
} }
}
finally
{
_readLock.unlock();
}
timers.clear(); timers.clear();
} }
getQuestTimers().clear(); _questTimers.clear();
} }
if (removeFromList) if (removeFromList)

View File

@ -23,13 +23,12 @@ import java.sql.ResultSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -90,10 +89,7 @@ public class Quest extends AbstractScript implements IIdentifiable
public static final Logger LOGGER = Logger.getLogger(Quest.class.getName()); public static final Logger LOGGER = Logger.getLogger(Quest.class.getName());
/** Map containing lists of timers from the name of the timer. */ /** Map containing lists of timers from the name of the timer. */
private Map<String, List<QuestTimer>> _questTimers = null; private final Map<String, List<QuestTimer>> _questTimers = new HashMap<>();
private final ReentrantReadWriteLock _rwLock = new ReentrantReadWriteLock();
private final WriteLock _writeLock = _rwLock.writeLock();
private final ReadLock _readLock = _rwLock.readLock();
/** Map containing all the start conditions. */ /** Map containing all the start conditions. */
private Set<QuestCondition> _startCondition = null; private Set<QuestCondition> _startCondition = null;
@ -255,7 +251,7 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean) * @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean)
*/ */
@ -270,16 +266,6 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public Map<String, List<QuestTimer>> getQuestTimers() public Map<String, List<QuestTimer>> getQuestTimers()
{ {
if (_questTimers == null)
{
synchronized (this)
{
if (_questTimers == null)
{
_questTimers = new ConcurrentHashMap<>(1);
}
}
}
return _questTimers; return _questTimers;
} }
@ -287,26 +273,21 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @param repeating indicates whether the timer is repeatable or one-time.<br> * @param repeating indicates whether the timer is repeatable or one-time.<br>
* If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped. * If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped.
*/ */
public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating) public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating)
{ {
final List<QuestTimer> timers = getQuestTimers().computeIfAbsent(name, k -> new ArrayList<>(1)); synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.computeIfAbsent(name, k -> new CopyOnWriteArrayList<>());
// If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards. // If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards.
if (getQuestTimer(name, npc, player) == null) if (getQuestTimer(name, npc, player) == null)
{
_writeLock.lock();
try
{ {
timers.add(new QuestTimer(this, name, time, npc, player, repeating)); timers.add(new QuestTimer(this, name, time, npc, player, repeating));
} }
finally
{
_writeLock.unlock();
}
} }
} }
@ -319,17 +300,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player) public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
if (_questTimers == null) if (name == null)
{ {
return null; return null;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_readLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return null;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if ((timer != null) && timer.equals(this, name, npc, player)) if ((timer != null) && timer.equals(this, name, npc, player))
@ -337,14 +320,9 @@ public class Quest extends AbstractScript implements IIdentifiable
return timer; return timer;
} }
} }
}
finally
{
_readLock.unlock();
}
}
return null; return null;
} }
}
/** /**
* Cancel all quest timers with the specified name. * Cancel all quest timers with the specified name.
@ -352,17 +330,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimers(String name) public void cancelQuestTimers(String name)
{ {
if (_questTimers == null) if (name == null)
{ {
return; return;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_writeLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if (timer != null) if (timer != null)
@ -372,11 +352,6 @@ public class Quest extends AbstractScript implements IIdentifiable
} }
timers.clear(); timers.clear();
} }
finally
{
_writeLock.unlock();
}
}
} }
/** /**
@ -387,10 +362,28 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimer(String name, Npc npc, PlayerInstance player) public void cancelQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
final QuestTimer timer = getQuestTimer(name, npc, player); if (name == null)
if (timer != null) {
return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(name);
if ((timers == null) || timers.isEmpty())
{
return;
}
for (QuestTimer timer : timers)
{
if ((timer != null) && timer.equals(this, name, npc, player))
{ {
timer.cancel(); timer.cancel();
timers.remove(timer);
return;
}
}
} }
} }
@ -401,21 +394,18 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void removeQuestTimer(QuestTimer timer) public void removeQuestTimer(QuestTimer timer)
{ {
if ((timer != null) && (_questTimers != null)) if (timer == null)
{ {
final List<QuestTimer> timers = getQuestTimers().get(timer.toString()); return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(timer.toString());
if (timers != null) if (timers != null)
{
_writeLock.lock();
try
{ {
timers.remove(timer); timers.remove(timer);
} }
finally
{
_writeLock.unlock();
}
}
} }
} }
@ -2836,25 +2826,17 @@ public class Quest extends AbstractScript implements IIdentifiable
// cancel all pending timers before reloading. // cancel all pending timers before reloading.
// if timers ought to be restarted, the quest can take care of it // if timers ought to be restarted, the quest can take care of it
// with its code (example: save global data indicating what timer must be restarted). // with its code (example: save global data indicating what timer must be restarted).
if (_questTimers != null) synchronized (_questTimers)
{ {
for (List<QuestTimer> timers : getQuestTimers().values()) for (List<QuestTimer> timers : _questTimers.values())
{
_readLock.lock();
try
{ {
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
timer.cancel(); timer.cancel();
} }
}
finally
{
_readLock.unlock();
}
timers.clear(); timers.clear();
} }
getQuestTimers().clear(); _questTimers.clear();
} }
if (removeFromList) if (removeFromList)

View File

@ -23,13 +23,12 @@ import java.sql.ResultSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -90,10 +89,7 @@ public class Quest extends AbstractScript implements IIdentifiable
public static final Logger LOGGER = Logger.getLogger(Quest.class.getName()); public static final Logger LOGGER = Logger.getLogger(Quest.class.getName());
/** Map containing lists of timers from the name of the timer. */ /** Map containing lists of timers from the name of the timer. */
private Map<String, List<QuestTimer>> _questTimers = null; private final Map<String, List<QuestTimer>> _questTimers = new HashMap<>();
private final ReentrantReadWriteLock _rwLock = new ReentrantReadWriteLock();
private final WriteLock _writeLock = _rwLock.writeLock();
private final ReadLock _readLock = _rwLock.readLock();
/** Map containing all the start conditions. */ /** Map containing all the start conditions. */
private Set<QuestCondition> _startCondition = null; private Set<QuestCondition> _startCondition = null;
@ -255,7 +251,7 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean) * @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean)
*/ */
@ -270,16 +266,6 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public Map<String, List<QuestTimer>> getQuestTimers() public Map<String, List<QuestTimer>> getQuestTimers()
{ {
if (_questTimers == null)
{
synchronized (this)
{
if (_questTimers == null)
{
_questTimers = new ConcurrentHashMap<>(1);
}
}
}
return _questTimers; return _questTimers;
} }
@ -287,26 +273,21 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @param repeating indicates whether the timer is repeatable or one-time.<br> * @param repeating indicates whether the timer is repeatable or one-time.<br>
* If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped. * If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped.
*/ */
public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating) public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating)
{ {
final List<QuestTimer> timers = getQuestTimers().computeIfAbsent(name, k -> new ArrayList<>(1)); synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.computeIfAbsent(name, k -> new CopyOnWriteArrayList<>());
// If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards. // If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards.
if (getQuestTimer(name, npc, player) == null) if (getQuestTimer(name, npc, player) == null)
{
_writeLock.lock();
try
{ {
timers.add(new QuestTimer(this, name, time, npc, player, repeating)); timers.add(new QuestTimer(this, name, time, npc, player, repeating));
} }
finally
{
_writeLock.unlock();
}
} }
} }
@ -319,17 +300,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player) public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
if (_questTimers == null) if (name == null)
{ {
return null; return null;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_readLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return null;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if ((timer != null) && timer.equals(this, name, npc, player)) if ((timer != null) && timer.equals(this, name, npc, player))
@ -337,14 +320,9 @@ public class Quest extends AbstractScript implements IIdentifiable
return timer; return timer;
} }
} }
}
finally
{
_readLock.unlock();
}
}
return null; return null;
} }
}
/** /**
* Cancel all quest timers with the specified name. * Cancel all quest timers with the specified name.
@ -352,17 +330,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimers(String name) public void cancelQuestTimers(String name)
{ {
if (_questTimers == null) if (name == null)
{ {
return; return;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_writeLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if (timer != null) if (timer != null)
@ -372,11 +352,6 @@ public class Quest extends AbstractScript implements IIdentifiable
} }
timers.clear(); timers.clear();
} }
finally
{
_writeLock.unlock();
}
}
} }
/** /**
@ -387,10 +362,28 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimer(String name, Npc npc, PlayerInstance player) public void cancelQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
final QuestTimer timer = getQuestTimer(name, npc, player); if (name == null)
if (timer != null) {
return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(name);
if ((timers == null) || timers.isEmpty())
{
return;
}
for (QuestTimer timer : timers)
{
if ((timer != null) && timer.equals(this, name, npc, player))
{ {
timer.cancel(); timer.cancel();
timers.remove(timer);
return;
}
}
} }
} }
@ -401,21 +394,18 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void removeQuestTimer(QuestTimer timer) public void removeQuestTimer(QuestTimer timer)
{ {
if ((timer != null) && (_questTimers != null)) if (timer == null)
{ {
final List<QuestTimer> timers = getQuestTimers().get(timer.toString()); return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(timer.toString());
if (timers != null) if (timers != null)
{
_writeLock.lock();
try
{ {
timers.remove(timer); timers.remove(timer);
} }
finally
{
_writeLock.unlock();
}
}
} }
} }
@ -2836,25 +2826,17 @@ public class Quest extends AbstractScript implements IIdentifiable
// cancel all pending timers before reloading. // cancel all pending timers before reloading.
// if timers ought to be restarted, the quest can take care of it // if timers ought to be restarted, the quest can take care of it
// with its code (example: save global data indicating what timer must be restarted). // with its code (example: save global data indicating what timer must be restarted).
if (_questTimers != null) synchronized (_questTimers)
{ {
for (List<QuestTimer> timers : getQuestTimers().values()) for (List<QuestTimer> timers : _questTimers.values())
{
_readLock.lock();
try
{ {
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
timer.cancel(); timer.cancel();
} }
}
finally
{
_readLock.unlock();
}
timers.clear(); timers.clear();
} }
getQuestTimers().clear(); _questTimers.clear();
} }
if (removeFromList) if (removeFromList)

View File

@ -23,13 +23,12 @@ import java.sql.ResultSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -90,10 +89,7 @@ public class Quest extends AbstractScript implements IIdentifiable
public static final Logger LOGGER = Logger.getLogger(Quest.class.getName()); public static final Logger LOGGER = Logger.getLogger(Quest.class.getName());
/** Map containing lists of timers from the name of the timer. */ /** Map containing lists of timers from the name of the timer. */
private Map<String, List<QuestTimer>> _questTimers = null; private final Map<String, List<QuestTimer>> _questTimers = new HashMap<>();
private final ReentrantReadWriteLock _rwLock = new ReentrantReadWriteLock();
private final WriteLock _writeLock = _rwLock.writeLock();
private final ReadLock _readLock = _rwLock.readLock();
/** Map containing all the start conditions. */ /** Map containing all the start conditions. */
private Set<QuestCondition> _startCondition = null; private Set<QuestCondition> _startCondition = null;
@ -255,7 +251,7 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean) * @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean)
*/ */
@ -270,16 +266,6 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public Map<String, List<QuestTimer>> getQuestTimers() public Map<String, List<QuestTimer>> getQuestTimers()
{ {
if (_questTimers == null)
{
synchronized (this)
{
if (_questTimers == null)
{
_questTimers = new ConcurrentHashMap<>(1);
}
}
}
return _questTimers; return _questTimers;
} }
@ -287,26 +273,21 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @param repeating indicates whether the timer is repeatable or one-time.<br> * @param repeating indicates whether the timer is repeatable or one-time.<br>
* If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped. * If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped.
*/ */
public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating) public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating)
{ {
final List<QuestTimer> timers = getQuestTimers().computeIfAbsent(name, k -> new ArrayList<>(1)); synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.computeIfAbsent(name, k -> new CopyOnWriteArrayList<>());
// If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards. // If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards.
if (getQuestTimer(name, npc, player) == null) if (getQuestTimer(name, npc, player) == null)
{
_writeLock.lock();
try
{ {
timers.add(new QuestTimer(this, name, time, npc, player, repeating)); timers.add(new QuestTimer(this, name, time, npc, player, repeating));
} }
finally
{
_writeLock.unlock();
}
} }
} }
@ -319,17 +300,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player) public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
if (_questTimers == null) if (name == null)
{ {
return null; return null;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_readLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return null;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if ((timer != null) && timer.equals(this, name, npc, player)) if ((timer != null) && timer.equals(this, name, npc, player))
@ -337,14 +320,9 @@ public class Quest extends AbstractScript implements IIdentifiable
return timer; return timer;
} }
} }
}
finally
{
_readLock.unlock();
}
}
return null; return null;
} }
}
/** /**
* Cancel all quest timers with the specified name. * Cancel all quest timers with the specified name.
@ -352,17 +330,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimers(String name) public void cancelQuestTimers(String name)
{ {
if (_questTimers == null) if (name == null)
{ {
return; return;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_writeLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if (timer != null) if (timer != null)
@ -372,11 +352,6 @@ public class Quest extends AbstractScript implements IIdentifiable
} }
timers.clear(); timers.clear();
} }
finally
{
_writeLock.unlock();
}
}
} }
/** /**
@ -387,10 +362,28 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimer(String name, Npc npc, PlayerInstance player) public void cancelQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
final QuestTimer timer = getQuestTimer(name, npc, player); if (name == null)
if (timer != null) {
return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(name);
if ((timers == null) || timers.isEmpty())
{
return;
}
for (QuestTimer timer : timers)
{
if ((timer != null) && timer.equals(this, name, npc, player))
{ {
timer.cancel(); timer.cancel();
timers.remove(timer);
return;
}
}
} }
} }
@ -401,21 +394,18 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void removeQuestTimer(QuestTimer timer) public void removeQuestTimer(QuestTimer timer)
{ {
if ((timer != null) && (_questTimers != null)) if (timer == null)
{ {
final List<QuestTimer> timers = getQuestTimers().get(timer.toString()); return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(timer.toString());
if (timers != null) if (timers != null)
{
_writeLock.lock();
try
{ {
timers.remove(timer); timers.remove(timer);
} }
finally
{
_writeLock.unlock();
}
}
} }
} }
@ -2836,25 +2826,17 @@ public class Quest extends AbstractScript implements IIdentifiable
// cancel all pending timers before reloading. // cancel all pending timers before reloading.
// if timers ought to be restarted, the quest can take care of it // if timers ought to be restarted, the quest can take care of it
// with its code (example: save global data indicating what timer must be restarted). // with its code (example: save global data indicating what timer must be restarted).
if (_questTimers != null) synchronized (_questTimers)
{ {
for (List<QuestTimer> timers : getQuestTimers().values()) for (List<QuestTimer> timers : _questTimers.values())
{
_readLock.lock();
try
{ {
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
timer.cancel(); timer.cancel();
} }
}
finally
{
_readLock.unlock();
}
timers.clear(); timers.clear();
} }
getQuestTimers().clear(); _questTimers.clear();
} }
if (removeFromList) if (removeFromList)

View File

@ -23,13 +23,12 @@ import java.sql.ResultSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -90,10 +89,7 @@ public class Quest extends AbstractScript implements IIdentifiable
public static final Logger LOGGER = Logger.getLogger(Quest.class.getName()); public static final Logger LOGGER = Logger.getLogger(Quest.class.getName());
/** Map containing lists of timers from the name of the timer. */ /** Map containing lists of timers from the name of the timer. */
private Map<String, List<QuestTimer>> _questTimers = null; private final Map<String, List<QuestTimer>> _questTimers = new HashMap<>();
private final ReentrantReadWriteLock _rwLock = new ReentrantReadWriteLock();
private final WriteLock _writeLock = _rwLock.writeLock();
private final ReadLock _readLock = _rwLock.readLock();
/** Map containing all the start conditions. */ /** Map containing all the start conditions. */
private Set<QuestCondition> _startCondition = null; private Set<QuestCondition> _startCondition = null;
@ -255,7 +251,7 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean) * @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean)
*/ */
@ -270,16 +266,6 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public Map<String, List<QuestTimer>> getQuestTimers() public Map<String, List<QuestTimer>> getQuestTimers()
{ {
if (_questTimers == null)
{
synchronized (this)
{
if (_questTimers == null)
{
_questTimers = new ConcurrentHashMap<>(1);
}
}
}
return _questTimers; return _questTimers;
} }
@ -287,26 +273,21 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @param repeating indicates whether the timer is repeatable or one-time.<br> * @param repeating indicates whether the timer is repeatable or one-time.<br>
* If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped. * If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped.
*/ */
public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating) public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating)
{ {
final List<QuestTimer> timers = getQuestTimers().computeIfAbsent(name, k -> new ArrayList<>(1)); synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.computeIfAbsent(name, k -> new CopyOnWriteArrayList<>());
// If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards. // If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards.
if (getQuestTimer(name, npc, player) == null) if (getQuestTimer(name, npc, player) == null)
{
_writeLock.lock();
try
{ {
timers.add(new QuestTimer(this, name, time, npc, player, repeating)); timers.add(new QuestTimer(this, name, time, npc, player, repeating));
} }
finally
{
_writeLock.unlock();
}
} }
} }
@ -319,17 +300,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player) public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
if (_questTimers == null) if (name == null)
{ {
return null; return null;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_readLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return null;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if ((timer != null) && timer.equals(this, name, npc, player)) if ((timer != null) && timer.equals(this, name, npc, player))
@ -337,14 +320,9 @@ public class Quest extends AbstractScript implements IIdentifiable
return timer; return timer;
} }
} }
}
finally
{
_readLock.unlock();
}
}
return null; return null;
} }
}
/** /**
* Cancel all quest timers with the specified name. * Cancel all quest timers with the specified name.
@ -352,17 +330,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimers(String name) public void cancelQuestTimers(String name)
{ {
if (_questTimers == null) if (name == null)
{ {
return; return;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_writeLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if (timer != null) if (timer != null)
@ -372,11 +352,6 @@ public class Quest extends AbstractScript implements IIdentifiable
} }
timers.clear(); timers.clear();
} }
finally
{
_writeLock.unlock();
}
}
} }
/** /**
@ -387,10 +362,28 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimer(String name, Npc npc, PlayerInstance player) public void cancelQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
final QuestTimer timer = getQuestTimer(name, npc, player); if (name == null)
if (timer != null) {
return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(name);
if ((timers == null) || timers.isEmpty())
{
return;
}
for (QuestTimer timer : timers)
{
if ((timer != null) && timer.equals(this, name, npc, player))
{ {
timer.cancel(); timer.cancel();
timers.remove(timer);
return;
}
}
} }
} }
@ -401,21 +394,18 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void removeQuestTimer(QuestTimer timer) public void removeQuestTimer(QuestTimer timer)
{ {
if ((timer != null) && (_questTimers != null)) if (timer == null)
{ {
final List<QuestTimer> timers = getQuestTimers().get(timer.toString()); return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(timer.toString());
if (timers != null) if (timers != null)
{
_writeLock.lock();
try
{ {
timers.remove(timer); timers.remove(timer);
} }
finally
{
_writeLock.unlock();
}
}
} }
} }
@ -2836,25 +2826,17 @@ public class Quest extends AbstractScript implements IIdentifiable
// cancel all pending timers before reloading. // cancel all pending timers before reloading.
// if timers ought to be restarted, the quest can take care of it // if timers ought to be restarted, the quest can take care of it
// with its code (example: save global data indicating what timer must be restarted). // with its code (example: save global data indicating what timer must be restarted).
if (_questTimers != null) synchronized (_questTimers)
{ {
for (List<QuestTimer> timers : getQuestTimers().values()) for (List<QuestTimer> timers : _questTimers.values())
{
_readLock.lock();
try
{ {
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
timer.cancel(); timer.cancel();
} }
}
finally
{
_readLock.unlock();
}
timers.clear(); timers.clear();
} }
getQuestTimers().clear(); _questTimers.clear();
} }
if (removeFromList) if (removeFromList)

View File

@ -23,13 +23,12 @@ import java.sql.ResultSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -90,10 +89,7 @@ public class Quest extends AbstractScript implements IIdentifiable
public static final Logger LOGGER = Logger.getLogger(Quest.class.getName()); public static final Logger LOGGER = Logger.getLogger(Quest.class.getName());
/** Map containing lists of timers from the name of the timer. */ /** Map containing lists of timers from the name of the timer. */
private Map<String, List<QuestTimer>> _questTimers = null; private final Map<String, List<QuestTimer>> _questTimers = new HashMap<>();
private final ReentrantReadWriteLock _rwLock = new ReentrantReadWriteLock();
private final WriteLock _writeLock = _rwLock.writeLock();
private final ReadLock _readLock = _rwLock.readLock();
/** Map containing all the start conditions. */ /** Map containing all the start conditions. */
private Set<QuestCondition> _startCondition = null; private Set<QuestCondition> _startCondition = null;
@ -265,7 +261,7 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean) * @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean)
*/ */
@ -280,16 +276,6 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public Map<String, List<QuestTimer>> getQuestTimers() public Map<String, List<QuestTimer>> getQuestTimers()
{ {
if (_questTimers == null)
{
synchronized (this)
{
if (_questTimers == null)
{
_questTimers = new ConcurrentHashMap<>(1);
}
}
}
return _questTimers; return _questTimers;
} }
@ -297,26 +283,21 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @param repeating indicates whether the timer is repeatable or one-time.<br> * @param repeating indicates whether the timer is repeatable or one-time.<br>
* If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped. * If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped.
*/ */
public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating) public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating)
{ {
final List<QuestTimer> timers = getQuestTimers().computeIfAbsent(name, k -> new ArrayList<>(1)); synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.computeIfAbsent(name, k -> new CopyOnWriteArrayList<>());
// If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards. // If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards.
if (getQuestTimer(name, npc, player) == null) if (getQuestTimer(name, npc, player) == null)
{
_writeLock.lock();
try
{ {
timers.add(new QuestTimer(this, name, time, npc, player, repeating)); timers.add(new QuestTimer(this, name, time, npc, player, repeating));
} }
finally
{
_writeLock.unlock();
}
} }
} }
@ -329,17 +310,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player) public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
if (_questTimers == null) if (name == null)
{ {
return null; return null;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_readLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return null;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if ((timer != null) && timer.equals(this, name, npc, player)) if ((timer != null) && timer.equals(this, name, npc, player))
@ -347,14 +330,9 @@ public class Quest extends AbstractScript implements IIdentifiable
return timer; return timer;
} }
} }
}
finally
{
_readLock.unlock();
}
}
return null; return null;
} }
}
/** /**
* Cancel all quest timers with the specified name. * Cancel all quest timers with the specified name.
@ -362,17 +340,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimers(String name) public void cancelQuestTimers(String name)
{ {
if (_questTimers == null) if (name == null)
{ {
return; return;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_writeLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if (timer != null) if (timer != null)
@ -382,11 +362,6 @@ public class Quest extends AbstractScript implements IIdentifiable
} }
timers.clear(); timers.clear();
} }
finally
{
_writeLock.unlock();
}
}
} }
/** /**
@ -397,10 +372,28 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimer(String name, Npc npc, PlayerInstance player) public void cancelQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
final QuestTimer timer = getQuestTimer(name, npc, player); if (name == null)
if (timer != null) {
return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(name);
if ((timers == null) || timers.isEmpty())
{
return;
}
for (QuestTimer timer : timers)
{
if ((timer != null) && timer.equals(this, name, npc, player))
{ {
timer.cancel(); timer.cancel();
timers.remove(timer);
return;
}
}
} }
} }
@ -411,21 +404,18 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void removeQuestTimer(QuestTimer timer) public void removeQuestTimer(QuestTimer timer)
{ {
if ((timer != null) && (_questTimers != null)) if (timer == null)
{ {
final List<QuestTimer> timers = getQuestTimers().get(timer.toString()); return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(timer.toString());
if (timers != null) if (timers != null)
{
_writeLock.lock();
try
{ {
timers.remove(timer); timers.remove(timer);
} }
finally
{
_writeLock.unlock();
}
}
} }
} }
@ -2846,25 +2836,17 @@ public class Quest extends AbstractScript implements IIdentifiable
// cancel all pending timers before reloading. // cancel all pending timers before reloading.
// if timers ought to be restarted, the quest can take care of it // if timers ought to be restarted, the quest can take care of it
// with its code (example: save global data indicating what timer must be restarted). // with its code (example: save global data indicating what timer must be restarted).
if (_questTimers != null) synchronized (_questTimers)
{ {
for (List<QuestTimer> timers : getQuestTimers().values()) for (List<QuestTimer> timers : _questTimers.values())
{
_readLock.lock();
try
{ {
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
timer.cancel(); timer.cancel();
} }
}
finally
{
_readLock.unlock();
}
timers.clear(); timers.clear();
} }
getQuestTimers().clear(); _questTimers.clear();
} }
if (removeFromList) if (removeFromList)

View File

@ -27,6 +27,7 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -64,10 +65,10 @@ public class Quest extends ManagedScript
private static final String HTML_NONE_AVAILABLE = "<html><body>You are either not on a quest that involves this NPC, or you don't meet this NPC's minimum quest requirements.</body></html>"; private static final String HTML_NONE_AVAILABLE = "<html><body>You are either not on a quest that involves this NPC, or you don't meet this NPC's minimum quest requirements.</body></html>";
private static final String HTML_ALREADY_COMPLETED = "<html><body>This quest has already been completed.</body></html>"; private static final String HTML_ALREADY_COMPLETED = "<html><body>This quest has already been completed.</body></html>";
/** HashMap containing events from String value of the event */ /** Map containing events from String value of the event */
private static Map<String, Quest> _allEventsS = new HashMap<>(); private static Map<String, Quest> _allEvents = new HashMap<>();
/** HashMap containing lists of timers from the name of the timer */ /** Map containing lists of timers from the name of the timer */
private final Map<String, ArrayList<QuestTimer>> _allEventTimers = new HashMap<>(); private final Map<String, List<QuestTimer>> _questTimers = new HashMap<>();
private final int _questId; private final int _questId;
private final String _prefixPath; // used only for admin_quest_reload private final String _prefixPath; // used only for admin_quest_reload
@ -140,7 +141,7 @@ public class Quest extends ManagedScript
*/ */
public static Collection<Quest> findAllEvents() public static Collection<Quest> findAllEvents()
{ {
return _allEventsS.values(); return _allEvents.values();
} }
/** /**
@ -168,7 +169,7 @@ public class Quest extends ManagedScript
} }
else else
{ {
_allEventsS.put(getName(), this); _allEvents.put(getName(), this);
} }
initGlobalData(); initGlobalData();
@ -317,10 +318,12 @@ public class Quest extends ManagedScript
} }
/** /**
* @param name * Add a timer to the quest (if it doesn't exist already) and start it.
* @param time * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, NpcInstance, PlayerInstance)})
* @param npc * @param time time in ms for when to fire the timer
* @param player * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null)
* @see #startQuestTimer(String, long, NpcInstance, PlayerInstance, boolean)
*/ */
public void startQuestTimer(String name, long time, NpcInstance npc, PlayerInstance player) public void startQuestTimer(String name, long time, NpcInstance npc, PlayerInstance player)
{ {
@ -328,53 +331,43 @@ public class Quest extends ManagedScript
} }
/** /**
* Add a timer to the quest, if it doesn't exist already. If the timer is repeatable, it will auto-fire automatically, at a fixed rate, until explicitly canceled. * Gets the quest timers.
* @param name name of the timer (also passed back as "event" in onAdvEvent) * @return the quest timers
* @param time time in ms for when to fire the timer
* @param npc npc associated with this timer (can be null)
* @param player player associated with this timer (can be null)
* @param repeating indicates if the timer is repeatable or one-time.
*/ */
public synchronized void startQuestTimer(String name, long time, NpcInstance npc, PlayerInstance player, boolean repeating) public Map<String, List<QuestTimer>> getQuestTimers()
{ {
synchronized (_allEventTimers) return _questTimers;
{
ArrayList<QuestTimer> timers = _allEventTimers.get(name);
// no timer exists with the same name, at all
if (timers == null)
{
timers = new ArrayList<>();
timers.add(new QuestTimer(this, name, time, npc, player, repeating));
// a timer with this name exists, but may not be for the same set of npc and player
}
else
{
QuestTimer timer = null;
for (int i = 0; i < timers.size(); i++)
{
final QuestTimer tmp = timers.get(i);
if ((tmp != null) && tmp.equals(this, name, npc, player))
{
timer = tmp;
break;
}
} }
// if there exists a timer with this name, allow the timer only if the [npc, player] set is unique /**
// nulls act as wildcards * Add a timer to the quest (if it doesn't exist already) and start it.
if (timer == null) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, NpcInstance, PlayerInstance)})
* @param time time in ms for when to fire the timer
* @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null)
* @param repeating indicates whether the timer is repeatable or one-time.<br>
* If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped.
*/
public void startQuestTimer(String name, long time, NpcInstance npc, PlayerInstance player, boolean repeating)
{
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.computeIfAbsent(name, k -> new CopyOnWriteArrayList<>());
// If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards.
if (getQuestTimer(name, npc, player) == null)
{ {
timers.add(new QuestTimer(this, name, time, npc, player, repeating)); timers.add(new QuestTimer(this, name, time, npc, player, repeating));
} }
} }
_allEventTimers.put(name, timers);
}
} }
/**
* Get a quest timer that matches the provided name and parameters.
* @param name the name of the quest timer to get
* @param npc the NPC associated with the quest timer to get
* @param player the player associated with the quest timer to get
* @return the quest timer that matches the specified parameters or {@code null} if nothing was found
*/
public QuestTimer getQuestTimer(String name, NpcInstance npc, PlayerInstance player) public QuestTimer getQuestTimer(String name, NpcInstance npc, PlayerInstance player)
{ {
if (name == null) if (name == null)
@ -382,19 +375,16 @@ public class Quest extends ManagedScript
return null; return null;
} }
synchronized (_allEventTimers) synchronized (_questTimers)
{ {
final ArrayList<QuestTimer> qt = _allEventTimers.get(name); final List<QuestTimer> timers = _questTimers.get(name);
if ((timers == null) || timers.isEmpty())
if ((qt == null) || qt.isEmpty())
{ {
return null; return null;
} }
for (int i = 0; i < qt.size(); i++) for (QuestTimer timer : timers)
{ {
final QuestTimer timer = qt.get(i);
if ((timer != null) && timer.equals(this, name, npc, player)) if ((timer != null) && timer.equals(this, name, npc, player))
{ {
return timer; return timer;
@ -404,34 +394,10 @@ public class Quest extends ManagedScript
} }
} }
public void cancelQuestTimer(String name, NpcInstance npc, PlayerInstance player) /**
{ * Cancel all quest timers with the specified name.
if (name == null) * @param name the name of the quest timers to cancel
{ */
return;
}
synchronized (_allEventTimers)
{
final ArrayList<QuestTimer> qt = _allEventTimers.get(name);
if ((qt == null) || qt.isEmpty())
{
return;
}
for (int i = 0; i < qt.size(); i++)
{
final QuestTimer timer = qt.get(i);
if ((timer != null) && timer.equals(this, name, npc, player))
{
timer.cancel();
qt.remove(timer);
return;
}
}
}
}
public void cancelQuestTimers(String name) public void cancelQuestTimers(String name)
{ {
if (name == null) if (name == null)
@ -439,11 +405,10 @@ public class Quest extends ManagedScript
return; return;
} }
synchronized (_allEventTimers) synchronized (_questTimers)
{ {
final ArrayList<QuestTimer> timers = _allEventTimers.get(name); final List<QuestTimer> timers = _questTimers.get(name);
if ((timers == null) || timers.isEmpty())
if (timers == null)
{ {
return; return;
} }
@ -455,11 +420,48 @@ public class Quest extends ManagedScript
timer.cancel(); timer.cancel();
} }
} }
timers.clear(); timers.clear();
} }
} }
/**
* Cancel the quest timer that matches the specified name and parameters.
* @param name the name of the quest timer to cancel
* @param npc the NPC associated with the quest timer to cancel
* @param player the player associated with the quest timer to cancel
*/
public void cancelQuestTimer(String name, NpcInstance npc, PlayerInstance player)
{
if (name == null)
{
return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(name);
if ((timers == null) || timers.isEmpty())
{
return;
}
for (QuestTimer timer : timers)
{
if ((timer != null) && timer.equals(this, name, npc, player))
{
timer.cancel();
timers.remove(timer);
return;
}
}
}
}
/**
* Remove a quest timer from the list of all timers.<br>
* Note: does not stop the timer itself!
* @param timer the {@link QuestState} object to remove
*/
public void removeQuestTimer(QuestTimer timer) public void removeQuestTimer(QuestTimer timer)
{ {
if (timer == null) if (timer == null)
@ -467,20 +469,18 @@ public class Quest extends ManagedScript
return; return;
} }
synchronized (_allEventTimers) synchronized (_questTimers)
{ {
final ArrayList<QuestTimer> timers = _allEventTimers.get(timer.toString()); final List<QuestTimer> timers = _questTimers.get(timer.toString());
if (timers != null)
if (timers == null)
{ {
return;
}
timers.remove(timer); timers.remove(timer);
} }
} }
}
// These are methods to call within the core to call the quest events.
// these are methods to call from java
public boolean notifyAttack(NpcInstance npc, PlayerInstance attacker, int damage, boolean isPet) public boolean notifyAttack(NpcInstance npc, PlayerInstance attacker, int damage, boolean isPet)
{ {
String res = null; String res = null;
@ -1121,7 +1121,7 @@ public class Quest extends ManagedScript
} }
// events // events
for (String name : _allEventsS.keySet()) for (String name : _allEvents.keySet())
{ {
player.processQuestEvent(name, "enter"); player.processQuestEvent(name, "enter");
} }
@ -1797,16 +1797,17 @@ public class Quest extends ManagedScript
saveGlobalData(); saveGlobalData();
// Cancel all pending timers before reloading. // Cancel all pending timers before reloading.
// If timers ought to be restarted, the quest can take care of it with its code (example: save global data indicating what timer must be restarted). // If timers ought to be restarted, the quest can take care of it with its code (example: save global data indicating what timer must be restarted).
synchronized (_allEventTimers) synchronized (_questTimers)
{ {
for (ArrayList<QuestTimer> timers : _allEventTimers.values()) for (List<QuestTimer> timers : _questTimers.values())
{ {
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
timer.cancel(); timer.cancel();
} }
timers.clear();
} }
_allEventTimers.clear(); _questTimers.clear();
} }
return QuestManager.getInstance().removeQuest(this); return QuestManager.getInstance().removeQuest(this);

View File

@ -22,14 +22,12 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.logging.Level; import java.util.logging.Level;
@ -75,10 +73,7 @@ import org.l2jmobius.gameserver.util.Util;
public class Quest extends AbstractScript implements IIdentifiable public class Quest extends AbstractScript implements IIdentifiable
{ {
/** Map containing lists of timers from the name of the timer. */ /** Map containing lists of timers from the name of the timer. */
private Map<String, List<QuestTimer>> _questTimers = null; private final Map<String, List<QuestTimer>> _questTimers = new HashMap<>();
private final ReentrantReadWriteLock _rwLock = new ReentrantReadWriteLock();
private final WriteLock _writeLock = _rwLock.writeLock();
private final ReadLock _readLock = _rwLock.readLock();
/** Map containing all the start conditions. */ /** Map containing all the start conditions. */
private Map<Predicate<PlayerInstance>, String> _startCondition = null; private Map<Predicate<PlayerInstance>, String> _startCondition = null;
@ -228,7 +223,7 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean) * @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean)
*/ */
@ -243,16 +238,6 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public Map<String, List<QuestTimer>> getQuestTimers() public Map<String, List<QuestTimer>> getQuestTimers()
{ {
if (_questTimers == null)
{
synchronized (this)
{
if (_questTimers == null)
{
_questTimers = new ConcurrentHashMap<>(1);
}
}
}
return _questTimers; return _questTimers;
} }
@ -260,26 +245,21 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @param repeating indicates whether the timer is repeatable or one-time.<br> * @param repeating indicates whether the timer is repeatable or one-time.<br>
* If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped. * If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped.
*/ */
public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating) public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating)
{ {
final List<QuestTimer> timers = getQuestTimers().computeIfAbsent(name, k -> new ArrayList<>(1)); synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.computeIfAbsent(name, k -> new CopyOnWriteArrayList<>());
// If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards. // If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards.
if (getQuestTimer(name, npc, player) == null) if (getQuestTimer(name, npc, player) == null)
{
_writeLock.lock();
try
{ {
timers.add(new QuestTimer(this, name, time, npc, player, repeating)); timers.add(new QuestTimer(this, name, time, npc, player, repeating));
} }
finally
{
_writeLock.unlock();
}
} }
} }
@ -292,17 +272,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player) public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
if (_questTimers == null) if (name == null)
{ {
return null; return null;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_readLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return null;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if ((timer != null) && timer.equals(this, name, npc, player)) if ((timer != null) && timer.equals(this, name, npc, player))
@ -310,14 +292,9 @@ public class Quest extends AbstractScript implements IIdentifiable
return timer; return timer;
} }
} }
}
finally
{
_readLock.unlock();
}
}
return null; return null;
} }
}
/** /**
* Cancel all quest timers with the specified name. * Cancel all quest timers with the specified name.
@ -325,17 +302,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimers(String name) public void cancelQuestTimers(String name)
{ {
if (_questTimers == null) if (name == null)
{ {
return; return;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_writeLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if (timer != null) if (timer != null)
@ -345,11 +324,6 @@ public class Quest extends AbstractScript implements IIdentifiable
} }
timers.clear(); timers.clear();
} }
finally
{
_writeLock.unlock();
}
}
} }
/** /**
@ -360,10 +334,28 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimer(String name, Npc npc, PlayerInstance player) public void cancelQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
final QuestTimer timer = getQuestTimer(name, npc, player); if (name == null)
if (timer != null) {
return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(name);
if ((timers == null) || timers.isEmpty())
{
return;
}
for (QuestTimer timer : timers)
{
if ((timer != null) && timer.equals(this, name, npc, player))
{ {
timer.cancel(); timer.cancel();
timers.remove(timer);
return;
}
}
} }
} }
@ -374,21 +366,18 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void removeQuestTimer(QuestTimer timer) public void removeQuestTimer(QuestTimer timer)
{ {
if ((timer != null) && (_questTimers != null)) if (timer == null)
{ {
final List<QuestTimer> timers = getQuestTimers().get(timer.toString()); return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(timer.toString());
if (timers != null) if (timers != null)
{
_writeLock.lock();
try
{ {
timers.remove(timer); timers.remove(timer);
} }
finally
{
_writeLock.unlock();
}
}
} }
} }
@ -2523,25 +2512,17 @@ public class Quest extends AbstractScript implements IIdentifiable
// cancel all pending timers before reloading. // cancel all pending timers before reloading.
// if timers ought to be restarted, the quest can take care of it // if timers ought to be restarted, the quest can take care of it
// with its code (example: save global data indicating what timer must be restarted). // with its code (example: save global data indicating what timer must be restarted).
if (_questTimers != null) synchronized (_questTimers)
{ {
for (List<QuestTimer> timers : getQuestTimers().values()) for (List<QuestTimer> timers : _questTimers.values())
{
_readLock.lock();
try
{ {
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
timer.cancel(); timer.cancel();
} }
}
finally
{
_readLock.unlock();
}
timers.clear(); timers.clear();
} }
getQuestTimers().clear(); _questTimers.clear();
} }
if (removeFromList) if (removeFromList)

View File

@ -22,14 +22,12 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.logging.Level; import java.util.logging.Level;
@ -77,10 +75,7 @@ import org.l2jmobius.gameserver.util.Util;
public class Quest extends AbstractScript implements IIdentifiable public class Quest extends AbstractScript implements IIdentifiable
{ {
/** Map containing lists of timers from the name of the timer. */ /** Map containing lists of timers from the name of the timer. */
private Map<String, List<QuestTimer>> _questTimers = null; private final Map<String, List<QuestTimer>> _questTimers = new HashMap<>();
private final ReentrantReadWriteLock _rwLock = new ReentrantReadWriteLock();
private final WriteLock _writeLock = _rwLock.writeLock();
private final ReadLock _readLock = _rwLock.readLock();
/** Map containing all the start conditions. */ /** Map containing all the start conditions. */
private Map<Predicate<PlayerInstance>, String> _startCondition = null; private Map<Predicate<PlayerInstance>, String> _startCondition = null;
@ -230,7 +225,7 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean) * @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean)
*/ */
@ -245,16 +240,6 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public Map<String, List<QuestTimer>> getQuestTimers() public Map<String, List<QuestTimer>> getQuestTimers()
{ {
if (_questTimers == null)
{
synchronized (this)
{
if (_questTimers == null)
{
_questTimers = new ConcurrentHashMap<>(1);
}
}
}
return _questTimers; return _questTimers;
} }
@ -262,26 +247,21 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @param repeating indicates whether the timer is repeatable or one-time.<br> * @param repeating indicates whether the timer is repeatable or one-time.<br>
* If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped. * If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped.
*/ */
public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating) public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating)
{ {
final List<QuestTimer> timers = getQuestTimers().computeIfAbsent(name, k -> new ArrayList<>(1)); synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.computeIfAbsent(name, k -> new CopyOnWriteArrayList<>());
// If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards. // If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards.
if (getQuestTimer(name, npc, player) == null) if (getQuestTimer(name, npc, player) == null)
{
_writeLock.lock();
try
{ {
timers.add(new QuestTimer(this, name, time, npc, player, repeating)); timers.add(new QuestTimer(this, name, time, npc, player, repeating));
} }
finally
{
_writeLock.unlock();
}
} }
} }
@ -294,17 +274,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player) public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
if (_questTimers == null) if (name == null)
{ {
return null; return null;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_readLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return null;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if ((timer != null) && timer.equals(this, name, npc, player)) if ((timer != null) && timer.equals(this, name, npc, player))
@ -312,14 +294,9 @@ public class Quest extends AbstractScript implements IIdentifiable
return timer; return timer;
} }
} }
}
finally
{
_readLock.unlock();
}
}
return null; return null;
} }
}
/** /**
* Cancel all quest timers with the specified name. * Cancel all quest timers with the specified name.
@ -327,17 +304,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimers(String name) public void cancelQuestTimers(String name)
{ {
if (_questTimers == null) if (name == null)
{ {
return; return;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_writeLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if (timer != null) if (timer != null)
@ -347,11 +326,6 @@ public class Quest extends AbstractScript implements IIdentifiable
} }
timers.clear(); timers.clear();
} }
finally
{
_writeLock.unlock();
}
}
} }
/** /**
@ -362,10 +336,28 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimer(String name, Npc npc, PlayerInstance player) public void cancelQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
final QuestTimer timer = getQuestTimer(name, npc, player); if (name == null)
if (timer != null) {
return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(name);
if ((timers == null) || timers.isEmpty())
{
return;
}
for (QuestTimer timer : timers)
{
if ((timer != null) && timer.equals(this, name, npc, player))
{ {
timer.cancel(); timer.cancel();
timers.remove(timer);
return;
}
}
} }
} }
@ -376,21 +368,18 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void removeQuestTimer(QuestTimer timer) public void removeQuestTimer(QuestTimer timer)
{ {
if ((timer != null) && (_questTimers != null)) if (timer == null)
{ {
final List<QuestTimer> timers = getQuestTimers().get(timer.toString()); return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(timer.toString());
if (timers != null) if (timers != null)
{
_writeLock.lock();
try
{ {
timers.remove(timer); timers.remove(timer);
} }
finally
{
_writeLock.unlock();
}
}
} }
} }
@ -2566,25 +2555,17 @@ public class Quest extends AbstractScript implements IIdentifiable
// cancel all pending timers before reloading. // cancel all pending timers before reloading.
// if timers ought to be restarted, the quest can take care of it // if timers ought to be restarted, the quest can take care of it
// with its code (example: save global data indicating what timer must be restarted). // with its code (example: save global data indicating what timer must be restarted).
if (_questTimers != null) synchronized (_questTimers)
{ {
for (List<QuestTimer> timers : getQuestTimers().values()) for (List<QuestTimer> timers : _questTimers.values())
{
_readLock.lock();
try
{ {
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
timer.cancel(); timer.cancel();
} }
}
finally
{
_readLock.unlock();
}
timers.clear(); timers.clear();
} }
getQuestTimers().clear(); _questTimers.clear();
} }
if (removeFromList) if (removeFromList)

View File

@ -23,13 +23,12 @@ import java.sql.ResultSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -87,10 +86,7 @@ public class Quest extends AbstractScript implements IIdentifiable
public static final Logger LOGGER = Logger.getLogger(Quest.class.getName()); public static final Logger LOGGER = Logger.getLogger(Quest.class.getName());
/** Map containing lists of timers from the name of the timer. */ /** Map containing lists of timers from the name of the timer. */
private Map<String, List<QuestTimer>> _questTimers = null; private final Map<String, List<QuestTimer>> _questTimers = new HashMap<>();
private final ReentrantReadWriteLock _rwLock = new ReentrantReadWriteLock();
private final WriteLock _writeLock = _rwLock.writeLock();
private final ReadLock _readLock = _rwLock.readLock();
/** Map containing all the start conditions. */ /** Map containing all the start conditions. */
private Set<QuestCondition> _startCondition = null; private Set<QuestCondition> _startCondition = null;
@ -253,7 +249,7 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean) * @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean)
*/ */
@ -268,16 +264,6 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public Map<String, List<QuestTimer>> getQuestTimers() public Map<String, List<QuestTimer>> getQuestTimers()
{ {
if (_questTimers == null)
{
synchronized (this)
{
if (_questTimers == null)
{
_questTimers = new ConcurrentHashMap<>(1);
}
}
}
return _questTimers; return _questTimers;
} }
@ -285,26 +271,21 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @param repeating indicates whether the timer is repeatable or one-time.<br> * @param repeating indicates whether the timer is repeatable or one-time.<br>
* If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped. * If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped.
*/ */
public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating) public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating)
{ {
final List<QuestTimer> timers = getQuestTimers().computeIfAbsent(name, k -> new ArrayList<>(1)); synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.computeIfAbsent(name, k -> new CopyOnWriteArrayList<>());
// If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards. // If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards.
if (getQuestTimer(name, npc, player) == null) if (getQuestTimer(name, npc, player) == null)
{
_writeLock.lock();
try
{ {
timers.add(new QuestTimer(this, name, time, npc, player, repeating)); timers.add(new QuestTimer(this, name, time, npc, player, repeating));
} }
finally
{
_writeLock.unlock();
}
} }
} }
@ -317,17 +298,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player) public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
if (_questTimers == null) if (name == null)
{ {
return null; return null;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_readLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return null;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if ((timer != null) && timer.equals(this, name, npc, player)) if ((timer != null) && timer.equals(this, name, npc, player))
@ -335,14 +318,9 @@ public class Quest extends AbstractScript implements IIdentifiable
return timer; return timer;
} }
} }
}
finally
{
_readLock.unlock();
}
}
return null; return null;
} }
}
/** /**
* Cancel all quest timers with the specified name. * Cancel all quest timers with the specified name.
@ -350,17 +328,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimers(String name) public void cancelQuestTimers(String name)
{ {
if (_questTimers == null) if (name == null)
{ {
return; return;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_writeLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if (timer != null) if (timer != null)
@ -370,11 +350,6 @@ public class Quest extends AbstractScript implements IIdentifiable
} }
timers.clear(); timers.clear();
} }
finally
{
_writeLock.unlock();
}
}
} }
/** /**
@ -385,10 +360,28 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimer(String name, Npc npc, PlayerInstance player) public void cancelQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
final QuestTimer timer = getQuestTimer(name, npc, player); if (name == null)
if (timer != null) {
return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(name);
if ((timers == null) || timers.isEmpty())
{
return;
}
for (QuestTimer timer : timers)
{
if ((timer != null) && timer.equals(this, name, npc, player))
{ {
timer.cancel(); timer.cancel();
timers.remove(timer);
return;
}
}
} }
} }
@ -399,21 +392,18 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void removeQuestTimer(QuestTimer timer) public void removeQuestTimer(QuestTimer timer)
{ {
if ((timer != null) && (_questTimers != null)) if (timer == null)
{ {
final List<QuestTimer> timers = getQuestTimers().get(timer.toString()); return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(timer.toString());
if (timers != null) if (timers != null)
{
_writeLock.lock();
try
{ {
timers.remove(timer); timers.remove(timer);
} }
finally
{
_writeLock.unlock();
}
}
} }
} }
@ -2790,25 +2780,17 @@ public class Quest extends AbstractScript implements IIdentifiable
// cancel all pending timers before reloading. // cancel all pending timers before reloading.
// if timers ought to be restarted, the quest can take care of it // if timers ought to be restarted, the quest can take care of it
// with its code (example: save global data indicating what timer must be restarted). // with its code (example: save global data indicating what timer must be restarted).
if (_questTimers != null) synchronized (_questTimers)
{ {
for (List<QuestTimer> timers : getQuestTimers().values()) for (List<QuestTimer> timers : _questTimers.values())
{
_readLock.lock();
try
{ {
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
timer.cancel(); timer.cancel();
} }
}
finally
{
_readLock.unlock();
}
timers.clear(); timers.clear();
} }
getQuestTimers().clear(); _questTimers.clear();
} }
if (removeFromList) if (removeFromList)

View File

@ -23,13 +23,12 @@ import java.sql.ResultSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -87,10 +86,7 @@ public class Quest extends AbstractScript implements IIdentifiable
public static final Logger LOGGER = Logger.getLogger(Quest.class.getName()); public static final Logger LOGGER = Logger.getLogger(Quest.class.getName());
/** Map containing lists of timers from the name of the timer. */ /** Map containing lists of timers from the name of the timer. */
private Map<String, List<QuestTimer>> _questTimers = null; private final Map<String, List<QuestTimer>> _questTimers = new HashMap<>();
private final ReentrantReadWriteLock _rwLock = new ReentrantReadWriteLock();
private final WriteLock _writeLock = _rwLock.writeLock();
private final ReadLock _readLock = _rwLock.readLock();
/** Map containing all the start conditions. */ /** Map containing all the start conditions. */
private Set<QuestCondition> _startCondition = null; private Set<QuestCondition> _startCondition = null;
@ -253,7 +249,7 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean) * @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean)
*/ */
@ -268,16 +264,6 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public Map<String, List<QuestTimer>> getQuestTimers() public Map<String, List<QuestTimer>> getQuestTimers()
{ {
if (_questTimers == null)
{
synchronized (this)
{
if (_questTimers == null)
{
_questTimers = new ConcurrentHashMap<>(1);
}
}
}
return _questTimers; return _questTimers;
} }
@ -285,26 +271,21 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @param repeating indicates whether the timer is repeatable or one-time.<br> * @param repeating indicates whether the timer is repeatable or one-time.<br>
* If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped. * If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped.
*/ */
public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating) public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating)
{ {
final List<QuestTimer> timers = getQuestTimers().computeIfAbsent(name, k -> new ArrayList<>(1)); synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.computeIfAbsent(name, k -> new CopyOnWriteArrayList<>());
// If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards. // If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards.
if (getQuestTimer(name, npc, player) == null) if (getQuestTimer(name, npc, player) == null)
{
_writeLock.lock();
try
{ {
timers.add(new QuestTimer(this, name, time, npc, player, repeating)); timers.add(new QuestTimer(this, name, time, npc, player, repeating));
} }
finally
{
_writeLock.unlock();
}
} }
} }
@ -317,17 +298,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player) public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
if (_questTimers == null) if (name == null)
{ {
return null; return null;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_readLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return null;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if ((timer != null) && timer.equals(this, name, npc, player)) if ((timer != null) && timer.equals(this, name, npc, player))
@ -335,14 +318,9 @@ public class Quest extends AbstractScript implements IIdentifiable
return timer; return timer;
} }
} }
}
finally
{
_readLock.unlock();
}
}
return null; return null;
} }
}
/** /**
* Cancel all quest timers with the specified name. * Cancel all quest timers with the specified name.
@ -350,17 +328,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimers(String name) public void cancelQuestTimers(String name)
{ {
if (_questTimers == null) if (name == null)
{ {
return; return;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_writeLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if (timer != null) if (timer != null)
@ -370,11 +350,6 @@ public class Quest extends AbstractScript implements IIdentifiable
} }
timers.clear(); timers.clear();
} }
finally
{
_writeLock.unlock();
}
}
} }
/** /**
@ -385,10 +360,28 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimer(String name, Npc npc, PlayerInstance player) public void cancelQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
final QuestTimer timer = getQuestTimer(name, npc, player); if (name == null)
if (timer != null) {
return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(name);
if ((timers == null) || timers.isEmpty())
{
return;
}
for (QuestTimer timer : timers)
{
if ((timer != null) && timer.equals(this, name, npc, player))
{ {
timer.cancel(); timer.cancel();
timers.remove(timer);
return;
}
}
} }
} }
@ -399,21 +392,18 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void removeQuestTimer(QuestTimer timer) public void removeQuestTimer(QuestTimer timer)
{ {
if ((timer != null) && (_questTimers != null)) if (timer == null)
{ {
final List<QuestTimer> timers = getQuestTimers().get(timer.toString()); return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(timer.toString());
if (timers != null) if (timers != null)
{
_writeLock.lock();
try
{ {
timers.remove(timer); timers.remove(timer);
} }
finally
{
_writeLock.unlock();
}
}
} }
} }
@ -2790,25 +2780,17 @@ public class Quest extends AbstractScript implements IIdentifiable
// cancel all pending timers before reloading. // cancel all pending timers before reloading.
// if timers ought to be restarted, the quest can take care of it // if timers ought to be restarted, the quest can take care of it
// with its code (example: save global data indicating what timer must be restarted). // with its code (example: save global data indicating what timer must be restarted).
if (_questTimers != null) synchronized (_questTimers)
{ {
for (List<QuestTimer> timers : getQuestTimers().values()) for (List<QuestTimer> timers : _questTimers.values())
{
_readLock.lock();
try
{ {
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
timer.cancel(); timer.cancel();
} }
}
finally
{
_readLock.unlock();
}
timers.clear(); timers.clear();
} }
getQuestTimers().clear(); _questTimers.clear();
} }
if (removeFromList) if (removeFromList)

View File

@ -23,13 +23,12 @@ import java.sql.ResultSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -87,10 +86,7 @@ public class Quest extends AbstractScript implements IIdentifiable
public static final Logger LOGGER = Logger.getLogger(Quest.class.getName()); public static final Logger LOGGER = Logger.getLogger(Quest.class.getName());
/** Map containing lists of timers from the name of the timer. */ /** Map containing lists of timers from the name of the timer. */
private Map<String, List<QuestTimer>> _questTimers = null; private final Map<String, List<QuestTimer>> _questTimers = new HashMap<>();
private final ReentrantReadWriteLock _rwLock = new ReentrantReadWriteLock();
private final WriteLock _writeLock = _rwLock.writeLock();
private final ReadLock _readLock = _rwLock.readLock();
/** Map containing all the start conditions. */ /** Map containing all the start conditions. */
private Set<QuestCondition> _startCondition = null; private Set<QuestCondition> _startCondition = null;
@ -253,7 +249,7 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean) * @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean)
*/ */
@ -268,16 +264,6 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public Map<String, List<QuestTimer>> getQuestTimers() public Map<String, List<QuestTimer>> getQuestTimers()
{ {
if (_questTimers == null)
{
synchronized (this)
{
if (_questTimers == null)
{
_questTimers = new ConcurrentHashMap<>(1);
}
}
}
return _questTimers; return _questTimers;
} }
@ -285,26 +271,21 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @param repeating indicates whether the timer is repeatable or one-time.<br> * @param repeating indicates whether the timer is repeatable or one-time.<br>
* If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped. * If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped.
*/ */
public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating) public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating)
{ {
final List<QuestTimer> timers = getQuestTimers().computeIfAbsent(name, k -> new ArrayList<>(1)); synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.computeIfAbsent(name, k -> new CopyOnWriteArrayList<>());
// If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards. // If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards.
if (getQuestTimer(name, npc, player) == null) if (getQuestTimer(name, npc, player) == null)
{
_writeLock.lock();
try
{ {
timers.add(new QuestTimer(this, name, time, npc, player, repeating)); timers.add(new QuestTimer(this, name, time, npc, player, repeating));
} }
finally
{
_writeLock.unlock();
}
} }
} }
@ -317,17 +298,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player) public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
if (_questTimers == null) if (name == null)
{ {
return null; return null;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_readLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return null;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if ((timer != null) && timer.equals(this, name, npc, player)) if ((timer != null) && timer.equals(this, name, npc, player))
@ -335,14 +318,9 @@ public class Quest extends AbstractScript implements IIdentifiable
return timer; return timer;
} }
} }
}
finally
{
_readLock.unlock();
}
}
return null; return null;
} }
}
/** /**
* Cancel all quest timers with the specified name. * Cancel all quest timers with the specified name.
@ -350,17 +328,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimers(String name) public void cancelQuestTimers(String name)
{ {
if (_questTimers == null) if (name == null)
{ {
return; return;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_writeLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if (timer != null) if (timer != null)
@ -370,11 +350,6 @@ public class Quest extends AbstractScript implements IIdentifiable
} }
timers.clear(); timers.clear();
} }
finally
{
_writeLock.unlock();
}
}
} }
/** /**
@ -385,10 +360,28 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimer(String name, Npc npc, PlayerInstance player) public void cancelQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
final QuestTimer timer = getQuestTimer(name, npc, player); if (name == null)
if (timer != null) {
return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(name);
if ((timers == null) || timers.isEmpty())
{
return;
}
for (QuestTimer timer : timers)
{
if ((timer != null) && timer.equals(this, name, npc, player))
{ {
timer.cancel(); timer.cancel();
timers.remove(timer);
return;
}
}
} }
} }
@ -399,21 +392,18 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void removeQuestTimer(QuestTimer timer) public void removeQuestTimer(QuestTimer timer)
{ {
if ((timer != null) && (_questTimers != null)) if (timer == null)
{ {
final List<QuestTimer> timers = getQuestTimers().get(timer.toString()); return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(timer.toString());
if (timers != null) if (timers != null)
{
_writeLock.lock();
try
{ {
timers.remove(timer); timers.remove(timer);
} }
finally
{
_writeLock.unlock();
}
}
} }
} }
@ -2790,25 +2780,17 @@ public class Quest extends AbstractScript implements IIdentifiable
// cancel all pending timers before reloading. // cancel all pending timers before reloading.
// if timers ought to be restarted, the quest can take care of it // if timers ought to be restarted, the quest can take care of it
// with its code (example: save global data indicating what timer must be restarted). // with its code (example: save global data indicating what timer must be restarted).
if (_questTimers != null) synchronized (_questTimers)
{ {
for (List<QuestTimer> timers : getQuestTimers().values()) for (List<QuestTimer> timers : _questTimers.values())
{
_readLock.lock();
try
{ {
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
timer.cancel(); timer.cancel();
} }
}
finally
{
_readLock.unlock();
}
timers.clear(); timers.clear();
} }
getQuestTimers().clear(); _questTimers.clear();
} }
if (removeFromList) if (removeFromList)

View File

@ -23,13 +23,12 @@ import java.sql.ResultSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -87,10 +86,7 @@ public class Quest extends AbstractScript implements IIdentifiable
public static final Logger LOGGER = Logger.getLogger(Quest.class.getName()); public static final Logger LOGGER = Logger.getLogger(Quest.class.getName());
/** Map containing lists of timers from the name of the timer. */ /** Map containing lists of timers from the name of the timer. */
private Map<String, List<QuestTimer>> _questTimers = null; private final Map<String, List<QuestTimer>> _questTimers = new HashMap<>();
private final ReentrantReadWriteLock _rwLock = new ReentrantReadWriteLock();
private final WriteLock _writeLock = _rwLock.writeLock();
private final ReadLock _readLock = _rwLock.readLock();
/** Map containing all the start conditions. */ /** Map containing all the start conditions. */
private Set<QuestCondition> _startCondition = null; private Set<QuestCondition> _startCondition = null;
@ -253,7 +249,7 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean) * @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean)
*/ */
@ -268,16 +264,6 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public Map<String, List<QuestTimer>> getQuestTimers() public Map<String, List<QuestTimer>> getQuestTimers()
{ {
if (_questTimers == null)
{
synchronized (this)
{
if (_questTimers == null)
{
_questTimers = new ConcurrentHashMap<>(1);
}
}
}
return _questTimers; return _questTimers;
} }
@ -285,26 +271,21 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @param repeating indicates whether the timer is repeatable or one-time.<br> * @param repeating indicates whether the timer is repeatable or one-time.<br>
* If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped. * If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped.
*/ */
public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating) public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating)
{ {
final List<QuestTimer> timers = getQuestTimers().computeIfAbsent(name, k -> new ArrayList<>(1)); synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.computeIfAbsent(name, k -> new CopyOnWriteArrayList<>());
// If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards. // If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards.
if (getQuestTimer(name, npc, player) == null) if (getQuestTimer(name, npc, player) == null)
{
_writeLock.lock();
try
{ {
timers.add(new QuestTimer(this, name, time, npc, player, repeating)); timers.add(new QuestTimer(this, name, time, npc, player, repeating));
} }
finally
{
_writeLock.unlock();
}
} }
} }
@ -317,17 +298,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player) public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
if (_questTimers == null) if (name == null)
{ {
return null; return null;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_readLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return null;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if ((timer != null) && timer.equals(this, name, npc, player)) if ((timer != null) && timer.equals(this, name, npc, player))
@ -335,14 +318,9 @@ public class Quest extends AbstractScript implements IIdentifiable
return timer; return timer;
} }
} }
}
finally
{
_readLock.unlock();
}
}
return null; return null;
} }
}
/** /**
* Cancel all quest timers with the specified name. * Cancel all quest timers with the specified name.
@ -350,17 +328,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimers(String name) public void cancelQuestTimers(String name)
{ {
if (_questTimers == null) if (name == null)
{ {
return; return;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_writeLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if (timer != null) if (timer != null)
@ -370,11 +350,6 @@ public class Quest extends AbstractScript implements IIdentifiable
} }
timers.clear(); timers.clear();
} }
finally
{
_writeLock.unlock();
}
}
} }
/** /**
@ -385,10 +360,28 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimer(String name, Npc npc, PlayerInstance player) public void cancelQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
final QuestTimer timer = getQuestTimer(name, npc, player); if (name == null)
if (timer != null) {
return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(name);
if ((timers == null) || timers.isEmpty())
{
return;
}
for (QuestTimer timer : timers)
{
if ((timer != null) && timer.equals(this, name, npc, player))
{ {
timer.cancel(); timer.cancel();
timers.remove(timer);
return;
}
}
} }
} }
@ -399,21 +392,18 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void removeQuestTimer(QuestTimer timer) public void removeQuestTimer(QuestTimer timer)
{ {
if ((timer != null) && (_questTimers != null)) if (timer == null)
{ {
final List<QuestTimer> timers = getQuestTimers().get(timer.toString()); return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(timer.toString());
if (timers != null) if (timers != null)
{
_writeLock.lock();
try
{ {
timers.remove(timer); timers.remove(timer);
} }
finally
{
_writeLock.unlock();
}
}
} }
} }
@ -2790,25 +2780,17 @@ public class Quest extends AbstractScript implements IIdentifiable
// cancel all pending timers before reloading. // cancel all pending timers before reloading.
// if timers ought to be restarted, the quest can take care of it // if timers ought to be restarted, the quest can take care of it
// with its code (example: save global data indicating what timer must be restarted). // with its code (example: save global data indicating what timer must be restarted).
if (_questTimers != null) synchronized (_questTimers)
{ {
for (List<QuestTimer> timers : getQuestTimers().values()) for (List<QuestTimer> timers : _questTimers.values())
{
_readLock.lock();
try
{ {
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
timer.cancel(); timer.cancel();
} }
}
finally
{
_readLock.unlock();
}
timers.clear(); timers.clear();
} }
getQuestTimers().clear(); _questTimers.clear();
} }
if (removeFromList) if (removeFromList)

View File

@ -23,13 +23,12 @@ import java.sql.ResultSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -87,10 +86,7 @@ public class Quest extends AbstractScript implements IIdentifiable
public static final Logger LOGGER = Logger.getLogger(Quest.class.getName()); public static final Logger LOGGER = Logger.getLogger(Quest.class.getName());
/** Map containing lists of timers from the name of the timer. */ /** Map containing lists of timers from the name of the timer. */
private Map<String, List<QuestTimer>> _questTimers = null; private final Map<String, List<QuestTimer>> _questTimers = new HashMap<>();
private final ReentrantReadWriteLock _rwLock = new ReentrantReadWriteLock();
private final WriteLock _writeLock = _rwLock.writeLock();
private final ReadLock _readLock = _rwLock.readLock();
/** Map containing all the start conditions. */ /** Map containing all the start conditions. */
private Set<QuestCondition> _startCondition = null; private Set<QuestCondition> _startCondition = null;
@ -253,7 +249,7 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean) * @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean)
*/ */
@ -268,16 +264,6 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public Map<String, List<QuestTimer>> getQuestTimers() public Map<String, List<QuestTimer>> getQuestTimers()
{ {
if (_questTimers == null)
{
synchronized (this)
{
if (_questTimers == null)
{
_questTimers = new ConcurrentHashMap<>(1);
}
}
}
return _questTimers; return _questTimers;
} }
@ -285,26 +271,21 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @param repeating indicates whether the timer is repeatable or one-time.<br> * @param repeating indicates whether the timer is repeatable or one-time.<br>
* If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped. * If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped.
*/ */
public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating) public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating)
{ {
final List<QuestTimer> timers = getQuestTimers().computeIfAbsent(name, k -> new ArrayList<>(1)); synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.computeIfAbsent(name, k -> new CopyOnWriteArrayList<>());
// If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards. // If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards.
if (getQuestTimer(name, npc, player) == null) if (getQuestTimer(name, npc, player) == null)
{
_writeLock.lock();
try
{ {
timers.add(new QuestTimer(this, name, time, npc, player, repeating)); timers.add(new QuestTimer(this, name, time, npc, player, repeating));
} }
finally
{
_writeLock.unlock();
}
} }
} }
@ -317,17 +298,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player) public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
if (_questTimers == null) if (name == null)
{ {
return null; return null;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_readLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return null;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if ((timer != null) && timer.equals(this, name, npc, player)) if ((timer != null) && timer.equals(this, name, npc, player))
@ -335,14 +318,9 @@ public class Quest extends AbstractScript implements IIdentifiable
return timer; return timer;
} }
} }
}
finally
{
_readLock.unlock();
}
}
return null; return null;
} }
}
/** /**
* Cancel all quest timers with the specified name. * Cancel all quest timers with the specified name.
@ -350,17 +328,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimers(String name) public void cancelQuestTimers(String name)
{ {
if (_questTimers == null) if (name == null)
{ {
return; return;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_writeLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if (timer != null) if (timer != null)
@ -370,11 +350,6 @@ public class Quest extends AbstractScript implements IIdentifiable
} }
timers.clear(); timers.clear();
} }
finally
{
_writeLock.unlock();
}
}
} }
/** /**
@ -385,10 +360,28 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimer(String name, Npc npc, PlayerInstance player) public void cancelQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
final QuestTimer timer = getQuestTimer(name, npc, player); if (name == null)
if (timer != null) {
return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(name);
if ((timers == null) || timers.isEmpty())
{
return;
}
for (QuestTimer timer : timers)
{
if ((timer != null) && timer.equals(this, name, npc, player))
{ {
timer.cancel(); timer.cancel();
timers.remove(timer);
return;
}
}
} }
} }
@ -399,21 +392,18 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void removeQuestTimer(QuestTimer timer) public void removeQuestTimer(QuestTimer timer)
{ {
if ((timer != null) && (_questTimers != null)) if (timer == null)
{ {
final List<QuestTimer> timers = getQuestTimers().get(timer.toString()); return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(timer.toString());
if (timers != null) if (timers != null)
{
_writeLock.lock();
try
{ {
timers.remove(timer); timers.remove(timer);
} }
finally
{
_writeLock.unlock();
}
}
} }
} }
@ -2790,25 +2780,17 @@ public class Quest extends AbstractScript implements IIdentifiable
// cancel all pending timers before reloading. // cancel all pending timers before reloading.
// if timers ought to be restarted, the quest can take care of it // if timers ought to be restarted, the quest can take care of it
// with its code (example: save global data indicating what timer must be restarted). // with its code (example: save global data indicating what timer must be restarted).
if (_questTimers != null) synchronized (_questTimers)
{ {
for (List<QuestTimer> timers : getQuestTimers().values()) for (List<QuestTimer> timers : _questTimers.values())
{
_readLock.lock();
try
{ {
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
timer.cancel(); timer.cancel();
} }
}
finally
{
_readLock.unlock();
}
timers.clear(); timers.clear();
} }
getQuestTimers().clear(); _questTimers.clear();
} }
if (removeFromList) if (removeFromList)

View File

@ -23,13 +23,12 @@ import java.sql.ResultSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -87,10 +86,7 @@ public class Quest extends AbstractScript implements IIdentifiable
public static final Logger LOGGER = Logger.getLogger(Quest.class.getName()); public static final Logger LOGGER = Logger.getLogger(Quest.class.getName());
/** Map containing lists of timers from the name of the timer. */ /** Map containing lists of timers from the name of the timer. */
private Map<String, List<QuestTimer>> _questTimers = null; private final Map<String, List<QuestTimer>> _questTimers = new HashMap<>();
private final ReentrantReadWriteLock _rwLock = new ReentrantReadWriteLock();
private final WriteLock _writeLock = _rwLock.writeLock();
private final ReadLock _readLock = _rwLock.readLock();
/** Map containing all the start conditions. */ /** Map containing all the start conditions. */
private Set<QuestCondition> _startCondition = null; private Set<QuestCondition> _startCondition = null;
@ -253,7 +249,7 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean) * @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean)
*/ */
@ -268,16 +264,6 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public Map<String, List<QuestTimer>> getQuestTimers() public Map<String, List<QuestTimer>> getQuestTimers()
{ {
if (_questTimers == null)
{
synchronized (this)
{
if (_questTimers == null)
{
_questTimers = new ConcurrentHashMap<>(1);
}
}
}
return _questTimers; return _questTimers;
} }
@ -285,26 +271,21 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @param repeating indicates whether the timer is repeatable or one-time.<br> * @param repeating indicates whether the timer is repeatable or one-time.<br>
* If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped. * If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped.
*/ */
public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating) public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating)
{ {
final List<QuestTimer> timers = getQuestTimers().computeIfAbsent(name, k -> new ArrayList<>(1)); synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.computeIfAbsent(name, k -> new CopyOnWriteArrayList<>());
// If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards. // If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards.
if (getQuestTimer(name, npc, player) == null) if (getQuestTimer(name, npc, player) == null)
{
_writeLock.lock();
try
{ {
timers.add(new QuestTimer(this, name, time, npc, player, repeating)); timers.add(new QuestTimer(this, name, time, npc, player, repeating));
} }
finally
{
_writeLock.unlock();
}
} }
} }
@ -317,17 +298,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player) public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
if (_questTimers == null) if (name == null)
{ {
return null; return null;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_readLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return null;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if ((timer != null) && timer.equals(this, name, npc, player)) if ((timer != null) && timer.equals(this, name, npc, player))
@ -335,14 +318,9 @@ public class Quest extends AbstractScript implements IIdentifiable
return timer; return timer;
} }
} }
}
finally
{
_readLock.unlock();
}
}
return null; return null;
} }
}
/** /**
* Cancel all quest timers with the specified name. * Cancel all quest timers with the specified name.
@ -350,17 +328,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimers(String name) public void cancelQuestTimers(String name)
{ {
if (_questTimers == null) if (name == null)
{ {
return; return;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_writeLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if (timer != null) if (timer != null)
@ -370,11 +350,6 @@ public class Quest extends AbstractScript implements IIdentifiable
} }
timers.clear(); timers.clear();
} }
finally
{
_writeLock.unlock();
}
}
} }
/** /**
@ -385,10 +360,28 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimer(String name, Npc npc, PlayerInstance player) public void cancelQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
final QuestTimer timer = getQuestTimer(name, npc, player); if (name == null)
if (timer != null) {
return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(name);
if ((timers == null) || timers.isEmpty())
{
return;
}
for (QuestTimer timer : timers)
{
if ((timer != null) && timer.equals(this, name, npc, player))
{ {
timer.cancel(); timer.cancel();
timers.remove(timer);
return;
}
}
} }
} }
@ -399,21 +392,18 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void removeQuestTimer(QuestTimer timer) public void removeQuestTimer(QuestTimer timer)
{ {
if ((timer != null) && (_questTimers != null)) if (timer == null)
{ {
final List<QuestTimer> timers = getQuestTimers().get(timer.toString()); return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(timer.toString());
if (timers != null) if (timers != null)
{
_writeLock.lock();
try
{ {
timers.remove(timer); timers.remove(timer);
} }
finally
{
_writeLock.unlock();
}
}
} }
} }
@ -2790,25 +2780,17 @@ public class Quest extends AbstractScript implements IIdentifiable
// cancel all pending timers before reloading. // cancel all pending timers before reloading.
// if timers ought to be restarted, the quest can take care of it // if timers ought to be restarted, the quest can take care of it
// with its code (example: save global data indicating what timer must be restarted). // with its code (example: save global data indicating what timer must be restarted).
if (_questTimers != null) synchronized (_questTimers)
{ {
for (List<QuestTimer> timers : getQuestTimers().values()) for (List<QuestTimer> timers : _questTimers.values())
{
_readLock.lock();
try
{ {
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
timer.cancel(); timer.cancel();
} }
}
finally
{
_readLock.unlock();
}
timers.clear(); timers.clear();
} }
getQuestTimers().clear(); _questTimers.clear();
} }
if (removeFromList) if (removeFromList)

View File

@ -23,13 +23,12 @@ import java.sql.ResultSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -87,10 +86,7 @@ public class Quest extends AbstractScript implements IIdentifiable
public static final Logger LOGGER = Logger.getLogger(Quest.class.getName()); public static final Logger LOGGER = Logger.getLogger(Quest.class.getName());
/** Map containing lists of timers from the name of the timer. */ /** Map containing lists of timers from the name of the timer. */
private Map<String, List<QuestTimer>> _questTimers = null; private final Map<String, List<QuestTimer>> _questTimers = new HashMap<>();
private final ReentrantReadWriteLock _rwLock = new ReentrantReadWriteLock();
private final WriteLock _writeLock = _rwLock.writeLock();
private final ReadLock _readLock = _rwLock.readLock();
/** Map containing all the start conditions. */ /** Map containing all the start conditions. */
private Set<QuestCondition> _startCondition = null; private Set<QuestCondition> _startCondition = null;
@ -253,7 +249,7 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean) * @see #startQuestTimer(String, long, Npc, PlayerInstance, boolean)
*/ */
@ -268,16 +264,6 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public Map<String, List<QuestTimer>> getQuestTimers() public Map<String, List<QuestTimer>> getQuestTimers()
{ {
if (_questTimers == null)
{
synchronized (this)
{
if (_questTimers == null)
{
_questTimers = new ConcurrentHashMap<>(1);
}
}
}
return _questTimers; return _questTimers;
} }
@ -285,26 +271,21 @@ public class Quest extends AbstractScript implements IIdentifiable
* Add a timer to the quest (if it doesn't exist already) and start it. * Add a timer to the quest (if it doesn't exist already) and start it.
* @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)}) * @param name the name of the timer (also passed back as "event" in {@link #onAdvEvent(String, Npc, PlayerInstance)})
* @param time time in ms for when to fire the timer * @param time time in ms for when to fire the timer
* @param npc the npc associated with this timer (can be null) * @param npc the NPC associated with this timer (can be null)
* @param player the player associated with this timer (can be null) * @param player the player associated with this timer (can be null)
* @param repeating indicates whether the timer is repeatable or one-time.<br> * @param repeating indicates whether the timer is repeatable or one-time.<br>
* If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped. * If {@code true}, the task is repeated every {@code time} milliseconds until explicitly stopped.
*/ */
public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating) public void startQuestTimer(String name, long time, Npc npc, PlayerInstance player, boolean repeating)
{ {
final List<QuestTimer> timers = getQuestTimers().computeIfAbsent(name, k -> new ArrayList<>(1)); synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.computeIfAbsent(name, k -> new CopyOnWriteArrayList<>());
// If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards. // If there exists a timer with this name, allow the timer only if the [npc, player] set is unique nulls act as wildcards.
if (getQuestTimer(name, npc, player) == null) if (getQuestTimer(name, npc, player) == null)
{
_writeLock.lock();
try
{ {
timers.add(new QuestTimer(this, name, time, npc, player, repeating)); timers.add(new QuestTimer(this, name, time, npc, player, repeating));
} }
finally
{
_writeLock.unlock();
}
} }
} }
@ -317,17 +298,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player) public QuestTimer getQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
if (_questTimers == null) if (name == null)
{ {
return null; return null;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_readLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return null;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if ((timer != null) && timer.equals(this, name, npc, player)) if ((timer != null) && timer.equals(this, name, npc, player))
@ -335,14 +318,9 @@ public class Quest extends AbstractScript implements IIdentifiable
return timer; return timer;
} }
} }
}
finally
{
_readLock.unlock();
}
}
return null; return null;
} }
}
/** /**
* Cancel all quest timers with the specified name. * Cancel all quest timers with the specified name.
@ -350,17 +328,19 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimers(String name) public void cancelQuestTimers(String name)
{ {
if (_questTimers == null) if (name == null)
{ {
return; return;
} }
final List<QuestTimer> timers = getQuestTimers().get(name); synchronized (_questTimers)
if (timers != null)
{ {
_writeLock.lock(); final List<QuestTimer> timers = _questTimers.get(name);
try if ((timers == null) || timers.isEmpty())
{ {
return;
}
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
if (timer != null) if (timer != null)
@ -370,11 +350,6 @@ public class Quest extends AbstractScript implements IIdentifiable
} }
timers.clear(); timers.clear();
} }
finally
{
_writeLock.unlock();
}
}
} }
/** /**
@ -385,10 +360,28 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void cancelQuestTimer(String name, Npc npc, PlayerInstance player) public void cancelQuestTimer(String name, Npc npc, PlayerInstance player)
{ {
final QuestTimer timer = getQuestTimer(name, npc, player); if (name == null)
if (timer != null) {
return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(name);
if ((timers == null) || timers.isEmpty())
{
return;
}
for (QuestTimer timer : timers)
{
if ((timer != null) && timer.equals(this, name, npc, player))
{ {
timer.cancel(); timer.cancel();
timers.remove(timer);
return;
}
}
} }
} }
@ -399,21 +392,18 @@ public class Quest extends AbstractScript implements IIdentifiable
*/ */
public void removeQuestTimer(QuestTimer timer) public void removeQuestTimer(QuestTimer timer)
{ {
if ((timer != null) && (_questTimers != null)) if (timer == null)
{ {
final List<QuestTimer> timers = getQuestTimers().get(timer.toString()); return;
}
synchronized (_questTimers)
{
final List<QuestTimer> timers = _questTimers.get(timer.toString());
if (timers != null) if (timers != null)
{
_writeLock.lock();
try
{ {
timers.remove(timer); timers.remove(timer);
} }
finally
{
_writeLock.unlock();
}
}
} }
} }
@ -2790,25 +2780,17 @@ public class Quest extends AbstractScript implements IIdentifiable
// cancel all pending timers before reloading. // cancel all pending timers before reloading.
// if timers ought to be restarted, the quest can take care of it // if timers ought to be restarted, the quest can take care of it
// with its code (example: save global data indicating what timer must be restarted). // with its code (example: save global data indicating what timer must be restarted).
if (_questTimers != null) synchronized (_questTimers)
{ {
for (List<QuestTimer> timers : getQuestTimers().values()) for (List<QuestTimer> timers : _questTimers.values())
{
_readLock.lock();
try
{ {
for (QuestTimer timer : timers) for (QuestTimer timer : timers)
{ {
timer.cancel(); timer.cancel();
} }
}
finally
{
_readLock.unlock();
}
timers.clear(); timers.clear();
} }
getQuestTimers().clear(); _questTimers.clear();
} }
if (removeFromList) if (removeFromList)