This commit is contained in:
Michael Schimmel
2025-06-06 01:22:25 +02:00
parent 73d656cda5
commit c4d8607d17
13 changed files with 393 additions and 173 deletions
+112 -5
View File
@@ -8,14 +8,53 @@ uses
Myc.Lazy; Myc.Lazy;
type type
TMycNullLazy<T> = class(TInterfacedObject, IMycLazy<T>) TMycNullMutable<T> = class(TInterfacedObject, TMutable<T>.IMutable)
protected
function GetChanged: TSignal;
function GetValue: T;
end;
TMycMutableBase<T> = class(TInterfacedObject, TMutable<T>.IMutable)
strict private
FChanged: TEvent;
FChangeState: TSignal.TSubscription;
function GetChanged: TSignal;
protected
function GetValue: T; virtual; abstract;
public
constructor Create(const AChanged: TSignal);
destructor Destroy; override;
end;
TMycFuncMutable<T> = class(TMycMutableBase<T>)
private
FProc: TFunc<T>;
protected
function GetValue: T; override;
public
constructor Create(const AChanged: TSignal; const AProc: TFunc<T>);
end;
TMycWriteableMutable<T> = class(TInterfacedObject, TMutable<T>.IMutable, TMutable<T>.IWriteableMutable)
private
FValue: T;
FChanged: TEvent;
protected
function GetChanged: TSignal;
function GetValue: T;
public
constructor Create(const AValue: T);
procedure SetValue(const Value: T);
end;
TMycNullLazy<T> = class(TInterfacedObject, TLazy<T>.ILazy)
protected protected
function GetChanged: TState; function GetChanged: TState;
public public
function Pop(out Res: T): Boolean; function Pop(out Res: T): Boolean;
end; end;
TMycLazyBase<T> = class(TInterfacedObject, IMycLazy<T>) TMycLazyBase<T> = class(TInterfacedObject, TLazy<T>.ILazy)
strict private strict private
FChanged: TFlag; FChanged: TFlag;
FChangeState: TSignal.TSubscription; FChangeState: TSignal.TSubscription;
@@ -39,15 +78,72 @@ type
TMycChainedLazy<T> = class(TMycLazyBase<T>) TMycChainedLazy<T> = class(TMycLazyBase<T>)
private private
FValue: IMycLazy<T>; FValue: TLazy<T>.ILazy;
protected protected
function GetValue: T; override; function GetValue: T; override;
public public
constructor Create(const AValue: IMycLazy<T>); constructor Create(const AValue: TLazy<T>.ILazy);
end; end;
implementation implementation
{ TMycNullMutable<T> }
function TMycNullMutable<T>.GetChanged: TSignal;
begin
Result := TSignal.Null;
end;
function TMycNullMutable<T>.GetValue: T;
begin
Result := Default(T);
end;
{ TMycMutableBase<T> }
constructor TMycMutableBase<T>.Create(const AChanged: TSignal);
begin
inherited Create;
FChanged := TEvent.CreateEvent;
FChangeState := AChanged.Subscribe(FChanged);
end;
destructor TMycMutableBase<T>.Destroy;
begin
FChangeState.Unsubscribe;
inherited;
end;
function TMycMutableBase<T>.GetChanged: TSignal;
begin
Result := FChanged.Signal;
end;
{ TMycWriteableMutable<T> }
constructor TMycWriteableMutable<T>.Create(const AValue: T);
begin
inherited Create;
FValue := AValue;
FChanged := TEvent.CreateEvent;
end;
function TMycWriteableMutable<T>.GetChanged: TSignal;
begin
Result := FChanged.Signal;
end;
function TMycWriteableMutable<T>.GetValue: T;
begin
Result := FValue;
end;
procedure TMycWriteableMutable<T>.SetValue(const Value: T);
begin
FValue := Value;
FChanged.Notify;
end;
{ TMycNullLazy<T> } { TMycNullLazy<T> }
function TMycNullLazy<T>.GetChanged: TState; function TMycNullLazy<T>.GetChanged: TState;
@@ -107,7 +203,7 @@ end;
{ TMycChainedLazy<T> } { TMycChainedLazy<T> }
constructor TMycChainedLazy<T>.Create(const AValue: IMycLazy<T>); constructor TMycChainedLazy<T>.Create(const AValue: TLazy<T>.ILazy);
begin begin
inherited Create(AValue.Changed); inherited Create(AValue.Changed);
FValue := AValue; FValue := AValue;
@@ -119,4 +215,15 @@ begin
raise Exception.Create('Lazy chain already popped'); raise Exception.Create('Lazy chain already popped');
end; end;
constructor TMycFuncMutable<T>.Create(const AChanged: TSignal; const AProc: TFunc<T>);
begin
inherited Create(AChanged);
FProc := AProc;
end;
function TMycFuncMutable<T>.GetValue: T;
begin
Result := FProc();
end;
end. end.
+44 -31
View File
@@ -12,8 +12,8 @@ type
// This state is always considered "set". // This state is always considered "set".
TMycNullSignal = class(TInterfacedObject, TSignal.ISignal) TMycNullSignal = class(TInterfacedObject, TSignal.ISignal)
public public
function Subscribe(Subscriber: IMycSubscriber): Pointer; function Subscribe(Subscriber: TSignal.ISubscriber): Pointer;
procedure Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag); procedure Unsubscribe(Tag: TMycNotifyList<TSignal.ISubscriber>.TTag);
end; end;
// TMycNullState implements a "null object" pattern for IMycState. // TMycNullState implements a "null object" pattern for IMycState.
@@ -27,29 +27,31 @@ type
// A state that acts as an event. It's state ist always set and it will always Trigger it's subscribers, if it gets notified by itself. // A state that acts as an event. It's state ist always set and it will always Trigger it's subscribers, if it gets notified by itself.
TMycEvent = class(TInterfacedObject, TSignal.ISignal, TEvent.IEvent) TMycEvent = class(TInterfacedObject, TSignal.ISignal, TEvent.IEvent)
strict private strict private
FSubscribers: TMycNotifyList<IMycSubscriber>; FSubscribers: TMycNotifyList<TSignal.ISubscriber>;
protected protected
function GetSignal: TSignal;
function GetIsSet: Boolean; function GetIsSet: Boolean;
public public
constructor Create; constructor Create;
destructor Destroy; override; destructor Destroy; override;
function Subscribe(Subscriber: IMycSubscriber): Pointer; function Subscribe(Subscriber: TSignal.ISubscriber): Pointer;
procedure Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag); procedure Unsubscribe(Tag: TMycNotifyList<TSignal.ISubscriber>.TTag);
procedure Trigger; function Notify: Boolean;
end; end;
TMycNullEvent = class(TMycNullSignal, TEvent.IEvent) TMycNullEvent = class(TMycNullSignal, TEvent.IEvent)
private private
procedure Trigger; function GetSignal: TSignal;
function Notify: Boolean;
end; end;
// TMycLatch implements a countdown latch. // TMycLatch implements a countdown latch.
// It is initialized with a count. Calls to its Notify method (as an IMycSubscriber) // It is initialized with a count. Calls to its Notify method (as an TSignal.ISubscriber)
// decrement this count. When the count reaches zero, the latch transitions to the "set" // 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. // state (IsSet becomes true), notifies its current subscribers once, and then remains set.
TMycLatch = class(TInterfacedObject, TState.IState, TLatch.ILatch) TMycLatch = class(TInterfacedObject, TState.IState, TLatch.ILatch)
strict private strict private
FSubscribers: TMycNotifyList<IMycSubscriber>; // List of subscribers waiting for this latch to be set. FSubscribers: TMycNotifyList<TSignal.ISubscriber>; // List of subscribers waiting for this latch to be set.
private private
[volatile] [volatile]
FCount: Integer; // The internal countdown value for the latch. FCount: Integer; // The internal countdown value for the latch.
@@ -63,10 +65,10 @@ type
destructor Destroy; override; destructor Destroy; override;
// ISignal implementation // ISignal implementation
function Subscribe(Subscriber: IMycSubscriber): Pointer; function Subscribe(Subscriber: TSignal.ISubscriber): Pointer;
procedure Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag); procedure Unsubscribe(Tag: TMycNotifyList<TSignal.ISubscriber>.TTag);
// IMycSubscriber implementation for TMycLatch itself. // TSignal.ISubscriber implementation for TMycLatch itself.
// Decrements the internal count. If the count reaches zero, // Decrements the internal count. If the count reaches zero,
// registered subscribers are notified, and the latch becomes permanently set. // registered subscribers are notified, and the latch becomes permanently set.
function Notify: Boolean; function Notify: Boolean;
@@ -83,7 +85,7 @@ type
// Subscribers are notified of relevant state changes. // Subscribers are notified of relevant state changes.
TMycFlag = class(TInterfacedObject, TState.IState, TFlag.IFlag) TMycFlag = class(TInterfacedObject, TState.IState, TFlag.IFlag)
strict private strict private
FSubscribers: TMycNotifyList<IMycSubscriber>; // List of subscribers interested in state changes of this dirty flag. FSubscribers: TMycNotifyList<TSignal.ISubscriber>; // List of subscribers interested in state changes of this dirty flag.
private private
[volatile] [volatile]
FFlag: Boolean; // Internal state: true if dirty/set, false if clean/reset. FFlag: Boolean; // Internal state: true if dirty/set, false if clean/reset.
@@ -98,10 +100,10 @@ type
// Factory method to create a new TMycFlag instance. // Factory method to create a new TMycFlag instance.
class function CreateDirty: TFlag.IFlag; static; class function CreateDirty: TFlag.IFlag; static;
function Subscribe(Subscriber: IMycSubscriber): Pointer; function Subscribe(Subscriber: TSignal.ISubscriber): Pointer;
procedure Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag); procedure Unsubscribe(Tag: TMycNotifyList<TSignal.ISubscriber>.TTag);
// IMycSubscriber implementation for TMycFlag. // TSignal.ISubscriber implementation for TMycFlag.
// Sets this flag to dirty (true). Notifies subscribers if it transitioned from clean to dirty. // Sets this flag to dirty (true). Notifies subscribers if it transitioned from clean to dirty.
function Notify: Boolean; function Notify: Boolean;
@@ -145,17 +147,23 @@ begin
Result := true; Result := true;
end; end;
procedure TMycEvent.Trigger; function TMycEvent.GetSignal: TSignal;
begin
Result := Self;
end;
function TMycEvent.Notify: Boolean;
begin begin
FSubscribers.Lock; FSubscribers.Lock;
try try
FSubscribers.Notify(function(Subscriber: IMycSubscriber): Boolean begin Result := Subscriber.Notify; end); FSubscribers.Notify(function(Subscriber: TSignal.ISubscriber): Boolean begin Result := Subscriber.Notify; end);
finally finally
FSubscribers.Release; FSubscribers.Release;
end; end;
Result := true;
end; end;
function TMycEvent.Subscribe(Subscriber: IMycSubscriber): Pointer; function TMycEvent.Subscribe(Subscriber: TSignal.ISubscriber): Pointer;
begin begin
Result := nil; Result := nil;
if not Assigned(Subscriber) then if not Assigned(Subscriber) then
@@ -170,7 +178,7 @@ begin
end; end;
end; end;
procedure TMycEvent.Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag); procedure TMycEvent.Unsubscribe(Tag: TMycNotifyList<TSignal.ISubscriber>.TTag);
begin begin
if Tag = nil then if Tag = nil then
exit; exit;
@@ -227,7 +235,7 @@ begin
if shouldNotifySubscribers then if shouldNotifySubscribers then
begin begin
FSubscribers.Notify(function(Subscriber: IMycSubscriber): Boolean begin Result := Subscriber.Notify; end); FSubscribers.Notify(function(Subscriber: TSignal.ISubscriber): Boolean begin Result := Subscriber.Notify; end);
end; end;
// Returns true if the latch has not yet been set by this Notify call (count > 0). // Returns true if the latch has not yet been set by this Notify call (count > 0).
@@ -254,7 +262,7 @@ begin
Result := Self; Result := Self;
end; end;
function TMycLatch.Subscribe(Subscriber: IMycSubscriber): Pointer; function TMycLatch.Subscribe(Subscriber: TSignal.ISubscriber): Pointer;
var var
alreadySet: Boolean; alreadySet: Boolean;
begin begin
@@ -281,7 +289,7 @@ begin
end; end;
end; end;
procedure TMycLatch.Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag); procedure TMycLatch.Unsubscribe(Tag: TMycNotifyList<TSignal.ISubscriber>.TTag);
begin begin
if Tag = nil then if Tag = nil then
exit; exit;
@@ -361,9 +369,9 @@ begin
if wasPreviouslyClean then // Only notify subscribers if state changed from clean to dirty. if wasPreviouslyClean then // Only notify subscribers if state changed from clean to dirty.
begin begin
FSubscribers.Notify(function(Subscriber: IMycSubscriber): Boolean begin Result := Subscriber.Notify; end); FSubscribers.Notify(function(Subscriber: TSignal.ISubscriber): Boolean begin Result := Subscriber.Notify; end);
end; end;
// The return value of this Notify (as an IMycSubscriber) indicates if this // The return value of this Notify (as an TSignal.ISubscriber) indicates if this
// TMycFlag 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. // Here, it reflects whether a state change to dirty occurred due to this call.
Result := wasPreviouslyClean; Result := wasPreviouslyClean;
@@ -372,7 +380,7 @@ begin
end; end;
end; end;
function TMycFlag.Subscribe(Subscriber: IMycSubscriber): Pointer; function TMycFlag.Subscribe(Subscriber: TSignal.ISubscriber): Pointer;
begin begin
if not Assigned(Subscriber) then if not Assigned(Subscriber) then
exit(nil); exit(nil);
@@ -394,7 +402,7 @@ begin
end; end;
end; end;
procedure TMycFlag.Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag); procedure TMycFlag.Unsubscribe(Tag: TMycNotifyList<TSignal.ISubscriber>.TTag);
begin begin
if Tag = nil then if Tag = nil then
exit; exit;
@@ -407,12 +415,17 @@ begin
end; end;
end; end;
procedure TMycNullEvent.Trigger; function TMycNullEvent.GetSignal: TSignal;
begin begin
// nop Result := TSignal.Null;
end; end;
function TMycNullSignal.Subscribe(Subscriber: IMycSubscriber): Pointer; function TMycNullEvent.Notify: Boolean;
begin
Result := false;
end;
function TMycNullSignal.Subscribe(Subscriber: TSignal.ISubscriber): Pointer;
begin begin
// Since the state is always set, notify the subscriber immediately. // Since the state is always set, notify the subscriber immediately.
if Assigned(Subscriber) then if Assigned(Subscriber) then
@@ -423,7 +436,7 @@ begin
Result := nil; Result := nil;
end; end;
procedure TMycNullSignal.Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag); procedure TMycNullSignal.Unsubscribe(Tag: TMycNotifyList<TSignal.ISubscriber>.TTag);
begin begin
// Unsubscribing from a null state has no effect. // Unsubscribing from a null state has no effect.
end; end;
+5 -5
View File
@@ -36,7 +36,7 @@ type
// Exceptions during Job execution are caught; the first one is stored // Exceptions during Job execution are caught; the first one is stored
// and can be re-raised in the main thread via WaitFor or Teardown. // and can be re-raised in the main thread via WaitFor or Teardown.
// Raises ETaskException if the factory is already terminated. // Raises ETaskException if the factory is already terminated.
function Run(Job: TProc): IMycSubscriber; function Run(Job: TProc): TSignal.ISubscriber;
procedure EnqueueJob(const Job: TProc); procedure EnqueueJob(const Job: TProc);
@@ -80,7 +80,7 @@ type
function CreateThread(const Proc: TProc): TState.IState; function CreateThread(const Proc: TProc): TState.IState;
procedure HandleException; procedure HandleException;
procedure EnqueueJob(const Job: TProc); procedure EnqueueJob(const Job: TProc);
function Run(Job: TProc): IMycSubscriber; function Run(Job: TProc): TSignal.ISubscriber;
function CreateTask(const Gate: TState; const Proc: TProc): TSignal.TSubscription; function CreateTask(const Gate: TState; const Proc: TProc): TSignal.TSubscription;
procedure WaitFor(State: TState.IState); procedure WaitFor(State: TState.IState);
procedure Teardown; procedure Teardown;
@@ -98,7 +98,7 @@ type
implementation implementation
type type
TMycPendingJob = class(TInterfacedObject, IMycSubscriber) TMycPendingJob = class(TInterfacedObject, TSignal.ISubscriber)
private private
[volatile] [volatile]
FJob: TProc; // The job to execute when count reaches zero FJob: TProc; // The job to execute when count reaches zero
@@ -112,7 +112,7 @@ type
property Owner: TMycTaskFactory read FOwner; property Owner: TMycTaskFactory read FOwner;
end; end;
TMycTaskWait = class sealed(TInterfacedObject, IMycSubscriber) TMycTaskWait = class sealed(TInterfacedObject, TSignal.ISubscriber)
private private
[volatile] [volatile]
FSemaphore: TSemaphore; // System.SyncObjs.TSemaphore to be released on notification FSemaphore: TSemaphore; // System.SyncObjs.TSemaphore to be released on notification
@@ -303,7 +303,7 @@ begin
TaskManager := nil; TaskManager := nil;
end; end;
function TMycTaskFactory.Run(Job: TProc): IMycSubscriber; function TMycTaskFactory.Run(Job: TProc): TSignal.ISubscriber;
begin begin
if FTerminated <> 0 then if FTerminated <> 0 then
raise ETaskException.Create('Task factory terminated'); raise ETaskException.Create('Task factory terminated');
-1
View File
@@ -81,7 +81,6 @@ end;
function TFuture<T>.Chain<S>(const Proc: TFunc<T, S>): TFuture<S>; function TFuture<T>.Chain<S>(const Proc: TFunc<T, S>): TFuture<S>;
begin begin
var Cap := FFuture; var Cap := FFuture;
Result := TFuture<S>.Construct(FFuture.Done, function: S begin Result := Proc(Cap.Result); end); Result := TFuture<S>.Construct(FFuture.Done, function: S begin Result := Proc(Cap.Result); end);
end; end;
+118 -28
View File
@@ -7,41 +7,80 @@ uses
Myc.Signals; Myc.Signals;
type type
IMycMutable<T> = interface TMutable<T> = record
function GetChanged: TState; type
function GetValue: T; IMutable = interface
property Changed: TState read GetChanged; {$REGION 'property access'}
property Value: T read GetValue; function GetChanged: TSignal;
end; function GetValue: T;
{$ENDREGION}
property Changed: TSignal read GetChanged;
property Value: T read GetValue;
end;
IMycLazy<T> = interface IWriteableMutable = interface(IMutable)
{$REGION 'property access'} procedure SetValue(const Value: T);
function GetChanged: TState; end;
{$ENDREGION}
function Pop(out Res: T): Boolean; {$REGION 'private'}
property Changed: TState read GetChanged; strict private
class var
FNull: IMutable;
class constructor CreateClass;
private
FMutable: IMutable;
function GetChanged: TSignal; inline;
{$ENDREGION}
public
constructor Create(const AMutable: IMutable);
class operator Initialize(out Dest: TMutable<T>);
class operator Implicit(const A: IMutable): TMutable<T>; overload;
class operator Implicit(const A: TMutable<T>): IMutable; overload;
class property Null: IMutable read FNull;
class function Construct(const Changing: TSignal; const Proc: TFunc<T>): TMutable<T>; overload; static;
class function CreateWriteable(const Init: T): IWriteableMutable; overload; static;
property Changed: TSignal read GetChanged;
end; end;
TLazy<T> = record TLazy<T> = record
private type
FLazy: IMycLazy<T>; ILazy = interface
function GetChanged: TState.IState; inline; {$REGION 'property access'}
function GetChanged: TState;
{$ENDREGION}
function Pop(out Res: T): Boolean;
property Changed: TState read GetChanged;
end;
{$REGION 'private'}
strict private
class var class var
FNull: IMycLazy<T>; FNull: ILazy;
class constructor CreateClass; class constructor CreateClass;
private
FLazy: ILazy;
function GetChanged: TState; inline;
{$ENDREGION}
public public
constructor Create(const ALazy: IMycLazy<T>); constructor Create(const ALazy: ILazy);
class operator Initialize(out Dest: TLazy<T>);
class operator Implicit(const A: ILazy): TLazy<T>; overload;
class operator Implicit(const A: TLazy<T>): ILazy; overload;
class function Construct(const Changing: TState.IState; const Proc: TFunc<T>): IMycLazy<T>; overload; static; class function Construct(const Changing: TState; const Proc: TFunc<T>): TLazy<T>; overload; static;
class function Construct(const Value: IMycLazy<T>): IMycLazy<T>; overload; static; class function Construct(const Value: ILazy): TLazy<T>; overload; static;
class operator Implicit(const A: IMycLazy<T>): TLazy<T>; overload; class property Null: ILazy read FNull;
class operator Implicit(const A: TLazy<T>): IMycLazy<T>; overload;
function Pop(out Res: T): Boolean; inline; function Pop(out Res: T): Boolean; inline;
property Changed: TState.IState read GetChanged; property Changed: TState read GetChanged;
end; end;
implementation implementation
@@ -49,14 +88,60 @@ implementation
uses uses
Myc.Core.Lazy; Myc.Core.Lazy;
constructor TLazy<T>.Create(const ALazy: IMycLazy<T>); { TMutable<T> }
constructor TMutable<T>.Create(const AMutable: IMutable);
begin
FMutable := AMutable;
if not Assigned(FMutable) then
FMutable := FNull;
end;
class constructor TMutable<T>.CreateClass;
begin
FNull := TMycNullMutable<T>.Create;
end;
class function TMutable<T>.Construct(const Changing: TSignal; const Proc: TFunc<T>): TMutable<T>;
begin
Result := TMycFuncMutable<T>.Create(Changing, Proc);
end;
class function TMutable<T>.CreateWriteable(const Init: T): IWriteableMutable;
begin
Result := TMycWriteableMutable<T>.Create(Init);
end;
function TMutable<T>.GetChanged: TSignal;
begin
Result := FMutable.Changed;
end;
class operator TMutable<T>.Implicit(const A: TMutable<T>): IMutable;
begin
Result := A.FMutable;
end;
class operator TMutable<T>.Implicit(const A: IMutable): TMutable<T>;
begin
Result.Create(A);
end;
class operator TMutable<T>.Initialize(out Dest: TMutable<T>);
begin
Dest.FMutable := FNull;
end;
{ TLazy<T> }
constructor TLazy<T>.Create(const ALazy: ILazy);
begin begin
FLazy := ALazy; FLazy := ALazy;
if not Assigned(FLazy) then if not Assigned(FLazy) then
FLazy := FNull; FLazy := FNull;
end; end;
class function TLazy<T>.Construct(const Changing: TState.IState; const Proc: TFunc<T>): IMycLazy<T>; class function TLazy<T>.Construct(const Changing: TState; const Proc: TFunc<T>): TLazy<T>;
begin begin
Result := TMycFuncLazy<T>.Create(Changing, Proc); Result := TMycFuncLazy<T>.Create(Changing, Proc);
end; end;
@@ -66,12 +151,12 @@ begin
FNull := TMycNullLazy<T>.Create; FNull := TMycNullLazy<T>.Create;
end; end;
class function TLazy<T>.Construct(const Value: IMycLazy<T>): IMycLazy<T>; class function TLazy<T>.Construct(const Value: ILazy): TLazy<T>;
begin begin
Result := TMycChainedLazy<T>.Create(Value); Result := TMycChainedLazy<T>.Create(Value);
end; end;
function TLazy<T>.GetChanged: TState.IState; function TLazy<T>.GetChanged: TState;
begin begin
Result := FLazy.Changed; Result := FLazy.Changed;
end; end;
@@ -81,14 +166,19 @@ begin
Result := FLazy.Pop(Res); Result := FLazy.Pop(Res);
end; end;
class operator TLazy<T>.Implicit(const A: IMycLazy<T>): TLazy<T>; class operator TLazy<T>.Implicit(const A: ILazy): TLazy<T>;
begin begin
Result.Create(A); Result.Create(A);
end; end;
class operator TLazy<T>.Implicit(const A: TLazy<T>): IMycLazy<T>; class operator TLazy<T>.Implicit(const A: TLazy<T>): ILazy;
begin begin
Result := A.FLazy; Result := A.FLazy;
end; end;
class operator TLazy<T>.Initialize(out Dest: TLazy<T>);
begin
Dest.FLazy := FNull;
end;
end. end.
+59 -48
View File
@@ -6,16 +6,16 @@ uses
System.SysUtils; System.SysUtils;
type type
IMycSubscriber = interface
function Notify: Boolean;
end;
TSignal = record TSignal = record
type type
ISubscriber = interface
function Notify: Boolean;
end;
TSubscriptionTag = Pointer; TSubscriptionTag = Pointer;
ISignal = interface ISignal = interface
function Subscribe(Subscriber: IMycSubscriber): TSubscriptionTag; function Subscribe(Subscriber: ISubscriber): TSubscriptionTag;
procedure Unsubscribe(Tag: TSubscriptionTag); procedure Unsubscribe(Tag: TSubscriptionTag);
end; end;
@@ -45,38 +45,11 @@ type
class operator Implicit(const A: ISignal): TSignal; overload; class operator Implicit(const A: ISignal): TSignal; overload;
class operator Implicit(const A: TSignal): ISignal; overload; class operator Implicit(const A: TSignal): ISignal; overload;
function Subscribe(Subscriber: IMycSubscriber): TSubscription; inline; function Subscribe(Subscriber: ISubscriber): TSubscription; inline;
class property Null: ISignal read FNull; class property Null: ISignal read FNull;
end; end;
TEvent = record
type
IEvent = interface(TSignal.ISignal)
procedure Trigger;
end;
{$REGION 'private'}
strict private
class var
FNull: IEvent;
class constructor ClassCreate;
private
FEvent: IEvent;
{$ENDREGION}
public
constructor Create(const AEvent: IEvent);
class operator Initialize(out Dest: TEvent);
class operator Implicit(const A: IEvent): TEvent; overload;
class operator Implicit(const A: TEvent): IEvent; overload;
class property Null: IEvent read FNull;
function Subscribe(Subscriber: IMycSubscriber): TSignal.TSubscription; inline;
procedure Unsubscribe(Tag: Pointer); inline;
end;
TState = record TState = record
type type
IState = interface(TSignal.ISignal) IState = interface(TSignal.ISignal)
@@ -108,18 +81,54 @@ type
class property Null: IState read FNull; class property Null: IState read FNull;
function Subscribe(Subscriber: IMycSubscriber): TSignal.TSubscription; inline; function Subscribe(Subscriber: TSignal.ISubscriber): TSignal.TSubscription; inline;
procedure Unsubscribe(Tag: Pointer); inline;
property IsSet: Boolean read GetIsSet; property IsSet: Boolean read GetIsSet;
end; end;
// An event is a stateless signal that forwards all notifications to the subscribers.
TEvent = record
type
IEvent = interface(TSignal.ISubscriber)
{$REGION 'property access'}
function GetSignal: TSignal;
{$ENDREGION}
property Signal: TSignal read GetSignal;
end;
{$REGION 'private'}
strict private
class var
FNull: IEvent;
class constructor ClassCreate;
private
FEvent: IEvent;
function GetSignal: TSignal; inline;
{$ENDREGION}
public
constructor Create(const AEvent: IEvent);
class operator Initialize(out Dest: TEvent);
class operator Implicit(const A: IEvent): TEvent; overload;
class operator Implicit(const A: TEvent): IEvent; overload;
class function CreateEvent: TEvent; static;
class property Null: IEvent read FNull;
function Notify: Boolean; inline;
property Signal: TSignal read GetSignal;
end;
// A latch is a specific type of event that, once set, remains set (non-resettable). // A latch is a specific type of event that, once set, remains set (non-resettable).
// It becomes set when the internal countdown reaches zero. // It becomes set when the internal countdown reaches zero.
TLatch = record TLatch = record
type type
ILatch = interface(IMycSubscriber) ILatch = interface(TSignal.ISubscriber)
{$REGION 'property access'}
function GetState: TState; function GetState: TState;
{$ENDREGION}
property State: TState read GetState; property State: TState read GetState;
end; end;
@@ -153,8 +162,10 @@ type
// TFlag represents a resettable flag, typically indicating if a State is "dirty" (requiring attention) or "clean". // TFlag represents a resettable flag, typically indicating if a State is "dirty" (requiring attention) or "clean".
TFlag = record TFlag = record
type type
IFlag = interface(IMycSubscriber) IFlag = interface(TSignal.ISubscriber)
{$REGION 'property access'}
function GetState: TState; function GetState: TState;
{$ENDREGION}
function Reset: Boolean; function Reset: Boolean;
property State: TState read GetState; property State: TState read GetState;
end; end;
@@ -260,16 +271,11 @@ begin
Result := FState.IsSet; Result := FState.IsSet;
end; end;
function TState.Subscribe(Subscriber: IMycSubscriber): TSignal.TSubscription; function TState.Subscribe(Subscriber: TSignal.ISubscriber): TSignal.TSubscription;
begin begin
Result := TSignal.TSubscription.Create(FState, FState.Subscribe(Subscriber)); Result := TSignal.TSubscription.Create(FState, FState.Subscribe(Subscriber));
end; end;
procedure TState.Unsubscribe(Tag: Pointer);
begin
FState.Unsubscribe(Tag);
end;
class operator TState.Implicit(const A: TState): IState; class operator TState.Implicit(const A: TState): IState;
begin begin
Result := A.FState; Result := A.FState;
@@ -299,14 +305,19 @@ begin
FNull := TMycNullEvent.Create(); FNull := TMycNullEvent.Create();
end; end;
function TEvent.Subscribe(Subscriber: IMycSubscriber): TSignal.TSubscription; class function TEvent.CreateEvent: TEvent;
begin begin
Result := TSignal.TSubscription.Create(FEvent, FEvent.Subscribe(Subscriber)); Result := TMycEvent.Create;
end; end;
procedure TEvent.Unsubscribe(Tag: Pointer); function TEvent.GetSignal: TSignal;
begin begin
FEvent.Unsubscribe(Tag); Result := FEvent.Signal;
end;
function TEvent.Notify: Boolean;
begin
Result := FEvent.Notify;
end; end;
class operator TEvent.Implicit(const A: TEvent): IEvent; class operator TEvent.Implicit(const A: TEvent): IEvent;
@@ -443,7 +454,7 @@ begin
FSignal := FNull; FSignal := FNull;
end; end;
function TSignal.Subscribe(Subscriber: IMycSubscriber): TSubscription; function TSignal.Subscribe(Subscriber: ISubscriber): TSubscription;
begin begin
Result := TSubscription.Create(FSignal, FSignal.Subscribe(Subscriber)); Result := TSubscription.Create(FSignal, FSignal.Subscribe(Subscriber));
end; end;
+1 -1
View File
@@ -30,7 +30,7 @@ uses
System.Generics.Collections; System.Generics.Collections;
type type
TMycExecMock = class(TInterfacedObject, IMycSubscriber) TMycExecMock = class(TInterfacedObject, TSignal.ISubscriber)
private private
FProc: TProc; FProc: TProc;
public public
+21 -21
View File
@@ -13,8 +13,8 @@ type
[TestFixture] [TestFixture]
TTestMycCoreLazy = class(TObject) TTestMycCoreLazy = class(TObject)
private private
procedure Helper_ConsumeInitialPop(const ALazy: IMycLazy<Integer>; InitialExpectedValue: Integer); overload; procedure Helper_ConsumeInitialPop(const ALazy: TLazy<Integer>.ILazy; InitialExpectedValue: Integer); overload;
procedure Helper_ConsumeInitialPop(const ALazy: IMycLazy<string>; const InitialExpectedValue: string); overload; procedure Helper_ConsumeInitialPop(const ALazy: TLazy<String>.ILazy; const InitialExpectedValue: string); overload;
public public
[Setup] [Setup]
procedure Setup; procedure Setup;
@@ -65,7 +65,7 @@ uses
{ TTestMycCoreLazy Helper Methods } { TTestMycCoreLazy Helper Methods }
procedure TTestMycCoreLazy.Helper_ConsumeInitialPop(const ALazy: IMycLazy<Integer>; InitialExpectedValue: Integer); procedure TTestMycCoreLazy.Helper_ConsumeInitialPop(const ALazy: TLazy<Integer>.ILazy; InitialExpectedValue: Integer);
var var
tempValue: Integer; tempValue: Integer;
popResult: Boolean; popResult: Boolean;
@@ -77,7 +77,7 @@ begin
Assert.IsFalse(ALazy.GetChanged.IsSet, 'Changed.IsSet should be false after consuming initial pop'); Assert.IsFalse(ALazy.GetChanged.IsSet, 'Changed.IsSet should be false after consuming initial pop');
end; end;
procedure TTestMycCoreLazy.Helper_ConsumeInitialPop(const ALazy: IMycLazy<string>; const InitialExpectedValue: string); procedure TTestMycCoreLazy.Helper_ConsumeInitialPop(const ALazy: TLazy<String>.ILazy; const InitialExpectedValue: string);
var var
tempValue: string; tempValue: string;
popResult: Boolean; popResult: Boolean;
@@ -102,10 +102,10 @@ end;
// == Tests for TMycNullLazy<T> == // == Tests for TMycNullLazy<T> ==
procedure TTestMycCoreLazy.TestNullLazy_GetChanged_IsAlwaysNullState; procedure TTestMycCoreLazy.TestNullLazy_GetChanged_IsAlwaysNullState;
var var
nullLazy: IMycLazy<Integer>; nullLazy: TLazy<Integer>.ILazy;
changedState: TState.IState; changedState: TState.IState;
begin begin
nullLazy := TMycNullLazy<Integer>.Create as IMycLazy<Integer>; nullLazy := TMycNullLazy<Integer>.Create as TLazy<Integer>.ILazy;
Assert.IsNotNull(nullLazy, 'TMycNullLazy<Integer> instance should not be nil'); Assert.IsNotNull(nullLazy, 'TMycNullLazy<Integer> instance should not be nil');
changedState := nullLazy.GetChanged; changedState := nullLazy.GetChanged;
Assert.AreSame(TState.Null, changedState, 'TMycNullLazy.GetChanged should return TState.Null'); Assert.AreSame(TState.Null, changedState, 'TMycNullLazy.GetChanged should return TState.Null');
@@ -114,11 +114,11 @@ end;
procedure TTestMycCoreLazy.TestNullLazy_Pop_ReturnsDefaultIntegerAndTrue; procedure TTestMycCoreLazy.TestNullLazy_Pop_ReturnsDefaultIntegerAndTrue;
var var
nullLazy: IMycLazy<Integer>; nullLazy: TLazy<Integer>.ILazy;
value: Integer; value: Integer;
result: Boolean; result: Boolean;
begin begin
nullLazy := TMycNullLazy<Integer>.Create as IMycLazy<Integer>; nullLazy := TMycNullLazy<Integer>.Create as TLazy<Integer>.ILazy;
Assert.IsNotNull(nullLazy, 'TMycNullLazy<Integer> instance should not be nil'); Assert.IsNotNull(nullLazy, 'TMycNullLazy<Integer> instance should not be nil');
value := 123; value := 123;
result := nullLazy.Pop(value); result := nullLazy.Pop(value);
@@ -128,11 +128,11 @@ end;
procedure TTestMycCoreLazy.TestNullLazy_Pop_ReturnsDefaultStringAndTrue; procedure TTestMycCoreLazy.TestNullLazy_Pop_ReturnsDefaultStringAndTrue;
var var
nullLazy: IMycLazy<string>; nullLazy: TLazy<String>.ILazy;
value: string; value: string;
result: Boolean; result: Boolean;
begin begin
nullLazy := TMycNullLazy<string>.Create as IMycLazy<string>; nullLazy := TMycNullLazy<string>.Create as TLazy<String>.ILazy;
Assert.IsNotNull(nullLazy, 'TMycNullLazy<string> instance should not be nil'); Assert.IsNotNull(nullLazy, 'TMycNullLazy<string> instance should not be nil');
value := 'test'; value := 'test';
result := nullLazy.Pop(value); result := nullLazy.Pop(value);
@@ -142,11 +142,11 @@ end;
procedure TTestMycCoreLazy.TestNullLazy_Pop_ReturnsDefaultInterfaceAndTrue; procedure TTestMycCoreLazy.TestNullLazy_Pop_ReturnsDefaultInterfaceAndTrue;
var var
nullLazy: IMycLazy<TState.IState>; nullLazy: TLazy<TState.IState>.ILazy;
value: TState.IState; value: TState.IState;
result: Boolean; result: Boolean;
begin begin
nullLazy := TMycNullLazy<TState.IState>.Create as IMycLazy<TState.IState>; nullLazy := TMycNullLazy<TState.IState>.Create as TLazy<TState.IState>.ILazy;
Assert.IsNotNull(nullLazy, 'TMycNullLazy<TState.IState> instance should not be nil'); Assert.IsNotNull(nullLazy, 'TMycNullLazy<TState.IState> instance should not be nil');
value := TState.Null; value := TState.Null;
result := nullLazy.Pop(value); result := nullLazy.Pop(value);
@@ -157,7 +157,7 @@ end;
// == Tests for TMycFuncLazy<T> - Initial State by Design == // == Tests for TMycFuncLazy<T> - Initial State by Design ==
procedure TTestMycCoreLazy.TestFuncLazy_GetChanged_IsAlwaysTrueAfterCreation; procedure TTestMycCoreLazy.TestFuncLazy_GetChanged_IsAlwaysTrueAfterCreation;
var var
funcLazy: IMycLazy<Integer>; funcLazy: TLazy<Integer>.ILazy;
changedState: TState.IState; changedState: TState.IState;
sourceDirty: TFlag.IFlag; sourceDirty: TFlag.IFlag;
begin begin
@@ -172,7 +172,7 @@ end;
procedure TTestMycCoreLazy.TestFuncLazy_FirstPop_AlwaysEvaluatesAndResetsChanged; procedure TTestMycCoreLazy.TestFuncLazy_FirstPop_AlwaysEvaluatesAndResetsChanged;
var var
funcLazy: IMycLazy<Integer>; funcLazy: TLazy<Integer>.ILazy;
value: Integer; value: Integer;
result: Boolean; result: Boolean;
sourceDirty: TFlag.IFlag; sourceDirty: TFlag.IFlag;
@@ -206,7 +206,7 @@ end;
procedure TTestMycCoreLazy.TestFuncLazy_AfterFirstPop_IfNoSourceChange_GetChangedIsFalse; procedure TTestMycCoreLazy.TestFuncLazy_AfterFirstPop_IfNoSourceChange_GetChangedIsFalse;
var var
funcLazy: IMycLazy<Integer>; funcLazy: TLazy<Integer>.ILazy;
sourceDirty: TFlag.IFlag; sourceDirty: TFlag.IFlag;
initialProcValue: Integer; initialProcValue: Integer;
begin begin
@@ -220,7 +220,7 @@ end;
procedure TTestMycCoreLazy.TestFuncLazy_AfterFirstPop_IfNoSourceChange_PopReturnsFalse_ValueUndefined; procedure TTestMycCoreLazy.TestFuncLazy_AfterFirstPop_IfNoSourceChange_PopReturnsFalse_ValueUndefined;
var var
funcLazy: IMycLazy<Integer>; funcLazy: TLazy<Integer>.ILazy;
value: Integer; value: Integer;
result: Boolean; result: Boolean;
sourceDirty: TFlag.IFlag; sourceDirty: TFlag.IFlag;
@@ -249,7 +249,7 @@ end;
procedure TTestMycCoreLazy.TestFuncLazy_AfterSourceChange_GetChanged_IsSet; procedure TTestMycCoreLazy.TestFuncLazy_AfterSourceChange_GetChanged_IsSet;
var var
funcLazy: IMycLazy<Integer>; funcLazy: TLazy<Integer>.ILazy;
changedState: TState.IState; changedState: TState.IState;
sourceDirty: TFlag.IFlag; sourceDirty: TFlag.IFlag;
initialProcValue: Integer; initialProcValue: Integer;
@@ -266,7 +266,7 @@ end;
procedure TTestMycCoreLazy.TestFuncLazy_AfterSourceChange_Pop_ReturnsTrueAndProcResult_ResetsChanged; procedure TTestMycCoreLazy.TestFuncLazy_AfterSourceChange_Pop_ReturnsTrueAndProcResult_ResetsChanged;
var var
funcLazy: IMycLazy<Integer>; funcLazy: TLazy<Integer>.ILazy;
value: Integer; value: Integer;
result: Boolean; result: Boolean;
sourceDirty: TFlag.IFlag; sourceDirty: TFlag.IFlag;
@@ -304,7 +304,7 @@ end;
procedure TTestMycCoreLazy.TestFuncLazy_Pop_Twice_SourceUnchanged_SecondPopReturnsFalse_ValueUndefined; procedure TTestMycCoreLazy.TestFuncLazy_Pop_Twice_SourceUnchanged_SecondPopReturnsFalse_ValueUndefined;
var var
funcLazy: IMycLazy<Integer>; funcLazy: TLazy<Integer>.ILazy;
value: Integer; value: Integer;
resultPop1, resultPop2: Boolean; resultPop1, resultPop2: Boolean;
sourceDirty: TFlag.IFlag; sourceDirty: TFlag.IFlag;
@@ -336,7 +336,7 @@ end;
// to TestFuncLazy_SourceInteraction_NotifyOnSetSourceDoesNotRetriggerLazy // to TestFuncLazy_SourceInteraction_NotifyOnSetSourceDoesNotRetriggerLazy
procedure TTestMycCoreLazy.TestFuncLazy_SourceInteraction_NotifyOnSetSourceDoesNotRetriggerLazy; procedure TTestMycCoreLazy.TestFuncLazy_SourceInteraction_NotifyOnSetSourceDoesNotRetriggerLazy;
var var
funcLazy: IMycLazy<Integer>; funcLazy: TLazy<Integer>.ILazy;
value: Integer; value: Integer;
result: Boolean; result: Boolean;
sourceDirty: TFlag.IFlag; sourceDirty: TFlag.IFlag;
@@ -417,7 +417,7 @@ end;
procedure TTestMycCoreLazy.TestFuncLazy_SourceIsInitiallySet_PopBehavesCorrectly; procedure TTestMycCoreLazy.TestFuncLazy_SourceIsInitiallySet_PopBehavesCorrectly;
var var
funcLazy: IMycLazy<Integer>; funcLazy: TLazy<Integer>.ILazy;
value: Integer; value: Integer;
result: Boolean; result: Boolean;
sourceDirty: TFlag.IFlag; sourceDirty: TFlag.IFlag;
+16 -16
View File
@@ -137,7 +137,7 @@ end;
procedure TTestMyLazy.TestConstruct_InitialChanged_IsAlwaysTrue; procedure TTestMyLazy.TestConstruct_InitialChanged_IsAlwaysTrue;
var var
lazyIntf: IMycLazy<Integer>; lazyIntf: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>; lazyRec: TLazy<Integer>;
procExecuted: Boolean; procExecuted: Boolean;
begin begin
@@ -161,7 +161,7 @@ end;
procedure TTestMyLazy.TestConstruct_FirstPop_SucceedsAndResetsChanged; procedure TTestMyLazy.TestConstruct_FirstPop_SucceedsAndResetsChanged;
var var
lazyIntf: IMycLazy<Integer>; lazyIntf: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>; lazyRec: TLazy<Integer>;
procExecuted: Boolean; procExecuted: Boolean;
expectedValue: Integer; expectedValue: Integer;
@@ -185,7 +185,7 @@ end;
procedure TTestMyLazy.TestConstruct_StateInteraction_SignalTriggersChanged; procedure TTestMyLazy.TestConstruct_StateInteraction_SignalTriggersChanged;
var var
lazyIntf: IMycLazy<Integer>; lazyIntf: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>; lazyRec: TLazy<Integer>;
expectedValue: Integer; expectedValue: Integer;
begin begin
@@ -203,7 +203,7 @@ end;
procedure TTestMyLazy.TestConstruct_StateInteraction_PopResetsChangedAfterSignal; procedure TTestMyLazy.TestConstruct_StateInteraction_PopResetsChangedAfterSignal;
var var
lazyIntf: IMycLazy<Integer>; lazyIntf: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>; lazyRec: TLazy<Integer>;
val: Integer; val: Integer;
popResult: Boolean; popResult: Boolean;
@@ -234,7 +234,7 @@ end;
procedure TTestMyLazy.TestConstruct_StateInteraction_NotifyOnAlreadySetSource_DoesNotRetrigger; procedure TTestMyLazy.TestConstruct_StateInteraction_NotifyOnAlreadySetSource_DoesNotRetrigger;
var var
lazyIntf: IMycLazy<Integer>; lazyIntf: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>; lazyRec: TLazy<Integer>;
val: Integer; val: Integer;
popResult: Boolean; popResult: Boolean;
@@ -280,7 +280,7 @@ end;
procedure TTestMyLazy.TestConstruct_Destruction_UnsubscribesAndNoCrashOnSourceNotify; procedure TTestMyLazy.TestConstruct_Destruction_UnsubscribesAndNoCrashOnSourceNotify;
var var
lazyIntf: IMycLazy<Integer>; lazyIntf: TLazy<Integer>.ILazy;
localChangingSignal: TFlag.IFlag; // Use a local signal for this test to control its lifetime localChangingSignal: TFlag.IFlag; // Use a local signal for this test to control its lifetime
begin begin
localChangingSignal := TFlag.CreateFlag; localChangingSignal := TFlag.CreateFlag;
@@ -293,7 +293,7 @@ begin
var lazyRec: TLazy<Integer> := lazyIntf; // Wrap for initial pop var lazyRec: TLazy<Integer> := lazyIntf; // Wrap for initial pop
ConsumeInitialPop(lazyRec, 1, 'TestConstruct_Destruction (Initial)'); ConsumeInitialPop(lazyRec, 1, 'TestConstruct_Destruction (Initial)');
lazyIntf := nil; // Release the IMycLazy interface. ARC should destroy the TMycFuncLazy object. lazyIntf := nil; // Release the ILazy interface. ARC should destroy the TMycFuncLazy object.
// This should trigger its destructor, which should unsubscribe from localChangingSignal. // This should trigger its destructor, which should unsubscribe from localChangingSignal.
Assert.WillNotRaise( Assert.WillNotRaise(
@@ -308,11 +308,11 @@ begin
localChangingSignal := nil; // Clean up the local signal itself. localChangingSignal := nil; // Clean up the local signal itself.
end; end;
// == Tests for TLazy<T>.Create with a pre-existing (non-nil) IMycLazy == // == Tests for TLazy<T>.Create with a pre-existing (non-nil) ILazy ==
procedure TTestMyLazy.TestCreateWithExistingLazy_DelegatesChangedCorrectly; procedure TTestMyLazy.TestCreateWithExistingLazy_DelegatesChangedCorrectly;
var var
originalLazyIntf: IMycLazy<Integer>; originalLazyIntf: TLazy<Integer>.ILazy;
wrappedLazyRec: TLazy<Integer>; wrappedLazyRec: TLazy<Integer>;
expectedValue: Integer; expectedValue: Integer;
begin begin
@@ -335,7 +335,7 @@ end;
procedure TTestMyLazy.TestCreateWithExistingLazy_DelegatesPopCorrectly; procedure TTestMyLazy.TestCreateWithExistingLazy_DelegatesPopCorrectly;
var var
originalLazyIntf: IMycLazy<Integer>; originalLazyIntf: TLazy<Integer>.ILazy;
wrappedLazyRec: TLazy<Integer>; wrappedLazyRec: TLazy<Integer>;
val: Integer; val: Integer;
popResult: Boolean; popResult: Boolean;
@@ -376,7 +376,7 @@ end;
procedure TTestMyLazy.TestImplicitOperator_FromInterfaceToRecord; procedure TTestMyLazy.TestImplicitOperator_FromInterfaceToRecord;
var var
lazyIntf: IMycLazy<Integer>; lazyIntf: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>; lazyRec: TLazy<Integer>;
expectedValue: Integer; expectedValue: Integer;
begin begin
@@ -384,7 +384,7 @@ begin
lazyIntf := TLazy<Integer>.Construct(FChangingSignal.State, function: Integer begin Result := expectedValue; end); lazyIntf := TLazy<Integer>.Construct(FChangingSignal.State, function: Integer begin Result := expectedValue; end);
Assert.IsNotNull(lazyIntf, 'Interface should be assigned'); Assert.IsNotNull(lazyIntf, 'Interface should be assigned');
lazyRec := lazyIntf; // Implicit conversion: IMycLazy<T> to TLazy<T> lazyRec := lazyIntf; // Implicit conversion: ILazy<T> to TLazy<T>
// Verify by using the record // Verify by using the record
Assert.IsTrue(lazyRec.Changed.IsSet, 'Record (from intf): Initial Changed.IsSet should be true'); Assert.IsTrue(lazyRec.Changed.IsSet, 'Record (from intf): Initial Changed.IsSet should be true');
@@ -393,9 +393,9 @@ end;
procedure TTestMyLazy.TestImplicitOperator_FromRecordToInterface; procedure TTestMyLazy.TestImplicitOperator_FromRecordToInterface;
var var
lazyIntfFromConstruct: IMycLazy<Integer>; lazyIntfFromConstruct: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>; lazyRec: TLazy<Integer>;
lazyIntfFromRecord: IMycLazy<Integer>; lazyIntfFromRecord: TLazy<Integer>.ILazy;
val: Integer; val: Integer;
expectedValue: Integer; expectedValue: Integer;
begin begin
@@ -403,7 +403,7 @@ begin
lazyIntfFromConstruct := TLazy<Integer>.Construct(FChangingSignal.State, function: Integer begin Result := expectedValue; end); lazyIntfFromConstruct := TLazy<Integer>.Construct(FChangingSignal.State, function: Integer begin Result := expectedValue; end);
lazyRec.Create(lazyIntfFromConstruct); // Explicitly create record lazyRec.Create(lazyIntfFromConstruct); // Explicitly create record
lazyIntfFromRecord := lazyRec; // Implicit conversion: TLazy<T> to IMycLazy<T> lazyIntfFromRecord := lazyRec; // Implicit conversion: TLazy<T> to ILazy<T>
// Verify by using the converted interface // Verify by using the converted interface
Assert.AreSame(lazyIntfFromConstruct, lazyIntfFromRecord, 'Converted interface should be the same as the original wrapped one'); Assert.AreSame(lazyIntfFromConstruct, lazyIntfFromRecord, 'Converted interface should be the same as the original wrapped one');
@@ -417,7 +417,7 @@ end;
// == Tests for TLazy<T>.Pop specific behaviors (Res undefined) == // == Tests for TLazy<T>.Pop specific behaviors (Res undefined) ==
procedure TTestMyLazy.TestPop_AfterInitialAndNoSignal_ReturnsFalseAndResUndefined; procedure TTestMyLazy.TestPop_AfterInitialAndNoSignal_ReturnsFalseAndResUndefined;
var var
lazyIntf: IMycLazy<Integer>; lazyIntf: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>; lazyRec: TLazy<Integer>;
val: Integer; // Value will not be checked as Pop returns false val: Integer; // Value will not be checked as Pop returns false
popResult: Boolean; popResult: Boolean;
+5 -5
View File
@@ -9,7 +9,7 @@ uses
type type
// Helper class to mock a subscriber (reuse or redefine if not in common unit) // Helper class to mock a subscriber (reuse or redefine if not in common unit)
TMockSubscriber = class(TInterfacedObject, IMycSubscriber) TMockSubscriber = class(TInterfacedObject, TSignal.ISubscriber)
public public
NotifyCount: Integer; NotifyCount: Integer;
NotifyShouldReturn: Boolean; NotifyShouldReturn: Boolean;
@@ -17,7 +17,7 @@ type
LastReturnedValueFromSource: Boolean; // To store what the source's Notify returned LastReturnedValueFromSource: Boolean; // To store what the source's Notify returned
constructor Create(AShouldReturn: Boolean = True); constructor Create(AShouldReturn: Boolean = True);
function Notify: Boolean; // IMycSubscriber implementation. function Notify: Boolean; // TSignal.ISubscriber implementation.
end; end;
[TestFixture] [TestFixture]
@@ -164,7 +164,7 @@ var
begin begin
dirtyFlag := CreateAndPrepareDirtyFlag(False); // Start clean dirtyFlag := CreateAndPrepareDirtyFlag(False); // Start clean
notifyResult := dirtyFlag.Notify; // Call IMycSubscriber.Notify to make it dirty notifyResult := dirtyFlag.Notify; // Call TSignal.ISubscriber.Notify to make it dirty
Assert.IsTrue(dirtyFlag.State.IsSet, 'Flag should be dirty (IsSet) after Notify on clean flag.'); Assert.IsTrue(dirtyFlag.State.IsSet, 'Flag should be dirty (IsSet) after Notify on clean flag.');
Assert.IsTrue(notifyResult, 'Notify() should return True indicating a state change from clean to dirty.'); Assert.IsTrue(notifyResult, 'Notify() should return True indicating a state change from clean to dirty.');
@@ -177,7 +177,7 @@ var
begin begin
dirtyFlag := CreateAndPrepareDirtyFlag(True); // Start dirty dirtyFlag := CreateAndPrepareDirtyFlag(True); // Start dirty
notifyResult := dirtyFlag.Notify; // Call IMycSubscriber.Notify again notifyResult := dirtyFlag.Notify; // Call TSignal.ISubscriber.Notify again
Assert.IsTrue(dirtyFlag.State.IsSet, 'Flag should remain dirty (IsSet).'); Assert.IsTrue(dirtyFlag.State.IsSet, 'Flag should remain dirty (IsSet).');
Assert.IsFalse(notifyResult, 'Notify() should return False as flag was already dirty (no state change to dirty).'); Assert.IsFalse(notifyResult, 'Notify() should return False as flag was already dirty (no state change to dirty).');
@@ -303,7 +303,7 @@ begin
end; end;
// Category 3: Chaining Dirty Flags // Category 3: Chaining Dirty Flags
// In these tests, FlagX.Notify means calling the IMycSubscriber.Notify method on FlagX. // In these tests, FlagX.Notify means calling the TSignal.ISubscriber.Notify method on FlagX.
// This makes FlagX dirty and potentially notifies its subscribers (like FlagY). // This makes FlagX dirty and potentially notifies its subscribers (like FlagY).
procedure TTestMycDirtyFlag.TestChain_A_Notifies_B_SimplePropagation; procedure TTestMycDirtyFlag.TestChain_A_Notifies_B_SimplePropagation;
+6 -6
View File
@@ -9,14 +9,14 @@ uses
type type
// Helper class to mock a subscriber. // Helper class to mock a subscriber.
TMockSubscriber = class(TInterfacedObject, IMycSubscriber) TMockSubscriber = class(TInterfacedObject, TSignal.ISubscriber)
public public
NotifyCount: Integer; NotifyCount: Integer;
NotifyShouldReturn: Boolean; // Determines what this mock's Notify method returns NotifyShouldReturn: Boolean; // Determines what this mock's Notify method returns
WasCalled: Boolean; WasCalled: Boolean;
constructor Create(AShouldReturn: Boolean = True); // Parameter name AShouldReturn for clarity constructor Create(AShouldReturn: Boolean = True); // Parameter name AShouldReturn for clarity
function Notify: Boolean; // IMycSubscriber implementation. function Notify: Boolean; // TSignal.ISubscriber implementation.
end; end;
[TestFixture] [TestFixture]
@@ -137,7 +137,7 @@ var
returnedValueFromNotify: Boolean; returnedValueFromNotify: Boolean;
begin begin
latch := TLatch.CreateLatch(InitialCount); latch := TLatch.CreateLatch(InitialCount);
returnedValueFromNotify := latch.Notify; // This is TLatch.ILatch (as IMycSubscriber).Notify returnedValueFromNotify := latch.Notify; // This is TLatch.ILatch (as TSignal.ISubscriber).Notify
Assert.AreEqual( Assert.AreEqual(
ExpectedReturn, ExpectedReturn,
@@ -282,18 +282,18 @@ var
subHandle_B_listens_A, subHandle_Mock_listens_B: TSignal.TSubscription; subHandle_B_listens_A, subHandle_Mock_listens_B: TSignal.TSubscription;
begin begin
latchA := TLatch.CreateLatch(1); latchA := TLatch.CreateLatch(1);
latchB := TLatch.CreateLatch(1); // latchB is an IMycSubscriber latchB := TLatch.CreateLatch(1); // latchB is an TSignal.ISubscriber
mockSubForB := TMockSubscriber.Create; mockSubForB := TMockSubscriber.Create;
subHandle_Mock_listens_B := latchB.State.Subscribe(mockSubForB); subHandle_Mock_listens_B := latchB.State.Subscribe(mockSubForB);
// LatchA's state is an IMycSignal. LatchB (as IMycSubscriber) subscribes to it. // LatchA's state is an IMycSignal. LatchB (as TSignal.ISubscriber) subscribes to it.
subHandle_B_listens_A := latchA.State.Subscribe(latchB); subHandle_B_listens_A := latchA.State.Subscribe(latchB);
Assert.IsFalse(latchA.State.IsSet, 'LatchA initially not set.'); Assert.IsFalse(latchA.State.IsSet, 'LatchA initially not set.');
Assert.IsFalse(latchB.State.IsSet, 'LatchB initially not set.'); Assert.IsFalse(latchB.State.IsSet, 'LatchB initially not set.');
Assert.AreEqual(0, mockSubForB.NotifyCount, 'MockSubForB initially not notified.'); Assert.AreEqual(0, mockSubForB.NotifyCount, 'MockSubForB initially not notified.');
latchA.Notify; // Call Notify on LatchA (as IMycSubscriber) latchA.Notify; // Call Notify on LatchA (as TSignal.ISubscriber)
Assert.IsTrue(latchA.State.IsSet, 'LatchA should be set after notify.'); 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.IsTrue(latchB.State.IsSet, 'LatchB should be set after being notified by LatchA.');
+4 -4
View File
@@ -14,13 +14,13 @@ uses
type type
// Helper class to notify a latch when its Notify method is called. // Helper class to notify a latch when its Notify method is called.
TLatchNotifierSubscriber = class(TInterfacedObject, IMycSubscriber) TLatchNotifierSubscriber = class(TInterfacedObject, TSignal.ISubscriber)
private private
FGateLatch: TLatch.ILatch; FGateLatch: TLatch.ILatch;
public public
constructor Create(AGateLatch: TLatch.ILatch); constructor Create(AGateLatch: TLatch.ILatch);
// IMycSubscriber // TSignal.ISubscriber
function Notify: Boolean; // Implements IMycSubscriber.Notify [cite: 46, 181, 299] function Notify: Boolean; // Implements TSignal.ISubscriber.Notify [cite: 46, 181, 299]
end; end;
[TestFixture] [TestFixture]
@@ -251,7 +251,7 @@ var
LPrerequisiteFuture1, LPrerequisiteFuture2: TFuture<Integer>.IFuture; LPrerequisiteFuture1, LPrerequisiteFuture2: TFuture<Integer>.IFuture;
LMainFuture: TFuture<string>.IFuture; LMainFuture: TFuture<string>.IFuture;
LGateLatch: TLatch.ILatch; LGateLatch: TLatch.ILatch;
LSub1, LSub2: IMycSubscriber; // To hold the TLatchNotifierSubscriber instances LSub1, LSub2: TSignal.ISubscriber; // To hold the TLatchNotifierSubscriber instances
LInitStateP1, LInitStateP2: TLatch.ILatch; LInitStateP1, LInitStateP2: TLatch.ILatch;
Subscriptions: array[1..2] of TSignal.TSubscription; // To manage subscriptions Subscriptions: array[1..2] of TSignal.TSubscription; // To manage subscriptions
const const
+2 -2
View File
@@ -108,7 +108,7 @@ procedure TMycTaskFactoryTests.TestRunDelayedJobExecutesAfterAllNotifies;
var var
jobExecuted: Boolean; jobExecuted: Boolean;
jobCompletedLatch: TLatch.ILatch; jobCompletedLatch: TLatch.ILatch;
subscriber: IMycSubscriber; subscriber: TSignal.ISubscriber;
begin begin
jobExecuted := False; jobExecuted := False;
jobCompletedLatch := TLatch.CreateLatch(1); // Changed from Signals.CreateLatch jobCompletedLatch := TLatch.CreateLatch(1); // Changed from Signals.CreateLatch
@@ -124,7 +124,7 @@ begin
Assert.IsNotNull(subscriber, 'Run should return a subscriber for delayed job'); Assert.IsNotNull(subscriber, 'Run should return a subscriber for delayed job');
// Use TMycLatch.Null for comparison // Use TMycLatch.Null for comparison
Assert.AreNotEqual<IMycSubscriber>(TLatch.Null, subscriber, 'Subscriber should not be TMycLatch.Null for StartCount > 0'); Assert.AreNotEqual<TSignal.ISubscriber>(TLatch.Null, subscriber, 'Subscriber should not be TMycLatch.Null for StartCount > 0');
subscriber.Notify; subscriber.Notify;