Skip to content

Commit

Permalink
Merge pull request #114 from deltacv/dev
Browse files Browse the repository at this point in the history
Release 3.6.0
  • Loading branch information
serivesmejia authored Sep 9, 2024
2 parents 5c5d99f + 5b88869 commit bf37fa2
Show file tree
Hide file tree
Showing 60 changed files with 2,487 additions and 152 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,55 @@
package com.github.serivesmejia.eocvsim.util.event

import com.github.serivesmejia.eocvsim.util.loggerOf
import java.lang.ref.WeakReference

/**
* Event handler class to manage event listeners
* @param name the name of the event handler
*/
class EventHandler(val name: String) : Runnable {

companion object {
private val bannedClassLoaders = mutableListOf<WeakReference<ClassLoader>>()

/**
* Ban a class loader from being able to add listeners
* and lazily remove all listeners from that class loader
*/
fun banClassLoader(loader: ClassLoader) = bannedClassLoaders.add(WeakReference(loader))

/**
* Check if a class loader is banned
*/
fun isBanned(classLoader: ClassLoader): Boolean {
for(bannedClassLoader in bannedClassLoaders) {
if(bannedClassLoader.get() == classLoader) return true
}

return false
}
}

val logger by loggerOf("${name}-EventHandler")

private val lock = Any()
private val onceLock = Any()

/**
* Get all the listeners in this event handler
* thread-safe operation (synchronized)
*/
val listeners: Array<EventListener>
get() {
synchronized(lock) {
return internalListeners.toTypedArray()
}
}

/**
* Get all the "doOnce" listeners in this event handler
* thread-safe operation (synchronized)
*/
val onceListeners: Array<EventListener>
get() {
synchronized(onceLock) {
Expand All @@ -51,6 +85,9 @@ class EventHandler(val name: String) : Runnable {
private val internalListeners = ArrayList<EventListener>()
private val internalOnceListeners = ArrayList<EventListener>()

/**
* Run all the listeners in this event handler
*/
override fun run() {
for(listener in listeners) {
try {
Expand Down Expand Up @@ -90,6 +127,10 @@ class EventHandler(val name: String) : Runnable {
}
}

/**
* Add a listener to this event handler to only be run once
* @param listener the listener to add
*/
fun doOnce(listener: EventListener) {
if(callRightAway)
runListener(listener, true)
Expand All @@ -98,9 +139,16 @@ class EventHandler(val name: String) : Runnable {
}
}

/**
* Add a runnable as a listener to this event handler to only be run once
* @param runnable the runnable to add
*/
fun doOnce(runnable: Runnable) = doOnce { runnable.run() }


/**
* Add a listener to this event handler to be run every time
* @param listener the listener to add
*/
fun doPersistent(listener: EventListener): EventListenerRemover {
synchronized(lock) {
internalListeners.add(listener)
Expand All @@ -111,36 +159,82 @@ class EventHandler(val name: String) : Runnable {
return EventListenerRemover(this, listener, false)
}

/**
* Add a runnable as a listener to this event handler to be run every time
* @param runnable the runnable to add
*/
fun doPersistent(runnable: Runnable) = doPersistent { runnable.run() }

/**
* Remove a listener from this event handler
* @param listener the listener to remove
*/
fun removePersistentListener(listener: EventListener) {
if(internalListeners.contains(listener)) {
synchronized(lock) { internalListeners.remove(listener) }
}
}

/**
* Remove a "doOnce" listener from this event handler
* @param listener the listener to remove
*/
fun removeOnceListener(listener: EventListener) {
if(internalOnceListeners.contains(listener)) {
synchronized(onceLock) { internalOnceListeners.remove(listener) }
}
}

/**
* Remove all listeners from this event handler
*/
fun removeAllListeners() {
removeAllPersistentListeners()
removeAllOnceListeners()
}

/**
* Remove all persistent listeners from this event handler
*/
fun removeAllPersistentListeners() = synchronized(lock) {
internalListeners.clear()
}

/**
* Remove all "doOnce" listeners from this event handler
*/
fun removeAllOnceListeners() = synchronized(onceLock) {
internalOnceListeners.clear()
}

/**
* Add a listener to this event handler to only be run once
* with kotlin operator overloading
* @param listener the listener to add
*/
operator fun invoke(listener: EventListener) = doPersistent(listener)

private fun runListener(listener: EventListener, isOnce: Boolean) =
private fun runListener(listener: EventListener, isOnce: Boolean) {
if(isBanned(listener.javaClass.classLoader)) {
removeBannedListeners()
return
}

listener.run(EventListenerRemover(this, listener, isOnce))
}

private fun removeBannedListeners() {
for(listener in internalListeners.toArray()) {
if(isBanned(listener.javaClass.classLoader)) {
internalListeners.remove(listener)
logger.warn("Removed banned listener from ${listener.javaClass.classLoader}")
}
}

for(listener in internalOnceListeners.toArray()) {
if(isBanned(listener.javaClass.classLoader)) internalOnceListeners.remove(listener)
logger.warn("Removed banned listener from ${listener.javaClass.classLoader}")
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,37 @@

package com.github.serivesmejia.eocvsim.util.event

/**
* Functional interface to be used as an event listener
*/
fun interface EventListener {
/**
* Function to be called when the event is emitted
* @param remover the remover object to remove the listener
*/
fun run(remover: EventListenerRemover)
}

/**
* Class to remove an event listener
* @param handler the event handler
* @param listener the event listener
* @param isOnceListener whether the listener is a once listener
*/
class EventListenerRemover(
val handler: EventHandler,
val listener: EventListener,
val isOnceListener: Boolean
) {

private val attached = mutableListOf<EventListenerRemover>()

/**
* Removes the listener from the event handler
*/
fun removeThis() {
if(isOnceListener)
handler.removeOnceListener(listener)
else
handler.removePersistentListener(listener)
}

fun attach(remover: EventListenerRemover) {

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,23 @@
import java.awt.image.BufferedImage;
import java.util.concurrent.ArrayBlockingQueue;

/**
* A class to recycle BufferedImages, to avoid creating and disposing them
* constantly, which can be very slow.
* This class is thread-safe.
*/
public class BufferedImageRecycler {

private final RecyclableBufferedImage[] allBufferedImages;
private final ArrayBlockingQueue<RecyclableBufferedImage> availableBufferedImages;

/**
* Creates a new BufferedImageRecycler with the specified number of BufferedImages
* @param num The number of BufferedImages to create
* @param allImgWidth The width of all BufferedImages
* @param allImgHeight The height of all BufferedImages
* @param allImgType The type of all BufferedImages
*/
public BufferedImageRecycler(int num, int allImgWidth, int allImgHeight, int allImgType) {
allBufferedImages = new RecyclableBufferedImage[num];
availableBufferedImages = new ArrayBlockingQueue<>(num);
Expand All @@ -42,20 +54,47 @@ public BufferedImageRecycler(int num, int allImgWidth, int allImgHeight, int all
}
}

/**
* Creates a new BufferedImageRecycler with the specified number of BufferedImages
* @param num The number of BufferedImages to create
* @param allImgSize The size of all BufferedImages
* @param allImgType The type of all BufferedImages
*/
public BufferedImageRecycler(int num, Dimension allImgSize, int allImgType) {
this(num, (int)allImgSize.getWidth(), (int)allImgSize.getHeight(), allImgType);
}

/**
* Creates a new BufferedImageRecycler with the specified number of BufferedImages
* @param num The number of BufferedImages to create
* @param allImgWidth The width of all BufferedImages
* @param allImgHeight The height of all BufferedImages
*/
public BufferedImageRecycler(int num, int allImgWidth, int allImgHeight) {
this(num, allImgWidth, allImgHeight, BufferedImage.TYPE_3BYTE_BGR);
}

/**
* Creates a new BufferedImageRecycler with the specified number of BufferedImages
* And with a default type of BufferedImage.TYPE_3BYTE_BGR
* @param num The number of BufferedImages to create
* @param allImgSize The size of all BufferedImages
*/
public BufferedImageRecycler(int num, Dimension allImgSize) {
this(num, (int)allImgSize.getWidth(), (int)allImgSize.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
}

/**
* Checks if at least one BufferedImage is currently being used
* @return True if at least one BufferedImage is being used, false otherwise
*/
public boolean isOnUse() { return allBufferedImages.length != availableBufferedImages.size(); }

/**
* Takes a BufferedImage from the recycler
* @throws RuntimeException If all BufferedImages have been checked out
* @return A BufferedImage from the recycler
*/
public synchronized RecyclableBufferedImage takeBufferedImage() {

if (availableBufferedImages.size() == 0) {
Expand All @@ -74,6 +113,10 @@ public synchronized RecyclableBufferedImage takeBufferedImage() {

}

/**
* Returns a BufferedImage to the recycler
* @param buffImg The BufferedImage to return
*/
public synchronized void returnBufferedImage(RecyclableBufferedImage buffImg) {
if (buffImg != allBufferedImages[buffImg.idx]) {
throw new IllegalArgumentException("This BufferedImage does not belong to this recycler!");
Expand All @@ -88,16 +131,31 @@ public synchronized void returnBufferedImage(RecyclableBufferedImage buffImg) {
}
}

/**
* Flushes all BufferedImages in the recycler
* This method should be called when the recycler is no longer needed
* to free up memory
*/
public synchronized void flushAll() {
for(BufferedImage img : allBufferedImages) {
img.flush();
}
}

/**
* A BufferedImage that can be recycled by a BufferedImageRecycler
*/
public static class RecyclableBufferedImage extends BufferedImage {
private int idx = -1;
private volatile boolean checkedOut = false;

/**
* Creates a new RecyclableBufferedImage
* @param idx The index of the BufferedImage in the recycler
* @param width The width of the BufferedImage
* @param height The height of the BufferedImage
* @param imageType The type of the BufferedImage
*/
private RecyclableBufferedImage(int idx, int width, int height, int imageType) {
super(width, height, imageType);
this.idx = idx;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,19 @@
import java.util.HashMap;
import java.util.Map;

/**
* A class that allows for recycling BufferedImages with different dimensions.
*/
public class DynamicBufferedImageRecycler {

private final HashMap<Dimension, BufferedImageRecycler> recyclers = new HashMap<>();

/**
* Get a BufferedImage with the specified dimensions.
* @param size The size of the BufferedImage
* @param recyclerSize If a recycler with the specified dimensions doesn't exist, this is the size of the new recycler
* @return A BufferedImage with the specified dimensions
*/
public synchronized BufferedImage giveBufferedImage(Dimension size, int recyclerSize) {

//look for existing buff image recycler with desired dimensions
Expand All @@ -58,6 +67,10 @@ public synchronized BufferedImage giveBufferedImage(Dimension size, int recycler
return buffImg;
}

/**
* Return a BufferedImage to the recycler.
* @param buffImg The BufferedImage to return
*/
public synchronized void returnBufferedImage(BufferedImage buffImg) {
Dimension dimension = new Dimension(buffImg.getWidth(), buffImg.getHeight());

Expand All @@ -66,7 +79,11 @@ public synchronized void returnBufferedImage(BufferedImage buffImg) {
if(recycler != null)
recycler.returnBufferedImage((BufferedImageRecycler.RecyclableBufferedImage) buffImg);
}


/**
* Flush all BufferedImages in all recyclers.
* Should be called when the recycler is no longer needed to free up memory.
*/
public synchronized void flushAll() {
for(BufferedImageRecycler recycler : recyclers.values()) {
recycler.flushAll();
Expand Down
Loading

0 comments on commit bf37fa2

Please sign in to comment.