The interpreter also has a "last executed" variable, which contains the serial number of the latest command to finish executing. |
those parameters are zero, the operation would proceed. If a non-zero value is found, the interpreter would sleep and retry that field later. |
those parameters are equal or less than "last executed", the interp knows that the parameters are up to date, and the operation would proceed. If not, the interpreter would sleep and retry that field later. |
would look at each parameter on its "params to write" list. If the "latest writer" number matches the operation's serial number, it updates the parameter, then sets "latest writer" to zero, to indicate that the parameter is up to date. If the "latest writer" field is non-zero but doesn't match the serial number of the current operation, it updates the parameter but doesn't touch "latest writer". Some subsequent queued operation will also be writing to that parameter, and it shouldn't be marked as current until that operation finishes. (If an intermediate operation needed the result of the current operation, the interpreter would have stopped at that point and "latest writer" would contain the serial number of the current operation.) A complication occurs if the user aborts the program. If queued commands are discarded, the parameters that those params would have written to will still have non-zero "latest writer" values, which will not get cleared. Any subsequent command that needs to read one of those parameters would hang. A possible solution would be to keep a "last completed" serial number in the interpreter. Whenever a command finishes, it would update "last completed". Commands would still write their own serial numbers into the "latest writer" field of parameters that they intend to write when they start, but they would NOT examine or zero the field when they finish. The test for allowing an operation to be executed or queued would be 'all params to read have "latest writer" fields less than or equal to "last completed" ', instead of 'all params to read have "latest writer" fields set to zero'. With this arrangement, the only thing needed when aborting or flushing the queue is to set "last completed" to the current serial number, then increment the current number. The second arrangement is better in a number of ways - the command finishing code (which is probably realtime) doesn't need access to the "latest writer" fields of the entire parameter list, it only needs to be able to set "last completed". |
would update the interpreter's "last executed" variable. This is where I get a little fuzzy. Suppose operation 10 is queued, and operations 11 thru 20 are not (simple math expressions). If 11 thru 18 do not depend on anything that is written by 10, they can (and should) execute _before_ 10 finishes. If 19 does depend on something that 10 writes, it will have to wait until 10 completes. In particular, it will wait until "last executed" is greater or equal to 10. But if we allow 11 thru 18 to update "last executed" before 10 finishes, we have a problem. I know this can be solved, I just don't have a nice O(1) solution at the moment. |
If we want probe results (for example) to be available in parameters, we need a way to ensure that the interpreter doesn't read ahead and use the parameter value before the probe move finishes.
Here is one possible solution to that:
Every canonical command has two lists associated with it, a list of parameters that it wants to read, and a list of parameters it wants to write. G-code lines of the form #100 = [#100+#101*2] also have lists, which are created on the fly when the expression is parsed - in this case it reads parameters 100 and 101, and writes parameter 100.
Every canonical command issued, as well as every expression executed, is given a serial number, a 64 bit integer. That is large enough to ensure that the numbers will be monotonically increasing during any run.
Each parameter has associated with it a "latest writer" field, which accepts a serial number.
The interpreter also has a "last executed" variable, which contains the serial number of the latest command to finish executing.
In the interpreter, when it is time to evaluate an expression, or to execute a canonical, the code would loop through the list of "params to be read" for that operation. If the "latest writer" fields for each of those parameters are equal or less than "last executed", the interp knows that the parameters are up to date, and the operation would proceed. If not, the interpreter would sleep and retry that field later.
Once the operation is allowed to start, it would write its own serial number into the "latest writer" fields of every parameter on its "params to write" list. That would force subsequent operations that want to read those parameters to wait.
Once an operation finishes (which may be much later due to queuing), it would update the interpreter's "last executed" variable. This is where I get a little fuzzy. Suppose operation 10 is queued, and operations 11 thru 20 are not (simple math expressions). If 11 thru 18 do not depend on anything that is written by 10, they can (and should) execute _before_ 10 finishes. If 19 does depend on something that 10 writes, it will have to wait until 10 completes. In particular, it will wait until "last executed" is greater or equal to 10. But if we allow 11 thru 18 to update "last executed" before 10 finishes, we have a problem. I know this can be solved, I just don't have a nice O(1) solution at the moment.
I just realized that assignments don't need "params to write", since they are executed entirely in the interpreter. The need "params to read" to ensure that they are using up-to-date input values, but their outputs are guaranteed to be written to the parameters before the next statement starts. Only queued items need to mark their "params to write" with their serial numbers.
Another thought - this could be used to implement pre- and post-conditions. If we dedicate a parameter to each possible condition, then operations that need to have a condition satisified simply list that parameter under their "params to read". Likewise, operations that make a condition true would list that parameter under "params to write".