diff --git a/api/src/main/kotlin/nebulosa/api/alignment/polar/tppa/TPPAJob.kt b/api/src/main/kotlin/nebulosa/api/alignment/polar/tppa/TPPAJob.kt index 89015adf2..629487c00 100644 --- a/api/src/main/kotlin/nebulosa/api/alignment/polar/tppa/TPPAJob.kt +++ b/api/src/main/kotlin/nebulosa/api/alignment/polar/tppa/TPPAJob.kt @@ -67,8 +67,6 @@ data class TPPAJob( get() = status.capture.savedPath init { - status.capture.exposureAmount = 0 - add(mountTrackTask) add(mountMoveTask) add(settleDelayTask) diff --git a/api/src/main/kotlin/nebulosa/api/cameras/CameraCaptureEvent.kt b/api/src/main/kotlin/nebulosa/api/cameras/CameraCaptureEvent.kt index 63e4fe529..c0a7510f0 100644 --- a/api/src/main/kotlin/nebulosa/api/cameras/CameraCaptureEvent.kt +++ b/api/src/main/kotlin/nebulosa/api/cameras/CameraCaptureEvent.kt @@ -91,6 +91,7 @@ data class CameraCaptureEvent( is CameraExposureElapsed -> handleCameraExposureElapsed(event) is CameraExposureFinished -> handleCameraExposureFinished(event) is CameraExposureStarted -> handleCameraExposureStarted(event) + is CameraExposureFailed -> handleTimedTaskEvent(event) } computeCaptureProgress() diff --git a/api/src/main/kotlin/nebulosa/api/cameras/CameraExposureFailed.kt b/api/src/main/kotlin/nebulosa/api/cameras/CameraExposureFailed.kt new file mode 100644 index 000000000..b9b9e1459 --- /dev/null +++ b/api/src/main/kotlin/nebulosa/api/cameras/CameraExposureFailed.kt @@ -0,0 +1,14 @@ +package nebulosa.api.cameras + +import nebulosa.job.manager.Job + +data class CameraExposureFailed( + override val job: Job, + override val task: CameraExposureTask, +) : CameraExposureEvent { + + override val elapsedTime = task.exposureTimeInMicroseconds + override val remainingTime = 0L + override val progress = 1.0 + override val savedPath = null +} diff --git a/api/src/main/kotlin/nebulosa/api/cameras/CameraExposureTask.kt b/api/src/main/kotlin/nebulosa/api/cameras/CameraExposureTask.kt index f7481e73f..2b6a741a1 100644 --- a/api/src/main/kotlin/nebulosa/api/cameras/CameraExposureTask.kt +++ b/api/src/main/kotlin/nebulosa/api/cameras/CameraExposureTask.kt @@ -6,7 +6,6 @@ import nebulosa.indi.device.camera.* import nebulosa.io.transferAndClose import nebulosa.job.manager.Job import nebulosa.job.manager.Task -import nebulosa.log.debug import nebulosa.log.loggerFor import nebulosa.util.concurrency.cancellation.CancellationSource import nebulosa.util.concurrency.latch.CountUpDownLatch @@ -39,9 +38,10 @@ data class CameraExposureTask( save(event) } is CameraExposureAborted, - is CameraExposureFailed, + is nebulosa.indi.device.camera.CameraExposureFailed, is CameraDetached -> { latch.reset() + job.accept(CameraExposureFailed(job, this)) } is CameraExposureProgressChanged -> { // "min" fix possible bug on SVBony exposure time? @@ -56,7 +56,7 @@ data class CameraExposureTask( override fun run() { if (camera.connected) { - LOG.debug { "Camera Exposure started. camera=$camera, request=$request" } + LOG.debug("Camera Exposure started. camera={}, request={}", camera, request) latch.countUp() @@ -79,7 +79,7 @@ data class CameraExposureTask( latch.await() - LOG.debug { "Camera Exposure finished. camera=$camera, request=$request" } + LOG.debug("Camera Exposure finished. camera={}, request={}", camera, request) } else { LOG.warn("camera not connected. camera={}, request={}", camera, request) } diff --git a/api/src/main/kotlin/nebulosa/api/sequencer/SequencerJob.kt b/api/src/main/kotlin/nebulosa/api/sequencer/SequencerJob.kt index a3ab7c493..1e8fd6109 100644 --- a/api/src/main/kotlin/nebulosa/api/sequencer/SequencerJob.kt +++ b/api/src/main/kotlin/nebulosa/api/sequencer/SequencerJob.kt @@ -227,6 +227,10 @@ data class SequencerJob( is CameraExposureEvent -> { status.capture.handleCameraExposureEvent(event) + if (event is CameraExposureFailed) { + return stop() + } + if (event is CameraExposureStarted) { captureStartElapsedTime = status.elapsedTime } else { diff --git a/api/src/main/kotlin/nebulosa/api/wizard/flat/FlatWizardJob.kt b/api/src/main/kotlin/nebulosa/api/wizard/flat/FlatWizardJob.kt index 956b545fe..dd2ee0e53 100644 --- a/api/src/main/kotlin/nebulosa/api/wizard/flat/FlatWizardJob.kt +++ b/api/src/main/kotlin/nebulosa/api/wizard/flat/FlatWizardJob.kt @@ -1,10 +1,6 @@ package nebulosa.api.wizard.flat -import nebulosa.api.cameras.AutoSubFolderMode -import nebulosa.api.cameras.CameraEventAware -import nebulosa.api.cameras.CameraExposureEvent -import nebulosa.api.cameras.CameraExposureFinished -import nebulosa.api.cameras.CameraExposureTask +import nebulosa.api.cameras.* import nebulosa.api.message.MessageEvent import nebulosa.fits.fits import nebulosa.image.Image @@ -14,11 +10,10 @@ import nebulosa.indi.device.camera.CameraEvent import nebulosa.indi.device.camera.FrameType import nebulosa.job.manager.AbstractJob import nebulosa.job.manager.Task -import nebulosa.log.debug import nebulosa.log.loggerFor +import nebulosa.util.concurrency.latch.CountUpDownLatch import java.nio.file.Path import java.time.Duration -import kotlin.use data class FlatWizardJob( @JvmField val flatWizardExecutor: FlatWizardExecutor, @@ -34,20 +29,22 @@ data class FlatWizardJob( @JvmField val status = FlatWizardEvent(camera) - @Volatile private var cameraRequest = request.capture.copy( - exposureTime = Duration.ZERO, frameType = FrameType.FLAT, - autoSave = false, autoSubFolderMode = AutoSubFolderMode.OFF, + @Volatile private var cameraExposureTask = CameraExposureTask( + this, camera, request.capture.copy( + exposureTime = Duration.ofNanos((exposureMin + exposureMax) / 2), + frameType = FrameType.FLAT, + autoSave = false, autoSubFolderMode = AutoSubFolderMode.OFF, + ) ) - @Volatile private var cameraExposureTask = CameraExposureTask(this, camera, cameraRequest) + private val waitToComputeOptimalExposureTime = CountUpDownLatch() inline val savedPath get() = status.capture.savedPath init { - status.capture.exposureAmount = 0 - add(cameraExposureTask) + status.exposureTime = cameraExposureTask.request.exposureTime.toNanos() / 1000L } override fun handleCameraEvent(event: CameraEvent) { @@ -62,8 +59,14 @@ data class FlatWizardJob( if (event is CameraExposureFinished) { status.capture.send() - status.state = FlatWizardState.CAPTURED - computeOptimalExposureTime(event.savedPath) + if (!computeOptimalExposureTime(event.savedPath)) { + val exposureTimeInNanos = (exposureMax + exposureMin) / 2L + val request = cameraExposureTask.request.copy(exposureTime = Duration.ofNanos(exposureTimeInNanos)) + status.exposureTime = exposureTimeInNanos / 1000L + add(CameraExposureTask(this, camera, request).also { cameraExposureTask = it }) + } + + waitToComputeOptimalExposureTime.reset() } status.send() @@ -71,48 +74,59 @@ data class FlatWizardJob( } } - private fun computeOptimalExposureTime(savedPath: Path) { + private fun computeOptimalExposureTime(savedPath: Path): Boolean { val image = savedPath.fits().use { Image.open(it, false) } val statistics = STATISTICS.compute(image) - LOG.debug { "flat frame computed. statistics=$statistics" } + LOG.debug("flat frame computed. statistics={}", statistics) if (statistics.mean in meanRange) { - LOG.debug { "found an optimal exposure time. exposureTime=${status.exposureTime}, path=$savedPath" } - status.state = FlatWizardState.IDLE - return stop() + LOG.debug("found an optimal exposure time. exposureTime={}, path={}", status.exposureTime, savedPath) + status.state = FlatWizardState.CAPTURED + status.capture.state = CameraCaptureState.IDLE + return true } else if (statistics.mean < meanRange.start) { - exposureMin = status.exposureTime - LOG.debug { "captured frame is below mean range. exposureTime=${status.exposureTime}, path=$savedPath" } + exposureMin = cameraExposureTask.request.exposureTime.toNanos() + LOG.debug("captured frame is below mean range. exposureTime={}, path={}", exposureMin, savedPath) } else { - exposureMax = status.exposureTime - LOG.debug { "captured frame is above mean range. exposureTime=${status.exposureTime}, path=$savedPath" } + exposureMax = cameraExposureTask.request.exposureTime.toNanos() + LOG.debug("captured frame is above mean range. exposureTime={}, path={}", exposureMax, savedPath) } val delta = exposureMax - exposureMin // 10ms - if (delta < 10000000) { + if (delta < MIN_DELTA_TIME) { LOG.warn("Failed to find an optimal exposure time. exposureMin={}, exposureMax={}", exposureMin, exposureMax) status.state = FlatWizardState.FAILED - return stop() + status.capture.state = CameraCaptureState.IDLE + return true } + + return false + } + + override fun beforeStart() { + LOG.debug("Flat Wizard started. camera={}, request={}", camera, request) + + status.state = FlatWizardState.EXPOSURING + status.send() } override fun beforeTask(task: Task) { - if (task === cameraExposureTask) { - val exposureTimeInNanos = (exposureMax + exposureMin) / 2L - cameraRequest = cameraRequest.copy(exposureTime = Duration.ofNanos(exposureTimeInNanos)) - status.exposureTime = exposureTimeInNanos / 1000L - } + waitToComputeOptimalExposureTime.countUp() } - override fun beforeStart() { - LOG.debug { "Flat Wizard started. camera=$camera, request=$request" } + override fun afterTask(task: Task, exception: Throwable?): Boolean { + if (exception == null) { + waitToComputeOptimalExposureTime.await() + } + + return super.afterTask(task, exception) } override fun afterFinish() { - LOG.debug { "Flat Wizard finished. camera=$camera, request=$request, exposureTime=${status.exposureTime} µs" } + LOG.debug("Flat Wizard finished. camera={}, request={}, exposureTime={} µs", camera, request, status.exposureTime) } @Suppress("NOTHING_TO_INLINE") @@ -122,6 +136,8 @@ data class FlatWizardJob( companion object { + private const val MIN_DELTA_TIME = 10000000 // 10ms + @JvmStatic private val LOG = loggerFor() @JvmStatic private val STATISTICS = Statistics(noMedian = true, noDeviation = true) } diff --git a/desktop/src/app/flat-wizard/flat-wizard.component.ts b/desktop/src/app/flat-wizard/flat-wizard.component.ts index 9be7b2260..0c822aadf 100644 --- a/desktop/src/app/flat-wizard/flat-wizard.component.ts +++ b/desktop/src/app/flat-wizard/flat-wizard.component.ts @@ -57,14 +57,16 @@ export class FlatWizardComponent implements AfterViewInit, OnDestroy, Tickable { electronService.on('FLAT_WIZARD.ELAPSED', (event) => { ngZone.run(() => { - if (event.state === 'EXPOSURING' && event.capture && event.capture.camera.id === this.camera?.id) { + if (event.state === 'EXPOSURING' && event.capture && event.camera.id === this.camera?.id) { this.running = true this.cameraExposure.handleCameraCaptureEvent(event.capture, true) } else if (event.state === 'CAPTURED') { this.running = false + this.cameraExposure.reset() this.angularService.message('Flat frame captured') } else if (event.state === 'FAILED') { this.running = false + this.cameraExposure.reset() this.angularService.message('Failed to find an optimal exposure time from given parameters', 'error') } }) diff --git a/desktop/src/shared/types/flat-wizard.types.ts b/desktop/src/shared/types/flat-wizard.types.ts index d7565dfe5..dbb0dd14b 100644 --- a/desktop/src/shared/types/flat-wizard.types.ts +++ b/desktop/src/shared/types/flat-wizard.types.ts @@ -1,4 +1,4 @@ -import { cameraStartCaptureWithDefault, DEFAULT_CAMERA_START_CAPTURE, type CameraCaptureEvent, type CameraStartCapture } from './camera.types' +import { cameraStartCaptureWithDefault, DEFAULT_CAMERA_START_CAPTURE, type Camera, type CameraCaptureEvent, type CameraStartCapture } from './camera.types' export type FlatWizardState = 'EXPOSURING' | 'CAPTURED' | 'FAILED' @@ -15,9 +15,10 @@ export interface FlatWizardRequest { } export interface FlatWizardEvent { + camera: Camera state: FlatWizardState exposureTime: number - capture?: CameraCaptureEvent + capture?: Omit savedPath?: string } diff --git a/desktop/src/shared/types/sequencer.types.ts b/desktop/src/shared/types/sequencer.types.ts index c7fffed48..207ff3f81 100644 --- a/desktop/src/shared/types/sequencer.types.ts +++ b/desktop/src/shared/types/sequencer.types.ts @@ -61,11 +61,12 @@ export interface SequencerPlan { } export interface SequencerEvent extends MessageEvent { + camera: Camera id: number elapsedTime: number remainingTime: number progress: number - capture?: CameraCaptureEvent + capture?: Omit state: SequencerState } diff --git a/nebulosa-job-manager/src/main/kotlin/nebulosa/job/manager/AbstractJob.kt b/nebulosa-job-manager/src/main/kotlin/nebulosa/job/manager/AbstractJob.kt index 3b57dcd83..93aab4cd4 100644 --- a/nebulosa-job-manager/src/main/kotlin/nebulosa/job/manager/AbstractJob.kt +++ b/nebulosa-job-manager/src/main/kotlin/nebulosa/job/manager/AbstractJob.kt @@ -5,21 +5,21 @@ import nebulosa.util.concurrency.cancellation.CancellationListener import nebulosa.util.concurrency.cancellation.CancellationSource import nebulosa.util.concurrency.latch.CountUpDownLatch import nebulosa.util.concurrency.latch.PauseListener +import java.util.* import java.util.concurrent.CompletableFuture import java.util.concurrent.Executor import java.util.concurrent.ForkJoinPool import java.util.concurrent.atomic.AtomicBoolean -import java.util.concurrent.atomic.AtomicReference +import java.util.concurrent.atomic.AtomicInteger abstract class AbstractJob : JobTask, CancellationListener, PauseListener { - @Volatile private var head: TaskNode? = null - @Volatile private var tail: TaskNode? = null + private val tasks = Collections.synchronizedList(ArrayList(128)) private val running = AtomicBoolean() private val cancelled = AtomicBoolean() private val pauseLatch = CountUpDownLatch() - private val current = AtomicReference() + private val current = AtomicInteger(-1) @Volatile final override var loopCount = 0 private set @@ -27,8 +27,8 @@ abstract class AbstractJob : JobTask, CancellationListener, PauseListener { @Volatile final override var taskCount = 0 private set - final override var size = 0 - private set + override val size + get() = tasks.size override val isRunning get() = running.get() @@ -40,7 +40,7 @@ abstract class AbstractJob : JobTask, CancellationListener, PauseListener { get() = !pauseLatch.get() override val currentTask - get() = current.get()?.item + get() = tasks.getOrNull(current.get()) protected open fun beforeStart() = Unit @@ -76,66 +76,74 @@ abstract class AbstractJob : JobTask, CancellationListener, PauseListener { } override fun runTask(task: Task, prev: Task?): TaskExecutionState { - checkIfPaused(task) + return try { + checkIfPaused(task) - return if (isCancelled) { - TaskExecutionState.BREAK - } else if (canRun(prev, task)) { - beforeTask(task) + if (isCancelled) { + TaskExecutionState.BREAK + } else if (canRun(prev, task)) { + beforeTask(task) - var exception: Throwable? = null + var exception: Throwable? = null - try { - taskCount++ - task.run() - } catch (e: Throwable) { - LOG.error("task execution failed", e) - exception = e - } + try { + taskCount++ + task.run() + } catch (e: Throwable) { + LOG.error("task execution failed", e) + exception = e + } - if (!afterTask(task, exception) || isCancelled) { - TaskExecutionState.BREAK + if (!afterTask(task, exception) || isCancelled) { + TaskExecutionState.BREAK + } else { + checkIfPaused(task) + TaskExecutionState.OK + } } else { - checkIfPaused(task) - TaskExecutionState.OK + TaskExecutionState.CONTINUE } - } else { + } catch (e: Throwable) { + LOG.error("task execution failed", e) TaskExecutionState.CONTINUE } } final override fun run() { - if (current.compareAndSet(null, requireNotNull(head))) { + if (current.compareAndSet(-1, 0)) { running.set(true) beforeStart() - var prev: TaskNode? = null + var prev: Task? = null - while (current.get() != null && isRunning && !isCancelled) { - val (task, _, next) = current.get() - val state = runTask(task, prev?.item) + while (isRunning && !isCancelled) { + val index = current.get() + val task = tasks.getOrNull(index) ?: break + val state = runTask(task, prev) if (state == TaskExecutionState.OK) { - prev = current.get() + prev = task } else if (state == TaskExecutionState.BREAK) { break } + val next = tasks.getOrNull(index + 1) + if (next != null) { - current.set(next) + current.set(index + 1) } else if (isLoop()) { loopCount++ - current.set(head) + current.set(0) } else { - current.set(null) + break } } afterFinish() running.set(false) - current.set(null) + current.set(-1) } } @@ -151,98 +159,37 @@ abstract class AbstractJob : JobTask, CancellationListener, PauseListener { } } - final override fun iterator(): MutableIterator { - return NextIterator() - } + final override fun iterator() = object : MutableIterator { - final override fun addFirst(e: Task) { - val h = head - val node = TaskNode(e, null, h) + @Volatile private var index = 0 - if (h == null) { - tail = node - } else { - h.prev = node + override fun hasNext(): Boolean { + return index < size } - head = node - size++ - } - - final override fun addLast(e: Task) { - val t = tail - val node = TaskNode(e, t, null) - - if (t == null) { - head = node - } else { - t.next = node + override fun next(): Task { + return tasks.getOrNull(index++) ?: throw NoSuchElementException() } - tail = node - size++ - } - - final override fun offerFirst(e: Task): Boolean { - addFirst(e) - return true - } - - final override fun offerLast(e: Task): Boolean { - return add(e) - } - - final override fun removeFirst(): Task { - return removeFirst(head ?: throw NoSuchElementException()) - } - - final override fun removeLast(): Task { - return removeLast(tail ?: throw NoSuchElementException()) - } - - final override fun pollFirst(): Task? { - return removeFirst(head ?: return null) - } - - final override fun pollLast(): Task? { - return removeLast(tail ?: return null) - } - - final override fun getFirst(): Task { - return peekFirst() ?: throw NoSuchElementException() - } - - final override fun getLast(): Task { - return peekLast() ?: throw NoSuchElementException() + override fun remove() { + TODO("Not yet implemented") + } } - final override fun peekFirst(): Task? { - return head?.item + fun addFirst(task: Task) { + tasks.add(0, task) } - final override fun peekLast(): Task? { - return tail?.item + fun addLast(task: Task) { + tasks.add(task) } - final override fun removeFirstOccurrence(o: Any?): Boolean { - return o is Task && remove(o) + fun removeFirst(): Task? { + return tasks.removeFirstOrNull() } - final override fun removeLastOccurrence(o: Any?): Boolean { - if (o == null || o !is Task) return false - - var node = tail - - while (node != null) { - if (node.item == o) { - remove(node) - return true - } - - node = node.prev - } - - return false + fun removeLast(): Task? { + return tasks.removeLastOrNull() } final override fun add(element: Task): Boolean { @@ -250,244 +197,36 @@ abstract class AbstractJob : JobTask, CancellationListener, PauseListener { return true } - final override fun offer(e: Task): Boolean { - return add(e) - } - - final override fun remove(): Task { - return removeFirst() - } - - final override fun remove(element: Task?): Boolean { - if (element == null) return false - - var node = head - - while (node != null) { - if (node.item == element) { - remove(node) - return true - } - - node = node.next - } - - return false - } - - final override fun poll(): Task? { - return head?.let(::removeFirst) - } - - final override fun element(): Task { - return first - } - - final override fun peek(): Task? { - return peekFirst() - } - - final override fun addAll(elements: Collection): Boolean { - var added = false - - for (item in elements) { - added = added || (item != null && add(item)) - } - - return added - } - - final override fun push(e: Task) { - addFirst(e) - } - - final override fun pop(): Task { - return removeFirst() - } - - final override fun contains(element: Task?): Boolean { - if (element == null) return false - - var node = head - - while (node != null) { - if (node.item == element) { - return true - } - - node = node.next - } - - return false - } - - final override fun descendingIterator(): Iterator { - return PrevIterator() + final override fun contains(element: Task): Boolean { + return element in tasks } final override fun clear() { - var node = head - - while (node != null) { - val next = node.next - node.prev = null - node.next = null - node = next - } - - head = null - tail = null - size = 0 + tasks.clear() } - final override fun removeAll(elements: Collection): Boolean { - var node = head - var removed = false - - while (node != null) { - if (node.item in elements) { - remove(node) - removed = true - } - - node = node.next - } - - return removed + final override fun removeAll(elements: Collection): Boolean { + return tasks.removeAll(elements) } - final override fun retainAll(elements: Collection): Boolean { - var node = head - var removed = false - - while (node != null) { - if (node.item !in elements) { - remove(node) - removed = true - } - - node = node.next - } - - return removed + final override fun retainAll(elements: Collection): Boolean { + return tasks.retainAll(elements) } final override fun isEmpty(): Boolean { return size == 0 } - final override fun containsAll(elements: Collection): Boolean { - return elements.all { it in this } + final override fun containsAll(elements: Collection): Boolean { + return tasks.containsAll(elements) } - private fun removeFirst(node: TaskNode): Task { - val item = node.item - val next = node.next - - node.next = null - head = next - - if (next == null) { - tail = null - } else { - next.prev = null - } - - size-- - - return item - } - - private fun removeLast(node: TaskNode): Task { - val item = node.item - val prev = node.prev - - node.prev = null - tail = prev - - if (prev == null) { - head = null - } else { - prev.next = null - } - - size-- - - return item + override fun addAll(elements: Collection): Boolean { + return tasks.addAll(elements) } - private fun remove(node: TaskNode): Task { - val item = node.item - val prev = node.prev - val next = node.next - - if (prev == null) { - head = next - } else { - prev.next = next - node.prev = null - } - - if (next == null) { - tail = prev - } else { - next.prev = prev - node.next = null - } - - size-- - - return item - } - - private inner class NextIterator : MutableIterator { - - @Volatile private var next: TaskNode? = null - - override fun remove() { - TODO("Not yet implemented") - } - - override fun hasNext(): Boolean { - return next != null || head != null - } - - override fun next(): Task { - val n = next - - if (n == null) { - next = head ?: throw NoSuchElementException() - return next!!.item - } else { - next = n.next - return n.item - } - } - } - - private inner class PrevIterator : MutableIterator { - - @Volatile private var next: TaskNode? = null - - override fun remove() { - TODO("Not yet implemented") - } - - override fun hasNext(): Boolean { - return next != null || tail != null - } - - override fun next(): Task { - val n = next - - if (n == null) { - next = tail ?: throw NoSuchElementException() - return next!!.item - } else { - next = n.prev - return n.item - } - } + override fun remove(element: Task): Boolean { + return tasks.remove(element) } companion object { diff --git a/nebulosa-job-manager/src/main/kotlin/nebulosa/job/manager/Job.kt b/nebulosa-job-manager/src/main/kotlin/nebulosa/job/manager/Job.kt index 0ad3f4351..0f54aff9f 100644 --- a/nebulosa-job-manager/src/main/kotlin/nebulosa/job/manager/Job.kt +++ b/nebulosa-job-manager/src/main/kotlin/nebulosa/job/manager/Job.kt @@ -2,10 +2,9 @@ package nebulosa.job.manager import nebulosa.util.Stoppable import nebulosa.util.concurrency.latch.Pauseable -import java.util.Deque import java.util.function.Consumer -interface Job : Deque, Runnable, Pauseable, Stoppable, Consumer { +interface Job : MutableCollection, Runnable, Pauseable, Stoppable, Consumer { val loopCount: Int diff --git a/nebulosa-job-manager/src/main/kotlin/nebulosa/job/manager/TaskNode.kt b/nebulosa-job-manager/src/main/kotlin/nebulosa/job/manager/TaskNode.kt deleted file mode 100644 index 9e70a08c2..000000000 --- a/nebulosa-job-manager/src/main/kotlin/nebulosa/job/manager/TaskNode.kt +++ /dev/null @@ -1,7 +0,0 @@ -package nebulosa.job.manager - -internal data class TaskNode( - @JvmField val item: Task, - @JvmField var prev: TaskNode? = null, - @JvmField var next: TaskNode? = null, -)