unit Myc.Data.Stream; interface uses System.SysUtils, System.Classes, System.Generics.Collections, System.SyncObjs, Myc.Data.Scalar, Myc.Data.Keyword, Myc.Data.Value, Myc.Core.Notifier; type TSignalKind = (skData, skHeartbeat); TStreamSignal = record private FCycleID: Int64; FKind: TSignalKind; public constructor Create(AKind: TSignalKind; ACycleID: Int64); function ToString: string; property CycleID: Int64 read FCycleID; property Kind: TSignalKind read FKind; end; IStreamObserver = interface procedure OnSignal(const Signal: TStreamSignal); end; TSubscriptionTag = Pointer; IStream = interface {$region 'private'} function GetSeries: IScalarRecordSeries; {$endregion} function Subscribe(const Observer: IStreamObserver): TSubscriptionTag; procedure Unsubscribe(Tag: TSubscriptionTag); // Returns the Series of the records this stream produces. Streams are scalar record series. Always! property Series: IScalarRecordSeries read GetSeries; end; IInputChannel = interface {$region 'private'} function GetStream: IStream; {$endregion} procedure Push(const Value: IKeywordMapping); property Stream: IStream read GetStream; end; IGraphExecutor = interface {$region 'private'} function GetCycleID: Int64; {$endregion} // Now requires a Definition when creating a channel function GetInputChannel(const Name: string; const Def: IScalarRecordDefinition): IInputChannel; procedure Step; property CycleID: Int64 read GetCycleID; end; TGraphExecutor = class(TInterfacedObject, IGraphExecutor) private type TChannel = class(TInterfacedObject, IInputChannel, IStream) private type TStreamNotifier = TMycNotifyList; private FName: string; FSeries: IWriteableScalarRecordSeries; FQueue: TQueue>; FQueueLock: TSpinLock; FObservers: TStreamNotifier; procedure Push(const Value: IKeywordMapping); function GetStream: IStream; function Subscribe(const Observer: IStreamObserver): TSubscriptionTag; procedure Unsubscribe(Tag: TSubscriptionTag); procedure Emit(ACycle: Int64); function GetSeries: IScalarRecordSeries; public constructor Create(const AName: string; const ASeries: IWriteableScalarRecordSeries); destructor Destroy; override; end; private FCycleID: Int64; FChannels: TDictionary; FLock: TSpinLock; function GetInputChannel(const Name: string; const Def: IScalarRecordDefinition): IInputChannel; function GetCycleID: Int64; public constructor Create; destructor Destroy; override; procedure Step; end; implementation constructor TStreamSignal.Create(AKind: TSignalKind; ACycleID: Int64); begin FKind := AKind; FCycleID := ACycleID; end; function TStreamSignal.ToString: string; begin if Kind = skData then Result := Format('Signal(Data, #%d)', [CycleID]) else Result := Format('Signal(Heartbeat, #%d)', [CycleID]); end; { TGraphExecutor.TChannel } constructor TGraphExecutor.TChannel.Create(const AName: string; const ASeries: IWriteableScalarRecordSeries); begin inherited Create; FName := AName; FSeries := ASeries; FQueue := TQueue>.Create; end; destructor TGraphExecutor.TChannel.Destroy; begin FObservers.Finalize; FQueue.Free; inherited; end; procedure TGraphExecutor.TChannel.Push(const Value: IKeywordMapping); begin FQueueLock.Enter; try FQueue.Enqueue(Value); finally FQueueLock.Exit; end; end; function TGraphExecutor.TChannel.GetStream: IStream; begin Result := Self; end; function TGraphExecutor.TChannel.Subscribe(const Observer: IStreamObserver): TSubscriptionTag; begin FObservers.Lock; try Result := FObservers.Advise(Observer); finally FObservers.Release; end; end; procedure TGraphExecutor.TChannel.Unsubscribe(Tag: TSubscriptionTag); begin FObservers.Lock; try FObservers.Unadvise(Tag); finally FObservers.Release; end; end; procedure TGraphExecutor.TChannel.Emit(ACycle: Int64); var signal: TStreamSignal; begin FQueueLock.Enter; try FSeries.Add(FQueue.Dequeue); signal := TStreamSignal.Create( if FQueue.Count > 0 then skData else skHeartbeat, ACycle ) finally FQueueLock.Exit; end; FObservers.Lock; try FObservers.Notify( function(const Obs: IStreamObserver): Boolean begin Obs.OnSignal(signal); Result := True; end ); finally FObservers.Release; end; end; function TGraphExecutor.TChannel.GetSeries: IScalarRecordSeries; begin Result := FSeries; end; { TGraphExecutor } constructor TGraphExecutor.Create; begin inherited Create; FChannels := TDictionary.Create; FCycleID := 0; end; destructor TGraphExecutor.Destroy; begin FChannels.Free; inherited; end; function TGraphExecutor.GetInputChannel(const Name: string; const Def: IScalarRecordDefinition): IInputChannel; var impl: TChannel; begin FLock.Enter; try if not FChannels.TryGetValue(Name, Result) then begin impl := TChannel.Create(Name, TScalarRecordSeries.Create(Def)); Result := impl; FChannels.Add(Name, Result); end; finally FLock.Exit; end; end; function TGraphExecutor.GetCycleID: Int64; begin Result := FCycleID; end; procedure TGraphExecutor.Step; var channelsArr: TArray; channel: IInputChannel; begin FLock.Enter; try Inc(FCycleID); channelsArr := FChannels.Values.ToArray; finally FLock.Exit; end; for channel in channelsArr do (channel as TChannel).Emit(FCycleID); end; initialization TMycNotifyList.ReverseOnNotify := False; end.