mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-01-30 14:39:56 +08:00
Task chain exception catching
This commit is contained in:
parent
c775e8368e
commit
b07144278b
@ -50,7 +50,7 @@ fun readHMCLModpackManifest(f: File): Modpack {
|
||||
|
||||
object HMCLModpackManifest
|
||||
|
||||
class HMCLModpackInstallTask(profile: Profile, private val zipFile: File, private val modpack: Modpack, private val name: String): Task() {
|
||||
class HMCLModpackInstallTask(profile: Profile, private val zipFile: File, modpack: Modpack, private val name: String): Task() {
|
||||
private val dependency = profile.dependency
|
||||
private val repository = profile.repository
|
||||
override val dependencies = mutableListOf<Task>()
|
||||
@ -63,6 +63,8 @@ class HMCLModpackInstallTask(profile: Profile, private val zipFile: File, privat
|
||||
version = version.copy(jar = null)
|
||||
dependents += dependency.gameBuilder().name(name).gameVersion(modpack.gameVersion!!).buildAsync()
|
||||
dependencies += VersionJSONSaveTask(repository, version) // override the json created by buildAsync()
|
||||
|
||||
onDone += { event -> if (event.failed) repository.removeVersionFromDisk(name) }
|
||||
}
|
||||
|
||||
private val run = repository.getRunDirectory(name)
|
||||
@ -119,6 +121,11 @@ class HMCLModpackExportTask @JvmOverloads constructor(
|
||||
private val modpack: Modpack,
|
||||
private val output: File,
|
||||
override val id: String = ID): TaskResult<ZipEngine>() {
|
||||
|
||||
init {
|
||||
onDone += { event -> if (event.failed) output.delete() }
|
||||
}
|
||||
|
||||
override fun execute() {
|
||||
val blackList = ArrayList<String>(MODPACK_BLACK_LIST)
|
||||
blackList.add(version + ".jar")
|
||||
|
@ -94,26 +94,28 @@ interface AbstractWizardDisplayer : WizardDisplayer {
|
||||
|
||||
navigateTo(StackPane().apply { children += vbox }, Navigation.NavigationDirection.FINISH)
|
||||
|
||||
task.executor().let { executor ->
|
||||
task.with(org.jackhuang.hmcl.task.task(Scheduler.JAVAFX) {
|
||||
navigateTo(Label("Successful"), Navigation.NavigationDirection.FINISH)
|
||||
}).executor().apply {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
executor.taskListener = object : TaskListener {
|
||||
taskListener = object : TaskListener {
|
||||
override fun onReady(task: Task) {
|
||||
Platform.runLater { tasksBar.progressProperty().set(finishedTasks * 1.0 / executor.totTask.get()) }
|
||||
Platform.runLater { tasksBar.progressProperty().set(finishedTasks * 1.0 / totTask.get()) }
|
||||
}
|
||||
|
||||
override fun onFinished(task: Task) {
|
||||
Platform.runLater {
|
||||
label.text = task.title
|
||||
++finishedTasks
|
||||
tasksBar.progressProperty().set(finishedTasks * 1.0 / executor.totTask.get())
|
||||
tasksBar.progressProperty().set(finishedTasks * 1.0 / totTask.get())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailed(task: Task) {
|
||||
override fun onFailed(task: Task, throwable: Throwable) {
|
||||
Platform.runLater {
|
||||
label.text = task.title
|
||||
++finishedTasks
|
||||
tasksBar.progressProperty().set(finishedTasks * 1.0 / executor.totTask.get())
|
||||
tasksBar.progressProperty().set(finishedTasks * 1.0 / totTask.get())
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,11 +125,7 @@ interface AbstractWizardDisplayer : WizardDisplayer {
|
||||
|
||||
}
|
||||
|
||||
cancelQueue.add(executor)
|
||||
|
||||
executor.submit(org.jackhuang.hmcl.task.task(Scheduler.JAVAFX) {
|
||||
navigateTo(Label("Successful"), Navigation.NavigationDirection.FINISH)
|
||||
})
|
||||
cancelQueue.add(this)
|
||||
}.start()
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ class DefaultGameBuilder(val dependencyManager: DefaultDependencyManager): GameB
|
||||
GameLoggingDownloadTask(dependencyManager, version),
|
||||
GameDownloadTask(version),
|
||||
GameLibrariesTask(dependencyManager, version) // Game libraries will be downloaded for multiple times partly, this time is for vanilla libraries.
|
||||
) then VersionJSONSaveTask(dependencyManager.repository, version)
|
||||
) with VersionJSONSaveTask(dependencyManager.repository, version) // using [with] because download failure here are tolerant.
|
||||
|
||||
if (toolVersions.containsKey("forge"))
|
||||
result = result then libraryTaskHelper(gameVersion, "forge")
|
||||
|
@ -43,6 +43,8 @@ class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager,
|
||||
override val dependencies = mutableListOf<Task>()
|
||||
override val id = "version"
|
||||
|
||||
override val reliesOnDependencies = false
|
||||
|
||||
init {
|
||||
if (!forgeVersionList.loaded)
|
||||
dependents += forgeVersionList.refreshAsync(dependencyManager.downloadProvider).then {
|
||||
|
@ -42,6 +42,8 @@ class OptiFineInstallTask(private val dependencyManager: DefaultDependencyManage
|
||||
override val dependencies = mutableListOf<Task>()
|
||||
override val id = "version"
|
||||
|
||||
override val reliesOnDependencies = false
|
||||
|
||||
init {
|
||||
if (!optiFineVersionList.loaded)
|
||||
dependents += optiFineVersionList.refreshAsync(dependencyManager.downloadProvider) then {
|
||||
|
@ -213,6 +213,8 @@ class MMCModpackInstallTask(private val dependencyManager: DefaultDependencyMana
|
||||
init {
|
||||
check(!repository.hasVersion(name), { "Version $name already exists." })
|
||||
dependents += dependencyManager.gameBuilder().name(name).gameVersion(manifest.gameVersion).buildAsync()
|
||||
|
||||
onDone += { event -> if (event.failed) repository.removeVersionFromDisk(name) }
|
||||
}
|
||||
|
||||
private val run = repository.getRunDirectory(name)
|
||||
|
@ -26,7 +26,7 @@ import org.jackhuang.hmcl.util.AutoTypingMap
|
||||
* @param succ a callback that returns the task runs after [pred], [succ] will be executed asynchronously. You can do something that relies on the result of [pred].
|
||||
* @param reliant true if this task chain will be broken when task [pred] fails.
|
||||
*/
|
||||
internal class CoupleTask<P: Task>(pred: P, private val succ: (AutoTypingMap<String>) -> Task?, override val reliant: Boolean) : Task() {
|
||||
internal class CoupleTask<P: Task>(pred: P, private val succ: (AutoTypingMap<String>) -> Task?, override val reliesOnDependents: Boolean) : Task() {
|
||||
override val hidden: Boolean = true
|
||||
|
||||
override val dependents: Collection<Task> = listOf(pred)
|
||||
|
@ -19,6 +19,7 @@ package org.jackhuang.hmcl.task
|
||||
|
||||
/**
|
||||
* The tasks that provides a way to execute tasks parallelly.
|
||||
* Fails when some of [tasks] failed.
|
||||
*
|
||||
* @param tasks the tasks that can be executed parallelly.
|
||||
*/
|
||||
|
@ -44,11 +44,20 @@ abstract class Task {
|
||||
open val scheduler: Scheduler = Scheduler.DEFAULT
|
||||
|
||||
/**
|
||||
* True if requires all dependent tasks finishing successfully.
|
||||
* True if requires all [dependents] finishing successfully.
|
||||
*
|
||||
* **Note** if this field is set false, you are not supposed to invoke [run]
|
||||
* @defaultValue true
|
||||
*/
|
||||
open val reliant: Boolean = true
|
||||
open val reliesOnDependents: Boolean = true
|
||||
|
||||
/**
|
||||
* True if requires all [dependencies] finishing successfully.
|
||||
*
|
||||
* **Note** if this field is set false, you are not supposed to invoke [run]
|
||||
* @defaultValue false
|
||||
*/
|
||||
open val reliesOnDependencies: Boolean = true
|
||||
|
||||
open var title: String = this.javaClass.toString()
|
||||
|
||||
@ -102,7 +111,7 @@ abstract class Task {
|
||||
val onDone = EventManager<TaskEvent>()
|
||||
|
||||
/**
|
||||
* **Note** reliant does not work here, which is always treated as true here.
|
||||
* **Note** [reliesOnDependents] and [reliesOnDependencies] does not work here, which is always treated as true here.
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
fun run() {
|
||||
@ -120,12 +129,10 @@ abstract class Task {
|
||||
this.progressPropertyImpl.unbind()
|
||||
}
|
||||
|
||||
fun executor() = TaskExecutor().submit(this)
|
||||
fun executor(taskListener: TaskListener) = TaskExecutor().submit(this).apply { this.taskListener = taskListener }
|
||||
fun executor() = TaskExecutor(this)
|
||||
fun executor(taskListener: TaskListener) = TaskExecutor(this).apply { this.taskListener = taskListener }
|
||||
fun start() = executor().start()
|
||||
fun subscribe(subscriber: Task) = executor().apply {
|
||||
submit(subscriber).start()
|
||||
}
|
||||
fun subscribe(subscriber: Task) = TaskExecutor(with(subscriber)).apply { start() }
|
||||
|
||||
fun subscribe(scheduler: Scheduler = Scheduler.DEFAULT, closure: (AutoTypingMap<String>) -> Unit) = subscribe(task(scheduler, closure))
|
||||
|
||||
|
@ -27,46 +27,22 @@ import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.logging.Level
|
||||
|
||||
class TaskExecutor() {
|
||||
class TaskExecutor(private val task: Task) {
|
||||
var taskListener: TaskListener? = null
|
||||
|
||||
var canceled = false
|
||||
private set
|
||||
val totTask = AtomicInteger(0)
|
||||
val variables = AutoTypingMap<String>(mutableMapOf())
|
||||
private val taskQueue = ConcurrentLinkedQueue<Task>()
|
||||
var lastException: Exception? = null
|
||||
private val workerQueue = ConcurrentLinkedQueue<Future<*>>()
|
||||
|
||||
/**
|
||||
* Submit a task to subscription to run.
|
||||
* You can submit a task even when started this subscription.
|
||||
* Thread-safe function.
|
||||
*/
|
||||
fun submit(task: Task): TaskExecutor {
|
||||
taskQueue.add(task)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the subscription and run all registered tasks asynchronously.
|
||||
*/
|
||||
fun start() {
|
||||
workerQueue.add(Scheduler.NEW_THREAD.schedule {
|
||||
totTask.addAndGet(taskQueue.size)
|
||||
while (!taskQueue.isEmpty()) {
|
||||
if (canceled) break
|
||||
val task = taskQueue.poll()
|
||||
if (task != null) {
|
||||
val future = task.scheduler.schedule { executeTask(task) }
|
||||
try {
|
||||
future?.get()
|
||||
} catch (e: InterruptedException) {
|
||||
Thread.currentThread().interrupt()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (canceled || Thread.interrupted())
|
||||
if (!executeTasks(listOf(task)))
|
||||
taskListener?.onTerminate()
|
||||
})
|
||||
}
|
||||
@ -120,34 +96,40 @@ class TaskExecutor() {
|
||||
|
||||
var flag = false
|
||||
try {
|
||||
if (!doDependentsSucceeded && t.reliant || canceled)
|
||||
if (!doDependentsSucceeded && t.reliesOnDependents || canceled)
|
||||
throw SilentException()
|
||||
|
||||
t.variables = variables
|
||||
t.execute()
|
||||
if (t is TaskResult<*>)
|
||||
variables[t.id] = t.result
|
||||
|
||||
if (!executeTasks(t.dependencies) && t.reliesOnDependencies)
|
||||
throw IllegalStateException("Subtasks failed for ${t.title}")
|
||||
|
||||
flag = true
|
||||
if (!t.hidden)
|
||||
LOG.finer("Task finished: ${t.title}")
|
||||
executeTasks(t.dependencies)
|
||||
|
||||
if (!t.hidden) {
|
||||
t.onDone(TaskEvent(source = this, task = t, failed = false))
|
||||
taskListener?.onFinished(t)
|
||||
}
|
||||
} catch (e: InterruptedException) {
|
||||
if (!t.hidden) {
|
||||
lastException = e
|
||||
LOG.log(Level.FINE, "Task aborted: ${t.title}", e)
|
||||
t.onDone(TaskEvent(source = this, task = t, failed = true))
|
||||
taskListener?.onFailed(t)
|
||||
taskListener?.onFailed(t, e)
|
||||
}
|
||||
} catch (e: SilentException) {
|
||||
// nothing here
|
||||
} catch (e: Exception) {
|
||||
if (!t.hidden) {
|
||||
lastException = e
|
||||
LOG.log(Level.SEVERE, "Task failed: ${t.title}", e)
|
||||
t.onDone(TaskEvent(source = this, task = t, failed = true))
|
||||
taskListener?.onFailed(t)
|
||||
taskListener?.onFailed(t, e)
|
||||
}
|
||||
} finally {
|
||||
t.variables = null
|
||||
|
@ -22,6 +22,6 @@ import java.util.*
|
||||
interface TaskListener : EventListener {
|
||||
fun onReady(task: Task) {}
|
||||
fun onFinished(task: Task) {}
|
||||
fun onFailed(task: Task) {}
|
||||
fun onFailed(task: Task, throwable: Throwable) {}
|
||||
fun onTerminate() {}
|
||||
}
|
@ -141,7 +141,7 @@ class Test {
|
||||
override fun onFinished(task: Task) {
|
||||
}
|
||||
|
||||
override fun onFailed(task: Task) {
|
||||
override fun onFailed(task: Task, throwable: Throwable) {
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
|
Loading…
Reference in New Issue
Block a user