Live Diagram
Overview
Live Diagram is a library derived from Diagram Studio. It has all the
features of the base suite and moreover it can execute the diagrams.
The main component is the Live Diagram (TLiveDiagram), a graphic control
able of managing the thread-safe execution of its Live Blocks
(TCustomLiveBlock descendants).
To have an executable Diagram, basically we need of design rules and
execution strategy. Design rules state how the blocks can be connected
each other to form a "well formatted" execution chart, execution
strategy states how the blocks must be executed i.e. their flow.
These rules are not set in the diagram, TCustomLiveBlock descendants
implement them, so they can be easily extended.
In a Live Diagram can be inserted Live Blocks and traditional components at the same time, but only live blocks will be "managed" (checked, linked, executed).
Purpose
Computer science fundamentals training and teaching.
Algorithms check.
Rapid prototype.
Extreme user-customization of applications.
Features overview
Inheritance of all features of TatDiagram without restrictions.
Design time support for executing native Delphi code.
Separate threads for execution and visualization, both synchronized.
Balancing of execution thread CPU consumption.
Thread safe model, multiple diagram can run at the same time.
Step by step execution mode.
Possibility of executing diagrams without a line of code.
Specialized blocks : Error handler, Line jointer, Connectors.
Multiple executions paths.
Lacks overview
Not suitable for industrial automation or critical applications.
Non time-deterministic execution.
Simulated multithread (due to Synchronize method).
TLiveDiagram component
It's the Live Diagram main component, it inherits from TatDiagram.
Key Properties
State: TDiagramState = (dsEdit, dsRunning, dsPaused)
Reflects the status of the diagram. It can be used to enable/disable
actions and buttons.
When not in edit mode, the editing is disabled. Do not modify State
directly, use DoAction method instead.
RunError: integer
Contains the value of the last Error flag (see TCustomLiveBlock.Execute).
ExitCode: integer
After the execution termination contains the exit code (see TCustomLiveBlock.Execute).
Liveds[index : integer]: TCustomLiveBlock
Contains the list of all LivedBlocks. It's only filled after a call to
Link method. Do not use it in edit mode.
IdlePercent: DWORD
Allows the CPU balancing. Every block execution is time-marked, so if a
block is executed in 20ms (for instance) and IdlePercent=50 the
execution thread will sleep for 20ms.
It's possible to modify it during the diagram execution.
SleepVisual: DWORD
It refers to the visual thread and is a fixed value of sleep time.
RunColor: TColor
It's the background diagram color during the execution.
Key Methods
function Link: boolean
Checks the diagram consistence and links the blocks, returns true if
everything is ok. After a successful link, Liveds property contains the
lived block list. Everything that doesn't inherits from
TCustomLiveBlock or from a wire is ignored.
Don't call Link directly, override it or PrepareRun.
function PrepareRun: boolean
It's invoked every time the run is request if the diagram was modified.
Just calls Link, override this method to perform special init
operations.
procedure DoAction(Action: TDiagramAction)
TDiagramAction = (daRun, daRunPaused, daPause, daStop, daForceStop, daReset, daStep)
Performs a diagram operation:
- daRun: Starts the diagram execution;
- daRunPaused: Starts the diagram execution in pause mode, a daStep command is necessary to continue;
- daPause: Pauses the execution;
- daStop: Stops the execution;
- daForceStop: Forces the stop without firing OnTerminate event;
- daReset: Resets the execution - Start became the current block;
- daStep: Executes the next block and returns in pause mode.
An operation to be performed must be compatible with the Diagram state, otherwise it's ignored.
dsEdit | dsRunning | dsPause | |
---|---|---|---|
daRun | OK | Ignored | OK |
daRunPaused | OK | Ignored | Ignored |
daPause | Ignored | OK | Ignored |
daStop | Ignored | OK | OK |
daForceStop | Ignored | OK | OK |
daReset | Ignored | Ignored | OK |
daStep | Ignored | Ignored | OK |
Key Events
property OnStart: TStartEvent = procedure(Sender: TLiveDiagram; StartMode: TStartMode)
TStartMode = (smCold, smWarm)
This event is fired whenever the daRun action is requested,
StartMode=smCold if the diagram starts the run from dsEdit,
StartMode=smWarm if the diagram starts the run from dsPaused.
Note
daStep action doesn't fire this event.
property OnChangeState: TChangeStateEvent = procedure(Sender: TLiveDiagram; NewState: TDiagramState)
This event is fired whenever the diagram state changes.
property OnUnhandledRunError: TRunErrorEvent = procedure(Sender: TLiveDiagram; Block: TCustomLiveBlock; var Error: integer; var ResumeNext: boolean = false)
This event is fired when an error occurred (User Error flag>0, see
TCustomLiveBlock.Execute) and an Error handler doesn't exists.
Block is the LiveBlock that raised the error, if ResumeNext is set to
true the error is zeroised and the execution continues with Block.Next,
otherwise the program is terminated with Exitcode=Error.
If this property is not assigned and Error handler doesn't exists the
program terminates with Exitcode=xUnhandled_error.
property OnShift: TShiftEvent = procedure(Sender: TLiveDiagram; OldBlock, NewBlock: TCustomLiveBlock)
This event is fired when NewBlock is being to execute, OldBlock is the
block just executed.
The method that fires the event is DoShift (virtual).
Warning
OldBlock or NewBlock could be nil.
property OnTerminate: TTerminateEvent = procedure(Sender: TLiveDiagram; ExitCode: integer)
This event is fired when the diagram terminates the execution.
TCustomLiveBlock base class
It is (and must be) the ancestor of all live blocks. It inherits from TCustomDiagramBlock. It's a base class, so isn't registered with RegisterDControl.
Key Properties
ControlState: TControlState = (csEdit, csRunON, csRunOFF, csRunERR)
It's the block state, this property is used to show different color
during the block execution.
PassThrough: boolean
It's true when the block is not "executable", a passthrough block is
used to perform special connections (see Jointers and Connectors) and
it's completely transparent to the execution. When implementing a
passthrough block a mechanism to avoid endless loop during the link
process must be set (see at TLiveJoin.GetNext).
IsErrorHandler: boolean
A descendent block that implements error handling must set to true this
property. Many error handler can be defined in the same set, but only
one of these can be inserted in a chart.
IsStartBlock: boolean
Every chart must contain a start block, it must be unique, this property
must be true when the descendant is defined as start point.
IsEndBlock: boolean
Every block can terminate the diagram, but only the End Block will
terminate it with the code xNormal_termination (see Execute procedure).
The End Block must have this property set to true.
Next: TCustomLiveBlock
Points to the next executable block. The execution strategy is
implemented here (the value is read through GetNext method).
RunColors: TBlockColors = class(TPersistent)
Stores the three run colors associated to the ControlState (when in edit
mode the block colors are stored in Color and SelColor properties).
It would be better to avoid the use of bitmaps in Liveblocks, the
gradient is automatically disabled during the execution, the shadow
doesn't create any problem.
Key Methods
function Link: boolean
Finds the next block and stores it into FNext (the field image of Next),
returns true when FNext is not nil. Every block which can have an
unassigned Next must return true to avoid a design error (see
TLiveHeader and
TLiveErrorHandler).
Link is called by TLiveDiagram.
function Execute(var Error, Decision: integer): TCustomLiveBlock
Executes the block. Do not call Execute directly, it's declared as
public only to be visible outside the unit, OnExecute must be used
instead. The variable Error is an user passthrough flag in accord to:
0: the execution continues with the Next block;
-1: the block requested the "End of Program", if the block has IsEndBlock=true the program exitcode is xNormal_Termination (0), otherwise the exitcode is xProgram_Termination (2);
-2: there was an exception in the OnExecute event, the program will terminate with xCode_Exception (4) exit code.
>0: the Error handler block is the next block to be executed. If it doesn't exists in the chart, TLiveDiagram fires the OnUnhandledRunError event.
The variable Decision must be set only for special blocks that perform
decisions, like
TLiveDecisionBlock and
TLiveCaseBlock.
The result of the function is the next block that have to be executed,
to perform special operation it would be better to override GetNext
method.
procedure DoBeforeRun
This method is called whenever the diagram is about to be executed,
override it to perform initialization stuff.
function AcceptLink(ALink: TCustomDiagramLine; LinkIndex: integer; Direction: TLinkDirection): boolean
TLinkDirection = (ldInput, ldOutput)
It's the key method for implementing the design rules. LinkIndex is the
block linkpoint number, Direction reflects the link (TCustomDiagramLine)
direction source or target.
This function is called by TLiveDiagram whenever a link between two link
points is about to be made. Do not call this method directly, override
it to perform special checks.
function GetNext: TCustomLiveBlock
It's the read function of the property Next, override this function to
setup the execution strategy.
Note
Do not perform heavy operation in this function to avoid slowing down, since it's called at runtime.
Key Events
property OnExecute: TExecuteEvent = function(Sender: TCustomLiveBlock; var Error, Decision: integer): integer
This event is fired on the block execution. See Execute method.
Visual examples of Passthrough, Next and Link
In the diagram below, the flowchart at the left performs the same thing as the flowchart at the right. The "A" blocks are TLiveConnectorSource and TLiveConnectorTarget blocks, and after (following) the second "A" there is a TLiveLineJoin. Those three blocks have the Passthrough property set to true, so they perform no action at all.
Also, in both diagrams, the Block1.Next property points to Block2.
In the diagram below, after a call to Link method, the error message appears. Regardless of the strange "path", Block1 is unlinked.
Basic live diagram objects
All live diagram objects are available under the category "Live Diagram". This category is displayed in the diagram toolbar.
For now, there are 3 groups of live diagram objects: basic, flowchart and statechart. This topic covers the basic objects.
From the left to right in the above picture:
TLiveConnectorSource and TLiveConnectionTarget
These blocks connect two or more points of a diagram to increase the readability. They are not executable i.e. PassThrough=true.
To be linked they must have the same text (case insensitive). Many source connectors can be linked to an unique target connector. In the picture below, Decision1.Next = Subroutine1.Next = Document1.Next = Action1.
TLiveLineJoin (Line jointer)
Joints multiple input links with an unique output link, it's not executable i.e. PassThrough=true.
Wire objects
The wire connection objects are TLiveWire (Live wire), TLiveSideWire (Live side wire), TLiveArc (Live arc) and TLiveBezier (Live bezier). Other wire objects can be created, they behave the same way.
The wire connections are "Vector" DiagramLines i.e. the target arrow is always explicit. They are connection aware, when a wire is not connected it's line appears dotted.
When their SourceLinkpoint is connected to a Case block an unique integer value must be written in the text cell.
Key Methods
function Transition(Sender: TCustomLiveBlock): boolean
This function fires the OnTransition event, returns false if the event
is not assigned.
Wires are not TCustomLiveBlock descendants, so the blocks that need to manage transition must call this function during their execution (see StateChart blocks implementation), Sender is the Block that requests the transition check.
Key Events
property OnTransition: TTransitionEvent = function(Sender: TCustomDiagramLine; FromBlock: TCustomLiveBlock): boolean
Is the user event fired by the Transition Function. Sender is the Wire
and FromBlock is the Block that called the function.
Flowchart objects
This set of objects (avaible in the LiveFlowChart.pas
unit) allows the
creation/execution of Flowcharts. The base rule implemented is that
every block can have many inputs and only an output (except for decision
and selection).
The integer selector (case) and the error handler are not present in the base flowchart specification, they represent a power extension.
The execution of a flowchart only depends on the blocks, the wires are only links.
From the left to right:
TLiveActionBlock (Action block)
Is the main live executable block, it inherits from TCustomLiveBlock. PassThrough property is false and OnExecute event is published.
Key Properties
ActionShape: TActionShape = (asBox, asAlternate, asData, asDocument, asInput, asSubRoutine)
It's only a cosmetic property, it can be changed at runtime without
loosing the linkpoints information i.e. the wires connected remain
valid.
TLiveDecisionBlock (Decision)
Performs the decision of flowchart, it inherits from TCustomLiveBlock. PassThrough property is false and OnExecute event is published. The Decision variable in the OnExecute method tells the digram if the decision was false (value 0) or true (value different from 0).
Key Properties
DecisionShape: TDecisionShape = (dsBottomRight, dsRightBottom, dsLeftRight, dsRightLeft, dsBottomLeft, dsLeftBottom)
It's only a cosmetic property but is very useful to have nice diagrams,
it can be changed at runtime without loosing the linkpoints information
i.e. the wires connected remain valid.
TLiveCaseBlock (Case)
Performs an integer selection (called case in more programming languages). It inherits from TCustomLiveBlock. PassThrough is false and OnExecute event is published. The next block executed depends of the integer value of parameter Decision of OnExecute event. If no case was matched, the block connected to ELSE output will be executed. Many wires can be connected to case output, and the chosen wire will be the one which text (caption) is the same as the number returned in the Decision parameter of OnExecute event.
Key Properties
CaseShape: TCaseShape = (csBottomRight, csBottomLeft)
It's the cosmetic property that allows different orientation of the
block, it can be changed at runtime without loosing the linkpoints
information i.e. the wires connected remain valid.
TLiveStartBlock (Start program)
It's the start point of the program, it's executable and can be executed once, so usually should contain initialization code.
Must be always present and must be unique. The text "START" can be freely edited.
TLiveEndBlock (End program)
It's the endpoint of the program, it's executable and can be executed once, so usually should contain deinitialization code.
Can be absent. The text "END" can be freely edited.
TLiveErrorHandler (Error handler)
It's an asynchronous block, it's executed when a block exits with
Error > 0.
It has no input, if it's Next is assigned the execution continues
there, otherwise is performed an implicit "resume next", i.e. is
executed the next block in the chain.
Error handler can be absent, but if present must be unique. To avoid
loop it must reset the error variable. The text "OnError" can be free
edited.
For historical reasons, the Error handler doesn't exists in the
traditional flowchart component set, it creates a "not structured"
point in the diagram.
Its use should be limited to error handling, use the decision for normal
branch.
Fixed output
Resume next
TLiveHeader (Header)
It's just a container and is not executable.
State chart objects
This set of objects allows the creation/execution of Statecharts. The base rule implemented is that every block can have many inputs and many outputs.
The execution of a statechart depends on the blocks and on the wires which are named transitions here, important examples of state diagrams are Petri's Net and Grafcet.
In this implementation the Transition method of wires is used, an extension could be possible to write a specialized block (TLiveStateTransition for example) to accomplish the shift check.
Since they are descendant of TCustomLiveBlock, the States blocks also have the Execute method.
The base class for all state blocks is the TCustomLiveState class. All state blocks inherit from it and make use of its key properties.
The avaiable state objects, from the left to right in the above picture, are:
TLiveStateBlock (State): It's the "usable" state block, it inherits from TCustomLiveState and only publishes some properties.
TLiveStateStart (Start state): It's the initial state in the chart i.e. it's the program start point. It accepts only an unique output link freely anchorable at any linkpoint, and there is no transition check i.e. the output link transition is always true.
Its shape reflects the UML standard (a small circle).TLiveStateEnd (End state): It's the program end. It accepts only input links, after it's execution the program terminates.
Its shape reflects the UML standard (a small circle with a black inscribed solid circle).TLiveStateError (Error state handler): It's the StateChart error handler. Since every state can have multiple outputs, here is not possible for the diagram to implement the "resume next" feature, so an output link must always be specified.
This block accepts only an unique output link freely anchorable at any linkpoint.
TCustomLiveState class
It's the ancestor of all "state" components, it implements the design rules and the execution strategy. It inherits from TCustomLiveBlock.
Key Properties
OneShot: boolean
Normally a state is active until a transition is satisfied, if OneShot =
true the event OnExecute is fired once, otherwise it's execute before
every transitions scan.
StateShape: TStateShape = (ssCircle, ssSquare)
It's the cosmetic property that allows different shapes of the block,
it can be changed at runtime without loosing the linkpoints information
i.e. the wires connected remain valid.
Extending Live Diagram
Due to its modular design, the extension of Live Diagram is quite simple. You should proceed through the following steps (after the reading the topics about the live diagram classes like TLiveDiagram, TCustomLiveBlock, flowchart blocks, statechart blocks and wires).
An extension is a set of blocks that must implement design rules and execution strategies; this set, for design coherence should be contained in a separated file (LiveXXXXChart for example), writing it from scratch or inheriting it from an existing one.
1. Set up design rules
As said, they state how the blocks can be connected each other to form a "well formatted" chart.
To do this, override the method TCustomLiveBlock.AcceptLink in order to reflect the rule during the design. The function Link must check the connections and must return true if everything (a design time) is ok, by default it pre-calculates the next block in the execution chain.
Moreover there are some basic rules that are always checked directly by the diagram.
Every chart must contain a Start Block and it must be unique.
Every chart can contain an End Block, it's suggested but it's not mandatory.
Every chart can contain an unique Error Handler Block.
2. Setup execution strategy
After the execution, a block must provide the "next" block that must be executed, by default this was pre-calculated by the function Link. In certain cases (when the block has more then one output) the block must determine at runtime his successor in according to its internal status. For example, the decision block and the case block in the flowchart set override the DoExecute method to do this.
Note that even though a block can have more than one output at design time, it must provide only one "next" at runtime.
Note
LiveChart requests only a well formatted diagram which is not automatically a structured diagram, especially if "case" blocks in flowchart are used (Search Böhm-Jacopini theorem on the Net for further information). Moreover the Diagram doesn't perform any "style check" - it's possible to insert in the same diagram flowchart blocks and statechart blocks.
Live Diagram limitations
- AutomaticNodes property doesn't affects the execution of the diagram but could generate confusion.