Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

loading spinner #232

Merged
merged 7 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions DifferenceGenerator/src/main/kotlin/DifferenceGenerator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ class DifferenceGenerator(
encoder.stop()
encoder.release()

// check one last time, if the algorithm is still alive
algorithm.isAlive()

videoReferenceGrabber.stop()
videoCurrentGrabber.stop()
videoReferenceGrabber.release()
Expand Down
12 changes: 12 additions & 0 deletions DifferenceGenerator/src/main/kotlin/Exceptions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,15 @@ class DifferenceGeneratorMaskException(message: String, cause: Throwable? = null
return "MaskException(message=$message, cause=$cause)"
}
}

/**
* Exception thrown when the algorithm run is stopped from the outside.
*
* @param message the detail message.
* @param cause the cause.
*/
class DifferenceGeneratorStoppedException(message: String, cause: Throwable? = null) : DifferenceGeneratorException(message, cause) {
override fun toString(): String {
return "StoppedException(message=$message, cause=$cause)"
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package algorithms

import DifferenceGeneratorStoppedException
import wrappers.ResettableIterable

/**
Expand All @@ -18,6 +19,56 @@ enum class AlignmentElement {
PERFECT,
}

/**
* A singleton class that can be used to signal the cancellation of an algorithm run.
*
* An algorithm implementation can check if the algorithm is still supposed to run by calling [isAlive].
* As this is a singleton, it is expected that only one algorithm instance is running at a time.
* After an algorithm has been cancelled, or before a new instance is started, [reset] should be called
* to avoid false positives.
*
* Inspired by this article: https://www.baeldung.com/kotlin/singleton-classes
*/
class AlgorithmExecutionState private constructor() {
companion object {
private var instance: AlgorithmExecutionState? = null

fun getInstance() =
instance ?: synchronized(this) {
instance ?: AlgorithmExecutionState().also { instance = it }
}
}

private var isAlive = true

/**
* Resets the state of the singleton.
*
* This prepares for a new algorithm run.
*/
fun reset() {
isAlive = true
}

/**
* Signals a stop to the running algorithm.
*
* This does not immediately stop the algorithm, but the algorithm can check if it is still supposed to run.
* The algorithm checks if it is still alive regularly. So, it might take a short time until the algorithm
* actually stops.
*/
fun stop() {
isAlive = false
}

/**
* Checks if the algorithm running thread is still wanted by the user.
*/
fun isAlive(): Boolean {
return isAlive
}
}

/**
* An abstract class for alignment algorithms.
*
Expand Down Expand Up @@ -48,4 +99,15 @@ abstract class AlignmentAlgorithm<T> {
a: ResettableIterable<T>,
b: ResettableIterable<T>,
): Array<AlignmentElement>

/**
* Function that checks if the algorithm is still supposed to run.
*
* If not, it throws a [DifferenceGeneratorStoppedException].
*/
fun isAlive() {
if (!AlgorithmExecutionState.getInstance().isAlive()) {
throw DifferenceGeneratorStoppedException("The difference computation was stopped")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ class DivideAndConquerAligner<T>(private val algorithm: AlignmentAlgorithm<T>, p
hashes1 = markDuplicates(hasher.getHashes(a))
hashes2 = markDuplicates(hasher.getHashes(b))

// check if the algorithm got cancelled from the outside
isAlive()

// find unique exact matches between the two sequences
val equals = findMatches()

Expand Down Expand Up @@ -75,13 +78,18 @@ class DivideAndConquerAligner<T>(private val algorithm: AlignmentAlgorithm<T>, p
// advance iterators to skip the current match's positions
a.next()
b.next()

// regularly check if thread is still needed
isAlive()
}

// process alignment after last known match
val subArray1 = a.take(a.size() - lastMatchIndex1)
val subArray2 = b.take(b.size() - lastMatchIndex2)
alignment.addAll(getSubAlignment(subArray1, subArray2))

isAlive()

return alignment.toTypedArray()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
package ui.components.selectVideoScreen

import DifferenceGeneratorException
import DifferenceGeneratorStoppedException
import algorithms.AlgorithmExecutionState
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import logic.differenceGeneratorWrapper.DifferenceGeneratorWrapper
import models.AppState
import org.bytedeco.javacv.FFmpegFrameGrabber
import ui.components.general.AutoSizeText
import ui.components.general.ConfirmationPopup
import ui.components.general.ErrorDialog
import java.nio.file.Files
import java.nio.file.Path
Expand All @@ -27,31 +29,23 @@ import java.nio.file.attribute.BasicFileAttributes
* @param state [AppState] object containing the state of the application.
* @return [Unit]
*/

@Composable
fun RowScope.ComputeDifferencesButton(state: MutableState<AppState>) {
val scope = rememberCoroutineScope()
fun RowScope.ComputeDifferencesButton(
state: MutableState<AppState>,
scope: CoroutineScope,
showDialog: MutableState<Boolean>,
) {
val showConfirmDialog = remember { mutableStateOf(false) }
val errorDialogText = remember { mutableStateOf<String?>(null) }

ConfirmationPopup(
text = "The reference video is newer than the current video. Are you sure you want to continue?",
showDialog = showConfirmDialog.value,
onConfirm = {
calculateVideoDifferences(scope, state, errorDialogText)
showConfirmDialog.value = false
},
onCancel = {
showConfirmDialog.value = false
},
)

Button(
// fills all available space
modifier = Modifier.weight(0.9f).padding(8.dp).fillMaxSize(1f),
onClick = {
try {
if (referenceIsOlderThanCurrent(state)) {
calculateVideoDifferences(scope, state, errorDialogText)
calculateVideoDifferences(scope, state, errorDialogText, showDialog)
} else {
showConfirmDialog.value = true
}
Expand Down Expand Up @@ -84,8 +78,12 @@ private fun calculateVideoDifferences(
scope: CoroutineScope,
state: MutableState<AppState>,
errorDialogText: MutableState<String?>,
isLoading: MutableState<Boolean>,
) {
scope.launch(Dispatchers.IO) {
isLoading.value = true
AlgorithmExecutionState.getInstance().reset()

// generate the differences
lateinit var generator: DifferenceGeneratorWrapper
try {
Expand All @@ -101,12 +99,22 @@ private fun calculateVideoDifferences(

try {
generator.getDifferences(state.value.outputPath)
} catch (e: DifferenceGeneratorStoppedException) {
println("stopped by canceling...")
return@launch
} catch (e: Exception) {
errorDialogText.value = "An unexpected exception was thrown when running" +
"the difference computation:\n\n${e.message}"
return@launch
}

isLoading.value = false

// check for cancellation one last time before switching to the diff screen
if (!AlgorithmExecutionState.getInstance().isAlive()) {
return@launch
}

// set the sequence and screen
state.value = state.value.copy(sequenceObj = generator.getSequence(), screen = Screen.DiffScreen)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package ui.components.selectVideoScreen

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.material.AlertDialog
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun LoadingDialog(onCancel: () -> Unit = {}) {
AlertDialog(
zino212 marked this conversation as resolved.
Show resolved Hide resolved
modifier = Modifier.size(300.dp, 300.dp),
onDismissRequest = onCancel,
title = { Text(text = "Computing") },
text = {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxWidth(),
) {
CircularProgressIndicator(modifier = Modifier.size(100.dp))
}
},
confirmButton = {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize(),
) {
Button(onClick = onCancel) {
Text("Cancel")
}
}
},
)
}
16 changes: 14 additions & 2 deletions GUI/src/main/kotlin/ui/screens/SelectVideoScreen.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
package ui.screens

import algorithms.AlgorithmExecutionState
import androidx.compose.foundation.layout.*
import androidx.compose.material.TopAppBar
import androidx.compose.runtime.*
Expand All @@ -9,6 +11,7 @@ import ui.components.general.ProjectMenu
import ui.components.selectVideoScreen.AdvancedSettingsButton
import ui.components.selectVideoScreen.ComputeDifferencesButton
import ui.components.selectVideoScreen.FileSelectorButton
import ui.components.selectVideoScreen.LoadingDialog

/**
* A Composable function that creates a screen to select the videos to compare.
Expand All @@ -17,7 +20,9 @@ import ui.components.selectVideoScreen.FileSelectorButton
*/
@Composable
fun SelectVideoScreen(state: MutableState<AppState>) {
// column represents the whole screen
val scope = rememberCoroutineScope()
val showLoadingDialog = remember { mutableStateOf(false) }

Column(modifier = Modifier.fillMaxSize()) {
// menu bar
TopAppBar(
Expand Down Expand Up @@ -53,8 +58,15 @@ fun SelectVideoScreen(state: MutableState<AppState>) {
}
// screen switch buttons
Row(modifier = Modifier.weight(0.15f)) {
ComputeDifferencesButton(state)
ComputeDifferencesButton(state, scope, showLoadingDialog)
AdvancedSettingsButton(state)
}
}

if (showLoadingDialog.value) {
LoadingDialog(onCancel = {
AlgorithmExecutionState.getInstance().stop()
showLoadingDialog.value = false
})
}
}
Loading