diff --git a/lib/src/main/java/com/amaze/filepreloaderlibrary/Loader.kt b/lib/src/main/java/com/amaze/filepreloaderlibrary/Loader.kt index 0ba7225..eb3d86f 100644 --- a/lib/src/main/java/com/amaze/filepreloaderlibrary/Loader.kt +++ b/lib/src/main/java/com/amaze/filepreloaderlibrary/Loader.kt @@ -10,6 +10,7 @@ import com.amaze.filepreloaderlibrary.utils.* import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.async +import kotlinx.coroutines.channels.produce import kotlinx.coroutines.launch import kotlinx.coroutines.sync.withLock @@ -31,26 +32,29 @@ internal class Loader(private val clazz: Class) { * on each file (represented by its path) inside the folder. */ internal fun loadFrom(unit: ProcessUnit) { - GlobalScope.launch { - var somethingAddedToPreload = false - val file = KFile(unit.path) + val file = KFile(unit.path) + processor.workHighPriority(GlobalScope.produce { //Load current folder getPreloadMapMutex(clazz).withLock { if (getPreloadMap(clazz)[file.path] == null) { val subfiles: Array = file.list() ?: arrayOf() - for (filename in subfiles) { - addToProcess(file.path, ProcessUnit(file.path + DIVIDER + filename, unit.fetcherFunction), PRIORITY_NOW) - } getPreloadMap(clazz)[file.path] = PreloadedFolder(subfiles.size) if (getPreloadMap(clazz).size > PRELOADED_MAP_MAXIMUM) cleanOldEntries() getDeleteQueue(clazz).add(file.path) - somethingAddedToPreload = somethingAddedToPreload || subfiles.isNotEmpty() + + for (filename in subfiles) { + send(toPreloadable(file.path, ProcessUnit(file.path + DIVIDER + filename, unit.fetcherFunction), PRIORITY_NOW)) + } } } + close() + }) + + processor.workLowPriority(GlobalScope.produce { //Load children folders file.listDirectoriesToList()?.forEach { getPreloadMapMutex(clazz).withLock { @@ -58,40 +62,37 @@ internal class Loader(private val clazz: Class) { if (getPreloadMap(clazz)[currentPath] == null) { val subfiles: Array = KFile(currentPath).list() ?: arrayOf() - for (filename in subfiles) { - addToProcess(currentPath, ProcessUnit(currentPath + DIVIDER + filename, unit.fetcherFunction), PRIORITY_FUTURE) - } getPreloadMap(clazz)[currentPath] = PreloadedFolder(subfiles.size) if (getPreloadMap(clazz).size > PRELOADED_MAP_MAXIMUM) cleanOldEntries() getDeleteQueue(clazz).add(currentPath) - somethingAddedToPreload = somethingAddedToPreload || subfiles.isNotEmpty() + for (filename in subfiles) { + send(toPreloadable(currentPath, ProcessUnit(currentPath + DIVIDER + filename, unit.fetcherFunction), PRIORITY_FUTURE)) + } } } } + //Load parent folder getPreloadMapMutex(clazz).withLock { val parentPath = file.parent if (parentPath != null && getPreloadMap(clazz)[parentPath] == null) { val subfiles: Array = KFile(parentPath).list() ?: arrayOf() - subfiles.forEach { - addToProcess(parentPath, ProcessUnit(parentPath + DIVIDER + it, unit.fetcherFunction), PRIORITY_FUTURE) - } getPreloadMap(clazz)[parentPath] = PreloadedFolder(subfiles.size) if (getPreloadMap(clazz).size > PRELOADED_MAP_MAXIMUM) cleanOldEntries() getDeleteQueue(clazz).add(parentPath) - somethingAddedToPreload = somethingAddedToPreload || subfiles.isNotEmpty() + subfiles.forEach { + send(toPreloadable(parentPath, ProcessUnit(parentPath + DIVIDER + it, unit.fetcherFunction), PRIORITY_FUTURE)) + } } } - if(somethingAddedToPreload) { - processor.work() - } - } + close() + }) } /** @@ -105,8 +106,10 @@ internal class Loader(private val clazz: Class) { val file = KFile(unit.path) val fileList = file.list() ?: arrayOf() - for (path in fileList) { - addToProcess(file.path, ProcessUnit(file.absolutePath + DIVIDER + path, unit.fetcherFunction), PRIORITY_NOW) + val preloadableFiles = produce { + for (path in fileList) { + send(toPreloadable(file.path, ProcessUnit(file.absolutePath + DIVIDER + path, unit.fetcherFunction), PRIORITY_NOW)) + } } getPreloadMapMutex(clazz).withLock { @@ -114,9 +117,7 @@ internal class Loader(private val clazz: Class) { getDeleteQueue(clazz).add(file.path) } - if (fileList.isNotEmpty()) { - processor.work() - } + processor.workHighPriority(preloadableFiles) } } @@ -134,7 +135,7 @@ internal class Loader(private val clazz: Class) { /** * Clear everything, all data loaded will be discarded. */ - internal suspend fun clear() { + internal fun clear() { processor.clear() getPreloadMap(clazz).clear() getDeleteQueue(clazz).clear() @@ -143,11 +144,11 @@ internal class Loader(private val clazz: Class) { /** * Add file (represented by [unit]) to the [preloadPriorityQueue] to be preloaded by [loadFolder]. */ - private suspend fun addToProcess(path: String, unit: ProcessUnit, priority: Int) { + private fun toPreloadable(path: String, unit: ProcessUnit, priority: Int): PreloadableUnit { val start = if(priority == PRIORITY_NOW) CoroutineStart.DEFAULT else CoroutineStart.LAZY val f = GlobalScope.async(start = start) { ProcessedUnit(path, unit.fetcherFunction(unit.path)) } - processor.add(PreloadableUnit(f, priority, unit.path.hashCode())) + return PreloadableUnit(f, priority) } /** diff --git a/lib/src/main/java/com/amaze/filepreloaderlibrary/Processor.kt b/lib/src/main/java/com/amaze/filepreloaderlibrary/Processor.kt index f7096b4..9f4ead2 100644 --- a/lib/src/main/java/com/amaze/filepreloaderlibrary/Processor.kt +++ b/lib/src/main/java/com/amaze/filepreloaderlibrary/Processor.kt @@ -1,19 +1,19 @@ package com.amaze.filepreloaderlibrary -import com.amaze.filepreloaderlibrary.PreloadedManager.getDeleteQueue import com.amaze.filepreloaderlibrary.PreloadedManager.getPreloadMap -import com.amaze.filepreloaderlibrary.PreloadedManager.getPreloadMapMutex import com.amaze.filepreloaderlibrary.datastructures.* import com.amaze.filepreloaderlibrary.utils.* -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import java.util.concurrent.atomic.AtomicBoolean +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.ReceiveChannel + +import java.util.* +import java.util.concurrent.atomic.AtomicInteger /** * Basically means call `[ProcessUnit].fetcherFunction` on each of `[ProcessUnit].path`'s files. * This is done asynchly. * -* @see Processor.work +* @see Processor.workHighPriority */ internal data class ProcessUnit(val path: String, val fetcherFunction: FetcherFunction) @@ -34,28 +34,33 @@ internal class Processor(private val clazz: Class) { } } - /** - * Thread safe. - * All the callable executions to load all the folders. - * - * 'Load a folder' means that the function `[unit].second` will be called - * on each file (represented by its path) inside the folder. - */ - private val preloadPriorityQueue: UniquePriorityBlockingQueue> = UniquePriorityBlockingQueue() - - private val isWorking = AtomicBoolean(false) + private val ranCoroutines = Collections.synchronizedSet(hashSetOf()) + private val workingWithHighPriority = AtomicInteger(0) /** * Calls each function in [preloadPriorityQueue] (removing it). * Then adds the result [(path, data)] to `[getPreloadMap].get(path)`. */ - internal fun work() { - if(isWorking.get()) return - isWorking.set(true) + internal fun workHighPriority(producer: ReceiveChannel?>) { + work(producer, { + workingWithHighPriority.incrementAndGet() + }) { + workingWithHighPriority.decrementAndGet() + } + } - GlobalScope.launch { - while (preloadPriorityQueue.isNotEmpty()) { - val elem = preloadPriorityQueue.poll() ?: throw IllegalStateException("Polled element cannot be null!") + internal fun workLowPriority(producer: ReceiveChannel?>) { + work(producer, { + while (workingWithHighPriority.get() != 0) yield() + }) + } + + private fun work(producer: ReceiveChannel?>, onStart: suspend () -> Unit, onEnd: suspend () -> Unit = {}) { + val job = GlobalScope.launch { + onStart() + + for (elem in producer) { + elem ?: throw IllegalStateException("Polled element cannot be null!") val (path, data) = elem.future.await() DebugLog.log("FilePreloader.Processor", "[P${elem.priority}] Loading from $path: $data") @@ -65,19 +70,24 @@ internal class Processor(private val clazz: Class) { list.add(data) } - isWorking.set(false) + onEnd() } - } - internal suspend fun add(element: PreloadableUnit) { - preloadPriorityQueue.add(element) + ranCoroutines.add(job) + + job.invokeOnCompletion { + ranCoroutines.remove(job) + } } - /** - * Clear everything, all data loaded will be discarded. - */ - internal suspend fun clear() { - preloadPriorityQueue.clear() + internal fun clear() { + GlobalScope.launch { + ranCoroutines.forEach { + it.cancelAndJoin() + } + + ranCoroutines.clear() + } } } \ No newline at end of file diff --git a/lib/src/main/java/com/amaze/filepreloaderlibrary/SpecializedPreloader.kt b/lib/src/main/java/com/amaze/filepreloaderlibrary/SpecializedPreloader.kt index e02edcd..f419188 100644 --- a/lib/src/main/java/com/amaze/filepreloaderlibrary/SpecializedPreloader.kt +++ b/lib/src/main/java/com/amaze/filepreloaderlibrary/SpecializedPreloader.kt @@ -67,5 +67,5 @@ class SpecializedPreloader(private val clazz: Class, * It's usage is not recommended as the [Processor] already has a more efficient cleaning * algorithm (see [Processor.deletionQueue]). */ - internal suspend fun clear() = loader.clear() + internal fun clear() = loader.clear() } \ No newline at end of file diff --git a/lib/src/main/java/com/amaze/filepreloaderlibrary/datastructures/PreloadableUnit.kt b/lib/src/main/java/com/amaze/filepreloaderlibrary/datastructures/PreloadableUnit.kt index 6cf9e16..768e2fb 100644 --- a/lib/src/main/java/com/amaze/filepreloaderlibrary/datastructures/PreloadableUnit.kt +++ b/lib/src/main/java/com/amaze/filepreloaderlibrary/datastructures/PreloadableUnit.kt @@ -3,17 +3,4 @@ package com.amaze.filepreloaderlibrary.datastructures import com.amaze.filepreloaderlibrary.ProcessedUnit import kotlinx.coroutines.Deferred -internal data class PreloadableUnit(val future: Deferred>, val priority: Int, val hash: Int): Comparable> { - override fun compareTo(other: PreloadableUnit) = priority.compareTo(other.priority) - override fun hashCode() = hash - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as PreloadableUnit<*> - - if (hash != other.hash) return false - - return true - } -} \ No newline at end of file +internal data class PreloadableUnit(val future: Deferred>, val priority: Int) \ No newline at end of file diff --git a/lib/src/main/java/com/amaze/filepreloaderlibrary/datastructures/UniquePriorityBlockingQueue.kt b/lib/src/main/java/com/amaze/filepreloaderlibrary/datastructures/UniquePriorityBlockingQueue.kt deleted file mode 100644 index 7196670..0000000 --- a/lib/src/main/java/com/amaze/filepreloaderlibrary/datastructures/UniquePriorityBlockingQueue.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.amaze.filepreloaderlibrary.datastructures - -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import java.util.* -import java.util.concurrent.PriorityBlockingQueue -import kotlin.collections.HashSet - -internal class UniquePriorityBlockingQueue> { - private val preloadPriorityQueue: PriorityBlockingQueue = PriorityBlockingQueue() - private val containedCheck = Collections.synchronizedSet(HashSet()) - private val mutex = Mutex() - - suspend fun add(element: E) = mutex.withLock { - if(containedCheck.contains(element)) { - preloadPriorityQueue.forEach { - if(it == element) { - it.future.cancel() - } - } - } else { - containedCheck.add(element); - } - - preloadPriorityQueue.add(element) - } - - suspend fun poll(): E? = mutex.withLock { - val element = preloadPriorityQueue.poll() - containedCheck.remove(element) - return element - } - - suspend fun clear() = mutex.withLock { - preloadPriorityQueue.clear() - preloadPriorityQueue.clear() - } - - fun isNotEmpty() = containedCheck.isNotEmpty() -} \ No newline at end of file