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;
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
function GetChanged: TState;
public
function Pop(out Res: T): Boolean;
end;
TMycLazyBase<T> = class(TInterfacedObject, IMycLazy<T>)
TMycLazyBase<T> = class(TInterfacedObject, TLazy<T>.ILazy)
strict private
FChanged: TFlag;
FChangeState: TSignal.TSubscription;
@@ -39,15 +78,72 @@ type
TMycChainedLazy<T> = class(TMycLazyBase<T>)
private
FValue: IMycLazy<T>;
FValue: TLazy<T>.ILazy;
protected
function GetValue: T; override;
public
constructor Create(const AValue: IMycLazy<T>);
constructor Create(const AValue: TLazy<T>.ILazy);
end;
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> }
function TMycNullLazy<T>.GetChanged: TState;
@@ -107,7 +203,7 @@ end;
{ TMycChainedLazy<T> }
constructor TMycChainedLazy<T>.Create(const AValue: IMycLazy<T>);
constructor TMycChainedLazy<T>.Create(const AValue: TLazy<T>.ILazy);
begin
inherited Create(AValue.Changed);
FValue := AValue;
@@ -119,4 +215,15 @@ begin
raise Exception.Create('Lazy chain already popped');
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.
+44 -31
View File
@@ -12,8 +12,8 @@ type
// This state is always considered "set".
TMycNullSignal = class(TInterfacedObject, TSignal.ISignal)
public
function Subscribe(Subscriber: IMycSubscriber): Pointer;
procedure Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag);
function Subscribe(Subscriber: TSignal.ISubscriber): Pointer;
procedure Unsubscribe(Tag: TMycNotifyList<TSignal.ISubscriber>.TTag);
end;
// 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.
TMycEvent = class(TInterfacedObject, TSignal.ISignal, TEvent.IEvent)
strict private
FSubscribers: TMycNotifyList<IMycSubscriber>;
FSubscribers: TMycNotifyList<TSignal.ISubscriber>;
protected
function GetSignal: TSignal;
function GetIsSet: Boolean;
public
constructor Create;
destructor Destroy; override;
function Subscribe(Subscriber: IMycSubscriber): Pointer;
procedure Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag);
procedure Trigger;
function Subscribe(Subscriber: TSignal.ISubscriber): Pointer;
procedure Unsubscribe(Tag: TMycNotifyList<TSignal.ISubscriber>.TTag);
function Notify: Boolean;
end;
TMycNullEvent = class(TMycNullSignal, TEvent.IEvent)
private
procedure Trigger;
function GetSignal: TSignal;
function Notify: Boolean;
end;
// 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"
// state (IsSet becomes true), notifies its current subscribers once, and then remains set.
TMycLatch = class(TInterfacedObject, TState.IState, TLatch.ILatch)
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
[volatile]
FCount: Integer; // The internal countdown value for the latch.
@@ -63,10 +65,10 @@ type
destructor Destroy; override;
// ISignal implementation
function Subscribe(Subscriber: IMycSubscriber): Pointer;
procedure Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag);
function Subscribe(Subscriber: TSignal.ISubscriber): Pointer;
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,
// registered subscribers are notified, and the latch becomes permanently set.
function Notify: Boolean;
@@ -83,7 +85,7 @@ type
// Subscribers are notified of relevant state changes.
TMycFlag = class(TInterfacedObject, TState.IState, TFlag.IFlag)
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
[volatile]
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.
class function CreateDirty: TFlag.IFlag; static;
function Subscribe(Subscriber: IMycSubscriber): Pointer;
procedure Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag);
function Subscribe(Subscriber: TSignal.ISubscriber): Pointer;
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.
function Notify: Boolean;
@@ -145,17 +147,23 @@ begin
Result := true;
end;
procedure TMycEvent.Trigger;
function TMycEvent.GetSignal: TSignal;
begin
Result := Self;
end;
function TMycEvent.Notify: Boolean;
begin
FSubscribers.Lock;
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
FSubscribers.Release;
end;
Result := true;
end;
function TMycEvent.Subscribe(Subscriber: IMycSubscriber): Pointer;
function TMycEvent.Subscribe(Subscriber: TSignal.ISubscriber): Pointer;
begin
Result := nil;
if not Assigned(Subscriber) then
@@ -170,7 +178,7 @@ begin
end;
end;
procedure TMycEvent.Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag);
procedure TMycEvent.Unsubscribe(Tag: TMycNotifyList<TSignal.ISubscriber>.TTag);
begin
if Tag = nil then
exit;
@@ -227,7 +235,7 @@ begin
if shouldNotifySubscribers then
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;
// Returns true if the latch has not yet been set by this Notify call (count > 0).
@@ -254,7 +262,7 @@ begin
Result := Self;
end;
function TMycLatch.Subscribe(Subscriber: IMycSubscriber): Pointer;
function TMycLatch.Subscribe(Subscriber: TSignal.ISubscriber): Pointer;
var
alreadySet: Boolean;
begin
@@ -281,7 +289,7 @@ begin
end;
end;
procedure TMycLatch.Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag);
procedure TMycLatch.Unsubscribe(Tag: TMycNotifyList<TSignal.ISubscriber>.TTag);
begin
if Tag = nil then
exit;
@@ -361,9 +369,9 @@ begin
if wasPreviouslyClean then // Only notify subscribers if state changed from clean to dirty.
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;
// 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.
// Here, it reflects whether a state change to dirty occurred due to this call.
Result := wasPreviouslyClean;
@@ -372,7 +380,7 @@ begin
end;
end;
function TMycFlag.Subscribe(Subscriber: IMycSubscriber): Pointer;
function TMycFlag.Subscribe(Subscriber: TSignal.ISubscriber): Pointer;
begin
if not Assigned(Subscriber) then
exit(nil);
@@ -394,7 +402,7 @@ begin
end;
end;
procedure TMycFlag.Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag);
procedure TMycFlag.Unsubscribe(Tag: TMycNotifyList<TSignal.ISubscriber>.TTag);
begin
if Tag = nil then
exit;
@@ -407,12 +415,17 @@ begin
end;
end;
procedure TMycNullEvent.Trigger;
function TMycNullEvent.GetSignal: TSignal;
begin
// nop
Result := TSignal.Null;
end;
function TMycNullSignal.Subscribe(Subscriber: IMycSubscriber): Pointer;
function TMycNullEvent.Notify: Boolean;
begin
Result := false;
end;
function TMycNullSignal.Subscribe(Subscriber: TSignal.ISubscriber): Pointer;
begin
// Since the state is always set, notify the subscriber immediately.
if Assigned(Subscriber) then
@@ -423,7 +436,7 @@ begin
Result := nil;
end;
procedure TMycNullSignal.Unsubscribe(Tag: TMycNotifyList<IMycSubscriber>.TTag);
procedure TMycNullSignal.Unsubscribe(Tag: TMycNotifyList<TSignal.ISubscriber>.TTag);
begin
// Unsubscribing from a null state has no effect.
end;
+5 -5
View File
@@ -36,7 +36,7 @@ type
// Exceptions during Job execution are caught; the first one is stored
// and can be re-raised in the main thread via WaitFor or Teardown.
// Raises ETaskException if the factory is already terminated.
function Run(Job: TProc): IMycSubscriber;
function Run(Job: TProc): TSignal.ISubscriber;
procedure EnqueueJob(const Job: TProc);
@@ -80,7 +80,7 @@ type
function CreateThread(const Proc: TProc): TState.IState;
procedure HandleException;
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;
procedure WaitFor(State: TState.IState);
procedure Teardown;
@@ -98,7 +98,7 @@ type
implementation
type
TMycPendingJob = class(TInterfacedObject, IMycSubscriber)
TMycPendingJob = class(TInterfacedObject, TSignal.ISubscriber)
private
[volatile]
FJob: TProc; // The job to execute when count reaches zero
@@ -112,7 +112,7 @@ type
property Owner: TMycTaskFactory read FOwner;
end;
TMycTaskWait = class sealed(TInterfacedObject, IMycSubscriber)
TMycTaskWait = class sealed(TInterfacedObject, TSignal.ISubscriber)
private
[volatile]
FSemaphore: TSemaphore; // System.SyncObjs.TSemaphore to be released on notification
@@ -303,7 +303,7 @@ begin
TaskManager := nil;
end;
function TMycTaskFactory.Run(Job: TProc): IMycSubscriber;
function TMycTaskFactory.Run(Job: TProc): TSignal.ISubscriber;
begin
if FTerminated <> 0 then
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>;
begin
var Cap := FFuture;
Result := TFuture<S>.Construct(FFuture.Done, function: S begin Result := Proc(Cap.Result); end);
end;
+118 -28
View File
@@ -7,41 +7,80 @@ uses
Myc.Signals;
type
IMycMutable<T> = interface
function GetChanged: TState;
function GetValue: T;
property Changed: TState read GetChanged;
property Value: T read GetValue;
end;
TMutable<T> = record
type
IMutable = interface
{$REGION 'property access'}
function GetChanged: TSignal;
function GetValue: T;
{$ENDREGION}
property Changed: TSignal read GetChanged;
property Value: T read GetValue;
end;
IMycLazy<T> = interface
{$REGION 'property access'}
function GetChanged: TState;
{$ENDREGION}
function Pop(out Res: T): Boolean;
property Changed: TState read GetChanged;
IWriteableMutable = interface(IMutable)
procedure SetValue(const Value: T);
end;
{$REGION 'private'}
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;
TLazy<T> = record
private
FLazy: IMycLazy<T>;
function GetChanged: TState.IState; inline;
type
ILazy = interface
{$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
FNull: IMycLazy<T>;
FNull: ILazy;
class constructor CreateClass;
private
FLazy: ILazy;
function GetChanged: TState; inline;
{$ENDREGION}
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 Value: IMycLazy<T>): IMycLazy<T>; overload; static;
class function Construct(const Changing: TState; const Proc: TFunc<T>): TLazy<T>; overload; static;
class function Construct(const Value: ILazy): TLazy<T>; overload; static;
class operator Implicit(const A: IMycLazy<T>): TLazy<T>; overload;
class operator Implicit(const A: TLazy<T>): IMycLazy<T>; overload;
class property Null: ILazy read FNull;
function Pop(out Res: T): Boolean; inline;
property Changed: TState.IState read GetChanged;
property Changed: TState read GetChanged;
end;
implementation
@@ -49,14 +88,60 @@ implementation
uses
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
FLazy := ALazy;
if not Assigned(FLazy) then
FLazy := FNull;
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
Result := TMycFuncLazy<T>.Create(Changing, Proc);
end;
@@ -66,12 +151,12 @@ begin
FNull := TMycNullLazy<T>.Create;
end;
class function TLazy<T>.Construct(const Value: IMycLazy<T>): IMycLazy<T>;
class function TLazy<T>.Construct(const Value: ILazy): TLazy<T>;
begin
Result := TMycChainedLazy<T>.Create(Value);
end;
function TLazy<T>.GetChanged: TState.IState;
function TLazy<T>.GetChanged: TState;
begin
Result := FLazy.Changed;
end;
@@ -81,14 +166,19 @@ begin
Result := FLazy.Pop(Res);
end;
class operator TLazy<T>.Implicit(const A: IMycLazy<T>): TLazy<T>;
class operator TLazy<T>.Implicit(const A: ILazy): TLazy<T>;
begin
Result.Create(A);
end;
class operator TLazy<T>.Implicit(const A: TLazy<T>): IMycLazy<T>;
class operator TLazy<T>.Implicit(const A: TLazy<T>): ILazy;
begin
Result := A.FLazy;
end;
class operator TLazy<T>.Initialize(out Dest: TLazy<T>);
begin
Dest.FLazy := FNull;
end;
end.
+59 -48
View File
@@ -6,16 +6,16 @@ uses
System.SysUtils;
type
IMycSubscriber = interface
function Notify: Boolean;
end;
TSignal = record
type
ISubscriber = interface
function Notify: Boolean;
end;
TSubscriptionTag = Pointer;
ISignal = interface
function Subscribe(Subscriber: IMycSubscriber): TSubscriptionTag;
function Subscribe(Subscriber: ISubscriber): TSubscriptionTag;
procedure Unsubscribe(Tag: TSubscriptionTag);
end;
@@ -45,38 +45,11 @@ type
class operator Implicit(const A: ISignal): TSignal; 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;
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
type
IState = interface(TSignal.ISignal)
@@ -108,18 +81,54 @@ type
class property Null: IState read FNull;
function Subscribe(Subscriber: IMycSubscriber): TSignal.TSubscription; inline;
procedure Unsubscribe(Tag: Pointer); inline;
function Subscribe(Subscriber: TSignal.ISubscriber): TSignal.TSubscription; inline;
property IsSet: Boolean read GetIsSet;
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).
// It becomes set when the internal countdown reaches zero.
TLatch = record
type
ILatch = interface(IMycSubscriber)
ILatch = interface(TSignal.ISubscriber)
{$REGION 'property access'}
function GetState: TState;
{$ENDREGION}
property State: TState read GetState;
end;
@@ -153,8 +162,10 @@ type
// TFlag represents a resettable flag, typically indicating if a State is "dirty" (requiring attention) or "clean".
TFlag = record
type
IFlag = interface(IMycSubscriber)
IFlag = interface(TSignal.ISubscriber)
{$REGION 'property access'}
function GetState: TState;
{$ENDREGION}
function Reset: Boolean;
property State: TState read GetState;
end;
@@ -260,16 +271,11 @@ begin
Result := FState.IsSet;
end;
function TState.Subscribe(Subscriber: IMycSubscriber): TSignal.TSubscription;
function TState.Subscribe(Subscriber: TSignal.ISubscriber): TSignal.TSubscription;
begin
Result := TSignal.TSubscription.Create(FState, FState.Subscribe(Subscriber));
end;
procedure TState.Unsubscribe(Tag: Pointer);
begin
FState.Unsubscribe(Tag);
end;
class operator TState.Implicit(const A: TState): IState;
begin
Result := A.FState;
@@ -299,14 +305,19 @@ begin
FNull := TMycNullEvent.Create();
end;
function TEvent.Subscribe(Subscriber: IMycSubscriber): TSignal.TSubscription;
class function TEvent.CreateEvent: TEvent;
begin
Result := TSignal.TSubscription.Create(FEvent, FEvent.Subscribe(Subscriber));
Result := TMycEvent.Create;
end;
procedure TEvent.Unsubscribe(Tag: Pointer);
function TEvent.GetSignal: TSignal;
begin
FEvent.Unsubscribe(Tag);
Result := FEvent.Signal;
end;
function TEvent.Notify: Boolean;
begin
Result := FEvent.Notify;
end;
class operator TEvent.Implicit(const A: TEvent): IEvent;
@@ -443,7 +454,7 @@ begin
FSignal := FNull;
end;
function TSignal.Subscribe(Subscriber: IMycSubscriber): TSubscription;
function TSignal.Subscribe(Subscriber: ISubscriber): TSubscription;
begin
Result := TSubscription.Create(FSignal, FSignal.Subscribe(Subscriber));
end;
+1 -1
View File
@@ -30,7 +30,7 @@ uses
System.Generics.Collections;
type
TMycExecMock = class(TInterfacedObject, IMycSubscriber)
TMycExecMock = class(TInterfacedObject, TSignal.ISubscriber)
private
FProc: TProc;
public
+21 -21
View File
@@ -13,8 +13,8 @@ type
[TestFixture]
TTestMycCoreLazy = class(TObject)
private
procedure Helper_ConsumeInitialPop(const ALazy: IMycLazy<Integer>; InitialExpectedValue: Integer); overload;
procedure Helper_ConsumeInitialPop(const ALazy: IMycLazy<string>; const InitialExpectedValue: string); overload;
procedure Helper_ConsumeInitialPop(const ALazy: TLazy<Integer>.ILazy; InitialExpectedValue: Integer); overload;
procedure Helper_ConsumeInitialPop(const ALazy: TLazy<String>.ILazy; const InitialExpectedValue: string); overload;
public
[Setup]
procedure Setup;
@@ -65,7 +65,7 @@ uses
{ 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
tempValue: Integer;
popResult: Boolean;
@@ -77,7 +77,7 @@ begin
Assert.IsFalse(ALazy.GetChanged.IsSet, 'Changed.IsSet should be false after consuming initial pop');
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
tempValue: string;
popResult: Boolean;
@@ -102,10 +102,10 @@ end;
// == Tests for TMycNullLazy<T> ==
procedure TTestMycCoreLazy.TestNullLazy_GetChanged_IsAlwaysNullState;
var
nullLazy: IMycLazy<Integer>;
nullLazy: TLazy<Integer>.ILazy;
changedState: TState.IState;
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');
changedState := nullLazy.GetChanged;
Assert.AreSame(TState.Null, changedState, 'TMycNullLazy.GetChanged should return TState.Null');
@@ -114,11 +114,11 @@ end;
procedure TTestMycCoreLazy.TestNullLazy_Pop_ReturnsDefaultIntegerAndTrue;
var
nullLazy: IMycLazy<Integer>;
nullLazy: TLazy<Integer>.ILazy;
value: Integer;
result: Boolean;
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');
value := 123;
result := nullLazy.Pop(value);
@@ -128,11 +128,11 @@ end;
procedure TTestMycCoreLazy.TestNullLazy_Pop_ReturnsDefaultStringAndTrue;
var
nullLazy: IMycLazy<string>;
nullLazy: TLazy<String>.ILazy;
value: string;
result: Boolean;
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');
value := 'test';
result := nullLazy.Pop(value);
@@ -142,11 +142,11 @@ end;
procedure TTestMycCoreLazy.TestNullLazy_Pop_ReturnsDefaultInterfaceAndTrue;
var
nullLazy: IMycLazy<TState.IState>;
nullLazy: TLazy<TState.IState>.ILazy;
value: TState.IState;
result: Boolean;
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');
value := TState.Null;
result := nullLazy.Pop(value);
@@ -157,7 +157,7 @@ end;
// == Tests for TMycFuncLazy<T> - Initial State by Design ==
procedure TTestMycCoreLazy.TestFuncLazy_GetChanged_IsAlwaysTrueAfterCreation;
var
funcLazy: IMycLazy<Integer>;
funcLazy: TLazy<Integer>.ILazy;
changedState: TState.IState;
sourceDirty: TFlag.IFlag;
begin
@@ -172,7 +172,7 @@ end;
procedure TTestMycCoreLazy.TestFuncLazy_FirstPop_AlwaysEvaluatesAndResetsChanged;
var
funcLazy: IMycLazy<Integer>;
funcLazy: TLazy<Integer>.ILazy;
value: Integer;
result: Boolean;
sourceDirty: TFlag.IFlag;
@@ -206,7 +206,7 @@ end;
procedure TTestMycCoreLazy.TestFuncLazy_AfterFirstPop_IfNoSourceChange_GetChangedIsFalse;
var
funcLazy: IMycLazy<Integer>;
funcLazy: TLazy<Integer>.ILazy;
sourceDirty: TFlag.IFlag;
initialProcValue: Integer;
begin
@@ -220,7 +220,7 @@ end;
procedure TTestMycCoreLazy.TestFuncLazy_AfterFirstPop_IfNoSourceChange_PopReturnsFalse_ValueUndefined;
var
funcLazy: IMycLazy<Integer>;
funcLazy: TLazy<Integer>.ILazy;
value: Integer;
result: Boolean;
sourceDirty: TFlag.IFlag;
@@ -249,7 +249,7 @@ end;
procedure TTestMycCoreLazy.TestFuncLazy_AfterSourceChange_GetChanged_IsSet;
var
funcLazy: IMycLazy<Integer>;
funcLazy: TLazy<Integer>.ILazy;
changedState: TState.IState;
sourceDirty: TFlag.IFlag;
initialProcValue: Integer;
@@ -266,7 +266,7 @@ end;
procedure TTestMycCoreLazy.TestFuncLazy_AfterSourceChange_Pop_ReturnsTrueAndProcResult_ResetsChanged;
var
funcLazy: IMycLazy<Integer>;
funcLazy: TLazy<Integer>.ILazy;
value: Integer;
result: Boolean;
sourceDirty: TFlag.IFlag;
@@ -304,7 +304,7 @@ end;
procedure TTestMycCoreLazy.TestFuncLazy_Pop_Twice_SourceUnchanged_SecondPopReturnsFalse_ValueUndefined;
var
funcLazy: IMycLazy<Integer>;
funcLazy: TLazy<Integer>.ILazy;
value: Integer;
resultPop1, resultPop2: Boolean;
sourceDirty: TFlag.IFlag;
@@ -336,7 +336,7 @@ end;
// to TestFuncLazy_SourceInteraction_NotifyOnSetSourceDoesNotRetriggerLazy
procedure TTestMycCoreLazy.TestFuncLazy_SourceInteraction_NotifyOnSetSourceDoesNotRetriggerLazy;
var
funcLazy: IMycLazy<Integer>;
funcLazy: TLazy<Integer>.ILazy;
value: Integer;
result: Boolean;
sourceDirty: TFlag.IFlag;
@@ -417,7 +417,7 @@ end;
procedure TTestMycCoreLazy.TestFuncLazy_SourceIsInitiallySet_PopBehavesCorrectly;
var
funcLazy: IMycLazy<Integer>;
funcLazy: TLazy<Integer>.ILazy;
value: Integer;
result: Boolean;
sourceDirty: TFlag.IFlag;
+16 -16
View File
@@ -137,7 +137,7 @@ end;
procedure TTestMyLazy.TestConstruct_InitialChanged_IsAlwaysTrue;
var
lazyIntf: IMycLazy<Integer>;
lazyIntf: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>;
procExecuted: Boolean;
begin
@@ -161,7 +161,7 @@ end;
procedure TTestMyLazy.TestConstruct_FirstPop_SucceedsAndResetsChanged;
var
lazyIntf: IMycLazy<Integer>;
lazyIntf: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>;
procExecuted: Boolean;
expectedValue: Integer;
@@ -185,7 +185,7 @@ end;
procedure TTestMyLazy.TestConstruct_StateInteraction_SignalTriggersChanged;
var
lazyIntf: IMycLazy<Integer>;
lazyIntf: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>;
expectedValue: Integer;
begin
@@ -203,7 +203,7 @@ end;
procedure TTestMyLazy.TestConstruct_StateInteraction_PopResetsChangedAfterSignal;
var
lazyIntf: IMycLazy<Integer>;
lazyIntf: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>;
val: Integer;
popResult: Boolean;
@@ -234,7 +234,7 @@ end;
procedure TTestMyLazy.TestConstruct_StateInteraction_NotifyOnAlreadySetSource_DoesNotRetrigger;
var
lazyIntf: IMycLazy<Integer>;
lazyIntf: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>;
val: Integer;
popResult: Boolean;
@@ -280,7 +280,7 @@ end;
procedure TTestMyLazy.TestConstruct_Destruction_UnsubscribesAndNoCrashOnSourceNotify;
var
lazyIntf: IMycLazy<Integer>;
lazyIntf: TLazy<Integer>.ILazy;
localChangingSignal: TFlag.IFlag; // Use a local signal for this test to control its lifetime
begin
localChangingSignal := TFlag.CreateFlag;
@@ -293,7 +293,7 @@ begin
var lazyRec: TLazy<Integer> := lazyIntf; // Wrap for initial pop
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.
Assert.WillNotRaise(
@@ -308,11 +308,11 @@ begin
localChangingSignal := nil; // Clean up the local signal itself.
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;
var
originalLazyIntf: IMycLazy<Integer>;
originalLazyIntf: TLazy<Integer>.ILazy;
wrappedLazyRec: TLazy<Integer>;
expectedValue: Integer;
begin
@@ -335,7 +335,7 @@ end;
procedure TTestMyLazy.TestCreateWithExistingLazy_DelegatesPopCorrectly;
var
originalLazyIntf: IMycLazy<Integer>;
originalLazyIntf: TLazy<Integer>.ILazy;
wrappedLazyRec: TLazy<Integer>;
val: Integer;
popResult: Boolean;
@@ -376,7 +376,7 @@ end;
procedure TTestMyLazy.TestImplicitOperator_FromInterfaceToRecord;
var
lazyIntf: IMycLazy<Integer>;
lazyIntf: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>;
expectedValue: Integer;
begin
@@ -384,7 +384,7 @@ begin
lazyIntf := TLazy<Integer>.Construct(FChangingSignal.State, function: Integer begin Result := expectedValue; end);
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
Assert.IsTrue(lazyRec.Changed.IsSet, 'Record (from intf): Initial Changed.IsSet should be true');
@@ -393,9 +393,9 @@ end;
procedure TTestMyLazy.TestImplicitOperator_FromRecordToInterface;
var
lazyIntfFromConstruct: IMycLazy<Integer>;
lazyIntfFromConstruct: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>;
lazyIntfFromRecord: IMycLazy<Integer>;
lazyIntfFromRecord: TLazy<Integer>.ILazy;
val: Integer;
expectedValue: Integer;
begin
@@ -403,7 +403,7 @@ begin
lazyIntfFromConstruct := TLazy<Integer>.Construct(FChangingSignal.State, function: Integer begin Result := expectedValue; end);
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
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) ==
procedure TTestMyLazy.TestPop_AfterInitialAndNoSignal_ReturnsFalseAndResUndefined;
var
lazyIntf: IMycLazy<Integer>;
lazyIntf: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>;
val: Integer; // Value will not be checked as Pop returns false
popResult: Boolean;
+5 -5
View File
@@ -9,7 +9,7 @@ uses
type
// Helper class to mock a subscriber (reuse or redefine if not in common unit)
TMockSubscriber = class(TInterfacedObject, IMycSubscriber)
TMockSubscriber = class(TInterfacedObject, TSignal.ISubscriber)
public
NotifyCount: Integer;
NotifyShouldReturn: Boolean;
@@ -17,7 +17,7 @@ type
LastReturnedValueFromSource: Boolean; // To store what the source's Notify returned
constructor Create(AShouldReturn: Boolean = True);
function Notify: Boolean; // IMycSubscriber implementation.
function Notify: Boolean; // TSignal.ISubscriber implementation.
end;
[TestFixture]
@@ -164,7 +164,7 @@ var
begin
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(notifyResult, 'Notify() should return True indicating a state change from clean to dirty.');
@@ -177,7 +177,7 @@ var
begin
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.IsFalse(notifyResult, 'Notify() should return False as flag was already dirty (no state change to dirty).');
@@ -303,7 +303,7 @@ begin
end;
// 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).
procedure TTestMycDirtyFlag.TestChain_A_Notifies_B_SimplePropagation;
+6 -6
View File
@@ -9,14 +9,14 @@ uses
type
// Helper class to mock a subscriber.
TMockSubscriber = class(TInterfacedObject, IMycSubscriber)
TMockSubscriber = class(TInterfacedObject, TSignal.ISubscriber)
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.
function Notify: Boolean; // TSignal.ISubscriber implementation.
end;
[TestFixture]
@@ -137,7 +137,7 @@ var
returnedValueFromNotify: Boolean;
begin
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(
ExpectedReturn,
@@ -282,18 +282,18 @@ var
subHandle_B_listens_A, subHandle_Mock_listens_B: TSignal.TSubscription;
begin
latchA := TLatch.CreateLatch(1);
latchB := TLatch.CreateLatch(1); // latchB is an IMycSubscriber
latchB := TLatch.CreateLatch(1); // latchB is an TSignal.ISubscriber
mockSubForB := TMockSubscriber.Create;
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);
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)
latchA.Notify; // Call Notify on LatchA (as TSignal.ISubscriber)
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.');
+4 -4
View File
@@ -14,13 +14,13 @@ uses
type
// Helper class to notify a latch when its Notify method is called.
TLatchNotifierSubscriber = class(TInterfacedObject, IMycSubscriber)
TLatchNotifierSubscriber = class(TInterfacedObject, TSignal.ISubscriber)
private
FGateLatch: TLatch.ILatch;
public
constructor Create(AGateLatch: TLatch.ILatch);
// IMycSubscriber
function Notify: Boolean; // Implements IMycSubscriber.Notify [cite: 46, 181, 299]
// TSignal.ISubscriber
function Notify: Boolean; // Implements TSignal.ISubscriber.Notify [cite: 46, 181, 299]
end;
[TestFixture]
@@ -251,7 +251,7 @@ var
LPrerequisiteFuture1, LPrerequisiteFuture2: TFuture<Integer>.IFuture;
LMainFuture: TFuture<string>.IFuture;
LGateLatch: TLatch.ILatch;
LSub1, LSub2: IMycSubscriber; // To hold the TLatchNotifierSubscriber instances
LSub1, LSub2: TSignal.ISubscriber; // To hold the TLatchNotifierSubscriber instances
LInitStateP1, LInitStateP2: TLatch.ILatch;
Subscriptions: array[1..2] of TSignal.TSubscription; // To manage subscriptions
const
+2 -2
View File
@@ -108,7 +108,7 @@ procedure TMycTaskFactoryTests.TestRunDelayedJobExecutesAfterAllNotifies;
var
jobExecuted: Boolean;
jobCompletedLatch: TLatch.ILatch;
subscriber: IMycSubscriber;
subscriber: TSignal.ISubscriber;
begin
jobExecuted := False;
jobCompletedLatch := TLatch.CreateLatch(1); // Changed from Signals.CreateLatch
@@ -124,7 +124,7 @@ begin
Assert.IsNotNull(subscriber, 'Run should return a subscriber for delayed job');
// 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;