unit Myc.DataFlow.Impl; interface uses System.SysUtils, System.SyncObjs, System.Generics.Collections, Myc.Signals, Myc.Mutable, Myc.Core.Notifier, Myc.DataFlow, Myc.Trade.Types, Myc.Trade.DataArray; type // Abstract base class for data consumers. TMycConsumer = class abstract(TContainedObject, IConsumer) strict private type TNull = class(TInterfacedObject, IConsumer) protected function Consume(const Value: T): TState; end; class var FNull: IConsumer; class constructor CreateClass; protected function Consume(const Value: T): TState; virtual; abstract; public class property Null: IConsumer read FNull; end; // A consumer implementation that is owned by a controller. TMycGenericConsumer = class(TMycConsumer) private FProc: TConstFunc; protected function Consume(const Value: T): TState; override; final; public constructor Create(const Controller: IInterface; const AProc: TConstFunc); end; TMycProducer = class(TInterfacedObject, IProducer) strict private type TNull = class(TInterfacedObject, IProducer) public function Link(const Consumer: IConsumer): TTag; procedure Unlink(Tag: TTag); end; class var FNull: IProducer; class constructor CreateClass; private FListeners: TMycNotifyList>; public constructor Create; destructor Destroy; override; function Broadcast(const Value: T): TState; function Link(const Consumer: IConsumer): TTag; procedure Unlink(Tag: TTag); class property Null: IProducer read FNull; end; // Concrete producer that manages a list of consumers (listeners). TMycContainedProducer = class(TContainedObject, IProducer) private FListeners: TMycNotifyList>; public constructor Create(const Controller: IInterface); destructor Destroy; override; function Broadcast(const Value: T): TState; function Link(const Consumer: IConsumer): TTag; procedure Unlink(Tag: TTag); end; // Abstract base class for components that now act as a producer and contain a consumer. TMycConverter = class abstract(TMycProducer, IConverter) strict private type TNull = class(TInterfacedObject, IConverter) private function GetConsumer: IConsumer; function Link(const Consumer: IConsumer): TTag; procedure Unlink(Tag: TTag); end; class var FNull: IConverter; class constructor CreateClass; private FConsumer: TMycGenericConsumer; function GetConsumer: IConsumer; protected // To be implemented by descendants to perform the actual conversion. function Consume(const Value: S): TState; virtual; abstract; public constructor Create; destructor Destroy; override; class property Null: IConverter read FNull; end; // A generic converter that uses a function reference for the conversion logic. TMycGenericConverter = class(TMycConverter) private FFunc: TConstFunc; protected function Consume(const Value: S): TState; override; public constructor Create(const AFunc: TConstFunc); end; TMycGenericAggregator = class(TMycConverter) private FFunc: TConstFunc.TBroadcastProc, TState>; protected function Consume(const Value: S): TState; override; public constructor Create(const AFunc: TConstFunc.TBroadcastProc, TState>); end; TMycGenericParallelConverter = class(TMycConverter) private FFunc: TConstFunc; FQueue: TState; protected function Consume(const Value: S): TState; override; public constructor Create(const AFunc: TConstFunc); end; TMycIdentityConverter = class(TMycConverter) protected function Consume(const Value: T): TState; override; final; end; // A converter that counts incoming data points and outputs the current count. TMycDataCounter = class(TMycConverter) private FCount: Int64; protected function Consume(const Value: T): TState; override; public constructor Create; end; // A converter that takes an array and broadcasts each element individually. TMycTicker = class(TMycConverter, T>) protected function Consume(const Values: TArray): TState; override; end; // A converter that reads a specific field from a record using RTTI. TMycRecordFieldReader = class(TMycConverter) private FOffset: Integer; protected function Consume(const Values: S): TState; override; public constructor Create(const AFieldName: String); end; // Endpoint that collects data into a series. TMycDataEndpoint = class(TInterfacedObject, TLazy>.ILazy) private type PItem = ^TItem; TItem = record Next: PItem; Value: T; end; private FConsumer: TMycGenericConsumer; FLookback: Integer; FChanged: TFlag; FLock: TLightweightMREW; FFirst: PItem; FCount: Integer; function GetChanged: TState; function Consume(const Value: T): TState; public constructor Create(ALookback: Integer); destructor Destroy; override; function Update(var Value: TSeries): Boolean; class function CreateDataEndpoint(Lookback: Integer; out Series: TLazy>): IConsumer; end; TTickAggregation = class(TMycConverter, TDataPoint>) private FTimeframe: TTimeframe; FCurrentBar: TDataPoint; function GetBarStartTime(const TimeStamp: TDateTime; const Timeframe: TTimeframe): TDateTime; function GetCurrentBar: TDataPoint; function GetTimeframe: TTimeframe; protected function Consume(const Value: TDataPoint): TState; override; public constructor Create(const ATimeframe: TTimeframe); property CurrentBar: TDataPoint read GetCurrentBar; property Timeframe: TTimeframe read GetTimeframe; end; TMycParallelConverter = class(TMycConverter) private FQueue: TState; protected function Consume(const Value: T): TState; override; final; end; TOhlcAggregation = class(TMycConverter, TDataPoint>) private FTimeframe: TTimeframe; FCurrentBar: TDataPoint; function GetBarStartTime(const TimeStamp: TDateTime; const Timeframe: TTimeframe): TDateTime; function GetCurrentBar: TDataPoint; function GetTimeframe: TTimeframe; protected function Consume(const Value: TDataPoint): TState; override; public constructor Create(const ATimeframe: TTimeframe); property CurrentBar: TDataPoint read GetCurrentBar; property Timeframe: TTimeframe read GetTimeframe; end; // Endpoint that collects data into a series. TMycDataJoin = class(TInterfacedObject, IProducer>) private FConsumers: array of record Consumer: TMycGenericConsumer; Queue: TQueue; end; FContainedProvider: TMycContainedProducer>; FLock: TSpinLock; FCount: Integer; function Consume(Idx: Integer; const Value: T): TState; function GetConsumers(Idx: Integer): IConsumer; function Link(const Consumer: IConsumer>): TTag; procedure Unlink(Tag: TTag); public constructor Create(ACount: Integer); destructor Destroy; override; property Consumers[Idx: Integer]: IConsumer read GetConsumers; end; TMycComposedConverter = class(TInterfacedObject, IConverter) private FConsumer: IConsumer; FProducer: TProducer; protected function GetConsumer: IConsumer; function Link(const Consumer: IConsumer): TTag; procedure Unlink(Tag: TTag); public constructor Create(const AConsumer: IConsumer; const AProducer: TProducer); end; TMycSequence = class(TInterfacedObject, IConsumer) private FProducers: TArray>; protected function Consume(const Value: T): TState; function ProcessProducer(Idx: Integer; const Value: T): TState; public constructor Create(ACount: Integer); destructor Destroy; override; class function CreateSequence(Count: Integer; out Producers: TArray>): IConsumer; static; end; implementation uses System.TypInfo, System.RTTI, System.DateUtils, System.Math, Winapi.Windows, Myc.TaskManager; class constructor TMycConsumer.CreateClass; begin FNull := TNull.Create; end; { TMycContainedProducer } constructor TMycContainedProducer.Create(const Controller: IInterface); begin inherited Create(Controller); end; destructor TMycContainedProducer.Destroy; begin FListeners.Finalize; inherited Destroy; end; function TMycContainedProducer.Broadcast(const Value: T): TState; begin FListeners.Lock; try var item := FListeners.First; if item = nil then exit(TState.Null); var i := 1; while item.Next <> nil do begin inc(i); item := item.Next; end; var done := TLatch.CreateLatch(i); while item <> nil do begin item.Receiver.Consume(Value).Signal.Subscribe(done); item := item.Prev; end; Result := done.State; finally FListeners.Release; end; end; function TMycContainedProducer.Link(const Consumer: IConsumer): TTag; begin // Add the Consumer to the notification list FListeners.Lock; try Result := FListeners.Advise(Consumer); finally FListeners.Release; end; end; procedure TMycContainedProducer.Unlink(Tag: TTag); begin FListeners.Lock; try FListeners.Unadvise(Tag); finally FListeners.Release; end; end; { TMycConverter } constructor TMycConverter.Create; begin inherited Create; FConsumer := TMycGenericConsumer.Create(Self, Consume); end; { TMycConverter } class constructor TMycConverter.CreateClass; begin FNull := TNull.Create; end; destructor TMycConverter.Destroy; begin FConsumer.Free; inherited Destroy; end; function TMycConverter.GetConsumer: IConsumer; begin Result := FConsumer; end; { TMycConverter.TNull } function TMycConverter.TNull.GetConsumer: IConsumer; begin Result := TMycConsumer.Null; end; function TMycConverter.TNull.Link(const Consumer: IConsumer): TTag; begin Result := nil; end; procedure TMycConverter.TNull.Unlink(Tag: TTag); begin // NOP end; { TMycGenericConverter } constructor TMycGenericConverter.Create(const AFunc: TConstFunc); begin inherited Create; FFunc := AFunc; end; function TMycGenericConverter.Consume(const Value: S): TState; begin Result := Broadcast(FFunc(Value)); end; { TMycDataCounter } constructor TMycDataCounter.Create; begin inherited Create; FCount := 0; end; function TMycDataCounter.Consume(const Value: T): TState; begin Result := Broadcast(FCount); inc(FCount); end; { TMycTicker } function TMycTicker.Consume(const Values: TArray): TState; begin var done := TLatch.CreateLatch(Length(Values)); for var i := 0 to High(Values) do Broadcast(Values[i]).Signal.Subscribe(done); Result := done.State; end; { TMycRecordFieldReader } constructor TMycRecordFieldReader.Create(const AFieldName: String); begin inherited Create; var Context := TRttiContext.Create; var Field := Context.GetType(TypeInfo(S)).GetField(AFieldName); var TypeT := Context.GetType(TypeInfo(T)); Assert(Assigned(Field), 'Field ' + AFieldName + ' not found'); Assert(Field.FieldType.TypeKind = TypeT.TypeKind, 'Incorrect type'); if Assigned(Field) and (Field.FieldType.TypeKind = TypeT.TypeKind) then FOffset := Field.Offset else FOffset := -1; end; function TMycRecordFieldReader.Consume(const Values: S): TState; type PT = ^T; begin if FOffset < 0 then exit(TState.Null); var fieldPtr := PByte(@Values); inc(fieldPtr, FOffset); Result := Broadcast(PT(fieldPtr)^); end; { TMycGenericConsumer } constructor TMycGenericConsumer.Create(const Controller: IInterface; const AProc: TConstFunc); begin inherited Create(Controller); FProc := AProc; end; function TMycGenericConsumer.Consume(const Value: T): TState; begin Result := FProc(Value); end; { TMycDataEndpoint } constructor TMycDataEndpoint.Create(ALookback: Integer); begin inherited Create; FLookback := ALookback; FConsumer := TMycGenericConsumer.Create(Self, Consume); end; destructor TMycDataEndpoint.Destroy; begin FConsumer.Free; inherited; end; function TMycDataEndpoint.GetChanged: TState; begin Result := FChanged.State end; function TMycDataEndpoint.Consume(const Value: T): TState; begin FLock.BeginWrite; try var P: PItem; New(P); P.Next := FFirst; FFirst := P; P.Value := Value; inc(FCount); FChanged.Notify; finally FLock.EndWrite; end; end; class function TMycDataEndpoint.CreateDataEndpoint(Lookback: Integer; out Series: TLazy>): IConsumer; var endPoint: TMycDataEndpoint; begin endPoint := TMycDataEndpoint.Create(Lookback); Result := endPoint.FConsumer; Series := endPoint; end; function TMycDataEndpoint.Update(var Value: TSeries): Boolean; begin FLock.BeginWrite; try Result := FChanged.Reset; if Result then begin if FCount > 0 then begin var item: PItem; var Arr: TArray; SetLength(Arr, FCount); while FFirst <> nil do begin item := FFirst; FFirst := item.Next; dec(FCount); Assert(FCount >= 0); Arr[FCount] := item.Value; Dispose(item); end; Assert(FCount = 0); Value.Add(Arr, FLookback); end; end; finally FLock.EndWrite; end; end; { TTickAggregation } constructor TTickAggregation.Create(const ATimeframe: TTimeframe); begin inherited Create; FTimeframe := ATimeframe; end; function TTickAggregation.GetBarStartTime(const TimeStamp: TDateTime; const Timeframe: TTimeframe): TDateTime; var baseTime: TDateTime; begin // Implementation is unchanged baseTime := RecodeMilliSecond(TimeStamp, 0); case Timeframe of S: Result := baseTime; S5: Result := RecodeSecond(baseTime, SecondOf(TimeStamp) - (SecondOf(TimeStamp) mod 5)); S15: Result := RecodeSecond(baseTime, SecondOf(TimeStamp) - (SecondOf(TimeStamp) mod 15)); S30: Result := RecodeSecond(baseTime, SecondOf(TimeStamp) - (SecondOf(TimeStamp) mod 30)); M: Result := RecodeSecond(baseTime, 0); M2: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 2)); M3: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 3)); M5: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 5)); M10: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 10)); M15: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 15)); M30: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 30)); H: Result := RecodeMinute(RecodeSecond(baseTime, 0), 0); H2: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 2)); H3: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 3)); H4: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 4)); H8: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 8)); H12: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 12)); D: Result := StartOfTheDay(TimeStamp); D2: Result := Floor(TimeStamp) - (Floor(TimeStamp) mod 2); D3: Result := Floor(TimeStamp) - (Floor(TimeStamp) mod 3); W: Result := TimeStamp.StartOfTheWeek; MN: Result := TimeStamp.StartOfTheMonth; MN3: Result := RecodeMonth(TimeStamp.StartOfTheMonth, (MonthOf(TimeStamp) - 1) div 3 * 3 + 1); MN6: Result := RecodeMonth(TimeStamp.StartOfTheMonth, (MonthOf(TimeStamp) - 1) div 6 * 6 + 1); Y: Result := TimeStamp.StartOfTheYear; else Result := 0; end; end; function TTickAggregation.GetCurrentBar: TDataPoint; begin Result := FCurrentBar; end; function TTickAggregation.GetTimeframe: TTimeframe; begin Result := FTimeframe; end; function TTickAggregation.Consume(const Value: TDataPoint): TState; var barStartTime: TDateTime; lastBarTime: TDateTime; begin barStartTime := GetBarStartTime(Value.Time, FTimeframe); lastBarTime := FCurrentBar.Time; if (barStartTime > lastBarTime) then begin if (lastBarTime > 0) then begin Result := Broadcast(FCurrentBar); end; FCurrentBar.Data.Open := Value.Data; FCurrentBar.Data.High := Value.Data; FCurrentBar.Data.Low := Value.Data; FCurrentBar.Data.Close := Value.Data; FCurrentBar.Data.Volume := 1; FCurrentBar.Time := barStartTime; end else begin if Value.Data > FCurrentBar.Data.High then FCurrentBar.Data.High := Value.Data; if Value.Data < FCurrentBar.Data.Low then FCurrentBar.Data.Low := Value.Data; FCurrentBar.Data.Close := Value.Data; FCurrentBar.Data.Volume := FCurrentBar.Data.Volume + 1; end; end; { TMycSequence } constructor TMycSequence.Create(ACount: Integer); begin inherited Create; SetLength(FProducers, ACount); for var i := 0 to High(FProducers) do FProducers[i] := TMycContainedProducer.Create(Self); end; destructor TMycSequence.Destroy; begin for var i := High(FProducers) downto 0 do FProducers[i].Free; inherited; end; class function TMycSequence.CreateSequence(Count: Integer; out Producers: TArray>): IConsumer; var Seq: TMycSequence; begin Seq := TMycSequence.Create(Count); Result := Seq; SetLength(Producers, Length(Seq.FProducers)); for var i := 0 to High(Producers) do Producers[i] := Seq.FProducers[i]; end; function TMycSequence.Consume(const Value: T): TState; begin Result := ProcessProducer(0, Value); end; function TMycSequence.ProcessProducer(Idx: Integer; const Value: T): TState; begin if Idx >= Length(FProducers) then exit; Result := TaskManager.RunTask(FProducers[idx].Broadcast(Value), function: TState begin Result := ProcessProducer(1 + idx, Value); end); end; function TMycIdentityConverter.Consume(const Value: T): TState; begin Result := Broadcast(Value); end; { TMycGenericParallelConverter } constructor TMycGenericParallelConverter.Create(const AFunc: TConstFunc); begin inherited Create; FFunc := AFunc; end; function TMycGenericParallelConverter.Consume(const Value: S): TState; begin var cValue := Value; Result := TaskManager.RunTask(FQueue, function: TState begin Result := Broadcast(FFunc(cValue)); end); FQueue := Result; end; { TMycParallelConverter } function TMycParallelConverter.Consume(const Value: T): TState; begin var cValue := Value; Result := TaskManager.RunTask(FQueue, function: TState begin Result := Broadcast(cValue); end); FQueue := Result; end; { TOhlcAggregation } constructor TOhlcAggregation.Create(const ATimeframe: TTimeframe); begin inherited Create; FTimeframe := ATimeframe; end; function TOhlcAggregation.GetBarStartTime(const TimeStamp: TDateTime; const Timeframe: TTimeframe): TDateTime; begin // Same implementation as TTickAggregation var baseTime := RecodeMilliSecond(TimeStamp, 0); case Timeframe of S: Result := baseTime; S5: Result := RecodeSecond(baseTime, SecondOf(TimeStamp) - (SecondOf(TimeStamp) mod 5)); S15: Result := RecodeSecond(baseTime, SecondOf(TimeStamp) - (SecondOf(TimeStamp) mod 15)); S30: Result := RecodeSecond(baseTime, SecondOf(TimeStamp) - (SecondOf(TimeStamp) mod 30)); M: Result := RecodeSecond(baseTime, 0); M2: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 2)); M3: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 3)); M5: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 5)); M10: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 10)); M15: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 15)); M30: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 30)); H: Result := RecodeMinute(RecodeSecond(baseTime, 0), 0); H2: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 2)); H3: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 3)); H4: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 4)); H8: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 8)); H12: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 12)); D: Result := StartOfTheDay(TimeStamp); D2: Result := Floor(TimeStamp) - (Floor(TimeStamp) mod 2); D3: Result := Floor(TimeStamp) - (Floor(TimeStamp) mod 3); W: Result := TimeStamp.StartOfTheWeek; MN: Result := TimeStamp.StartOfTheMonth; MN3: Result := RecodeMonth(TimeStamp.StartOfTheMonth, (MonthOf(TimeStamp) - 1) div 3 * 3 + 1); MN6: Result := RecodeMonth(TimeStamp.StartOfTheMonth, (MonthOf(TimeStamp) - 1) div 6 * 6 + 1); Y: Result := TimeStamp.StartOfTheYear; else Result := 0; end; end; function TOhlcAggregation.GetCurrentBar: TDataPoint; begin Result := FCurrentBar; end; function TOhlcAggregation.GetTimeframe: TTimeframe; begin Result := FTimeframe; end; function TOhlcAggregation.Consume(const Value: TDataPoint): TState; var barStartTime: TDateTime; lastBarTime: TDateTime; begin barStartTime := GetBarStartTime(Value.Time, FTimeframe); lastBarTime := FCurrentBar.Time; if (barStartTime > lastBarTime) then begin if (lastBarTime > 0) then begin Result := Broadcast(FCurrentBar); end; FCurrentBar.Data := Value.Data; FCurrentBar.Time := barStartTime; end else begin if Value.Data.High > FCurrentBar.Data.High then FCurrentBar.Data.High := Value.Data.High; if Value.Data.Low < FCurrentBar.Data.Low then FCurrentBar.Data.Low := Value.Data.Low; FCurrentBar.Data.Close := Value.Data.Close; FCurrentBar.Data.Volume := FCurrentBar.Data.Volume + Value.Data.Volume; end; end; { TMycDataJoin } constructor TMycDataJoin.Create(ACount: Integer); begin inherited Create; SetLength(FConsumers, ACount); FLock := TSpinLock.Create(false); FCount := Length(FConsumers); FContainedProvider := TMycContainedProducer>.Create(Self); var cFunc := function(Idx: Integer): TConstFunc begin Result := function(const Value: T): TState begin Result := Consume(Idx, Value); end end; for var i := 0 to High(FConsumers) do with FConsumers[i] do begin Queue := TQueue.Create; Consumer := TMycGenericConsumer.Create(Self, cFunc(i)); end; end; destructor TMycDataJoin.Destroy; begin for var i := High(FConsumers) downto 0 do with FConsumers[i] do begin Consumer.Free; Queue.Free; end; FContainedProvider.Free; inherited; end; function TMycDataJoin.Link(const Consumer: IConsumer>): TTag; begin Result := FContainedProvider.Link(Consumer); end; procedure TMycDataJoin.Unlink(Tag: TTag); begin FContainedProvider.Unlink(Tag); end; function TMycDataJoin.Consume(Idx: Integer; const Value: T): TState; begin var Arr: TArray; FLock.Enter; try if FConsumers[Idx].Queue.Count = 0 then dec(FCount); if FCount = 0 then begin SetLength(Arr, Length(FConsumers)); Arr[Idx] := Value; FCount := Length(FConsumers); for var i := 0 to High(FConsumers) do if i <> Idx then begin Arr[i] := FConsumers[i].Queue.Dequeue; if FConsumers[i].Queue.Count > 0 then dec(FCount); end; end else FConsumers[Idx].Queue.Enqueue(Value); finally FLock.Exit; end; if Arr <> nil then FContainedProvider.Broadcast(Arr); end; function TMycDataJoin.GetConsumers(Idx: Integer): IConsumer; begin Result := FConsumers[Idx].Consumer; end; { TMycGenericAggregator } constructor TMycGenericAggregator.Create(const AFunc: TConstFunc.TBroadcastProc, TState>); begin inherited Create; FFunc := AFunc; end; function TMycGenericAggregator.Consume(const Value: S): TState; begin Result := FFunc(Value, Broadcast); end; { TMycComposedConverter } constructor TMycComposedConverter.Create(const AConsumer: IConsumer; const AProducer: TProducer); begin inherited Create; FConsumer := AConsumer; FProducer := TProducer(AProducer); end; function TMycComposedConverter.GetConsumer: IConsumer; begin Result := FConsumer; end; function TMycComposedConverter.Link(const Consumer: IConsumer): TTag; begin // Delegate to the contained producer Result := IProducer(FProducer).Link(Consumer); end; procedure TMycComposedConverter.Unlink(Tag: TTag); begin // Delegate to the contained producer IProducer(FProducer).Unlink(Tag); end; { TMycProducer } constructor TMycProducer.Create; begin inherited Create; end; class constructor TMycProducer.CreateClass; begin FNull := TNull.Create; end; destructor TMycProducer.Destroy; begin FListeners.Finalize; inherited Destroy; end; function TMycProducer.Broadcast(const Value: T): TState; begin FListeners.Lock; try var item := FListeners.First; if item = nil then exit(TState.Null); var i := 1; while item.Next <> nil do begin inc(i); item := item.Next; end; var done := TLatch.CreateLatch(i); while item <> nil do begin item.Receiver.Consume(Value).Signal.Subscribe(done); item := item.Prev; end; Result := done.State; finally FListeners.Release; end; end; function TMycProducer.Link(const Consumer: IConsumer): TTag; begin // Add the Consumer to the notification list FListeners.Lock; try Result := FListeners.Advise(Consumer); finally FListeners.Release; end; end; procedure TMycProducer.Unlink(Tag: TTag); begin FListeners.Lock; try FListeners.Unadvise(Tag); finally FListeners.Release; end; end; function TMycConsumer.TNull.Consume(const Value: T): TState; begin Result := TState.Null; end; function TMycProducer.TNull.Link(const Consumer: IConsumer): TTag; begin Result := nil; end; procedure TMycProducer.TNull.Unlink(Tag: TTag); begin end; end.