Coverage Summary for Class: QueueExecutionResult (kr.open.library.simple_ui.xml.ui.adapter.list.base.queue)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| QueueExecutionResult$Failure |
100%
(1/1)
|
|
100%
(2/2)
|
100%
(7/7)
|
| QueueExecutionResult$Success |
100%
(1/1)
|
|
100%
(1/1)
|
100%
(2/2)
|
| Total |
100%
(2/2)
|
|
100%
(3/3)
|
100%
(9/9)
|
package kr.open.library.simple_ui.xml.ui.adapter.list.base.queue
import kr.open.library.simple_ui.core.logcat.Logx
/**
* Shared operation queue engine for sequential processing.<br><br>
* 연산을 순차 처리하기 위한 공통 큐 엔진입니다.<br>
*
* @param schedule Executor hook that runs queue processing on the desired thread.<br><br>
* 큐 처리를 원하는 스레드에서 실행하기 위한 스케줄러입니다.<br>
*
* @param getName Function to extract an operation name for logging.<br><br>
* 로깅을 위해 연산 이름을 추출하는 함수입니다.<br>
*
* @param execute Function that runs an operation and calls complete with success flag.<br><br>
* 연산을 실행하고 성공 여부를 complete로 전달하는 함수입니다.<br>
*
* @param onComplete Callback invoked after each operation completes.<br><br>
* 각 연산 완료 후 호출되는 콜백입니다.<br>
*
* @param onError Error handler for unexpected failures during processing.<br><br>
* 처리 중 예외가 발생했을 때 호출되는 에러 핸들러입니다.<br>
*
* @param maxPending Maximum pending queue size; Int.MAX_VALUE means unlimited.<br><br>
* 최대 대기 큐 크기이며 Int.MAX_VALUE는 무제한입니다.<br>
*
* @param overflowPolicy Overflow handling policy when queue is full.<br><br>
* 큐가 가득 찼을 때 적용할 오버플로 정책입니다.<br>
*
* @param onDrop Callback invoked when an operation is dropped.<br><br>
* 연산이 드롭될 때 호출되는 콜백입니다.<br>
*/
internal sealed interface QueueExecutionResult {
/**
* Indicates successful operation completion.<br><br>
* 연산이 성공적으로 완료되었음을 나타냅니다.<br>
*/
data object Success : QueueExecutionResult
/**
* Indicates failed operation completion with optional cause.<br><br>
* 원인 정보와 함께 연산이 실패했음을 나타냅니다.<br>
*/
data class Failure(
val cause: Throwable?,
) : QueueExecutionResult
}
internal class OperationQueueProcessor<OPERATION>(
private val schedule: ((() -> Unit) -> Unit),
private val getName: (OPERATION) -> String,
private val execute: (OPERATION, (QueueExecutionResult) -> Unit) -> Unit,
private val onComplete: (OPERATION, QueueExecutionResult) -> Unit,
private val onError: (message: String, cause: Throwable?) -> Unit,
/**
* Maximum pending queue size; Int.MAX_VALUE means unlimited.<br><br>
* 최대 대기 큐 크기이며 Int.MAX_VALUE는 무제한입니다.<br>
*/
maxPending: Int = Int.MAX_VALUE,
/**
* Overflow handling policy when queue is full.<br><br>
* 큐가 가득 찼을 때 적용할 오버플로 정책입니다.<br>
*/
overflowPolicy: QueueOverflowPolicy = QueueOverflowPolicy.DROP_NEW,
/**
* Callback invoked when an operation is dropped.<br><br>
* 연산이 드롭될 때 호출되는 콜백입니다.<br>
*/
private val onDrop: ((OPERATION, QueueDropReason) -> Unit)? = null,
) {
/**
* Internal queue storing pending operations.<br><br>
* 대기 중인 연산을 보관하는 내부 큐입니다.<br>
*/
private val operationQueue = ArrayDeque<OPERATION>()
/**
* Flag indicating whether an operation is currently running.<br><br>
* 현재 연산 처리 중인지 나타내는 플래그입니다.<br>
*/
private var isProcessing = false
/**
* Lock object for queue synchronization.<br><br>
* 큐 동기화를 위한 락 객체입니다.<br>
*/
private val queueLock = Any()
/**
* Maximum pending queue size; Int.MAX_VALUE means unlimited.<br><br>
* 최대 대기 큐 크기이며 Int.MAX_VALUE는 무제한입니다.<br>
*/
private var maxPending: Int = maxPending
/**
* Overflow handling policy when queue is full.<br><br>
* 큐가 가득 찼을 때 적용할 오버플로 정책입니다.<br>
*/
private var overflowPolicy: QueueOverflowPolicy = overflowPolicy
/**
* Updates queue policy for overflow handling.<br><br>
* 큐 오버플로 처리를 위한 정책을 업데이트합니다.<br>
*/
fun setQueuePolicy(maxPending: Int, overflowPolicy: QueueOverflowPolicy) {
synchronized(queueLock) {
this.maxPending = if (maxPending <= 0) Int.MAX_VALUE else maxPending
this.overflowPolicy = overflowPolicy
}
}
/**
* Enqueues an operation and starts processing if idle.<br><br>
* 연산을 큐에 추가하고 유휴 상태면 처리를 시작합니다.<br>
*/
fun enqueue(operation: OPERATION) {
val dropped = mutableListOf<Pair<OPERATION, QueueDropReason>>()
val shouldStart: Boolean
synchronized(queueLock) {
val limit = maxPending
if (limit != Int.MAX_VALUE && operationQueue.size >= limit) {
when (overflowPolicy) {
QueueOverflowPolicy.DROP_NEW -> {
dropped.add(operation to QueueDropReason.QUEUE_FULL_DROP_NEW)
}
QueueOverflowPolicy.DROP_OLDEST -> {
if (operationQueue.isNotEmpty()) {
val removed = operationQueue.removeFirst()
dropped.add(removed to QueueDropReason.QUEUE_FULL_DROP_OLDEST)
}
operationQueue.add(operation)
}
QueueOverflowPolicy.CLEAR_AND_ENQUEUE -> {
while (operationQueue.isNotEmpty()) {
dropped.add(operationQueue.removeFirst() to QueueDropReason.QUEUE_FULL_CLEAR)
}
operationQueue.add(operation)
}
}
} else {
operationQueue.add(operation)
}
shouldStart = operationQueue.isNotEmpty() && !isProcessing
if (shouldStart) {
isProcessing = true
}
}
if (dropped.isNotEmpty()) {
dropped.forEach { (droppedOperation, reason) ->
onDrop?.invoke(droppedOperation, reason)
}
}
if (shouldStart) {
schedule { processNext() }
}
}
/**
* Clears queued operations and processes the provided one immediately.<br><br>
* 대기 큐를 비우고 전달된 연산을 즉시 처리합니다.<br>
*/
fun clearAndEnqueue(operation: OPERATION) {
val dropped = mutableListOf<OPERATION>()
val shouldStart: Boolean
synchronized(queueLock) {
while (operationQueue.isNotEmpty()) {
dropped.add(operationQueue.removeFirst())
}
operationQueue.add(operation)
shouldStart = !isProcessing && operationQueue.isNotEmpty()
if (shouldStart) {
isProcessing = true
}
}
if (dropped.isNotEmpty()) {
dropped.forEach { droppedOperation ->
onDrop?.invoke(droppedOperation, QueueDropReason.CLEARED_EXPLICIT)
}
}
if (shouldStart) {
schedule { processNext() }
}
}
/**
* Clears pending operations without enqueueing a new one.<br><br>
* 새 연산을 추가하지 않고 대기 큐를 비웁니다.<br>
*/
fun clearQueue() {
val dropped = mutableListOf<OPERATION>()
synchronized(queueLock) {
while (operationQueue.isNotEmpty()) {
dropped.add(operationQueue.removeFirst())
}
}
if (dropped.isEmpty()) {
return
}
dropped.forEach { droppedOperation ->
onDrop?.invoke(droppedOperation, QueueDropReason.CLEARED_BY_API)
}
}
/**
* Processes the next queued operation.<br><br>
* 큐의 다음 연산을 처리합니다.<br>
*/
private fun processNext() {
val operation =
synchronized(queueLock) {
if (operationQueue.isEmpty()) {
isProcessing = false
return
}
operationQueue.removeFirst()
}
try {
execute(operation) { result -> complete(operation, result) }
} catch (e: RuntimeException) {
reportError("Error executing operation: ${getName(operation)}", e)
complete(operation, QueueExecutionResult.Failure(e))
}
}
/**
* Completes the operation and schedules the next one if available.<br><br>
* 연산을 완료 처리하고 다음 연산이 있으면 이어서 처리합니다.<br>
*/
private fun complete(operation: OPERATION, result: QueueExecutionResult) {
try {
onComplete(operation, result)
} catch (e: RuntimeException) {
reportError("Error in operation completion: ${getName(operation)}", e)
}
val hasNext: Boolean
synchronized(queueLock) {
if (operationQueue.isEmpty()) {
isProcessing = false
hasNext = false
} else {
hasNext = true
}
}
if (hasNext) {
schedule { processNext() }
}
}
/**
* Reports errors through the injected handler safely.<br><br>
* 주입된 에러 핸들러로 안전하게 에러를 전달합니다.<br>
*/
private fun reportError(message: String, cause: Throwable?) {
try {
onError(message, cause)
} catch (e: RuntimeException) {
// Log and suppress errors from error reporting to avoid breaking the queue flow.
// 에러 보고 중 예외는 로깅 후 큐 흐름을 깨지 않도록 억제합니다.
Logx.w("Error reporting failed: ${e.message}")
}
}
}