unit template_userstatemachine; {$IFDEF FPC} {$mode objfpc}{$H+} interface {$ENDIF} {$I Options.inc} uses {$IFDEF FPC} Classes, SysUtils, FileUtil, ethernet_hub, olcb_transport_layer, template_hardware, olcb_defines, // LCLIntf, // LCLType, {$ENDIF} Float16, opstacktypes, opstackdefines, template_node, opstack_api, nmranetdefines, nmranetutilities; procedure UserStateMachine_Initialize; procedure AppCallback_UserStateMachine_Process(Node: PNMRAnetNode); procedure AppCallback_NodeInitialize(Node: PNMRAnetNode); // Called every 100ms typically from another thread so only use to update flags procedure AppCallback_Timer_1s; // These message are called from the mainstatemachine loop. They have been stored in // internal storage buffers. See the notes to understand the implications of this and how to use them correctly procedure AppCallback_SimpleNodeInfoReply(Node: PNMRAnetNode; AMessage: POPStackMessage); procedure AppCallBack_ProtocolSupportReply(Node: PNMRAnetNode; AMessage: POPStackMessage); // This could be 2 replies per call.. read docs procedure AppCallback_RemoteButtonReply(Node: PNMRAnetNode; AMessage: POPStackMessage); {$IFDEF SUPPORT_TRACTION} procedure AppCallback_TractionProtocol(Node: PNMRAnetNode; AMessage: POPStackMessage); procedure AppCallback_TractionProtocolReply(Node: PNMRAnetNode; AMessage: POPStackMessage); procedure AppCallback_SimpleTrainNodeInfoReply(Node: PNMRAnetNode; AMessage: POPStackMessage); {$ENDIF} {$IFDEF SUPPORT_TRACTION_PROXY} procedure AppCallback_TractionProxyProtocol(Node: PNMRAnetNode; AMessage: POPStackMessage; SourceHasLock: Boolean); procedure AppCallback_TractionProxyProtocolReply(Node: PNMRAnetNode; AMessage: POPStackMessage); {$ENDIF} // These messages are called directly from the hardware receive buffer. See the notes to understand the // implications of this and how to use them correctly procedure AppCallback_InitializationComplete(var Source: TNodeInfo; NodeID: PNodeID); procedure AppCallback_VerifiedNodeID(var Source: TNodeInfo; NodeID: PNodeID); procedure AppCallback_ConsumerIdentified(var Source: TNodeInfo; MTI: Word; EventID: PEventID); procedure AppCallback_ProducerIdentified(var Source: TNodeInfo; MTI: Word; EventID: PEventID); procedure AppCallback_LearnEvent(var Source: TNodeInfo; EventID: PEventID); procedure AppCallBack_PCEventReport(var Source: TNodeInfo; EventID: PEventID); procedure AppCallBack_ConfigMemReadReply(Node: PNMRAnetNode; AMessage: POPStackMessage; Success: Boolean); procedure AppCallBack_ConfigMemStreamReadReply(Node: PNMRAnetNode; AMessage: POPStackMessage; Success: Boolean); procedure AppCallBack_ConfigMemWriteReply(Node: PNMRAnetNode; AMessage: POPStackMessage; Success: Boolean); procedure AppCallBack_ConfigMemStreamWriteReply(Node: PNMRAnetNode; AMessage: POPStackMessage; Success: Boolean); {$IFNDEF FPC} function OPStackNode_Allocate: PNMRAnetNode; external; procedure OPStackNode_MarkForRelease(Node: PNMRAnetNode); external; function OPStackNode_Find(AMessage: POPStackMessage; FindBy: Byte): PNMRAnetNode; external; // See FIND_BY_xxxx constants function OPStackNode_FindByTrainID(TrainID: Word): PNMRANetNode; external; function OPStackNode_FindByAlias(AliasID: Word): PNMRAnetNode; external; function OPStackNode_FindByID(var ID: TNodeID): PNMRAnetNode; external; procedure TractionProxyProtocolReply(Node: PNMRAnetNode; var MessageToSend, NextMessage: POPStackMessage); external; procedure TractionProtocolReply(Node: PNMRAnetNode; var MessageToSend, NextMessage: POPStackMessage); external; {$ENDIF} {$IFDEF FPC} const SYNC_NONE = $0000; SYNC_STATE_SPEED_DIR = $0100; SYNC_STATE_FUNCTIONS = $0200; SYNC_STATE_ADDRESS = $0400; SYNC_CONTROLLER = $0020; SYNC_REPLY_ALLOC_NODE = $1000; SYNC_REPLY_DEALLOC_NODE = $2000; type TSyncRec = record Node: PNMRAnetNode; State: Word; // SYNC_xxxxx constant ObjPtr: TObject; // Points to an object to link GUI items with the node end; PSyncRec = ^TSyncRec; function TrainItem(iIndex: Integer): PSyncRec; var OPStackCriticalSection: TRTLCriticalSection; Trains: TList; {$ENDIF} implementation {$IFDEF FPC} {$IFDEF SUPPORT_TRACTION and SUPPORT_TRACTION_PROXY} uses opstacknode, opstackcore_traction, opstackcore_traction_proxy; {$ELSE} {$IFDEF SUPPORT_TRACTION} uses opstackcore_traction; {$ELSE} uses opstackcore_traction_proxy; {$ENDIF} {$ENDIF} {$ENDIF} const // Physical Proxy Node User StateMachine STATE_PROXY_USER_START = 0; STATE_PROXY_IDLE = 1; // Virtual Train Node User StateMachine STATE_TRAIN_USER_START = 0; STATE_TRAIN_ALLOCATE_PROXY_REPLY = 1; STATE_TRAIN_IDLE = 2; var GlobalTimer: Word; {$IFDEF FPC} procedure ZeroSyncRec(SyncRec: PSyncRec); begin SyncRec^.Node := nil; SyncRec^.State := 0; SyncRec^.ObjPtr := nil; end; function TrainItem(iIndex: Integer): PSyncRec; begin Result := nil; if iIndex < Trains.Count then Result := PSyncRec( Trains.Items[iIndex]) end; function FindSync(Node: PNMRAnetNode): PSyncRec; var i: Integer; begin Result := nil; for i := 0 to Trains.Count - 1 do begin if PSyncRec( Trains.Items[i])^.Node = Node then begin Result := PSyncRec( Trains.Items[i]); Break end; end; end; procedure SetSync(Node: PNMRAnetNode; SyncCode: Word); var Train: PSyncRec; begin Train := FindSync(Node); if Assigned(Train) then Train^.State := Train^.State or SyncCode; end; {$ENDIF} // ***************************************************************************** // procedure UserStateMachine_Initialize // Parameters: : None // Returns : None // Description : Called once when the library is starting. Use to initalize // variables, etc // ***************************************************************************** procedure UserStateMachine_Initialize; begin end; // ***************************************************************************** // procedure AppCallback_UserStateMachine_Process // Parameters: : None // Returns : None // Description : Called as often as possible to run the user statemachine // ***************************************************************************** procedure AppCallback_UserStateMachine_Process(Node: PNMRAnetNode); {$IFDEF FPC} var Sync: PSyncRec; {$ENDIF} begin {$IFDEF FPC}EnterCriticalsection(OPStackCriticalSection);{$ENDIF} if Node = GetPhysicalNode then begin // Proxy Node User StateMachine case Node^.iUserStateMachine of STATE_PROXY_USER_START : begin if Node^.State and NS_PERMITTED <> 0 then begin {$IFDEF DEBUG_TRAINSERVER_STATEMACHINE} UART1_Write_Text('STATE_PROXY_USER_START'+LF); {$ENDIF} Node^.iUserStateMachine := STATE_PROXY_IDLE end; end; STATE_PROXY_IDLE : begin // Waiting for something to do end; end; end else begin // Train Node User StateMachine case Node^.iUserStateMachine of STATE_TRAIN_USER_START : begin if Node^.State and NS_PERMITTED <> 0 then begin {$IFDEF DEBUG_TRAINOBJECT_STATEMACHINE} UART1_Write_Text('STATE_TRAIN_USER_START'+LF); {$ENDIF} {$IFDEF FPC} New(Sync); // The Alias is valid by here so add it ZeroSyncRec(Sync); Sync^.Node := Node; Sync^.State := SYNC_REPLY_ALLOC_NODE; Trains.Add(Sync); {$ENDIF} Node^.iUserStateMachine := STATE_TRAIN_ALLOCATE_PROXY_REPLY end end; STATE_TRAIN_ALLOCATE_PROXY_REPLY : begin {$IFDEF DEBUG_TRAINOBJECT_STATEMACHINE} UART1_Write_Text('STATE_TRAIN_ALLOCATE_PROXY_REPLY'+LF); {$ENDIF} if TrySendTractionProxyAllocateReply(GetPhysicalNode^.Info, Node^.TrainData.LinkedNode, TRACTION_PROXY_TECH_ID_DCC, Node^.Info, Node^.TrainData.Address) then Node^.iUserStateMachine := STATE_TRAIN_IDLE; end; STATE_TRAIN_IDLE : begin // Waiting for something to do end; end end; {$IFDEF FPC}LeaveCriticalsection(OPStackCriticalSection);{$ENDIF} end; // ***************************************************************************** // procedure AppCallback_NodeInitialize // Parameters: : Node : Pointer to the node that needs to be initilized to its intial value // Returns : None // Description : Typically called when a node is being intialized to be // logged into the network. It is possible the node can be // discarded then reused so it may be called more than once for // virtual nodes // ***************************************************************************** procedure AppCallback_NodeInitialize(Node: PNMRAnetNode); begin Node^.iUserStateMachine := STATE_PROXY_USER_START; end; {$IFDEF SUPPORT_TRACTION} // ***************************************************************************** // procedure AppCallback_SimpleTrainNodeInfoReply // Parameters: : Source : Full Node ID (and Alias if on CAN) of the source node for the message // Dest : Full Node ID (and Alias if on CAN) of the dest node for the message // TrainNodeInfo: pointer to the null terminated strings // Returns : None // Description : This is called directly from the Hardware receive buffer. Do // not do anything here that stalls the call. This is called // Asyncronously from the Statemachine loop and the Statemachine loop // is stalled until this returns. Set a flag and move on is the // best stratagy or store info in a buffer and process in the // main statemachine. // ***************************************************************************** procedure AppCallback_SimpleTrainNodeInfoReply(Node: PNMRAnetNode; AMessage: POPStackMessage); begin end; // ***************************************************************************** // procedure AppCallback_TractionProtocol // Parameters: : Node : Pointer to the node that the traction protocol has been called on // ReplyMessage : The Reply Message that needs to be allocated, populated and returned so it can be sent // RequestingMessage : Message that was sent to the node containing the requested information // Returns : None // the internal buffer queue. It is recommended that the message get handled quickly and released. // The internal system can not process other incoming messages that require a reply until this message // is cleared. This means that if a reply can not be sent until another message is sent/received this // will block that second message. If that is required then return True with ReplyMessage = nil to // release the Requesting message then send the reply to this message at a later time // ***************************************************************************** procedure AppCallback_TractionProtocol(Node: PNMRAnetNode; AMessage: POPStackMessage); begin {$IFDEF FPC}EnterCriticalsection(OPStackCriticalSection);{$ENDIF} {$IFDEF DEBUG_TRACTION_PROTOCOL} UART1_Write_Text('AppCallback_TractionProtocol'+LF); {$ENDIF} case AMessage^.Buffer^.DataArray[0] of TRACTION_CONTROLLER_CONFIG : begin case AMessage^.Buffer^.DataArray[1] of TRACTION_CONTROLLER_CONFIG_ASSIGN : begin {$IFDEF FPC}SetSync(Node, SYNC_CONTROLLER);{$ENDIF} {$IFDEF DEBUG_TRACTION_PROTOCOL} UART1_Write_Text('TRACTION_CONTROLLER_CONFIG_ASSIGN'+LF); {$ENDIF} Exit; end; TRACTION_CONSIST_DETACH : begin {$IFDEF FPC}SetSync(Node, SYNC_CONTROLLER);{$ENDIF} {$IFDEF DEBUG_TRACTION_PROTOCOL} UART1_Write_Text('TRACTION_CONSIST_DETACH'+LF); {$ENDIF} Exit; end; end end; TRACTION_SPEED_DIR : begin {$IFDEF FPC}SetSync(Node, SYNC_STATE_SPEED_DIR);{$ENDIF} {$IFDEF DEBUG_TRACTION_PROTOCOL} UART1_Write_Text('TRACTION_SPEED_DIR'+LF); {$ENDIF} Exit; end; TRACTION_FUNCTION : begin {$IFDEF FPC}SetSync(Node, SYNC_STATE_FUNCTIONS);{$ENDIF} {$IFDEF DEBUG_TRACTION_PROTOCOL} UART1_Write_Text('TRACTION_FUNCTION'+LF); {$ENDIF} Exit; end; TRACTION_E_STOP : begin {$IFDEF FPC}SetSync(Node, SYNC_STATE_SPEED_DIR);{$ENDIF} {$IFDEF DEBUG_TRACTION_PROTOCOL} UART1_Write_Text('TRACTION_E_STOP'+LF); {$ENDIF} Exit; end else begin end; end; {$IFDEF FPC}LeaveCriticalsection(OPStackCriticalSection);{$ENDIF} end; // ***************************************************************************** // procedure AppCallback_TractionProtocolReply // Parameters: : Node : Pointer to the node that the traction protocol has been called on // ReplyMessage : The Reply Message that needs to be allocated, populated and returned so it can be sent // RequestingMessage : Message that was sent to the node containing the requested information // Returns : True if the RequestingMessage is handled and the ReplyMessage is ready to send // False if the request has not been completed due to no available buffers or waiting on other information // Description : Called in response to a Traction Protcool request // ***************************************************************************** procedure AppCallback_TractionProtocolReply(Node: PNMRAnetNode; AMessage: POPStackMessage); begin end; {$ENDIF} {$IFDEF SUPPORT_TRACTION_PROXY} // ***************************************************************************** // procedure AppCallback_TractionProxyProtocol // Parameters: : Node : Pointer to the node that the traction protocol has been called on // ReplyMessage : The Reply Message that needs to be allocated, populated and returned so it can be sent // RequestingMessage : Message that was sent to the node containing the requested information // Returns : True if the RequestingMessage is handled and the ReplyMessage is ready to send // False if the request has not been completed due to no available buffers or waiting on other information // Description : // ***************************************************************************** procedure AppCallback_TractionProxyProtocol(Node: PNMRAnetNode; AMessage: POPStackMessage; SourceHasLock: Boolean); var i: Integer; TrainID: Word; TrainNode: PNMRAnetNode; begin {$IFDEF FPC}EnterCriticalsection(OPStackCriticalSection);{$ENDIF} {$IFDEF DEBUG_TRACTION_PROXY_PROTOCOL} UART1_Write_Text('AppCallback_TractionProxyProtocol'+LF); {$ENDIF} // Only the main physical node is the proxy and can reply to these messages if Node = GetPhysicalNode then begin case AMessage^.Buffer^.DataArray[0] of TRACTION_PROXY_ALLOCATE : begin {$IFDEF DEBUG_TRACTION_PROXY_PROTOCOL} UART1_Write_Text('TRACTION_PROXY_ALLOCATE'+LF); {$ENDIF} TrainID := (AMessage^.Buffer^.DataArray[2] shl 8) or AMessage^.Buffer^.DataArray[3]; TrainNode := OPStackNode_FindByTrainID(TrainID); if TrainNode <> nil then TrainNode^.iUserStateMachine := STATE_TRAIN_ALLOCATE_PROXY_REPLY // Node with this address exists, use it and set the state to send a Allocate Reply else TrainNode := OPStackNode_Allocate; if TrainNode <> nil then begin // This node will now start and log in, then it will reply to the Allocate Message TrainNode^.TrainData.Address := TrainID; if AMessage^.Buffer^.DataArray[1] = TRACTION_PROXY_TECH_ID_DCC then TrainNode^.TrainData.SpeedSteps := AMessage^.Buffer^.DataArray[4]; TrainNode^.TrainData.LinkedNode.ID[0] := AMessage^.Source.ID[0]; // Store the node that sent the Allocate message TrainNode^.TrainData.LinkedNode.ID[1] := AMessage^.Source.ID[1]; TrainNode^.TrainData.LinkedNode.AliasID := AMessage^.Source.AliasID; end; end; end end; {$IFDEF FPC}LeaveCriticalSection(OPStackCriticalSection);{$ENDIF} end; // ***************************************************************************** // procedure AppCallback_TractionProxyProtocolReply // Parameters: : Source : Full Node ID (and Alias if on CAN) of the source node for the message // Dest : Full Node ID (and Alias if on CAN) of the dest node for the message // DataBytes: pointer Raw data bytes, Byte 0 and 1 are the Alias // Returns : None // Description : // ***************************************************************************** procedure AppCallback_TractionProxyProtocolReply(Node: PNMRAnetNode; AMessage: POPStackMessage); begin end; {$ENDIF} // ***************************************************************************** // procedure AppCallBack_ProtocolSupportReply // Parameters: : Source : Full Node ID (and Alias if on CAN) of the source node for the message // Dest : Full Node ID (and Alias if on CAN) of the dest node for the message // DataBytes: pointer Raw data bytes, Byte 0 and 1 are the Alias // Returns : None // Description : // ***************************************************************************** procedure AppCallBack_ProtocolSupportReply(Node: PNMRAnetNode; AMessage: POPStackMessage); begin end; // ***************************************************************************** // procedure AppCallback_ConsumerIdentified // Parameters: : Source : Full Node ID (and Alias if on CAN) of the source node for the message // Dest : Full Node ID (and Alias if on CAN) of the dest node for the message // MTI : MTI of the message // EventID: pointer to the Event ID for the message // Returns : None // Description : This is called directly from the Hardware receive buffer. Do // not do anything here that stalls the call. This is called // Asyncronously from the Statemachine loop and the Statemachine loop // is stalled until this returns. Set a flag and move on is the // best stratagy or store info in a buffer and process in the // main statemachine. // ***************************************************************************** procedure AppCallback_ConsumerIdentified(var Source: TNodeInfo; MTI: Word; EventID: PEventID); begin end; // ***************************************************************************** // procedure AppCallback_ProducerIdentified // Parameters: : Source : Full Node ID (and Alias if on CAN) of the source node for the message // Dest : Full Node ID (and Alias if on CAN) of the dest node for the message // MTI : MTI of the message // EventID: pointer to the Event ID for the message // Returns : None // Description : This is called directly from the Hardware receive buffer. Do // not do anything here that stalls the call. This is called // Asyncronously from the Statemachine loop and the Statemachine loop // is stalled until this returns. Set a flag and move on is the // best stratagy or store info in a buffer and process in the // main statemachine. // ***************************************************************************** procedure AppCallback_ProducerIdentified(var Source: TNodeInfo; MTI: Word; EventID: PEventID); begin end; // ***************************************************************************** // procedure AppCallback_LearnEvent // Parameters: : Source : Full Node ID (and Alias if on CAN) of the source node for the message // EventID: pointer to the Event ID for the message // Returns : None // Description : This is called directly from the Hardware receive buffer. Do // not do anything here that stalls the call. This is called // Asyncronously from the Statemachine loop and the Statemachine loop // is stalled until this returns. Set a flag and move on is the // best stratagy or store info in a buffer and process in the // main statemachine. // ***************************************************************************** procedure AppCallback_LearnEvent(var Source: TNodeInfo; EventID: PEventID); begin end; // ***************************************************************************** // procedure AppCallBack_PCEventReport // Parameters: : Source : Full Node ID (and Alias if on CAN) of the source node for the message // EventID: pointer to the Event ID for the message // Returns : None // Description : This is called directly from the Hardware receive buffer. Do // not do anything here that stalls the call. This is called // Asyncronously from the Statemachine loop and the Statemachine loop // is stalled until this returns. Set a flag and move on is the // best stratagy or store info in a buffer and process in the // main statemachine. // ***************************************************************************** procedure AppCallBack_PCEventReport(var Source: TNodeInfo; EventID: PEventID); begin end; // ***************************************************************************** // procedure AppCallBack_ConfigMemReadReply // Parameters: : Node : Pointer to the node that needs to be initilized to its intial value // Returns : None // Description : Typically called when a node is being intialized to be // logged into the network. It is possible the node can be // discarded then reused so it may be called more than once for // virtual nodes // ***************************************************************************** procedure AppCallBack_ConfigMemReadReply(Node: PNMRAnetNode; AMessage: POPStackMessage; Success: Boolean); begin end; // ***************************************************************************** // procedure AppCallBack_ConfigMemStreamReadReply // Parameters: : Node : Pointer to the node that needs to be initilized to its intial value // Returns : None // Description : Typically called when a node is being intialized to be // logged into the network. It is possible the node can be // discarded then reused so it may be called more than once for // virtual nodes // ***************************************************************************** procedure AppCallBack_ConfigMemStreamReadReply(Node: PNMRAnetNode; AMessage: POPStackMessage; Success: Boolean); begin end; // ***************************************************************************** // procedure AppCallBack_ConfigMemWriteReply // Parameters: : Node : Pointer to the node that needs to be initilized to its intial value // Returns : None // Description : Typically called when a node is being intialized to be // logged into the network. It is possible the node can be // discarded then reused so it may be called more than once for // virtual nodes // ***************************************************************************** procedure AppCallBack_ConfigMemWriteReply(Node: PNMRAnetNode; AMessage: POPStackMessage; Success: Boolean); begin end; // ***************************************************************************** // procedure AppCallBack_ConfigMemStreamWriteReply // Parameters: : Node : Pointer to the node that needs to be initilized to its intial value // Returns : None // Description : Typically called when a node is being intialized to be // logged into the network. It is possible the node can be // discarded then reused so it may be called more than once for // virtual nodes // ***************************************************************************** procedure AppCallBack_ConfigMemStreamWriteReply(Node: PNMRAnetNode; AMessage: POPStackMessage; Success: Boolean); begin end; // ***************************************************************************** // procedure AppCallback_RemoteButtonReply // Parameters: : Source : Full Node ID (and Alias if on CAN) of the source node for the message // Dest : Full Node ID (and Alias if on CAN) of the dest node for the message // DataBytes: pointer to the raw data bytes // Returns : None // Description : This is called directly from the Hardware receive buffer. Do // not do anything here that stalls the call. This is called // Asyncronously from the Statemachine loop and the Statemachine loop // is stalled until this returns. Set a flag and move on is the // best stratagy or store info in a buffer and process in the // main statemachine. // ***************************************************************************** procedure AppCallback_RemoteButtonReply(Node: PNMRAnetNode; AMessage: POPStackMessage); begin end; // ***************************************************************************** // procedure AppCallback_Timer_1s // Parameters: : None // Returns : None // Description : Typcally called from another thread or interrupt, only use // to update asyncronous flags // ***************************************************************************** procedure AppCallback_Timer_1s; begin Inc(GlobalTimer); end; // ***************************************************************************** // procedure AppCallback_SimpleNodeInfoReply // Parameters: : Source : Full Node ID (and Alias if on CAN) of the source node for the message // Dest : Full Node ID (and Alias if on CAN) of the dest node for the message // NodeInfo : pointer to the null terminated strings // Returns : None // Description : This is called directly from the Hardware receive buffer. Do // not do anything here that stalls the call. This is called // Asyncronously from the Statemachine loop and the Statemachine loop // is stalled until this returns. Set a flag and move on is the // best stratagy or store info in a buffer and process in the // main statemachine. // ***************************************************************************** procedure AppCallback_SimpleNodeInfoReply(Node: PNMRAnetNode; AMessage: POPStackMessage); begin end; // ***************************************************************************** // procedure AppCallback_VerifiedNodeID // Parameters: : Source : Full Node ID (and Alias if on CAN) of the source node for the message // EventID: pointer to the Event ID for the message // Returns : None // Description : This is called directly from the Hardware receive buffer. Do // not do anything here that stalls the call. This is called // Asyncronously from the Statemachine loop and the Statemachine loop // is stalled until this returns. Set a flag and move on is the // best stratagy or store info in a buffer and process in the // main statemachine. // ***************************************************************************** procedure AppCallback_VerifiedNodeID(var Source: TNodeInfo; NodeID: PNodeID); begin end; // ***************************************************************************** // procedure AppCallback_InitializationComplete // Parameters: : Source : Full Node ID (and Alias if on CAN) of the source node for the message // EventID: pointer to the Event ID for the message // Returns : None // Description : This is called directly from the Hardware receive buffer. Do // not do anything here that stalls the call. This is called // Asyncronously from the Statemachine loop and the Statemachine loop // is stalled until this returns. Set a flag and move on is the // best stratagy or store info in a buffer and process in the // main statemachine. // ***************************************************************************** procedure AppCallback_InitializationComplete(var Source: TNodeInfo; NodeID: PNodeID); begin end; {$IFDEF FPC} var i: Integer; initialization System.InitCriticalSection(OPStackCriticalSection); Trains := TList.Create; Finalization DoneCriticalsection( OPStackCriticalSection); for i := 0 to Trains.Count - 1 do begin Dispose( TrainItem(i)); end; {$ENDIF} end.