Hey there 6502 forum folks, been a while since I posted eh?
I'm still working on stuff for my "GameTank" console, but usually the software challenges I encounter are either non- specific to the 6502, or are exceedingly specific to my hardware configuration.
Getting to the point: I've gotten a lot of use of Fusik's INFLATE routine in my game programming, but the fact that it wants to do a whole file in one go is getting to be somewhat limiting.
Ideally I'd like to be able to start a decompression and pause it when an interrupt comes in, to be resumed later when free cycles would otherwise be spent waiting for vertical sync.
While I could probably manage to edit the implementation to run single iterations at a time, I'm thinking there might be cleaner and more generic approaches to putting long running work on the back burner.
What are some ways you would handle or have handled spreading a long runtime operation across separate blocks of spare cycles on a 6502-based system?
One method I'm thinking of trying is moving my rendering and game logic loop to the interrupt handler, and using the reset vector context as a pseudo background thread.
Suspending and resuming long processes
Re: Suspending and resuming long processes
Example usage is that I use INFLATE to unpack a lot of graphics between levels, but they can take a while. When it's known for sure that a graphic will be needed in the next level it could be loaded in spare cycles while playing the current one.
- commodorejohn
- Posts: 299
- Joined: 21 Jan 2016
- Location: Placerville, CA
- Contact:
Re: Suspending and resuming long processes
If you're juggling more than just interrupt-service stuff alongside your Big Intensive Background Task, and you don't want to have to modify that, there's about two ways I can see for you to go:
A. Have your interrupt service, upon completion, pass control to any active "medium-priority" tasks - things that run periodically but aren't time-critical - which then return control to the Background Task. Essentially, make everything above the Background Task cooperatively multitasked, and let the Background Task soak up any spare cycles.
B. Implement a true preemptive scheduler with basic priority handling; keep genuinely real-time critical stuff (input polling, page-flipping on the display, firing music notes/SFX at the right moment) in the interrupt handler, but move "normal game interrupt stuff" (input processing, script execution, background drawing operations, all the other processing for music/SFX) into high-priority tasks, and the Background Task running at the lowest priority. Tasks which complete can cede the rest of their time-slice to the next highest-priority task, as determined by the scheduler.
A. is likely the simplest; B. is more flexible in the long run, but will probably require you to increase the interrupt rate to achieve a reasonable time granularity.
A. Have your interrupt service, upon completion, pass control to any active "medium-priority" tasks - things that run periodically but aren't time-critical - which then return control to the Background Task. Essentially, make everything above the Background Task cooperatively multitasked, and let the Background Task soak up any spare cycles.
B. Implement a true preemptive scheduler with basic priority handling; keep genuinely real-time critical stuff (input polling, page-flipping on the display, firing music notes/SFX at the right moment) in the interrupt handler, but move "normal game interrupt stuff" (input processing, script execution, background drawing operations, all the other processing for music/SFX) into high-priority tasks, and the Background Task running at the lowest priority. Tasks which complete can cede the rest of their time-slice to the next highest-priority task, as determined by the scheduler.
A. is likely the simplest; B. is more flexible in the long run, but will probably require you to increase the interrupt rate to achieve a reasonable time granularity.
Re: Suspending and resuming long processes
Regarding "A", you probably want to enable interrupts again after the true interrupt servicing ends, and take care with reentrancy (so if a second interrupt comes in, just RTI from it after handling the interrupt, don't let it fall through into the game update code which is already halfway through one level further down).
Generically you can think of the stack as a context buffer. When your background talk is interrupted, if you push all the registers to the stack, then the SP value is all you need to resume it. But you could instead stash SP somewhere, set it to a new value, and resume some different work. This is roughly how my preemptive multitasking system works.
So you could for example have your background task's stack start at FF and foreground at 7F. On interrupt, after the interrupt completes, you can either resume the task that was already running (with just RTI) or stash its stack pointer in a known location before restoring the other task's stack pointer and resuming that one.
If it needs to, your ISR can easily tell which task was running based on the stack pointer value. So if it was the high priority task, just resume it, but if it was the low priority task, resume the high priority one instead.
When the high priority one runs out of work to do it also needs to yield to the low priority one - an easy way may be to issue BRK and have the ISR recognise that this means there's no high priority work to do and it should resume the low priority task instead.
In your case though with only two tasks I think this is overkill, and it is simpler to just have one stack, use a marker byte in memory to indicate whether high priority work was in progress, and for the ISR based on that to either RTI directly, or enable interrupts and initiate the next cycle of high priority work.
Generically you can think of the stack as a context buffer. When your background talk is interrupted, if you push all the registers to the stack, then the SP value is all you need to resume it. But you could instead stash SP somewhere, set it to a new value, and resume some different work. This is roughly how my preemptive multitasking system works.
So you could for example have your background task's stack start at FF and foreground at 7F. On interrupt, after the interrupt completes, you can either resume the task that was already running (with just RTI) or stash its stack pointer in a known location before restoring the other task's stack pointer and resuming that one.
If it needs to, your ISR can easily tell which task was running based on the stack pointer value. So if it was the high priority task, just resume it, but if it was the low priority task, resume the high priority one instead.
When the high priority one runs out of work to do it also needs to yield to the low priority one - an easy way may be to issue BRK and have the ISR recognise that this means there's no high priority work to do and it should resume the low priority task instead.
In your case though with only two tasks I think this is overkill, and it is simpler to just have one stack, use a marker byte in memory to indicate whether high priority work was in progress, and for the ISR based on that to either RTI directly, or enable interrupts and initiate the next cycle of high priority work.