From c4d8607d17e75d0fa79a06d82e1a4bee9551843c Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Fri, 6 Jun 2025 01:22:25 +0200 Subject: [PATCH] TMutable --- Src/Myc.Core.Lazy.pas | 117 ++++++++++++++++++++++++-- Src/Myc.Core.Signals.pas | 75 ++++++++++------- Src/Myc.Core.Tasks.pas | 10 +-- Src/Myc.Futures.pas | 1 - Src/Myc.Lazy.pas | 146 ++++++++++++++++++++++++++------- Src/Myc.Signals.pas | 107 +++++++++++++----------- Src/Myc.TaskManager.pas | 2 +- Src/Myc.Test.Core.Lazy.pas | 42 +++++----- Src/Myc.Test.Lazy.pas | 32 ++++---- Src/Myc.Test.Signals.Dirty.pas | 10 +-- Src/Myc.Test.Signals.Latch.pas | 12 +-- Test/TestCoreFutures.pas | 8 +- Test/TestTasks.pas | 4 +- 13 files changed, 393 insertions(+), 173 deletions(-) diff --git a/Src/Myc.Core.Lazy.pas b/Src/Myc.Core.Lazy.pas index 1078a0e..a04e591 100644 --- a/Src/Myc.Core.Lazy.pas +++ b/Src/Myc.Core.Lazy.pas @@ -8,14 +8,53 @@ uses Myc.Lazy; type - TMycNullLazy = class(TInterfacedObject, IMycLazy) + TMycNullMutable = class(TInterfacedObject, TMutable.IMutable) + protected + function GetChanged: TSignal; + function GetValue: T; + end; + + TMycMutableBase = class(TInterfacedObject, TMutable.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 = class(TMycMutableBase) + private + FProc: TFunc; + protected + function GetValue: T; override; + public + constructor Create(const AChanged: TSignal; const AProc: TFunc); + end; + + TMycWriteableMutable = class(TInterfacedObject, TMutable.IMutable, TMutable.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 = class(TInterfacedObject, TLazy.ILazy) protected function GetChanged: TState; public function Pop(out Res: T): Boolean; end; - TMycLazyBase = class(TInterfacedObject, IMycLazy) + TMycLazyBase = class(TInterfacedObject, TLazy.ILazy) strict private FChanged: TFlag; FChangeState: TSignal.TSubscription; @@ -39,15 +78,72 @@ type TMycChainedLazy = class(TMycLazyBase) private - FValue: IMycLazy; + FValue: TLazy.ILazy; protected function GetValue: T; override; public - constructor Create(const AValue: IMycLazy); + constructor Create(const AValue: TLazy.ILazy); end; implementation +{ TMycNullMutable } + +function TMycNullMutable.GetChanged: TSignal; +begin + Result := TSignal.Null; +end; + +function TMycNullMutable.GetValue: T; +begin + Result := Default(T); +end; + +{ TMycMutableBase } + +constructor TMycMutableBase.Create(const AChanged: TSignal); +begin + inherited Create; + FChanged := TEvent.CreateEvent; + FChangeState := AChanged.Subscribe(FChanged); +end; + +destructor TMycMutableBase.Destroy; +begin + FChangeState.Unsubscribe; + inherited; +end; + +function TMycMutableBase.GetChanged: TSignal; +begin + Result := FChanged.Signal; +end; + +{ TMycWriteableMutable } + +constructor TMycWriteableMutable.Create(const AValue: T); +begin + inherited Create; + FValue := AValue; + FChanged := TEvent.CreateEvent; +end; + +function TMycWriteableMutable.GetChanged: TSignal; +begin + Result := FChanged.Signal; +end; + +function TMycWriteableMutable.GetValue: T; +begin + Result := FValue; +end; + +procedure TMycWriteableMutable.SetValue(const Value: T); +begin + FValue := Value; + FChanged.Notify; +end; + { TMycNullLazy } function TMycNullLazy.GetChanged: TState; @@ -107,7 +203,7 @@ end; { TMycChainedLazy } -constructor TMycChainedLazy.Create(const AValue: IMycLazy); +constructor TMycChainedLazy.Create(const AValue: TLazy.ILazy); begin inherited Create(AValue.Changed); FValue := AValue; @@ -119,4 +215,15 @@ begin raise Exception.Create('Lazy chain already popped'); end; +constructor TMycFuncMutable.Create(const AChanged: TSignal; const AProc: TFunc); +begin + inherited Create(AChanged); + FProc := AProc; +end; + +function TMycFuncMutable.GetValue: T; +begin + Result := FProc(); +end; + end. diff --git a/Src/Myc.Core.Signals.pas b/Src/Myc.Core.Signals.pas index b1feb79..241fdf6 100644 --- a/Src/Myc.Core.Signals.pas +++ b/Src/Myc.Core.Signals.pas @@ -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.TTag); + function Subscribe(Subscriber: TSignal.ISubscriber): Pointer; + procedure Unsubscribe(Tag: TMycNotifyList.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; + FSubscribers: TMycNotifyList; protected + function GetSignal: TSignal; function GetIsSet: Boolean; public constructor Create; destructor Destroy; override; - function Subscribe(Subscriber: IMycSubscriber): Pointer; - procedure Unsubscribe(Tag: TMycNotifyList.TTag); - procedure Trigger; + function Subscribe(Subscriber: TSignal.ISubscriber): Pointer; + procedure Unsubscribe(Tag: TMycNotifyList.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; // List of subscribers waiting for this latch to be set. + FSubscribers: TMycNotifyList; // 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.TTag); + function Subscribe(Subscriber: TSignal.ISubscriber): Pointer; + procedure Unsubscribe(Tag: TMycNotifyList.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; // List of subscribers interested in state changes of this dirty flag. + FSubscribers: TMycNotifyList; // 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.TTag); + function Subscribe(Subscriber: TSignal.ISubscriber): Pointer; + procedure Unsubscribe(Tag: TMycNotifyList.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.TTag); +procedure TMycEvent.Unsubscribe(Tag: TMycNotifyList.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.TTag); +procedure TMycLatch.Unsubscribe(Tag: TMycNotifyList.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.TTag); +procedure TMycFlag.Unsubscribe(Tag: TMycNotifyList.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.TTag); +procedure TMycNullSignal.Unsubscribe(Tag: TMycNotifyList.TTag); begin // Unsubscribing from a null state has no effect. end; diff --git a/Src/Myc.Core.Tasks.pas b/Src/Myc.Core.Tasks.pas index a710d2a..4669745 100644 --- a/Src/Myc.Core.Tasks.pas +++ b/Src/Myc.Core.Tasks.pas @@ -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'); diff --git a/Src/Myc.Futures.pas b/Src/Myc.Futures.pas index bab18bc..fb83189 100644 --- a/Src/Myc.Futures.pas +++ b/Src/Myc.Futures.pas @@ -81,7 +81,6 @@ end; function TFuture.Chain(const Proc: TFunc): TFuture; begin var Cap := FFuture; - Result := TFuture.Construct(FFuture.Done, function: S begin Result := Proc(Cap.Result); end); end; diff --git a/Src/Myc.Lazy.pas b/Src/Myc.Lazy.pas index 8dd74b5..bbe6f1d 100644 --- a/Src/Myc.Lazy.pas +++ b/Src/Myc.Lazy.pas @@ -7,41 +7,80 @@ uses Myc.Signals; type - IMycMutable = interface - function GetChanged: TState; - function GetValue: T; - property Changed: TState read GetChanged; - property Value: T read GetValue; - end; + TMutable = 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 = 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); + class operator Implicit(const A: IMutable): TMutable; overload; + class operator Implicit(const A: TMutable): IMutable; overload; + + class property Null: IMutable read FNull; + + class function Construct(const Changing: TSignal; const Proc: TFunc): TMutable; overload; static; + + class function CreateWriteable(const Init: T): IWriteableMutable; overload; static; + + property Changed: TSignal read GetChanged; end; TLazy = record - private - FLazy: IMycLazy; - 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; + FNull: ILazy; class constructor CreateClass; + private + FLazy: ILazy; + function GetChanged: TState; inline; + {$ENDREGION} public - constructor Create(const ALazy: IMycLazy); + constructor Create(const ALazy: ILazy); + class operator Initialize(out Dest: TLazy); + class operator Implicit(const A: ILazy): TLazy; overload; + class operator Implicit(const A: TLazy): ILazy; overload; - class function Construct(const Changing: TState.IState; const Proc: TFunc): IMycLazy; overload; static; - class function Construct(const Value: IMycLazy): IMycLazy; overload; static; + class function Construct(const Changing: TState; const Proc: TFunc): TLazy; overload; static; + class function Construct(const Value: ILazy): TLazy; overload; static; - class operator Implicit(const A: IMycLazy): TLazy; overload; - class operator Implicit(const A: TLazy): IMycLazy; 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.Create(const ALazy: IMycLazy); +{ TMutable } + +constructor TMutable.Create(const AMutable: IMutable); +begin + FMutable := AMutable; + if not Assigned(FMutable) then + FMutable := FNull; +end; + +class constructor TMutable.CreateClass; +begin + FNull := TMycNullMutable.Create; +end; + +class function TMutable.Construct(const Changing: TSignal; const Proc: TFunc): TMutable; +begin + Result := TMycFuncMutable.Create(Changing, Proc); +end; + +class function TMutable.CreateWriteable(const Init: T): IWriteableMutable; +begin + Result := TMycWriteableMutable.Create(Init); +end; + +function TMutable.GetChanged: TSignal; +begin + Result := FMutable.Changed; +end; + +class operator TMutable.Implicit(const A: TMutable): IMutable; +begin + Result := A.FMutable; +end; + +class operator TMutable.Implicit(const A: IMutable): TMutable; +begin + Result.Create(A); +end; + +class operator TMutable.Initialize(out Dest: TMutable); +begin + Dest.FMutable := FNull; +end; + +{ TLazy } + +constructor TLazy.Create(const ALazy: ILazy); begin FLazy := ALazy; if not Assigned(FLazy) then FLazy := FNull; end; -class function TLazy.Construct(const Changing: TState.IState; const Proc: TFunc): IMycLazy; +class function TLazy.Construct(const Changing: TState; const Proc: TFunc): TLazy; begin Result := TMycFuncLazy.Create(Changing, Proc); end; @@ -66,12 +151,12 @@ begin FNull := TMycNullLazy.Create; end; -class function TLazy.Construct(const Value: IMycLazy): IMycLazy; +class function TLazy.Construct(const Value: ILazy): TLazy; begin Result := TMycChainedLazy.Create(Value); end; -function TLazy.GetChanged: TState.IState; +function TLazy.GetChanged: TState; begin Result := FLazy.Changed; end; @@ -81,14 +166,19 @@ begin Result := FLazy.Pop(Res); end; -class operator TLazy.Implicit(const A: IMycLazy): TLazy; +class operator TLazy.Implicit(const A: ILazy): TLazy; begin Result.Create(A); end; -class operator TLazy.Implicit(const A: TLazy): IMycLazy; +class operator TLazy.Implicit(const A: TLazy): ILazy; begin Result := A.FLazy; end; +class operator TLazy.Initialize(out Dest: TLazy); +begin + Dest.FLazy := FNull; +end; + end. diff --git a/Src/Myc.Signals.pas b/Src/Myc.Signals.pas index 41e04fa..2fb0bd1 100644 --- a/Src/Myc.Signals.pas +++ b/Src/Myc.Signals.pas @@ -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; diff --git a/Src/Myc.TaskManager.pas b/Src/Myc.TaskManager.pas index 24ab6ab..943c647 100644 --- a/Src/Myc.TaskManager.pas +++ b/Src/Myc.TaskManager.pas @@ -30,7 +30,7 @@ uses System.Generics.Collections; type - TMycExecMock = class(TInterfacedObject, IMycSubscriber) + TMycExecMock = class(TInterfacedObject, TSignal.ISubscriber) private FProc: TProc; public diff --git a/Src/Myc.Test.Core.Lazy.pas b/Src/Myc.Test.Core.Lazy.pas index 13a8774..aa8c600 100644 --- a/Src/Myc.Test.Core.Lazy.pas +++ b/Src/Myc.Test.Core.Lazy.pas @@ -13,8 +13,8 @@ type [TestFixture] TTestMycCoreLazy = class(TObject) private - procedure Helper_ConsumeInitialPop(const ALazy: IMycLazy; InitialExpectedValue: Integer); overload; - procedure Helper_ConsumeInitialPop(const ALazy: IMycLazy; const InitialExpectedValue: string); overload; + procedure Helper_ConsumeInitialPop(const ALazy: TLazy.ILazy; InitialExpectedValue: Integer); overload; + procedure Helper_ConsumeInitialPop(const ALazy: TLazy.ILazy; const InitialExpectedValue: string); overload; public [Setup] procedure Setup; @@ -65,7 +65,7 @@ uses { TTestMycCoreLazy Helper Methods } -procedure TTestMycCoreLazy.Helper_ConsumeInitialPop(const ALazy: IMycLazy; InitialExpectedValue: Integer); +procedure TTestMycCoreLazy.Helper_ConsumeInitialPop(const ALazy: TLazy.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; const InitialExpectedValue: string); +procedure TTestMycCoreLazy.Helper_ConsumeInitialPop(const ALazy: TLazy.ILazy; const InitialExpectedValue: string); var tempValue: string; popResult: Boolean; @@ -102,10 +102,10 @@ end; // == Tests for TMycNullLazy == procedure TTestMycCoreLazy.TestNullLazy_GetChanged_IsAlwaysNullState; var - nullLazy: IMycLazy; + nullLazy: TLazy.ILazy; changedState: TState.IState; begin - nullLazy := TMycNullLazy.Create as IMycLazy; + nullLazy := TMycNullLazy.Create as TLazy.ILazy; Assert.IsNotNull(nullLazy, 'TMycNullLazy 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; + nullLazy: TLazy.ILazy; value: Integer; result: Boolean; begin - nullLazy := TMycNullLazy.Create as IMycLazy; + nullLazy := TMycNullLazy.Create as TLazy.ILazy; Assert.IsNotNull(nullLazy, 'TMycNullLazy instance should not be nil'); value := 123; result := nullLazy.Pop(value); @@ -128,11 +128,11 @@ end; procedure TTestMycCoreLazy.TestNullLazy_Pop_ReturnsDefaultStringAndTrue; var - nullLazy: IMycLazy; + nullLazy: TLazy.ILazy; value: string; result: Boolean; begin - nullLazy := TMycNullLazy.Create as IMycLazy; + nullLazy := TMycNullLazy.Create as TLazy.ILazy; Assert.IsNotNull(nullLazy, 'TMycNullLazy instance should not be nil'); value := 'test'; result := nullLazy.Pop(value); @@ -142,11 +142,11 @@ end; procedure TTestMycCoreLazy.TestNullLazy_Pop_ReturnsDefaultInterfaceAndTrue; var - nullLazy: IMycLazy; + nullLazy: TLazy.ILazy; value: TState.IState; result: Boolean; begin - nullLazy := TMycNullLazy.Create as IMycLazy; + nullLazy := TMycNullLazy.Create as TLazy.ILazy; Assert.IsNotNull(nullLazy, 'TMycNullLazy instance should not be nil'); value := TState.Null; result := nullLazy.Pop(value); @@ -157,7 +157,7 @@ end; // == Tests for TMycFuncLazy - Initial State by Design == procedure TTestMycCoreLazy.TestFuncLazy_GetChanged_IsAlwaysTrueAfterCreation; var - funcLazy: IMycLazy; + funcLazy: TLazy.ILazy; changedState: TState.IState; sourceDirty: TFlag.IFlag; begin @@ -172,7 +172,7 @@ end; procedure TTestMycCoreLazy.TestFuncLazy_FirstPop_AlwaysEvaluatesAndResetsChanged; var - funcLazy: IMycLazy; + funcLazy: TLazy.ILazy; value: Integer; result: Boolean; sourceDirty: TFlag.IFlag; @@ -206,7 +206,7 @@ end; procedure TTestMycCoreLazy.TestFuncLazy_AfterFirstPop_IfNoSourceChange_GetChangedIsFalse; var - funcLazy: IMycLazy; + funcLazy: TLazy.ILazy; sourceDirty: TFlag.IFlag; initialProcValue: Integer; begin @@ -220,7 +220,7 @@ end; procedure TTestMycCoreLazy.TestFuncLazy_AfterFirstPop_IfNoSourceChange_PopReturnsFalse_ValueUndefined; var - funcLazy: IMycLazy; + funcLazy: TLazy.ILazy; value: Integer; result: Boolean; sourceDirty: TFlag.IFlag; @@ -249,7 +249,7 @@ end; procedure TTestMycCoreLazy.TestFuncLazy_AfterSourceChange_GetChanged_IsSet; var - funcLazy: IMycLazy; + funcLazy: TLazy.ILazy; changedState: TState.IState; sourceDirty: TFlag.IFlag; initialProcValue: Integer; @@ -266,7 +266,7 @@ end; procedure TTestMycCoreLazy.TestFuncLazy_AfterSourceChange_Pop_ReturnsTrueAndProcResult_ResetsChanged; var - funcLazy: IMycLazy; + funcLazy: TLazy.ILazy; value: Integer; result: Boolean; sourceDirty: TFlag.IFlag; @@ -304,7 +304,7 @@ end; procedure TTestMycCoreLazy.TestFuncLazy_Pop_Twice_SourceUnchanged_SecondPopReturnsFalse_ValueUndefined; var - funcLazy: IMycLazy; + funcLazy: TLazy.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; + funcLazy: TLazy.ILazy; value: Integer; result: Boolean; sourceDirty: TFlag.IFlag; @@ -417,7 +417,7 @@ end; procedure TTestMycCoreLazy.TestFuncLazy_SourceIsInitiallySet_PopBehavesCorrectly; var - funcLazy: IMycLazy; + funcLazy: TLazy.ILazy; value: Integer; result: Boolean; sourceDirty: TFlag.IFlag; diff --git a/Src/Myc.Test.Lazy.pas b/Src/Myc.Test.Lazy.pas index 917db40..dcd3bf5 100644 --- a/Src/Myc.Test.Lazy.pas +++ b/Src/Myc.Test.Lazy.pas @@ -137,7 +137,7 @@ end; procedure TTestMyLazy.TestConstruct_InitialChanged_IsAlwaysTrue; var - lazyIntf: IMycLazy; + lazyIntf: TLazy.ILazy; lazyRec: TLazy; procExecuted: Boolean; begin @@ -161,7 +161,7 @@ end; procedure TTestMyLazy.TestConstruct_FirstPop_SucceedsAndResetsChanged; var - lazyIntf: IMycLazy; + lazyIntf: TLazy.ILazy; lazyRec: TLazy; procExecuted: Boolean; expectedValue: Integer; @@ -185,7 +185,7 @@ end; procedure TTestMyLazy.TestConstruct_StateInteraction_SignalTriggersChanged; var - lazyIntf: IMycLazy; + lazyIntf: TLazy.ILazy; lazyRec: TLazy; expectedValue: Integer; begin @@ -203,7 +203,7 @@ end; procedure TTestMyLazy.TestConstruct_StateInteraction_PopResetsChangedAfterSignal; var - lazyIntf: IMycLazy; + lazyIntf: TLazy.ILazy; lazyRec: TLazy; val: Integer; popResult: Boolean; @@ -234,7 +234,7 @@ end; procedure TTestMyLazy.TestConstruct_StateInteraction_NotifyOnAlreadySetSource_DoesNotRetrigger; var - lazyIntf: IMycLazy; + lazyIntf: TLazy.ILazy; lazyRec: TLazy; val: Integer; popResult: Boolean; @@ -280,7 +280,7 @@ end; procedure TTestMyLazy.TestConstruct_Destruction_UnsubscribesAndNoCrashOnSourceNotify; var - lazyIntf: IMycLazy; + lazyIntf: TLazy.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 := 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.Create with a pre-existing (non-nil) IMycLazy == +// == Tests for TLazy.Create with a pre-existing (non-nil) ILazy == procedure TTestMyLazy.TestCreateWithExistingLazy_DelegatesChangedCorrectly; var - originalLazyIntf: IMycLazy; + originalLazyIntf: TLazy.ILazy; wrappedLazyRec: TLazy; expectedValue: Integer; begin @@ -335,7 +335,7 @@ end; procedure TTestMyLazy.TestCreateWithExistingLazy_DelegatesPopCorrectly; var - originalLazyIntf: IMycLazy; + originalLazyIntf: TLazy.ILazy; wrappedLazyRec: TLazy; val: Integer; popResult: Boolean; @@ -376,7 +376,7 @@ end; procedure TTestMyLazy.TestImplicitOperator_FromInterfaceToRecord; var - lazyIntf: IMycLazy; + lazyIntf: TLazy.ILazy; lazyRec: TLazy; expectedValue: Integer; begin @@ -384,7 +384,7 @@ begin lazyIntf := TLazy.Construct(FChangingSignal.State, function: Integer begin Result := expectedValue; end); Assert.IsNotNull(lazyIntf, 'Interface should be assigned'); - lazyRec := lazyIntf; // Implicit conversion: IMycLazy to TLazy + lazyRec := lazyIntf; // Implicit conversion: ILazy to TLazy // 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; + lazyIntfFromConstruct: TLazy.ILazy; lazyRec: TLazy; - lazyIntfFromRecord: IMycLazy; + lazyIntfFromRecord: TLazy.ILazy; val: Integer; expectedValue: Integer; begin @@ -403,7 +403,7 @@ begin lazyIntfFromConstruct := TLazy.Construct(FChangingSignal.State, function: Integer begin Result := expectedValue; end); lazyRec.Create(lazyIntfFromConstruct); // Explicitly create record - lazyIntfFromRecord := lazyRec; // Implicit conversion: TLazy to IMycLazy + lazyIntfFromRecord := lazyRec; // Implicit conversion: TLazy to ILazy // 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.Pop specific behaviors (Res undefined) == procedure TTestMyLazy.TestPop_AfterInitialAndNoSignal_ReturnsFalseAndResUndefined; var - lazyIntf: IMycLazy; + lazyIntf: TLazy.ILazy; lazyRec: TLazy; val: Integer; // Value will not be checked as Pop returns false popResult: Boolean; diff --git a/Src/Myc.Test.Signals.Dirty.pas b/Src/Myc.Test.Signals.Dirty.pas index ba4cd68..90df183 100644 --- a/Src/Myc.Test.Signals.Dirty.pas +++ b/Src/Myc.Test.Signals.Dirty.pas @@ -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; diff --git a/Src/Myc.Test.Signals.Latch.pas b/Src/Myc.Test.Signals.Latch.pas index d908884..582da80 100644 --- a/Src/Myc.Test.Signals.Latch.pas +++ b/Src/Myc.Test.Signals.Latch.pas @@ -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.'); diff --git a/Test/TestCoreFutures.pas b/Test/TestCoreFutures.pas index 28537f4..d95d31d 100644 --- a/Test/TestCoreFutures.pas +++ b/Test/TestCoreFutures.pas @@ -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.IFuture; LMainFuture: TFuture.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 diff --git a/Test/TestTasks.pas b/Test/TestTasks.pas index 616ec35..3b0fa79 100644 --- a/Test/TestTasks.pas +++ b/Test/TestTasks.pas @@ -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(TLatch.Null, subscriber, 'Subscriber should not be TMycLatch.Null for StartCount > 0'); + Assert.AreNotEqual(TLatch.Null, subscriber, 'Subscriber should not be TMycLatch.Null for StartCount > 0'); subscriber.Notify;