diff --git a/app/src/main/java/com/quillstudios/pokegoalshelper/ml/MLResult.kt b/app/src/main/java/com/quillstudios/pokegoalshelper/ml/MLResult.kt new file mode 100644 index 0000000..b1890d2 --- /dev/null +++ b/app/src/main/java/com/quillstudios/pokegoalshelper/ml/MLResult.kt @@ -0,0 +1,83 @@ +package com.quillstudios.pokegoalshelper.ml + +/** + * Result wrapper for ML operations to standardize error handling + */ +sealed class MLResult +{ + data class Success(val data: T) : MLResult() + data class Error(val exception: Throwable, val message: String) : MLResult() + + /** + * Returns the data if successful, or null if error + */ + fun getOrNull(): T? = when (this) + { + is Success -> data + is Error -> null + } + + /** + * Returns the data if successful, or the default value if error + */ + fun getOrDefault(default: T): T = when (this) + { + is Success -> data + is Error -> default + } + + /** + * Execute block if successful + */ + inline fun onSuccess(block: (T) -> Unit): MLResult + { + if (this is Success) block(data) + return this + } + + /** + * Execute block if error + */ + inline fun onError(block: (Throwable, String) -> Unit): MLResult + { + if (this is Error) block(exception, message) + return this + } + + /** + * Transform the data if successful + */ + inline fun map(transform: (T) -> R): MLResult = when (this) + { + is Success -> try { Success(transform(data)) } + catch (e: Exception) { Error(e, "Transform failed: ${e.message}") } + is Error -> this + } +} + +/** + * Helper functions for creating results + */ +inline fun mlTry(operation: () -> T): MLResult +{ + return try + { + MLResult.Success(operation()) + } + catch (e: Exception) + { + MLResult.Error(e, "Operation failed: ${e.message}") + } +} + +inline fun mlTryWithMessage(message: String, operation: () -> T): MLResult +{ + return try + { + MLResult.Success(operation()) + } + catch (e: Exception) + { + MLResult.Error(e, "$message: ${e.message}") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/quillstudios/pokegoalshelper/ml/MatUtils.kt b/app/src/main/java/com/quillstudios/pokegoalshelper/ml/MatUtils.kt new file mode 100644 index 0000000..ca0c9c4 --- /dev/null +++ b/app/src/main/java/com/quillstudios/pokegoalshelper/ml/MatUtils.kt @@ -0,0 +1,61 @@ +package com.quillstudios.pokegoalshelper.ml + +import org.opencv.core.Mat + +/** + * Utility functions for safe Mat resource management + */ + +/** + * Execute a block with automatic Mat cleanup + */ +inline fun Mat.use(block: (Mat) -> T): T +{ + return try + { + block(this) + } + finally + { + this.release() + } +} + +/** + * Execute a block with multiple Mats and automatic cleanup + */ +inline fun useMats(vararg mats: Mat, block: () -> T): T +{ + return try + { + block() + } + finally + { + mats.forEach { it.release() } + } +} + +/** + * Create a new Mat and execute block with automatic cleanup + */ +inline fun withNewMat(block: (Mat) -> T): T +{ + return Mat().use(block) +} + +/** + * Create multiple new Mats and execute block with automatic cleanup + */ +inline fun withNewMats(count: Int, block: (List) -> T): T +{ + val mats = (1..count).map { Mat() } + return try + { + block(mats) + } + finally + { + mats.forEach { it.release() } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/quillstudios/pokegoalshelper/ml/YOLOInferenceEngine.kt b/app/src/main/java/com/quillstudios/pokegoalshelper/ml/YOLOInferenceEngine.kt index 80d3e3e..f60d271 100644 --- a/app/src/main/java/com/quillstudios/pokegoalshelper/ml/YOLOInferenceEngine.kt +++ b/app/src/main/java/com/quillstudios/pokegoalshelper/ml/YOLOInferenceEngine.kt @@ -92,6 +92,9 @@ class YOLOInferenceEngine(private val context: Context) : MLInferenceEngine private var ortEnvironment: OrtEnvironment? = null private var isInitialized = false + // Shared thread pool for preprocessing operations (prevents creating new pools per detection) + private val preprocessingExecutor = Executors.newFixedThreadPool(3) + private var confidenceThreshold = CONFIDENCE_THRESHOLD private var classFilter: String? = null @@ -247,8 +250,7 @@ class YOLOInferenceEngine(private val context: Context) : MLInferenceEngine try { - val detections = detectWithMat(input_mat) - detections + detectWithMat(input_mat) } finally { @@ -302,29 +304,28 @@ class YOLOInferenceEngine(private val context: Context) : MLInferenceEngine private suspend fun detectWithMultiplePreprocessing(inputMat: Mat): List { - val executor = Executors.newFixedThreadPool(3) val futures = mutableListOf>>() try { - // Submit different preprocessing methods + // Submit different preprocessing methods using shared executor if (ENABLE_ULTRALYTICS_PREPROCESSING) { - futures.add(executor.submit> { + futures.add(preprocessingExecutor.submit> { detectWithPreprocessing(inputMat, "ultralytics") }) } if (ENABLE_CONTRAST_ENHANCEMENT) { - futures.add(executor.submit> { + futures.add(preprocessingExecutor.submit> { detectWithPreprocessing(inputMat, "enhanced") }) } if (ENABLE_SHARPENING) { - futures.add(executor.submit> { + futures.add(preprocessingExecutor.submit> { detectWithPreprocessing(inputMat, "sharpened") }) } @@ -347,9 +348,10 @@ class YOLOInferenceEngine(private val context: Context) : MLInferenceEngine // Merge and filter detections return mergeAndFilterDetections(all_detections) } - finally + catch (e: Exception) { - executor.shutdownNow() + Log.e(TAG, "❌ Error in multiple preprocessing", e) + return emptyList() } } @@ -433,6 +435,14 @@ class YOLOInferenceEngine(private val context: Context) : MLInferenceEngine try { + // Shutdown thread pool with grace period + preprocessingExecutor.shutdown() + if (!preprocessingExecutor.awaitTermination(2, TimeUnit.SECONDS)) + { + Log.w(TAG, "⚠️ Thread pool shutdown timeout, forcing shutdown") + preprocessingExecutor.shutdownNow() + } + ortSession?.close() ortEnvironment?.close() }