We'll be talking about looping in programming, not just your usually for loops that complete their task quite quickly and move along, but while loops that come together together to form an interactive program which exists over time.
fixed frequency loops (FFL)
We say that a while loop is running at a frequency F Hz if each iteration of the while loop takes approximately 1 / F seconds to complete on each iteration. Additionally we will call a loop which runs at a frequency a fixed frequency loop (FFL)
If you just make an empty `while (true)` loop and run it, it will start running at a frequency which is less than the max possible frequency which would be the clock speed of your CPU, additionally at this speed there will not be much room for anyting else to be run on the cpu, and thus by using other programs on your computer the rate of this loop will reduce.
If we want to have a for loop where we can designate its frequency then we need some logic to do so.
sleep based FFLs (SBFFL)
Suppose we have some logic which want to run at a fixed rate, we can package all that logic up into some function f, and now we do the following.
Given the target frequency of F Hz, then to compute the period we do P = 1 / F which is the duration in seconds of one cycle, inside the loop we can run the function f and measure how long that took, and suppose it took X seconds, then the remaining time budget is given by P - X, and so long as that value is positive, then we sleep for P - X seconds.
Fundamentally SBFFL's should be thought of as drop in replacements for while loops, with the ability to control how fast they run.
Sleep based loops cannot be nested because once one starts, it will iterate forever (or until you ask it to stop)
multiple systems which run at different rates
As noted above, we see that SBFFL's cannot be nested to allow multiple systems to run at different rates. So how can we do this? In order to do it we introduce things known as periodic signals (PS). Periodic signals are objects which can do timing so long as they're being called frequently, whenever their update function is called it will return a boolean to denote whether or not it's time to run the logic associated with this frequency.
Since each periodic signal can run at its own frequency then each system can have its own periodic signal, and when the update function says it's time to run it will run the associated logic.
As we mentioned, the update function of every periodic signal needs to be called frequently, and usually the way we get it to run frequently is by embedding it inside of some other loop, we could use a while loop, but as mentioned in the SBFFL section, a while loop eats up the cpu, so using a SBFFL as the outer loop is a good approach.
gotchas for periodic signals
they are delayed
When you call the update function of a periodic signal, it only returns true if the signal has already occurred, this means that there's always some reaction time to the fact that the signal changed, which may be different on each iteration. The good thing is that most of the time the delay/reaction time is the same every time or at least similar and it means that you still react close to the requested frequency.
outer loop must run faster
The first restriction on the outer loop that contains a periodic signal is that it should be running at a frequency that is greater than the requested frequency of the periodic signal, for example if the outer loop is running at 1Hz, and the periodic signal is supposed to run at 2Hz, we have a problem because a TAFFL only gets the chance to run its code at the rate that the outer loops body is iterated, therefore the periodic signal will run at 1Hz.
outer loop determines accuracy
Even if you're outer loop is running faster, how fast it runs determines the resolution/accuracy of the inner periodic signal.
Suppose the outer loop runs at frequency X Hz and the periodic signal wants to run at frequency Y Hz, based on ECR, there will be a point in time at which the outer loop will iterate right before the periodic signal's update function would've returned true.
Specifically suppose that the peridoic signal would have run at time T. One way to think of T is that T = L + 1/Y where L was the time of the last signal. But now suppose we check the PS's condition at time T - epsilon where epsilon is a small number, then the condition is false, so now we have to wait a duration of 1 / X, to get back to this line of code (ie the period of the outer loop). thus this leads then the peridi will not call its body until 1 / Y + 1 / X, when it should call it every 1 / Y seconds. The take away here is that a TAFFL will run its logic at the requested frequency with error at most the period of the outer loop (the duration of one extra outer loop iteration)
When we get back to this line of code the current time will be T - epsilon + 1 / X = 1 / Y + 1 / X - epsilon. So with respect to the target period of 1/Y we've now accumulated a 1 / X - epsilon error.
One way to slightly improve accuracy is to compare how much error would be produced running the logic associated with the PS right now vs running it next tick of the outer loop. If the current amount of time since the last PS signal is given by A, and the period of the PS is P = 1 / Y, then if the amount of time is closer to the period as compared to amount of time since the last PS on the next iteration which is given by A + the amount of time that the outer loop takes (1 / X), that is: abs(P - A) < abs(A + (1/X) - P), we should just iterate on this iteration rather than waiting until the next iteration, then cuts down on the maximum error bringing it to (1/X) / 2.
Because the outer loop determines the error of the PS, and the maximum error is given by 1 / X, then in order to minimize the error an easy way to do so is to simply increase X and make the outer loop run faster, in a usual PS setup you use a SBFFL on the outer loop which runs alot faster than the inner loop so that the error is minimal, and then inside the outer loop you can put as many PS based systems as you need.
general facts
loops aren't perfect
Eventually Collision Rule (ECR): Given two frequencies which are different, then in theory they will eventually tick at the same time, the reason this is true is because on a computer we can only represent rational numbers, and thus we multiply each side by the lcm of the denominators and obtain an integer ratio and then use the lcm of those ratios to determine when the two frequencies will collide.When working with real loops on computers and not theoretical frequencies, ECR becomes even more prevalent, when given two frequencies which are identical, but are run in two different machines, each of which counts its time independently, its most likely the case that the two loops will drift and eventually tick at the same time, in general when we have our loops we just have to be in the mindset that they are not perfect but will be doing their best, and having these types of issues
paying time forward for more accuracy
As mentioned, TAFFLE based loops compute the time since the last time they iterated and then if this exceeds the period, it runs the logic, but suppose the period of the TAFFL is given by P, and then the amount of time since the last iteration is A, if A > P, then if we simply reset A to 0 after we run the logic, then our lateness A - P will not be accounted for making the loop run slower than we requested, thus in order fix this slower than requested problem, for the next iteration we need ot make sure that we remove the late time, so that we want to make sure that we run the next loop if under the following condition.
First we denote the new amount of time since the last iteration (denoted by CA for current amount of time), and the amount of time waited on the last iteration as LA, then our condition on the current iteration to run the TAFFL body is if (CA > (P - (LA - P)) <=> CA > 2P - LA. This equation makes it so that if we were late on the last iteration we need to be early on the next iteration allow us to not be so slow.
I feel like there are more intricacies to how paying time forward works, come back to this later