Basically, the labor system uses a controller to select a labor to fulfill some request, then it passes information about the request through a command. The labor then picks up the command and acts on it. This seems simple enough, but reading through the code it doesn't seem so straightforward, as you can't see the labor actually doing anything. This is because of popups.
Popups in QUEST allow a user to write incredibly flexible (reusable) logics at the expense of readability. In the default labor process logic, you can see the labor just constantly sits there getting the next pending command to work on, and then does a switch block to see what kind of command it is. It then gets a handle to the popup it should use to execute the command, and runs that popup.
So in the case of laborers, a popup is simply an object that lets us run some SCL code without knowing ahead of time precisely which SCL routine/procedure to run. You just query the popup that a user has selected, and run it, and assume that logic is taking care of business.
When we apply this thinking to the labor system, it demystifies things a little bit (at least for me). The regular labor process logic then becomes fairly simplistic...do a switch against the command type, and based on command type, get a handle to the logic to run, then run that logic.
The real meat of the labor logics, and the thing that kept me from understanding how they work, is in the default popup implementations.
In the labor Logics window in QUEST, you'll see a few options beyond the regular process and init logics. These are all popups, just a way of selecting a procedure/routine that's going to get called in the default labor logics. So to see what these default selections look like, we can just look at a labor's properties and see the routine names and file paths for the selected popups. The default labor load popup is notify_after_all_loads and can be found in QUESTlib\SYSDEF\LOGICS\agv_load.scl.src.
If you load that file up in an SCL editory, you can see the notify_after_all_loads is a procedure that takes an agv_cmd handle (agv's and labors are pretty similar, you can see, by digging into their logics. My understanding is laborers were essentially copied and pasted from agv's, back in the day at Deneb. Old/ex Deneb people can be dangerously misleading, but interesting nonetheless).
The flow of this logic is much much more readable than the labor process logic, again, because this is where we actually DO stuff. So here we can see that we do load processes if necessary and all that, but eventually we just do a REQUIRE PART EXACT the_part, where the_part is just a handle passed in as cmd->part_handle.
The interesting thing about this line of code is that it's the same thing a machine element uses to get parts (or buffers, whatever). If you look at the unload popups, you see it's just transferring a part to whatever element it's at. There is nothing the least bit magical or mystifying here. In fact, this makes the labor system seem to make a lot of sense.
This may have been obvious to you, but I was never able to get a handle on this until someone walked through the whole thing with me. I know I can't do as good a job as Martin did, but I hope this gives newer QUEST users who don't know the labor system the ability to go in and see just what's going on. Ever since I "saw the light" on how this all works, I've been able to write my own labor controllers and labor logics. With that understanding comes the ability to get very detailed control of your labors, as well as the ability to write some really really bad code. Be careful.
Here's a quick example of a custom labor load popup I recently had to write. The labor would load a part and immediately destroy it, with the controller having already gotten what it needs from the part. Keep in mind I'm using a custom controller, so I don't need to notify the controller that the labor destroyed the part. Your mileage may vary.
procedure custom_labor_load_popup( cmd : Agv_Cmd )
Var
the_part : Part
Begin
the_part = cmd->part_handle
require part exact the_part
destroy( the_part )
End
1 comment:
Post a Comment