Early peek at the Timers/Scripting engine

This commit is contained in:
Lucas Scharenbroich 2021-08-24 15:45:33 -05:00
parent 31ab86b7fc
commit 48bb361730
2 changed files with 385 additions and 0 deletions

148
src/Script.s Normal file
View File

@ -0,0 +1,148 @@
; A simple scripting engine
;
; The scripting engine is driven by the GTE Timers, which are
; driven by the VBL Tick count.
;
; The scripting engine is composed of a sequence of simple commands, defined as follows
;
; COMMAND ARG1 ARG2 ARG3
;
; The COMMAND word has its bits defined as:
;
; bit 15 = 1 if the end of a sequece
; bit 14 = 0 proceed to next action, 1 jump
; bit 13 = 0 (Reserved)
; bit 12 = 0 (Reserved)
; bit 11 - 8 = signed jump displacement
; bit 8 - 0 = command number
;
; The defined commands are
;
; COMMANDS ARG1 ARG2 ARG3
; -----------------------------------------------------
; $0002 SET_PALETTE_ENTRY ADDR COLOR ---- : Set the palette entry at ARG1 to the color in ARG2
; $0004 SWAP_PALETTE_ENTRY ADDR1 ADDR2 ---- : Swap the palette entries in ADDR1 <-> ADDR2
; $0010 CALLBACK LONG_ADDR PARAM : Call a user-defined function (JSL) with a parameter value in accumulator
; Start a new script
;
; A = low word of script command array
; X = high word of script command array
; Y = number of ticks between each command step
StartScript ENT
phb
phk
plb
phx ; Save the script array address
pha
lda #_DoScriptStep ; Try to create a timer for this script
ldx #^_DoScriptStep
clc
jsl AddTimer
bcs :err ; No timer slots available :(
tax ; Initialize the UeerData with the command array and PC
pla
sta Timers+8,x
pla
sta Timers+10,x
stz Timers+12,x ; Index of the commands
plb
rtl
:err
pla ; Pop the values and return with the carry flag set
pla
plb
rtl
_DoScriptStep
lda Timers+8,x
lda Timers+10,x
ldy Timers+12,y
lda [CmdList],y
pha ; save the full command word
and #$001E
tax
jsr (:commands,x)
pla ; restore the command word
bit #$4000 ; If the branch bit is set, change the command pointer
beq :no_jump
:no_jump
bit #$8000 ; If the terminate bit is set, remove this handler
beq :no_term
txa
jsl RemoveTimer
:no_term
rtl
:commands dw _Null,_SetPalEntry,_SwapPalEntry,_Null,_Null,_Null,_Null,_Null
dw _UserCallback,_Null,_Null,_Null,_Null,_Null,_Null,_Null
ARG1 equ 2
ARG2 equ 4
ARG3 equ 6
; Implementation of the built-in commands
_Null rts
_SetPalEntry
txy
ldx: ARG1,y
lda: ARG2,y
stal SHR_PALETTES,x
rts
_SwapPalEntry txy
ldx ARG1,y ; Load palette values
ldal SHR_PALETTES,x
pha
ldx ARG2,y
ldal SHR_PALETTES,x
ldx ARG1,y ; and swap
stal SHR_PALETTES,x
ldx ARG2,y
pla
stal SHR_PALETTES,x
rts
_UserCallback lda ARG1,x
sta :dispatch+1
lda ARG1+1,x
sta :dispatch+2
lda ARG3,x
:dispatch jsl $000000
rts

237
src/Timer.s Normal file
View File

@ -0,0 +1,237 @@
; Timer implementation
;
; The engire provides four timer slot that can be used by one-shot or
; recurring timers. Each timer is given an initial tick count, a
; reset tick count (0 = one-shot), and an action to perform.
;
; The timers handle overflow, so if a recurring timer has a tick count of 3
; and 7 VBL ticks have passed, then the timer will be fired twice and
; a tick count of 2 will be set.
;
; As such, the timers are appropriate to drive physical and other game
; behaviors at a frame-independent rate.
;
; A collection of 4 timers that are triggered when their countdown
; goes below zero. Each timer takes up 16 bytes
;
; A timer can fire multiple times during a singular evaluation. For example, if the
; timer delay is set to 1 and 3 VBL ticks happen, then the timer delta is -2, will fire,
; have the delay added and get -1, fire again, increment to zero, first again and then
; finally reset to 1.
;
; +0 counter decremented by the number of ticks since last run
; +2 reset copied into counter when triggered. 0 turns off the timer.
; +4 addr long address of timer routine
; +8 user 8 bytes of user data space for timer state
MAX_TIMERS equ 4
TIMER_REC_SIZE equ 16
lastTick ds 2
Timers ds TIMER_REC_SIZE*MAX_TIMERS
_GetVBLTicks
PushLong #0
_GetTick
pla
plx
rts
; Initialize the timers
InitTimers
jsr _GetVBLTicks
sta lastTick
lda #0
ldx #{TIMER_REC_SIZE*MAX_TIMERS}-2
:loop sta Timers,x
dex
dex
bne :loop
rts
; Add a new timer
;
; Input
; A = low word of timer callback function
; X = high word of timer callback function
; Y = number of ticks for the timer to delay
; C = set to 1 if the timer is a one-shot timer
;
; Return
; C = 0 if success, 1 if no timer slots are available
; A = timer slot ID if C = 0
AddTimer ENT
phb
php ; Save the input parameters
phx
pha
phy
phk
plb
ldx #0
:loop lda Timers,x ; If the counter is zero, timer is free
beq :freeslot
txa ; Advance to the next timer record
clc
adc #TIMER_REC_SIZE
tax
cpx #{TIMER_REC_SIZE*MAX_TIMERS}
bcc :loop
bra :notimers
:freeslot pla
sta Timer+0,x ; set the counter and
stz Timer+2,x ; default to a zero reset value
pla
sta Timer+4,x ; set the callback address
pla
sta Timer+6,x
stz Timer+8,x ; Clear the user data space
stz Timer+10,x ; Clear the user data space
stz Timer+12,x ; Clear the user data space
stz Timer+14,x ; Clear the user data space
plp
bcc :oneshot
lda Timer+0,x ; if not a one-shot, put the counter
sta Timer+2,x ; value into the reset field
:oneshot plb
txa ; return the slot ID and a success status
clc
rtl
:notimers ply
pla
plx
plp
plb
sec ; Return an error status
lda #0
rtl
; Small function to remove a timer
;
; A = Timer ID
RemoveTimer ENT
phb
phk
plb
cmp #{TIMER_REC_SIZE*{MAX_TIMERS-1}}+1
bcs :exit
tax
stz Timer,x
stz Timer+2,x
stz Timer+4,x
stz Timer+6,x
:exit
plb
rtl
; Execute the timer functions
DoTimers ENT
phb
phk
plb
jsr _GetVBLTicks
cmp lastTick ; Throttle to 60 fps
beq :exit
tax ; Calculate the increment
sec
sbc lastTick
stx lastTick
jsr _DoTimers
:exit plb
rtl
; Countdown the timers
;
; A = number of elapsed ticks
_DoTimers
pha
ldx #0
:loop
lda Timers,x ; A zero means do not fire
beq :skip
sec
sbc 1,s ; subtract the number of ticks
sta Timers,x
:retry beq :fire ; getting <= zero triggers
bpl :skip
:fire pha ; Save the counter
phx ; Save our index
lda Timers+4,x ; execute the timer callback
sta :dispatch+1
lda Timers+5,x
sta :dispatch+2
:dispatch jsl $000000
plx
pla
clc
adc Timers+2,x ; Add the increment
sta Timers,x ; Store in the count
bra :retry ; See if we have >0 ticks to wait until the next trigger
:skip txa
clc
adc #8
tax
cpx #8*MAX_TIMERS
bcc :loop
pla
rts