gb-tarot/Async.inc
2025-03-11 17:01:21 -04:00

305 lines
6.3 KiB
PHP

; async variables somewhere in RAM
def vAsyncAF equ ASYNC_VARS_START
def vAsyncHL EQU vAsyncAF+2
def vAsyncDE EQU vAsyncHL+2
def vAsyncBC equ vAsyncDE+2
def vAsyncPC equ vAsyncBC+2
def vAsyncNext equ vAsyncPC+2
def vAsyncAfter equ vAsyncNext+2
def vAsyncProgress equ vAsyncAfter+2
def vAsyncMainSP equ vAsyncProgress+2
def vAsyncThreadSP equ vAsyncMainSP+2
; canonical ordering to push should be: AF, BC, DE, HL,
def ASYNC_STACK_TOP equ $ffc0
; stack top is ffc0
; first value on the stack is the early return pointer, at ffbe = ffc0-2
; second value is the destination of the async call, at ffbc = ffbe-2 = ffc0-2-2
def ASYNC_THREAD_CALL equ ASYNC_STACK_TOP - 2 - 2
def SAFE_ASYNC_START EQU 148
def SAFE_ASYNC_END EQU 153
Async_Spawn_HL:
di
ld a, l
ld [ASYNC_THREAD_CALL], a
ld a, h
ld [ASYNC_THREAD_CALL+1], a
Async_Spawn:
di
nop
; save all the registers
push af
ld a, l
ld [vAsyncHL], a
ld a, h
ld [vAsyncHL+1], a
ld a, e
ld [vAsyncDE], a
ld a, d
ld [vAsyncDE+1], a
ld a, c
ld [vAsyncBC], a
ld a, b
ld [vAsyncBC+1], a
pop hl
ld a, l
ld [vAsyncAF], a
ld a, h
ld [vAsyncAF+1], a
; save main sp
ld hl, sp+0
ld a, l
ld [vAsyncMainSP], a
ld a, h
ld [vAsyncMainSP+1], a
; switch to thread sp
ld a, LOW(ASYNC_STACK_TOP)
ld l, a
ld a, HIGH(ASYNC_STACK_TOP)
ld h, a
ld sp, hl
; push early return onto thread stack
ld l, LOW(Async_EarlyReturn)
ld h, HIGH(Async_EarlyReturn)
push hl
; requestedd pc is expected to be set at ASYNC_THREAD_CALL by the calling func
ld hl, sp-2
ld sp, hl
; push registers
; canonical ordering to push should be: AF, BC, DE, HL,
ld a, [vAsyncAF]
ld l, a
ld a, [vAsyncAF+1]
ld h, a
push hl
ld a, [vAsyncBC]
ld l, a
ld a, [vAsyncBC+1]
ld h, a
push hl
ld a, [vAsyncDE]
ld l, a
ld a, [vAsyncDE+1]
ld h, a
push hl
ld a, [vAsyncHL]
ld l, a
ld a, [vAsyncHL+1]
ld h, a
push hl
; save current sp to vAsyncThreadSP
ld hl, sp+0
ld a, l
ld [vAsyncThreadSP], a
ld a, h
ld [vAsyncThreadSP+1], a
; now the stack looks like: early return, ASYNC_THREAD_CALL, AF, BC, DE, HL
; switch back to main thread.
; register enterthread
ld a, LOW(Async_EnterThread)
ld [INTERRUPT_LCD], a
ld a, HIGH(Async_EnterThread)
ld [INTERRUPT_LCD + 1], a; set interrupt handler to "ENTER THREAD"
ld a, SAFE_ASYNC_START ; CHANGE ME TO ADJUST SAFE TRANSFER TIMING
ld [rLYC], a ; set LYC
ld hl, rIE
set 1, [hl] ; enable vblank
ld hl, rSTAT
set 6, [hl] ; set the stat interrupt to LYC mode
ld hl, rIF
res 1, [hl] ; clear the interrupt so we don't immediately fire it
; restore main sp
ld a, [vAsyncMainSP]
ld l, a
ld a, [vAsyncMainSP+1]
ld h, a
ld sp, hl
; restore registers from memory
ld a, [vAsyncAF]
ld l, a
ld a, [vAsyncAF+1]
ld h, a
push hl ; this is so we can pop af when we're done
ld a, [vAsyncHL]
ld l, a
ld a, [vAsyncHL+1]
ld h, a
ld a, [vAsyncDE]
ld e, a
ld a, [vAsyncDE+1]
ld d, a
ld a, [vAsyncBC]
ld c, a
ld a, [vAsyncBC+1]
ld b, a
pop af
ei
ret
Async_Kill:
ld hl, EndOfInstructions
call Async_Spawn_HL
ret
Async_EnterThread:
;stack looks like:
;c113 (SMC int @ LYC 90 pc), 004b (hw int @ LYC 90 pc), outer context pc
push af
push bc
push de
push hl
; check if there's anything queued up for executing; if no, just clean up
ld a, LOW(ASYNC_STACK_TOP) ; we're checkng if the current async stack is empty
ld hl, vAsyncThreadSP ; comparing to the current thread sp
cp a, [hl] ; are they equal?
jp z, Async_CleanUpThread
;af, de, bc, hl, c113 (SMC interrupt pc), 004b (hardwired interrput pc), outer context pc
ld a, LOW(Async_ExitThread)
ld [INTERRUPT_LCD], a
ld a, HIGH(Async_ExitThread)
ld [INTERRUPT_LCD+1], a
ld a, SAFE_ASYNC_END ; CHANGE ME TO ADJUST SAFE TRANSFER TIMING
ld [rLYC], a ; set lcd interrupt handler to EXIT SAFE MODE on line 153
; save main thread stack pointer
ld hl, sp+0
ld a, l
ld [vAsyncMainSP], a
ld a, h
ld [vAsyncMainSP+1], a
; load side thread stack pointer
ld a, [vAsyncThreadSP]
ld l, a
ld a, [vAsyncThreadSP+1]
ld h, a
ld sp, hl
; pop registers
; canonical ordering to push should be: AF, BC, DE, HL,
; pop is HL, DE, BC, AF
pop hl
pop de
pop bc
pop af
reti ; "return" to the vAsyncPC wee put on the stack previously.
; this is more or less a jump, not a return.
; is that the source of our problems?
; after this instruction executes, stack looks like:
; early return, af, de, bc, hl, PC (smc int), PC (hardwired int), PC (outer context)
; and we'll be in the async thread, executing
Async_ExitThread:
; save interrupt registers (AF, BC, DE, HL)
push af
push bc
push de
push hl
;af, de, bc, hl, c113 (SMC interrupt pc), 004b (hardwired interrput pc), thread pc, return
ld a, LOW(Async_EnterThread)
ld [INTERRUPT_LCD], a
ld a, HIGH(Async_EnterThread)
ld [INTERRUPT_LCD+1], a
ld a, SAFE_ASYNC_START ; CHANGE ME TO ADJUST SAFE TRANSFER TIMING
ld [rLYC], a ; set lcd interrupt handler to EXIT SAFE MODE on line 153
; save side thread stack pointer
ld hl, sp+0
ld a, l
ld [vAsyncThreadSP], a
ld a, h
ld [vAsyncThreadSP+1], a
; load main thread stack pointer
ld a, [vAsyncMainSP]
ld l, a
ld a, [vAsyncMainSP+1]
ld h, a
ld sp, hl
; pop registers
; canonical ordering to push should be: AF, BC, DE, HL,
; pop is HL, DE, BC, AF
pop hl
pop de
pop bc
pop af
reti
Async_EarlyReturn:
di
; don't care about current registers bc we're done executing.
; store side thread SP, so everyone knows that the side thread stack is empty
ld hl, sp+0
ld a, l
ld [vAsyncThreadSP], a
ld a, h
ld [vAsyncThreadSP+1], a
; unset next call
ld hl, rIE
res 1, [hl] ; disable vblank
ld hl, rIF
res 1, [hl] ; clear the interrupt
; load main thread stack pointer
ld a, [vAsyncMainSP]
ld l, a
ld a, [vAsyncMainSP+1]
ld h, a
ld sp, hl
; restore the pre-interrupt registers, return and enable interrupts
pop af
pop de
pop bc
pop hl
reti
Async_CleanUpThread:
;stack looks like:
;hl, de, bc, af, c113 (SMC int @ LYC 90 pc), 004b (hw int @ LYC 90 pc), outer context pc
pop hl
pop de
pop bc
pop af
reti