mirror of
https://github.com/NoCheatPlus/NoCheatPlus.git
synced 2024-09-27 14:13:11 +02:00
Add DualCollection, for future use.
This commit is contained in:
parent
f4a401dbe1
commit
73a62a1e13
@ -0,0 +1,311 @@
|
||||
package fr.neatmonster.nocheatplus.utilities.ds.collection;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Keep two collections, one for access by the primary thread, one for
|
||||
* asynchronous access, for iteration within the primary thread. This is
|
||||
* intended to provide a way to keep locking away from the primary thread as
|
||||
* much as possible, for cases where it's certain that both the primary thread
|
||||
* as well as other threads will add elements, at least on occasion. Internal
|
||||
* collections are kept null, with a little hysteresis built in, to avoid
|
||||
* constant repetition of object creation. Multiple collections and differing
|
||||
* collection types can be processed under the same lock, to reduce the
|
||||
* probability of the primary thread running into contention.
|
||||
* <hr>
|
||||
* The methods don't check if you're within the correct thread, consequently
|
||||
* they are named according to how they're supposed to be used. Thus use of this
|
||||
* class is not confined to the Bukkit primary thread, despite having been
|
||||
* created with that context in mind.<br>
|
||||
* The internal collections stay null, until elements are added. Setting the
|
||||
* fields to null, will happen lazily within mergePrimaryThread and
|
||||
* isEmptyPrimaryThread, to avoid re-initializing every iteration. So the
|
||||
* suggested sequence is: adding elements - mergePrimaryThread - only if not
|
||||
* isEmptyPrimaryThread() then iteratorPrimaryThread, because the iterator will
|
||||
* force creation of a collection, if the field is set to null. After iteration
|
||||
* use clearPrimaryThread. Instead of mergePrimaryThread() and then checking
|
||||
* isEmptyPrimaryThread, isEmtpyAfterMergePrimaryThread can be used to do both
|
||||
* in one method call.
|
||||
*
|
||||
* @author asofold
|
||||
*
|
||||
*
|
||||
* @param <T>
|
||||
* The type of stored elements.
|
||||
* @param <C>
|
||||
* The Collection type.
|
||||
*
|
||||
*/
|
||||
public abstract class DualCollection<T, C extends Collection<T>> {
|
||||
|
||||
private final Lock lock;
|
||||
|
||||
// Might consider setting sets to null, until used.
|
||||
private Collection<T> primaryThreadCollection = null;
|
||||
private Collection<T> asynchronousCollection = null;
|
||||
|
||||
/** Once emptyCount reaches nullCount, empty collections are nulled. */
|
||||
private short nullCount = 6;
|
||||
/**
|
||||
* Number of times, clear has been called with the collection being empty
|
||||
* already.
|
||||
*/
|
||||
private short emptyCount = 0;
|
||||
|
||||
/**
|
||||
* Initialize with a new ReentrantLock.
|
||||
*/
|
||||
public DualCollection() {
|
||||
this(new ReentrantLock());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialized with the given lock.
|
||||
* @param lock
|
||||
* The lock to use for locking.
|
||||
*/
|
||||
public DualCollection(Lock lock) {
|
||||
this.lock = lock;
|
||||
}
|
||||
|
||||
//////////
|
||||
// Setup
|
||||
//////////
|
||||
|
||||
/**
|
||||
* Roughly control how fast lists reset. This is not exact, as both primary
|
||||
* thread and asynchronous collections being empty but not null, could both
|
||||
* cause the emptyCount to increase. Thus with one being null for a couple
|
||||
* of iterations, the count only is increased with the other being
|
||||
* empty.<br>
|
||||
* The emptyCount is increased within the primary thread only, on
|
||||
* mergePrimaryThread for the asynchronous collection being empty, on
|
||||
* isEmptyPrimaryThread for the primary thread collection being empty. The
|
||||
* emptyCount is reset to 0 only within isEmtpyPrimaryThread, on setting the
|
||||
* primaryThreadCollection to null.
|
||||
*
|
||||
* @param nullCount
|
||||
*/
|
||||
public void setNullCount(short nullCount) {
|
||||
this.nullCount = nullCount;
|
||||
}
|
||||
|
||||
/////////////
|
||||
// Abstract
|
||||
/////////////
|
||||
|
||||
/**
|
||||
* Retrieve a new collection for internal storage. <br>
|
||||
* Must be thread-safe, as collections may be created lazily, and on
|
||||
* occasion get nulled.
|
||||
*/
|
||||
protected abstract C newCollection();
|
||||
|
||||
////////////////
|
||||
// Thread-safe
|
||||
////////////////
|
||||
|
||||
/**
|
||||
* Add an element to the asynchronous collection. <br>
|
||||
* Thread-safe.
|
||||
*
|
||||
* @param element
|
||||
* @return
|
||||
*/
|
||||
public boolean addAsynchronous(T element) {
|
||||
lock.lock();
|
||||
if (asynchronousCollection == null) {
|
||||
asynchronousCollection = newCollection();
|
||||
}
|
||||
final boolean res = asynchronousCollection.add(element);
|
||||
lock.unlock();
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple elements to the asynchronous collection. <br>
|
||||
* Thread-safe.
|
||||
*
|
||||
* @param elements
|
||||
* @return
|
||||
*/
|
||||
public boolean addAllAsynchronous(Collection<T> elements) {
|
||||
if (elements.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
lock.lock();
|
||||
if (asynchronousCollection == null) {
|
||||
asynchronousCollection = newCollection();
|
||||
}
|
||||
final boolean res = asynchronousCollection.addAll(elements);
|
||||
lock.unlock();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////
|
||||
// Primary thread only.
|
||||
/////////////////////////
|
||||
|
||||
/**
|
||||
* Add all elements from the asynchronous collection to the primary thread
|
||||
* collection under lock - this may be omitted, if the asynchronous
|
||||
* collection is nulled, to avoid locking. This will clear the asynchronous
|
||||
* collection. This will increase the emtpyCount, and would null the
|
||||
* asynchronousCollection on the emptyCount reaching the nullCount. <br>
|
||||
* Primary thread only.
|
||||
*/
|
||||
public void mergePrimaryThread() {
|
||||
if (asynchronousCollection != null) { // Called from the primary thread anyway.
|
||||
lock.lock();
|
||||
internalMergePrimaryThreadNoLock();
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Demands asynchronousCollection to be not null. <br>
|
||||
* Primary thread only.
|
||||
*/
|
||||
private void internalMergePrimaryThreadNoLock() {
|
||||
if (asynchronousCollection.isEmpty()) {
|
||||
if (++ emptyCount >= nullCount) {
|
||||
asynchronousCollection = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
primaryThreadCollection.addAll(asynchronousCollection);
|
||||
asynchronousCollection.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as mergePrimaryThread, just not locking. <br>
|
||||
* <b>Only use with external control over the lock and in locked
|
||||
* state.</b><br>
|
||||
* Primary thread only.
|
||||
*/
|
||||
public void mergePrimaryThreadNoLock() {
|
||||
if (asynchronousCollection != null) {
|
||||
internalMergePrimaryThreadNoLock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an element to the primary thread collection. <br>
|
||||
* Primary thread only.
|
||||
*
|
||||
* @param element
|
||||
* @return
|
||||
*/
|
||||
public boolean addPrimaryThread(T element) {
|
||||
if (primaryThreadCollection == null) {
|
||||
primaryThreadCollection = newCollection();
|
||||
}
|
||||
return primaryThreadCollection.add(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple elements to the primary thread collection. <br>
|
||||
* Primary thread only.
|
||||
*
|
||||
* @param elements
|
||||
* @return
|
||||
*/
|
||||
public boolean addAllPrimaryThread(Collection<T> elements) {
|
||||
if (primaryThreadCollection == null) {
|
||||
primaryThreadCollection = newCollection();
|
||||
}
|
||||
return primaryThreadCollection.addAll(elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the primary thread collection is empty. This will increase the
|
||||
* emtpyCount, if the collection is empty, and it would null the
|
||||
* primaryThreadCollection and reset the emptyCount, if the emptyCount
|
||||
* reaches the nullCount.<br>
|
||||
* Primary thread only.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isEmptyPrimaryThread() {
|
||||
if (primaryThreadCollection == null) {
|
||||
return true;
|
||||
}
|
||||
else if (primaryThreadCollection.isEmpty()) {
|
||||
if (++ emptyCount >= nullCount) {
|
||||
primaryThreadCollection = null;
|
||||
emptyCount = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to call mergePrimaryThread() first, then return
|
||||
* isEmptyPrimaryThread().<br>
|
||||
* Primary thread only.
|
||||
*
|
||||
* @return If the primary thread collection is empty, after adding all
|
||||
* elements from the asynchronous collection.
|
||||
*/
|
||||
public boolean isEmtpyAfterMergePrimaryThread() {
|
||||
mergePrimaryThread();
|
||||
return isEmptyPrimaryThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as isEmtpyAfterMergePrimaryThread, just not locking. <br>
|
||||
* <b>Only use with external control over the lock and in locked
|
||||
* state.</b><br>
|
||||
* Primary thread only.
|
||||
*/
|
||||
public boolean isEmtpyAfterMergePrimaryThreadNoLock() {
|
||||
mergePrimaryThreadNoLock();
|
||||
return isEmptyPrimaryThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the primary thread collection contains the given element. <br>
|
||||
* Primary thread only.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean containsPrimaryThread(final T element) {
|
||||
return primaryThreadCollection == null ? false : primaryThreadCollection.contains(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterator for the primary thread collection. <br>
|
||||
* Primary thread only.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Iterator<T> iteratorPrimaryThread() {
|
||||
// TODO: Consider to store an empty and/or unmodifiable iterator.
|
||||
if (primaryThreadCollection == null) {
|
||||
primaryThreadCollection = newCollection();
|
||||
}
|
||||
return primaryThreadCollection.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the primary thread collection. <br>
|
||||
* Primary thread only.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public void clearPrimaryThread() {
|
||||
if (primaryThreadCollection != null) {
|
||||
primaryThreadCollection.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package fr.neatmonster.nocheatplus.utilities.ds.collection;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
|
||||
/**
|
||||
* Use LinkedList internally.
|
||||
*
|
||||
* @author asofold
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
public class DualList<T> extends DualCollection<T, List<T>> {
|
||||
|
||||
public DualList() {
|
||||
super();
|
||||
}
|
||||
|
||||
public DualList(Lock lock) {
|
||||
super(lock);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<T> newCollection() {
|
||||
return new LinkedList<T>();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package fr.neatmonster.nocheatplus.utilities.ds.collection;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
|
||||
/**
|
||||
* Use LinkedHashSet internally.
|
||||
*
|
||||
* @author asofold
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
public class DualSet<T> extends DualCollection<T, Set<T>>{
|
||||
|
||||
public DualSet() {
|
||||
super();
|
||||
}
|
||||
|
||||
public DualSet(Lock lock) {
|
||||
super(lock);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<T> newCollection() {
|
||||
return new LinkedHashSet<T>();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user