protoFSM (+ protoState + protoEvent)
by Jim Schram
Copyright 1996 by Apple Computer, Inc. All rights reserved
Version 1.2 - May 8, 1996

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 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.

-----------

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,
Funcs, Frames, Arrays, Booleans, Integers, & Reals are all supported.

NOTE:  This function does NOT work with recursive/self-referential frames and
ignores _proto and _parent slots. The output format is valid NewtonScript
syntax, except for func slots.

-----------

=============  What's New?

-----------

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.
