From 08d978bd5816062a8c0d6912877a7fecaf9cad64 Mon Sep 17 00:00:00 2001 From: shoofle Date: Wed, 26 Feb 2025 15:49:02 -0500 Subject: [PATCH] switch to two-stack async setup!!! --- 00TheFool.inc | 28 +-- Async.inc | 446 ++++++++++++++++++++++------------------- CardHelpers.inc | 72 +------ CopyRangeSafe.inc | 3 - Random.inc | 10 +- ScreenCardBrowse.inc | 81 ++++---- ScreenCardRead.inc | 125 ++++++------ ScreenMainMenu.inc | 71 +++---- ScreenShuffle.inc | 141 ++++++------- ScreenSpreadSelect.inc | 270 +++++++------------------ main.asm | 47 +++-- screendesigns.aseprite | Bin 4947 -> 4890 bytes source.zip | Bin 307273 -> 303261 bytes 13 files changed, 572 insertions(+), 722 deletions(-) diff --git a/00TheFool.inc b/00TheFool.inc index a18231a..4fad592 100644 --- a/00TheFool.inc +++ b/00TheFool.inc @@ -29,42 +29,42 @@ TheFool: ld [hl+], a ; $c201 = timer for big zero .fUpdate: - ldh a, [rDELTAT] + ld a, [rDELTAT] ld b, a - ld a, [$c300] + ld a, [CARD_VARS_START] add a, b - ld [$c200], a - ld a, [$c300+1] + ld [CARD_VARS_START], a + ld a, [CARD_VARS_START+1] adc a, 0 - ld [$c300+1], a ; increment time. when the 16bit time register is greater + ld [CARD_VARS_START+1], a ; increment time. when the 16bit time register is greater - ld a, [$c300+1] + ld a, [CARD_VARS_START+1] cp a, $08 ; $10 00 = 1 second jp c, .doneWithTimer1 ; if the timer is less than $0800, skip to end ;otherwise reset the timer ld a, 0 - ld [$c300], a - ld [$c300+1], a + ld [CARD_VARS_START], a + ld [CARD_VARS_START+1], a .doneWithTimer1 - ld a, [$c302] + ld a, [CARD_VARS_START+2] add a, b - ld [$c302], a - ld a, [$c302+1] + ld [CARD_VARS_START+2], a + ld a, [CARD_VARS_START+2+1] adc a, 0 ld [$c302+1], a - ld a, [$c302+1] + ld a, [CARD_VARS_START+2+1] cp a, $10 ; $10 00 = 1 second jp c, .doneWithTimer2 ; if the timer is less than $0800, skip to end ;otherwise reset the timer ld a, 0 - ld [$c302], a - ld [$c302+1], a + ld [CARD_VARS_START+2], a + ld [CARD_VARS_START+2+1], a .doneWithTimer2 diff --git a/Async.inc b/Async.inc index 12c84a6..ddd9447 100644 --- a/Async.inc +++ b/Async.inc @@ -1,55 +1,120 @@ -; async variables from ff80-ff8f! -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 vAsyncProgress equ $ff8e +; 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 -def SAFE_ASYNC_START EQU 146 -def SAFE_ASYNC_END EQU 152 +; canonical ordering to push should be: AF, BC, DE, HL, -DoInAsyncVBlank: +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 - push af - - ld a, l - ldh [vAsyncHL], a + ld a, l + ld [ASYNC_THREAD_CALL], 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 + 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 - ldh [vAsyncAF], a + ld [vAsyncAF], a ld a, h - ldh [vAsyncAF+1], a + ld [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 + ; save main sp + ld hl, sp+0 + ld a, l + ld [vAsyncMainSP], a + ld a, h + ld [vAsyncMainSP+1], a - ld a, LOW(DoInAsyncVBlank_EnterThread) + ; 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(DoInAsyncVBlank_EnterThread) + 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 @@ -59,204 +124,177 @@ DoInAsyncVBlank: 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 + + + ; 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 -DoInAsyncVBlank_EnterThread: +Async_EnterThread: ;stack looks like: ;c113 (SMC int @ LYC 90 pc), 004b (hw int @ LYC 90 pc), outer context pc - push hl + push af push bc push de - push af - + 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 - ; 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 a, LOW(Async_ExitThread) ld [INTERRUPT_LCD], a - ld a, HIGH(DoInAsyncVBlank_ExitThread) + 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 - ; 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] + ; load side thread stack pointer + ld a, [vAsyncThreadSP] ld l, a - ldh a, [vAsyncPC+1] + ld a, [vAsyncThreadSP+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 + ld sp, hl - 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. + ; 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 -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 + ;af, de, bc, hl, c113 (SMC interrupt pc), 004b (hardwired interrput pc), thread pc, return - 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 a, LOW(Async_EnterThread) ld [INTERRUPT_LCD], a - ld a, HIGH(DoInAsyncVBlank_EnterThread) + ld a, HIGH(Async_EnterThread) ld [INTERRUPT_LCD+1], a - ld a, SAFE_ASYNC_START - ld [rLYC], 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 -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 + + +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 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 + pop 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 - \ No newline at end of file + reti \ No newline at end of file diff --git a/CardHelpers.inc b/CardHelpers.inc index 91aea69..69585f1 100644 --- a/CardHelpers.inc +++ b/CardHelpers.inc @@ -1,9 +1,8 @@ -LoadCardDataAsync: ; to be called as async +LoadCardData: +LoadCardDataAsync: ei - ld a, 1 - ldh [vBlocked], a - ldh a, [vSelectedCardIndex] - ldh [vPreviousCardIndex], a + ld a, [vSelectedCardIndex] + ld [vPreviousCardIndex], a ld b, 0 ld c, a ; load bc from a, the number of the card in the cards list @@ -23,20 +22,7 @@ LoadCardDataAsync: ; to be called as async ld h, b ld l, c ; hl now contains the address of the card data. - ld a, LOW(.duringDraw) - ld [vAsyncNext], a - ld a, HIGH(.duringDraw) - ld [vAsyncNext+1], a - ld a, 0 - ld [vAsyncAfter], a - ld [vAsyncAfter+1], a - di - nop - nop - ret .duringDraw - di ; these function calls should be fast so we'll disable interrupts and do them - nop ; synchronously ; hl points to a card struct. @@ -55,8 +41,6 @@ LoadCardDataAsync: ; to be called as async ld de, $9800 + 32*16 + 10 call PrintString - ei - ; hl now contains the address after all the strings. ; [hl+] and [hl+] read the length first, into bc ld a, [hl+] @@ -74,22 +58,10 @@ LoadCardDataAsync: ; to be called as async ld b, 16 ; height ld c, 8 ; width - ld a, LOW(CopyTilesToMapThreadsafe) - ld [vAsyncNext], a - ld a, HIGH(CopyTilesToMapThreadsafe) - ld [vAsyncNext+1], a - ld a, LOW(.afterCopyTiles) - ld [vAsyncAfter], a - ld a, HIGH(.afterCopyTiles) - ld [vAsyncAfter+1], a - di - nop - nop - ret -.afterCopyTiles - ei - ldh a, [vSelectedCardIndex] - ldh [vPreviousCardIndex], a + call CopyTilesToMapUnsafe + + ld a, [vSelectedCardIndex] + ld [vPreviousCardIndex], a ld b, 0 ld c, a ; load bc from a, the number of the card in the cards list @@ -108,13 +80,11 @@ LoadCardDataAsync: ; to be called as async ld h, b ld l, c ; hl now contains the address of the card data. - di call PassList call PassList call PassList call PassList call PassList ; skip the strings - ei inc hl inc hl ; skip tile map width inc hl @@ -132,30 +102,8 @@ LoadCardDataAsync: ; to be called as async ld l, e ; hl takes the source ld de, $9000 + VARIABLE_TILES_START*$10 ; always load tile data into the same spot in vram - ld a, LOW(CopyRangeUnsafe) - ld [vAsyncNext], a - ld a, HIGH(CopyRangeUnsafe) - ld [vAsyncNext+1], a - ld a, LOW(.afterLoadingTiles) - ld [vAsyncAfter], a - ld a, HIGH(.afterLoadingTiles) - ld [vAsyncAfter+1], a + call CopyRangeUnsafe + di nop - nop - ret -.afterLoadingTiles - ei - ld a, 0 - ldh [vBlocked], a - - ld a, 0 - ld [vAsyncNext], a - ld [vAsyncNext+1], a - ld [vAsyncAfter], a - ld [vAsyncAfter+1], a - di - nop - nop - ret diff --git a/CopyRangeSafe.inc b/CopyRangeSafe.inc index 687018c..97762b1 100644 --- a/CopyRangeSafe.inc +++ b/CopyRangeSafe.inc @@ -212,9 +212,6 @@ CopyRangeUnsafe: ; this is threadsafe but not vblank safe ld a, b or a, c ; check if bc is zero jp nz, CopyRangeUnsafe - di - nop - nop ret diff --git a/Random.inc b/Random.inc index 4edfdc3..3e6dc2d 100644 --- a/Random.inc +++ b/Random.inc @@ -1,6 +1,6 @@ ClockLFSR: ; uses af, bc and clocks one bit of the LFSR. ; at return time, a holds the bottom eight of lfsr state. - ldh a, [rLFSR] ; + ld a, [rLFSR] ; ld b, a ; b has shifted value ; 1 cycle srl b ; second tap is two shifts ; 2 cycles @@ -21,12 +21,12 @@ ClockLFSR: ; uses af, bc and clocks one bit of the LFSR. jr nz, .noComplementCarry ; 2 or 3 cycles ccf ; 1 cycle if prev was 2 cycles :) .noComplementCarry - ldh a, [rLFSR+1] ; 2 cycles + ld a, [rLFSR+1] ; 2 cycles rra ; 1 cycle ; this should populate with the carry flag - ldh [rLFSR+1], a ; 2 cycles - ldh a, [rLFSR] ; 2 cycles + ld [rLFSR+1], a ; 2 cycles + ld a, [rLFSR] ; 2 cycles rra ; 1 cycle - ldh [rLFSR], a ; 2 cycles + ld [rLFSR], a ; 2 cycles ret ; 37 cycles total. can call roughly three of these per scanline OneRandomByte: ; clocks LFSR 8 times so a is diff --git a/ScreenCardBrowse.inc b/ScreenCardBrowse.inc index 4f044fb..a6da19b 100644 --- a/ScreenCardBrowse.inc +++ b/ScreenCardBrowse.inc @@ -1,6 +1,5 @@ -; screen variables already ddefined in screencardread +; screen variables shared with screencardread ;DEF vPreviousCardIndex EQU VARIABLES_START -;def vBlocked equ vPreviousCardIndex + 1 ScreenCardBrowse: dw CardBrowseSetup @@ -10,68 +9,54 @@ ScreenCardBrowse: CardBrowseSetup: ld a, 1 - ldh [vBlocked], a + ld [vBlocked], a - ld a, LOW(RunDMA) ; zero out the OAM - ld [vAsyncNext], a - ld a, HIGH(RunDMA) - ld [vAsyncNext+1], a - ld a, LOW(.loadUIMap) - ld [vAsyncAfter], a - ld a, HIGH(.loadUIMap) - ld [vAsyncAfter+1], a - - ld a, HIGH(ZEROES) - ld de, $ffc0 ; arguments to the first async call. - - call DoInAsyncVBlank + ld hl, .loadUIMap + call Async_Spawn_HL ret .loadUIMap + ld a, HIGH(ZEROES) + ld de, SAFE_DMA_LOCATION ; arguments to the first async call. + call RunDMA ld hl, CardBrowse.UITilemap ; origin ld de, $9800 ; destination ld b, 18 ; height ld c, 20 ; width - ld a, LOW(CopyTilesToMapThreadsafe) - ld [vAsyncNext], a - ld a, HIGH(CopyTilesToMapThreadsafe) - ld [vAsyncNext+1], a + call CopyTilesToMapUnsafe - ld a, LOW(LoadCardDataAsync) - ld [vAsyncAfter], a - ld a, HIGH(LoadCardDataAsync) - ld [vAsyncAfter+1], a + call LoadCardData ret CardBrowseUpdate: ld hl, vTime - ldh a, [rDELTAT] + ld a, [rDELTAT] ld b, a - ldh a, [vTime] + ld a, [vTime] add a, b - ldh [vTime], a - ldh a, [vTime+1] + ld [vTime], a + ld a, [vTime+1] adc a, 0 - ldh [vTime+1], a ; increment time. when the 16bit time register is greater + ld [vTime+1], a ; increment time. when the 16bit time register is greater ; than 4096 ($10_00) then one second has passed. so that's satisfied when ; vTime+1 is equal to or greater than $10 - ldh a, [vTime+1] + ld a, [vTime+1] cp a, $01 jp c, .doneTimer ; if the timer is less than $0100, skip to end ;otherwise reset the timer ld a, 0 - ldh [vTime], a - ldh [vTime+1], a + ld [vTime], a + ld [vTime+1], a ld hl, SquaresTiles - ldh a, [vFrameCountSquares] + ld a, [vFrameCountSquares] inc a call ArrayClampLooping - ldh [vFrameCountSquares], a + ld [vFrameCountSquares], a .doneTimer ld hl, rMYBTNP @@ -82,7 +67,7 @@ CardBrowseUpdate: ret .doneWithB - ldh a, [vSelectedCardIndex] + ld a, [vSelectedCardIndex] ld hl, rMYBTNP bit 3, [hl] jp z, :+ ; skip the following code if down is not pressed @@ -92,36 +77,40 @@ CardBrowseUpdate: jp z, :+ ; skip the following code if up is not pressed dec a : - ldh [vSelectedCardIndex], a + ld [vSelectedCardIndex], a ld hl, Cards call ArrayClampLooping - ldh [vSelectedCardIndex], a + ld [vSelectedCardIndex], a - ldh a, [vSelectedCardIndex] + ld a, [vSelectedCardIndex] ld hl, vPreviousCardIndex cp a, [hl] ret z ; if the selected card diddn't change, nothing to do - ldh a, [vBlocked] + ld a, [vBlocked] cp a, 0 ret nz - ld a, LOW(LoadCardDataAsync) - ld [vAsyncAfter], a - ld a, HIGH(LoadCardDataAsync) - ld [vAsyncAfter+1], a - call DoInAsyncVBlank - + ld hl, LoadCardTask + call Async_Spawn ret +LoadCardTask: + ld a, 1 + ld [vBlocked], a + call LoadCardData + ld a, 0 + ld [vBlocked], a + ret + CardBrowseDraw: ; the card data is loaded asynchronously, initiated in CardReadUpdate ld hl, SquaresTiles inc hl ld b, 0 - ldh a, [vFrameCountSquares] + ld a, [vFrameCountSquares] ld c, a add hl, bc add hl, bc diff --git a/ScreenCardRead.inc b/ScreenCardRead.inc index ea470b1..5963a3b 100644 --- a/ScreenCardRead.inc +++ b/ScreenCardRead.inc @@ -1,6 +1,5 @@ ; screen variables -DEF vPreviousCardIndex EQU VARIABLES_START -def vBlocked equ vPreviousCardIndex + 1 +DEF vPreviousCardIndex EQU SCREEN_VARS_START+16 ScreenCardRead: dw CardReadSetup @@ -10,53 +9,48 @@ ScreenCardRead: CardReadSetup: ld a, 1 - ldh [vBlocked], a + ld [vBlocked], a - ld [vAsyncAfter+1], a + ld hl, CardReadSetupAsyncTask + call Async_Spawn_HL + ret + +CardReadSetupAsyncTask: ld hl, UITilemap ; origin ld de, $9800 ; destination ld b, 18 ; height ld c, 20 ; width - - ld a, LOW(CopyTilesToMapThreadsafe) - ld [vAsyncNext], a - ld a, HIGH(CopyTilesToMapThreadsafe) - ld [vAsyncNext+1], a - - ld a, LOW(LoadCardDataAsync) - ld [vAsyncAfter], a - ld a, HIGH(LoadCardDataAsync) - ld [vAsyncAfter+1], a - call DoInAsyncVBlank + call CopyTilesToMapUnsafe + call ChangedCardTask ret CardReadUpdate: ld hl, vTime - ldh a, [rDELTAT] + ld a, [rDELTAT] ld b, a - ldh a, [vTime] + ld a, [vTime] add a, b - ldh [vTime], a - ldh a, [vTime+1] + ld [vTime], a + ld a, [vTime+1] adc a, 0 - ldh [vTime+1], a ; increment time. when the 16bit time register is greater + ld [vTime+1], a ; increment time. when the 16bit time register is greater ; than 4096 ($10_00) then one second has passed. so that's satisfied when ; vTime+1 is equal to or greater than $10 - ldh a, [vTime+1] + ld a, [vTime+1] cp a, $01 jp c, .doneTimer ; if the timer is less than $0100, skip to end ;otherwise reset the timer ld a, 0 - ldh [vTime], a - ldh [vTime+1], a + ld [vTime], a + ld [vTime+1], a ld hl, SquaresTiles - ldh a, [vFrameCountSquares] + ld a, [vFrameCountSquares] inc a call ArrayClampLooping - ldh [vFrameCountSquares], a + ld [vFrameCountSquares], a .doneTimer ld hl, rMYBTNP @@ -67,7 +61,7 @@ CardReadUpdate: ret .doneWithB - ldh a, [vSelectedSpreadCard] + ld a, [vSelectedSpreadCard] ld hl, rMYBTNP bit 1, [hl] jp z, :+ ; skip the following code if left is not pressed @@ -77,39 +71,34 @@ CardReadUpdate: jp z, :+ ; skip the following code if right is not pressed inc a : - ldh [vSelectedSpreadCard], a - ldh a, [vCurrentSpread] + ld [vSelectedSpreadCard], a + ld a, [vCurrentSpread] ld l, a - ldh a, [vCurrentSpread+1] + ld a, [vCurrentSpread+1] ld h, a - ldh a, [vSelectedSpreadCard] + ld a, [vSelectedSpreadCard] call ArrayClampLooping - ldh [vSelectedSpreadCard], a - ldh [vSelectedCardIndex], a - ldh a, [vSelectedCardIndex] - ld hl, vPreviousCardIndex + ld [vSelectedSpreadCard], a + ld hl, vPreviousSpreadCard cp a, [hl] ret z ; if the selected card diddn't change, nothing to do - ldh a, [vBlocked] + ld a, [vBlocked] cp a, 0 ret nz - ld a, LOW(LoadCardDataAsync) - ld [vAsyncAfter], a - ld a, HIGH(LoadCardDataAsync) - ld [vAsyncAfter+1], a - call DoInAsyncVBlank - - + ld a, 1 + ld [vBlocked], a + ld hl, ChangedCardTask + call Async_Spawn_HL ret CardReadDraw: ld hl, SquaresTiles inc hl ld b, 0 - ldh a, [vFrameCountSquares] + ld a, [vFrameCountSquares] ld c, a add hl, bc add hl, bc @@ -121,23 +110,31 @@ CardReadDraw: ld de, $8000+$100*16 + 1*16 ld bc, (SquaresTileset8 - SquaresTileset7) / 8 call CopyRangeUnsafeBy8s - - ; then draw the spread minimap - ldh a, [vCurrentSpread] - ld c, a - ldh a, [vCurrentSpread+1] - ld b, a - ld hl, $9800 + (32*1)+11 - ldh a, [vSelectedSpreadCard] - call DrawSpreadMinimap + ; the card data is loaded asynchronously, initiated in CardReadUpdate + ret + +CardReadTeardown: + ret - ldh a, [vCurrentSpread] +ChangedCardTask: + ld a, [vSelectedSpreadCard] + ld [vPreviousSpreadCard], a + + ld a, [vCurrentSpread] + ld c, a + ld a, [vCurrentSpread+1] + ld b, a ; gett bc as cuurrent spread address + ld hl, $9800 + (32*1)+11 + ld a, [vSelectedSpreadCard] + call DrawSpreadMinimap + + ld a, [vCurrentSpread] ld l, a - ldh a, [vCurrentSpread+1] + ld a, [vCurrentSpread+1] ld h, a call PassList ; skip spread layout - ldh a, [vSelectedSpreadCard] + ld a, [vSelectedSpreadCard] or a, a .loopThroughSpreadPositions jp z, .foundSpreadPositionDescription @@ -151,11 +148,21 @@ CardReadDraw: ld de, $9800 + 32*6 + 11 call PrintString - ; the card data is loaded asynchronously, initiated in CardReadUpdate - ret - -CardReadTeardown: + ld hl, SHUFFLED_DECK+1 + ld a, [vSelectedSpreadCard] + ld c, a + ld b, 0 + add hl, bc + ld a, [hl] + ld [vSelectedCardIndex], a + ld [vPreviousCardIndex], a + + call LoadCardData + + ld a, 0 + ld [vBlocked], a ret + UITilemap: db $0e, $0a, $0a, $0a, $0a, $0a, $0a, $0a, $0a, $0f, $09, $02, $02, $02, $02, $02, $02, $02, $08, $01 diff --git a/ScreenMainMenu.inc b/ScreenMainMenu.inc index f635f16..ab4f550 100644 --- a/ScreenMainMenu.inc +++ b/ScreenMainMenu.inc @@ -5,11 +5,12 @@ def vSelectedSpreadCard equ vSelectedSpreadIndex + 1 ; ff93 def vSelectedCardIndex equ vSelectedSpreadCard+1 ; ff94 DEF vFrameCountSquares EQU vSelectedCardIndex+1 ; ff95 DEF vTime EQU vFrameCountSquares+1 ; 16bit ; ff96 -println "after vTime is ", vTime+2 ; ff98 +def vBlocked EQU vTime+2 +println "vBlocked is ", vBlocked ; ; screen-specific variables -DEF vFrameCount1 EQU VARIABLES_START +DEF vFrameCount1 EQU SCREEN_VARS_START DEF vFrameCount2 equ vFrameCount1+1 DEF vFrameCount3 EQU vFrameCount2+1 DEF vMenuIndex equ vFrameCount3+1 @@ -118,24 +119,26 @@ MainMenuSetup_ScreenOff: cp a, l jp nz, :- - ld de, vTime+2 + ld de, SAFE_DMA_LOCATION ld a, $c0 call RunDMA ; set LCD and display registers ld a, %11100100 - ld [rBGP], a - ld [rOBP0], a + ldh [rBGP], a + ldh [rOBP0], a ld a, LCDCF_BLK21 | LCDCF_ON | LCDCF_BGON | LCDCF_OBJON | LCDCF_OBJ16 ldh [rLCDC], a ld a, 0 - ldh [vFrameCount1], a ; first starts at 0 - ldh [vTime], a - ldh [vTime+1], a - ldh [vMenuIndex], a - ldh [vMenuIndexPrevious], a + ld [vFrameCount1], a ; first starts at 0 + ld [vTime], a + ld [vTime+1], a + ld [vMenuIndex], a + ld [vMenuIndexPrevious], a + + ld [vSelectedSpreadCard], a ; second starts at 1/3 length which is approximately L/2 - L/4 - L/8 + L/16 ? @@ -157,7 +160,7 @@ MainMenuSetup_ScreenOff: srl b add a, b ; L - L/2 - L/4 + L/8 - L/16 + L/32 - L/64 + L/128 ; that should be approx 1/3 of L ! - ldh [vFrameCount2], a + ld [vFrameCount2], a ; third starts at 2/3 length which is approximately L/2 - L/4 + L/8 - L/16 ? ld hl, Coords @@ -178,7 +181,7 @@ MainMenuSetup_ScreenOff: srl b sub a, b ; L - L/2 + L/4 - L/8 + L/16 - L/32 + L/64 - L/128 ; that should be just about 2/3 of L ! - ldh [vFrameCount3], a + ld [vFrameCount3], a ;ld hl, Coords ;ld a, [hl] @@ -186,7 +189,7 @@ MainMenuSetup_ScreenOff: ;ldh [vFrameCount4], a ld a, 0 - ldh [vFrameCountSquares], a + ld [vFrameCountSquares], a ; load graphics into vram for deck face @@ -202,7 +205,7 @@ MainMenuUpdate: ; if timer is max, turn off animation state and unblock? ld hl, rMYBTNP - ldh a, [vMenuIndex] + ld a, [vMenuIndex] bit 3, [hl] ; select the down key jp z, .doneWithDownInput ; skip the following code if down is not pressed inc a @@ -213,7 +216,7 @@ MainMenuUpdate: .doneWithUpInput ld hl, MenuCount call ArrayClampLooping - ldh [vMenuIndex], a + ld [vMenuIndex], a ld hl, rMYBTNP @@ -230,7 +233,7 @@ MainMenuUpdate: jp .doneWithMenuSelect .option1 ld a, 0 - ldh [vSelectedSpreadIndex], a + ld [vSelectedSpreadIndex], a ld hl, ScreenSpreadSelect call ChangeScene ret @@ -240,56 +243,56 @@ MainMenuUpdate: ret .option3 ld a, 0 - ldh [vSelectedCardIndex], a + ld [vSelectedCardIndex], a ld hl, ScreenCardBrowse call ChangeScene ret .doneWithMenuSelect - ldh a, [rDELTAT] + ld a, [rDELTAT] ld b, a - ldh a, [vTime] + ld a, [vTime] add a, b - ldh [vTime], a - ldh a, [vTime+1] + ld [vTime], a + ld a, [vTime+1] adc a, 0 - ldh [vTime+1], a ; increment time. when the 16bit time register is greater + ld [vTime+1], a ; increment time. when the 16bit time register is greater ; than 4096 ($10_00) then one second has passed. so that's satisfied when ; vTime+1 is equal to or greater than $10 - ldh a, [vTime+1] + ld a, [vTime+1] cp a, $01 jp c, MainMenuUpdate_Done ; if the timer is less than $0100, skip to end ;otherwise reset the timer ld a, 0 - ldh [vTime], a - ldh [vTime+1], a + ld [vTime], a + ld [vTime+1], a ld hl, Coords ; and advance the frame counts - ldh a, [vFrameCount1] + ld a, [vFrameCount1] inc a call ArrayClampLooping - ldh [vFrameCount1], a + ld [vFrameCount1], a - ldh a, [vFrameCount2] + ld a, [vFrameCount2] inc a call ArrayClampLooping - ldh [vFrameCount2], a + ld [vFrameCount2], a - ldh a, [vFrameCount3] + ld a, [vFrameCount3] inc a call ArrayClampLooping - ldh [vFrameCount3], a + ld [vFrameCount3], a ld hl, SquaresTiles - ldh a, [vFrameCountSquares] + ld a, [vFrameCountSquares] inc a call ArrayClampLooping - ldh [vFrameCountSquares], a + ld [vFrameCountSquares], a @@ -381,7 +384,7 @@ MainMenuDraw: ld hl, SquaresTiles inc hl ld b, 0 - ldh a, [vFrameCountSquares] + ld a, [vFrameCountSquares] ld c, a add hl, bc add hl, bc diff --git a/ScreenShuffle.inc b/ScreenShuffle.inc index 2b2d151..97156f6 100644 --- a/ScreenShuffle.inc +++ b/ScreenShuffle.inc @@ -1,7 +1,7 @@ ; screen variables already ddefined in screencardread ;DEF vPreviousCardIndex EQU VARIABLES_START ;def vBlocked equ vPreviousCardIndex + 1 -def vAnimationFrame EQU VARIABLES_START +def vAnimationFrame EQU SCREEN_VARS_START def vAnimationState EQU vAnimationFrame+1 def vCurrentAnimation EQU vAnimationState+1 ; 2 bytes def vShuffleIndex equ vCurrentAnimation+2 @@ -17,71 +17,44 @@ ShuffleSetup: ld hl, SHUFFLED_DECK ld a, [hl] dec a - ldh [vShuffleIndex], a + ld [vShuffleIndex], a ld a, 0 - ldh [vAnimationFrame], a - ldh [vAnimationState], a + ld [vAnimationFrame], a + ld [vAnimationState], a + + ld hl, .asyncTask + call Async_Spawn_HL + ret +.asyncTask ld a, LOW(ShuffleAnimationRight) - ldh [vCurrentAnimation], a + ld [vCurrentAnimation], a ld a, HIGH(ShuffleAnimationRight) - ldh [vCurrentAnimation+1], a + ld [vCurrentAnimation+1], a - ld a, LOW(RunDMA) ; zero out the OAM - ld [vAsyncNext], a - ld a, HIGH(RunDMA) - ld [vAsyncNext+1], a - ld a, LOW(.loadUIMap) - ld [vAsyncAfter], a - ld a, HIGH(.loadUIMap) - ld [vAsyncAfter+1], a - - ld a, HIGH(ZEROES) - ld de, vMenuIndexPrevious+2 ; arguments to the first async call. - - call DoInAsyncVBlank - - ld hl, ZEROES ; hl is source - ld de, $c000 ; de is destination - ld bc, $100 ; length to copy + ld hl, ZEROES + ld de, MY_OAM + ld bc, $100 call CopyRangeUnsafe - ret - -.loadUIMap + ld de, SAFE_DMA_LOCATION + ld a, HIGH(ZEROES) + call RunDMA ld hl, Shuffle.UITilemap ; origin ld de, $9800 ; destination ld b, 18 ; height ld c, 20 ; width - - ld a, LOW(CopyTilesToMapThreadsafe) - ld [vAsyncNext], a - ld a, HIGH(CopyTilesToMapThreadsafe) - ld [vAsyncNext+1], a - - ld a, LOW(.loadTiles) - ld [vAsyncAfter], a - ld a, HIGH(.loadTiles) - ld [vAsyncAfter+1], a - ret -.loadTiles + call CopyTilesToMapUnsafe + ld hl, Shuffle.UITileData ld de, $9000 + VARIABLE_TILES_START*16 ld bc, Shuffle.UITileDataEnd - Shuffle.UITileData + call CopyRangeUnsafe - ld a, LOW(CopyRangeUnsafe) - ldh [vAsyncNext], a - ld a, HIGH(CopyRangeUnsafe) - ldh [vAsyncNext+1], a - ld a, LOW(.drawBigCard) - ld [vAsyncAfter], a - ld a, HIGH(.drawBigCard) - ld [vAsyncAfter+1], a - ret -.drawBigCard - ld hl, $9800 + $20*5 + 8 + ; manually drawing the Big Card + ld hl, $9800 + $20*5 + 7 ld a, VARIABLE_TILES_START ld [hl+], a inc a @@ -93,7 +66,6 @@ ShuffleSetup: ld c, 32 - 4 add hl, bc - inc a ld [hl+], a inc a @@ -159,7 +131,7 @@ ShuffleSetup: ShuffleUpdate: ld hl, vShuffleTime - ldh a, [rDELTAT] + ld a, [rDELTAT] ld b, a ld a, [hl] add a, b @@ -171,7 +143,7 @@ ShuffleUpdate: ld hl, vTime - ldh a, [rDELTAT] + ld a, [rDELTAT] ld b, a ld a, [hl] add a, b @@ -192,10 +164,10 @@ ShuffleUpdate: ld [hl], a ld hl, SquaresTiles - ldh a, [vFrameCountSquares] + ld a, [vFrameCountSquares] inc a call ArrayClampLooping - ldh [vFrameCountSquares], a + ld [vFrameCountSquares], a .doneTimer @@ -204,10 +176,10 @@ ShuffleUpdate: ld a, 0 cp a, [hl] jr z, .noButtons - ldh a, [rLFSR] - ldh [rLFSR+1], a ; lfsr = (lfsr << 8) + (vShuffleTime & $ff) - ldh a, [vShuffleTime] - ldh [rLFSR], a + ld a, [rLFSR] + ld [rLFSR+1], a ; lfsr = (lfsr << 8) + (vShuffleTime & $ff) + ld a, [vShuffleTime] + ld [rLFSR], a .noButtons @@ -221,14 +193,14 @@ ShuffleUpdate: jp z, .doneWithRight ld a, 1 - ldh [vAnimationState], a + ld [vAnimationState], a ld a, 0 - ldh [vAnimationFrame], a + ld [vAnimationFrame], a ld a, LOW(ShuffleAnimationRight) - ldh [vCurrentAnimation], a + ld [vCurrentAnimation], a ld a, HIGH(ShuffleAnimationRight) - ldh [vCurrentAnimation+1], a + ld [vCurrentAnimation+1], a call DoSomeShuffling .doneWithRight @@ -236,43 +208,43 @@ ShuffleUpdate: jp z, .doneWithLeft ld a, 1 - ldh [vAnimationState], a + ld [vAnimationState], a ld a, 0 - ldh [vAnimationFrame], a + ld [vAnimationFrame], a ld a, LOW(ShuffleAnimationRightReturn) - ldh [vCurrentAnimation], a + ld [vCurrentAnimation], a ld a, HIGH(ShuffleAnimationRightReturn) - ldh [vCurrentAnimation+1], a + ld [vCurrentAnimation+1], a .doneWithLeft ;animation logic! - ldh a, [vCurrentAnimation] + ld a, [vCurrentAnimation] ld l, a - ldh a, [vCurrentAnimation+1] + ld a, [vCurrentAnimation+1] ld h, a ; fetch current animation - ldh a, [vAnimationState] + ld a, [vAnimationState] or a, a jp z, .doneWithAnimation - ldh a, [vAnimationFrame] + ld a, [vAnimationFrame] inc a cp a, [hl] jp nz, .animNotDone dec a - ldh [vAnimationFrame], a + ld [vAnimationFrame], a ld a, 0 ld [vAnimationState], a - ldh a, [vAnimationFrame] + ld a, [vAnimationFrame] .animNotDone call ArrayClampLooping - ldh [vAnimationFrame], a + ld [vAnimationFrame], a .doneWithAnimation inc hl ld b, 0 - ldh a, [vAnimationFrame] + ld a, [vAnimationFrame] ld c, a add hl, bc add hl, bc ; two bytes per entry @@ -288,17 +260,21 @@ ShuffleUpdate: ld a, 32 ld e, a ld hl, $c000 - call DrawWholeCard + call DrawWholeCard ; hl memory location, b y, c x, e width, d wiggle ret ShuffleDraw: + ld de, SAFE_DMA_LOCATION ; safe bytes in hram for dma code to live at + ld a, HIGH(MY_OAM) + call RunDMA + ; the card data is loaded asynchronously, initiated in CardReadUpdate ld hl, SquaresTiles inc hl ld b, 0 - ldh a, [vFrameCountSquares] + ld a, [vFrameCountSquares] ld c, a add hl, bc add hl, bc @@ -310,10 +286,7 @@ ShuffleDraw: ld de, $8000+$100*16 + 1*16 ; tile number $101 is the sliding background ld bc, (SquaresTileset8 - SquaresTileset7) / 8 call CopyRangeUnsafeBy8s - - ld de, vMenuIndexPrevious+2 ; safe bytes in hram for dma code to live at - ld a, $c0 - call RunDMA + ret @@ -331,7 +304,7 @@ DoSomeShuffling: OneSwap: ; shuffles once and decrements vshuffleindex ; vShuffleIndex holds the index of the next card to swap with something - ldh a, [vShuffleIndex] + ld a, [vShuffleIndex] cp a, 1 jp z, .zeroIndex ; if we're swapping index 1 with index 0 skip it @@ -344,15 +317,15 @@ OneSwap: ; shuffles once and decrements vshuffleindex ld hl, SHUFFLED_DECK call SwapCards ; arguments c and e as indices to swap, hl as array in memory - ldh a, [vShuffleIndex] + ld a, [vShuffleIndex] dec a - ldh [vShuffleIndex], a ; decrement vshuffleindex so the next time around + ld [vShuffleIndex], a ; decrement vshuffleindex so the next time around ; we do the next step of the shuffle ret .zeroIndex ld a, [SHUFFLED_DECK] dec a - ldh [vShuffleIndex], a + ld [vShuffleIndex], a ret diff --git a/ScreenSpreadSelect.inc b/ScreenSpreadSelect.inc index e363eae..c12594a 100644 --- a/ScreenSpreadSelect.inc +++ b/ScreenSpreadSelect.inc @@ -1,4 +1,4 @@ -DEF vPreviousSpreadIndex EQU VARIABLES_START +DEF vPreviousSpreadIndex EQU SCREEN_VARS_START def vPreviousSpreadCard equ vPreviousSpreadIndex + 1 ScreenSpreadSelect: @@ -8,60 +8,40 @@ ScreenSpreadSelect: dw SpreadSelectTeardown SpreadSelectSetup: - ld a, 0 - ldh [vPreviousSpreadIndex], a - ldh [vSelectedSpreadCard], a - ldh [vPreviousSpreadCard], a + ld a, [vSelectedSpreadIndex] + ld [vPreviousSpreadIndex], a + ld a, [vSelectedSpreadCard] + ld [vPreviousSpreadCard], a + ld a, 1 + ld [vBlocked], a call UpdateCurrentSpread - ld a, LOW(RunDMA) ; zero out the OAM - ld [vAsyncNext], a - ld a, HIGH(RunDMA) - ld [vAsyncNext+1], a - ld a, LOW(.loadUIMap) - ld [vAsyncAfter], a - ld a, HIGH(.loadUIMap) - ld [vAsyncAfter+1], a - - ld a, HIGH(ZEROES) - ld de, $ffc0 ; arguments to the first async call. - - call DoInAsyncVBlank + ld hl, .asyncTask + call Async_Spawn_HL ret -.loadUIMap - +.asyncTask ; setup task to be executed async + ld a, HIGH(ZEROES) + ld de, SAFE_DMA_LOCATION + call RunDMA + ld hl, SpreadSelectTilemap ld de, $9800 ld b, 18 ld c, 20 - - ld a, LOW(CopyTilesToMapThreadsafe) - ld [vAsyncNext], a - ld a, HIGH(CopyTilesToMapThreadsafe) - ld [vAsyncNext+1], a - ld a, LOW(.loadFaceCardTiles) - ld [vAsyncAfter], a - ld a, HIGH(.loadFaceCardTiles) - ld [vAsyncAfter+1], a - - ret + call CopyTilesToMapUnsafe -.loadFaceCardTiles ld hl, CardPartTiles ld de, $9000 - ($10)*16 ld bc, CardPartTilesEnd - CardPartTiles - ld a, LOW(CopyRangeUnsafe) - ld [vAsyncNext], a - ld a, HIGH(CopyRangeUnsafe) - ld [vAsyncNext+1], a - ld a, LOW(DrawSpreadAsync) - ld [vAsyncAfter], a - ld a, HIGH(DrawSpreadAsync) - ld [vAsyncAfter+1], a + call CopyRangeUnsafe + + call DrawSpreadTask + ld a, 0 + ld [vBlocked], a ret SpreadSelectUpdate: @@ -92,10 +72,10 @@ SpreadSelectUpdate: .doneUp ld hl, Spreads call ArrayClampLooping - ldh [vSelectedSpreadIndex], a ; save clamped index + ld [vSelectedSpreadIndex], a ; save clamped index ; left and righgt - ldh a, [vSelectedSpreadCard] + ld a, [vSelectedSpreadCard] ld hl, rMYBTNP bit 1, [hl] jp z, .doneLeft ; skip the following code if left is not pressed @@ -113,43 +93,43 @@ SpreadSelectUpdate: ld l, c ; hl has current spread, a has index call ArrayClampLooping - ldh [vSelectedSpreadCard], a + ld [vSelectedSpreadCard], a ld hl, vTime - ldh a, [rDELTAT] + ld a, [rDELTAT] ld b, a - ldh a, [vTime] + ld a, [vTime] add a, b - ldh [vTime], a - ldh a, [vTime+1] + ld [vTime], a + ld a, [vTime+1] adc a, 0 - ldh [vTime+1], a ; increment time. when the 16bit time register is greater + ld [vTime+1], a ; increment time. when the 16bit time register is greater ; than 4096 ($10_00) then one second has passed. so that's satisfied when ; vTime+1 is equal to or greater than $10 - ldh a, [vTime+1] + ld a, [vTime+1] cp a, $01 jp c, .doneTimer ; if the timer is less than $0100, skip to end ;otherwise reset the timer ld a, 0 - ldh [vTime], a - ldh [vTime+1], a + ld [vTime], a + ld [vTime+1], a ld hl, SquaresTiles - ldh a, [vFrameCountSquares] + ld a, [vFrameCountSquares] inc a call ArrayClampLooping - ldh [vFrameCountSquares], a + ld [vFrameCountSquares], a .doneTimer - ldh a, [vSelectedSpreadIndex] + ld a, [vSelectedSpreadIndex] ld hl, vPreviousSpreadIndex cp a, [hl] jp nz, .spreadChanged ; update the spread if the spread changed - ldh a, [vSelectedSpreadCard] + ld a, [vSelectedSpreadCard] ld hl, vPreviousSpreadCard cp a, [hl] jp nz, .cardChanged ; update the spread if the card changed @@ -157,50 +137,41 @@ SpreadSelectUpdate: ret .spreadChanged - ld a, [vAsyncPC] - ld b, a - ld a, [vAsyncPC+1] - or a, b - ret nz ; early return if the async threadd is in use + ld a, [vBlocked] + cp a, 0 + ret nz ; early return if we're blocked + ld a, 1 + ld [vBlocked], a ; block! ld a, [vSelectedSpreadIndex] ld [vPreviousSpreadIndex], a ld a, 0 - ld [vSelectedSpreadCard], a + ld [vSelectedSpreadCard], a ld [vPreviousSpreadCard], a call UpdateCurrentSpread ; execute an async call to DrawSpreadAsync. - ld a, LOW(DrawSpreadAsync) - ld [vAsyncNext], a - ld a, HIGH(DrawSpreadAsync) - ld [vAsyncNext+1], a - - call DoInAsyncVBlank + ld hl, DrawSpreadTask + call Async_Spawn_HL ret .cardChanged - ld a, [vAsyncPC] - ld b, a - ld a, [vAsyncPC+1] - or a, b + ld a, [vBlocked] + cp a, 0 ret nz ; early return if the async threadd is in use + ld a, 1 + ld [vBlocked], a ; block! ld a, [vSelectedSpreadIndex] ld [vPreviousSpreadIndex], a ld a, [vSelectedSpreadCard] ld [vPreviousSpreadCard], a - call UpdateCurrentSpread - ; execute an async call to DrawSpreadAsync. - ld a, LOW(DrawSpreadALittleAsync) - ld [vAsyncNext], a - ld a, HIGH(DrawSpreadALittleAsync) - ld [vAsyncNext+1], a - - call DoInAsyncVBlank + ; execute an async call to DrawSpread. + ld hl, DrawSpreadTaskWithoutRefreshingBackgroundFirst + call Async_Spawn_HL ret @@ -220,12 +191,10 @@ UpdateCurrentSpread: add hl, de .skipCardDescription ; e has number of cards in spread - di call PassList call PassList ; one card description has two strings dec e ; this will not work if the spreadd had zero cardss. i will overflow. jp nz, .skipCardDescription - ei ld e, [hl] ; skip title of spread inc hl @@ -243,101 +212,54 @@ UpdateCurrentSpread: ld [vCurrentSpread+1], a ; save the current spread (hl) into vcurrentspread. ret -DrawSpreadAsync: - ldh a, [vSelectedSpreadIndex] - ldh [vPreviousSpreadIndex], a - +DrawSpreadTask: ; draw the spread large in the middle of the screen, and descs + ; clear the space to scrolling background tiles ld de, $9800 + 32*5 + 3 ld hl, ONES ld b, 8 ld c, 14 - - ld a, LOW(CopyTilesToMapThreadsafe) - ld [vAsyncNext], a - ld a, HIGH(CopyTilesToMapThreadsafe) - ld [vAsyncNext+1], a - ld a, LOW(.afterClear) - ld [vAsyncAfter], a - ld a, HIGH(.afterClear) - ld [vAsyncAfter+1], a - di - nop - ret ; return from async execution now that we've registered desire - ; to call copytilestomapthreadsafe - ; and then drawspread - -.afterClear + call CopyTilesToMapUnsafe +DrawSpreadTaskWithoutRefreshingBackgroundFirst: + ; step past the spread layout to get to the spread description ld a, [vCurrentSpread] ld l, a ld a, [vCurrentSpread+1] ld h, a ld e, [hl] ; e holds length of spread - di call PassList ; step past spread layout - ei - nop - nop .PassCardPositionDescriptions - di call PassList ; step past one pdesc call PassList ; step past two pdesc - ei dec e ; we've looked at one jp nz, .PassCardPositionDescriptions -.foundSpreadTitle + + ; now hl is pointing at the title string ld de, $9800 + 32 + 1 - ld a, LOW(PrintString) - ld [vAsyncNext], a - ld a, HIGH(PrintString) - ld [vAsyncNext+1], a - ld a, LOW(.afterTitle) - ld [vAsyncAfter], a - ld a, HIGH(.afterTitle) - ld [vAsyncAfter+1], a - di - nop + call PrintString + + ; now hl is pointing at the description + ld de, $9800 + (32*2) + 1 + call PrintString + + call DrawSpreadCards + + ld a, 0 + ld [vBlocked], a ret -.afterTitle - ld de, $9800 + (32*2) + 1 - ld a, LOW(PrintString) - ld [vAsyncNext], a - ld a, HIGH(PrintString) - ld [vAsyncNext+1], a - ld a, LOW(DrawSpreadALittleAsync) - ld [vAsyncAfter], a - ld a, HIGH(DrawSpreadALittleAsync) - ld [vAsyncAfter+1], a - di - nop - ret - -DrawSpreadALittleAsync: - +DrawSpreadCards: ld hl, $9800 + 32*5 + 3 - ldh a, [vSelectedSpreadCard] + ld a, [vSelectedSpreadCard] ld d, 0 ld e, a ; e contains the selected index - - ld a, LOW(DrawSpreadBigAndThreadsafe) - ld [vAsyncNext], a - ld a, HIGH(DrawSpreadBigAndThreadsafe) - ld [vAsyncNext+1], a - ld a, LOW(.drawSpreadPositionDescription) - ld [vAsyncAfter], a - ld a, HIGH(.drawSpreadPositionDescription) - ld [vAsyncAfter+1], a - di - nop - ret + call DrawSpreadBig ; draw the large cards for the spread .drawSpreadPositionDescription - di ld a, [vCurrentSpread] ld l, a ld a, [vCurrentSpread+1] - ld h, a ; hl points at beginning of card list + ld h, a ; hl points at beginning of card postion list call PassList ; hl points at first card description ld a, [vSelectedSpreadCard] ld e, a @@ -349,41 +271,18 @@ DrawSpreadALittleAsync: dec e jp nz, .stepForwardCardDescription .printIt - ei - nop - nop -.drawFirstDescription ld de, $9800+32*15 + 6 - ld a, LOW(PrintString) - ld [vAsyncNext], a - ld a, HIGH(PrintString) - ld [vAsyncNext+1], a - ld a, LOW(.drawSecondDescription) - ld [vAsyncAfter], a - ld a, HIGH(.drawSecondDescription) - ld [vAsyncAfter+1], a - di - nop - ret - -.drawSecondDescription + call PrintString ld de, $9800+32*16 + 6 - ld a, LOW(PrintString) - ld [vAsyncNext], a - ld a, HIGH(PrintString) - ld [vAsyncNext+1], a - ld a, 0 - ld [vAsyncAfter], a - ld [vAsyncAfter+1], a - di - nop + call PrintString + ret SpreadSelectDraw: ld hl, SquaresTiles inc hl ld b, 0 - ldh a, [vFrameCountSquares] + ld a, [vFrameCountSquares] ld c, a add hl, bc add hl, bc @@ -400,11 +299,10 @@ SpreadSelectDraw: SpreadSelectTeardown: ret -DrawSpreadBigAndThreadsafe: +DrawSpreadBig: ; hl for location on screen ; current spread address in vCurrentSpread ; e for index of selected card - di ld a, [vCurrentSpread] ld c, a ld a, [vCurrentSpread+1] @@ -415,10 +313,7 @@ DrawSpreadBigAndThreadsafe: ld d, a ; length of spread in d inc d .drawCards - ei - nop - nop - di + dec d jp z, .doneWithSpread ; if we're drawing zero remaining cards, stop drawing inc bc ; step forward @@ -451,22 +346,13 @@ DrawSpreadBigAndThreadsafe: ld h, b ld l, c; retrieve vram address - ei - nop - di - nop - nop call DrawBigCardSelected .doneDrawingSpread - di - nop - nop ret DrawBigCard: ; starting from screen location hl, draw a card at ;the location described in a -; saves de and is therefore not threadsafe push de ld d, a swap a @@ -573,7 +459,7 @@ DrawSpreadMinimap: pop hl jp .drawCards -.doneWithSpread ; stack has hl, bc, af at the time of jumping here +.doneWithSpread pop bc ; stack: af pop af ld d, 0 diff --git a/main.asm b/main.asm index 94c174d..7de367d 100644 --- a/main.asm +++ b/main.asm @@ -4,45 +4,54 @@ ; moss for keeping me from working sixteen hours a day and burning out ; yuri for letting me bounce ideas off you at all times + + ; 0xc100 CALL ; 0xc101 LOW - SCENE_SETUP points to this ; 0xc102 HIGH ; 0xc103 RET ; 0xc104 CALL ... def MY_OAM equ $c000 + +; $c100 - c120 call handles, scene stack and interrupt DEF SCENE_SETUP EQU $c101 -DEF SCENE_UPDATE EQU SCENE_SETUP + 4 ; call then ret is 3+1 bytes +DEF SCENE_UPDATE EQU SCENE_SETUP + 4 ; call then ret is 3+1 bytes DEF SCENE_DRAW EQU SCENE_UPDATE + 4 DEF SCENE_TEARDOWN EQU SCENE_DRAW + 4 +DEF INTERRUPT_LCD EQU SCENE_TEARDOWN + 4 -DEF INTERRUPT_LCD EQU $c111 -def SHUFFLED_DECK equ $c200 ; location for the shuffled deck -def CARD_VARIABLES equ $c300 +; each of these sections is way bgger than it needs to be +; i doubt any of them will hold more than $20 bytes at all +; but might as well put them at round numbers for now +def ASYNC_VARS_START equ $c200 ; this space's layout defined manually in async.inc +def SYSTEM_VARS_START equ $c300 ; system variables like buttons pressed, rng, time +def GLOBAL_VARS_START equ $c400 ; defined mostly in mainmenu, program-wide state +def SCREEN_VARS_START equ $c500 ; per-screen variables like animation stuff +def CARD_VARS_START equ $c600 ; variables for animation of individual cards + +def SHUFFLED_DECK equ $c700 ; location for the shuffled deck def ZEROES equ $D000 def ONES equ $D200 -DEF ASYNC_VARS_START equ $ff80 ; these are defined manually in async.inc -DEF SYSTEM_VARS_START equ ASYNC_VARS_START + $10 -; allocating $8 spaces for system variables, currently only using $3 bytes +; allocating $8 spaces for system variables, currently only using $4 bytes DEF rMYBTN EQU SYSTEM_VARS_START DEF rMYBTNP EQU rMYBTN + 1 DEF rDELTAT EQU rMYBTNP + 1 ; delta_t where $1000 = 1 second def rLFSR equ rDELTAT + 1 ; 16 bit -DEF GLOBAL_VARS_START EQU SYSTEM_VARS_START + $8 -; global app-wide variables go after GLOBAL_VARS_START. allocated $10 for them -def VARIABLES_START equ GLOBAL_VARS_START + $10 -; screen-specific variables live after VARIABLES_START + ; WRAM Layout looks like this: ; $c000 - $c100 OAM DMA source ; $c100 - $c114 self-modifying code interrupts ; $c200 - $c21a shuffled deck ; $c300 - $c400 card display variables -; $c300-$c30f timers + ; $c300 - $c30f timers ; $c300 - $c400 card display variables ; $d000 - $d400 zeroes and ones, just because they're handy! +def SAFE_DMA_LOCATION equ $ffc1 + def VARIABLE_TILES_START equ 26 @@ -103,8 +112,8 @@ EntryPoint: ; initialize global variables ld a, 0 - ldh [vSelectedSpreadIndex], a - ldh [vSelectedCardIndex], a + ld [vSelectedSpreadIndex], a + ld [vSelectedCardIndex], a ld hl, Cards ld b, [hl] ld hl, SHUFFLED_DECK @@ -117,7 +126,7 @@ EntryPoint: jr nz, .writeCard ld a, %1010_1010 - ldh [rLFSR], a + ld [rLFSR], a ; move on to framework stuff @@ -205,7 +214,7 @@ Loop: sra a sra a add a, 64 - ldh [rDELTAT], a + ld [rDELTAT], a xor a, a ; zero a out in one cycle ; store dpad and btn state in the rMYBTN register @@ -228,12 +237,12 @@ Loop: swap a or a, b ld b, a ; put new input state in b - ldh a, [rMYBTN] ; previous input state in a + ld a, [rMYBTN] ; previous input state in a cpl ; a holds buttons which weren't pressed last frame and a, b ; select buttons which were pressed this frame, but not last frame - ldh [rMYBTNP], a ; save that as btnp state + ld [rMYBTNP], a ; save that as btnp state ld a, b ; select buttons which were pressed this frame - ldh [rMYBTN], a ; save that as btn state + ld [rMYBTN], a ; save that as btn state call SCENE_UPDATE - 1 ; hope this takes not too many scanlines! diff --git a/screendesigns.aseprite b/screendesigns.aseprite index 51fb7f0252ca875c365bc5550df74242f0656c6c..34bdc8d4872dde8ef2d784184b0df7cf880e1447 100644 GIT binary patch delta 48 zcmcbtHcO3BY9eDjVBjKv`GJ@dh?($U4j^U(Vr~Y) zfCq?~38-NiVDs33m=zCZC(%4cBJrqsL(@Ebc>@^}YaX#+)V#rEo*)qO62Jl^mYND$Hj^|dM6 diff --git a/source.zip b/source.zip index adec502d7e81bab8fcdadbdff7e66a054424b50a..901e789502aea1059cdfab880167ae7473db44ea 100644 GIT binary patch delta 10604 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 OUjyRHe@GHULHIu(LRSC) delta 15028 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