Inhalt
Topic:.stateM_en.
Statemachines are well known both for hardware design (FPGA, hardware logic) and in software. Especially the UML defines statecharts as one of diagram kinds. Statecharts in UML support special features such as parallel states and nested states with defined rules of executing.
In cohesion with statemachines or statecharts in software the Event driven execution is used often. Events are data objects which contains information. Events can be stored in queues and be executed in another thread. In this kind it is a helpfull mechanism for thread interactions.
Both, statemachines respectively statecharts and events are present for software development if an UML-tool is used. For ordinary development of software in statement-oriented languages such as C, C++ or Java, statemachines and events are not used as medium of expressibility in the programming language often. Instead quests and set of boolean or enum-Variables are used to save the state of software. That is because:
The classic languages with their libraries don't support events and states as a first goal.
The execution of state machines regarding the UML-specifications requires some special program flow organization. The automatic code generation from UML-tools produces that necessary code, but writing manually is not fine to do.
Thinking in events and state machines is another approach than procedurale programming. It may be adequate to use that technologies of software. But if that technology is bounded to special tools for UML, it is not used ubiquitously.
This article describes a framework for programming in states and events for Java and C/C++ language for normal line sources without usage of UML-tools but UML-conform. Some aspects of software technology are discussed.
Topic:.stateM_en.exam.
The example is a communication process. There are some states of working:
Some discrete requests are assembled to a one communication request. Procedure addRequest.
If all discrete requests are given, then the sending should start with the first datagram. Procedure finit.
One after another answer is received, the next datagram should be send if there is more as one.
If all datagrams for answers are received, the process is in state idle again and can assemble the next discrete requests.
As a special problem a timeout of answer should be handled. In the example the requests should not be repeated automatically on timeout, but new requests should be handled only if either the current requests are finished (all answeres are received) or a timeout is expired.
Thinking without states the problem was resolved as following:
A request is refused if the communication is pending yet. Check the boolean variable bTaskPending
and check the timestamp of the last request to detect a timeout situation.
If the communication is in the idle state - no pending request - the addRequest prepares telegrams and set a boolean Variable bFilling
.
The finit checks whether any addRequest are done (it may be called without any request too, because cyclically). The first datagram is send, the timestamp is stored
and a AtomicBoolean variable bTaskPending
is set.
Receiving an answer in another thread (communication receive thread) executes something because the answered data and sends the next datagram.
Receiving the last datagram (it is marked) sets bTaskPending
to false. Now the state is idle again.
That solution works correctly. Some things are sophisticated: Requests while the communication is pending - how to handle? Is the timeout expired? The execution of the answer in another thread than the preparation of request does not may proper, because of data consistence. But for the concrete requirements it is okay.
Thinking in states it is more clarified:
There is not any need of long explaination, the diagram shows the principles.
The state diagram reveals a potential problem: The execution may hang in the state Filling
if the finit was not executed.
If events should be used, adequate the statechart execution technology in UML, the execution of the answer can be done in the same thread like all others, the thread of the state machine.
Topic:.stateM_en.UMLusing.
The UML is a well proper software technology. Using of UML, of course with a good tool, has some advantages. It is without question.
But the decision for using an UML tool influences the whole organisation of software development:
Anybody should use this tool, costs of education, costs of licenses. What is with external collaborators?
Writing classic source codes has some advantages too - the tools (Integrated Development Environment) are powerfull. Are they compatible with the UML-tool?
Though the UML technology and some tools are present since 15..20 years, they are not omnipresent for developing of software. Why ...?
Topic:.stateM_en..
In cohesion with statemachines or statecharts in software the Event Driven Execution is used often.
An event is given if any application or part of application (a thread) receives data to execute. The data were transmitted from another part.
An Event Object is an instance of a type derived from java.lang.EventObject
(in Java language) which contains information. In that meaning an Event Object is a message really.
There are some rules for event handling:
Event Objects are stored in a queue on receiver because it is possible that the receiver does not wait currently but executes the last event yet. It is possible that more events from several sources are received one after another. All of them are queued and execute then in order of receiving.
Events are expected or there are not expected in the current state. Especially for state programming events can fire a transition, or there are not usefull just now.
If an event was processed (any execution was started because the event) then the event is removed.
If an event cannot be processed in the current state there are removed. Events should not be stored for later execution expected in special cases for special events. The UML-2 definition /omg-uml/, page 563, knows the terminology of deferred events. But that is a special case. If an ordinary event would be stored if it cannot be processed yet a state machine or another algorithm may get an unexpected behaviour.
If an event is sent the transmitter does not know whether the event will be received on the correct destination, whether the receiver works and in which state it is. It depends of some conditions. Therefore for an orderly communication it is necessary to design an answer or acknowledge event and the possibility of repetition of sending the request event in a proper time.
Events can be send from one thread (invokes any routine to send, enqueues the event) to another thread (dequeues and executes). Events can be send from one application of the same system to another application (system process) using interprocess communication mechanism for example shared memory, dual port RAM.
Event Objects can be send via communication in networks. Java offers the serialize possibility for data which can be used for that. Event handling is possible for large dispensed (distributed) systems.
Advantage of the event concept as interface between applications or part of a program:
The event interface is extrem universal. Any event data type is derived from java.lang.EventObject
. It is the only one data type which should be known in the interface. The method for transmitting is
sendEvent(EventObject data, int sizeData);
More is not necessary. Because the concept of serializing in Java a communication layer can prepare the data for example for
UDP-Communication via ethernet. For shared memory communication the size of the data are possible to detect using reflection
mechanism in Java. Other programming languages (especially C) may need only the size of data as second argument sizeData
. Serializing in C is very simple - it uses the byte image of the data immediately.
Disadventage:
The advantage is the disadventage. Because the interface is extrem universal, it does not allow recognizing what and how. The interface does not document the data exchange and control flow.
Therefore: Use an universal event interface between applications to save effort or hide information but Use dedicated method-calls which may deal with events internally for the application-internal flow.
Cyclically execution, conditions and polling
In some ordinary publications the alternative to event driven programming is named as polling (cyclically quest) which would need some more calculation time, which is ineffective therefore. That's wrong. Polling is done with a cycle time usually. The cycle time is determined by the thing itself. If for example a control unit works cyclically, it checks in its own cycle whether conditions are met. It is insignificant whether events are used - the queue would be checked in the controller cycle - or conditions are tested in any cycle. Because the effort to enqueue and survey an event in comparison with testing a simple bit - the cyclically check of conditions may be a better decision, especially for embedding programming.
Topic:.stateM_en.EventClass.
Events are established in Java firstly for graphical programming. In AWT (Advanced Widget Toolkit) a class java.awt.Event
war created in the Version 1.0. This class is now designated as obsolete, instead a common super class java.util.EventObject
was defined in Version 1.1 which is extended from java.awt.AWTEvent
now. Other frameworks use the common class java.util.EventObject
too for example java.beans.beancontext.BeanContextEvent
. That are specializations.
For a common approach an event class org.vishia.event.EventWithDst
is defined which has two extensions:
The event object has a destination of type org.vishia.event.EventConsumer
which knows a method processEvent(EventObject)
. Additional a destination thread is given: org.vishia.event.EventTimerThread
with its core method storeEvent(EventObject)
. With both an event object is like a letter with a destination address and a postal code. It can be stored anywhere and processed
by the correct destination without any context.
The event object can be resused. A instance may be in use or free. This is a concept comming from embedded programming (in C):
Topic:.stateM_en.EventClass..
The event based on the java.util.eventObject
. Therefore an
Object source
is given with the super class. This source is type of org.vishia.event.EventSource
by construction. The source will be notified for some reasons, see interface srcJava_vishiaBase/org/vishia/event/EventSource.
EventTimerThread_ifc evDstThread
: This is the thread where the event should be enqueued if it will be sent. The srcJava_vishiaBase/org/vishia/event/EventTimerThread_ifc has two methods, to enqueue (storeEvent(event)
) and to remove an event from the queue.
EventConsumer evDst
: That instance which is the receiver of the event, which should execute something because the event. The interface srcJava_vishiaBase/org/vishia/event/EventConsumer has only one method processEvent(ev)
which should be overridden by the implementors.
AtomicLong dateCreation
: This is the time where the event was created respectively occupied. It is an information for the destination at least for
debugging, maybe for check whether the event is a newly information. It is the time in milliseconds after 1970, like the Java
conventions in java.util.Date
or the Unix operation system conventions. It is possible that a remote event receiver checks the time exactly if they are
synchronized. If this timestamp is 0, the event is not occupied. It is an AtomicLong
instead a simple long
value to support a thread safe occupying.
int dateOrder
: This is a fine designation especially for logging and debugging if more as one event's or other time stamp is gotten in
the same millisecond. A log output or such may need the exact order of events. The dateOrder is managed by an instance of
srcJava_vishiaBase/org/vishia/util/DateOrder
long oderId
: This is a data cell which contains an identifying number. It is intend to mirror in an answer event to detect the cohesion
between request and response.
char stateOfEvent
: It is helpfull for debugging. The second function is: control relinquish.
boolean bAwaitReserve
: This bit is necessary if any thread waits for relinquish, to notify it. It is set and check under synchronized
.
Topic:.stateM_en.EventClass.reuse.
The idea comes from embedded programming usual in C. In Java usual new instances were created whenever an event is necessary. If a source of events sends new events in a short time interval, for example for status messages to show, but the destination does not work yet, then the queue of events is overfilled and the memory of the system peters out.
Usual events are used in a ping-pong kind. If one event was sent, the receiver sends anything back etc. If the back event is missed, a timeout repetition is approriate, but for a longer time interval. For that only two events are necessary.
A sent event can be reused for the timeout repetition too: It can be presumed that it is stored in its destination queue which is not processed. Dequeue it and resuse! Only for a possible coincidence the event won't be found in the queue because it is processed just now. Then the event cannot be reused. In that situation the timeout repetition is just not appropriated. Either skip it, or wait for a short time with a synchronized mechanism.
The capability of reusing is build in the basic event type EventWithDst
independent whether it is need or not. That is because the decision about reusing does not depend on the type of the event,
it does depend on the usage in the application. The effort is less, no additional data are need, only a few methods for reusing
are given:
occupy(timeout, source, destination, thread)
: The event should be used, before that it should be occupied. With them the destination is written into the event instance.
It is expected that the event is free for occupying. The method returns false
if the event instance is in use.
occupyRecall(...)
: The event should be occupied. It is expected that the event may be in use. If it is only queued and the execution thread
is busy, then the event reference is removed from the queue to occupy newly. If the event is processed just now, then the
calling thread waits a given timeout to allow finish processing. If the processing seems to be hanging, maybe because an exception
prevents the relinquishing, then the event can be occupied if the time stamp of older than a given time intervall. This feature
prevents hanging of the system for reused events.
relinquish()
: This routine is called after the event is processing. It is free for next occupying.
doNotRelinquish()
: This method prevents relinquish() after processing. It should be called for the situation if the event is stored in another
queue for later execution, which relinquishs it then.
Topic:.stateM_en.EventClass.events.
srcJava_vishiaGui/org/vishia/gral/base/GralGraphicTimeOrder:
The GralGraphicTimeOrder
is the base class for orders which should be execute in the graphic thread of the GRaphic Adaption Layer see srcJava_vishiaGui/org/vishia/gral/package-summary. The method executeOrder()
have to be overridden by an implementation class with the functionality which should be execute in the graphic thread. That
instance is enqueued in the queue of the graphic thread if the time is its turn. A waiting time is managed by the EventTimerThread
.
It is applicated in the following form: The content of a widget (a text field etc.) can be changed in any thread but only
with setting the propriate information such as a new color as value, a text as String, the state of a button as boolean etc.
Another thread must not invoke graphic routines of a SWT graphic system and it should not invoke routines of AWT because thread safety. After setting the information the existing instance of the TimeOrder is activated with TimeOrder.activate
. It, that is executed in the graphic thread and change the graphical widget. It can be activated with a delay for example
100 ms to save calculation time for thread switching. For example 100 widgets are changed in one step. It is not necessary
to force a thread switch for any widget. Firstly write all information. Wait a time because more information can be changed
furthermore. Then refresh the graphic. A time of 100 ms is short enaugh for most of graphical data presentations. A time of
10 ms is long enough to prevent unnecessary thread switches.
srcJava_vishiaBase/org/vishia/event/TimeOrder:
The TimeOrder
can be used in any application to organize a delayed or therewith cyclical invocation of a routine in the timer thread. The
functionality should be programmed in the overridden method srcJava_vishiaBase/org/vishia/event/TimeOrder#executeOrder().
A time order routine can be written as instance of an anonymous inner class:
@SuppressWarnings("serial") TimeOrder myTimeOrder = new TimeOrder("name", threadTimer) { @Override protected void executeOrder(){ //action code } };
The time order is mananged especially by the srcJava_vishiaBase/org/vishia/event/EventTimerThread and executed either in the event thread or maybe in any other thread. For the second case an EventConsumer
routine is necessary to enqueue the time order in the other thread. An example is contained in srcJava_vishiaBase/org/vishia/event/test/TestTimeOrder. This case is used for the GralGraphicTimeOrder
to execute the graphic action in the graphic thread:
private EventConsumer enqueue = new EventConsumer() { @Override public int processEvent(EventObject ev) { execThread.addOrder((TimeOrder)ev); //casting admissible because special using. return 0; } }; TimeOrder order = new TimeOrder("name", enqueue, threadTimer) { //...implementation }
srcJava_vishiaBase/org/vishia/event/EventTimeout:
The EventTimeout
is the super class of the TimeOrder
but also a ordinary event especially for a timeout. In a srcJava_vishiaBase/org/vishia/states/StateMachine there is one persistent instance for one parallel state or the top state which is used from the active state if a timeout
transition is necessary.
To activate a timeout event or a time order from any thread only one of the following methods should be called.
myTimeOrder.activate(milliseconds); myTimeOrder.activateAt(date); // with an absolute time stamp. myTimeOrder.activateAt(date, last);// define a last execution time stamp.
It is possible that the time order should be deferred on a repeated call of activate(milliseconds)
some milliseconds later. The time order is executed only one time even the activate(...)
is called more as one times before the time is elapsed. To prevent a deferring until the cows come home by repeated invocations of activate(delay)
it is possible to set a latest time with srcJava_vishiaBase/org/vishia/event/EventTimeout#activateAt(long, long).
srcJava_vishiaBase/org/vishia/event/EventWithDst:
It is the base class of all this event types. An instance knows a destination where the event is processed: srcJava_vishiaBase/org/vishia/event/EventConsumer and optional a srcJava_vishiaBase/org/vishia/event/EventTimerThread where the event is stored and its execution is invoked.
srcJava_vishiaBase/org/vishia/event/EventCmdtype:
This event base class knows a field for a command which is an enum
type. The enum
type is used as generic argument. An derived instance should define the generic enum type in its extends
statement:
class MyEventType extends EventCmdtype<MyEnumCmd> { //some more data are possible. }
Then the
MyEnumCmd cmd = myEventType.getCmd();
returns the proper type of cmd.
srcJava_vishiaBase/org/vishia/event/EventCmdtypeWithBackEvent:
Events are used often with back events: One instance sends an event, the other instance responses with the proper counterpart which may have another cmd enum type. The requester can create such an double-event. The responder can use the opposite event instance from the received event. A derived event with its opponent is defined like:
/**defines the class for the callback event. The opponent type is MyEvent. */ class MyEventBack extends EventCmdtypeWithBackEvent < EnumCmdBack, MyEvent> { /**package private or private constructor. */ MyEventBack(EventConsumer callback) { super(null, callback, myExecutionThread, null); //here without opponent, just unknown. } } /**defines the class for the forward event. The opponent type is MyEventBack. */ class MyEvent extends EventCmdtypeWithBackEvent < EnumCmd, MyEventBack> { public MyEvent(EventConsumer dst, EventConsumer callback) { //creates the back event as opponent in super invocation; super(null, dst, myExecution, new MyEventBack(callback)); getOpponent().setOpponent(this); //set the backward opponent reference } } /**The event with the callback event as opponent. */ MyEvent ev = new MyEvent(responder, callback);
The callback event can be gotten from the received event and then used to send back:
EventConsumer responder = new EventConsumer() { @Override public int processEvent(EventObject evArg){ assert(evArg instanceof MyEvent); MyEvent ev = (MyEvent)evArg; MyEventBack eventBack = ev.getOpponent(); eventBack.sendEvent(EnumCmdBack.theCmd); }
Another usage for the double event is: If an event is sent in a statemachine for itself to force an non-Run-To-Complete continuation,
the current event is used yet and a second event is need. Only one event instance of a EventCmdtypeWithBackEvent
is provided. One of them is free for usage always:
/**The event type for intern events. One permanent instance of this class will be created. * The opponent will be used alternately because 2 instances may need contemporary. */ public final class EventInternal extends EventCmdtypeWithBackEvent<CmdIntern, EventInternal>{ private static final long serialVersionUID = 0L; /**The constructor to create the double event. */ EventInternal(EventConsumer dst, EventTimerThread_ifc thread){ super(null, dst, thread, new EventInternal(dst, thread, true)); } /**Creates a simple event as opponent. */ EventInternal(EventConsumer dst, EventTimerThread_ifc thread, boolean second){ super(null, dst, thread, null); } /**Sends either with this or the opponent, one of them is able to occupy always. * The other one may in use yet. */ public boolean sendEvent(CmdIntern cmd, boolean requested) { final EventInternal ev; boolean bOk; //evSrc from outer class. if(bOk = this.occupy(evSrc, false)) { ev = this; } else { //if this is in use: ev = this.getOpponent(); //the opponent should be able to occupy. bOk = ev.occupy(evSrc, requested); } if(bOk) { ev.sendEvent(cmd); } return bOk; } }
Topic:.stateM_en.thread.
Actions usually need only a few microseconds, less then a millisecond. An action or operation can be triggered by an external event such a keyboard or mouse input by a operator or receiving a telegram via network. An action can be triggered by a timer, maybe cyclically. The frequency of an action is in range of 1 per milliseconds (it's high) or less for typical non fast realtime applications which runs on a PC platform. The expectable response time for an event on a PC operation system may be typically less then 1 ms, but it may be 10..100 ms if the processor is busy for other things or a longer non interuptable operation is executing yet.
It means one thread can execute all event or timer triggered actions one after another because usually the singly action is fast and the frequency and expected response time is low. There is an advantage to execute all actions in the same thread: The actions are not interrupted. It means no synchronize mechanism are need if several actions work with the same consistent data. The synchronize mechanism would need more calculation time as the action itself possibly.
Therefore using one thread for all execution of actions in several instances is the proper decision for event driven execution.
srcJava_vishiaBase/org/vishia/event/EventTimerThread
This class contains a queue for all events. It executes the events in order of queueing. It supports time orders to execute.
An event should be initialized with the destination respectively the consumer and the thread:
MyEventWithDst myEvent = new MyEventWithDst(source, dst, myEventTimerThread, otherData);
The the event can be sent which enteres it in the queue in the EventTimerThread instance:
myEvent.sendEvent(); ...invokes: myEventTimerThread.storeEvent(myEvent);
The reference to the thread may not need to store in the event instance if the application invokes thread.storeEvent(event)
immediately. But this reference is need if the event will be recalled from the queue because it should be used newly with
new data if it is not processed yet:
myEvent.occupyRecall(...); //needs knowledge of the storing thread
It is possible to enqueue the event in one queue, dequeue it to process but enqueue it in another queue. Then the currently storing instance of type srcJava_vishiaBase/org/vishia/event/EventTimerThread_ifc can be noted in the event to enable recalling.
Timer thread:
The same instance and thread is used for queueing and starting the processing of an event and for managing time orders. An event may be an timeout event. Then a timer is used. If the time is elapsed then the event will be processed in this thread as event thread.
But the concept is not limited to handle timeout events. Another aproach is managing and execution of srcJava_vishiaBase/org/vishia/event/TimeOrder. It is based on the srcJava_vishiaBase/org/vishia/event/EventTimeout because they are handled from the EventTimerThread
in the same way. A TimeOrder
instance does not need a destination respectively consumer. It can be act as consumer in its own behalf. An event is only
a carrier of data without functionalty. A TimeOrder
has a functionality. See 4.3 The event family and time orders.
A time order is also executing in the EventTimerThread
. But it is possible to dequeue the TimeOrder
and process it like an event if a destination EventConsumer
is given by construction. The processing routine can enqueue the TimeOrder
in any other queue to process it in another thread. Then the time management thread and the executing thread is different.
This is used for the organisation of graphical orders. They are delayed in the timer thread and then executed in the graphic
thread.
A time order has its execution time. This time is stored as absolute millisecond timestamp in the TimeOrder
instance. The srcJava_vishiaBase/org/vishia/event/EventTimerThread contains a queue of all TimeOrder
. A TimeOrder
can be deferred by reenter in the timer queue. Then its execution time is deferred only.
Topic:.stateM_en.stmJava.
The goal is: Working with state machines should be simple but powerfull. The properties of UML-like state machines with parallel and nested states and event-driven mechanism should be available and used, but without effort for writing code and with capability for debugging.
Topic:.stateM_en.stmJava.pattern.
The package org.vishia.states
/Jstm/ offers some classes which can be applied in the users code. The package has dependencies only to less other classes in the
same software component (srcJava_vishiaBase) and it is available with the LPGL open source software license. It means everybody can use it.
The states are defined as classes. The following code lines are necessary:
import org.vishia.event.*; import org.vishia.states.*; class MyStates extends StateMachine { MyStates(EventTimerThread thread){ super("name", thread); } class StateA extends StateSimple { ....} class StateB extends StateComposite { class StateB1 extends StateSimple { ....} //inner state class StateP extends StateParallel { //container for parallel states class StateP1 extends StateComposite { class StateP1A extends StateSimple {....} ... } class StateP2 extends StateComposite { class StateP2X extends StateSimple {....} ... } } .... } };
It defines the state machine. Anywhere in the code (in constructor usually) an instance should be created:
EventTimerThread thread = new EventTimerThread("thread"); MyStates states = new MyStates(thread);
The event driven statemachine runs in a thread, which can used for other instances of other Statemachines too.
Inside the state-classes transitions and actions are defined:
class StateA extends StateSimple { final boolean isDefault = true; //default-state @Override protected int entry(EventObject ev) { System.out.println("Entry in state A"); return 0; } @Override protected void exit() { System.out.println("Exit from state A"); } Trans transTo_B1 = new Trans(StateB.StateB1.class); Trans transParallel = new Trans( StateB.StateP.StateP1.StateP1A.class , StateB.StateP.StateP2.StateP2X.class) { @Override protected void action(EventObject ev) { System.out.println("action of transition StateA -> Parallel P1A, P2X"); } }; @Override protected Trans checkTrans(EventObject ev){ .... } }
A state class can define an entry
and exit
method with the applications code if necessary. All transitions are defined as transition instance. The name of the instances
are free. A well style is cause_DestinationState
.
The destination state of a transition is given as argument in the constructor as java.lang.Class
-instance of the state-class. For a fork-transition to more as one parallel destination states more as one argument is used.
Trans transTo_B1 = new Trans(StateB.StateB1.class);
A transition instance as derived anonymous class can contain an action
method.
To check the transitions the checkTrans(EventObject ev)
method is used.
@Override protected Trans checkTrans(EventObject ev){ if(ev == evA) { return transTo_B1.eventConsumed(); } else if(ev instanceof EvCmdX && ((EvCmdX)ev).getCmd() == CmdX.cmdY) { transParallel.doExit(); //maybe code here to execute as transition code after exit. return transParallel.eventConsumed(); } else { //maybe code here to execute cyclically in this state if the state machine will be invoked cyclically return null; } }
The checkTrans method of a state is executed if that state is active and the state machine are invoked. The user can set a
debug-breakpoint in a special state with a special condition or write a debug output (System.out.println("...")
) to check difficult and selten situations.
This method should check only the conditions for the transition and select the transition. If a condition tests and uses the
event then the method eventConsumed()
should be called. It sets the bit for mEventConsumed
it a temporary variable in the transition which is evaluated by the state engine.
return theTransition.eventConsumed();
It is possible to add a transtion action either simply, executed before the exit-state is done, or after calling
transXYZ.doExit(); my.transition.code();
The last one is UML-conform. The exit and entry organisation to the states in the correct hierarchie is done by the state engine in the StateSimple class with the information of preparation phase.
If a timeout is need in a state a timeout transition should be defined. The shown action
in the timeout instance is optional. The timeout should be checked in the checkTrans-routine. (An older version of the state
engine had checked and execute the timeout in its core routines. But then the timeout is more difficult to debug - set a breakpoint).
The check whether the event is a timeout event exactly for this state is done by the isTimeout(...)
routine of this state. Note that another timeout can be received too. Debugging hint: The class StateSimple
has a field evTimeout
and a field transTimeout
. Both is set in the preparation phase because the Timeout transition. The event is compared with the evTimeout
instance which is used and activate on entry of the state.
class StateWaitAnswer extends StateSimple { Timeout transIdle_Timeout = new Timeout(5000, StateIdle.class) { @Override protected void action(EventObject ev) { System.err.println("timeout"); } }; @Override protected Trans checkTrans(EventObject ev) { if( ... } else if(isTimeout(ev)) { return transIdle_Timeout; } else return null; }
A join transition is written as instance of TransJoin
with the destination state(s) in its constructor and the source states of the join in the method srcStates(...) called after construction. This join transition is moved to all source states automaticly and checked there,
without any additional programming effort.
class StateP extends StateParallel { ... TransJoin to_off = (new TransJoin(StateOff.class)) .srcStates( StateActive2.StateShouldOff.class , StateActive1.StateFinit.class );
A choice transition is written by an instance of TransChoice
which contains the further transitions and a choice(EventObject)
method to check the choice. This method should be invoked inside the superior checkTrans(...)
method:
public TransChoice on = new TransChoice() { Trans cont_history = new TransDeepHistory(StateWork.class); Trans ready = new Trans(StateWork.StateReady.class); @Override public Trans choice() { if(cond.cont) return cont_history; else return ready; } }; Trans otherTrans ... @Override protected Trans checkTrans(EventObject ev) { if( ev instanceof EventA ) return on.choice().eventConsumed(); else if(...) return otherTans; else return null; }
Note: stateMachine
is an association in any state, refers the srcJava_vishiaBase/org/vishia/states/StateMachine.
If the programmer should get an overview over his/here states, for example the outline tree of an Eclipse IDE can be used. It shows:
The transitions are designated with the trigger or another significant name and the destination state. In this kind the outline tree shows the state flow maybe sufficient.
Topic:.stateM_en.stmJava.refl.
Nowhere the quest of all transitions is programmed. The states are only given as classes, not as instances. Nothing of them is written in source code. What's the matter?
The execution of state quests are contained in the base classes of org.vishia.states
of course.
The missing data are created by methods in the base classes of the states which uses reflection mechanism:
The srcJava_vishiaBase/org/vishia/states/StateMachine class creates instances of all found State classes via reflection and stores them in a HashMap table. The sorting key is the hash of the State classes. Therefore for the specification of a destination state the class instance is sufficient.
All composite and parallel state gets an array of its child states. This array is only used for debugging.
Any composite state have to contain a default state. It is that state where a boolean isdefault
variable is found.
All transitions srcJava_vishiaBase/org/vishia/states/StateSimple.Trans get a List of states to exit and states to entry via reflection by knowledge the source (own) and destination state or more as one destination state for a fork transition. That is a complex check if nested and parallel states are used. The well checked algorithm can it do better than a hand written transition procedure. On debugging time this data can be visited to check its correctness against expected behavior. Maybe states are faulty. An example is given in the next chapter.
The following fields are set by reflecton, able to check by debugging:
StateComposite.aSubstates : StateSimple[]
: Array of all sub states.
StateParallel.aParallelstates : StateComposite[]
: Array of all parallel composite states.
StateSimple.enclState : StateSimple
: The enclosing state, either a StateComposite or a StateParallel
StateSimple.stateMachine : StateMachine
: Aggregation to the enclosing StateMachine state.
StateSimple.statePath : StateSimple[]
: All states from the top state of the StateMachine
in the hierarchy of nested states. It is used to build the transition path.
StateSimple.transTimeout : StateSimple.Timeout
: Set with a found instance of Timeout, null
if the state has not a timeout. This transition will be used if an srcJava_vishiaBase/org/vishia/event/EventTimeout is processed.
StateSimple.evTimeout : EventTimeout
: If the state has a timeout transition then an event instance is created permanently in the StateComposite
inside a StateParallel
or in the top state of the StateMachine
. This event instance is used for all timeouts in this non-parallel states. Another parallel StateComposite
needs an own EventTimeout
instance because the timeouts are independent. The timeout
aggregation is set for all states which has a timeout transition and the top state of the non-parallel states.
StateSimple.millisectimeout : int
: Set if the state has a timeout transition with the given time.
StateSimple.Trans.exitStates: StateSimple[]
: For each transition the path from the own state to the destination state(s) is searched. The common state between the own
state and all destination states is the root of this path. It is not the top state of the StateMachine
in any cases. A composite state is remain active if the transition path don't leaf it. Then this state is the common state
of the transition. The exitStates
contains the own state at position [0]
and the state on level of the common state at the last position. The common state is not exited. This array is used if this
transition fires. For all that state the srcJavaPriv_vishiaBase/org/vishia/states/StateSimple#exitTheState() will be invoked. If the own state is a composite one then its exitTheState()
organizes exiting of the current sub state(s). They are not contained in the exitStates
array because the current sub state(s) are not staticly defined. It may be sufficient to invoke exitTheState()
only for the last one below the common state because the same logic is valid. But the exitStates
array shows the correct order staticly usefull for debugging.
srcJavaPriv_vishiaBase/org/vishia/states/StateSimple.Trans#entryStates
: StateSimple[]
: It is the second part of the transition path from the common state to all destination states. The state at position [0]
is the first state to enter after all states are exited and the optional transition action was invoked. If a fork transition is used to more as one parallel states then the array contains the entry states in the correct order: A common state of a
fork is entered only one time. The order of branches is dependent on the order of destination states in the Trans
constructor arguments. The following example may shown the order:
The state Cxy
may be the exit state, C
is the common state which is not left. The array entryStates
contains:
The order of entry states followes the indices in the srcJavaPriv_vishiaBase/org/vishia/states/StateSimple#statePath of all 3 entry states. The first index is 2
because at [1]
the common not left state is referred. All states of [2]
are entered. It is only Ap
because this is a common state for the fork-path. Then all states of [3]
are entered etc. The order is then:
Ap, A1, Bp, B1, B2, B12, B25, B251
That is UML-conform. Another order may be also UML-conform and also able to expect, firstly the full first path, then the second etc. which is:
Ap, A1, Bp, B1, B12, B2, B25, B251
The order of entering effects on the order of enter()
actions of the state. It should not be meaningfull for the application because the UMl does not define the order. The effect
of following run-to-complete transitions are not affected by this order because that operations are done after this transition
switch.
Topic:.stateM_en.stmJava.except.
Catch any cycle of event execution:
In a well tested software exceptions should only thrown if they are able to expected. For example a FileNotFoundException
on creation of a FileReader
.
If a software is under construction programming errors are able to expect. If a software is well tested and exceptions are
never expected it is a less effort to handle exceptions with a simple output on System.err
which can be redirected to any log output. That is for the unexpected case there is an exception nonetheless.
If an exception is occured on the execution of an event and this exception is not catched then the thread for all event execution is aborted. The whole execution of all events after them is abandoned. It is not possible to continue working to repeat the situation and find out the cause. Therefore the loop of the event execution from the event queue needs a catch for all exceptions:
@Override public void run() { while(cont) { EventObject ev = queue.offer(); if(ev !=null) { try { process(ev); } catch(Exception exc){ CharSequence text = Assert.exceptionInfo("EventTimerThread unexpected Exception ", exc, 0, 50); System.out.append(text); } } } }
This is part of org.vishia.event.EventTimerThread
.The Assert.exceptionInfo
assembles a text which contains the stacktrace of the given number of levels (max. 50 in this example) as one long line.
This is proper to write it in a log file or in a line in any console output which wraps the line.
If the exception is thrown in the middle in the run to completion of any state switch - that state switch is incomplete. It is possible that data structures are inconsistent hence. Nevertheless it is possible to repair the situation for example with a restart event or such ones.
Catch any transition:
If the execution code of a transition or the user's entry- or exit code fails then the singly transition or action can be thrown. In that case the state execution is correct. Only the users data can be confused because the aborted execution. That is proper if unknown exceptions are expect-able because the software is not well tested in all situation:
Trans trans; try{ trans = checkTrans(ev); } catch(Exception exc){ if(stateMachine.permitException){ StringBuilder u = new StringBuilder(1000); u.append("StateSimple trans exception - ").append(stateMachine.getStateInfo()).append(";"); if(ev !=null){ u.append("event: ").append(ev.toString()); } CharSequence text = Assert.exceptionInfo(u, exc, 0, 50); System.err.append(text); trans = null; } else { throw new RuntimeException(exc); //forward it but without need of declaration of throws exception } } if(trans !=null){ ....
This code snippet is part of the execution of transitions to find the firing one. If there is an exception, no transition
fires and the machine remain in the current state. If the transition code contains the doExit()
it is possible that a state is exited but the destination state was not entered. Nevertheless the state machine may be situated
in a proper state. With the outputted error text, the current state and some more stimulies after them the mistake is able
to encircle often.
The adequate code wrapes the invocation of the users entry and exit actions of the states.
Topic:.stateM_en.stmC.
See article StateMGen
State machines are used in C or C++ often in a simple kind. Therefore they are programmed usually manually with switch - case
, setting of the state variable immediately and without specific entry and exit actions. Using of an enum
definition of the states is a good idea:
switch(stateVariable) { case EnumStateX: { if (condition) { action(); stateVariable = EnumStateY; } } break; .... } //switch
If the requirements for state processing are more complex, this schema is limited. How to write nested states? How to write parallel states? Unique programming of onEntry and onExit? Event handling?
It is the same problem like in Java: The written code should be short and pitty, but the necessities for execution need a verbose execution code. The solution in Java is (see 6.2 How does it work - missing data via reflection): using reflection to generate the necesarry data, use it in prefabricated methods of the base (super) classes of the states. This principle does not work in C language. There are not reflection, inheritance (in C++) should not be used.
Therefore another approach is used: Code generation. Therefore a ZBNF parser is used to parse and a JZcmd is used to generate the C file. Both tools are script controlled. The user can change the scripts without study the complex programming of the generater, to adapt to its requirements to the execution code.
Source Internal Prepared 2. Source State machine----> ZBNF parser ----> Java data ---Java data----> JZcmdExecuter ---> C-program (any language) ^ image ^ generated ============== | | ========= ZBNF syntax JZcmdScript script ^ ========== | | JZcmd -------> JZcmd using generation ZBNF parser script ==========
The source language for the state machine's code can be any language. It is determined and change-able by the ZBNF syntax script and the JZcmd generation scripts.
Topic:.stateM_en.stmC.patternCpp.
The source code in C is written in this articel with a background color in yellow:
...The source code...
whereby the generated code is written with a light blue background:
...The generated code in C...
The user should write the definition of the State machine in a header file. The example source file is examples_Zmake/StateMGen/src/ExampleSimpleData.states.h.html. This is a full example for one state of the statemachine in the chapter Parallel and nesting states ....
#ifndef __ExampleSimpleData_states_h__ #define __ExampleSimpleData_states_h__ /* stateMcHgen-Syntax: firstly that header is included, which will be generated then. */ #include "../result/exampleSimpleData_genState.h" /* stateMcHgen-Syntax: secondly this header is named by define, to know for include. */ #define SrcHeader_StateMgen "ExampleSimpleData.states.h"
That are the first lines of the header file. It starts with the guard for header files with any definition. The first include should name the generated header file. It is included here, after translation of course. Some generated definitions are used. Secondly this header itself is named, it is used for include in the generated C-File. A line
#include "ExampleSimpleData.states.h"
is produced in the generated C-file with them.
The next lines can include some other files additionally which are necessary especially to access the states data inside the user struct. Therefore the Headerfile which defines that user struct is included here. It is not used for code generation but for C translation of the generated code too.
/* stateMcHgen-Syntax: Includes the header file for the compilation unit, where the class is defined which contains the generated State structure. */ #include "ExampleSimpleData.h"
The some definitions should follow. That definitions are used to generate the correct code for some things, see description in the example source code:
/* stateMcHgen-Syntax: Definition of the type of struct for the users data which are associated to this state machine. * This Definition is used as suffix for function names to get unique identifier. */ #define UserDataType_StateMgen ExampleSimpleData /* stateMcHgen-Syntax: Definition of the type of struct for the states. It is used as struct type name for generation. */ #define TopStateType_StateMgen StateMachine_ExampleSimpleData /* stateMcHgen-Syntax: Definition of the access to the state data inside the users data. * It should be matching to the TransFn args. In this example it uses the 'thiz' pointer from the Transfn args. * Note: write it without spaces. */ #define StateInstance_StateMgen thiz->state
The TransFn
is a function type which is used for all transition codes for the state machine.
/* stateMcHgen-Syntax: Prototype especially for all transition execution routines from any state to a given destination state. * The same arguments but slightly a different return value are used for all generated functions for entry, exit, doTrans, currentState too. * Coincidently, the same argument names should be used for the exit and entry routines. */ typedef int TransFn(struct ExampleSimpleData_t* thiz, int event);
Now the definitions of the states follow:
/* stateMcHgen-Syntax: Any state have to be defined with its transitions to any other state, maybe with an parent state. * If the parent state is not given here, it is a state at top level. */ /**Write a comment for documentation for the state here. */ typedef struct Off_State_t { /**The internal state data, the identifier after underline is the state number*/ StateBaseGen Top; /**A transition is defined as TransFn pointer. The name of the transition determines the destination state(s). * The names are the simple state type names independent of their nested structure. * If it is a fork transition, the state should be separated by underliner _ //TODO remove: Special: history on end: Go to the history pseudo state. * Write a comment here for any transition: * If on_cont is given, the state Work is entered and the work is continued at the history state. */ TransFn* workHistory; /**If on_ready is given, the statemachine activates Ready, independent of the history state. */ TransFn* Ready; } Off_State;
The Definition of any state is created as const data in the generated C-code. It should contain a base struct StateBaseGen
. This is a generated typedef in the local scope inside the generated header:
This element in the state structure has a meaning for code generation: The name describes the parent or enclosing state. For
the top level, or for a simple state machine without nested states, the name of the parent should be Top
like shown here.
After this StateBaseGen
all transitions should follow with any identifier name. Only a History Transition should end with
...History
or ...DeepHistory
for a deep history entry.
...ShallowHistory
for a shallow history entry.
The transitions are used in the manual written transition routine.
In the generated header, this type StateBaseGen
has the following form:
/**This structure is the base structure of any state in this context for the const STATE_StateConst definition. * It is special, therefore code-generated, because the TransRnGen and the ExitRnGen hava a special signature in the users context. * The assembling of the data is the same for all code-generated state machines of this tool StateMcHgen. */ typedef struct StateBaseGen_t { void const* signature; int id; int ixCompositeState; int ixOwnState; ARRAYCONST_SimpleC(struct StateBaseGen_t const*) statePath; /**Kind of the states, see bit definition in Fwc/fw_StateMachine.h. */ int kind; StateCurrentRnGen* stateCurrent; //to get the current state of a composite. TransRnGen* checkTrans; //Function pointer, routine pointer EntryRnGen* entry; //Function pointer, routine pointer ExitRnGen* exit; //Function pointer, routine pointer ExitParallelRnGen* exitParallel; //to exit a Composite or Parallel state. } StateBaseGen;
After the definition of any state either a forward declaration of the entry-, exit- and trans routine should be written, or the routine is written as inline or static, like usual in header files:
//stateMcHgen-Syntax: If a prototype or definition for entry_-, exit_-, in_- and checkTrans-_ is given, //that routines are called by the generated state machines code. //* That routines are arranged after the state's struct definition. Therewith they are recognized as associated to the state. //* The names of the routine should start with 'entry_', 'exit_', 'checkTrans_'. The rest of the name is arbitrariness. //It should be proper for the users association. //* The routines should be defined by normal C-Programming in the compilation unit. //* If they are written as inline, the keyword 'INLINE_Fwc' may be used instead 'inline' or 'static'. //It's defined in os_types_def.h proper to the platform. //* The argument names have to be the same like used in the 'TransFn' definition because that names are used for calling //in the code generation of the doTrans- routine. // /**An comment is possible here.*/ INLINE_Fwc void entry_Off(struct ExampleSimpleData_t* thiz) { thiz->work = false; // } //stateMcHgen-Syntax: If the routine is defined with a body, only the last '}' should be written on start of line. //Therewith it is recognized by the parser. The end is '\n}' or '\r}'. /**An comment is possible here.*/ INLINE_Fwc void exit_Off(struct ExampleSimpleData_t* thiz) { thiz->off = false; //prevent immeditately switch to off if this condition is set already and erroneously. }
The name of the methods after entry...
, exit...
and checkTrans...
can be given with any identifier. The association to the correct state is given because the methods are written after the
state structure definition. The names of the arguments should be the same as in the TransFn
function type definition. The checkTrans...
-Method should have an additional argument with is the yet defined state type. It is used to select the transitions for this
state in the body of the routine. The name of any transAction_
routine should match to the name of the TransFn
!
//stateMcHgen-Syntax: The checkTrans routine for any state should be declared in this form. //The name of the routine should start with 'checkTrans...', the rest is no matter. //The name of the state is the Type of the last parameter named 'state'. It should be matched to the state struct definition above. //The code of the checkTrans routine is invoked by the genarated code. It is not regarded for code generation. // int checkTrans_Off(struct ExampleSimpleData_t* thiz, int event, Off_State const* state); //stateMcHgen-Syntax: If a 'transAction_...' is given, it is invoked in the correct order after exit the states. //The name of the trans action should match to the name of the transition in the form <stateName>_<transitionName>. // void transAction_Work(struct ExampleSimpleData_t* thiz);
It follows the next state etc.
A State is a 'StateCompositeFlat' or a 'StateComposite' if another state refers it:
typedef struct Ready_State_t{ StateBaseGen Work; /**If start is given, the state running is entered, and as parallel state the RemainOn. */ TransFn* Running_RemainOn; } Ready_State;
It is a state as nested state of Work
therefore Work
is a 'StateComposite (Flat)'. Work is a 'StateComposite', not 'Flat' because it contains an history-pseudostate.
The difference between 'StateComposite' and '...Flat' is: A 'StateComposite' has a state variable. A 'StateCompositeFlat' is an enclosing state, contains an inner state Machine, but the inner state is controlled by the superior 'StateComposite' or the top state. The reason for a 'StateCompositeFlat': There are transitions which are valid for all inner states. The other reason: extra entry and exit routines.
A StateParallel is designated with the parallelBase_...
identifier, following with its enclosing state:
typedef struct Active_t { StateBaseGen parallelBase_Work; } Active;
A state is a composite state if any state refers to it as StateBaseGen
. The state Work
in this example contains the Active
as a substate. Active
is a state which contains more as one parallel inner state machine.
The Active1
in this example is one of the parallel states in the StateParallel Active
because it refers to it.
typedef struct {
StateBaseGen Active;
} Active1;
Because the Running_State
refers to Active1
, the Active1
is a 'StateCompositeFlat'. Because Active1
has a 'StateParallel' as its base, it is a 'StateComposite', not flat:
typedef struct {
StateBaseGen Active1; TransFn* FinitState; ...
} Running_State;
That's all in the header file:
#endif //__ExampleSimpleData_states_h__
The checkTrans...()
and the entry- and exit routines which are forward declared here are written in any C-source. Usual it is the C-source-file
associated to the UserDataType_...
. It is not used for generation. The routines are invoked from the generated code. For debugging it is possible to set breakpoints
there immediately.
In a checkTrans...
routine the TransFn* ...
form the State definition are used:
int checkTrans_Work(ExampleSimpleData* thiz, int ev, Work const* state) { if(ev == kOff_Event_ExampleSimple) { thiz->isWorking = false; return state->Off(thiz, ev); } else { return 0; } }
This is the implementation (definition) of the checkTrans_Work(...)
declared in the translated header file.
The transition to Off
contains a transition code. It is executed before the exit(...)
routines are invoked. That is not exactly UML-conform but usual the simplest and non-problematic way.
The state definition Work
is given as last argument. Therefore in the body the transitions of Work
are able to access. The transition routine referred as Function pointer in C are generated and contains all necessary exit, entry and set of state variables. That is not necessary to program manually.
TODO timeout.
A timeout as condition is given by writing (bool time = value)
.
Topic:.stateM_en.stmC.genCtrl.
The translation of the header file of the state machine to the C state execution code is done by a Java program:
java org.vishia.stateMGen.StateMGen -i:src/exampleSimple.state.h -s:../../zbnfjax/zbnf/StateMcH.zbnf -d:result/data_Statem.txt -scriptcheck:result/data_script.txt -c:../../zbnfjax/zmake/States.genDocu.jzgen -y:result/exampleSimple.topic -c:../../zbnfjax/zmake/stateMcHgenC.jzgen -y:result/exampleSimpleData_genState.c -c:../../zbnfjax/zmake/stateMcHgenH.jzgen -y:result/exampleSimpleData_genState.h --rlevel:333 --report:result/log.txt
That command with some arguments controls the generation. All input- and output files are given by the command line arguments. More simple is, using a Zmake script. It is a batch file for windows or a shell script:
REM: Note for Windows: use %0 to get the path of this batch, the same file is the input file of jzcmd. jzcmd %0 exit /B ==JZcmd== currdir = scriptdir; include ../../zbnfjax/zmake/stateMcHgen.jzcmd; main(){ zmake "result/exampleSimpleData_genState.*" := stateMcHgen(src="src/exampleSimpleData.states.h"); }
The included script zbnfjax/zmake/stateMcHgen.jzcmd.html organize the java invocation with the given arguments.
The Java program parses the given source code in C++, composes some coherences between the data and generates the output files. The parsing of the source is script-controlled by the ZBNF parser. The syntax can be changed, another input form (language) can be used instead C++ only with changing the syntax file. The composition of coherences is widely the same like the algorithm for java statemachines, see How does it work - missing data via reflection. The generation of the output is controlled by a JZcmd script. In this script some adaptions can be done to influence the generated code. That is explained in an extra document StateMGen because it is more for system engeneers (administrators of a development process) as for the user programmer which will use state machines.
Topic:.stateM_en.stmC.genStmC.
The parsing and preparation of the state machine delivers data, the code generation can be done with several different principles. The following style of generation code should be comprehended only as one possibility.
The generated code does not use the known switch-case strategy. Instead: For any state a _StateConst
will be generated in the const data area which contains function pointers to checkTrans...()
and some other routines. From the view of software stability function pointers in a data area may be a source of unpredictability
because any software error can override a function pointer with a faulty value, then a imponderable machine code is executed.
But if the function pointer is a part of a const
the probability of disturbing a const
is some more lesser. If the const
data area is protected for write access it is zero. It is possible to check whether a pointer to the ...StateConst
is correct. After this check the invocation of a function pointer is safety.
In the translated header file a struct
/**This struct contains all data which are necessary in the generated code for the state processing. */ typedef struct StateMachine_ExampleSimpleData_t { StateBaseGen const* statetop; StateBaseGen const* stateWork; .... } StateMachine_ExampleSimpleData;
is generated which contains a reference to the ..._StateConst
for any 'StateComposite'. The StateBaseGen
is the abstracted part of any ...StateConst
. It is defined per code generation because it is specialized for the user's data type defined in
/* stateMcHgen-Syntax: Definition of the type of struct for the users data which are associated to this state machine. * This Definition is used as suffix for function names to get unique identifier. */ #define UserDataType_StateMgen ExampleSimpleData
With the UserDataType the type of the function pointer are defined:
/**Function type definition for getting the current state of a composite. */ typedef struct StateBaseGen_t const* StateCurrentRnGen(struct ExampleSimpleData_t* thiz, int event); /**Function type definition for the generated trans check routine. */ typedef int TransRnGen(struct ExampleSimpleData_t* thiz, int event); /**Function type definition for the user's exit routine. */ typedef int EntryRnGen(struct ExampleSimpleData_t* thiz, int event); /**Function type definition for the user's exit routine. */ typedef void ExitRnGen(struct ExampleSimpleData_t* thiz, int event); /**Function type definition for the exit routine for a composite or parallel state. */ typedef void ExitParallelRnGen(struct ExampleSimpleData_t* thiz, int event, struct StateBaseGen_t const* stateExitLast);
They are used in the StateBaseGen
structure type definition:
/**This structure is the base structure of any state in this context for the const STATE_StateConst definition. * It is special, therefore code-generated, because the TransRnGen and the ExitRnGen hava a special signature in the users context. * The assembling of the data is the same for all code-generated state machines of this tool StateMcHgen. */ typedef struct StateBaseGen_t { void const* signature; int id; int ixCompositeState; int ixOwnState; ARRAYCONST_SimpleC(struct StateBaseGen_t const*) statePath; /**Kind of the states, see bit definition in Fwc/fw_StateMachine.h. */ int kind; StateCurrentRnGen* stateCurrent; //to get the current state of a composite. TransRnGen* checkTrans; //Function pointer, routine pointer EntryRnGen* entry; //Function pointer, routine pointer ExitRnGen* exit; //Function pointer, routine pointer ExitParallelRnGen* exitParallel; //to exit a Composite or Parallel state. } StateBaseGen;
A stateConst, for is defined like:
//It is the path to the nested state inside the top state, used for a history transition, used as info for documentation. // static StateBaseGen const* const statePath_Running2_State [] = ////(stateMcHgenC.jzgen: const statePath) { &Work_StateConst.Top , &Active_StateConst.parallelBase_Work , &Active1_StateConst.Active , &Running_State_StateConst.Active1 , &Running2_State_StateConst.Running_State //stateDefault ((stateMcHgenC.jzgen: stateDefault-path) , &Running21_State_StateConst.Running2_State }; ... //It is the const description data for the state Running2_State, containing the reference to the doTransition... routines. // Running2_State const Running2_State_StateConst = //(stateMcHgenC.jzgen: generateStateConst) { //The state const head data, type StateBaseGen: { signature_State_Fwc, 0x000 //->signature ->id , 2 //->ixCompositeState (in statepath) , 4 //->ixOwnState (in statepath) , {statePath_Running2_State, ARRAYLEN_SimpleC(statePath_Running2_State) } //->statePath.ptr ->statePath.length , kCompositeFlat_EKindState_StateMachine_Fwc //->kind type: EKindState_StateMachine_Fwc , null //no StateComposite, currentState , checkTransGen_Running2_State , entryGen_Running2_State , exitGen_Running2_State , null //no StateParallel, exitParallel } //The dotrans routines used in the manual written transition routines: (stateMcHgenC.jzgen dotransConst) , doTransRunning2_State_f1__Running1 };
It shows an deeper nested state in a parallel state bough which is a composite-flat state - an complex example. The statePath_... contains 2 parts:
The path to the nested state. Here it can be seen that this state Running2
is a member of Running
, Active1
etc. This part of state path is used to exit a state in the correct order and to entry a history transition.
The last entry in the statePath is the default state of this.
The ..._StateConst
starts with the values of the StateBaseGen
. That only is used for exit and history entry. The first element is a signature, defined with a reference to a const text.
While referencing a _StateConst
this first element is checked whether it refers the signature. If not, then it is a fatal software error caused by another
bug. Then the function pointers are not used. The possibility that a pointer to a StateBaseGen
refers a faulty memory location and this memory location refers exact this memory address, which is not used for another
things, or it contains an integer value with exact the adequate value, is 1 / 2 power 32. Very less. Only as result of a bug
which's possibility should be less. On the other hand, the prohability to detect such an faulty pointer while testing is high.
The next values contains indices to the statePath, the kind of the state, and the reference to the state routines (function pointer) which are used in the generated code.
Process the state machine:
The following function is generated too:
int stepStates_State_ExampleSimpleData(struct ExampleSimpleData_t* thiz, int event) { StateBaseGen const* stateTop = thiz->state.statetop; if(stateTop == null) { //do entry. TODO: doEntry! stateTop = thiz->state.statetop = &Off_State_StateConst.Top; } return stateTop->checkTrans(thiz, event); }
This routine should be called from the application either if an event is gotten or cyclically with or without an event. The
routine organnizes the first entry to the default state. That's on-entry actions should be called intially. Then the routine
calls the checkTrans(...)
-routine of the top State. This routine is the manual written routine which's prototype was found by translation. But the
reference to this routine is associated by the generated ...StateConst
.
//stateMcHgen-Syntax: Any state can have one checkTrans...().routine. //Its prototype should be defined in the generation input header (ExampleSimpleData.states.h). //A checktrans routine without prototype is not used. // //The prototype should have the same parameter list as the TransFn, but additionally the state reference is given as last argument. //With that state argument the transition should be selected. //If the transfunction has an abbreviating parameter list than the generated code does not match, it causes a compiler error. // //The generated code contains a checkTrans routine for any state which calls this routine but calls the checkTrans routine //from all environment states too. // //The user documentation for the transition should be given on the prototype because a documentation will be generated with them. // int checkTrans_Off(ExampleSimpleData* thiz, int ev, Off_State const* state) { if(ev == kOn_Cont_Event_ExampleSimple){ return state->workHistory(thiz, ev); } else if(thiz->on_ready) { //thiz->trans(&states.Work.History, &data->state) | thiz->trans(&states.Work.Ready, &data->state); } return state->Work(thiz, ev); } else return 0; }
This routine gets the ...StateConst
as argument. It calls state->Work(...)
which is a generated routine referred in the Off_State_StateConst
:
//Transition of Off_State static int doTransOff_State_Work(struct ExampleSimpleData_t* thiz, int event) //(stateMcHgenC.jzgen dotrans) { int trans = 0; //state: Off_State //exit the current state of this composite till given level: (stateMcHgenC.jzgen: doTrans-exit) exitGenState(thiz, event, thiz->state.statetop, 0); transAction_Work(thiz); //(stateMcHgen.jzgen: transAction) thiz->state.statetop = &Work_StateConst.Top; //(stateMcHgenC.jzgen: set state) entry_Work(thiz); //(stateMcHgen.jzgen: call entry) #ifdef __DEBUG_entryprintf_States_Fwc__ printf(" entry Work;\n"); #endif thiz->state.stateWork = &Ready_State_StateConst.Work; //(stateMcHgenC.jzgen: set state) #ifdef __DEBUG_entryprintf_States_Fwc__ printf(" entry Ready_State;\n"); #endif trans |= mTransit_States_Fwc; return trans; }
This routine calls exitGenState(...)
to exit the current state maybe with exit sub states. Then the state variable is changed with the new ...StateConst
. Then all entry_()
-Routines in the correct order are invoked if there are existing (the prototype was found by generation). A printf
is generated for better debugging. That can be changed if the generation script is changed for that.
exitGenState(...)
:
The doTrans...
routine is generated for that state where a transition was found in the struct ...State
. It is possible that it is not a leaf state but a composite (enclosing) state. It means that not only that state should be
exiting but the current state which is known only at runtime, not at generation time. Therefore the exitGenState(...)
routine starts with the found current state and calls exit for all states of the state path till the common state of transition.
The last state to exit is given by the last argument, the index in state path. In this example it is 0 because the last exting
state is at top level.
The exitGenState(...)
routine is a standard algorithm but it is generated to regard the user data type:
//The generated exit routine calls the exit of the current state of composite //and exits all states in the hierarchie till exclusive the parent composite state. //It is used when the parent composite state is exited. //(stateMcHgenC.jzgen: exitComposite) static void exitGenState(struct ExampleSimpleData_t* thiz, int event, StateBaseGen const* stateCurr, int level) { if(stateCurr != null) { if(stateCurr->kind == kComposite_EKindState_StateMachine_Fwc) { StateBaseGen const* stateCurrent = stateCurr->stateCurrent(thiz, event); //exits the current state(s) of composite, recursively for all composites. But not the composite itself. exitGenState(thiz, event, stateCurrent, stateCurr->ixOwnState +1); } //int levelComposite = stateCurr->ixCompositeState; //if(levelComposite < level) { // levelComposite = level; //no more //} StateBaseGen const* stateExit = stateCurr; StateBaseGen const* stateExitLast = null; int ixStatePath = stateCurr->ixOwnState; do { if(stateCurr->kind == kParallel_EKindState_StateMachine_Fwc) { stateExit->exitParallel(thiz, event, stateExitLast); } stateExit->exit(thiz, event); stateExitLast = stateExit; stateExit = stateExit->statePath.ptr[--ixStatePath]; //the enclosing state. } while(ixStatePath >= level); //Composite); } }
Because a StateComposite
has an own current state the routine is called recursively for a current composite state. Then a do-while loop is processed
from the ixownState
in the statePath
till the given level
of the last exiting state. If one of the state in the path is a StateParallel
all states of any parallel bough should be exiting. Therefore the generated and referred routine exitParallel(...)
is called. This routinen knows by generation the parallel boughs. Because one bough, from the detect current state, is already
exits, it is given as argument for exclusion.
In the simple case only the current state->exit(...)
routine was called.
History entry:
The history entry uses the saved current states of a before-inactive StateComposite to re-enter. This routine uses the statePath
to enter all states of the path till the current history states:
//Prototype for the doEntry execution in deep history. static int doEntryDeepHistory(struct ExampleSimpleData_t* thiz, int event, StateBaseGen const* stateDst); //Routine to entry in the old current state(s), invoked if a History pseudo class is given as destination in a transition. //Note: it is only visible in this compilation unit, static and without prototype in a header. //Note: It is the same algorithm as in Java: org.vishia.states.StateComposite.doEntryDeepHistory(...) // but the argument types are generated, in Java there is an automatic aggregation to the enclosing class. // static int entryDeepHistory(struct ExampleSimpleData_t* thiz, int event, StateBaseGen const* stateComposite) { int cont; StateBaseGen const* stateDst = stateComposite->stateCurrent(thiz, event); //get the old current state if(stateDst == null) { //on first history entry, an history state is not known. stateDst = stateComposite->statePath.ptr[stateComposite->ixOwnState+1]; //the default state is the last state in statePath (stateMcHgenC.jzgen: stateDefault) } cont |= doEntryDeepHistory(thiz, event, stateDst); //stateDst is the old current state of this composite. return cont; } //Routine to execute the entry in deep history. //Note: It is only visible in this compilation unit, static and without prototype in a header. //Note: It is the same algorithm as in Java: org.vishia.states.StateComposite.doEntryDeepHistory(...) // but the argument types are generated, in Java there is an automatic aggregation to the enclosing class. // static int doEntryDeepHistory(struct ExampleSimpleData_t* thiz, int event, StateBaseGen const* stateDst) { int cont = 0; int ix; StateBaseGen const* const* statePath = stateDst->statePath.ptr; int ixOwnState = stateDst->ixOwnState; //The last position for the statePath in statePath for(ix = stateDst->ixCompositeState+1; ix <= ixOwnState; ++ix) { StateBaseGen const* stateEntry = statePath[ix]; cont |= stateEntry->entry(thiz, event); } if(stateDst->kind == kComposite_EKindState_StateMachine_Fwc) { //if the current state is a composite too: entryDeepHistory(thiz, event, stateDst); } else if(stateDst->kind == kParallel_EKindState_StateMachine_Fwc) { //If the current state is a StateParallel, enter all parallels with deep history: for(ix = stateDst->ixOwnState +1; ix < stateDst->statePath.length; ++ix) { StateBaseGen const* stateP = statePath[ix]; cont |= stateP->entry(thiz, event); ////entry in the parallel state bough, composite or simple if(stateP->kind == kComposite_EKindState_StateMachine_Fwc) { cont |= entryDeepHistory(thiz, event, stateP); } } } return cont; }
The code is similar to the Java code in srcJava_vishiaBase/org/vishia/states/StateComposite#entryDeepHistory(java.util.EventObject).
TODO explain
OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD
This style of code generation uses a switch-case..-construct as core processing of a state machine, how it is used often for hand-written state machines:
switch(stateVariable) { case stateA: if(conditionXYZ){ //switch to stateB... ....
To keep overview all entry-, exit- and trans- actions are contained in extra methods (or C-functions).But that C-functions
are written as static
or inline
. Therewith the compiler can expand the machine code without calling the sub routine, create a flat code.
A further important decision for code generation is: If a transition fires, then the switch-case
quest is repeated till no more transition fires. That is the run-to-complete-principle of state machine's processing. Maybe manual written state machines don't thing about such ones. But it is consequently
and correct. Therewith the core construct of state-switching looks like:
int stepStates_State_ExampleSimpleData( struct ExampleSimpleData_t* thiz, int event ) { int trans = 0; //set to true if transition has fired. int ctSwitchState = 10; //prevent too many transitions - a endless loop do { trans &= ~mTransit_States_Fwc; switch(thiz->state.statetop) { case kStateA: trans |= transXYZ(thiz, event); break; case ... } //switch } while( trans & mTransit_States_Fwc) //continue checking transitions if one of the has fired, it is run to completion. && --ctSwitchState >0); //prevent hanging }
The next important decision for code generation is: support parallel states, support states with an history entry. For parallel states any of the parallel bough need in own state variable. For states with an history entry an own state variable is necessary to save the state. But commonly for composite states there is no necessity for one state variable for each composite state. An ordinary composite state without history has the capability that it has inner states, and its transitions have to be regarded. But all inner states can be immaged with one state variable as flat presentation. If the user defined an composite state to pool some states for only one common transition or only for documentation, the generated code should not become more complex.
All state variables for parallel and composite-with-history states are processed in nested switch-case operations. Therewith the code looks like the example see * examples_Zmake/StateMGen/result.cmp/exampleSimpleStates.c.html#stepStates
do { switch(stateVariable) { case stateA: if(conditionXYZ){ //switch to stateB...
Topic:.stateM_en.stmC.C1.
The given scripts with the -c:
option in the command line above are the standard scripts used for this example:
zbnfjax/zbnf/StateMCpp.zbnf: ZBNF-syntax for C++ source of a state machine.
zbnfjax:zbnf/StateMCpp.zbnf: ZBNF-syntax for C++ source of a state machine.
zbnfjax:zbnf/StateMJava.zbnf: ZBNF-syntax for Java source of a state machine for the same approach.
zbnfjax:zmake/States.genC1.jzgen: JZcmd generation script for C-code of a state machine.
zbnfjax:zmake/States.genH1.jzgen: JZcmd generation script for the headerfile.
zbnfjax:zmake/States.genDocu.jzgen: JZcmd generation script for a documentation source with topics.
zbnfjax:zmake/States.genHtml.jzgen: JZcmd generation script for a documentation as html file.
The result of the translation are the named C- and Header file and a documentation. The output generated with the standard scripts have the following form. The example shows that code which was generated for the given C++ source code only for this one state of the example. The code is more complex if nested and parallel states are used. Visit the example source and generated files:
examples_Zmake/StateMGen/src/ExampleSimple.state.cpp.html: source in cpp
examples_Zmake/StateMGen/result/exampleSimpleStates.h.html: generated files
Headerfile:
//This file was generated by StateMGen - States.genH1 #ifndef __exampleSimpleStates_h__ #define __exampleSimpleStates_h__ /**This struct contains all data which are necessary in the generated code for the state processing. */ typedef struct State_ExampleSimpleData_t { /**Contains the state identifier for nested level with history or parallel states. */ int statetop; .... } State_ExampleSimpleData;
The state machine needs one or more as one state variable. The standard generation uses a simple integer variable for the state which is set with several values of a define-macro. This form is common used for state programming in C. A maybe better and faster variant is the usage of function pointers because the switch-case-statement is not necessary then. More as one state variable is necessary for parallel states or states with a history. Use this struct to embedd it into the users data.
int stepStates_State_ExampleSimpleData(struct ExampleSimpleData_t* thiz, int event);
That is the prototype for the step routine which can be invoked by the application either cyclically or if an event is present in a users queue. Examples for event handling are contained in the example.
#define kOff_State_ExampleSimpleData 1 ...
That are the values for any state, which can be evaluated by the application too.
#endif // __exampleSimpleStates_h__
That is the header file. It contains only public elements which can be used by an application.
The generated C-File:
/*This file is generated from StateMGen.java */ #include "ExampleSimpleData.h" #include <stdio.h> #include "exampleSimpleStates.h"
The genrated C-file includes all of the headers which are included in the C++ source.
//all entry-prototypes: void entry_Off_ExampleSimpleData(struct ExampleSimpleData_t* thiz, int event); ... void exit_Off_ExampleSimpleData(struct ExampleSimpleData_t* thiz, int event); ...
The prototypes in the generated C source are necessary because they should be defined before usage.
The entry- and exit method is defined as static
(private visible) because they are used only in this source.
static void entry_Off_ExampleSimpleData(struct ExampleSimpleData_t* thiz, int event) { //genStateM: entry StateComposite or StateSimple. thiz->state.statetop = kOff_State_ExampleSimpleData; thiz->work = 0; #ifdef __DEBUG_entryprintf_States_Fwc__ printf(" entry Off;\n"); #endif }
The entry method contains the association of the state value to the state variable.
INLINE_Fwc void exit_Off_ExampleSimpleData(struct ExampleSimpleData_t* thiz, int event) { #ifdef __DEBUG_entryprintf_States_Fwc__ printf(" exit Off;\n"); #endif }
The conditional printf
statement is a nuance of the code generation with this script. It may be helpfull for debugging on runtime.
All transitions and the inState
-routine in the C++ source are generated into one trans...
method with a chain of if(...)
in order of the transitions in the C++ source.
INLINE_Fwc int trans_Off_ExampleSimpleData(struct ExampleSimpleData_t* thiz, int event) { int trans = 0; //genStateM: check all conditions of transitions, return on transition with != 0 if(thiz->on_ready) { exit_Off_ExampleSimpleData(thiz, event); thiz->work = 1; entry_Work_ExampleSimpleData(thiz, event); entry_Ready_ExampleSimpleData(thiz, event); trans = mTransit_States_Fwc; } else if(thiz->on_cont) { exit_Off_ExampleSimpleData(thiz, event); entry_Work_ExampleSimpleData(thiz, event); trans = mTransit_States_Fwc; } else { //StateMGen: action in state. No transition switched. thiz->counter +=1; } return trans; }
Last not least a complex stateswitch routine is generated, which contains the switch-case
for the states:
int stepStates_State_ExampleSimpleData( struct ExampleSimpleData_t* thiz, int event ) { int trans = 0; //set to true if transition has fired. int ctSwitchState = 10; //prevent too many transitions - a endless loop do { trans &= ~mTransit_States_Fwc; switch(thiz->state.statetop) { //if the state was entried newly without define an inner state, then the statedefault will be entered now. //Note that the default state cannot be entered on entry action because it is unknown in that time //whether there will be an entry to a designated state. case 0: entry_Off_ExampleSimpleData(thiz, event); //without break, do trans: //switch to the current state: case kOff_State_ExampleSimpleData: trans |= trans_Off_ExampleSimpleData(thiz, event); break; ... ... } while((trans & mTransit_States_Fwc) //continue checking transitions if one of the has fired, it is run to completion. && thiz->state.statetop !=0 //don't continue if the composite is inactive now. && --ctSwitchState >0); //don't execute too may loops, only a safety check. // //for all parallel states: switch only if this state is the active one still. Regard a leave of the state from any substate. return trans;
}
See article StateMGen for more explaination.
Topic:.stateM_en.Paralnested.
The possibility of parallel and nested states is a known property of UML statecharts. It may be a proper means of expression for states. Look for the exampe:
It is a test example contained in srcJava_vishiaBase/org/vishia/states/example/StatesNestedParallel.java
.
Any process can be switched on. The idle state is Off
. An event or condition on_ready
switches to ready for operation or in the state Ready
.
The event or condition start
activates two states with two parallel threads. The left thread Active1
controls the process between Running
and Finit
. In practice there may be some more states. The right thread Active2
controls a user interface for the process. If the process runs and the user press a button offAfterRunning
, then the Active
-state is left, switches to Off
, if the left thread reaches Finit
. The user can press that button (repectively send the event or sets the condition) offAfterRunning
while the state is Running
, it is independent, e.g. parallel.
If the state Finit
was reached, and the offAfterRunning
was not given, the state switches to Ready
. That is because the transition between Finit
and Ready
which checks the state of the parallel thread.
Furthermore the process can be switched off
any time without condition. It is the transition from Work
to Off
. Additionally the Running
process can be aborted only after offAfterRunning
It is the transition between ShouldOff
and Off
Last not least the process can be switched from Off
to the last active state with the History state to continue its work. That is the counterpart to the off
transition between Work
and Off
.
That example is a constructed example which uses all possibilities. It may be proper for practice in any kind.
Event or Condition:
From view to this state machine it is not defined whether the transitions are triggered from events or conditions. Event driven programming comes together with the state machine thinking often, but it is another topic generally. From this view, event or condition is a question of implementation more than a topic of design. Using of events is a decision of software organization more than a decision of the users scope.
The threads of parallel states: From view of the operation system there are user threads. Never it is multithreading of the operation system. The state machine runs checking events or conditions, one parallel state after another in the same operation system thread. Then it waits for the next event or condition check step. If data should be handled consistently, there is not an interrupt between state switch actions. Sometimes that user threads are named fiber.
How does it should be written in Java:
See the source code in srcJava_vishiaBase/org/vishia/states/example/StatesNestedParallel.java
.
The parallel states are written like
Topic:.stateM_en.event.
From view of the execution of a state machine it is not meanfully whether the transitions checks events or conditions. Event driven programming comes together with the state machine thinking often, but it is another topic generally.
The quest is: Who executes the switching of a state machine, the transition test. There are more as one possibility:
Any thread which changes a condition executes the state machine after them: That seems as a simple approach. It is possible
to do so but it is not recommended. If another thread executes the state machine concurrently, it may be confuse. The state
execution is not thread safe. It is possible to guard the state machine execution with synchronized
(a semaphore).
One thread executes the state machine cyclically independent whether conditions are changed or not. In that case events are not necessary. The conditions should be atomic. Therewith no thread problems are expected. The conditions can be changed in any other thread therefore. This schema is proper to use for example in cyclic threads of a controlling algorithm. It is a important and noteworthy possibility!
There is only one thread which executes the state machine cyclically and checks and changes conditions. It is possible that this thread knows divided sampling times. This mechanism assures that operation are not interrupted. Then there are no thread problems. This mechanism is proper to use for controlling algorithm too. It is the simplest way for embedded control.
The state machine is executed only if an event is given. The execution thread is a special thread which sleeps and waits for events. It is only active if an event is found in the event queue of that thread. That is known as event driven mechanism for state machines. It is important but not the only possible one. The state machine is executed on a given event. If only a condition is changed nothing occurs.
In an event driven mechanism it is possible to force the execution of the state machine by a cyclically (timer) event. That event may not requested by the state machine, but the conditions of all transitions of the active states are checked then. That is similar like the cyclic non-event-driven execution, able to use if conditions should determine the state machine in an event driven environment.
Events and Conditions can be used simultaneously. The execution of a state machine can be forced only by events or cyclically.
Topic:.stateM_en.event..
Using conditions may precipitate a problem: If a condition was set after an action, for example a key was pressed, the state machine reacts on this condition. That is okay. But if the condition is given a longer time (the key is still pressed, a status bit is set continuing) and the state machine enters the same state again, the condition force switching again.
Sometimes event-like condition bits are used: One thread sets the bit (if the key was pressed, if the other state is comming). If the condition is discerned by the state machine the event bit is resetted to prevent twice discerning.
An event bit may set in an older time. It was not resetted. Later the state machine is starting and the event bit was discerned though it is not expected in that context. To prevent such a behaviour all event bits should be cleaned in some states.
Using a simple event mechanism may be better than using event bits. The advantage: An event is a communication mechanism which acts only in the present. An event is not stored for a longer time.
The event driven execution of a state machine is the proper solution for a complex thread system: Any thread changes a condition,
itself or another thread sends the event, the event contains data for execution, some other threads may sent events simultaneously.
All of the event are written in the same queue in its order of emitting. The writing is executed in any thread but threadsafe
for the queue. A java.util.concurrent.ConcurrentLinkedQueue
is a proper class for that.
Topic:.stateM_en.event..
All events are evaluated in the same thread. That thread can execute not only one state machine but any number of ones. The advantage of execution of some more state machines in one thread is: There are not problems of thread safety if the state machines works with the data of some other classes concurrently. The execution process which is forced from one event is named fiber, or sometimes user thread. All fibers of the state machines thread are executed one after another without interruption. But on the other hand it means, an execution process of one event is delayed if there are some more events for some other state machines. Usual an execution process should be shortly in time. If an execution duration is for example 1 ms, the expected response time is about 100 ms in maximum and there are about 100 Events simulatneously emitted, it is narrowly. Therefore for deterministic systems especially cyclically controlling processes an event queue may not the proper solution.
A transition should never delayed by any Thread.sleep(...)
or Object.wait(...)
invocation or by a synchronized[...}
operation except that is a guaranteed short waiting action because a high priority thread processes a short action. If one
transition is delayed, the whole processing of all state machines are delayed which works in this same event thread.
An implementation for such an event queue thread is given with the class org.vishia.event.EventTimerThread
. This class works with org.vishia.event.Event
instances which contains a reference to a org.vishia.event.EventConsumer
-interface. The org.vishia.states.StateMachine
implements that interface to execute it.
If a state machine should be executed then the method
StateMachine.applyEvent(event);
should be invoked. Either the StateMachine was constructed with a given EventTimerThread
then the event is stored in the queue and the execution is done in the event thread, or the StateMachine is executed immediately.
In the last case the event can bei ommited, null
can be given. The StateMachine has not an extra execution line for non-event (only conditional) execution because from view
of the state machine's execution an event is also a condion only. But an event can contain data which are forwarded to the
actions of the state machine!
Topic:.stateM_en.event.RunCompl.
The state machine is executed if any event is given. If the given event cannot be applied to any transition the event is removed after them. It is not stored and applied later again. This is an important mechanism. If an unexpected event is stored and applied later again, an unexpected reaction can be occured. The known deferred event in the OMG.org documentation /omg-uml/ page 587 is a special case only!
If an event was applied to one transition in a sequence of transitions it is not applied to a next transition. It is removed after usage. But an event will be applied to all current parallel states. It is possible that one event switches more as one parallel state. That is UML-conform. See /omg-uml/ page 575.
An event will be applied firstly to the inner state of a composite state. Only if the event is not used there, it is applied to the composite state levels. See /omg-uml/ page 575 Firing priorities:
omg-uml/: In the presence of orthogonal regions it is possible to fire multiple transitions as a result of the same event occurrence as many as one transition in each region in the current state configuration.
If an event has triggered the execution of the state machine then all following transitions which are not event triggered are fired, if there condition (guard) is true. That is done for all parallel states.
Topic:.stateM_en.rule.
===State machines in C and C++===
The
C++
Topic:.stateM_en..
/Jstm/: Java-sources with base classes and examples for state machines how it is described in this article, contained in the software component srcJava_vishiaBase. This softwarecomponent is able to download via "www.vishia.org/Java/srcJava_vishiaBase.zip". An bazaar-archive is contained in
/omg-uml/: Documentation of the UML standard of omg.org, able to find in "http://www.omg.org/spec/UML/2.4.1/Superstructure/PDF"