diff --git a/src/instructionset/Executor.kt b/src/instructionset/Executor.kt new file mode 100644 index 0000000..c3c7c65 --- /dev/null +++ b/src/instructionset/Executor.kt @@ -0,0 +1,98 @@ +package com.adclock.instructionset + +import com.adclock.model.ClockWall + +class Executor(val wall: ClockWall) { + + private val queue = mutableListOf() + private val stack = mutableListOf() + private var delayUntil: Long = 0 + + + val active: Boolean + get() = stack.isNotEmpty() + + val pendingTasks: Boolean + get() = queue.isNotEmpty() + + + fun queueTask(task: Task) { + queue += task + if (!active) + startNextQueuedTask() + } + + fun executeStep() { + if (!active) + throw IllegalStateException("Not active. Can not execute next step.") + + if (delayUntil > System.currentTimeMillis()) + return + + stack.last().run { + if (currentInstruction.apply(this@Executor, wall)) + current++ + + if (completed) + removeLatestTaskFromStack() + } + } + + private fun removeLatestTaskFromStack() { + stack.removeLast() + if (stack.isNotEmpty()) { + stack.last().current++ // Subcall is complete. + + // if previous task is complete remove from stack to + if (stack.last().completed) + return removeLatestTaskFromStack() + } + + // Check if pending task exists and start it + if (!active && pendingTasks) + startNextQueuedTask() + } + + private fun startNextQueuedTask() { + if (active) + throw IllegalStateException("Task already active. Can not run tasks in parallel.") + + if (!pendingTasks) + throw IllegalStateException("No task queued.") + + startSubTask(queue.removeFirst()) + } + + internal fun startSubTask(task: Task) { + if (task.instructions.isEmpty()) + throw IllegalArgumentException("The Task ${task.name} has no instructions.") + + if (stack.any { it.task.name == task.name }) + throw IllegalArgumentException("Loop detected. Can not start task ${task.name} because its already in program stack.") + + stack += StackEntry(task) + } + + /** + * Restarts the execution. + * There are two modes: + * - restart current task = restarts only current executed task + * - restart from begin = clear hole program stack and restarts very first task + * + * @param currentTask Boolean + */ + internal fun restartTask(currentTask: Boolean = false) { + if (!active) + throw IllegalStateException("No Task active. Can not restart current task because there is no one.") + + // Remove all Stack Entries. Restart first task + while (!currentTask && stack.size > 1) + stack.removeLast() + + stack.last().current = 0 + } + + internal fun delay(delay: Int) { + delayUntil = System.currentTimeMillis() + delay * 1000 + } +} \ No newline at end of file diff --git a/src/instructionset/StackEntry.kt b/src/instructionset/StackEntry.kt new file mode 100644 index 0000000..32eb6db --- /dev/null +++ b/src/instructionset/StackEntry.kt @@ -0,0 +1,12 @@ +package com.adclock.instructionset + +import com.adclock.instructionset.instructions.Instruction + +data class StackEntry(val task: Task, var current: Int = 0) { + + val completed: Boolean + get() = task.instructions.size == current + + val currentInstruction: Instruction + get() = task.instructions[current] +} \ No newline at end of file diff --git a/src/instructionset/Task.kt b/src/instructionset/Task.kt index f705ee6..ecb085c 100644 --- a/src/instructionset/Task.kt +++ b/src/instructionset/Task.kt @@ -1,47 +1,5 @@ package com.adclock.instructionset import com.adclock.instructionset.instructions.Instruction -import com.adclock.model.ClockWall -data class Task(val name: String, val instructions: MutableList = mutableListOf()) { - private var current: Int = 0 - var sleepUntil: Long = 0 - internal set - - fun isCompleted() = current >= instructions.size - - fun apply(wall: ClockWall, preview: Boolean = false) { - if (isCompleted()) - throw IllegalStateException("This task is already completed. Can't perform more steps.") - - if (sleepUntil > System.currentTimeMillis() && !preview) - return - - if (applyInstruction(wall, current(), preview)) - current++ - } - - fun current() = instructions[current] - - internal fun restart() { - current = 0 - } - - /** - * Apply instruction to wall. - * In normal Mode just execute once. - * In preview Mode execute until a significant step was made. - * - * @param wall ClockWall - * @param instruction Instruction - * @param preview Boolean - * @return Boolean - */ - private fun applyInstruction(wall: ClockWall, instruction: Instruction, preview: Boolean): Boolean { - var stepMade: Boolean - do { - stepMade = instruction.apply(this, wall) - } while (preview && !stepMade) - return stepMade - } -} \ No newline at end of file +data class Task(val name: String, val instructions: MutableList = mutableListOf()) \ No newline at end of file diff --git a/src/instructionset/instructions/Instruction.kt b/src/instructionset/instructions/Instruction.kt index 7839b8e..8485efa 100644 --- a/src/instructionset/instructions/Instruction.kt +++ b/src/instructionset/instructions/Instruction.kt @@ -1,8 +1,8 @@ package com.adclock.instructionset.instructions -import com.adclock.instructionset.Task +import com.adclock.instructionset.Executor import com.adclock.model.ClockWall interface Instruction { - fun apply(task: Task, wall: ClockWall): Boolean + fun apply(executor: Executor, wall: ClockWall): Boolean } diff --git a/src/instructionset/instructions/basic/RepeatInstruction.kt b/src/instructionset/instructions/basic/RepeatInstruction.kt index 969f5ef..a1ef58a 100644 --- a/src/instructionset/instructions/basic/RepeatInstruction.kt +++ b/src/instructionset/instructions/basic/RepeatInstruction.kt @@ -1,5 +1,6 @@ package com.adclock.instructionset.instructions.basic +import com.adclock.instructionset.Executor import com.adclock.instructionset.Task import com.adclock.instructionset.instructions.Instruction import com.adclock.instructionset.instructions.InstructionParser @@ -7,9 +8,9 @@ import com.adclock.model.ClockWall class RepeatInstruction : Instruction { - override fun apply(task: Task, wall: ClockWall): Boolean { - task.restart() - return false + override fun apply(executor: Executor, wall: ClockWall): Boolean { + executor.restartTask() + return false // because program pointer already set with restartTask() } diff --git a/src/instructionset/instructions/basic/SleepInstruction.kt b/src/instructionset/instructions/basic/SleepInstruction.kt index bcaf346..ef13b0f 100644 --- a/src/instructionset/instructions/basic/SleepInstruction.kt +++ b/src/instructionset/instructions/basic/SleepInstruction.kt @@ -1,14 +1,14 @@ package com.adclock.instructionset.instructions.basic -import com.adclock.instructionset.Task +import com.adclock.instructionset.Executor import com.adclock.instructionset.instructions.Instruction import com.adclock.instructionset.instructions.InstructionParser import com.adclock.model.ClockWall -class SleepInstruction(val sleep: Int) : Instruction { +class SleepInstruction(val sleepSeconds: Int) : Instruction { - override fun apply(task: Task, wall: ClockWall): Boolean { - task.sleepUntil = System.currentTimeMillis() + sleep * 1000 + override fun apply(executor: Executor, wall: ClockWall): Boolean { + executor.delay(sleepSeconds) return true } @@ -21,7 +21,7 @@ class SleepInstruction(val sleep: Int) : Instruction { return SleepInstruction(input.toInt()) } - override fun serialize(instruction: SleepInstruction) = instruction.sleep.toString() + override fun serialize(instruction: SleepInstruction) = instruction.sleepSeconds.toString() } } diff --git a/src/instructionset/instructions/wall/WallInstruction.kt b/src/instructionset/instructions/wall/WallInstruction.kt index 6850f12..e0d653a 100644 --- a/src/instructionset/instructions/wall/WallInstruction.kt +++ b/src/instructionset/instructions/wall/WallInstruction.kt @@ -1,5 +1,6 @@ package com.adclock.instructionset.instructions.wall +import com.adclock.instructionset.Executor import com.adclock.instructionset.Task import com.adclock.instructionset.instructions.Instruction import com.adclock.model.ClockWall @@ -7,7 +8,7 @@ import com.adclock.model.ClockWall interface WallInstruction : Instruction { fun apply(wall: ClockWall) - override fun apply(task: Task, wall: ClockWall): Boolean { + override fun apply(executor: Executor, wall: ClockWall): Boolean { apply(wall) return true // a wall instruction is always completed. It's a single instruction }