269 lines
6.7 KiB
ObjectPascal
269 lines
6.7 KiB
ObjectPascal
unit Myc.Data.Stream;
|
|
|
|
interface
|
|
|
|
uses
|
|
System.SysUtils,
|
|
System.Classes,
|
|
System.Generics.Collections,
|
|
System.SyncObjs,
|
|
Myc.Data.Scalar,
|
|
Myc.Data.Keyword,
|
|
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<TScalar>);
|
|
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<IStreamObserver>;
|
|
private
|
|
FName: string;
|
|
FSeries: IWriteableScalarRecordSeries;
|
|
FQueue: TQueue<IKeywordMapping<TScalar>>;
|
|
FQueueLock: TSpinLock;
|
|
FObservers: TStreamNotifier;
|
|
|
|
procedure Push(const Value: IKeywordMapping<TScalar>);
|
|
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<string, IInputChannel>;
|
|
FLock: TSpinLock;
|
|
function GetInputChannel(const Name: string; const Def: IScalarRecordDefinition): IInputChannel;
|
|
function GetCycleID: Int64;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure Step;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
Winapi.Windows;
|
|
|
|
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<IKeywordMapping<TScalar>>.Create;
|
|
end;
|
|
|
|
destructor TGraphExecutor.TChannel.Destroy;
|
|
begin
|
|
FObservers.Finalize;
|
|
FQueue.Free;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TGraphExecutor.TChannel.Push(const Value: IKeywordMapping<TScalar>);
|
|
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<string, IInputChannel>.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<IInputChannel>;
|
|
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<IStreamObserver>.ReverseOnNotify := False;
|
|
|
|
end.
|