Re: State Machine/Class Granularity



Responding to Wavemaker...

I've been working on my state machine toolkit, and I've made some
progress (I think). First off, I got rid of the observer/broadcast model
in favor of peer-to-peer communication. I've also added a class called
SubsystemBuilder. It uses a collection of StateMachineBuilders
internally to create the STT code for each state machine. It enforces
some constraints such as making sure each state machine has a unique
name and ID (more on the state machine ID below) and resides in the
subsystem's namespace.

Alas, I sense another disconnect between us.

Why do you need multiple StateMachineBuilders? Once you develop an identity strategy it should be reusable. So you would need only one library class for the event queue. The static consumeEvent behavior is going to always be the same since it is driven parametrically off the STT and it will manipulate the currentState attribute the same way for transitions.

So the only things that are unique to an individual class are the values in the STT, which map to action body stubs defined for the class. (Since by the time the STT is initialized, event and state identity has been reduced to integer indices, that portion is quite generic as well.) So I would expect the builder to build every state machine exactly the same way once it is provided with the events, transitions, and action identifiers. It seems to me your builder could do that for any state machine with basically the same API you already described.

That is, the <ASCII> API just has to link {from state, event, to state} and (state, action) for Moore models or {from state, action, to state} for Mealy models. The builder then converts ASCII to indices and does the nuts & bolts code generation for the class. But at that point the processing should be pretty generic.

The last sentence also concerns me because it implies that the state machine exists as an identifiable entity. While it is, indeed, associated uniquely with a class, it is actually distributed in the code among different object elements: the STT, the currentState attribute, the static consumeEvent dispatch method, and the action methods. (This ignores optimizations we talked about for global vs. local event identity.) So the only thing one can really point at as being THE state machine is the STT, which is just a data table that will probably have the same name (identity) in every class.

BTW, consistent with this view that the state machine only exists as elements of the class, you might think about letting the builder build the entire class skeleton. That is, the only thing left to define when one uses an object state machine for behavior are the class attributes. Since those are just {name, ADT ID} pairs, those declarations are trivial to provide in your state machine builder UI. Then all you have to fill in manually are the action method stubs. IOW, think of your builder as a class builder rather than a state machine builder.

[Naturally life is rarely that simple. B-) Some attributes may be complex enough to warrant accessors that are more than dumb getters/setters and those synchronous services will also have to be defined somehow. There are also issues like whether a particular setter wants to be public and whatnot. As I indicated before, the devil in code generation is in the details. But you can still save yourself quite a few keystrokes.]


It also reads the events from the state machines and generates an enumeration type representing all of the events in the subsystem. The values of the enumerations are used as event IDs. This to me seems like a much more robust approach than what I was using before by having the state machines register/subscribe their events by name with the event queue. That solution is looking a little silly to me at this point.

