Test Refactoring of signals
This commit is contained in:
@@ -24,7 +24,7 @@ type
|
||||
TMycGateFuncFuture<T> = class(TMycFuture<T>)
|
||||
private
|
||||
FInit: TState.TSubscription;
|
||||
FDone: IMycLatch;
|
||||
FDone: IMycEvent;
|
||||
FResult: T;
|
||||
protected
|
||||
function GetResult: T; override;
|
||||
@@ -54,7 +54,7 @@ constructor TMycGateFuncFuture<T>.Create(const ATaskManager: IMycTaskManager; co
|
||||
begin
|
||||
inherited Create;
|
||||
|
||||
FDone := TLatch.Construct(1);
|
||||
FDone := TEvent.CreateLatch(1);
|
||||
|
||||
// Subscribe the job execution to AGate.
|
||||
// The job will run when AGate notifies the subscriber returned by Run.
|
||||
|
||||
+101
-26
@@ -8,38 +8,61 @@ uses
|
||||
Myc.Lazy;
|
||||
|
||||
type
|
||||
TMycLazy<T> = class abstract(TInterfacedObject, IMycLazy<T>)
|
||||
TMycNullLazy<T> = class(TInterfacedObject, IMycLazy<T>)
|
||||
protected
|
||||
function GetChanged: IMycState; virtual; abstract;
|
||||
function GetChanged: TState;
|
||||
public
|
||||
function Pop(out Res: T): Boolean; virtual; abstract;
|
||||
function Pop(out Res: T): Boolean;
|
||||
end;
|
||||
|
||||
TMycNullLazy<T> = class(TMycLazy<T>)
|
||||
protected
|
||||
function GetChanged: IMycState; override;
|
||||
public
|
||||
function Pop(out Res: T): Boolean; override;
|
||||
end;
|
||||
|
||||
TMycFuncLazy<T> = class(TMycLazy<T>)
|
||||
private
|
||||
FChanged: IMycDirty;
|
||||
TMycLazyBase<T> = class(TInterfacedObject, IMycLazy<T>)
|
||||
strict private
|
||||
FChanged: TFlag;
|
||||
FChangeState: TState.TSubscription;
|
||||
function GetChanged: TState;
|
||||
protected
|
||||
function GetValue: T; virtual; abstract;
|
||||
public
|
||||
constructor Create(const AChanged: TState);
|
||||
destructor Destroy; override;
|
||||
function Pop(out Res: T): Boolean;
|
||||
end;
|
||||
|
||||
TMycFuncLazy<T> = class(TMycLazyBase<T>)
|
||||
private
|
||||
FProc: TFunc<T>;
|
||||
protected
|
||||
function GetChanged: IMycState; override;
|
||||
function GetValue: T; override;
|
||||
public
|
||||
constructor Create(const AChanged: TState; const AProc: TFunc<T>);
|
||||
destructor Destroy; override;
|
||||
function Pop(out Res: T): Boolean; override;
|
||||
end;
|
||||
|
||||
TMycChainedLazy<T> = class(TMycLazyBase<T>)
|
||||
private
|
||||
FValue: IMycLazy<T>;
|
||||
protected
|
||||
function GetValue: T; override;
|
||||
public
|
||||
constructor Create(const AValue: IMycLazy<T>);
|
||||
end;
|
||||
|
||||
TMycMutable<T> = class(TInterfacedObject, IMycLazy<T>, IMycMutable<T>)
|
||||
private
|
||||
FNotifier: TEvent;
|
||||
FValue: T;
|
||||
protected
|
||||
function GetChanged: TState;
|
||||
public
|
||||
constructor Create;
|
||||
function Pop(out Res: T): Boolean;
|
||||
procedure SendValue(const Value: T);
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TMycNullLazy<T> }
|
||||
|
||||
function TMycNullLazy<T>.GetChanged: IMycState;
|
||||
function TMycNullLazy<T>.GetChanged: TState;
|
||||
begin
|
||||
Result := TState.Null;
|
||||
end;
|
||||
@@ -50,36 +73,88 @@ begin
|
||||
Result := true;
|
||||
end;
|
||||
|
||||
{ TMycFuncLazy<T> }
|
||||
{ TMycLazyBase<T> }
|
||||
|
||||
constructor TMycFuncLazy<T>.Create(const AChanged: TState; const AProc: TFunc<T>);
|
||||
constructor TMycLazyBase<T>.Create(const AChanged: TState);
|
||||
begin
|
||||
inherited Create;
|
||||
FChanged := TDirty.Construct;
|
||||
FProc := AProc;
|
||||
FChanged := TFlag.CreateFlag;
|
||||
FChangeState := AChanged.Subscribe(FChanged);
|
||||
end;
|
||||
|
||||
destructor TMycFuncLazy<T>.Destroy;
|
||||
destructor TMycLazyBase<T>.Destroy;
|
||||
begin
|
||||
FChangeState.Unsubscribe;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
function TMycFuncLazy<T>.GetChanged: IMycState;
|
||||
function TMycLazyBase<T>.GetChanged: TState;
|
||||
begin
|
||||
Result := FChanged.State;
|
||||
end;
|
||||
|
||||
function TMycFuncLazy<T>.Pop(out Res: T): Boolean;
|
||||
function TMycLazyBase<T>.Pop(out Res: T): Boolean;
|
||||
begin
|
||||
Result := FChanged.State.IsSet;
|
||||
if Result then
|
||||
begin
|
||||
if Assigned(FProc) then
|
||||
Res := FProc;
|
||||
Res := GetValue;
|
||||
FChanged.Reset;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TMycFuncLazy<T> }
|
||||
|
||||
constructor TMycFuncLazy<T>.Create(const AChanged: TState; const AProc: TFunc<T>);
|
||||
begin
|
||||
inherited Create( AChanged );
|
||||
FProc := AProc;
|
||||
Assert( Assigned(FProc) );
|
||||
end;
|
||||
|
||||
function TMycFuncLazy<T>.GetValue: T;
|
||||
begin
|
||||
Result := FProc();
|
||||
end;
|
||||
|
||||
{ TMycChainedLazy<T> }
|
||||
|
||||
constructor TMycChainedLazy<T>.Create(const AValue: IMycLazy<T>);
|
||||
begin
|
||||
inherited Create( AValue.Changed );
|
||||
FValue := AValue;
|
||||
end;
|
||||
|
||||
function TMycChainedLazy<T>.GetValue: T;
|
||||
begin
|
||||
if not FValue.Pop( Result ) then
|
||||
raise Exception.Create('Lazy chain already popped');
|
||||
end;
|
||||
|
||||
{ TMycMutable<T> }
|
||||
|
||||
constructor TMycMutable<T>.Create;
|
||||
begin
|
||||
inherited Create;
|
||||
FNotifier := TEvent.CreateNotifier;
|
||||
FValue := Default(T);
|
||||
end;
|
||||
|
||||
function TMycMutable<T>.GetChanged: TState;
|
||||
begin
|
||||
Result := FNotifier.State;
|
||||
end;
|
||||
|
||||
function TMycMutable<T>.Pop(out Res: T): Boolean;
|
||||
begin
|
||||
Res := FValue;
|
||||
exit(true);
|
||||
end;
|
||||
|
||||
procedure TMycMutable<T>.SendValue(const Value: T);
|
||||
begin
|
||||
FValue := Value;
|
||||
FNotifier.Notify;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
+116
-48
@@ -8,32 +8,38 @@ uses
|
||||
Myc.Signals;
|
||||
|
||||
type
|
||||
TMycState = class abstract(TInterfacedObject, IMycState)
|
||||
protected
|
||||
function GetIsSet: Boolean; virtual; abstract;
|
||||
public
|
||||
function Subscribe(Subscriber: IMycSubscriber): Pointer; virtual; abstract;
|
||||
procedure Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag); virtual; abstract;
|
||||
property IsSet: Boolean read GetIsSet;
|
||||
end;
|
||||
|
||||
// TMycNullState implements a "null object" pattern for IMycState.
|
||||
// This state is always considered "set".
|
||||
TMycNullState = class(TInterfacedObject, IMycState)
|
||||
protected
|
||||
// IMycState
|
||||
function GetIsSet: Boolean;
|
||||
function Subscribe(Subscriber: IMycSubscriber): Pointer;
|
||||
procedure Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag);
|
||||
public
|
||||
constructor Create;
|
||||
function Subscribe(Subscriber: IMycSubscriber): Pointer;
|
||||
procedure Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag);
|
||||
end;
|
||||
|
||||
// A state that acts as an event. It's state ist always set and it will always notify it's subscribers, if it gets notified by itself.
|
||||
TMycNotifyEvent = class(TInterfacedObject, IMycState, IMycEvent)
|
||||
strict private
|
||||
FSubscribers: TMycNotifyList<IMycSubscriber>;
|
||||
protected
|
||||
function GetIsSet: Boolean;
|
||||
function GetState: TState;
|
||||
public
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
function Subscribe(Subscriber: IMycSubscriber): Pointer;
|
||||
procedure Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag);
|
||||
function Notify: Boolean;
|
||||
end;
|
||||
|
||||
// TMycLatch implements a countdown latch.
|
||||
// It is initialized with a count. Calls to its Notify method (as an IMycSubscriber)
|
||||
// decrement this count. When the count reaches zero, the latch transitions to the "set"
|
||||
// state (IsSet becomes true), notifies its current subscribers once, and then remains set.
|
||||
TMycLatch = class(TMycState, IMycLatch)
|
||||
TMycLatch = class(TInterfacedObject, IMycState, IMycEvent)
|
||||
strict private
|
||||
FSubscribers: TMycNotifyList<IMycSubscriber>; // List of subscribers waiting for this latch to be set.
|
||||
private
|
||||
@@ -41,7 +47,7 @@ type
|
||||
FCount: Integer; // The internal countdown value for the latch.
|
||||
function GetState: TState; // Implementation for IMycLatch.GetState and IMycFlag.GetState.
|
||||
protected
|
||||
function GetIsSet: Boolean; override; final; // Implementation for IMycState.GetIsSet.
|
||||
function GetIsSet: Boolean;
|
||||
public
|
||||
// Creates the latch with an initial count.
|
||||
// If ACount is 0 or less, the latch is effectively pre-set (delegates to FNull via CreateLatch factory).
|
||||
@@ -49,8 +55,8 @@ type
|
||||
destructor Destroy; override;
|
||||
|
||||
// IMycSignal implementation
|
||||
function Subscribe(Subscriber: IMycSubscriber): Pointer; override;
|
||||
procedure Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag); override;
|
||||
function Subscribe(Subscriber: IMycSubscriber): Pointer;
|
||||
procedure Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag);
|
||||
|
||||
// IMycSubscriber implementation for TMycLatch itself.
|
||||
// Decrements the internal count. If the count reaches zero,
|
||||
@@ -58,16 +64,16 @@ type
|
||||
function Notify: Boolean;
|
||||
end;
|
||||
|
||||
TMycNullLatch = class(TInterfacedObject, IMycLatch)
|
||||
TMycNullEvent = class(TInterfacedObject, IMycEvent)
|
||||
private
|
||||
function GetState: TState;
|
||||
function Notify: Boolean;
|
||||
end;
|
||||
|
||||
// TMycDirty implements a resettable "dirty flag".
|
||||
// TMycFlag implements a resettable "dirty flag".
|
||||
// It can be explicitly set to dirty (via Notify) or reset to clean (via Reset).
|
||||
// Subscribers are notified of relevant state changes.
|
||||
TMycDirty = class(TMycState, IMycDirty)
|
||||
TMycFlag = class(TInterfacedObject, IMycState, IMycFlag)
|
||||
strict private
|
||||
FSubscribers: TMycNotifyList<IMycSubscriber>; // List of subscribers interested in state changes of this dirty flag.
|
||||
private
|
||||
@@ -75,27 +81,27 @@ type
|
||||
FFlag: Boolean; // Internal state: true if dirty/set, false if clean/reset.
|
||||
function GetState: TState;
|
||||
protected
|
||||
function GetIsSet: Boolean; override; final; // Implementation for IMycState.GetIsSet (true if dirty).
|
||||
function GetIsSet: Boolean;
|
||||
public
|
||||
// Creates a new dirty flag, initially set to dirty (true).
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
|
||||
// Factory method to create a new TMycDirty instance.
|
||||
class function CreateDirty: IMycDirty; static;
|
||||
// Factory method to create a new TMycFlag instance.
|
||||
class function CreateDirty: IMycFlag; static;
|
||||
|
||||
function Subscribe(Subscriber: IMycSubscriber): Pointer; override;
|
||||
procedure Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag); override;
|
||||
function Subscribe(Subscriber: IMycSubscriber): Pointer;
|
||||
procedure Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag);
|
||||
|
||||
// IMycSubscriber implementation for TMycDirty.
|
||||
// IMycSubscriber implementation for TMycFlag.
|
||||
// Sets this flag to dirty (true). Notifies subscribers if it transitioned from clean to dirty.
|
||||
function Notify: Boolean;
|
||||
|
||||
// IMycDirty implementation. Resets the flag to clean (false).
|
||||
// IMycFlag implementation. Resets the flag to clean (false).
|
||||
function Reset: Boolean;
|
||||
end;
|
||||
|
||||
TMycNullDirty = class(TInterfacedObject, IMycDirty)
|
||||
TMycNullFlag = class(TInterfacedObject, IMycFlag)
|
||||
private
|
||||
function GetState: TState;
|
||||
function Notify: Boolean;
|
||||
@@ -135,14 +141,76 @@ begin
|
||||
// Unsubscribing from a null state has no effect.
|
||||
end;
|
||||
|
||||
{ TMycNullLatch }
|
||||
{ TMycNotifyEvent }
|
||||
|
||||
function TMycNullLatch.GetState: TState;
|
||||
constructor TMycNotifyEvent.Create;
|
||||
begin
|
||||
inherited Create;
|
||||
FSubscribers.Create;
|
||||
end;
|
||||
|
||||
destructor TMycNotifyEvent.Destroy;
|
||||
begin
|
||||
FSubscribers.Destroy;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
function TMycNotifyEvent.GetIsSet: Boolean;
|
||||
begin
|
||||
Result := true;
|
||||
end;
|
||||
|
||||
function TMycNotifyEvent.GetState: TState;
|
||||
begin
|
||||
Result := Self;
|
||||
end;
|
||||
|
||||
function TMycNotifyEvent.Notify: Boolean;
|
||||
begin
|
||||
FSubscribers.Lock;
|
||||
try
|
||||
FSubscribers.Notify(function(Subscriber: IMycSubscriber): Boolean begin Result := Subscriber.Notify; end);
|
||||
finally
|
||||
FSubscribers.Release;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TMycNotifyEvent.Subscribe(Subscriber: IMycSubscriber): Pointer;
|
||||
begin
|
||||
Result := nil;
|
||||
if not Assigned(Subscriber) then
|
||||
exit;
|
||||
|
||||
FSubscribers.Lock;
|
||||
try
|
||||
Subscriber.Notify;
|
||||
Result := FSubscribers.Advise(Subscriber);
|
||||
finally
|
||||
FSubscribers.Release;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMycNotifyEvent.Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag);
|
||||
begin
|
||||
if Tag = nil then
|
||||
exit;
|
||||
|
||||
FSubscribers.Lock;
|
||||
try
|
||||
FSubscribers.Unadvise(Tag);
|
||||
finally
|
||||
FSubscribers.Release;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TMycNullEvent }
|
||||
|
||||
function TMycNullEvent.GetState: TState;
|
||||
begin
|
||||
Result := TState.Null;
|
||||
end;
|
||||
|
||||
function TMycNullLatch.Notify: Boolean;
|
||||
function TMycNullEvent.Notify: Boolean;
|
||||
begin
|
||||
Result := false;
|
||||
end;
|
||||
@@ -246,63 +314,63 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TMycNullDirty }
|
||||
{ TMycNullFlag }
|
||||
|
||||
function TMycNullDirty.GetState: TState;
|
||||
function TMycNullFlag.GetState: TState;
|
||||
begin
|
||||
Result := TState.Null;
|
||||
end;
|
||||
|
||||
function TMycNullDirty.Notify: Boolean;
|
||||
function TMycNullFlag.Notify: Boolean;
|
||||
begin
|
||||
Result := false;
|
||||
end;
|
||||
|
||||
function TMycNullDirty.Reset: Boolean;
|
||||
function TMycNullFlag.Reset: Boolean;
|
||||
begin
|
||||
Result := true;
|
||||
end;
|
||||
|
||||
{ TMycDirty }
|
||||
{ TMycFlag }
|
||||
|
||||
constructor TMycDirty.Create;
|
||||
constructor TMycFlag.Create;
|
||||
begin
|
||||
inherited Create;
|
||||
FFlag := true; // A new dirty flag is initially considered dirty.
|
||||
FSubscribers.Create;
|
||||
end;
|
||||
|
||||
class function TMycDirty.CreateDirty: IMycDirty;
|
||||
class function TMycFlag.CreateDirty: IMycFlag;
|
||||
begin
|
||||
// Factory method to create a new TMycDirty instance.
|
||||
Result := TMycDirty.Create;
|
||||
// Factory method to create a new TMycFlag instance.
|
||||
Result := TMycFlag.Create;
|
||||
end;
|
||||
|
||||
destructor TMycDirty.Destroy;
|
||||
destructor TMycFlag.Destroy;
|
||||
begin
|
||||
FSubscribers.Destroy; // Clean up the subscriber list.
|
||||
inherited;
|
||||
end;
|
||||
|
||||
function TMycDirty.Reset: Boolean;
|
||||
function TMycFlag.Reset: Boolean;
|
||||
begin
|
||||
// Sets the flag to false (clean) and returns true if it was previously true (dirty).
|
||||
Result := TInterlocked.Exchange(FFlag, false);
|
||||
end;
|
||||
|
||||
function TMycDirty.GetIsSet: Boolean;
|
||||
function TMycFlag.GetIsSet: Boolean;
|
||||
begin
|
||||
// IsSet is true if the flag is dirty (FFlag = true).
|
||||
Result := FFlag;
|
||||
end;
|
||||
|
||||
function TMycDirty.GetState: TState;
|
||||
function TMycFlag.GetState: TState;
|
||||
begin
|
||||
// TMycDirty itself implements IMycState.
|
||||
// TMycFlag itself implements IMycState.
|
||||
Result := Self;
|
||||
end;
|
||||
|
||||
function TMycDirty.Notify: Boolean;
|
||||
function TMycFlag.Notify: Boolean;
|
||||
var
|
||||
wasPreviouslyClean: Boolean;
|
||||
begin
|
||||
@@ -316,7 +384,7 @@ begin
|
||||
FSubscribers.Notify(function(Subscriber: IMycSubscriber): Boolean begin Result := Subscriber.Notify; end);
|
||||
end;
|
||||
// The return value of this Notify (as an IMycSubscriber) indicates if this
|
||||
// TMycDirty instance itself would want more notifications if it were subscribed to something.
|
||||
// TMycFlag instance itself would want more notifications if it were subscribed to something.
|
||||
// Here, it reflects whether a state change to dirty occurred due to this call.
|
||||
Result := wasPreviouslyClean;
|
||||
finally
|
||||
@@ -324,12 +392,12 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
function TMycDirty.Subscribe(Subscriber: IMycSubscriber): Pointer;
|
||||
function TMycFlag.Subscribe(Subscriber: IMycSubscriber): Pointer;
|
||||
begin
|
||||
if not Assigned(Subscriber) then
|
||||
exit(nil);
|
||||
|
||||
// For TMycDirty, we always add the subscriber and then check if we need to notify immediately.
|
||||
// For TMycFlag, we always add the subscriber and then check if we need to notify immediately.
|
||||
// Ref counting for the subscriber interface is assumed to be handled by TMycNotifyList.Advise/Unadvise.
|
||||
// Or, if explicit management is needed like in TMycLatch, it should be added.
|
||||
// For consistency with TMycLatch, let's add AddRef/Release here too.
|
||||
@@ -346,7 +414,7 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMycDirty.Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag);
|
||||
procedure TMycFlag.Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag);
|
||||
begin
|
||||
if Tag = nil then
|
||||
exit;
|
||||
|
||||
@@ -203,11 +203,11 @@ end;
|
||||
|
||||
function TMycTaskFactory.CreateThread(const Proc: TProc): IMycState;
|
||||
var
|
||||
res: IMycLatch;
|
||||
res: IMycEvent;
|
||||
capturedProc: TProc;
|
||||
begin
|
||||
// Create a latch that will be signaled when the proc finishes
|
||||
res := TLatch.Construct(1); // Changed to use direct static call on TMycLatch
|
||||
res := TEvent.CreateLatch(1);
|
||||
|
||||
capturedProc := Proc; // Capture Proc for the anonymous method
|
||||
CreateAnonymousThread(
|
||||
@@ -309,7 +309,7 @@ begin
|
||||
raise ETaskException.Create('Task factory terminated');
|
||||
|
||||
if not Assigned(Job) then
|
||||
exit(TLatch.Null); // Already correct, uses direct static property on TMycLatch
|
||||
exit(TEvent.Null); // Already correct, uses direct static property on TMycLatch
|
||||
|
||||
// Create a pending job
|
||||
Result := TMycPendingJob.Create(Self, Job);
|
||||
|
||||
+16
-3
@@ -7,12 +7,19 @@ uses
|
||||
Myc.Signals;
|
||||
|
||||
type
|
||||
IMycMutable<T> = interface
|
||||
function GetChanged: TState;
|
||||
function GetValue: T;
|
||||
property Changed: TState read GetChanged;
|
||||
property Value: T read GetValue;
|
||||
end;
|
||||
|
||||
IMycLazy<T> = interface
|
||||
{$REGION 'property access'}
|
||||
function GetChanged: IMycState;
|
||||
function GetChanged: TState;
|
||||
{$ENDREGION}
|
||||
function Pop(out Res: T): Boolean;
|
||||
property Changed: IMycState read GetChanged;
|
||||
property Changed: TState read GetChanged;
|
||||
end;
|
||||
|
||||
TLazy<T> = record
|
||||
@@ -26,7 +33,8 @@ type
|
||||
public
|
||||
constructor Create(const ALazy: IMycLazy<T>);
|
||||
|
||||
class function Construct(const Changing: IMycState; const Proc: TFunc<T>): IMycLazy<T>; static;
|
||||
class function Construct(const Changing: IMycState; const Proc: TFunc<T>): IMycLazy<T>; overload; static;
|
||||
class function Construct(const Value: IMycLazy<T>): IMycLazy<T>; overload; static;
|
||||
|
||||
class operator Implicit(const A: IMycLazy<T>): TLazy<T>; overload;
|
||||
class operator Implicit(const A: TLazy<T>): IMycLazy<T>; overload;
|
||||
@@ -58,6 +66,11 @@ begin
|
||||
FNull := TMycNullLazy<T>.Create;
|
||||
end;
|
||||
|
||||
class function TLazy<T>.Construct(const Value: IMycLazy<T>): IMycLazy<T>;
|
||||
begin
|
||||
Result := TMycChainedLazy<T>.Create(Value);
|
||||
end;
|
||||
|
||||
function TLazy<T>.GetChanged: IMycState;
|
||||
begin
|
||||
Result := FLazy.Changed;
|
||||
|
||||
+110
-51
@@ -61,44 +61,44 @@ type
|
||||
property IsSet: Boolean read GetIsSet;
|
||||
end;
|
||||
|
||||
// IMycLatch is a specific type of flag that, once set, remains set (non-resettable).
|
||||
// It typically becomes set when an internal countdown reaches zero.
|
||||
IMycLatch = interface(IMycSubscriber)
|
||||
// Provides access to the IMycState interface of the flag.
|
||||
IMycEvent = interface(IMycSubscriber)
|
||||
function GetState: TState;
|
||||
property State: TState read GetState;
|
||||
end;
|
||||
|
||||
TLatch = record
|
||||
TEvent = record
|
||||
strict private
|
||||
class var
|
||||
FNull: IMycLatch;
|
||||
FNull: IMycEvent;
|
||||
class constructor ClassCreate;
|
||||
private
|
||||
FLatch: IMycLatch;
|
||||
FLatch: IMycEvent;
|
||||
function GetState: TState; inline;
|
||||
|
||||
public
|
||||
constructor Create(const ALatch: IMycLatch);
|
||||
class operator Initialize(out Dest: TLatch);
|
||||
class operator Implicit(const A: IMycLatch): TLatch; overload;
|
||||
class operator Implicit(const A: TLatch): IMycLatch; overload;
|
||||
constructor Create(const ALatch: IMycEvent);
|
||||
class operator Initialize(out Dest: TEvent);
|
||||
class operator Implicit(const A: IMycEvent): TEvent; overload;
|
||||
class operator Implicit(const A: TEvent): IMycEvent; overload;
|
||||
|
||||
class function Construct(Count: Integer): TLatch; static;
|
||||
// A notifier is an event that acts as a multi-cast event. Its state is always set and it forwards all notifications the all subscribers.
|
||||
class function CreateNotifier: TEvent; static;
|
||||
|
||||
class function Enqueue(var Gate: TLatch; Count: Integer = 1): TState; static;
|
||||
// A latch is a specific type of event that, once set, remains set (non-resettable).
|
||||
// It becomes set when the internal countdown reaches zero.
|
||||
class function CreateLatch(Count: Integer): TEvent; static;
|
||||
|
||||
class property Null: IMycLatch read FNull;
|
||||
class function Enqueue(var Gate: TEvent; Count: Integer = 1): TState; static;
|
||||
|
||||
class property Null: IMycEvent read FNull;
|
||||
|
||||
function Notify: Boolean; inline;
|
||||
|
||||
property State: TState read GetState;
|
||||
end;
|
||||
|
||||
// IMycDirty represents a resettable flag, typically indicating if a State is "dirty" (requiring attention) or "clean".
|
||||
// It inherits from IMycFlag, meaning it has a State, can be notified, and subscribed to.
|
||||
IMycDirty = interface(IMycSubscriber)
|
||||
// Provides access to the IMycState interface of the flag.
|
||||
// IMycFlag represents a resettable flag, typically indicating if a State is "dirty" (requiring attention) or "clean".
|
||||
IMycFlag = interface(IMycSubscriber)
|
||||
function GetState: TState;
|
||||
// Resets the flag to its "clean" (not set / not dirty) State.
|
||||
// Returns true if the flag was actually dirty before this reset, false otherwise.
|
||||
@@ -106,14 +106,29 @@ type
|
||||
property State: TState read GetState;
|
||||
end;
|
||||
|
||||
TDirty = record
|
||||
TFlag = record
|
||||
strict private
|
||||
class var
|
||||
FNull: IMycDirty;
|
||||
FNull: IMycFlag;
|
||||
class constructor ClassCreate;
|
||||
|
||||
private
|
||||
FDirty: IMycFlag;
|
||||
function GetState: TState; inline;
|
||||
|
||||
public
|
||||
class function Construct: IMycDirty; static;
|
||||
class property Null: IMycDirty read FNull;
|
||||
constructor Create(const ADirty: IMycFlag);
|
||||
class operator Implicit(const A: IMycFlag): TFlag; overload;
|
||||
class operator Implicit(const A: TFlag): IMycFlag; overload;
|
||||
class operator Initialize(out Dest: TFlag);
|
||||
|
||||
class function CreateFlag: IMycFlag; static;
|
||||
class property Null: IMycFlag read FNull;
|
||||
|
||||
function Notify: Boolean; inline;
|
||||
function Reset: Boolean;
|
||||
|
||||
property State: TState read GetState;
|
||||
end;
|
||||
|
||||
implementation
|
||||
@@ -161,9 +176,9 @@ end;
|
||||
|
||||
class function TState.All(const States: TArray<TState>): TState;
|
||||
var
|
||||
Latch: IMycLatch;
|
||||
Latch: IMycEvent;
|
||||
begin
|
||||
Latch := TLatch.Construct(Length(States));
|
||||
Latch := TEvent.CreateLatch(Length(States));
|
||||
for var i := 0 to High(States) do
|
||||
States[i].Subscribe(Latch);
|
||||
Result := Latch.State;
|
||||
@@ -171,7 +186,7 @@ end;
|
||||
|
||||
class function TState.Any(const States: TArray<TState>): TState;
|
||||
var
|
||||
Latch: IMycLatch;
|
||||
Latch: IMycEvent;
|
||||
begin
|
||||
if Length(States) = 0 then
|
||||
begin
|
||||
@@ -179,7 +194,7 @@ begin
|
||||
end
|
||||
else
|
||||
begin
|
||||
Latch := TLatch.Construct(1);
|
||||
Latch := TEvent.CreateLatch(1);
|
||||
for var i := 0 to High(States) do
|
||||
States[i].Subscribe(Latch);
|
||||
Result := Latch.State;
|
||||
@@ -211,24 +226,75 @@ begin
|
||||
Result.Create(A);
|
||||
end;
|
||||
|
||||
{ TLatch }
|
||||
{ TFlag }
|
||||
|
||||
class constructor TLatch.ClassCreate;
|
||||
class constructor TFlag.ClassCreate;
|
||||
begin
|
||||
// Create a singleton null latch instance that is initially (and always) set.
|
||||
FNull := TMycNullLatch.Create; // Calls the instance constructor
|
||||
FNull := TMycNullFlag.Create;
|
||||
end;
|
||||
|
||||
{ TLatch }
|
||||
{ TFlag }
|
||||
|
||||
constructor TLatch.Create(const ALatch: IMycLatch);
|
||||
constructor TFlag.Create(const ADirty: IMycFlag);
|
||||
begin
|
||||
FDirty := ADirty;
|
||||
if not Assigned(FDirty) then
|
||||
FDirty := FNull;
|
||||
end;
|
||||
|
||||
class function TFlag.CreateFlag: IMycFlag;
|
||||
begin
|
||||
Result := TMycFlag.Create;
|
||||
end;
|
||||
|
||||
function TFlag.GetState: TState;
|
||||
begin
|
||||
Result := FDirty.State;
|
||||
end;
|
||||
|
||||
function TFlag.Notify: Boolean;
|
||||
begin
|
||||
Result := FDirty.Notify;
|
||||
end;
|
||||
|
||||
function TFlag.Reset: Boolean;
|
||||
begin
|
||||
Result := FDirty.Reset;
|
||||
end;
|
||||
|
||||
class operator TFlag.Implicit(const A: IMycFlag): TFlag;
|
||||
begin
|
||||
Result.Create(A);
|
||||
end;
|
||||
|
||||
class operator TFlag.Implicit(const A: TFlag): IMycFlag;
|
||||
begin
|
||||
Result := A.FDirty;
|
||||
end;
|
||||
|
||||
class operator TFlag.Initialize(out Dest: TFlag);
|
||||
begin
|
||||
Dest.FDirty := FNull;
|
||||
end;
|
||||
|
||||
{ TEvent }
|
||||
|
||||
class constructor TEvent.ClassCreate;
|
||||
begin
|
||||
// Create a singleton null latch instance that is initially (and always) set.
|
||||
FNull := TMycNullEvent.Create;
|
||||
end;
|
||||
|
||||
{ TEvent }
|
||||
|
||||
constructor TEvent.Create(const ALatch: IMycEvent);
|
||||
begin
|
||||
FLatch := ALatch;
|
||||
if not Assigned(FLatch) then
|
||||
FLatch := FNull;
|
||||
end;
|
||||
|
||||
class function TLatch.Construct(Count: Integer): TLatch;
|
||||
class function TEvent.CreateLatch(Count: Integer): TEvent;
|
||||
begin
|
||||
if Count > 0 then
|
||||
Result := TMycLatch.Create(Count)
|
||||
@@ -236,7 +302,12 @@ begin
|
||||
Result := FNull;
|
||||
end;
|
||||
|
||||
class function TLatch.Enqueue(var Gate: TLatch; Count: Integer = 1): TState;
|
||||
class function TEvent.CreateNotifier: TEvent;
|
||||
begin
|
||||
Result := TMycNotifyEvent.Create;
|
||||
end;
|
||||
|
||||
class function TEvent.Enqueue(var Gate: TEvent; Count: Integer = 1): TState;
|
||||
begin
|
||||
var gateState := Gate.State;
|
||||
Gate := TMycLatch.Create(Count);
|
||||
@@ -244,41 +315,29 @@ begin
|
||||
exit(Gate.State);
|
||||
end;
|
||||
|
||||
function TLatch.GetState: TState;
|
||||
function TEvent.GetState: TState;
|
||||
begin
|
||||
Result := FLatch.State;
|
||||
end;
|
||||
|
||||
function TLatch.Notify: Boolean;
|
||||
function TEvent.Notify: Boolean;
|
||||
begin
|
||||
Result := FLatch.Notify;
|
||||
end;
|
||||
|
||||
class operator TLatch.Implicit(const A: IMycLatch): TLatch;
|
||||
class operator TEvent.Implicit(const A: IMycEvent): TEvent;
|
||||
begin
|
||||
Result.Create(A);
|
||||
end;
|
||||
|
||||
class operator TLatch.Implicit(const A: TLatch): IMycLatch;
|
||||
class operator TEvent.Implicit(const A: TEvent): IMycEvent;
|
||||
begin
|
||||
Result := A.FLatch;
|
||||
end;
|
||||
|
||||
class operator TLatch.Initialize(out Dest: TLatch);
|
||||
class operator TEvent.Initialize(out Dest: TEvent);
|
||||
begin
|
||||
Dest.FLatch := FNull;
|
||||
end;
|
||||
|
||||
{ TDirty }
|
||||
|
||||
class constructor TDirty.ClassCreate;
|
||||
begin
|
||||
FNull := TMycNullDirty.Create;
|
||||
end;
|
||||
|
||||
class function TDirty.Construct: IMycDirty;
|
||||
begin
|
||||
Result := TMycDirty.Create;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
+22
-22
@@ -162,9 +162,9 @@ procedure TTestMycCoreLazy.TestFuncLazy_GetChanged_IsAlwaysTrueAfterCreation;
|
||||
var
|
||||
funcLazy: IMycLazy<Integer>;
|
||||
changedState: IMycState;
|
||||
sourceDirty: IMycDirty;
|
||||
sourceDirty: IMycFlag;
|
||||
begin
|
||||
sourceDirty := TDirty.Construct;
|
||||
sourceDirty := TFlag.CreateFlag;
|
||||
sourceDirty.Reset;
|
||||
funcLazy := TMycFuncLazy<Integer>.Create(sourceDirty.State, function: Integer begin Result := 10; end);
|
||||
Assert.IsNotNull(funcLazy, 'TMycFuncLazy<Integer> instance should not be nil');
|
||||
@@ -178,11 +178,11 @@ var
|
||||
funcLazy: IMycLazy<Integer>;
|
||||
value: Integer;
|
||||
result: Boolean;
|
||||
sourceDirty: IMycDirty;
|
||||
sourceDirty: IMycFlag;
|
||||
procExecuted: Boolean;
|
||||
expectedValue: Integer;
|
||||
begin
|
||||
sourceDirty := TDirty.Construct;
|
||||
sourceDirty := TFlag.CreateFlag;
|
||||
sourceDirty.Reset;
|
||||
procExecuted := False;
|
||||
expectedValue := 50;
|
||||
@@ -211,9 +211,9 @@ var
|
||||
value: Integer;
|
||||
preCallValue: Integer;
|
||||
result: Boolean;
|
||||
sourceDirty: IMycDirty;
|
||||
sourceDirty: IMycFlag;
|
||||
begin
|
||||
sourceDirty := TDirty.Construct;
|
||||
sourceDirty := TFlag.CreateFlag;
|
||||
sourceDirty.Reset;
|
||||
funcLazy := TMycFuncLazy<Integer>.Create(sourceDirty.State, nil);
|
||||
Assert.IsNotNull(funcLazy, 'TMycFuncLazy<Integer> instance should not be nil');
|
||||
@@ -235,10 +235,10 @@ end;
|
||||
procedure TTestMycCoreLazy.TestFuncLazy_AfterFirstPop_IfNoSourceChange_GetChangedIsFalse;
|
||||
var
|
||||
funcLazy: IMycLazy<Integer>;
|
||||
sourceDirty: IMycDirty;
|
||||
sourceDirty: IMycFlag;
|
||||
initialProcValue: Integer;
|
||||
begin
|
||||
sourceDirty := TDirty.Construct;
|
||||
sourceDirty := TFlag.CreateFlag;
|
||||
sourceDirty.Reset;
|
||||
initialProcValue := 33;
|
||||
funcLazy := TMycFuncLazy<Integer>.Create(sourceDirty.State, function: Integer begin Result := initialProcValue; end);
|
||||
@@ -251,11 +251,11 @@ var
|
||||
funcLazy: IMycLazy<Integer>;
|
||||
value: Integer;
|
||||
result: Boolean;
|
||||
sourceDirty: IMycDirty;
|
||||
sourceDirty: IMycFlag;
|
||||
initialProcValue: Integer;
|
||||
procExecutedCount: Integer;
|
||||
begin
|
||||
sourceDirty := TDirty.Construct;
|
||||
sourceDirty := TFlag.CreateFlag;
|
||||
sourceDirty.Reset;
|
||||
initialProcValue := 44;
|
||||
procExecutedCount := 0;
|
||||
@@ -279,10 +279,10 @@ procedure TTestMycCoreLazy.TestFuncLazy_AfterSourceChange_GetChanged_IsSet;
|
||||
var
|
||||
funcLazy: IMycLazy<Integer>;
|
||||
changedState: IMycState;
|
||||
sourceDirty: IMycDirty;
|
||||
sourceDirty: IMycFlag;
|
||||
initialProcValue: Integer;
|
||||
begin
|
||||
sourceDirty := TDirty.Construct;
|
||||
sourceDirty := TFlag.CreateFlag;
|
||||
sourceDirty.Reset;
|
||||
initialProcValue := 20;
|
||||
funcLazy := TMycFuncLazy<Integer>.Create(sourceDirty.State, function: Integer begin Result := initialProcValue; end);
|
||||
@@ -297,11 +297,11 @@ var
|
||||
funcLazy: IMycLazy<Integer>;
|
||||
value: Integer;
|
||||
result: Boolean;
|
||||
sourceDirty: IMycDirty;
|
||||
sourceDirty: IMycFlag;
|
||||
procCallCount: Integer;
|
||||
currentExpectedValue: Integer;
|
||||
begin
|
||||
sourceDirty := TDirty.Construct;
|
||||
sourceDirty := TFlag.CreateFlag;
|
||||
sourceDirty.Reset;
|
||||
procCallCount := 0;
|
||||
funcLazy :=
|
||||
@@ -335,10 +335,10 @@ var
|
||||
funcLazy: IMycLazy<Integer>;
|
||||
value: Integer;
|
||||
resultPop1, resultPop2: Boolean;
|
||||
sourceDirty: IMycDirty;
|
||||
sourceDirty: IMycFlag;
|
||||
procCallCount: Integer;
|
||||
begin
|
||||
sourceDirty := TDirty.Construct;
|
||||
sourceDirty := TFlag.CreateFlag;
|
||||
sourceDirty.Reset;
|
||||
procCallCount := 0;
|
||||
funcLazy :=
|
||||
@@ -367,11 +367,11 @@ var
|
||||
funcLazy: IMycLazy<Integer>;
|
||||
value: Integer;
|
||||
result: Boolean;
|
||||
sourceDirty: IMycDirty;
|
||||
sourceDirty: IMycFlag;
|
||||
procCallCount: Integer;
|
||||
initialValue, firstSourceChangeValue: Integer;
|
||||
begin
|
||||
sourceDirty := TDirty.Construct;
|
||||
sourceDirty := TFlag.CreateFlag;
|
||||
sourceDirty.Reset; // sourceDirty.IsSet is FALSE
|
||||
procCallCount := 0;
|
||||
initialValue := 51;
|
||||
@@ -427,10 +427,10 @@ end;
|
||||
procedure TTestMycCoreLazy.TestFuncLazy_Destroy_UnsubscribesFromSource;
|
||||
var
|
||||
funcLazyObj: TMycFuncLazy<Integer>;
|
||||
sourceDirty: IMycDirty;
|
||||
sourceDirty: IMycFlag;
|
||||
tempVal: Integer;
|
||||
begin
|
||||
sourceDirty := TDirty.Construct;
|
||||
sourceDirty := TFlag.CreateFlag;
|
||||
sourceDirty.Reset;
|
||||
funcLazyObj := TMycFuncLazy<Integer>.Create(sourceDirty.State, function: Integer begin Result := 1; end);
|
||||
Assert.IsTrue(funcLazyObj.Pop(tempVal), 'Initial Pop should succeed');
|
||||
@@ -448,11 +448,11 @@ var
|
||||
funcLazy: IMycLazy<Integer>;
|
||||
value: Integer;
|
||||
result: Boolean;
|
||||
sourceDirty: IMycDirty;
|
||||
sourceDirty: IMycFlag;
|
||||
expectedValue: Integer;
|
||||
procExecuted: Boolean;
|
||||
begin
|
||||
sourceDirty := TDirty.Construct;
|
||||
sourceDirty := TFlag.CreateFlag;
|
||||
Assert.IsTrue(sourceDirty.State.IsSet, 'SourceDirty should be initially set for this test scenario');
|
||||
expectedValue := 70;
|
||||
procExecuted := False;
|
||||
|
||||
@@ -12,7 +12,7 @@ type
|
||||
[TestFixture]
|
||||
TTestMyLazy = class(TObject)
|
||||
private
|
||||
FChangingSignal: IMycDirty; // Used as the 'Changing' state for functional lazy objects
|
||||
FChangingSignal: IMycFlag; // Used as the 'Changing' state for functional lazy objects
|
||||
|
||||
// Helper to consume the initial pop, which is always expected to succeed
|
||||
// for a TLazy wrapping a functional lazy object due to "Changed.IsSet initially true" design.
|
||||
@@ -76,7 +76,7 @@ begin
|
||||
// Create a common signal source for tests that need it.
|
||||
// TState.CreateDirty is from Myc.Signals.pas (interface part)
|
||||
// Its implementation might rely on Myc.Core.Signals, but that's an indirect usage.
|
||||
FChangingSignal := TDirty.Construct;
|
||||
FChangingSignal := TFlag.CreateFlag;
|
||||
FChangingSignal.Reset; // Start with a clean (not set) signal for predictable test starts
|
||||
end;
|
||||
|
||||
@@ -306,9 +306,9 @@ end;
|
||||
procedure TTestMyLazy.TestConstruct_Destruction_UnsubscribesAndNoCrashOnSourceNotify;
|
||||
var
|
||||
lazyIntf: IMycLazy<Integer>;
|
||||
localChangingSignal: IMycDirty; // Use a local signal for this test to control its lifetime
|
||||
localChangingSignal: IMycFlag; // Use a local signal for this test to control its lifetime
|
||||
begin
|
||||
localChangingSignal := TDirty.Construct;
|
||||
localChangingSignal := TFlag.CreateFlag;
|
||||
localChangingSignal.Reset;
|
||||
|
||||
lazyIntf := TLazy<Integer>.Construct(localChangingSignal.State, function: Integer begin Result := 1; end);
|
||||
|
||||
@@ -0,0 +1,512 @@
|
||||
unit Myc.Test.Signals.Latch;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
DUnitX.TestFramework,
|
||||
System.SysUtils,
|
||||
Myc.Signals; // Unit under test, using TMycLatch static members
|
||||
|
||||
type
|
||||
// Helper class to mock a subscriber.
|
||||
TMockSubscriber = class(TInterfacedObject, IMycSubscriber)
|
||||
public
|
||||
NotifyCount: Integer;
|
||||
NotifyShouldReturn: Boolean; // Determines what this mock's Notify method returns
|
||||
WasCalled: Boolean;
|
||||
|
||||
constructor Create(AShouldReturn: Boolean = True); // Parameter name AShouldReturn for clarity
|
||||
function Notify: Boolean; // IMycSubscriber implementation.
|
||||
end;
|
||||
|
||||
[TestFixture]
|
||||
TTestMycLatch = class(TObject)
|
||||
public
|
||||
[Setup]
|
||||
procedure Setup;
|
||||
[TearDown]
|
||||
procedure TearDown;
|
||||
|
||||
// --- Category 1: Basic Counter and State Functionality (Parameterized) ---
|
||||
[TestCase('InitialPositive', '1,False')]
|
||||
[TestCase('InitialZero_ReturnsNullLatch', '0,True')]
|
||||
[TestCase('InitialNegative_ReturnsNullLatch', '-1,True')]
|
||||
[TestCase('InitialLargePositive', '5,False')]
|
||||
procedure TestCreate_InitialState(InitialCount: Integer; ExpectedIsSet: Boolean);
|
||||
|
||||
[TestCase('DecrementPositiveToZero', '1,False,True')] // InitialCount, ExpectedReturnFromLatchNotify, ExpectedIsSetAfterNotify
|
||||
[TestCase('DecrementPositiveToPositive', '2,True,False')]
|
||||
[TestCase('DecrementZeroToNegative', '0,False,True')] // Latch.Notify on count 0 returns false (0 > 0 is false)
|
||||
[TestCase('DecrementNegativeToNegative', '-1,False,True')] // Latch.Notify on count -1 returns false (-1 > 0 is false)
|
||||
procedure TestNotify_ReturnValueAndState_AfterOneNotify(InitialCount: Integer; ExpectedReturn: Boolean; ExpectedIsSet: Boolean);
|
||||
|
||||
[Test]
|
||||
procedure TestNotify_DecrementsCounter_StateChanges;
|
||||
[Test]
|
||||
procedure TestNotify_MultipleNotifies_CounterBecomesNegativeAndStaysSet;
|
||||
|
||||
// --- Category 2: Notification to Subscribers ---
|
||||
[Test]
|
||||
procedure TestSubscriber_Single_IsNotifiedWhenLatchSet;
|
||||
[Test]
|
||||
procedure TestSubscriber_Multiple_AreNotifiedWhenLatchSet;
|
||||
[Test]
|
||||
procedure TestSubscriber_NotNotified_BeforeLatchSet;
|
||||
[Test]
|
||||
procedure TestSubscriber_NotifiedOnlyOnce_AfterLatchSetAndUnadvised; // Clarified name
|
||||
[Test]
|
||||
procedure TestSubscribe_ToAlreadySetLatch_NotifiesImmediately;
|
||||
|
||||
// --- Category 3: Chaining and Cascading Latches ---
|
||||
[Test]
|
||||
procedure TestChaining_A_Notifies_B;
|
||||
[Test]
|
||||
procedure TestCascade_A_Notifies_B_Notifies_C;
|
||||
[Test]
|
||||
procedure TestChaining_B_NeedsMultipleNotifiesFromA_WithSubscriberOnB;
|
||||
|
||||
// --- Category 4: Special Cases ---
|
||||
[Test]
|
||||
procedure TestInitiallySetLatch_NotifiesNewSubscriberImmediately; // Using CreateLatch(0)
|
||||
|
||||
[Test]
|
||||
procedure TestUnsubscribe_SubscriberNotNotified;
|
||||
|
||||
// --- Category 5: Counter Integrity / State for Later Subscribers ---
|
||||
[Test]
|
||||
procedure TestMultipleTriggersNoSubscriber_StateCorrectForLaterSubscriber;
|
||||
|
||||
// --- Category 6: UnadviseAll Behavior on Latch Set ---
|
||||
[Test]
|
||||
procedure TestLatchSet_ClearsSubscribers_NoFurtherNotification;
|
||||
[Test]
|
||||
procedure TestLatchSet_SubscriptionFinalize_IsSafePostUnadviseAll;
|
||||
[Test]
|
||||
procedure TestLatchSet_NewSubscriberToSetLatch_NotifiedImmediatelyButNotPersistedForLatchNotify;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TMockSubscriber }
|
||||
|
||||
constructor TMockSubscriber.Create(AShouldReturn: Boolean = True);
|
||||
begin
|
||||
inherited Create;
|
||||
NotifyCount := 0;
|
||||
NotifyShouldReturn := AShouldReturn;
|
||||
WasCalled := False;
|
||||
end;
|
||||
|
||||
function TMockSubscriber.Notify: Boolean;
|
||||
begin
|
||||
Inc(NotifyCount);
|
||||
WasCalled := True;
|
||||
Result := NotifyShouldReturn; // This mock subscriber returns what it's configured to.
|
||||
end;
|
||||
|
||||
{ TTestMycLatch }
|
||||
|
||||
procedure TTestMycLatch.Setup;
|
||||
begin
|
||||
// Per-test setup code can go here (if any).
|
||||
end;
|
||||
|
||||
procedure TTestMycLatch.TearDown;
|
||||
begin
|
||||
// Per-test teardown code can go here (if any).
|
||||
end;
|
||||
|
||||
// --- Category 1: Basic Counter and State Functionality (Parameterized) ---
|
||||
|
||||
procedure TTestMycLatch.TestCreate_InitialState(InitialCount: Integer; ExpectedIsSet: Boolean);
|
||||
var
|
||||
latch: IMycEvent;
|
||||
begin
|
||||
// TMycLatch.CreateLatch returns TMycLatch.Null if InitialCount <= 0
|
||||
latch := TEvent.CreateLatch(InitialCount);
|
||||
Assert.AreEqual(ExpectedIsSet, latch.State.IsSet, 'Latch initial IsSet state mismatch for count ' + IntToStr(InitialCount) + '.');
|
||||
end;
|
||||
|
||||
procedure TTestMycLatch.TestNotify_ReturnValueAndState_AfterOneNotify(
|
||||
InitialCount: Integer;
|
||||
ExpectedReturn: Boolean;
|
||||
ExpectedIsSet: Boolean
|
||||
);
|
||||
var
|
||||
latch: IMycEvent;
|
||||
returnedValueFromNotify: Boolean;
|
||||
begin
|
||||
latch := TEvent.CreateLatch(InitialCount);
|
||||
returnedValueFromNotify := latch.Notify; // This is IMycEvent (as IMycSubscriber).Notify
|
||||
|
||||
Assert.AreEqual(
|
||||
ExpectedReturn,
|
||||
returnedValueFromNotify,
|
||||
'Unexpected return value from Latch.Notify call for initial count ' + IntToStr(InitialCount) + '.'
|
||||
);
|
||||
Assert.AreEqual(
|
||||
ExpectedIsSet,
|
||||
latch.State.IsSet,
|
||||
'Unexpected IsSet state after Latch.Notify call for initial count ' + IntToStr(InitialCount) + '.'
|
||||
);
|
||||
end;
|
||||
|
||||
procedure TTestMycLatch.TestNotify_DecrementsCounter_StateChanges;
|
||||
var
|
||||
latch: IMycEvent;
|
||||
begin
|
||||
latch := TEvent.CreateLatch(1);
|
||||
Assert.IsFalse(latch.State.IsSet, 'Latch should initially not be set (Count=1).');
|
||||
latch.Notify; // Count becomes 0
|
||||
Assert.IsTrue(latch.State.IsSet, 'Latch should be set after Notify (Count became 0).');
|
||||
end;
|
||||
|
||||
procedure TTestMycLatch.TestNotify_MultipleNotifies_CounterBecomesNegativeAndStaysSet;
|
||||
var
|
||||
latch: IMycEvent;
|
||||
begin
|
||||
latch := TEvent.CreateLatch(1);
|
||||
latch.Notify; // Count becomes 0, IsSet = True
|
||||
Assert.IsTrue(latch.State.IsSet, 'Latch should be set after first Notify.');
|
||||
latch.Notify; // Count becomes -1, IsSet should remain True
|
||||
Assert.IsTrue(latch.State.IsSet, 'Latch should remain set after second Notify.');
|
||||
end;
|
||||
|
||||
// --- Category 2: Notification to Subscribers ---
|
||||
|
||||
procedure TTestMycLatch.TestSubscriber_Single_IsNotifiedWhenLatchSet;
|
||||
var
|
||||
latch: IMycEvent;
|
||||
mockSub: TMockSubscriber;
|
||||
subscription: TState.TSubscription;
|
||||
begin
|
||||
latch := TEvent.CreateLatch(1);
|
||||
mockSub := TMockSubscriber.Create;
|
||||
subscription := latch.State.Subscribe(mockSub); // Subscribing to the Latch's state
|
||||
|
||||
Assert.IsFalse(latch.State.IsSet, 'Latch should initially not be set.');
|
||||
Assert.AreEqual(0, mockSub.NotifyCount, 'MockSub should not have been notified yet (before latch set).');
|
||||
|
||||
latch.Notify; // Latch becomes set, notifies subscribers
|
||||
|
||||
Assert.IsTrue(latch.State.IsSet, 'Latch should be set after its Notify is called.');
|
||||
Assert.AreEqual(1, mockSub.NotifyCount, 'MockSub should have been notified once when latch becomes set.');
|
||||
end;
|
||||
|
||||
procedure TTestMycLatch.TestSubscriber_Multiple_AreNotifiedWhenLatchSet;
|
||||
var
|
||||
latch: IMycEvent;
|
||||
mockSub1, mockSub2, mockSub3: TMockSubscriber;
|
||||
sub1, sub2, sub3: TState.TSubscription; // Keep subscriptions in scope
|
||||
begin
|
||||
latch := TEvent.CreateLatch(1);
|
||||
mockSub1 := TMockSubscriber.Create;
|
||||
mockSub2 := TMockSubscriber.Create;
|
||||
mockSub3 := TMockSubscriber.Create;
|
||||
|
||||
sub1 := latch.State.Subscribe(mockSub1);
|
||||
sub2 := latch.State.Subscribe(mockSub2);
|
||||
sub3 := latch.State.Subscribe(mockSub3);
|
||||
|
||||
latch.Notify; // Latch becomes set
|
||||
|
||||
Assert.AreEqual(1, mockSub1.NotifyCount, 'MockSub1 notification count mismatch.');
|
||||
Assert.AreEqual(1, mockSub2.NotifyCount, 'MockSub2 notification count mismatch.');
|
||||
Assert.AreEqual(1, mockSub3.NotifyCount, 'MockSub3 notification count mismatch.');
|
||||
end;
|
||||
|
||||
procedure TTestMycLatch.TestSubscriber_NotNotified_BeforeLatchSet;
|
||||
var
|
||||
latch: IMycEvent;
|
||||
mockSub: TMockSubscriber;
|
||||
subscription: TState.TSubscription;
|
||||
begin
|
||||
latch := TEvent.CreateLatch(2); // Count = 2
|
||||
mockSub := TMockSubscriber.Create;
|
||||
subscription := latch.State.Subscribe(mockSub);
|
||||
|
||||
Assert.AreEqual(0, mockSub.NotifyCount, 'MockSub should not be notified upon subscription if latch is not set.');
|
||||
latch.Notify; // Count becomes 1, still not set
|
||||
Assert.AreEqual(0, mockSub.NotifyCount, 'MockSub should still not be notified as latch is not yet set (Count=1).');
|
||||
end;
|
||||
|
||||
procedure TTestMycLatch.TestSubscriber_NotifiedOnlyOnce_AfterLatchSetAndUnadvised;
|
||||
var
|
||||
latch: IMycEvent;
|
||||
mockSub: TMockSubscriber;
|
||||
subscription: TState.TSubscription;
|
||||
begin
|
||||
latch := TEvent.CreateLatch(1);
|
||||
mockSub := TMockSubscriber.Create;
|
||||
subscription := latch.State.Subscribe(mockSub);
|
||||
|
||||
latch.Notify; // Sets latch, notifies MockSub. Subscribers are then unadvised by UnadviseAll.
|
||||
Assert.AreEqual(1, mockSub.NotifyCount, 'MockSub notify count should be 1 after latch set.');
|
||||
|
||||
latch.Notify; // Call Notify on latch again (FCount becomes more negative).
|
||||
Assert.AreEqual(1, mockSub.NotifyCount, 'MockSub notify count should remain 1 (not notified again as it was unadvised).');
|
||||
end;
|
||||
|
||||
procedure TTestMycLatch.TestSubscribe_ToAlreadySetLatch_NotifiesImmediately;
|
||||
var
|
||||
latch: IMycEvent;
|
||||
mockSub1, mockSub2: TMockSubscriber;
|
||||
subscription1, subscription2: TState.TSubscription;
|
||||
begin
|
||||
latch := TEvent.CreateLatch(1); // Create with count 1
|
||||
mockSub1 := TMockSubscriber.Create;
|
||||
subscription1 := latch.State.Subscribe(mockSub1); // Subscribe before set
|
||||
|
||||
latch.Notify; // Latch becomes set. mockSub1 is notified.
|
||||
|
||||
Assert.IsTrue(latch.State.IsSet, 'Latch should be set after initial trigger.');
|
||||
Assert.AreEqual(1, mockSub1.NotifyCount, 'MockSub1 should have been notified once.');
|
||||
|
||||
// Attempt to subscribe mockSub2 after the latch is already set.
|
||||
mockSub2 := TMockSubscriber.Create;
|
||||
subscription2 := latch.State.Subscribe(mockSub2); // TMycLatch.Subscribe (via TMycAbstractFlag) notifies immediately if already set.
|
||||
|
||||
Assert.AreEqual(1, mockSub2.NotifyCount, 'MockSub2 should be notified immediately upon subscribing to an already set latch.');
|
||||
|
||||
latch.Notify; // Call Notify on latch again (FCount becomes more negative).
|
||||
Assert.AreEqual(1, mockSub1.NotifyCount, 'MockSub1 notify count should remain 1 (was unadvised).');
|
||||
Assert.AreEqual(1, mockSub2.NotifyCount, 'MockSub2 notify count should remain 1 (was only notified on subscribe, not added to list).');
|
||||
end;
|
||||
|
||||
// --- Category 3: Chaining and Cascading Latches ---
|
||||
|
||||
procedure TTestMycLatch.TestChaining_A_Notifies_B;
|
||||
var
|
||||
latchA, latchB: IMycEvent;
|
||||
mockSubForB: TMockSubscriber;
|
||||
subHandle_B_listens_A, subHandle_Mock_listens_B: TState.TSubscription;
|
||||
begin
|
||||
latchA := TEvent.CreateLatch(1);
|
||||
latchB := TEvent.CreateLatch(1); // latchB is an IMycSubscriber
|
||||
mockSubForB := TMockSubscriber.Create;
|
||||
|
||||
subHandle_Mock_listens_B := latchB.State.Subscribe(mockSubForB);
|
||||
// LatchA's state is an IMycSignal. LatchB (as IMycSubscriber) subscribes to it.
|
||||
subHandle_B_listens_A := latchA.State.Subscribe(latchB);
|
||||
|
||||
Assert.IsFalse(latchA.State.IsSet, 'LatchA initially not set.');
|
||||
Assert.IsFalse(latchB.State.IsSet, 'LatchB initially not set.');
|
||||
Assert.AreEqual(0, mockSubForB.NotifyCount, 'MockSubForB initially not notified.');
|
||||
|
||||
latchA.Notify; // Call Notify on LatchA (as IMycSubscriber)
|
||||
|
||||
Assert.IsTrue(latchA.State.IsSet, 'LatchA should be set after notify.');
|
||||
Assert.IsTrue(latchB.State.IsSet, 'LatchB should be set after being notified by LatchA.');
|
||||
Assert.AreEqual(1, mockSubForB.NotifyCount, 'MockSubForB should have been notified by LatchB.');
|
||||
end;
|
||||
|
||||
procedure TTestMycLatch.TestCascade_A_Notifies_B_Notifies_C;
|
||||
var
|
||||
latchA, latchB, latchC: IMycEvent;
|
||||
mockSubForC: TMockSubscriber;
|
||||
sub_Mock_C, sub_C_B, sub_B_A: TState.TSubscription;
|
||||
begin
|
||||
latchA := TEvent.CreateLatch(1);
|
||||
latchB := TEvent.CreateLatch(1);
|
||||
latchC := TEvent.CreateLatch(1);
|
||||
mockSubForC := TMockSubscriber.Create;
|
||||
|
||||
sub_Mock_C := latchC.State.Subscribe(mockSubForC);
|
||||
sub_C_B := latchB.State.Subscribe(latchC); // LatchC subscribes to LatchB's state
|
||||
sub_B_A := latchA.State.Subscribe(latchB); // LatchB subscribes to LatchA's state
|
||||
|
||||
latchA.Notify; // Call Notify on LatchA
|
||||
|
||||
Assert.IsTrue(latchA.State.IsSet, 'LatchA state mismatch in cascade.');
|
||||
Assert.IsTrue(latchB.State.IsSet, 'LatchB state mismatch in cascade.');
|
||||
Assert.IsTrue(latchC.State.IsSet, 'LatchC state mismatch in cascade.');
|
||||
Assert.AreEqual(1, mockSubForC.NotifyCount, 'MockSubForC notification count mismatch in cascade.');
|
||||
end;
|
||||
|
||||
procedure TTestMycLatch.TestChaining_B_NeedsMultipleNotifiesFromA_WithSubscriberOnB;
|
||||
var
|
||||
latchA, latchB: IMycEvent;
|
||||
mockSubForB: TMockSubscriber;
|
||||
sub_Mock_B, sub_B_A: TState.TSubscription;
|
||||
begin
|
||||
// LatchA needs 2 notifies to become set. LatchB needs 1 notify to become set.
|
||||
// LatchB subscribes to LatchA. So LatchB will be notified once LatchA becomes set.
|
||||
latchA := TEvent.CreateLatch(2);
|
||||
latchB := TEvent.CreateLatch(1);
|
||||
mockSubForB := TMockSubscriber.Create;
|
||||
|
||||
sub_Mock_B := latchB.State.Subscribe(mockSubForB);
|
||||
sub_B_A := latchA.State.Subscribe(latchB);
|
||||
|
||||
latchA.Notify; // First notify for LatchA (count becomes 1)
|
||||
Assert.IsFalse(latchA.State.IsSet, 'LatchA should not be set after first notify of two.');
|
||||
Assert.IsFalse(latchB.State.IsSet, 'LatchB should not have been triggered yet by LatchA.');
|
||||
Assert.AreEqual(0, mockSubForB.NotifyCount, 'MockSubForB should not have been notified yet.');
|
||||
|
||||
latchA.Notify; // Second notify for LatchA (count becomes 0, LatchA becomes set)
|
||||
Assert.IsTrue(latchA.State.IsSet, 'LatchA should be set after second notify.');
|
||||
// When LatchA becomes set, it notifies its subscribers (LatchB). LatchB.Notify is called.
|
||||
Assert.IsTrue(latchB.State.IsSet, 'LatchB should now be set by LatchA.');
|
||||
Assert.AreEqual(1, mockSubForB.NotifyCount, 'MockSubForB should have been notified by LatchB.');
|
||||
end;
|
||||
|
||||
// --- Category 4: Special Cases ---
|
||||
|
||||
procedure TTestMycLatch.TestInitiallySetLatch_NotifiesNewSubscriberImmediately;
|
||||
var
|
||||
latch: IMycEvent; // Will be TMycLatch.Null
|
||||
mockSub: TMockSubscriber;
|
||||
subscription: TState.TSubscription;
|
||||
begin
|
||||
latch := TEvent.CreateLatch(0); // Returns TMycLatch.Null which is set.
|
||||
mockSub := TMockSubscriber.Create;
|
||||
|
||||
subscription := latch.State.Subscribe(mockSub); // TMycLatch.Subscribe notifies immediately if already set.
|
||||
|
||||
Assert.IsTrue(latch.State.IsSet, 'Latch IsSet property mismatch (should be true for Null latch).');
|
||||
Assert.AreEqual(1, mockSub.NotifyCount, 'MockSub should be notified immediately upon subscribing to an initially set latch.');
|
||||
end;
|
||||
|
||||
procedure TTestMycLatch.TestUnsubscribe_SubscriberNotNotified;
|
||||
var
|
||||
latch: IMycEvent;
|
||||
mockSub: TMockSubscriber;
|
||||
subscription: TState.TSubscription;
|
||||
begin
|
||||
latch := TEvent.CreateLatch(1);
|
||||
mockSub := TMockSubscriber.Create;
|
||||
|
||||
subscription := latch.State.Subscribe(mockSub);
|
||||
subscription.Unsubscribe; // Explicitly call Unsubscribe on the record
|
||||
|
||||
latch.Notify; // Latch becomes set
|
||||
|
||||
Assert.AreEqual(0, mockSub.NotifyCount, 'Unsubscribed MockSub should not be notified.');
|
||||
end;
|
||||
|
||||
// --- Category 5: Counter Integrity / State for Later Subscribers ---
|
||||
|
||||
procedure TTestMycLatch.TestMultipleTriggersNoSubscriber_StateCorrectForLaterSubscriber;
|
||||
var
|
||||
latch: IMycEvent;
|
||||
mockSub: TMockSubscriber;
|
||||
subscription: TState.TSubscription;
|
||||
begin
|
||||
latch := TEvent.CreateLatch(3);
|
||||
|
||||
latch.Notify; // Count -> 2
|
||||
latch.Notify; // Count -> 1
|
||||
Assert.IsFalse(latch.State.IsSet, 'Latch should not be set yet after partial notifies.');
|
||||
|
||||
mockSub := TMockSubscriber.Create;
|
||||
subscription := latch.State.Subscribe(mockSub);
|
||||
Assert.AreEqual(0, mockSub.NotifyCount, 'MockSub should not be notified on subscribe if latch not yet set.');
|
||||
|
||||
latch.Notify; // Count -> 0, Latch becomes set and notifies mockSub
|
||||
Assert.IsTrue(latch.State.IsSet, 'Latch should now be set.');
|
||||
Assert.AreEqual(1, mockSub.NotifyCount, 'MockSub should have been notified when latch becomes set.');
|
||||
end;
|
||||
|
||||
// --- Category 6: UnadviseAll Behavior on Latch Set ---
|
||||
|
||||
procedure TTestMycLatch.TestLatchSet_ClearsSubscribers_NoFurtherNotification;
|
||||
var
|
||||
latch: IMycEvent;
|
||||
mockSub1, mockSub2: TMockSubscriber;
|
||||
subscription1, subscription2: TState.TSubscription;
|
||||
begin
|
||||
latch := TEvent.CreateLatch(1);
|
||||
mockSub1 := TMockSubscriber.Create;
|
||||
mockSub2 := TMockSubscriber.Create;
|
||||
|
||||
subscription1 := latch.State.Subscribe(mockSub1);
|
||||
subscription2 := latch.State.Subscribe(mockSub2);
|
||||
|
||||
Assert.AreEqual(0, mockSub1.NotifyCount, 'MockSub1 initial NotifyCount should be 0.');
|
||||
Assert.AreEqual(0, mockSub2.NotifyCount, 'MockSub2 initial NotifyCount should be 0.');
|
||||
|
||||
latch.Notify; // FCount becomes 0. Subscribers notified, then UnadviseAll called.
|
||||
|
||||
Assert.IsTrue(latch.State.IsSet, 'Latch should be set.');
|
||||
Assert.AreEqual(1, mockSub1.NotifyCount, 'MockSub1 should have been notified once when latch became set.');
|
||||
Assert.AreEqual(1, mockSub2.NotifyCount, 'MockSub2 should have been notified once when latch became set.');
|
||||
|
||||
latch.Notify; // Call Notify on the latch again (FCount becomes -1)
|
||||
|
||||
Assert.AreEqual(1, mockSub1.NotifyCount, 'MockSub1 should NOT be notified again after UnadviseAll.');
|
||||
Assert.AreEqual(1, mockSub2.NotifyCount, 'MockSub2 should NOT be notified again after UnadviseAll.');
|
||||
end;
|
||||
|
||||
procedure TTestMycLatch.TestLatchSet_SubscriptionFinalize_IsSafePostUnadviseAll;
|
||||
var
|
||||
latch: IMycEvent;
|
||||
mockSub: TMockSubscriber;
|
||||
// 'subscription' will be declared in an inner scope to control its finalization
|
||||
begin
|
||||
latch := TEvent.CreateLatch(1);
|
||||
mockSub := TMockSubscriber.Create;
|
||||
|
||||
var noException: Boolean := True;
|
||||
try
|
||||
begin // Inner block to control lifetime of 'subscription'
|
||||
var subscription: TState.TSubscription; // Local to this block
|
||||
subscription := latch.State.Subscribe(mockSub);
|
||||
|
||||
latch.Notify; // Sets the latch, mockSub is notified, UnadviseAll is called internally.
|
||||
Assert.AreEqual(1, mockSub.NotifyCount, 'MockSub was notified when latch set.');
|
||||
|
||||
// When 'subscription' goes out of scope at the end of this 'begin..end' block,
|
||||
// its Finalize operator will be called, which in turn calls subscription.Unsubscribe.
|
||||
// This happens *after* UnadviseAll was triggered by latch.Notify.
|
||||
end; // <-- subscription.Finalize (and thus Unsubscribe) is called here.
|
||||
except
|
||||
on E: Exception do
|
||||
begin
|
||||
noException := False;
|
||||
// Optional: Log.Error('Exception in TestLatchSet_SubscriptionFinalize_IsSafePostUnadviseAll: ' + E.Message);
|
||||
end;
|
||||
end;
|
||||
Assert.IsTrue(noException, 'Finalizing subscription after Latch set (and UnadviseAll) should not raise an exception.');
|
||||
|
||||
// Further check: calling Notify on latch again should not affect the original mockSub
|
||||
latch.Notify;
|
||||
Assert.AreEqual(1, mockSub.NotifyCount, 'MockSub should not be notified after its subscription was finalized post UnadviseAll.');
|
||||
end;
|
||||
|
||||
procedure TTestMycLatch.TestLatchSet_NewSubscriberToSetLatch_NotifiedImmediatelyButNotPersistedForLatchNotify;
|
||||
var
|
||||
latch: IMycEvent;
|
||||
mockSubInitial, mockSubNew: TMockSubscriber;
|
||||
subscriptionInitial, subscriptionNew: TState.TSubscription;
|
||||
begin
|
||||
latch := TEvent.CreateLatch(1);
|
||||
mockSubInitial := TMockSubscriber.Create;
|
||||
subscriptionInitial := latch.State.Subscribe(mockSubInitial);
|
||||
|
||||
// Set the latch; mockSubInitial is notified, UnadviseAll is called.
|
||||
latch.Notify;
|
||||
Assert.AreEqual(1, mockSubInitial.NotifyCount, 'Initial subscriber notified.');
|
||||
|
||||
// Subscribe a new subscriber to the already set latch
|
||||
mockSubNew := TMockSubscriber.Create;
|
||||
subscriptionNew := latch.State.Subscribe(mockSubNew);
|
||||
|
||||
// New subscriber should be notified immediately upon subscription to an already set latch
|
||||
Assert.AreEqual(1, mockSubNew.NotifyCount, 'New subscriber should be notified immediately upon subscription.');
|
||||
// The direct check of subscriptionNew.FTag was removed as FTag is private.
|
||||
// The correct behavior (no persistent subscription) is verified by the next step.
|
||||
|
||||
// Call Notify on the latch again.
|
||||
latch.Notify;
|
||||
|
||||
// mockSubInitial should not be notified again (was unadvised).
|
||||
Assert.AreEqual(1, mockSubInitial.NotifyCount, 'Initial subscriber (unadvised) not notified again.');
|
||||
// mockSubNew should also not be notified again, as it was only notified immediately
|
||||
// and not persistently added to FSubscribers for subsequent Latch.Notify calls.
|
||||
Assert
|
||||
.AreEqual(1, mockSubNew.NotifyCount, 'New subscriber (notified once on subscribe) not notified by subsequent Latch.Notify calls.');
|
||||
end;
|
||||
|
||||
initialization
|
||||
TDUnitX.RegisterTestFixture(TTestMycLatch);
|
||||
end.
|
||||
@@ -257,11 +257,6 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
function FindNextFile(const Filename: String): String;
|
||||
begin
|
||||
|
||||
end;
|
||||
|
||||
class destructor TDataSeries<T>.CreateClass;
|
||||
begin
|
||||
FCachedFiles := TDictionary<String, TCachedFile>.Create;
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
unit Myc.Trade.Ticker;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Myc.Lazy;
|
||||
|
||||
type
|
||||
TMycLogger = reference to procedure(const Msg: String);
|
||||
|
||||
IMycTradeObject = interface
|
||||
end;
|
||||
|
||||
TMycTradeObject = class(TInterfacedObject, IMycTradeObject)
|
||||
private
|
||||
FCaption: string;
|
||||
FLog: TMycLogger;
|
||||
function GetCaption: string;
|
||||
public
|
||||
constructor Create(const ACaption: string; ALog: TMycLogger);
|
||||
property Caption: string read GetCaption;
|
||||
property Log: TMycLogger read FLog;
|
||||
end;
|
||||
|
||||
IMycTime = interface
|
||||
{$REGION 'property access'}
|
||||
function GetTimeStamp: TDateTime;
|
||||
{$ENDREGION}
|
||||
property TimeStamp: TDateTime read GetTimeStamp;
|
||||
end;
|
||||
|
||||
IMycTick = interface
|
||||
{$REGION 'property access'}
|
||||
function GetAsk: Double;
|
||||
function GetBid: Double;
|
||||
function GetTime: IMycTime;
|
||||
function GetVolume: Double;
|
||||
{$ENDREGION}
|
||||
property Ask: Double read GetAsk;
|
||||
property Bid: Double read GetBid;
|
||||
property Time: IMycTime read GetTime;
|
||||
property Volume: Double read GetVolume;
|
||||
end;
|
||||
|
||||
IMycTicker = interface(IMycTradeObject)
|
||||
{$REGION 'property access'}
|
||||
function GetLastTick: TLazy<IMycTick>;
|
||||
function GetCurrentTime: IMycTime;
|
||||
{$ENDREGION}
|
||||
property LastTick: TLazy<IMycTick> read GetLastTick;
|
||||
property CurrentTime: IMycTime read GetCurrentTime;
|
||||
end;
|
||||
|
||||
IMycHistoryTicker = interface(IMycTicker)
|
||||
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
constructor TMycTradeObject.Create(const ACaption: string; ALog: TMycLogger);
|
||||
begin
|
||||
inherited Create;
|
||||
FCaption := ACaption;
|
||||
FLog := procedure(const Msg: String) begin ALog('[' + FCaption + '] ' + Msg); end;
|
||||
end;
|
||||
|
||||
function TMycTradeObject.GetCaption: string;
|
||||
begin
|
||||
Result := FCaption;
|
||||
end;
|
||||
|
||||
end.
|
||||
Reference in New Issue
Block a user