diff --git a/compiler/res/prog8lib/coroutines.p8 b/compiler/res/prog8lib/coroutines.p8 index 03aed6442..3e87aa56b 100644 --- a/compiler/res/prog8lib/coroutines.p8 +++ b/compiler/res/prog8lib/coroutines.p8 @@ -5,27 +5,28 @@ ; Uses cpu stack return address juggling to cycle between different tasks. ; ; Features: -; - can have a dynamic number of tasks (max 64), when tasks end they're automaticall removed from the task list. -; - you can add new tasks, even from IRQ handlers, while the rest is already running. +; - can have a dynamic number of active tasks (max 64), when a task ends it is automatically removed from the task list. +; - you can add new tasks, while the rest is already running. Just not yet from inside IRQ handlers! ; - tasks are regular subroutines but have to call yield() to pass control to the next task (round-robin) ; - yield() returns the registered userdata value for that task, so a single subroutine could be used as multiple tasks on different userdata ; BUT!! in that case, the subroutine cannot have any variables of its own that keep state, because they're shared across the multiple tasks ; - you can kill a task (if you know it's id...) ; - when all tasks are finished the run() call will also return. ; - tasks can't push anything on the cpu stack before calling yield() - that will cause chaos. +; - this library is not (yet) usable from IRQ handlers. Don't do it. It will end badly. (can't manipulate the task list simultaneously) ; ; Difference from IRQ handlers: ; - you can have many tasks instead of only 2 (main program + irq handler) ; - it's not tied to any IRQ setup, and will run as fast as the tasks themselves allow ; - tasks fully control the switch to the next task; there is no preemptive switching +; - tasks will need to save/restore their own state, maybe by useing the userdata (pointer?) and/or task id for that. ; ; USAGE: ; - call add(taskaddress) to add a new task. It returns the task id. ; - call run() to start executing all tasks until none are left. ; - in tasks: call yield() to pass control to the next task. Use the returned userdata value to do different things. -; - in tasks: if you need that userdata value immediately, simply start the task with a yield() call. ; - call current() to get the current task id. -; - call kill(tasknumber) to kill a task by id. +; - call kill(taskid) to kill a task by id. ; - call killall() to kill all tasks. ; - IMPORTANT: if you add the same subroutine multiple times, IT CANNOT DEPEND ON ANY LOCAL VARIABLES OR R0-R15 TO KEEP STATE. NOT EVEN REPEAT LOOP COUNTERS. ; Those are all shared in the different tasks! You HAVE to use a mechanism around the userdata value (pointer?) to keep separate state elsewhere! @@ -41,18 +42,15 @@ coroutines { ; find the next empty slot in the tasklist and stick it there ; returns the task id of the new task, or 255 if there was no space for more tasks. 0 is a valid task id! ; also returns the success in the Carry flag (carry set=success, carry clear = task was not added) - sys.irqsafe_set_irqd() for cx16.r0L in 0 to len(tasklist)-1 { if tasklist[cx16.r0L] == 0 { tasklist[cx16.r0L] = taskaddress userdatas[cx16.r0L] = userdata returnaddresses[cx16.r0L] = 0 - sys.irqsafe_clear_irqd() sys.set_carry() return cx16.r0L } } - sys.irqsafe_clear_irqd() ; no space for new task sys.clear_carry() return 255 @@ -60,11 +58,9 @@ coroutines { sub killall() { ; kill all existing tasks - sys.irqsafe_set_irqd() for cx16.r0L in 0 to len(tasklist)-1 { kill(cx16.r0L) } - sys.irqsafe_clear_irqd() } sub run() { @@ -104,7 +100,6 @@ resume_with_next_task: sub next_task() -> bool { ; search through the task list for the next active task - sys.irqsafe_set_irqd() repeat len(tasklist) { active_task++ if active_task==len(returnaddresses) @@ -112,18 +107,16 @@ resume_with_next_task: task_start = tasklist[active_task] if task_start!=0 { task_continue = returnaddresses[active_task] - sys.irqsafe_clear_irqd() return true } } - sys.irqsafe_clear_irqd() return false ; no task } } - sub kill(ubyte tasknum) { - tasklist[tasknum] = 0 - returnaddresses[tasknum] = 0 + sub kill(ubyte taskid) { + tasklist[taskid] = 0 + returnaddresses[taskid] = 0 } sub current() -> ubyte { diff --git a/compiler/res/prog8lib/pet32/textio.p8 b/compiler/res/prog8lib/pet32/textio.p8 index c3ddd17b3..6b497c212 100644 --- a/compiler/res/prog8lib/pet32/textio.p8 +++ b/compiler/res/prog8lib/pet32/textio.p8 @@ -225,13 +225,13 @@ asmsub plot (ubyte col @ Y, ubyte row @ X) { jsr home cpy #0 beq + -- lda #17 +- lda #29 jsr chrout dey bne - + cpx #0 beq + -- lda #29 +- lda #17 jsr chrout dex bne - diff --git a/examples/multitasking.p8 b/examples/multitasking.p8 index c8046c7b4..8846eb68a 100644 --- a/examples/multitasking.p8 +++ b/examples/multitasking.p8 @@ -29,20 +29,22 @@ main { repeat { ubyte key = cbm.GETIN2() if key!=0 { - void coroutines.add(&countertask, key) + ubyte taskid = coroutines.add(&countertask, key) + counters[taskid] = 222 } void coroutines.yield() } } - ubyte[coroutines.MAX_TASKS] counters = [100] * coroutines.MAX_TASKS + ubyte[coroutines.MAX_TASKS] counters sub countertask() { repeat { uword userdata = coroutines.yield() ; yield and obtain our userdata ubyte tid = coroutines.current() ; what task are we? + counters[tid]-- ; our counter is in the array, it cannot be kept in a local variable (shared state) + txt.plot(15, 10 + tid) - counters[tid]-- ; our counter is in the array, cannot be a local variable (shared state) if counters[tid] == 0 { txt.print(" ") return ; done, exit the task diff --git a/examples/test.p8 b/examples/test.p8 index 6aa3b8c46..0c644c39c 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,4 +1,3 @@ -%import coroutines %import textio %zeropage basicsafe @@ -6,120 +5,7 @@ main { sub start() { - cx16.r0-- - - txt.print("cooperative multitasking / coroutines\n\n") - txt.print("here are couple of routines that each\nrun a few loops bouncing a digit around.\n") - - void coroutines.add(&task1) - void coroutines.add(&task2) - void coroutines.add(&task3) - void coroutines.add(&task4) - void coroutines.add(&vsynctask) - coroutines.run() - - txt.print("we're all done!\n") - } - - sub task1() { - const ubyte x = 5 - ubyte y - repeat 3 { - for y in 10 to 24 { - txt.setchr(x, y-1, sc:' ') - txt.setchr(x, y, sc:'1') - coroutines.yield() - } - for y in 24 downto 10 { - txt.setchr(x, y+1, sc:' ') - txt.setchr(x, y, sc:'1') - coroutines.yield() - } - } - txt.setchr(x, 10, sc:' ') - } - - sub task2() { - const ubyte x = 10 - ubyte y - repeat 2 { - for y in 5 to 18 { - txt.setchr(x, y-1, sc:' ') - txt.setchr(x, y, sc:'2') - coroutines.yield() - } - for y in 18 downto 5 { - txt.setchr(x, y+1, sc:' ') - txt.setchr(x, y, sc:'2') - coroutines.yield() - } - } - txt.setchr(x, 5, sc:' ') - - ; add a new task dynamically - void coroutines.add(&task5) - } - - sub task3() { - ubyte x - const ubyte y = 10 - repeat 4 { - for x in 14 to 38 { - txt.setchr(x-1, y, sc:' ') - txt.setchr(x, y, sc:'3') - coroutines.yield() - } - for x in 38 downto 14 { - txt.setchr(x+1, y, sc:' ') - txt.setchr(x, y, sc:'3') - coroutines.yield() - } - } - txt.setchr(14, y, sc:' ') - } - - sub task4() { - ubyte x - const ubyte y = 14 - repeat 4 { - for x in 15 to 30 { - txt.setchr(x-1, y, sc:' ') - txt.setchr(x, y, sc:'4') - coroutines.yield() - } - for x in 30 downto 15 { - txt.setchr(x+1, y, sc:' ') - txt.setchr(x, y, sc:'4') - coroutines.yield() - } - } - txt.setchr(15, y, sc:' ') - } - - sub task5() { - ubyte x - const ubyte y = 16 - repeat 4 { - for x in 15 to 30 { - txt.setchr(x-1, y, sc:' ') - txt.setchr(x, y, sc:'5') - coroutines.yield() - } - for x in 30 downto 15 { - txt.setchr(x+1, y, sc:' ') - txt.setchr(x, y, sc:'5') - coroutines.yield() - } - } - txt.setchr(15, y, sc:' ') - } - - sub vsynctask() { - repeat 200 { - sys.waitvsync() - sys.waitvsync() - coroutines.yield() - } + txt.plot(20,5) + txt.print("hello") } } -