com.jogamp.common.util.LFRingbuffer Maven / Gradle / Ivy
Show all versions of gluegen-rt Show documentation
/**
* Copyright 2013 JogAmp Community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of JogAmp Community.
*/
package com.jogamp.common.util;
import java.io.PrintStream;
import java.lang.reflect.Array;
/**
* Simple implementation of {@link Ringbuffer},
* exposing lock-free
* {@link #get() get*(..)} and {@link #put(Object) put*(..)} methods.
*
* Implementation utilizes the Always Keep One Slot Open,
* hence implementation maintains an internal array of capacity
plus one!
*
*
* Implementation is thread safe if:
*
* - {@link #get() get*(..)} operations are performed from one thread only.
* - {@link #put(Object) put*(..)} operations are performed from one thread only.
* - {@link #get() get*(..)} and {@link #put(Object) put*(..)} thread may be the same.
*
*
*
* Following methods utilize global synchronization:
*
* - {@link #resetFull(Object[])}
* - {@link #clear()}
* - {@link #growEmptyBuffer(Object[])}
*
* User needs to synchronize above methods w/ the lock-free
* w/ {@link #get() get*(..)} and {@link #put(Object) put*(..)} methods,
* e.g. by controlling their threads before invoking the above.
*
*
* Characteristics:
*
* - Read position points to the last read element.
* - Write position points to the last written element.
*
*
* Empty writePos == readPos size == 0
* Full writePos == readPos - 1 size == capacity
*
*
*/
public class LFRingbuffer implements Ringbuffer {
private final Object syncRead = new Object();
private final Object syncWrite = new Object();
private final Object syncGlobal = new Object();
private /* final */ volatile T[] array; // not final due to grow
private /* final */ volatile int capacityPlusOne; // not final due to grow
private volatile int readPos;
private volatile int writePos;
private volatile int size;
@Override
public final String toString() {
return "LFRingbuffer>[filled "+size+" / "+(capacityPlusOne-1)+", writePos "+writePos+", readPos "+readPos+"]";
}
@Override
public final void dump(final PrintStream stream, final String prefix) {
stream.println(prefix+" "+toString()+" {");
for(int i=0; i
* Example for a 10 element Integer array:
*
* Integer[] source = new Integer[10];
* // fill source with content ..
* Ringbuffer rb = new LFRingbuffer(source);
*
*
*
* {@link #isFull()} returns true on the newly created full ring buffer.
*
*
* Implementation will allocate an internal array with size of array copyFrom
plus one,
* and copy all elements from array copyFrom
into the internal array.
*
* @param copyFrom mandatory source array determining ring buffer's net {@link #capacity()} and initial content.
* @throws IllegalArgumentException if copyFrom
is null
*/
@SuppressWarnings("unchecked")
public LFRingbuffer(final T[] copyFrom) throws IllegalArgumentException {
capacityPlusOne = copyFrom.length + 1;
array = (T[]) newArray(copyFrom.getClass(), capacityPlusOne);
resetImpl(true, copyFrom);
}
/**
* Create an empty ring buffer instance w/ the given net capacity
.
*
* Example for a 10 element Integer array:
*
* Ringbuffer rb = new LFRingbuffer(10, Integer[].class);
*
*
*
* {@link #isEmpty()} returns true on the newly created empty ring buffer.
*
*
* Implementation will allocate an internal array of size capacity
plus one.
*
* @param arrayType the array type of the created empty internal array.
* @param capacity the initial net capacity of the ring buffer
*/
public LFRingbuffer(final Class extends T[]> arrayType, final int capacity) {
capacityPlusOne = capacity+1;
array = newArray(arrayType, capacityPlusOne);
resetImpl(false, null /* empty, nothing to copy */ );
}
@Override
public final int capacity() { return capacityPlusOne-1; }
@Override
public final void clear() {
synchronized ( syncGlobal ) {
resetImpl(false, null);
for(int i=0; i
* Implementation advances the read position and returns the element at it, if not empty.
*
*/
@Override
public final T get() {
try {
return getImpl(false, false);
} catch (final InterruptedException ie) { throw new RuntimeException(ie); }
}
/**
* {@inheritDoc}
*
* Implementation advances the read position and returns the element at it, if not empty.
*
*/
@Override
public final T getBlocking() throws InterruptedException {
return getImpl(true, false);
}
@Override
public final T peek() {
try {
return getImpl(false, true);
} catch (final InterruptedException ie) { throw new RuntimeException(ie); }
}
@Override
public final T peekBlocking() throws InterruptedException {
return getImpl(true, true);
}
private final T getImpl(final boolean blocking, final boolean peek) throws InterruptedException {
int localReadPos = readPos;
if( localReadPos == writePos ) {
if( blocking ) {
synchronized( syncRead ) {
while( localReadPos == writePos ) {
syncRead.wait();
}
}
} else {
return null;
}
}
localReadPos = (localReadPos + 1) % capacityPlusOne;
final T r = array[localReadPos];
if( !peek ) {
array[localReadPos] = null;
synchronized ( syncWrite ) {
size--;
readPos = localReadPos;
syncWrite.notifyAll(); // notify waiting putter
}
}
return r;
}
/**
* {@inheritDoc}
*
* Implementation advances the write position and stores the given element at it, if not full.
*
*/
@Override
public final boolean put(final T e) {
try {
return putImpl(e, false, false);
} catch (final InterruptedException ie) { throw new RuntimeException(ie); }
}
/**
* {@inheritDoc}
*
* Implementation advances the write position and stores the given element at it, if not full.
*
*/
@Override
public final void putBlocking(final T e) throws InterruptedException {
if( !putImpl(e, false, true) ) {
throw new InternalError("Blocking put failed: "+this);
}
}
/**
* {@inheritDoc}
*
* Implementation advances the write position and keeps the element at it, if not full.
*
*/
@Override
public final boolean putSame(final boolean blocking) throws InterruptedException {
return putImpl(null, true, blocking);
}
private final boolean putImpl(final T e, final boolean sameRef, final boolean blocking) throws InterruptedException {
int localWritePos = writePos;
localWritePos = (localWritePos + 1) % capacityPlusOne;
if( localWritePos == readPos ) {
if( blocking ) {
synchronized( syncWrite ) {
while( localWritePos == readPos ) {
syncWrite.wait();
}
}
} else {
return false;
}
}
if( !sameRef ) {
array[localWritePos] = e;
}
synchronized ( syncRead ) {
size++;
writePos = localWritePos;
syncRead.notifyAll(); // notify waiting getter
}
return true;
}
@Override
public final void waitForFreeSlots(final int count) throws InterruptedException {
synchronized ( syncRead ) {
if( capacityPlusOne - 1 - size < count ) {
while( capacityPlusOne - 1 - size < count ) {
syncRead.wait();
}
}
}
}
@Override
public final void growEmptyBuffer(final T[] newElements) throws IllegalStateException, IllegalArgumentException {
synchronized( syncGlobal ) {
if( null == newElements ) {
throw new IllegalArgumentException("newElements is null");
}
@SuppressWarnings("unchecked")
final Class extends T[]> arrayTypeInternal = (Class extends T[]>) array.getClass();
@SuppressWarnings("unchecked")
final Class extends T[]> arrayTypeNew = (Class extends T[]>) newElements.getClass();
if( arrayTypeInternal != arrayTypeNew ) {
throw new IllegalArgumentException("newElements array-type mismatch, internal "+arrayTypeInternal+", newElements "+arrayTypeNew);
}
if( 0 != size ) {
throw new IllegalStateException("Buffer is not empty: "+this);
}
if( readPos != writePos ) {
throw new InternalError("R/W pos not equal: "+this);
}
if( readPos != writePos ) {
throw new InternalError("R/W pos not equal at empty: "+this);
}
final int growAmount = newElements.length;
final int newCapacity = capacityPlusOne + growAmount;
final T[] oldArray = array;
final T[] newArray = newArray(arrayTypeInternal, newCapacity);
// writePos == readPos
writePos += growAmount; // warp writePos to the end of the new data location
if( readPos >= 0 ) {
System.arraycopy(oldArray, 0, newArray, 0, readPos+1);
}
if( growAmount > 0 ) {
System.arraycopy(newElements, 0, newArray, readPos+1, growAmount);
}
final int tail = capacityPlusOne-1-readPos;
if( tail > 0 ) {
System.arraycopy(oldArray, readPos+1, newArray, writePos+1, tail);
}
size = growAmount;
capacityPlusOne = newCapacity;
array = newArray;
}
}
@Override
public final void growFullBuffer(final int growAmount) throws IllegalStateException, IllegalArgumentException {
synchronized ( syncGlobal ) {
if( 0 > growAmount ) {
throw new IllegalArgumentException("amount "+growAmount+" < 0 ");
}
if( capacityPlusOne-1 != size ) {
throw new IllegalStateException("Buffer is not full: "+this);
}
final int wp1 = ( writePos + 1 ) % capacityPlusOne;
if( wp1 != readPos ) {
throw new InternalError("R != W+1 pos at full: "+this);
}
@SuppressWarnings("unchecked")
final Class extends T[]> arrayTypeInternal = (Class extends T[]>) array.getClass();
final int newCapacity = capacityPlusOne + growAmount;
final T[] oldArray = array;
final T[] newArray = newArray(arrayTypeInternal, newCapacity);
// writePos == readPos - 1
readPos = ( writePos + 1 + growAmount ) % newCapacity; // warp readPos to the end of the new data location
if(writePos >= 0) {
System.arraycopy(oldArray, 0, newArray, 0, writePos+1);
}
final int tail = capacityPlusOne-1-writePos;
if( tail > 0 ) {
System.arraycopy(oldArray, writePos+1, newArray, readPos, tail);
}
capacityPlusOne = newCapacity;
array = newArray;
}
}
@SuppressWarnings("unchecked")
private static T[] newArray(final Class extends T[]> arrayType, final int length) {
return ((Object)arrayType == (Object)Object[].class)
? (T[]) new Object[length]
: (T[]) Array.newInstance(arrayType.getComponentType(), length);
}
}