mirror of
https://github.com/irmen/prog8.git
synced 2025-11-23 14:17:51 +00:00
optimized coroutines library
This commit is contained in:
@@ -28,6 +28,8 @@
|
|||||||
; that routine can for instance call current() (or just look at the active_task variable) to get the id of the next task to execute.
|
; that routine can for instance call current() (or just look at the active_task variable) to get the id of the next task to execute.
|
||||||
; It has then to return a boolean: true=next task is to be executed, false=skip the task this time.
|
; It has then to return a boolean: true=next task is to be executed, false=skip the task this time.
|
||||||
; - in tasks: call yield() to pass control to the next task. Use the returned userdata value to do different things.
|
; - in tasks: call yield() to pass control to the next task. Use the returned userdata value to do different things.
|
||||||
|
; For now, you MUST call yield() only from the actual subroutine that has been registered as a task!
|
||||||
|
; (this is because otherwise the cpu call stack gets messed up and an RTS in task1 could suddenly pop a return address belonging to another tasks' call frame)
|
||||||
; - call current() to get the current task id.
|
; - call current() to get the current task id.
|
||||||
; - call kill(taskid) to kill a task by id.
|
; - call kill(taskid) to kill a task by id.
|
||||||
; - call killall() to kill all tasks.
|
; - call killall() to kill all tasks.
|
||||||
@@ -41,19 +43,17 @@ coroutines {
|
|||||||
const ubyte MAX_TASKS = 64
|
const ubyte MAX_TASKS = 64
|
||||||
uword[MAX_TASKS] tasklist
|
uword[MAX_TASKS] tasklist
|
||||||
uword[MAX_TASKS] userdatas
|
uword[MAX_TASKS] userdatas
|
||||||
uword[MAX_TASKS] returnaddresses
|
|
||||||
ubyte active_task
|
ubyte active_task
|
||||||
uword supervisor
|
uword supervisor
|
||||||
|
|
||||||
sub add(uword taskaddress, uword userdata) -> ubyte {
|
sub add(uword @nozp taskaddress, uword @nozp userdata) -> ubyte {
|
||||||
; find the next empty slot in the tasklist and stick it there
|
; 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!
|
; 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)
|
; also returns the success in the Carry flag (carry set=success, carry clear = task was not added)
|
||||||
for cx16.r0L in 0 to len(tasklist)-1 {
|
for cx16.r0L in 0 to len(tasklist)-1 {
|
||||||
if tasklist[cx16.r0L] == 0 {
|
if tasklist[cx16.r0L] == 0 {
|
||||||
tasklist[cx16.r0L] = taskaddress
|
tasklist[cx16.r0L] = sys.get_as_returnaddress(taskaddress)
|
||||||
userdatas[cx16.r0L] = userdata
|
userdatas[cx16.r0L] = userdata
|
||||||
returnaddresses[cx16.r0L] = 0
|
|
||||||
sys.set_carry()
|
sys.set_carry()
|
||||||
return cx16.r0L
|
return cx16.r0L
|
||||||
}
|
}
|
||||||
@@ -70,57 +70,48 @@ coroutines {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub run(uword supervisor_routine) {
|
sub run(uword @nozp supervisor_routine) {
|
||||||
supervisor = supervisor_routine
|
supervisor = supervisor_routine
|
||||||
for active_task in 0 to len(tasklist)-1 {
|
for active_task in 0 to len(tasklist)-1 {
|
||||||
if tasklist[active_task]!=0 {
|
if tasklist[active_task]!=0 {
|
||||||
; activate the termination handler and start the first task
|
; activate the termination handler and start the first task
|
||||||
; note: cannot use pushw() because JSR doesn't push the return address in the same way
|
; note: cannot use pushw() because JSR doesn't push the return address in the same way
|
||||||
sys.push_returnaddress(&termination)
|
sys.push_returnaddress(&termination)
|
||||||
goto tasklist[active_task]
|
sys.pushw(tasklist[active_task])
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub yield() -> uword {
|
sub yield() -> uword {
|
||||||
; Store the return address of the yielding task,
|
; Store the return address of the yielding task, and continue with the next one instead (round-robin)
|
||||||
; and continue with the next one instead (round-robin)
|
; Returns the associated userdata value.
|
||||||
; Returns the associated userdata value
|
; NOTE: CAN ONLY BE CALLED FROM THE SCOPE OF THE SUBROUTINE THAT HAS BEEN REGISTERED AS THE TASK!
|
||||||
uword task_start, task_continue
|
uword task_return_address
|
||||||
returnaddresses[active_task] = sys.popw()
|
tasklist[active_task] = sys.popw()
|
||||||
|
|
||||||
resume_with_next_task:
|
skip_task:
|
||||||
if not next_task() {
|
if not next_task() {
|
||||||
void sys.popw() ; remove return to the termination handler
|
void sys.popw() ; remove return to the termination handler
|
||||||
return 0 ; exiting here will now actually return from the start() call back to the calling program :)
|
return 0 ; exiting here will now actually return back to the calling program that called run()
|
||||||
}
|
}
|
||||||
|
|
||||||
if supervisor!=0 {
|
if supervisor!=0
|
||||||
if lsb(call(supervisor))==0
|
if lsb(call(supervisor))==0
|
||||||
goto resume_with_next_task
|
goto skip_task
|
||||||
}
|
|
||||||
|
|
||||||
if task_continue==0 {
|
; returning from yield then continues with the next coroutine:
|
||||||
; fetch start address of next task.
|
sys.pushw(task_return_address)
|
||||||
; address on the stack must be pushed in reverse byte order
|
|
||||||
; also, subtract 1 from the start address because JSR pushes returnaddress minus 1
|
|
||||||
; note: cannot use pushw() because JSR doesn't push the return address in the same way
|
|
||||||
sys.push_returnaddress(task_start)
|
|
||||||
} else
|
|
||||||
sys.pushw(task_continue)
|
|
||||||
|
|
||||||
; returning from yield then continues with the next coroutine
|
|
||||||
return userdatas[active_task]
|
return userdatas[active_task]
|
||||||
|
|
||||||
sub next_task() -> bool {
|
sub next_task() -> bool {
|
||||||
; search through the task list for the next active task
|
; search through the task list for the next active task
|
||||||
repeat len(tasklist) {
|
repeat len(tasklist) {
|
||||||
active_task++
|
active_task++
|
||||||
if active_task==len(returnaddresses)
|
if active_task==len(tasklist)
|
||||||
active_task=0
|
active_task=0
|
||||||
task_start = tasklist[active_task]
|
task_return_address = tasklist[active_task]
|
||||||
if task_start!=0 {
|
if task_return_address!=0 {
|
||||||
task_continue = returnaddresses[active_task]
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,9 +119,8 @@ resume_with_next_task:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub kill(ubyte taskid) {
|
sub kill(ubyte @nozp taskid) {
|
||||||
tasklist[taskid] = 0
|
tasklist[taskid] = 0
|
||||||
returnaddresses[taskid] = 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub current() -> ubyte {
|
sub current() -> ubyte {
|
||||||
@@ -138,12 +128,10 @@ resume_with_next_task:
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub termination() {
|
sub termination() {
|
||||||
; a task has terminated. wipe it from the list.
|
; internal routine: a task has terminated. wipe it from the list.
|
||||||
; this is an internal routine
|
|
||||||
kill(active_task)
|
kill(active_task)
|
||||||
; reactivate this termination handler
|
; reactivate this termination handler and go to the next task
|
||||||
; note: cannot use pushw() because JSR doesn't push the return address in the same way
|
|
||||||
sys.push_returnaddress(&termination)
|
sys.push_returnaddress(&termination)
|
||||||
goto coroutines.yield.resume_with_next_task
|
goto coroutines.yield.skip_task
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2028,6 +2028,18 @@ save_SCRATCH_ZPWORD2 .word ?
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
|
||||||
|
%asm {{
|
||||||
|
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
|
||||||
|
cpx #0
|
||||||
|
bne +
|
||||||
|
dey
|
||||||
|
+ dex
|
||||||
|
tya
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
inline asmsub pop() -> ubyte @A {
|
inline asmsub pop() -> ubyte @A {
|
||||||
%asm {{
|
%asm {{
|
||||||
pla
|
pla
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ Idea is to make it feature complete in the IR/Virtual target, then merge it to m
|
|||||||
Future Things and Ideas
|
Future Things and Ideas
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- remove (experimental) tag in docs from 3 libraries
|
||||||
- is "checkAssignmentCompatible" redundant (gets called just 1 time!) when we also have "checkValueTypeAndRange" ?
|
- is "checkAssignmentCompatible" redundant (gets called just 1 time!) when we also have "checkValueTypeAndRange" ?
|
||||||
- enums?
|
- enums?
|
||||||
- romable: should we have a way to explicitly set the memory address for the BSS area (instead of only the highram bank number on X16, allow a memory address too for the -varshigh option?)
|
- romable: should we have a way to explicitly set the memory address for the BSS area (instead of only the highram bank number on X16, allow a memory address too for the -varshigh option?)
|
||||||
|
|||||||
Reference in New Issue
Block a user