Notify list refactoring

This commit is contained in:
Michael Schimmel
2025-07-15 09:46:16 +02:00
parent ce0cba720a
commit 1a07468ad8
4 changed files with 325 additions and 281 deletions
+99 -59
View File
@@ -19,8 +19,15 @@ type
PItem = ^TItem; PItem = ^TItem;
// Internal structure for storing a receiver and list linkage. // Internal structure for storing a receiver and list linkage.
TItem = record TItem = record
Next, Prev: PItem; // Pointers to the next and previous items in the doubly linked list. private
FNext: PItem;
FPrev: PItem;
function GetNext: PItem; inline;
public
Receiver: T; // The registered interface instance (the event sink). Receiver: T; // The registered interface instance (the event sink).
property Next: PItem read GetNext;
property Prev: PItem read FPrev;
end; end;
// Notify function. If this results false, it will be removed from the notify list // Notify function. If this results false, it will be removed from the notify list
@@ -31,6 +38,9 @@ type
// Bit 0 of the address stores the lock state (0 = locked, 1 = unlocked). // Bit 0 of the address stores the lock state (0 = locked, 1 = unlocked).
[volatile] [volatile]
FList: PItem; FList: PItem;
class var
FReverseOnNotify: Boolean;
class constructor CreateClass;
// Allocates memory for a new TItem. // Allocates memory for a new TItem.
class function AllocItem: PItem; static; inline; class function AllocItem: PItem; static; inline;
@@ -62,10 +72,18 @@ type
// by setting its interface reference to nil. The list item itself is not freed here. // by setting its interface reference to nil. The list item itself is not freed here.
procedure Notify(const Func: TNotifyProc); procedure Notify(const Func: TNotifyProc);
property First: PItem read GetFirst; property First: PItem read GetFirst;
// If this is true, the list is reversed after each Notify, hoping for a better distrubution of events.
class property ReverseOnNotify: Boolean read FReverseOnNotify write FReverseOnNotify;
end; end;
implementation implementation
class constructor TMycNotifyList<T>.CreateClass;
begin
FReverseOnNotify := true;
end;
class operator TMycNotifyList<T>.Initialize(out Dest: TMycNotifyList<T>); class operator TMycNotifyList<T>.Initialize(out Dest: TMycNotifyList<T>);
begin begin
NativeUInt(Dest.FList) := 1; NativeUInt(Dest.FList) := 1;
@@ -88,10 +106,10 @@ begin
Item := AllocItem; Item := AllocItem;
Item.Receiver := Receiver; Item.Receiver := Receiver;
Item.Prev := nil; Item.FPrev := nil;
Item.Next := FList; Item.FNext := FList;
if Item.Next <> nil then if Item.FNext <> nil then
Item.Next.Prev := Item; Item.FNext.FPrev := Item;
FList := Item; FList := Item;
@@ -133,108 +151,123 @@ var
begin begin
Assert(IsLocked); Assert(IsLocked);
var rev: PItem := nil; // Invariant: Append all detached items at end of the list. We can't simply free them, because the subscription is owned by the client.
var tmp: PItem := nil;
if FReverseOnNotify then
begin
// Method: stack assigned items and rebuild the list from the stack (reversing the order)
var stack: PItem := nil;
var released: PItem := nil;
while (FList <> nil) and Assigned(FList.Receiver) do while (FList <> nil) and Assigned(FList.Receiver) do
begin begin
Item := FList; Item := FList;
FList := Item.Next; FList := Item.FNext;
if Func(Item.Receiver) then if Func(Item.Receiver) then
begin begin
Item.Next := rev; // item stays valid
rev := Item; Item.FNext := stack;
stack := Item;
end end
else else
begin begin
// release the receiver // release the receiver
Item.Receiver := nil; Item.Receiver := nil;
Item.Next := tmp; Item.FNext := released;
tmp := Item; released := Item;
end; end;
end; end;
while tmp <> nil do // The list now only contains old released items. Push the newly released ones.
while released <> nil do
begin begin
Item := tmp; Item := released;
tmp := Item.Next; released := Item.FNext;
Item.Prev := nil; Item.FPrev := nil;
Item.Next := FList; Item.FNext := FList;
FList := Item; FList := Item;
if Item.Next <> nil then if Item.FNext <> nil then
Item.Next.Prev := Item; Item.FNext.FPrev := Item;
end; end;
while rev <> nil do // Now push the valid items, so that they are at the beginning of the list.
while stack <> nil do
begin begin
Item := rev; Item := stack;
rev := Item.Next; stack := Item.FNext;
Item.Prev := nil; Item.FPrev := nil;
Item.Next := FList; Item.FNext := FList;
FList := Item; FList := Item;
if Item.Next <> nil then if Item.FNext <> nil then
Item.Next.Prev := Item; Item.FNext.FPrev := Item;
end; end;
{ end
tmp := nil; else
Last := nil; begin
// Method: filter released items and add then to the end of the list
var released: PItem := nil;
var lastValid: PItem := nil;
Item := FList; Item := FList;
while (Item <> nil) and Assigned(Item.Receiver) do while (Item <> nil) and Assigned(Item.Receiver) do
begin begin
if not Func(Item.Receiver) then if not Func(Item.Receiver) then
begin begin
// Receiver wants no more notifications, detach it // item is now invalid
if Item = FList then if Item = FList then
FList := Item.Next; FList := Item.FNext;
if Item.Prev <> nil then if Item.FPrev <> nil then
Item.Prev.Next := Item.Next; Item.FPrev.FNext := Item.FNext;
if Item.Next <> nil then if Item.FNext <> nil then
Item.Next.Prev := Item.Prev; Item.FNext.FPrev := Item.FPrev;
// release the receiver // release the receiver
Item.Receiver := nil; Item.Receiver := nil;
// and save the list item in a tmp list for later use // and save the list item in a tmp list for later use
var nxt := Item.Next; var nxt := Item.FNext;
Item.Next := tmp; Item.FNext := released;
tmp := Item; released := Item;
Item := nxt; Item := nxt;
end end
else else
begin begin
Last := Item; // save a pointer to the last valid item
Item := Last.Next; lastValid := Item;
Item := lastValid.FNext;
end; end;
end; end;
// Append all detached items at end of the list. We can't simply free them, because the subscription is owned by the client. // insert all new released items after the last valid item
while tmp <> nil do while released <> nil do
begin begin
Item := tmp; Item := released;
tmp := Item.Next; released := Item.FNext;
if Last = nil then if lastValid = nil then
begin begin
Item.Prev := nil; Item.FPrev := nil;
Item.Next := FList; Item.FNext := FList;
FList := Item; FList := Item;
end end
else else
begin begin
Item.Prev := Last; Item.FPrev := lastValid;
Item.Next := Last.Next; Item.FNext := lastValid.Next;
if Item.Prev <> nil then if Item.FPrev <> nil then
Item.Prev.Next := Item; Item.FPrev.FNext := Item;
end;
if Item.FNext <> nil then
Item.FNext.FPrev := Item;
end; end;
if Item.Next <> nil then
Item.Next.Prev := Item;
end; end;
}
end; end;
procedure TMycNotifyList<T>.Release; procedure TMycNotifyList<T>.Release;
@@ -270,12 +303,12 @@ begin
Item.Receiver := nil; Item.Receiver := nil;
if Item = FList then if Item = FList then
FList := Item.Next; FList := Item.FNext;
if Item.Prev <> nil then if Item.FPrev <> nil then
Item.Prev.Next := Item.Next; Item.FPrev.FNext := Item.FNext;
if Item.Next <> nil then if Item.FNext <> nil then
Item.Next.Prev := Item.Prev; Item.FNext.FPrev := Item.FPrev;
FreeItem(Item); FreeItem(Item);
end; end;
@@ -287,4 +320,11 @@ begin
Unadvise(TTag(FList)); Unadvise(TTag(FList));
end; end;
function TMycNotifyList<T>.TItem.GetNext: PItem;
begin
if not Assigned(Receiver) then
exit(nil);
Result := FNext;
end;
end. end.
+1
View File
@@ -11,6 +11,7 @@ uses
Myc.Trade.Types, Myc.Trade.Types,
Myc.Trade.DataArray, Myc.Trade.DataArray,
Myc.Trade.DataPoint, Myc.Trade.DataPoint,
Myc.Trade.DataPoint.Impl,
Myc.Fmx.Chart; Myc.Fmx.Chart;
type type
+182 -21
View File
@@ -3,12 +3,57 @@ unit Myc.Trade.DataPoint.Impl;
interface interface
uses uses
System.SysUtils,
Myc.Signals, Myc.Signals,
Myc.Trade.Types, Myc.Trade.Types,
Myc.Trade.DataPoint; Myc.Trade.DataPoint,
Myc.Core.Notifier;
type type
// Null object implementation for IMycConverter // Abstract base class for data consumers.
TMycProcessor<T> = class abstract(TInterfacedObject, IMycProcessor<T>)
protected
function ProcessData(const Value: T): TState; virtual; abstract;
end;
// Concrete data provider that manages a list of processors (listeners).
TMycDataProvider<T> = class abstract(TContainedObject, TDataProvider<T>.IDataProvider)
private
FListeners: TMycNotifyList<IMycProcessor<T>>;
public
constructor Create(const Controller: IInterface);
destructor Destroy; override;
// Notifies all linked processors.
function Broadcast(const Value: T): TState;
// Link a Processor
function Link(const Processor: IMycProcessor<T>): TDataProvider<T>.TTag;
// Unlink a linked strategy
procedure Unlink(Tag: TDataProvider<T>.TTag);
end;
// Null object implementation for IDataProvider.
TNullDataProvider<T> = class(TInterfacedObject, TDataProvider<T>.IDataProvider)
public
function Link(const Receiver: IMycProcessor<T>): TDataProvider<T>.TTag;
procedure Unlink(Tag: TDataProvider<T>.TTag);
end;
// Abstract base class for components that process data of type S and provide data of type T.
TMycConverter<S, T> = class abstract(TMycProcessor<S>, TConverter<S, T>.IConverter)
private
FSender: TMycDataProvider<T>;
function GetSender: TDataProvider<T>.IDataProvider;
protected
function ProcessData(const Value: S): TState; override; abstract;
// Broadcasts the given data to all linked processors.
function Broadcast(const Value: T): TState;
public
constructor Create;
destructor Destroy; override;
property Sender: TDataProvider<T>.IDataProvider read GetSender;
end;
// Null object implementation for IConverter.
TNullConverter<S, T> = class(TInterfacedObject, TConverter<S, T>.IConverter) TNullConverter<S, T> = class(TInterfacedObject, TConverter<S, T>.IConverter)
private private
function GetSender: TDataProvider<T>.IDataProvider; function GetSender: TDataProvider<T>.IDataProvider;
@@ -16,22 +61,7 @@ type
function ProcessData(const Value: S): TState; function ProcessData(const Value: S): TState;
end; end;
TMycConverter<S, T> = class abstract(TMycProcessor<S>, TConverter<S, T>.IConverter) // A generic converter that uses a function reference for the conversion logic.
private
FSender: TMycDataProvider<T>;
function GetSender: TDataProvider<T>.IDataProvider;
protected
function ProcessData(const Value: S): TState; override; abstract;
// Broadcasts the given data to all linked processors.
function Broadcast(const Value: T): TState;
public
constructor Create;
destructor Destroy; override;
property Sender: TDataProvider<T>.IDataProvider read GetSender;
end;
TMycGenericConverter<S, T> = class(TMycConverter<S, T>) TMycGenericConverter<S, T> = class(TMycConverter<S, T>)
private private
FFunc: TConstFunc<S, T>; FFunc: TConstFunc<S, T>;
@@ -41,12 +71,14 @@ type
constructor Create(const AFunc: TConstFunc<S, T>); constructor Create(const AFunc: TConstFunc<S, T>);
end; end;
// A converter specialized for calculating indicators.
TMycIndicator<S, T> = class(TMycConverter<S, T>) TMycIndicator<S, T> = class(TMycConverter<S, T>)
protected protected
function ProcessData(const Value: S): TState; override; final; function ProcessData(const Value: S): TState; override; final;
function Calculate(const Value: S): T; virtual; abstract; function Calculate(const Value: S): T; virtual; abstract;
end; end;
// A converter that counts incoming data points and outputs the current count.
TMycDataCounter<T> = class(TMycConverter<T, Int64>) TMycDataCounter<T> = class(TMycConverter<T, Int64>)
private private
FCount: Int64; FCount: Int64;
@@ -56,27 +88,124 @@ type
constructor Create; constructor Create;
end; end;
// A converter that takes an array and broadcasts each element individually.
TMycTicker<T> = class(TMycConverter<TArray<T>, T>) TMycTicker<T> = class(TMycConverter<TArray<T>, T>)
public public
function ProcessData(const Values: TArray<T>): TState; override; function ProcessData(const Values: TArray<T>): TState; override;
end; end;
// A converter that reads a specific field from a record using RTTI.
TMycRecordFieldReader<S, T> = class(TMycConverter<S, T>) TMycRecordFieldReader<S, T> = class(TMycConverter<S, T>)
private private
FOffset: Integer; FOffset: Integer;
public public
constructor Create(const AFieldName: String); constructor Create(const AFieldName: String);
function ProcessData(const Values: S): TState; override; function ProcessData(const Values: S): TState; override;
end; end;
// A generic processor implementation that uses a function reference.
TMycGenericProcessor<T> = class(TMycProcessor<T>)
type
TProc = reference to function(const Value: T): TState;
private
FProc: TProc;
protected
function ProcessData(const Value: T): TState; override; final;
public
constructor Create(const AProc: TProc);
end;
// A processor implementation that is owned by a controller.
TMycContainedProcessor<T> = class(TContainedObject, IMycProcessor<T>)
type
TProc = function(const Value: T): TState of object;
private
FProc: TProc;
function ProcessData(const Value: T): TState;
public
constructor Create(const Controller: IInterface; const AProc: TProc);
end;
implementation implementation
uses uses
System.TypInfo, System.TypInfo,
System.SysUtils,
System.RTTI; System.RTTI;
{ TMycDataProvider<T> }
constructor TMycDataProvider<T>.Create(const Controller: IInterface);
begin
inherited Create(Controller);
end;
destructor TMycDataProvider<T>.Destroy;
begin
FListeners.Finalize;
inherited Destroy;
end;
function TMycDataProvider<T>.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.ProcessData(Value).Signal.Subscribe(done);
item := item.Prev;
end;
Result := done.State;
finally
FListeners.Release;
end;
end;
function TMycDataProvider<T>.Link(const Processor: IMycProcessor<T>): TDataProvider<T>.TTag;
begin
// Add the Processor to the notification list
FListeners.Lock;
try
Result := FListeners.Advise(Processor);
finally
FListeners.Release;
end;
end;
procedure TMycDataProvider<T>.Unlink(Tag: TDataProvider<T>.TTag);
begin
FListeners.Lock;
try
FListeners.Unadvise(Tag);
finally
FListeners.Release;
end;
end;
{ TNullDataProvider<T> }
function TNullDataProvider<T>.Link(const Receiver: IMycProcessor<T>): TDataProvider<T>.TTag;
begin
Result := nil;
end;
procedure TNullDataProvider<T>.Unlink(Tag: TDataProvider<T>.TTag);
begin
// Do nothing in the null implementation.
end;
{ TMycConverter<S, T> } { TMycConverter<S, T> }
constructor TMycConverter<S, T>.Create; constructor TMycConverter<S, T>.Create;
@@ -101,7 +230,7 @@ begin
Result := FSender; Result := FSender;
end; end;
{ TNullConverter } { TNullConverter<S, T> }
function TNullConverter<S, T>.GetSender: TDataProvider<T>.IDataProvider; function TNullConverter<S, T>.GetSender: TDataProvider<T>.IDataProvider;
begin begin
@@ -133,6 +262,8 @@ begin
Result := Broadcast(Calculate(Value)); Result := Broadcast(Calculate(Value));
end; end;
{ TMycDataCounter<T> }
constructor TMycDataCounter<T>.Create; constructor TMycDataCounter<T>.Create;
begin begin
inherited Create; inherited Create;
@@ -145,6 +276,8 @@ begin
inc(FCount); inc(FCount);
end; end;
{ TMycTicker<T> }
function TMycTicker<T>.ProcessData(const Values: TArray<T>): TState; function TMycTicker<T>.ProcessData(const Values: TArray<T>): TState;
begin begin
var done := TLatch.CreateLatch(Length(Values)); var done := TLatch.CreateLatch(Length(Values));
@@ -156,6 +289,8 @@ begin
Result := done.State; Result := done.State;
end; end;
{ TMycRecordFieldReader<S, T> }
constructor TMycRecordFieldReader<S, T>.Create(const AFieldName: String); constructor TMycRecordFieldReader<S, T>.Create(const AFieldName: String);
begin begin
inherited Create; inherited Create;
@@ -193,4 +328,30 @@ begin
Result := Broadcast(PT(fieldPtr)^); Result := Broadcast(PT(fieldPtr)^);
end; end;
{ TMycGenericProcessor<T> }
constructor TMycGenericProcessor<T>.Create(const AProc: TProc);
begin
inherited Create;
FProc := AProc;
end;
function TMycGenericProcessor<T>.ProcessData(const Value: T): TState;
begin
Result := FProc(Value);
end;
{ TMycContainedProcessor<T> }
constructor TMycContainedProcessor<T>.Create(const Controller: IInterface; const AProc: TProc);
begin
inherited Create(Controller);
FProc := AProc;
end;
function TMycContainedProcessor<T>.ProcessData(const Value: T): TState;
begin
Result := FProc(Value);
end;
end. end.
+1 -159
View File
@@ -4,8 +4,7 @@ interface
uses uses
Myc.Signals, Myc.Signals,
Myc.Trade.Types, Myc.Trade.Types;
Myc.Core.Notifier;
type type
// Represents a time-stamped data point in a series. // Represents a time-stamped data point in a series.
@@ -19,11 +18,6 @@ type
function ProcessData(const Value: T): TState; function ProcessData(const Value: T): TState;
end; end;
TMycProcessor<T> = class abstract(TInterfacedObject, IMycProcessor<T>)
protected
function ProcessData(const Value: T): TState; virtual; abstract;
end;
TDataProvider<T> = record TDataProvider<T> = record
type type
TTag = Pointer; TTag = Pointer;
@@ -56,48 +50,6 @@ type
class property Null: IDataProvider read FNull; class property Null: IDataProvider read FNull;
end; end;
TMycDataProvider<T> = class abstract(TContainedObject, TDataProvider<T>.IDataProvider)
private
FListeners: TMycNotifyList<IMycProcessor<T>>;
public
constructor Create(const Controller: IInterface);
destructor Destroy; override;
// Notifies all linked processors.
function Broadcast(const Value: T): TState;
procedure Notify(const Func: TMycNotifyList<IMycProcessor<T>>.TNotifyProc);
// Link a Processor
function Link(const Processor: IMycProcessor<T>): TDataProvider<T>.TTag;
// Unlink a linked strategy
procedure Unlink(Tag: TDataProvider<T>.TTag);
end;
TMycGenericProcessor<T> = class(TMycProcessor<T>)
type
TProc = reference to function(const Value: T): TState;
private
FProc: TProc;
protected
function ProcessData(const Value: T): TState; override; final;
public
constructor Create(const AProc: TProc);
end;
TMycContainedProcessor<T> = class(TContainedObject, IMycProcessor<T>)
type
TProc = function(const Value: T): TState of object;
private
FProc: TProc;
function ProcessData(const Value: T): TState;
public
constructor Create(const Controller: IInterface; const AProc: TProc);
end;
TNullDataProvider<T> = class(TInterfacedObject, TDataProvider<T>.IDataProvider)
public
function Link(const Receiver: IMycProcessor<T>): TDataProvider<T>.TTag;
procedure Unlink(Tag: TDataProvider<T>.TTag);
end;
// Interface helper for IMycConverter<S,T> providing the null object pattern. // Interface helper for IMycConverter<S,T> providing the null object pattern.
TConverter<S, T> = record TConverter<S, T> = record
type type
@@ -150,18 +102,6 @@ implementation
uses uses
Myc.Trade.DataPoint.Impl; Myc.Trade.DataPoint.Impl;
{ TNullDataProvider }
function TNullDataProvider<T>.Link(const Receiver: IMycProcessor<T>): TDataProvider<T>.TTag;
begin
Result := nil;
end;
procedure TNullDataProvider<T>.Unlink(Tag: TDataProvider<T>.TTag);
begin
// Do nothing in the null implementation.
end;
{ TDataPoint<T> } { TDataPoint<T> }
constructor TDataPoint<T>.Create(ATime: TDateTime; const AData: T); constructor TDataPoint<T>.Create(ATime: TDateTime; const AData: T);
@@ -216,104 +156,6 @@ begin
FDataProvider.Unlink(Tag); FDataProvider.Unlink(Tag);
end; end;
{ TMycGenericProcessor<T> }
constructor TMycGenericProcessor<T>.Create(const AProc: TProc);
begin
inherited Create;
FProc := AProc;
end;
function TMycGenericProcessor<T>.ProcessData(const Value: T): TState;
begin
Result := FProc(Value);
end;
{ TMycContainedProcessor<T> }
constructor TMycContainedProcessor<T>.Create(const Controller: IInterface; const AProc: TProc);
begin
inherited Create(Controller);
FProc := AProc;
end;
function TMycContainedProcessor<T>.ProcessData(const Value: T): TState;
begin
Result := FProc(Value);
end;
{ TMycDataProvider<T> }
constructor TMycDataProvider<T>.Create(const Controller: IInterface);
begin
inherited Create(Controller);
end;
destructor TMycDataProvider<T>.Destroy;
begin
FListeners.Finalize;
inherited Destroy;
end;
function TMycDataProvider<T>.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.ProcessData(Value).Signal.Subscribe(done);
item := item.Prev;
end;
Result := done.State;
finally
FListeners.Release;
end;
end;
procedure TMycDataProvider<T>.Notify(const Func: TMycNotifyList<IMycProcessor<T>>.TNotifyProc);
begin
FListeners.Lock;
try
FListeners.Notify(Func);
finally
FListeners.Release;
end;
end;
function TMycDataProvider<T>.Link(const Processor: IMycProcessor<T>): TDataProvider<T>.TTag;
begin
// Add the Processor to the notification list
FListeners.Lock;
try
Result := FListeners.Advise(Processor);
finally
FListeners.Release;
end;
end;
procedure TMycDataProvider<T>.Unlink(Tag: TDataProvider<T>.TTag);
begin
FListeners.Lock;
try
FListeners.Unadvise(Tag);
finally
FListeners.Release;
end;
end;
{ TConverter<S, T> } { TConverter<S, T> }
class constructor TConverter<S, T>.CreateClass; class constructor TConverter<S, T>.CreateClass;