machinekit / machinekit-cnc

CNC stack split out into a separate package
Other
60 stars 37 forks source link

Interpreter API: rearchitecting required #7

Open ArcEye opened 6 years ago

ArcEye commented 6 years ago

Issue by mhaberler Sun Apr 6 19:21:03 2014 Originally opened as https://github.com/machinekit/machinekit/issues/106


The way the G-code interpreter is integrated into task is seriously convoluted and suggests reconsideration.

This document describes the status and issues: https://github.com/mhaberler/asciidoc-sandbox/wiki/Interpreter-control-flow-explained

A prelimimary requirements draft is here: https://github.com/mhaberler/asciidoc-sandbox/wiki/Interpreter-API-requirements

The removal of NML does require a revision of task, interpreter and the task/motion interfaces anyway, so it makes sense to redesign this API.

This is a high-priority item because it needs to be addressed before the task/motion/interpreter complex can be switch away from NML.

ArcEye commented 6 years ago

Comment by bobyellin Sun Apr 6 19:50:56 2014


This is quite clear in it’s description of the structure and limitations of the existing architecture. I need to think on your new requirements a bit, and don’t want to give you a reply that is not well thought out. However, my VERY initial impressions are that we still have some things pathologically coupled, left over from our legacy. Just thinking aloud at the moment, but want to give you food for thought.

Can we make an interpreter instance stateless relative to machine state? Each interpreter instance is a process, and furthermore, it is recursively executable. Let’s try to eliminate the notion of a periodic SYNCH in favor of a different paradigm.

We have a situation today where task and interpreter have a little dance, each keeping a copy of “state,” and the notion of queue-buster and synch due to redundant and possibly outdated machine states. The "state” should be thought of as it’s own object, and maintained as such, as a shared service with multiple subscribers, and NOT an attribute of any interpreter instance. Of course it would have the appropriate integrity structures build around it to prevent concurrent/destructive update as in any shared data structure. But, primtiives such as get_state and set_state should be interlaced throughout an instance of any interpreters execution (and everybody else’s execution who consumes or updates it). It is each subscribers responsibility to get the current state prior to initiating any actions. In the interpreters case, this would be its output.

The only state the interpreter needs to keep as it’s own are variables related to looping and conditionals in G code. But here is where the beauty of recursion comes in. The instance of the interpreter that is running a conditional block keeps the looping context (this is an appropriate state for the instance to keep as it is a function of that instance, not the machine), and delegates the execution of each line within the block to recursive execution. Given such a structure, hitting PAUSE in the middle of a conditional block of code that relies on a PARAM, then executing a MIDI line which changes that PARAM , then resuming with CYCLE start and having the loop behavior change should not be an issue at all. In other words, one can always execute a single line of G-code from anywhere.

My ignorance of Linuxcnc may come out here, so what follows may be useless opinion. Each line of code needs an identifier, as you aptly pointed out, so that all subscribers know exactly where the code was when a stop or abort occurs. The current line should be part of the system state, set by each interpreter instance. We need a way to properly denote MDI vs file originated lines of code. For that matter, the “current line” needs to be a stack structure. Think in terms of a main line g code program, executing an O work file, which is interrupted by a PAUSE, and then a MDI line, and then resumed via RUN. Each interpreter instance involved in this stream needs to add and delete it’s current line from the stack as appropriate. If motion control is executed synchronously (not sure if this is possible), as opposed to consuming Canon’s output queue, keeping track of where we are relative to source input becomes a matter of popping the “ current line “ stack. If this is not possible, then the current line id needs to be interjected into motion control’s input queue as a bookend structure.

Some other thoughts on enhancements top think about later. Solicitation of input from the user from the interpreters context, and allowing for blocking is handled nicely by your scheme. However, the practicalities of getting the input can be dicey from a GUI and programming point of view. Let’s make it simple for the non-programmer to get user input. Might it be possible to invent some G or M codes that can be embedded in an O word routine that would allow users prompt the machine operator easily. Perhaps a dialog box in the GIMP window? Or a message text field that appears in that window? Some PARAM that can be tested with G-Code when the dialog box is answered. I know that Tormach is playing with a similar implementation for their mill controller.

ArcEye commented 6 years ago

Comment by mhaberler Sun Apr 6 20:09:10 2014


re: Can we make an interpreter instance stateless relative to machine state?

Depends on the language, but I dont see yet how that can be done with RS274NGC where machine state syncing is occasionally needed for position prediction (in theory it might be possible by dropping absolute mode, but that wouldnt be very useful).

The sync() doesnt bother me all that much, rather the current convoluted control structure; its just syncing maybe 20, 30 variables. I didnt outline ideas for the future sync() in detail but I imagine it would be a single RPC-type call, originating from an interpreter instance into haltalk, to retrieve a couple of HAL pin values. All that needs is making the relevant motion state scalars HAL pins; most of them are already I guess. That's be pretty easy.

ArcEye commented 6 years ago

Comment by mhaberler Sun Apr 6 20:19:36 2014


It just occurred to me another flow simplification could be this:

right now the code post a queuebuster monitors queue sizes (motion queue, interpreter list); once that reaches zero, the sync() is executed (by task calling into interp.sync()).

That monitoring logic (done in task) can be completely dropped like so: the interpreter queues a special canon command (lets call it READY_FOR_SYNC() for now) and records it's command ID; within motion, it's a noop - just the completion of this command is reported by signaling the command ID with status RCS_DONE).

Once the interpreter sees that command ID as 'completed', it does the sync() operation, and continues. Task need not be involved at all (I have that sneaking suspicion task does way too much anyway, I just cant tell yet what it is ;)

ArcEye commented 6 years ago

Comment by klerman Sun Apr 6 20:28:27 2014


To the term "current line" should mean a lot more than just a line number in a file. That's because of the introduction of subroutines. When the interpreter is stopped at a particular line, it is not just the line and the current variable values that define the context. The subroutine call stack is also part of that context.

That can be very important if you stop the program at a particular line and want to restart earlier in the program.

If it were up to me, the interpreter would interface with the "executor" (is that task) as follows: -- A canonical command that is passed for execution would carry a complete context with it -- The context would include a representation of the call stack together with its arguments -- -- The call stack would reference file names and line numbers within the file -- There is no need to actually copy the context. The context is available in the interpreter and could be accessed remotely. (Axis or any other gui would need access to the context.) Unfortunately, this probably breaks Axis badly.

You could think of stopping the interpreted program as what happens when you interrupt a program under the control of a debugger. You can examine all of the variables by name (or number), you can look at the stack, you can walk up the stack, you can single step, ...

There are those who suggest that they wouldn't use any of this stuff. "Production programs need to be straight line plain gcode." While I understand that might be true for some production environments, I take the stance that all of the subroutine and flow control stuff is important for prototype shops. The work I did on GWiz was an attempt to use subroutines to implement common operations like holes on a circle, surfacing, rectangular pocketing...

----- Sorry for the lack of organization of my thoughts.

Regards,

Ken