package org.ebookdroid.common.bitmaps;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
import org.emdev.common.log.LogContext;
import org.emdev.common.log.LogManager;
import org.emdev.utils.LengthUtils;
import org.emdev.utils.collections.ArrayDeque;
import org.emdev.utils.collections.SparseArrayEx;
import org.emdev.utils.collections.TLIterator;
public class ByteBufferManager {
static final LogContext LCTX = LogManager.root().lctx("ByteBufferManager", false);
private final static long BITMAP_MEMORY_LIMIT = Runtime.getRuntime().maxMemory() / 2;
private static final int GENERATION_THRESHOLD = 10;
private static SparseArrayEx<ByteBufferBitmap> used = new SparseArrayEx<ByteBufferBitmap>();
private static ArrayDeque<ByteBufferBitmap> pool = new ArrayDeque<ByteBufferBitmap>();
private static Queue<Object> releasing = new ConcurrentLinkedQueue<Object>();
private static final AtomicLong created = new AtomicLong();
private static final AtomicLong reused = new AtomicLong();
private static final AtomicLong memoryUsed = new AtomicLong();
private static final AtomicLong memoryPooled = new AtomicLong();
private static AtomicLong generation = new AtomicLong();
private static ReentrantLock lock = new ReentrantLock();
static int partSize = 1 << 7;
public static ByteBufferBitmap getBitmap(final int width, final int height) {
lock.lock();
try {
if (LCTX.isDebugEnabled()) {
if (memoryUsed.get() + memoryPooled.get() == 0) {
LCTX.d("!!! Bitmap pool size: " + (BITMAP_MEMORY_LIMIT / 1024) + "KB");
}
}
final TLIterator<ByteBufferBitmap> it = pool.iterator();
try {
while (it.hasNext()) {
final ByteBufferBitmap ref = it.next();
if (ref.size >= 4 * width * height) {
if (ref.used.compareAndSet(false, true)) {
it.remove();
ref.pixels.rewind();
ref.gen = generation.get();
ref.width = width;
ref.height = height;
used.append(ref.id, ref);
reused.incrementAndGet();
memoryPooled.addAndGet(-ref.size);
memoryUsed.addAndGet(ref.size);
if (LCTX.isDebugEnabled()) {
LCTX.d("Reuse bitmap: [" + ref.id + ", " + width + ", " + height + "], created="
+ created + ", reused=" + reused + ", memoryUsed=" + used.size() + "/"
+ (memoryUsed.get() / 1024) + "KB" + ", memoryInPool=" + pool.size() + "/"
+ (memoryPooled.get() / 1024) + "KB");
}
return ref;
} else {
if (LCTX.isDebugEnabled()) {
LCTX.d("Attempt to re-use used bitmap: " + ref);
}
}
}
}
} finally {
it.release();
}
final ByteBufferBitmap ref = new ByteBufferBitmap(width, height);
used.put(ref.id, ref);
created.incrementAndGet();
memoryUsed.addAndGet(ref.size);
if (LCTX.isDebugEnabled()) {
LCTX.d("Create bitmap: [" + ref.id + ", " + width + ", " + height + "], created=" + created
+ ", reused=" + reused + ", memoryUsed=" + used.size() + "/" + (memoryUsed.get() / 1024) + "KB"
+ ", memoryInPool=" + pool.size() + "/" + (memoryPooled.get() / 1024) + "KB");
}
shrinkPool(BITMAP_MEMORY_LIMIT);
return ref;
} finally {
lock.unlock();
}
}
public static ByteBufferBitmap[] getParts(final int partSize, final int rows, final int columns) {
lock.lock();
try {
if (LCTX.isDebugEnabled()) {
if (memoryUsed.get() + memoryPooled.get() == 0) {
LCTX.d("!!! Bitmap pool size: " + (BITMAP_MEMORY_LIMIT / 1024) + "KB");
}
}
int filled = 0;
final int length = rows * columns;
final ByteBufferBitmap[] arr = new ByteBufferBitmap[length];
final int size = 4 * partSize * partSize;
final TLIterator<ByteBufferBitmap> it = pool.iterator();
try {
while (filled < length && it.hasNext()) {
final ByteBufferBitmap ref = it.next();
if (ref.size == size) {
if (ref.used.compareAndSet(false, true)) {
it.remove();
ref.pixels.rewind();
ref.gen = generation.get();
ref.width = partSize;
ref.height = partSize;
used.append(ref.id, ref);
reused.incrementAndGet();
memoryPooled.addAndGet(-ref.size);
memoryUsed.addAndGet(ref.size);
arr[filled++] = ref;
} else {
if (LCTX.isDebugEnabled()) {
LCTX.d("Attempt to re-use used bitmap: " + ref);
}
}
}
}
} finally {
it.release();
}
final int reused = filled;
final int additional = length - filled;
if (additional > 0) {
for (int i = 0; i < additional; i++) {
final ByteBufferBitmap ref = new ByteBufferBitmap(partSize, partSize);
arr[filled++] = ref;
used.append(ref.id, ref);
created.incrementAndGet();
memoryUsed.addAndGet(ref.size);
}
}
if (LCTX.isDebugEnabled()) {
LCTX.d("Parts created : " + (additional) + ", resused: " + reused + ". Totally created=" + created
+ ", reused=" + reused + ", memoryUsed=" + used.size() + "/" + (memoryUsed.get() / 1024) + "KB"
+ ", memoryInPool=" + pool.size() + "/" + (memoryPooled.get() / 1024) + "KB");
}
if (additional > 0) {
shrinkPool(BITMAP_MEMORY_LIMIT);
}
return arr;
} finally {
lock.unlock();
}
}
public static void clear(final String msg) {
lock.lock();
try {
generation.addAndGet(GENERATION_THRESHOLD * 2);
removeOldRefs();
release();
shrinkPool(0);
} finally {
lock.unlock();
}
}
@SuppressWarnings("unchecked")
public static void release() {
lock.lock();
try {
generation.incrementAndGet();
removeOldRefs();
int count = 0;
final int queueBefore = LCTX.isDebugEnabled() ? releasing.size() : 0;
while (!releasing.isEmpty()) {
final Object ref = releasing.poll();
if (ref instanceof ByteBufferBitmap) {
releaseImpl((ByteBufferBitmap) ref);
count++;
} else if (ref instanceof GLBitmaps) {
final GLBitmaps bmp = (GLBitmaps) ref;
final ByteBufferBitmap[] bitmaps = bmp.clear();
if (bitmaps != null) {
for (final ByteBufferBitmap b : bitmaps) {
releaseImpl(b);
count++;
}
}
} else if (ref instanceof List) {
final List<GLBitmaps> list = (List<GLBitmaps>) ref;
for (final GLBitmaps bmp : list) {
final ByteBufferBitmap[] bitmaps = bmp.clear();
if (bitmaps != null) {
for (final ByteBufferBitmap b : bitmaps) {
releaseImpl(b);
count++;
}
}
}
} else if (ref instanceof ByteBufferBitmap[]) {
final ByteBufferBitmap[] bitmaps = (ByteBufferBitmap[]) ref;
for (final ByteBufferBitmap bitmap : bitmaps) {
if (bitmap != null) {
releaseImpl(bitmap);
count++;
}
}
} else {
LCTX.e("Unknown object in release queue: " + ref);
}
}
shrinkPool(BITMAP_MEMORY_LIMIT);
if (LCTX.isDebugEnabled()) {
LCTX.d("Return " + count + " bitmap(s) to pool: " + "memoryUsed=" + used.size() + "/"
+ (memoryUsed.get() / 1024) + "KB" + ", memoryInPool=" + pool.size() + "/"
+ (memoryPooled.get() / 1024) + "KB" + ", releasing queue size " + queueBefore + " => 0");
}
} finally {
lock.unlock();
}
}
public static void release(final ByteBufferBitmap ref) {
if (ref != null) {
if (LCTX.isDebugEnabled()) {
LCTX.d("Adding 1 ref to release queue");
}
releasing.add(ref);
}
}
public static void release(final ByteBufferBitmap[] refs) {
if (refs != null) {
if (LCTX.isDebugEnabled()) {
LCTX.d("Adding " + refs.length + " refs to release queue");
}
releasing.add(refs);
}
}
public static void release(final GLBitmaps ref) {
if (ref != null) {
if (LCTX.isDebugEnabled()) {
LCTX.d("Adding 1 bitmaps to release queue");
}
releasing.add(ref);
}
}
public static void release(final List<GLBitmaps> bitmapsToRecycle) {
if (LengthUtils.isNotEmpty(bitmapsToRecycle)) {
if (LCTX.isDebugEnabled()) {
LCTX.d("Adding list of " + bitmapsToRecycle.size() + " bitmaps to release queue");
}
releasing.add(new ArrayList<GLBitmaps>(bitmapsToRecycle));
}
}
static void releaseImpl(final ByteBufferBitmap ref) {
assert ref != null;
if (ref.used.compareAndSet(true, false)) {
if (used.get(ref.id, null) == ref) {
used.remove(ref.id);
memoryUsed.addAndGet(-ref.size);
} else {
LCTX.e("The bitmap " + ref + " not found in used ones");
}
} else {
if (LCTX.isDebugEnabled()) {
LCTX.d("Attempt to release unused bitmap");
}
}
pool.add(ref);
memoryPooled.addAndGet(ref.size);
}
private static void removeOldRefs() {
final long gen = generation.get();
int recycled = 0;
final Iterator<ByteBufferBitmap> it = pool.iterator();
while (it.hasNext()) {
final ByteBufferBitmap ref = it.next();
if (gen - ref.gen > GENERATION_THRESHOLD) {
it.remove();
ref.recycle();
recycled++;
memoryPooled.addAndGet(-ref.size);
}
}
if (recycled > 0) {
if (LCTX.isDebugEnabled()) {
LCTX.d("Recycled " + recycled + " pooled bitmap(s): " + "memoryUsed=" + used.size() + "/"
+ (memoryUsed.get() / 1024) + "KB" + ", memoryInPool=" + pool.size() + "/"
+ (memoryPooled.get() / 1024) + "KB");
}
}
}
private static void shrinkPool(final long limit) {
int recycled = 0;
while (memoryPooled.get() + memoryUsed.get() > limit && !pool.isEmpty()) {
final ByteBufferBitmap ref = pool.poll();
if (ref != null) {
ref.recycle();
memoryPooled.addAndGet(-ref.size);
recycled++;
}
}
if (recycled > 0) {
if (LCTX.isDebugEnabled()) {
LCTX.d("Recycled " + recycled + " pooled bitmap(s): " + "memoryUsed=" + used.size() + "/"
+ (memoryUsed.get() / 1024) + "KB" + ", memoryInPool=" + pool.size() + "/"
+ (memoryPooled.get() / 1024) + "KB");
}
}
}
public static int getPartSize() {
return partSize;
}
public static void setPartSize(final int partSize) {
ByteBufferManager.partSize = partSize;
}
}