protoFSM (+ protoState + protoEvent) by Jim Schram Copyright 1996 by Apple Computer, Inc. All rights reserved Version 7 - April 17, 1997 protoFSM is a set of three user protos designed to make writing finite state machines much easier. To use protoFSM, you simply create a new layout in NTK and drag out a protoFSM user proto "view". Inside protoFSM, drag out protoState "views" which represent the various states of your machine. Inside each state, drag out protoEvent "views" which represent the various events each state can handle. Add viewBounds slots as necessary. Switching to the NTK browser view of your protoFSM implementation, give each layout object a descriptive name or phrase using the "Template Info" feature of NTK. Now, add declareSelf slots containing a symbol you will use to identify each event, state, and the finite state machine itself in each of these "views." For each event, add an Action function if the event is to perform some work. A nil action function or no function at all is acceptable (it just means this event has no work to perform). Action procedures have zero or more parameters (refer to the documentation on fsm:DoEvent(...) for more information). For each event, add nextState slots containing the symbol of the next state the finite state machine should enter after the event's action procedure completes. A nil nextState slot or no nextState slot at all indicates the machine should remain in the current state after the action procedure completes. For each event which is to process the next event "atomically" (i.e. without returning to the main NewtonScript event loop) add a nextNoIdle slot with the value True. A nil nextNoIdle slot, or no nextNoIdle slot at all, will cause the FSM to return to the main NewtonScript event loop as soon as the event's Action routine returns. For each state which is a terminal state, add a terminal slot with the value true. A terminal state indicates the machine is in a "disposable" condition. When the finite state machine is instantiated, it is placed in the Genesis state (a required state). Genesis is a terminal state because the machine simply "exists" at this point; no clean up is required in order to dispose of the machine. You must manually add a protoState "view" with a declareSelf slot containing the value 'Genesis before your finite state machine will compile. Lastly, add an afterScript slot to your protoFSM implementation and insert the following statement: call kFSMCleanUpFunc with (thisView); This function converts the data structures created by NTK's layout editor into "pure" finite state machine structures used by protoFSM. For more information about the use of protoFSM, refer to the Newton Technical Journal article on Finite State Machines by Bruce Thompson and Jim Schram of Newton DTS. ========== Public Methods The following methods are to be used when interacting with protoFSM. All other fields and methods are considered private and are subject to change in future implementations. ----------- call kFSMCleanUpFunc with (thisView); To be called in your finite state machine implementation's afterScript, this compile-time global function converts the data structures created by NTK's layout editor into the state-event structures used by the finite state machine. ----------- call kFSMBuildTemplateFunc with (name, context, definition, engine) --> fsmTemplate This compile-time global function constructs an FSM template from a text file definition or direct frame definition. The name parameter is a symbol indicating the name of the FSM (i.e. the declareSelf slot of an FSM layout). The context parameter can be nil or a frame which will become the _proto of the FSM itself (i.e. the FSM layout). The definition parameter can be a string which is the pathname to a text file containing the FSM definition (i.e. HOME & "myFSM.f") or a frame which is the FSM definition directly (i.e. { state1: { event1: ... } } ). The engine parameter is a reference to the protoFSM proto (i.e. GetLayout("protoFSM") ) or an equivalent FSM engine. The routine is intended to be used as follows: Contents of file "myFSM.f" (the FSM description): { state1: { event1: { Action: func()... nextState: 'state2, }, event2: { Action: func()... nextState: 'state1, }, }, state2: { event1: { Action: func()... nextState: 'state2, }, }, } DefConst( 'kMyFSMContext, { fMyData: nil, MMyFunc: func(...)... }; DefConst( 'kMyFSM, call kFSMBuildTemplateFunc with ( 'myFSM, kMyFSMContext, HOME & "myFSM.f", GetLayout("protoFSM"), ); myFSM := kMyFSM:Instantiate(); // run-time ----------- fsmTemplate:Instantiate() --> fsm Returns an instance of a finite state machine. The machine is initially in the Genesis state (a terminal state) waiting for events. ----------- fsm:Dispose() --> nil Release all resources in use by the finite state machine. This should only be called when the machine is in a terminal state. ----------- fsm:DoEvent(eventSymbol, eventParametersArray) --> nil Posts an event and an array of event action parameters to the state machine's event queue. The calling context of this method can be any frame which inherits to the fsm instance frame. NOTE: The events will be processed when control returns to the main NewtonScript event loop. You may post multiple events; they will be processed in the order in which they were posted. ----------- fsm:ExceptionHandler(exception) --> nil The default exception handler for the state machine. Override this method to perform your own exception management for exceptions not caught by local try-onexception handlers in the event Action procedures. ----------- fsm:?DebugFSM(reasonSymbol, stateSymbol, eventSymbol, parametersArray) --> nil If defined, and in debug builds only, this method is called when there is an inconsistency in the state machine. The reasonSymbol parameter will contain a symbol which describes what the problem is ('UnknownEvent, 'UnknownState, 'NilState). The stateSymbol and eventSymbol parameters correspond to the state and event the machine is currently in. The parametersArray are the parameters to be passed to the event action procedure. ----------- fsm:?TraceFSM(reasonSymbol, stateSymbol, eventSymbol, parametersArray) --> nil If defined, and in debug builds only, this method is called as the state machine executes. The reasonSymbol parameter will contain a symbol which describes what the trace action is ('PreAction, 'PostAction, 'NextState). The stateSymbol and eventSymbol parameters correspond to the state and event the state machine is currently in, and the parametersArray are the event action procedure parameters. ----------- fsm:IsBusy() --> nil, true Indicates when the state machine is busy executing events (i.e. the state machine event queue is not empty). ----------- fsm:GetSpeed() --> integer Returns the speed at which the state machine will process events, in milliseconds. ----------- fsm:SetSpeed(integer) --> integer Sets the speed at which the state machine will process events, in milliseconds. Useful for debugging. Default value is 1 (process events every millisecond). ----------- fsm:GoToState(stateSymbol) --> nil Forces the finite state machine to go to the stateSymbol state, and resets the event queues. NOTE: This function is for DEBUGGING USE ONLY. It is automatically stripped from non-debug builds. ----------- fsm:ProtoClone(readOnlyFrame) --> modifiableFrame Returns a deeply modifiable frame given a read-only (or partially read-only) frame. This function is similar to DeepClone in that it iterates over each nested subframe, but instead of cloning all the slots in each nested subframe, ProtoClone creates new frames with _proto slots pointing to the subframes. NOTE: This function does NOT work with recursive/self-referential frames! ----------- fsm:ObjectToString(object) --> string Returns a string representation of object similar to SPrintObject. However, all NewtonScript data types are supported, as are recursive/self-referential frames and arrays, controlled traversal of _proto and _parent slots, and formatting of real numbers. This function respects the printDepth and printLength global variables in the same way the global Print function does. In addition, the printProto global (true or nil) controls whether or not to follow _proto slots. Likewise, the printParent global (true or nil) controls whether or not to follow _parent slots. The printReal global contains a FormattedNumberStr real number format string (e.g. "%.2f"). Unlike the Print function, all numbers are in decimal, as indicated by a plus ("+") or minus ("-") sign preceeding the number. Frames and arrays are numbered as they are encountered so that if back-pointers exist at some other level in the frame, the correct reference can be seen. ----------- ============= What's New? ----------- 7 To reduce confusion, the version number now matches the actual project version number. The ObjectToString method in protoFSM now handles all NewtonScript data types, as well as recursive/self-referential frames and arrays. The printDepth and printLength global variables are respected, as are three other globals: printProto, printParent, and printReal. See the documentation above for details. Also, unlike the global Print function, all frames and arrays are numbered so that back-pointer references can easily be seen. Numbers are shown in decimal, as indicated by a plus ("+") or minus ("-") sign preceeding the number. ObjectToString can now safely be used as a general-purpose SPrintObject replacement for debugging. ----------- 1.4 There is a new slot, nextNoIdle, which can be added to event frames. This is a boolean slot and specifies whether or not to return to the main NewtonScript event loop after the event's Action routine returns. If true, the next event (if any) will be pulled from the event queue and processed immediately. If nextNoIdle is nil, or does not exist, control will return to the main NewtonScript event loop after the event's Action routine returns, to allow other events to be processed before pulling the next event from the event queue (if any) and processing it. There is no stack build-up with this feature; the next event is simply processed within DoEvent_Loop immediately. There is a new build-time constant function, kFSMBuildTemplateFunc, which allows you to create FSMs from text files or direct frame descriptions rather than from NTK layouts. This is intended for developers who use an external FSM generator application, or for those developers who prefer to code their FSMs in text files. Note that build-time FSM validation is more limited with this method. If you are upgrading from an earlier version of the protoFSM user protos (1.3 or earlier) you may wish to delete the viewJustify slots of both protoEvent and protoState. These slots have been added with sibling justification in order to simplify the creation and maintenance of state and event "views" within the layout. It is no longer necessary to add viewBounds slots to each state and event of your FSM and manually position them within your FSM layout; they will position themselves for you automatically. However, this change will affect existing protoFSM layouts by causing the states and events created with the old justification settings to appear in undesirable locations within the layout. Simply delete the viewJustify slots from protoEvent and protoState, or remove all viewBounds slots from your FSM layout's state and event "views" to correct the situation. These slots are automatically stripped out at build time in the kFSMCleanUpFunc routine. ----------- 1.3 A minor bug fix release. fsm:Dispose() now works correctly. ----------- 1.2 New state frames are constructed only when transitioning to new states. This allows the developer to store information temporarily in the current state which can be referenced by the state's event Action procedures. This is useful, for example, in "debouncing" events that don't involve movement to new states. A nil nextState slot in an event frame is now equivalent to no nextState slot at all. The machine will no longer halt on a nil nextState slot in the event frame. Likewise, the DebugFSM routine will no longer receive the 'NilNextState symbol. ----------- 1.1 Event action procedures are now called in the context of the event frame! The event frame has _parent inheritance to the state frame. The state frame has _parent inheritance to the finite state machine instance frame. These frames are created each time the event action procedure is called, so function closures which rely on the inheritance path will always have a valid path even when the machine is in a new state. The slots currentStateFrame and currentEventFrame can still be used during the event Action procedure to locate the current state and event frames. A new slot (fsm) is located in the finite state machine's "base" instance frame and can be used to locate the instance frame via _parent inheritance from an event Action procedure (similar to the way the base slot is used in the view system). Some methods have been removed in order to reduce the compiled size of the machine. These methods were never documented, and were for debugging use only. ----------- 1.0 Initial release as Newton DTS Sample Code. ----------- No part of this document or the software described in it may be altered in any way and subsequently distributed as original material without prior written permission of Apple Computer, Inc. No licenses, express or implied, are granted with respect to any of the technology described therein. This document and the accompanying software are intended to assist application developers to develop applications only for licensed Newton platforms. Apple makes no warranty or representation either express or implied of the aforementioned technology with respect to the quality, accuracy, merchantability, or fitness for a particular purpose. As a result, this material is distributed "as is" and you, the reader, are assuming the entire risk as to its quality and accuracy. It's sample code, darn it.