Not so silly, I think. You still need to register the local event ID with the event queue manager. More important, the API for the builder may look like a registration service but all it is really doing is defining the STT. The registration paradigm just happens to be a convenient one for doing that incrementally and generically in a UI. (As opposed to my suggestion of filling in place holders in source templates for the poor man's builder.)


It seems to me that the only thing that has changed is the location of some of the code that is being generated (i.e., from EventQueue to class). I don't think the API technique for specifying it needs to change that much.


The basic functionality of the event queue is in an abstract class called EventQueueBase. When a new subsystem is built, it generates an EventQueue class derived from EventQueueBase. The EventQueue class initializes a static event conversion table that it passes to its base class. This is the table used by the base class for converting event IDs. This way, the base class never has to be touched by the code generator; it's only generating code for the derived class.

This is another notion that I am uncomfortable with. Subclassing represents generalization/specialization within a group of entities that are fundamentally the same. That is, any object from any leaf subclass IS-A root superclass entity. What you seem to be describing here is that the superclass and subclass have a sort of client/service relationship where superclass and subclass have fundamentally different responsibilities (i.e., one builds things that the other needs to use).


If I understand your description properly, the activities executed by the EventQueue subclass are only performed at startup while the EventQueueBase superclass's responsibilities are only executed when events are processed during collaborations. To me that says these are different critters rather than one being a specialization of the other.

<whole new world digression>
Note that the only reason you need the EventQueue subclass is to provide customized code generation of the tables for the specific application (more precisely, subsystem). You could do that without subclassing via


             1     R1       1
[EventQueue] ---------------- [TableSpec]

where [TableSpec] is a dumb data holder for the tables. Your code builder constructs that table and the EventQueue instance just does the lookup from it. This provides the same sort of decoupling with less static infrastructure.

However, there is an added bonus. I used "TableSpec" advisedly (rather than just "TableData") because this is an analysis pattern for parametric polymorphism. [TableSpec] is just a dumb data holder for values that parametrically influence a generic behavior in [EventQueue]. So it could be initialized from an external configuration data file. So you don't really need a builder for the [EventQueue] at all; you can modify its behavior by simply modifying the table data in the configuration file. (A factory of some sort is needed to read the configuration file and create TableSpec, though.)

Now let's take it one step further:

               1      R1       *
[QueueFactory] ----------------- [TableSpec]
      | 1                             | 1
      |                               |
      | R2                            | R3[equal,R1,R3]
      |                               |
      | creates                       |
      | *     1 specifies             |
 [EventQueue] ------------------------+

(The constraint on R3 just says one must get to the same [EventQueue] instances from [TableSpec] via R3 as one gets to via R1 -> R2.)

Here [TableSpec] is that same dumb data holder. But now it is used by a factory object to initialize the tables in an EventQueue when it is created. Again, [QueueFactory] has an invariant behavior that is influenced parametrically by [TableSpec]. So now one can reuse [QueueFactory] and [EventQueue] across applications or subsystems without change. All one has to provide is the unique configuration file that is used to initialize the TableSpec. So now all your builder has to do is create the external configuration file. Since there is no code generation here you can modify the events without even recompiling and relinking the application!

[Since you probably don't want to have QueueFactory pass a gazillion parameters to a constructor, [EventQueue] will probably have an addEntry interface method for initializing the tables that QueueFactory will tediously invoke for each TableSpec entry when the EventQueue is created at startup.]

I'll leave it as an exercise for the student to figure out how to do exactly the same thing for object state machines. B-) So if you really want to go gung ho over this, you can can fully define new object state machines without even regenerating the code and <sometimes> without even recompiling it.
</whole new world digression>


When the code generator writes the event generation code, it needs
access to that same table to insert the right class index into the
event.  (It knows what's on the end of the relationship from the Class
Model, so it knows the name when it processes the reference.)


If I understand, a full code generator would also generate code for
raising events. IOW, this code would not be written by hand. Because the
event raising code is generated from a class model, the code generator
knows which class ID value to insert into the code; it can judge which
class type a state machine is sending the event to. Is that correct?

Exactly. This is done in the methods that generate the events put on the queue. A code generator looking at the full model will have enough information to determine the class from the AAL relationship navigation and the Class Model. It would have its own internal tables to convert class name to an index for the identity strategy.


Since you aren't doing a full code generator, you will have to put those calls in yourself manually. When you do, you will need to look at the table to get the right class index.


I haven't gotten that far, and I doubt that I will (though you never know). So I think a compromise to the architecture you've described is to keep my state machine class. When the code generator generates a state machine derived class, it adds code that passes a unique class ID up to the state machine base class. This ID is what is used by the event queue to index the event conversion table. Also, the Dispatch method is in the state machine class, so it can call it directly rather than having to look it up in a dispatch table.

Without the help of a code generator to generate the event raising code,
clients will have to keep track of not only the instance object they are
sending events to but also the class ID. With the state machine base
class, clients only have to have a state machine instance to include
with the event. So I think for now my design is an ok compromise for my
goals. Does this make sense?

Sure.

However, if you have some spare time and want to pursue the Wonderful World of Code Generation a bit further, I would point out that generating events is a very aspect-like process since the format is identical. Recall my AAL example:

ref = this -> R1
GENERATE e1 TO ref WITH <data values>

The GENERATE statement will transform into something like:

myEventQueue->push (14, ref, <data values>);

That can easily be put in a function macro:

GENERATE(12, ref, <data values>)

Now that doesn't gain a lot but I am traveling a road here. Now suppose one also has some generic naming conventions for relationship implementation, such as

Object* navigateR1;

Now the first statement transforms into something like:

Object* ref;

ref = this.navigateR1;

Again, fodder for a function macro. (A bit more complicated for set access via * relationships.) So we have

GENERATE(14, (NAVIGATE(R1)), <data values>);

Now suppose we don't have the whole table yet to convert the event ID to 14. What we would like to do is write:

GENERATE(reset, R1, <variables>);

and come back later and do the substitution once we knew what all the events were. One way to do that is to treat the GENERATE as a placeholder and use a perl script to do the lexical replacement when we do have the table. To do that the perl script just needs to read the table that maps event name to 14 from wherever it is defined and then it can "walk" the code to do the substitution for all event generations. [In this case one wouldn't need the function macros; the script would write the code directly. They were just convenient to the example logic chain.]

One could take that perl script a step further and eliminate the Object* and navigateR1 obfuscation by letting the script name things properly. To do that one just needs another set of tables for the perl script to read that map {from class, relationship ID, to class, semantic name} where "semantic name" is a meaningful name for the referential attribute. In effect you are putting part of a Class Model into a table for the perl script. The perl script can then "walk" any code you have where relationships are navigated and insert readable code that will have full type checking by the compiler.

[Warning. You might find this an amusing exercise to get insight into the feasibility of full code generation. However, perl scripts is not the way to go in the long term. It's another case of the devil being in the details and the more specialized perl scripts one has the more time one will spend tinkering with them for each new application.]


************* There is nothing wrong with me that could not be cured by a capful of Drano.

H. S. Lahman
hsl@xxxxxxxxxxxxxxxxx
Pathfinder Solutions  -- Put MDA to Work
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
(888)OOA-PATH



.



Relevant Pages

  • Re: State Machine/Class Granularity
    ... > consumeEvent does the action STT lookup using the local event ID. ... > Note that by moving the actual FSM STT to the class, the EventQueue ... > objects can be in different states of their common state machine. ... > When the code generator writes the event generation code, ...
    (comp.object)
  • Re: State Machine/Class Granularity
    ... state machine code generator; it's in the form of a builder class. ... FSMs dynamically? ...
    (comp.object)
  • Re: State Machine/Class Granularity
    ... Since those are just pairs, those declarations are trivial to provide in your state machine builder UI. ... So if you wanted to create a subsystem and begin by adding a ... An instance of this class would be passed to the event queue at ...
    (comp.object)
  • Re: State Machine/Class Granularity
    ... >> internally to create the STT code for each state machine. ... When I say state machines in a subsystem, ... So I would expect the builder to build every state machine ... // A custom event queue for this subsystem. ...
    (comp.object)
  • Re: Complex application flow. Best approach ???
    ... >>In case nobody does, I guess your advise to try to cut the giant FSM into ... (State Machine Compiler). ... > to change the code generator to generate something else. ... > "The aim of science is not to open the door to infinite wisdom, ...
    (comp.object)