Refactoring IState - ISignal

This commit is contained in:
Michael Schimmel
2025-06-24 09:24:37 +02:00
parent a3da63ad6a
commit 6ea0f94e36
11 changed files with 120 additions and 118 deletions
+21 -24
View File
@@ -93,7 +93,7 @@ begin
FSymbolsValid :=
TFMXValidationState.Create(
SymbolsComboBox,
FSymbols.Done,
FSymbols.Done.Signal,
procedure
begin
SymbolsComboBox.BeginUpdate;
@@ -143,14 +143,11 @@ end;
procedure TForm1.RandomButtonClick(Sender: TObject);
begin
var tst: TFlag := TFlag.CreateObserver( TSignal.Null );
var rnd: TRndItem;
var ass := FSymbols.WaitFor;
rnd.Stream := FServer.CreateStream(ass[Random(Length(FSymbols.WaitFor))]);
rnd.Data := TDataStreamProvider.Create<TAskBidItem>(3000, 100, rnd.Stream);
rnd.Data := TDataStreamProvider.Create<TAskBidItem>(3000, 1000, rnd.Stream);
rnd.Labl := TLabel.Create(Self);
rnd.Labl.Parent := FlowLayout;
@@ -163,26 +160,26 @@ begin
var Proc :=
procedure(idx: Integer)
begin
TFMXValidationState.Create(
FRandom[idx].Labl,
FRandom[idx].Stream.HasData,
procedure
TFMXValidationState.Create(
FRandom[idx].Labl,
FRandom[idx].Stream.HasData,
procedure
begin
var data := FRandom[idx].Data.Value;
if data.Count > 0 then
begin
var data := FRandom[idx].Data.Value;
if data.Count > 0 then
begin
var dp := data[0];
FRandom[idx].Labl.Text :=
FRandom[idx].Stream.Symbol
+ ' '
+ dp.Time.ToString
+ ' '
+ data.TotalCount.ToString
+ ' '
+ dp.Data.Ask.ToString;
end;
end
);
var dp := data[0];
FRandom[idx].Labl.Text :=
FRandom[idx].Stream.Symbol
+ ' '
+ dp.Time.ToString
+ ' '
+ data.TotalCount.ToString
+ ' '
+ dp.Data.Ask.ToString;
end;
end
);
end;
Proc(FRandom.Count - 1);
end;
+1
View File
@@ -154,6 +154,7 @@ constructor TMycLazyBase<T>.Create(const AChanged: TSignal);
begin
inherited Create;
FChanged := TFlag.CreateFlag;
FChanged.Notify;
FChangeState := AChanged.Subscribe(FChanged);
end;
+27 -13
View File
@@ -8,30 +8,29 @@ uses
Myc.Signals;
type
// TMycNullSignal implements a "null object" pattern for IMycState.
// This state is always considered "set".
// TMycNullSignal implements a "null object" pattern for ISignal.
TMycNullSignal = class(TInterfacedObject, TSignal.ISignal)
public
function Subscribe(Subscriber: TSignal.ISubscriber): Pointer;
procedure Unsubscribe(Tag: Pointer);
end;
// TMycNullState implements a "null object" pattern for IMycState.
// TMycNullState implements a "null object" pattern for IState.
// This state is always considered "set".
TMycNullState = class(TInterfacedObject, TSignal.ISignal, TState.IState)
protected
// TState.IState
private
function GetSignal: TSignal;
function GetIsSet: Boolean;
public
function Subscribe(Subscriber: TSignal.ISubscriber): Pointer;
procedure Unsubscribe(Tag: Pointer);
property Signal: TSignal read GetSignal;
end;
// 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.ISubscriber, TSignal.ISignal, TEvent.IEvent)
strict private
FSubscribers: TMycNotifyList<TSignal.ISubscriber>;
protected
function GetSignal: TSignal;
function GetIsSet: Boolean;
public
@@ -69,7 +68,7 @@ type
[volatile]
FCount: Integer; // The internal countdown value for the latch.
function GetState: TState; // Implementation for TLatch.ILatch.GetState and IFlag.GetState.
protected
function GetSignal: TSignal;
function GetIsSet: Boolean;
public
// Creates the latch with an initial count.
@@ -103,11 +102,11 @@ type
[volatile]
FFlag: Boolean; // Internal state: true if dirty/set, false if clean/reset.
function GetState: TState;
protected
function GetSignal: TSignal;
function GetIsSet: Boolean;
public
// Creates a new dirty flag, initially set to dirty (true).
constructor Create;
constructor Create(AInit: Boolean);
destructor Destroy; override;
// Factory method to create a new TMycFlag instance.
@@ -166,6 +165,11 @@ begin
Result := true; // Null state is always set.
end;
function TMycNullState.GetSignal: TSignal;
begin
Result := Self;
end;
function TMycNullState.Subscribe(Subscriber: TSignal.ISubscriber): Pointer;
begin
// Since the state is always set, notify the subscriber immediately.
@@ -327,6 +331,11 @@ begin
Result := FCount <= 0;
end;
function TMycLatch.GetSignal: TSignal;
begin
Result := Self;
end;
function TMycLatch.GetState: TState;
begin
// TMycLatch itself implements TState.IState.
@@ -392,17 +401,17 @@ end;
{ TMycFlag }
constructor TMycFlag.Create;
constructor TMycFlag.Create(AInit: Boolean);
begin
inherited Create;
FFlag := false;
FFlag := AInit;
FSubscribers.Create;
end;
class function TMycFlag.CreateDirty: TFlag.IFlag;
begin
// Factory method to create a new TMycFlag instance.
Result := TMycFlag.Create;
Result := TMycFlag.Create( true );
end;
destructor TMycFlag.Destroy;
@@ -423,6 +432,11 @@ begin
Result := FFlag;
end;
function TMycFlag.GetSignal: TSignal;
begin
Result := Self;
end;
function TMycFlag.GetState: TState;
begin
// TMycFlag itself implements TState.IState.
@@ -497,7 +511,7 @@ end;
constructor TMycObserverFlag.Create(const ASignal: TSignal.ISignal);
begin
inherited Create;
inherited Create( false );
FSignal := ASignal;
end;
+2 -2
View File
@@ -197,7 +197,7 @@ begin
else
begin
// The job will run when Gate notifies the subscriber returned by Run.
Result := Gate.Subscribe(Run(Proc));
Result := Gate.Signal.Subscribe(Run(Proc));
end;
end;
@@ -361,7 +361,7 @@ begin
lock := TSemaphore.Create(nil, 0, 1, '');
try
{var subscription :=}
State.Subscribe(TMycTaskWait.Create(lock));
State.Signal.Subscribe(TMycTaskWait.Create(lock));
lock.Acquire;
finally
FWaitSemaphores.Push(lock);
+4 -6
View File
@@ -93,9 +93,8 @@ uses
constructor TMutable<T>.Create(const AMutable: IMutable);
begin
FMutable := AMutable;
if not Assigned(FMutable) then
FMutable := FNull;
if Assigned(AMutable) then
FMutable := AMutable;
end;
class constructor TMutable<T>.CreateClass;
@@ -142,9 +141,8 @@ end;
constructor TLazy<T>.Create(const ALazy: ILazy);
begin
FLazy := ALazy;
if not Assigned(FLazy) then
FLazy := FNull;
if Assigned(ALazy) then
FLazy := ALazy;
end;
class function TLazy<T>.Construct(const Changing: TSignal.ISignal; const Proc: TFunc<T>): TLazy<T>;
+34 -44
View File
@@ -25,7 +25,6 @@ type
FTag: TSubscriptionTag;
constructor Create(const ASignal: ISignal; ATag: TSubscriptionTag);
public
class operator Initialize(out Dest: TSubscription);
// Unsubscribes from the associated signal
procedure Unsubscribe;
end;
@@ -52,12 +51,14 @@ type
TState = record
type
IState = interface(TSignal.ISignal)
IState = interface
{$REGION 'property access'}
function GetIsSet: Boolean;
function GetSignal: TSignal;
{$ENDREGION}
// IsSet is true if the TState has been reached or the condition is met.
property IsSet: Boolean read GetIsSet;
property Signal: TSignal read GetSignal;
end;
{$REGION 'private'}
@@ -69,7 +70,7 @@ type
private
FState: IState;
function GetIsSet: Boolean; inline;
function GetAsSignal: TSignal; inline;
function GetSignal: TSignal; inline;
{$ENDREGION}
public
constructor Create(const AState: IState);
@@ -80,15 +81,12 @@ type
class function All(const States: TArray<TState>): TState; static;
class function Any(const States: TArray<TState>): TState; static;
class property Null: IState read FNull;
function Subscribe(Subscriber: TSignal.ISubscriber): TSignal.TSubscription; inline;
property IsSet: Boolean read GetIsSet;
class property Null: IState read FNull;
// Explicitly cast a state to a signal. This is dangerous, use with care!
// States only notify, when they change. This behaviour is obscured by treating it like a signal.
property AsSignal: TSignal read GetAsSignal;
property IsSet: Boolean read GetIsSet;
property Signal: TSignal read GetSignal;
end;
// An event is a stateless signal that forwards all notifications to the subscribers.
@@ -195,7 +193,7 @@ type
class operator Implicit(const A: IFlag): TFlag; overload;
class operator Implicit(const A: TFlag): IFlag; overload;
class function CreateFlag: TFlag; static;
class function CreateFlag(Init: Boolean = false): TFlag; static;
class function CreateObserver(const Signal: TSignal): TFlag; static;
class property Null: IFlag read FNull;
@@ -222,24 +220,20 @@ end;
procedure TSignal.TSubscription.Unsubscribe;
begin
FSignal.Unsubscribe(FTag);
FSignal := TSignal.Null;
if Assigned(FSignal) then
begin
FSignal.Unsubscribe(FTag);
FSignal := TSignal.Null;
end;
FTag := nil;
end;
class operator TSignal.TSubscription.Initialize(out Dest: TSubscription);
begin
Dest.FSignal := TSignal.Null;
Dest.FTag := nil;
end;
{ TState }
constructor TState.Create(const AState: IState);
begin
FState := AState;
if not Assigned(FState) then
FState := FNull;
if Assigned(AState) then
FState := AState;
end;
class constructor TState.ClassCreate;
@@ -253,8 +247,8 @@ var
Latch: TLatch.ILatch;
begin
Latch := TLatch.CreateLatch(Length(States));
for var i := 0 to High(States) do
States[i].Subscribe(Latch);
for var state in States do
state.Signal.Subscribe(Latch);
Result := Latch.State;
end;
@@ -269,15 +263,15 @@ begin
else
begin
Latch := TLatch.CreateLatch(1);
for var i := 0 to High(States) do
States[i].Subscribe(Latch);
for var state in States do
state.Signal.Subscribe(Latch);
Result := Latch.State;
end;
end;
function TState.GetAsSignal: TSignal;
function TState.GetSignal: TSignal;
begin
Result := FState;
Result := FState.Signal;
end;
function TState.GetIsSet: Boolean;
@@ -287,7 +281,7 @@ end;
function TState.Subscribe(Subscriber: TSignal.ISubscriber): TSignal.TSubscription;
begin
Result := TSignal.TSubscription.Create(FState, FState.Subscribe(Subscriber));
Result := Signal.Subscribe(Subscriber);
end;
class operator TState.Implicit(const A: TState): IState;
@@ -309,9 +303,8 @@ end;
constructor TEvent.Create(const AEvent: IEvent);
begin
FEvent := AEvent;
if not Assigned(FEvent) then
FEvent := FNull;
if Assigned(AEvent) then
FEvent := AEvent;
end;
class constructor TEvent.ClassCreate;
@@ -363,14 +356,13 @@ end;
constructor TFlag.Create(const AFlag: IFlag);
begin
FFlag := AFlag;
if not Assigned(FFlag) then
FFlag := FNull;
if Assigned(AFlag) then
FFlag := AFlag;
end;
class function TFlag.CreateFlag: TFlag;
class function TFlag.CreateFlag(Init: Boolean = false): TFlag;
begin
Result := TMycFlag.Create;
Result := TMycFlag.Create( Init );
end;
class function TFlag.CreateObserver(const Signal: TSignal): TFlag;
@@ -419,9 +411,8 @@ end;
constructor TLatch.Create(const ALatch: TLatch.ILatch);
begin
FLatch := ALatch;
if not Assigned(FLatch) then
FLatch := FNull;
if Assigned(ALatch) then
FLatch := ALatch;
end;
class function TLatch.CreateLatch(Count: Integer): ILatch;
@@ -445,7 +436,7 @@ begin
until AtomicCmpExchange(FQueueLock, 1, 0) = 0;
try
Gate.State.Subscribe(Latch);
Gate.State.Signal.Subscribe(Latch);
Gate := Latch;
Result := Latch.State;
finally
@@ -466,7 +457,7 @@ begin
until AtomicCmpExchange(FQueueLock, 1, 0) = 0;
try
FLatch.State.Subscribe(Latch);
FLatch.State.Signal.Subscribe(Latch);
FLatch := Latch;
Result := FLatch.State;
finally
@@ -508,9 +499,8 @@ end;
constructor TSignal.Create(const ASignal: ISignal);
begin
FSignal := ASignal;
if not Assigned(FSignal) then
FSignal := FNull;
if Assigned(FSignal) then
FSignal := ASignal;
end;
function TSignal.Subscribe(Subscriber: ISubscriber): TSubscription;
+1 -1
View File
@@ -74,7 +74,7 @@ end;
function TMycTaskManagerMock.CreateTask(const Gate: TState; const Proc: TProc): TSignal.TSubscription;
begin
Result := Gate.Subscribe(TMycExecMock.Create(Proc));
Result := Gate.Signal.Subscribe(TMycExecMock.Create(Proc));
end;
procedure TMycTaskManagerMock.WaitFor(State: TState.IState);
+11 -11
View File
@@ -150,7 +150,7 @@ var
begin
sourceDirty := TFlag.CreateFlag;
sourceDirty.Reset;
funcLazy := TMycFuncLazy<Integer>.Create(sourceDirty.State, function: Integer begin Result := 10; end);
funcLazy := TMycFuncLazy<Integer>.Create(sourceDirty.State.Signal, function: Integer begin Result := 10; end);
Assert.IsNotNull(funcLazy, 'TMycFuncLazy<Integer> instance should not be nil');
changedState := funcLazy.GetChanged;
Assert.IsNotNull(changedState, 'funcLazy.GetChanged should return a valid TState.IState');
@@ -172,7 +172,7 @@ begin
expectedValue := 50;
funcLazy :=
TMycFuncLazy<Integer>.Create(
sourceDirty.State,
sourceDirty.State.Signal,
function: Integer
begin
procExecuted := True;
@@ -200,7 +200,7 @@ begin
sourceDirty := TFlag.CreateFlag;
sourceDirty.Reset;
initialProcValue := 33;
funcLazy := TMycFuncLazy<Integer>.Create(sourceDirty.State, function: Integer begin Result := initialProcValue; end);
funcLazy := TMycFuncLazy<Integer>.Create(sourceDirty.State.Signal, function: Integer begin Result := initialProcValue; end);
Helper_ConsumeInitialPop(funcLazy, initialProcValue);
Assert.IsFalse(funcLazy.GetChanged.IsSet, 'After initial Pop and no source change, GetChanged.IsSet should be false');
end;
@@ -220,7 +220,7 @@ begin
procExecutedCount := 0;
funcLazy :=
TMycFuncLazy<Integer>.Create(
sourceDirty.State,
sourceDirty.State.Signal,
function: Integer
begin
Inc(procExecutedCount);
@@ -244,7 +244,7 @@ begin
sourceDirty := TFlag.CreateFlag;
sourceDirty.Reset;
initialProcValue := 20;
funcLazy := TMycFuncLazy<Integer>.Create(sourceDirty.State, function: Integer begin Result := initialProcValue; end);
funcLazy := TMycFuncLazy<Integer>.Create(sourceDirty.State.Signal, function: Integer begin Result := initialProcValue; end);
Helper_ConsumeInitialPop(funcLazy, initialProcValue);
sourceDirty.Notify;
changedState := funcLazy.GetChanged;
@@ -265,7 +265,7 @@ begin
procCallCount := 0;
funcLazy :=
TMycFuncLazy<Integer>.Create(
sourceDirty.State,
sourceDirty.State.Signal,
function: Integer
begin
Inc(procCallCount);
@@ -302,7 +302,7 @@ begin
procCallCount := 0;
funcLazy :=
TMycFuncLazy<Integer>.Create(
sourceDirty.State,
sourceDirty.State.Signal,
function: Integer
begin
Inc(procCallCount);
@@ -338,7 +338,7 @@ begin
funcLazy :=
TMycFuncLazy<Integer>.Create(
sourceDirty.State,
sourceDirty.State.Signal,
function: Integer
begin
Inc(procCallCount);
@@ -391,7 +391,7 @@ var
begin
sourceDirty := TFlag.CreateFlag;
sourceDirty.Reset;
funcLazyObj := TMycFuncLazy<Integer>.Create(sourceDirty.State, function: Integer begin Result := 1; end);
funcLazyObj := TMycFuncLazy<Integer>.Create(sourceDirty.State.Signal, function: Integer begin Result := 1; end);
Assert.IsTrue(funcLazyObj.Pop(tempVal), 'Initial Pop should succeed');
funcLazyObj.Destroy;
Assert.WillNotRaise(
@@ -411,13 +411,13 @@ var
expectedValue: Integer;
procExecuted: Boolean;
begin
sourceDirty := TFlag.CreateFlag;
sourceDirty := TFlag.CreateFlag( true );
Assert.IsTrue(sourceDirty.State.IsSet, 'SourceDirty should be initially set for this test scenario');
expectedValue := 70;
procExecuted := False;
funcLazy :=
TMycFuncLazy<Integer>.Create(
sourceDirty.State,
sourceDirty.State.Signal,
function: Integer
begin
procExecuted := True;
+11 -11
View File
@@ -145,7 +145,7 @@ begin
// FChangingSignal is reset in Setup
lazyIntf :=
TLazy<Integer>.Construct(
FChangingSignal.State,
FChangingSignal.State.Signal,
function: Integer
begin
procExecuted := True;
@@ -170,7 +170,7 @@ begin
expectedValue := 20;
lazyIntf :=
TLazy<Integer>.Construct(
FChangingSignal.State,
FChangingSignal.State.Signal,
function: Integer
begin
procExecuted := True;
@@ -190,7 +190,7 @@ var
expectedValue: Integer;
begin
expectedValue := 30;
lazyIntf := TLazy<Integer>.Construct(FChangingSignal.State, function: Integer begin Result := expectedValue; end);
lazyIntf := TLazy<Integer>.Construct(FChangingSignal.State.Signal, function: Integer begin Result := expectedValue; end);
lazyRec := lazyIntf;
ConsumeInitialPop(lazyRec, expectedValue, 'TestConstruct_StateInteraction_SignalTriggersChanged (Initial)');
@@ -212,7 +212,7 @@ begin
procCallCount := 0;
lazyIntf :=
TLazy<Integer>.Construct(
FChangingSignal.State,
FChangingSignal.State.Signal,
function: Integer
begin
Inc(procCallCount);
@@ -243,7 +243,7 @@ begin
procCallCount := 0;
lazyIntf :=
TLazy<Integer>.Construct(
FChangingSignal.State,
FChangingSignal.State.Signal,
function: Integer
begin
Inc(procCallCount);
@@ -286,7 +286,7 @@ begin
localChangingSignal := TFlag.CreateFlag;
localChangingSignal.Reset;
lazyIntf := TLazy<Integer>.Construct(localChangingSignal.State, function: Integer begin Result := 1; end);
lazyIntf := TLazy<Integer>.Construct(localChangingSignal.State.Signal, function: Integer begin Result := 1; end);
Assert.IsNotNull(lazyIntf, 'Constructed lazy interface should not be nil');
// Simulate usage and release of the lazy object
@@ -317,7 +317,7 @@ var
expectedValue: Integer;
begin
expectedValue := 60;
originalLazyIntf := TLazy<Integer>.Construct(FChangingSignal.State, function: Integer begin Result := expectedValue; end);
originalLazyIntf := TLazy<Integer>.Construct(FChangingSignal.State.Signal, function: Integer begin Result := expectedValue; end);
// originalLazyIntf.Changed.IsSet is true by design
wrappedLazyRec := TLazy<Integer>.Create(originalLazyIntf);
@@ -344,7 +344,7 @@ begin
procCallCount := 0;
originalLazyIntf :=
TLazy<Integer>.Construct(
FChangingSignal.State,
FChangingSignal.State.Signal,
function: Integer
begin
Inc(procCallCount);
@@ -381,7 +381,7 @@ var
expectedValue: Integer;
begin
expectedValue := 80;
lazyIntf := TLazy<Integer>.Construct(FChangingSignal.State, function: Integer begin Result := expectedValue; end);
lazyIntf := TLazy<Integer>.Construct(FChangingSignal.State.Signal, function: Integer begin Result := expectedValue; end);
Assert.IsNotNull(lazyIntf, 'Interface should be assigned');
lazyRec := lazyIntf; // Implicit conversion: ILazy<T> to TLazy<T>
@@ -400,7 +400,7 @@ var
expectedValue: Integer;
begin
expectedValue := 90;
lazyIntfFromConstruct := TLazy<Integer>.Construct(FChangingSignal.State, function: Integer begin Result := expectedValue; end);
lazyIntfFromConstruct := TLazy<Integer>.Construct(FChangingSignal.State.Signal, function: Integer begin Result := expectedValue; end);
lazyRec.Create(lazyIntfFromConstruct); // Explicitly create record
lazyIntfFromRecord := lazyRec; // Implicit conversion: TLazy<T> to ILazy<T>
@@ -426,7 +426,7 @@ begin
procExecuted := False;
lazyIntf :=
TLazy<Integer>.Construct(
FChangingSignal.State,
FChangingSignal.State.Signal,
function: Integer
begin
procExecuted := True;
+5 -3
View File
@@ -105,7 +105,7 @@ end;
function TTestMycDirtyFlag.CreateAndPrepareDirtyFlag(StartDirty: Boolean = True): TFlag.IFlag;
begin
Result := TFlag.CreateFlag; // Initially dirty
Result := TFlag.CreateFlag( true ); // Initially dirty
if not StartDirty then
Result.Reset; // Reset to make it clean
Assert.AreEqual(StartDirty, Result.State.IsSet, 'CreateAndPrepareDirtyFlag initial state incorrect.');
@@ -127,8 +127,10 @@ procedure TTestMycDirtyFlag.TestCreate_InitialStateIsDirty;
var
dirtyFlag: TFlag.IFlag;
begin
dirtyFlag := TFlag.CreateFlag;
Assert.IsTrue(dirtyFlag.State.IsSet, 'Newly created dirty flag should be IsSet (dirty).');
dirtyFlag := TFlag.CreateFlag( true );
Assert.IsTrue(dirtyFlag.State.IsSet, 'Newly created dirty flag should be IsSet.');
dirtyFlag := TFlag.CreateFlag( false );
Assert.IsFalse(dirtyFlag.State.IsSet, 'Newly created dirty flag should not be IsSet.');
end;
procedure TTestMycDirtyFlag.TestReset_FromDirtyState_BecomesCleanAndReturnsTrue;
+3 -3
View File
@@ -403,7 +403,7 @@ begin
FDataServer := ADataServer;
FSymbol := ASymbol;
FNextReady := TFlag.CreateFlag;
FHasData := TEvent.CreateRouter(FNextReady.State.AsSignal);
FHasData := TEvent.CreateRouter(FNextReady.State.Signal);
end;
destructor TAuraFileStream<T>.Destroy;
@@ -428,7 +428,7 @@ begin
begin
Result.FileInfo := FileInfo;
Result.Data := FDataServer.LoadDataFile(FileInfo);
Result.Data.Done.Subscribe(FNextReady);
Result.Data.Done.Signal.Subscribe(FNextReady);
end;
end
);
@@ -525,7 +525,7 @@ begin
begin
Result.FileInfo := nextFileInfo;
Result.Data := FDataServer.LoadDataFile(nextFileInfo);
Result.Data.Done.Subscribe(FNextReady);
Result.Data.Done.Signal.Subscribe(FNextReady);
end;
end;
end