def vAsyncAF equ $ff80 def vAsyncHL EQU $ff82 def vAsyncDE EQU $ff84 def vAsyncBC equ $ff86 def vAsyncPC equ $ff88 def vAsyncNext equ $ff8a def vAsyncAfter equ $ff8c def SAFE_ASYNC_START EQU 144 def SAFE_ASYNC_END EQU 151 DoInAsyncVBlank: di push af ld a, l ldh [vAsyncHL], a ld a, h ldh [vAsyncHL+1], a ld a, e ldh [vAsyncDE], a ld a, d ldh [vAsyncDE+1], a ld a, c ldh [vAsyncBC], a ld a, b ldh [vAsyncBC+1], a pop hl ld a, l ldh [vAsyncAF], a ld a, h ldh [vAsyncAF+1], a ; set pu the next call! ldh a, [vAsyncNext] ldh [vAsyncPC], a ldh a, [vAsyncNext+1] ldh [vAsyncPC+1], a ; put next into pc ldh a, [vAsyncAfter] ldh [vAsyncNext], a ldh a, [vAsyncAfter+1] ldh [vAsyncNext+1], a ; puut after into next ld a, 0 ldh [vAsyncAfter], a ; unless this gets overwritten later, end execution after that ldh [vAsyncAfter+1], a ld a, LOW(DoInAsyncVBlank_EnterThread) ld [INTERRUPT_LCD], a ld a, HIGH(DoInAsyncVBlank_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 ei ret DoInAsyncVBlank_EnterThread: ;stack looks like: ;c113 (SMC int @ LYC 90 pc), 004b (hw int @ LYC 90 pc), outer context pc push hl push bc push de push af ;af, de, bc, hl, c113 (SMC interrupt pc), 004b (hardwired interrput pc), outer context pc ; check if there's anything queued up for next execution ldh a, [vAsyncPC] ld l, a ldh a, [vAsyncPC+1] ld h, a or a, l jp z, DoInAsyncVBlank_EndThread ; if nothing is queued up, jump to cleanup ld a, LOW(DoInAsyncVBlank_ExitThread) ld [INTERRUPT_LCD], a ld a, HIGH(DoInAsyncVBlank_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 ; what if our async thread calls return? ; we need to have a PC to return to. if that happens, we will want to ; reti. ld hl, DoInAsyncVBlank_EarlyReturn push hl ; address for if the thread retrns ldh a, [vAsyncPC] ld l, a ldh a, [vAsyncPC+1] ld h, a push hl ; put vAsyncPC on the stack! for jumping to it! ; stack looks like: vasyncpc, early return, af, de, bc, hll, SMC interrput pc, hardwired interrupt pc, outer context pc ldh a, [vAsyncAF] ld l, a ldh a, [vAsyncAF+1] ld h, a push hl ldh a, [vAsyncHL] ld l, a ldh a, [vAsyncHL+1] ld h, a ldh a, [vAsyncDE] ld e, a ldh a, [vAsyncDE+1] ld d, a ldh a, [vAsyncBC] ld c, a ldh a, [vAsyncBC+1] ld b, a pop af; putting vAsyncAF into af requires this hoop-jumping ei ret ; "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 DoInAsyncVBlank_EarlyReturn: ; stack: ; af, de, bc, hl, PC (smc int @ LYC90), PC (hw int @ LYC 90), PC (outer context) PRINTln "early return handle is ", DoInAsyncVBlank_EarlyReturn ; save state of registers di push af ld a, l ldh [vAsyncHL], a ld a, h ldh [vAsyncHL+1], a ld a, e ldh [vAsyncDE], a ld a, d ldh [vAsyncDE+1], a ld a, c ldh [vAsyncBC], a ld a, b ldh [vAsyncBC+1], a pop hl ld a, l ldh [vAsyncAF], a ld a, h ldh [vAsyncAF+1], a ; set pu the next call! ldh a, [vAsyncNext] ldh [vAsyncPC], a ldh a, [vAsyncNext+1] ldh [vAsyncPC+1], a ; put next into pc ldh a, [vAsyncAfter] ldh [vAsyncNext], a ldh a, [vAsyncAfter+1] ldh [vAsyncNext+1], a ; puut after into next ld a, 0 ldh [vAsyncAfter], a ; unless this gets overwritten later, end execution after that ldh [vAsyncAfter+1], a ld a, LOW(DoInAsyncVBlank_EnterThread) ; set up next call ld [INTERRUPT_LCD], a ld a, HIGH(DoInAsyncVBlank_EnterThread) ld [INTERRUPT_LCD+1], a ld a, SAFE_ASYNC_START ld [rLYC], a ; restore the pre-interrupt registers, return and enable interrupts pop af pop de pop bc pop hl reti DoInAsyncVBlank_EndThread: ; end execution of the thread by disabling the interrupt ; and restore the state for the outer thread ld hl, rIE res 1, [hl] ; disable STAT/vblank interrupt pop af pop de pop bc pop hl reti DoInAsyncVBlank_ExitThread: ; at this point, it's an interrpt being called from inside the interrupt ; our stack probably looks like this: ; PC (smc int), PC (hardwired int), PC (inside thread), early return, af, de, bc, hl, PC (smc int), PC (hardwired int), PC (outer context) ; first, save the interrpt thread registers. push af ld a, l ldh [vAsyncHL], a ld a, h ldh [vAsyncHL+1], a ld a, e ldh [vAsyncDE], a ld a, d ldh [vAsyncDE+1], a ld a, c ldh [vAsyncBC], a ld a, b ldh [vAsyncBC+1], a pop hl ld a, l ldh [vAsyncAF], a ld a, h ldh [vAsyncAF+1], a ;god i'm such a genius ; this was called as an interrupt inside the interrupt ;so our stack looks like this: ; PC (smc int), PC (hardwired int), PC (inside thread), early return, af, de, bc, hl, PC (smc int), PC (hardwired int), PC (outer context) ; we've got the two PCs from the inner interrupt call stack. ; get rid of them! we're running without handrails! i know what i'm doing! I hope! pop hl pop hl ; now, save the interrupt thread's pc., which is on the stack from this ; interrupt being called. pop hl ld a, l ldh [vAsyncPC], a ld a, h ldh [vAsyncPC+1], a ; now we are done with this entire execution of thread. now we need to set up ; the next execution of this thread. ld a, LOW(DoInAsyncVBlank_EnterThread) ld [INTERRUPT_LCD], a ld a, HIGH(DoInAsyncVBlank_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 ;at present the stack looks like: ; early return, af, de, bc, hl, pc (smc int @ LYC 90), pc (hardwired int at LYC 90), PC (outer context) ; pop the early return and discard it. pop hl ; and finally, we can restore the state of the registers to what they were ; before this whole handler got called. pop af pop de pop bc pop hl reti ; this is a proper return