Post by gtoal on Apr 28, 2020 9:07:17 GMT -5
I'm working on a game that can be redrawn within the 50hz frame rate but which does a huge number-crunching computation whenever the human makes a move. I've been mulling over a general mechanism for this situation, which comes up a lot...
Here's a sketch of what I'm thinking might be possible...
The code that implements the thread may need to set up its own private stack. Can't rely on just using
static variables because simple C expression evaluation can make use of the stack and an interrupt may
hit during that part of the computation. So needs to be handled much like coroutines.
So the overall philosophy is *NOT* to run in an interrupt routine, but to run in the foreground until
interrupted, and return to the foreground loop when that happens. This is very much standard coroutines
except that the coroutine execution is stopped on a timer interrupt. The code then goes on to perform
a Wait_Recal() as it normaly would have, within the 30,000 cycle limit since the last one, and relies on
the normal computation and drawing being done in noticably less than that amount of time - for example in
27000 or 28000 cycles, with only a few thousand cycles free for the longer 'background' computation.
That's the theory. Now I have to think of a way to implement it (with your help...)
Here's a sketch of what I'm thinking might be possible...
static int long_computation_started = 0;
void do_some_long_computation(void) {
long_computation_started = 1;
// code to do number crunching that may take more than say 100,000 cycles to complete
// and has no simple way of running in smaller chunks...
long_computation_started = 0;
}
// main loop
for (;;) {
Wait_Recal();
Start_timer(); // start a timer that will fire in just under 30,000 cycles
// perform game logic and graphics that are known to finish in less than 30,000 cycles
do_simple_computations();
draw_frame();
// we now have a variable number of cycles available until the end of the next scheduled Wait_Recal()
if (long_computation_started == 0 && /* the computation is needed...*/) {
start_thread(do_some_long_computation); // runs until hit by interrupt, just short of 30000 cycles
// since last Wait_Recal(). Information for "rti" is stored
// in a static area for later.
} else if (long_computation_started) {
resume_thread(); // restarts thread that was suspended on previous frame.
// restores registers, jumps to where previous call was
// interrupted. Note that this code is still running as
// a foreground process, it is not running as an interrupt
// routine. Again it runs until just short of when a WaitRecal()
// is due.
}
// Either start_thread or resume_thread can return normally when the long background computation
// is complete. But if the computation is still running when the timer goes off, start_thread or
// resume_thread will also return to here but with the computation unfinished and the info for
// resuming on the next cycle stored in a static area ready for the next resume_thread()
}
The code that implements the thread may need to set up its own private stack. Can't rely on just using
static variables because simple C expression evaluation can make use of the stack and an interrupt may
hit during that part of the computation. So needs to be handled much like coroutines.
So the overall philosophy is *NOT* to run in an interrupt routine, but to run in the foreground until
interrupted, and return to the foreground loop when that happens. This is very much standard coroutines
except that the coroutine execution is stopped on a timer interrupt. The code then goes on to perform
a Wait_Recal() as it normaly would have, within the 30,000 cycle limit since the last one, and relies on
the normal computation and drawing being done in noticably less than that amount of time - for example in
27000 or 28000 cycles, with only a few thousand cycles free for the longer 'background' computation.
That's the theory. Now I have to think of a way to implement it (with your help...)