TaskManager refactoring
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
<ProjectVersion>20.3</ProjectVersion>
|
<ProjectVersion>20.3</ProjectVersion>
|
||||||
<FrameworkType>FMX</FrameworkType>
|
<FrameworkType>FMX</FrameworkType>
|
||||||
<Base>True</Base>
|
<Base>True</Base>
|
||||||
<Config Condition="'$(Config)'==''">Release</Config>
|
<Config Condition="'$(Config)'==''">Debug</Config>
|
||||||
<Platform Condition="'$(Platform)'==''">Win64</Platform>
|
<Platform Condition="'$(Platform)'==''">Win64</Platform>
|
||||||
<ProjectName Condition="'$(ProjectName)'==''">AuraTrader</ProjectName>
|
<ProjectName Condition="'$(ProjectName)'==''">AuraTrader</ProjectName>
|
||||||
<TargetedPlatforms>3</TargetedPlatforms>
|
<TargetedPlatforms>3</TargetedPlatforms>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ object Form1: TForm1
|
|||||||
Top = 0
|
Top = 0
|
||||||
Caption = 'Form1'
|
Caption = 'Form1'
|
||||||
ClientHeight = 840
|
ClientHeight = 840
|
||||||
ClientWidth = 797
|
ClientWidth = 796
|
||||||
FormFactor.Width = 320
|
FormFactor.Width = 320
|
||||||
FormFactor.Height = 480
|
FormFactor.Height = 480
|
||||||
FormFactor.Devices = [Desktop]
|
FormFactor.Devices = [Desktop]
|
||||||
@@ -12,7 +12,7 @@ object Form1: TForm1
|
|||||||
DesignerMasterStyle = 0
|
DesignerMasterStyle = 0
|
||||||
object Panel1: TPanel
|
object Panel1: TPanel
|
||||||
Align = Top
|
Align = Top
|
||||||
Size.Width = 797.00000000000000000
|
Size.Width = 796.00000000000000000
|
||||||
Size.Height = 529.00000000000000000
|
Size.Height = 529.00000000000000000
|
||||||
Size.PlatformDefault = False
|
Size.PlatformDefault = False
|
||||||
TabOrder = 2
|
TabOrder = 2
|
||||||
@@ -28,13 +28,13 @@ object Form1: TForm1
|
|||||||
Anchors = [akLeft, akTop, akRight]
|
Anchors = [akLeft, akTop, akRight]
|
||||||
Position.X = 8.00000000000000000
|
Position.X = 8.00000000000000000
|
||||||
Position.Y = 8.00000000000000000
|
Position.Y = 8.00000000000000000
|
||||||
Size.Width = 262.00000000000000000
|
Size.Width = 261.00000000000000000
|
||||||
Size.Height = 513.00000000000000000
|
Size.Height = 513.00000000000000000
|
||||||
Size.PlatformDefault = False
|
Size.PlatformDefault = False
|
||||||
end
|
end
|
||||||
object SymbolsComboBox: TComboBox
|
object SymbolsComboBox: TComboBox
|
||||||
Anchors = [akTop, akRight]
|
Anchors = [akTop, akRight]
|
||||||
Position.X = 494.00000000000000000
|
Position.X = 493.00000000000000000
|
||||||
Position.Y = 8.00000000000000000
|
Position.Y = 8.00000000000000000
|
||||||
Size.Width = 217.00000000000000000
|
Size.Width = 217.00000000000000000
|
||||||
Size.Height = 22.00000000000000000
|
Size.Height = 22.00000000000000000
|
||||||
@@ -43,7 +43,7 @@ object Form1: TForm1
|
|||||||
end
|
end
|
||||||
object LoadButton: TButton
|
object LoadButton: TButton
|
||||||
Anchors = [akTop, akRight]
|
Anchors = [akTop, akRight]
|
||||||
Position.X = 719.00000000000000000
|
Position.X = 718.00000000000000000
|
||||||
Position.Y = 8.00000000000000000
|
Position.Y = 8.00000000000000000
|
||||||
Size.Width = 47.00000000000000000
|
Size.Width = 47.00000000000000000
|
||||||
Size.Height = 22.00000000000000000
|
Size.Height = 22.00000000000000000
|
||||||
@@ -85,11 +85,11 @@ object Form1: TForm1
|
|||||||
Touch.InteractiveGestures = [Pan, LongTap, DoubleTap]
|
Touch.InteractiveGestures = [Pan, LongTap, DoubleTap]
|
||||||
DataDetectorTypes = []
|
DataDetectorTypes = []
|
||||||
Align = Client
|
Align = Client
|
||||||
Size.Width = 797.00000000000000000
|
Size.Width = 796.00000000000000000
|
||||||
Size.Height = 311.00000000000000000
|
Size.Height = 311.00000000000000000
|
||||||
Size.PlatformDefault = False
|
Size.PlatformDefault = False
|
||||||
TabOrder = 1
|
TabOrder = 1
|
||||||
Viewport.Width = 793.00000000000000000
|
Viewport.Width = 796.00000000000000000
|
||||||
Viewport.Height = 307.00000000000000000
|
Viewport.Height = 311.00000000000000000
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -220,12 +220,12 @@ begin
|
|||||||
const width = 1000;
|
const width = 1000;
|
||||||
const incsize = 100;
|
const incsize = 100;
|
||||||
|
|
||||||
TaskManager.RunTask(
|
var done := TaskManager.RunTask(
|
||||||
nil,
|
nil,
|
||||||
procedure
|
function: TState
|
||||||
begin
|
begin
|
||||||
var arr := TDataSeries<TAskBidItem>.CreateWriteable(width);
|
var arr := TDataSeries<TAskBidItem>.CreateWriteable(width);
|
||||||
var done :=
|
Result :=
|
||||||
FServer.ProcessData(
|
FServer.ProcessData(
|
||||||
Symbol,
|
Symbol,
|
||||||
FTerminate.Signal,
|
FTerminate.Signal,
|
||||||
@@ -246,10 +246,11 @@ begin
|
|||||||
currPathData.Value := TObjectRef<TPathData>.Create(PathData);
|
currPathData.Value := TObjectRef<TPathData>.Create(PathData);
|
||||||
end
|
end
|
||||||
);
|
);
|
||||||
FLoadDone := TState.All([FLoadDone, done]);
|
|
||||||
end
|
end
|
||||||
);
|
);
|
||||||
|
|
||||||
|
FLoadDone := TState.All([FLoadDone, done]);
|
||||||
|
|
||||||
LogMemo.AddIdleHandler(currLog.Changed, procedure begin LogMemo.Lines.Add(Symbol + ': ' + currLog.Value); end);
|
LogMemo.AddIdleHandler(currLog.Changed, procedure begin LogMemo.Lines.Add(Symbol + ': ' + currLog.Value); end);
|
||||||
|
|
||||||
Path1.AddIdleHandler(
|
Path1.AddIdleHandler(
|
||||||
|
|||||||
+10
-15
@@ -25,7 +25,7 @@ type
|
|||||||
|
|
||||||
TMycGateFuncFuture<T> = class(TMycFuture<T>)
|
TMycGateFuncFuture<T> = class(TMycFuture<T>)
|
||||||
private
|
private
|
||||||
FDone: TLatch.ILatch;
|
FDone: TState;
|
||||||
FResult: T;
|
FResult: T;
|
||||||
protected
|
protected
|
||||||
function GetValue: T; override;
|
function GetValue: T; override;
|
||||||
@@ -74,26 +74,21 @@ constructor TMycGateFuncFuture<T>.Create(const ATaskManager: TTaskManager; const
|
|||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
|
|
||||||
FDone := TLatch.CreateLatch(1);
|
|
||||||
|
|
||||||
// Subscribe the job execution to AGate.
|
// Subscribe the job execution to AGate.
|
||||||
// The job will run when AGate notifies the subscriber returned by Run.
|
// The job will run when AGate notifies the subscriber returned by Run.
|
||||||
ATaskManager.RunTask(
|
FDone :=
|
||||||
AGate,
|
ATaskManager.RunTask(
|
||||||
procedure
|
AGate,
|
||||||
begin
|
function: TState
|
||||||
try
|
begin
|
||||||
try
|
try
|
||||||
Self.FResult := AProc();
|
Self.FResult := AProc();
|
||||||
except
|
except
|
||||||
Self.FResult := Default(T); // Set result to Default(T) on error
|
Self.FResult := Default(T); // Set result to Default(T) on error
|
||||||
raise; // Re-raise for TaskFactory to handle
|
raise; // Re-raise for TaskFactory to handle
|
||||||
end;
|
end;
|
||||||
finally
|
end
|
||||||
Self.FDone.Notify; // Signal that this future is done (successfully or with error)
|
);
|
||||||
end;
|
|
||||||
end
|
|
||||||
);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
destructor TMycGateFuncFuture<T>.Destroy;
|
destructor TMycGateFuncFuture<T>.Destroy;
|
||||||
@@ -103,12 +98,12 @@ end;
|
|||||||
|
|
||||||
function TMycGateFuncFuture<T>.GetDone: TState;
|
function TMycGateFuncFuture<T>.GetDone: TState;
|
||||||
begin
|
begin
|
||||||
Result := FDone.State;
|
Result := FDone;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TMycGateFuncFuture<T>.GetValue: T;
|
function TMycGateFuncFuture<T>.GetValue: T;
|
||||||
begin
|
begin
|
||||||
Assert(FDone.State.IsSet, 'Result is not yet available.');
|
Assert(FDone.IsSet, 'Result is not yet available.');
|
||||||
Result := FResult;
|
Result := FResult;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|||||||
+16
-48
@@ -11,37 +11,22 @@ uses
|
|||||||
Myc.Signals;
|
Myc.Signals;
|
||||||
|
|
||||||
type
|
type
|
||||||
IMycThreadPool = interface(TTaskManager.IMycTaskManager)
|
IThreadPool = interface(TTaskManager.ITaskManager)
|
||||||
{$region 'property access'}
|
{$region 'property access'}
|
||||||
// Retrieves the number of worker threads.
|
// Retrieves the number of worker threads.
|
||||||
function GetThreadCount: Integer;
|
function GetThreadCount: Integer;
|
||||||
{$endregion}
|
{$endregion}
|
||||||
|
|
||||||
// Creates and starts a new, independent thread executing Proc.
|
|
||||||
// Returns IMycState to await thread completion.
|
|
||||||
// Exceptions within Proc are caught; the first one is stored
|
|
||||||
// and can be re-raised in the main thread via WaitFor or Teardown.
|
|
||||||
function CreateThread(const Proc: TProc): TState.IState;
|
|
||||||
|
|
||||||
// Returns True if called from the main application thread.
|
// Returns True if called from the main application thread.
|
||||||
function InMainThread: Boolean;
|
function InMainThread: Boolean;
|
||||||
|
|
||||||
// Returns True if called from one of the factory's worker threads.
|
// Returns True if called from one of the factory's worker threads.
|
||||||
function InWorkerThread: Boolean;
|
function InWorkerThread: Boolean;
|
||||||
|
|
||||||
// Schedules Job for execution by a worker thread.
|
|
||||||
// If StartCount > 0, Job is deferred until Notify is called StartCount times on the returned IMycSubscriber.
|
|
||||||
// If StartCount <= 0, Job is enqueued immediately.
|
|
||||||
// Returns IMycSubscriber for deferred jobs, or TMycLatch.Null for immediate jobs or if Job is nil.
|
|
||||||
// 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): TSignal.ISubscriber;
|
|
||||||
|
|
||||||
procedure EnqueueJob(const Job: TProc);
|
procedure EnqueueJob(const Job: TProc);
|
||||||
|
|
||||||
// Shuts down the task factory: terminates worker threads and cleans up resources.
|
// Shuts down the task factory: terminates worker threads and cleans up resources.
|
||||||
// Before shutdown, any first stored exception from a worker thread (from Run or CreateThread tasks managed by this factory)
|
// Before shutdown, any first stored exception from a worker thread (from CreateThread tasks managed by this factory)
|
||||||
// will be re-raised in the calling (main) thread.
|
// will be re-raised in the calling (main) thread.
|
||||||
procedure Teardown;
|
procedure Teardown;
|
||||||
|
|
||||||
@@ -49,7 +34,7 @@ type
|
|||||||
property ThreadCount: Integer read GetThreadCount;
|
property ThreadCount: Integer read GetThreadCount;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TMycTaskFactory = class(TInterfacedObject, TTaskManager.IMycTaskManager, IMycThreadPool)
|
TMycTaskFactory = class(TInterfacedObject, TTaskManager.ITaskManager, IThreadPool)
|
||||||
type
|
type
|
||||||
ETaskException = class(Exception)
|
ETaskException = class(Exception)
|
||||||
end;
|
end;
|
||||||
@@ -75,10 +60,9 @@ type
|
|||||||
constructor Create;
|
constructor Create;
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
class function CreateAnonymousThread(const DbgName: String; const Proc: TProc): TThread;
|
class function CreateAnonymousThread(const DbgName: String; const Proc: TProc): TThread;
|
||||||
function CreateThread(const Proc: TProc): TState.IState;
|
procedure CreateThread(const Proc: TProc);
|
||||||
procedure HandleException;
|
procedure HandleException;
|
||||||
procedure EnqueueJob(const Job: TProc);
|
procedure EnqueueJob(const Job: TProc);
|
||||||
function Run(Job: TProc): TSignal.ISubscriber;
|
|
||||||
procedure RunTask(const Gate: TState; const Proc: TProc);
|
procedure RunTask(const Gate: TState; const Proc: TProc);
|
||||||
procedure WaitFor(const State: TState);
|
procedure WaitFor(const State: TState);
|
||||||
procedure Teardown;
|
procedure Teardown;
|
||||||
@@ -188,41 +172,37 @@ end;
|
|||||||
|
|
||||||
procedure TMycTaskFactory.RunTask(const Gate: TState; const Proc: TProc);
|
procedure TMycTaskFactory.RunTask(const Gate: TState; const Proc: TProc);
|
||||||
begin
|
begin
|
||||||
|
if not Assigned(Proc) then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
if FTerminated <> 0 then
|
||||||
|
raise ETaskException.Create('Task factory terminated');
|
||||||
|
|
||||||
if Gate.IsSet then
|
if Gate.IsSet then
|
||||||
begin
|
begin
|
||||||
EnqueueJob(Proc);
|
EnqueueJob(Proc);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
// The job will run when Gate notifies the subscriber returned by Run.
|
// Create a pending job
|
||||||
Gate.Signal.Subscribe(Run(Proc));
|
Gate.Signal.Subscribe(TMycPendingJob.Create(Self, Proc));
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TMycTaskFactory.CreateThread(const Proc: TProc): TState.IState;
|
procedure TMycTaskFactory.CreateThread(const Proc: TProc);
|
||||||
var
|
|
||||||
res: TLatch.ILatch;
|
|
||||||
capturedProc: TProc;
|
|
||||||
begin
|
begin
|
||||||
// Create a latch that will be signaled when the proc finishes
|
var cProc := Proc; // Capture Proc for the anonymous method
|
||||||
res := TLatch.CreateLatch(1);
|
|
||||||
|
|
||||||
capturedProc := Proc; // Capture Proc for the anonymous method
|
|
||||||
CreateAnonymousThread(
|
CreateAnonymousThread(
|
||||||
'Thread',
|
'Thread',
|
||||||
procedure
|
procedure
|
||||||
begin
|
begin
|
||||||
try
|
try
|
||||||
capturedProc(); // Execute the provided procedure
|
cProc(); // Execute the provided procedure
|
||||||
finally
|
finally
|
||||||
capturedProc := nil; // Clear the captured proc
|
cProc := nil; // Clear the captured proc
|
||||||
res.Notify; // Signal completion via the latch's Notify method
|
|
||||||
end;
|
end;
|
||||||
end)
|
end)
|
||||||
.Start;
|
.Start;
|
||||||
|
|
||||||
// Return the state interface of the latch
|
|
||||||
exit(res.State);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMycTaskFactory.EnqueueJob(const Job: TProc);
|
procedure TMycTaskFactory.EnqueueJob(const Job: TProc);
|
||||||
@@ -287,18 +267,6 @@ begin
|
|||||||
exit(false); // Not found in worker thread list
|
exit(false); // Not found in worker thread list
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TMycTaskFactory.Run(Job: TProc): TSignal.ISubscriber;
|
|
||||||
begin
|
|
||||||
if FTerminated <> 0 then
|
|
||||||
raise ETaskException.Create('Task factory terminated');
|
|
||||||
|
|
||||||
if not Assigned(Job) then
|
|
||||||
exit(TLatch.Null); // Already correct, uses direct static property on TMycLatch
|
|
||||||
|
|
||||||
// Create a pending job
|
|
||||||
Result := TMycPendingJob.Create(Self, Job);
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TMycTaskFactory.Teardown;
|
procedure TMycTaskFactory.Teardown;
|
||||||
var
|
var
|
||||||
i: Integer;
|
i: Integer;
|
||||||
|
|||||||
+8
-10
@@ -44,7 +44,7 @@ type
|
|||||||
class operator Implicit(const A: TFuture<T>): IFuture; overload;
|
class operator Implicit(const A: TFuture<T>): IFuture; overload;
|
||||||
|
|
||||||
class function Construct(const Proc: TFunc<T>): TFuture<T>; overload; static;
|
class function Construct(const Proc: TFunc<T>): TFuture<T>; overload; static;
|
||||||
class function Construct(const Gate: TState.IState; const Proc: TFunc<T>): TFuture<T>; overload; static;
|
class function Construct(const Gate: TState; const Proc: TFunc<T>): TFuture<T>; overload; static;
|
||||||
|
|
||||||
class property Null: IFuture read FNull;
|
class property Null: IFuture read FNull;
|
||||||
|
|
||||||
@@ -94,23 +94,21 @@ end;
|
|||||||
|
|
||||||
function TFuture<T>.Chain(const Proc: TProcConst<T, TState>): TState;
|
function TFuture<T>.Chain(const Proc: TProcConst<T, TState>): TState;
|
||||||
begin
|
begin
|
||||||
var Done := TLatch.CreateLatch(1);
|
var cFuture := FFuture;
|
||||||
Result := Done.State;
|
|
||||||
|
|
||||||
var future := FFuture;
|
Result := TaskManager.RunTask(cFuture.Done, function: TState begin Result := Proc(cFuture.Value); end);
|
||||||
TaskManager.RunTask(future.Done, procedure begin Proc(future.Value).Subscribe(Done); end);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TFuture<T>.Chain<S>(const Proc: TFunc<T, S>): TFuture<S>;
|
function TFuture<T>.Chain<S>(const Proc: TFunc<T, S>): TFuture<S>;
|
||||||
begin
|
begin
|
||||||
var Cap := FFuture;
|
var cFuture := FFuture;
|
||||||
Result := TFuture<S>.Construct(FFuture.Done, function: S begin Result := Proc(Cap.Value); end);
|
Result := TFuture<S>.Construct(cFuture.Done, function: S begin Result := Proc(cFuture.Value); end);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TFuture<T>.Chain<S>(const Proc: TFuncConst<T, S>): TFuture<S>;
|
function TFuture<T>.Chain<S>(const Proc: TFuncConst<T, S>): TFuture<S>;
|
||||||
begin
|
begin
|
||||||
var Cap := FFuture;
|
var cFuture := FFuture;
|
||||||
Result := TFuture<S>.Construct(FFuture.Done, function: S begin Result := Proc(Cap.Value); end);
|
Result := TFuture<S>.Construct(cFuture.Done, function: S begin Result := Proc(cFuture.Value); end);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TFuture<T>.Construct(const Proc: TFunc<T>): TFuture<T>;
|
class function TFuture<T>.Construct(const Proc: TFunc<T>): TFuture<T>;
|
||||||
@@ -118,7 +116,7 @@ begin
|
|||||||
Result := TMycGateFuncFuture<T>.Create(TaskManager, nil, Proc);
|
Result := TMycGateFuncFuture<T>.Create(TaskManager, nil, Proc);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TFuture<T>.Construct(const Gate: TState.IState; const Proc: TFunc<T>): TFuture<T>;
|
class function TFuture<T>.Construct(const Gate: TState; const Proc: TFunc<T>): TFuture<T>;
|
||||||
begin
|
begin
|
||||||
Result := TMycGateFuncFuture<T>.Create(TaskManager, Gate, Proc);
|
Result := TMycGateFuncFuture<T>.Create(TaskManager, Gate, Proc);
|
||||||
end;
|
end;
|
||||||
|
|||||||
@@ -81,8 +81,6 @@ type
|
|||||||
class function All(const States: TArray<TState>): TState; static;
|
class function All(const States: TArray<TState>): TState; static;
|
||||||
class function Any(const States: TArray<TState>): TState; static;
|
class function Any(const States: TArray<TState>): TState; static;
|
||||||
|
|
||||||
function Subscribe(Subscriber: TSignal.ISubscriber): TSignal.TSubscription; inline;
|
|
||||||
|
|
||||||
class property Null: IState read FNull;
|
class property Null: IState read FNull;
|
||||||
|
|
||||||
property IsSet: Boolean read GetIsSet;
|
property IsSet: Boolean read GetIsSet;
|
||||||
@@ -281,11 +279,6 @@ begin
|
|||||||
Result := FState.IsSet;
|
Result := FState.IsSet;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TState.Subscribe(Subscriber: TSignal.ISubscriber): TSignal.TSubscription;
|
|
||||||
begin
|
|
||||||
Result := Signal.Subscribe(Subscriber);
|
|
||||||
end;
|
|
||||||
|
|
||||||
class operator TState.Implicit(const A: TState): IState;
|
class operator TState.Implicit(const A: TState): IState;
|
||||||
begin
|
begin
|
||||||
Result := A.FState;
|
Result := A.FState;
|
||||||
|
|||||||
+64
-17
@@ -9,23 +9,29 @@ uses
|
|||||||
type
|
type
|
||||||
TTaskManager = record
|
TTaskManager = record
|
||||||
type
|
type
|
||||||
IMycTaskManager = interface
|
ITaskManager = interface
|
||||||
|
procedure CreateThread(const Proc: TProc);
|
||||||
procedure RunTask(const Gate: TState; const Proc: TProc);
|
procedure RunTask(const Gate: TState; const Proc: TProc);
|
||||||
procedure WaitFor(const State: TState);
|
procedure WaitFor(const State: TState);
|
||||||
end;
|
end;
|
||||||
private
|
private
|
||||||
FTaskManager: IMycTaskManager;
|
FTaskManager: ITaskManager;
|
||||||
|
|
||||||
public
|
public
|
||||||
constructor Create(const ATaskManager: IMycTaskManager);
|
constructor Create(const ATaskManager: ITaskManager);
|
||||||
class operator Implicit(const A: IMycTaskManager): TTaskManager; overload;
|
class operator Implicit(const A: ITaskManager): TTaskManager; overload;
|
||||||
class operator Implicit(const A: TTaskManager): IMycTaskManager; overload;
|
class operator Implicit(const A: TTaskManager): ITaskManager; overload;
|
||||||
|
|
||||||
|
// Creates and starts a new, independent thread executing Proc.
|
||||||
|
// Returns a State to await thread completion.
|
||||||
|
function CreateThread(const Proc: TProc): TState;
|
||||||
|
|
||||||
// Run a task when the gate is opened.
|
// Run a task when the gate is opened.
|
||||||
procedure RunTask(const Gate: TState; const Proc: TProc); inline;
|
// Returns a State to await thread completion.
|
||||||
|
function RunTask(const Gate: TState; const Proc: TFunc<TState>): TState;
|
||||||
|
|
||||||
// Waits for the operation associated with State to complete.
|
// Waits for the operation associated with State to complete.
|
||||||
// Must not be called from a worker thread of this factory.
|
// Must not be called from a task of this factory.
|
||||||
// After waiting, or if the state is already set, any first stored exception
|
// After waiting, or if the state is already set, any first stored exception
|
||||||
// from any worker thread of this factory will be re-raised in the calling (main) thread.
|
// from any worker thread of this factory will be re-raised in the calling (main) thread.
|
||||||
// Raises ETaskException if called from within a worker thread.
|
// Raises ETaskException if called from within a worker thread.
|
||||||
@@ -40,7 +46,6 @@ procedure SetupTaskManagerMock;
|
|||||||
implementation
|
implementation
|
||||||
|
|
||||||
uses
|
uses
|
||||||
System.Generics.Collections,
|
|
||||||
Myc.Core.Tasks;
|
Myc.Core.Tasks;
|
||||||
|
|
||||||
type
|
type
|
||||||
@@ -52,13 +57,12 @@ type
|
|||||||
function Notify: Boolean;
|
function Notify: Boolean;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TMycTaskManagerMock = class(TInterfacedObject, TTaskManager.IMycTaskManager)
|
TMycTaskManagerMock = class(TInterfacedObject, TTaskManager.ITaskManager)
|
||||||
public
|
public
|
||||||
// IMycTaskManager
|
constructor Create;
|
||||||
procedure RunTask(const Gate: TState; const Proc: TProc);
|
procedure RunTask(const Gate: TState; const Proc: TProc);
|
||||||
procedure WaitFor(const State: TState);
|
procedure WaitFor(const State: TState);
|
||||||
|
procedure CreateThread(const Proc: TProc);
|
||||||
constructor Create;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TMycExecMock }
|
{ TMycExecMock }
|
||||||
@@ -86,6 +90,11 @@ begin
|
|||||||
inherited Create;
|
inherited Create;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TMycTaskManagerMock.CreateThread(const Proc: TProc);
|
||||||
|
begin
|
||||||
|
Proc();
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TMycTaskManagerMock.RunTask(const Gate: TState; const Proc: TProc);
|
procedure TMycTaskManagerMock.RunTask(const Gate: TState; const Proc: TProc);
|
||||||
begin
|
begin
|
||||||
Gate.Signal.Subscribe(TMycExecMock.Create(Proc));
|
Gate.Signal.Subscribe(TMycExecMock.Create(Proc));
|
||||||
@@ -101,14 +110,52 @@ begin
|
|||||||
TaskManager.Create(TMycTaskManagerMock.Create);
|
TaskManager.Create(TMycTaskManagerMock.Create);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
constructor TTaskManager.Create(const ATaskManager: IMycTaskManager);
|
constructor TTaskManager.Create(const ATaskManager: ITaskManager);
|
||||||
begin
|
begin
|
||||||
FTaskManager := ATaskManager;
|
FTaskManager := ATaskManager;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TTaskManager.RunTask(const Gate: TState; const Proc: TProc);
|
function TTaskManager.CreateThread(const Proc: TProc): TState;
|
||||||
begin
|
begin
|
||||||
FTaskManager.RunTask(Gate, Proc);
|
// Create a latch that will be signaled when the proc finishes
|
||||||
|
var done := TLatch.CreateLatch(1);
|
||||||
|
Result := done.State;
|
||||||
|
|
||||||
|
var cProc := Proc; // Capture Proc for the anonymous method
|
||||||
|
FTaskManager.CreateThread(
|
||||||
|
procedure
|
||||||
|
begin
|
||||||
|
try
|
||||||
|
cProc(); // Execute the provided procedure
|
||||||
|
finally
|
||||||
|
cProc := nil; // Clear the captured proc
|
||||||
|
done.Notify; // Signal completion via the latch's Notify method
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TTaskManager.RunTask(const Gate: TState; const Proc: TFunc<TState>): TState;
|
||||||
|
begin
|
||||||
|
var cProc: TFunc<TState> := Proc;
|
||||||
|
if not Assigned(@cProc) then
|
||||||
|
exit(TState.Null);
|
||||||
|
|
||||||
|
var Done := TLatch.CreateLatch(1);
|
||||||
|
Result := Done.State;
|
||||||
|
|
||||||
|
FTaskManager.RunTask(
|
||||||
|
Gate,
|
||||||
|
procedure
|
||||||
|
begin
|
||||||
|
try
|
||||||
|
cProc.Signal.Subscribe(Done);
|
||||||
|
except
|
||||||
|
Done.Notify;
|
||||||
|
raise;
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TTaskManager.WaitFor(const State: TState);
|
procedure TTaskManager.WaitFor(const State: TState);
|
||||||
@@ -116,12 +163,12 @@ begin
|
|||||||
FTaskManager.WaitFor(State);
|
FTaskManager.WaitFor(State);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class operator TTaskManager.Implicit(const A: IMycTaskManager): TTaskManager;
|
class operator TTaskManager.Implicit(const A: ITaskManager): TTaskManager;
|
||||||
begin
|
begin
|
||||||
Result.Create(A);
|
Result.Create(A);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class operator TTaskManager.Implicit(const A: TTaskManager): IMycTaskManager;
|
class operator TTaskManager.Implicit(const A: TTaskManager): ITaskManager;
|
||||||
begin
|
begin
|
||||||
Result := A.FTaskManager;
|
Result := A.FTaskManager;
|
||||||
end;
|
end;
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ begin
|
|||||||
dirtyFlag := CreateAndPrepareDirtyFlag(True); // Start dirty
|
dirtyFlag := CreateAndPrepareDirtyFlag(True); // Start dirty
|
||||||
subscriber := TMockSubscriber.Create;
|
subscriber := TMockSubscriber.Create;
|
||||||
|
|
||||||
subscription := dirtyFlag.State.Subscribe(subscriber);
|
subscription := dirtyFlag.State.Signal.Subscribe(subscriber);
|
||||||
|
|
||||||
Assert.AreEqual(1, subscriber.NotifyCount, 'Subscriber should be notified immediately when subscribing to an already dirty flag.');
|
Assert.AreEqual(1, subscriber.NotifyCount, 'Subscriber should be notified immediately when subscribing to an already dirty flag.');
|
||||||
Assert.IsTrue(subscriber.WasCalled, 'Subscriber WasCalled should be true.');
|
Assert.IsTrue(subscriber.WasCalled, 'Subscriber WasCalled should be true.');
|
||||||
@@ -211,7 +211,7 @@ begin
|
|||||||
dirtyFlag := CreateAndPrepareDirtyFlag(False); // Start clean
|
dirtyFlag := CreateAndPrepareDirtyFlag(False); // Start clean
|
||||||
subscriber := TMockSubscriber.Create;
|
subscriber := TMockSubscriber.Create;
|
||||||
|
|
||||||
subscription := dirtyFlag.State.Subscribe(subscriber);
|
subscription := dirtyFlag.State.Signal.Subscribe(subscriber);
|
||||||
|
|
||||||
Assert.AreEqual(0, subscriber.NotifyCount, 'Subscriber should NOT be notified immediately when subscribing to a clean flag.');
|
Assert.AreEqual(0, subscriber.NotifyCount, 'Subscriber should NOT be notified immediately when subscribing to a clean flag.');
|
||||||
Assert.IsFalse(subscriber.WasCalled, 'Subscriber WasCalled should be false.');
|
Assert.IsFalse(subscriber.WasCalled, 'Subscriber WasCalled should be false.');
|
||||||
@@ -225,7 +225,7 @@ var
|
|||||||
begin
|
begin
|
||||||
dirtyFlag := CreateAndPrepareDirtyFlag(False); // Start clean
|
dirtyFlag := CreateAndPrepareDirtyFlag(False); // Start clean
|
||||||
subscriber := TMockSubscriber.Create;
|
subscriber := TMockSubscriber.Create;
|
||||||
subscription := dirtyFlag.State.Subscribe(subscriber);
|
subscription := dirtyFlag.State.Signal.Subscribe(subscriber);
|
||||||
|
|
||||||
Assert.AreEqual(0, subscriber.NotifyCount, 'Subscriber initially not notified.');
|
Assert.AreEqual(0, subscriber.NotifyCount, 'Subscriber initially not notified.');
|
||||||
|
|
||||||
@@ -242,7 +242,7 @@ var
|
|||||||
begin
|
begin
|
||||||
dirtyFlag := CreateAndPrepareDirtyFlag(True); // Start dirty
|
dirtyFlag := CreateAndPrepareDirtyFlag(True); // Start dirty
|
||||||
subscriber := TMockSubscriber.Create;
|
subscriber := TMockSubscriber.Create;
|
||||||
subscription := dirtyFlag.State.Subscribe(subscriber); // Gets initial notification
|
subscription := dirtyFlag.State.Signal.Subscribe(subscriber); // Gets initial notification
|
||||||
|
|
||||||
Assert.AreEqual(1, subscriber.NotifyCount, 'Subscriber notified on subscribe to dirty flag.');
|
Assert.AreEqual(1, subscriber.NotifyCount, 'Subscriber notified on subscribe to dirty flag.');
|
||||||
|
|
||||||
@@ -259,7 +259,7 @@ var
|
|||||||
begin
|
begin
|
||||||
dirtyFlag := CreateAndPrepareDirtyFlag(True); // Start dirty
|
dirtyFlag := CreateAndPrepareDirtyFlag(True); // Start dirty
|
||||||
subscriber := TMockSubscriber.Create;
|
subscriber := TMockSubscriber.Create;
|
||||||
subscription := dirtyFlag.State.Subscribe(subscriber); // Gets initial notification
|
subscription := dirtyFlag.State.Signal.Subscribe(subscriber); // Gets initial notification
|
||||||
|
|
||||||
Assert.AreEqual(1, subscriber.NotifyCount, 'Subscriber notified on subscribe.');
|
Assert.AreEqual(1, subscriber.NotifyCount, 'Subscriber notified on subscribe.');
|
||||||
dirtyFlag.Reset; // Reset the flag
|
dirtyFlag.Reset; // Reset the flag
|
||||||
@@ -278,8 +278,8 @@ begin
|
|||||||
dirtyFlag := CreateAndPrepareDirtyFlag(False); // Start clean
|
dirtyFlag := CreateAndPrepareDirtyFlag(False); // Start clean
|
||||||
sub1 := TMockSubscriber.Create;
|
sub1 := TMockSubscriber.Create;
|
||||||
sub2 := TMockSubscriber.Create;
|
sub2 := TMockSubscriber.Create;
|
||||||
subscription1 := dirtyFlag.State.Subscribe(sub1);
|
subscription1 := dirtyFlag.State.Signal.Subscribe(sub1);
|
||||||
subscription2 := dirtyFlag.State.Subscribe(sub2);
|
subscription2 := dirtyFlag.State.Signal.Subscribe(sub2);
|
||||||
|
|
||||||
dirtyFlag.Notify; // Make flag dirty
|
dirtyFlag.Notify; // Make flag dirty
|
||||||
|
|
||||||
@@ -295,7 +295,7 @@ var
|
|||||||
begin
|
begin
|
||||||
dirtyFlag := CreateAndPrepareDirtyFlag(False); // Start clean
|
dirtyFlag := CreateAndPrepareDirtyFlag(False); // Start clean
|
||||||
subscriber := TMockSubscriber.Create;
|
subscriber := TMockSubscriber.Create;
|
||||||
subscription := dirtyFlag.State.Subscribe(subscriber);
|
subscription := dirtyFlag.State.Signal.Subscribe(subscriber);
|
||||||
|
|
||||||
subscription.Unsubscribe; // Explicitly unsubscribe
|
subscription.Unsubscribe; // Explicitly unsubscribe
|
||||||
|
|
||||||
@@ -318,8 +318,8 @@ begin
|
|||||||
flagB := CreateAndPrepareDirtyFlag(False); // B starts clean
|
flagB := CreateAndPrepareDirtyFlag(False); // B starts clean
|
||||||
subToB := TMockSubscriber.Create;
|
subToB := TMockSubscriber.Create;
|
||||||
|
|
||||||
subscriptionMock_B := flagB.State.Subscribe(subToB);
|
subscriptionMock_B := flagB.State.Signal.Subscribe(subToB);
|
||||||
subscriptionA_B := flagA.State.Subscribe(flagB); // B subscribes to A's state changes (B's Notify will be called)
|
subscriptionA_B := flagA.State.Signal.Subscribe(flagB); // B subscribes to A's state changes (B's Notify will be called)
|
||||||
|
|
||||||
flagA.Notify; // Make A dirty. This should trigger B.Notify
|
flagA.Notify; // Make A dirty. This should trigger B.Notify
|
||||||
|
|
||||||
@@ -339,9 +339,9 @@ begin
|
|||||||
flagC := CreateAndPrepareDirtyFlag(False);
|
flagC := CreateAndPrepareDirtyFlag(False);
|
||||||
subToC := TMockSubscriber.Create;
|
subToC := TMockSubscriber.Create;
|
||||||
|
|
||||||
subscriptionMock_C := flagC.State.Subscribe(subToC);
|
subscriptionMock_C := flagC.State.Signal.Subscribe(subToC);
|
||||||
subscriptionC_B := flagB.State.Subscribe(flagC); // C subscribes to B
|
subscriptionC_B := flagB.State.Signal.Subscribe(flagC); // C subscribes to B
|
||||||
subscriptionB_A := flagA.State.Subscribe(flagB); // B subscribes to A
|
subscriptionB_A := flagA.State.Signal.Subscribe(flagB); // B subscribes to A
|
||||||
|
|
||||||
flagA.Notify; // Make A dirty
|
flagA.Notify; // Make A dirty
|
||||||
|
|
||||||
@@ -362,8 +362,8 @@ begin
|
|||||||
flagA := CreateAndPrepareDirtyFlag(False);
|
flagA := CreateAndPrepareDirtyFlag(False);
|
||||||
flagB := CreateAndPrepareDirtyFlag(False);
|
flagB := CreateAndPrepareDirtyFlag(False);
|
||||||
subToB := TMockSubscriber.Create;
|
subToB := TMockSubscriber.Create;
|
||||||
subscriptionMock_B := flagB.State.Subscribe(subToB);
|
subscriptionMock_B := flagB.State.Signal.Subscribe(subToB);
|
||||||
subscriptionA_B := flagA.State.Subscribe(flagB);
|
subscriptionA_B := flagA.State.Signal.Subscribe(flagB);
|
||||||
|
|
||||||
flagA.Notify; // A dirty -> B dirty. subToB notified.
|
flagA.Notify; // A dirty -> B dirty. subToB notified.
|
||||||
Assert.IsTrue(flagB.State.IsSet, 'Flag B should be dirty initially.');
|
Assert.IsTrue(flagB.State.IsSet, 'Flag B should be dirty initially.');
|
||||||
@@ -391,8 +391,8 @@ begin
|
|||||||
flagA := CreateAndPrepareDirtyFlag(False);
|
flagA := CreateAndPrepareDirtyFlag(False);
|
||||||
flagB := CreateAndPrepareDirtyFlag(False);
|
flagB := CreateAndPrepareDirtyFlag(False);
|
||||||
subToB := TMockSubscriber.Create;
|
subToB := TMockSubscriber.Create;
|
||||||
subscriptionMock_B := flagB.State.Subscribe(subToB);
|
subscriptionMock_B := flagB.State.Signal.Subscribe(subToB);
|
||||||
subscriptionA_B := flagA.State.Subscribe(flagB);
|
subscriptionA_B := flagA.State.Signal.Subscribe(flagB);
|
||||||
|
|
||||||
flagA.Notify; // A dirty -> B dirty. subToB notified.
|
flagA.Notify; // A dirty -> B dirty. subToB notified.
|
||||||
Assert.IsTrue(flagB.State.IsSet, 'Flag B should be dirty after A triggers.');
|
Assert.IsTrue(flagB.State.IsSet, 'Flag B should be dirty after A triggers.');
|
||||||
@@ -422,9 +422,9 @@ begin
|
|||||||
flagC := CreateAndPrepareDirtyFlag(False);
|
flagC := CreateAndPrepareDirtyFlag(False);
|
||||||
subToC := TMockSubscriber.Create;
|
subToC := TMockSubscriber.Create;
|
||||||
|
|
||||||
subscriptionMock_C := flagC.State.Subscribe(subToC);
|
subscriptionMock_C := flagC.State.Signal.Subscribe(subToC);
|
||||||
subscriptionC_B := flagB.State.Subscribe(flagC);
|
subscriptionC_B := flagB.State.Signal.Subscribe(flagC);
|
||||||
subscriptionB_A := flagA.State.Subscribe(flagB);
|
subscriptionB_A := flagA.State.Signal.Subscribe(flagB);
|
||||||
|
|
||||||
// Initial full propagation
|
// Initial full propagation
|
||||||
flagA.Notify; // A dirty -> B dirty -> C dirty. subToC notified.
|
flagA.Notify; // A dirty -> B dirty -> C dirty. subToC notified.
|
||||||
@@ -461,9 +461,9 @@ begin
|
|||||||
flagC := CreateAndPrepareDirtyFlag(False);
|
flagC := CreateAndPrepareDirtyFlag(False);
|
||||||
subToC := TMockSubscriber.Create;
|
subToC := TMockSubscriber.Create;
|
||||||
|
|
||||||
subscriptionMock_C := flagC.State.Subscribe(subToC);
|
subscriptionMock_C := flagC.State.Signal.Subscribe(subToC);
|
||||||
subscriptionC_B := flagB.State.Subscribe(flagC);
|
subscriptionC_B := flagB.State.Signal.Subscribe(flagC);
|
||||||
subscriptionB_A := flagA.State.Subscribe(flagB);
|
subscriptionB_A := flagA.State.Signal.Subscribe(flagB);
|
||||||
|
|
||||||
// All are clean. Trigger A.
|
// All are clean. Trigger A.
|
||||||
flagA.Notify; // A dirty -> B dirty -> C dirty. subToC notified.
|
flagA.Notify; // A dirty -> B dirty -> C dirty. subToC notified.
|
||||||
@@ -501,9 +501,9 @@ begin
|
|||||||
flagC := CreateAndPrepareDirtyFlag(False);
|
flagC := CreateAndPrepareDirtyFlag(False);
|
||||||
subToC := TMockSubscriber.Create;
|
subToC := TMockSubscriber.Create;
|
||||||
|
|
||||||
subscriptionMock_C := flagC.State.Subscribe(subToC);
|
subscriptionMock_C := flagC.State.Signal.Subscribe(subToC);
|
||||||
subscriptionC_B := flagB.State.Subscribe(flagC);
|
subscriptionC_B := flagB.State.Signal.Subscribe(flagC);
|
||||||
subscriptionB_A := flagA.State.Subscribe(flagB);
|
subscriptionB_A := flagA.State.Signal.Subscribe(flagB);
|
||||||
|
|
||||||
// 1. Trigger A
|
// 1. Trigger A
|
||||||
flagA.Notify; // A, B, C become dirty. subToC notified (count = 1).
|
flagA.Notify; // A, B, C become dirty. subToC notified (count = 1).
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ var
|
|||||||
begin
|
begin
|
||||||
latch := TLatch.CreateLatch(1);
|
latch := TLatch.CreateLatch(1);
|
||||||
mockSub := TMockSubscriber.Create;
|
mockSub := TMockSubscriber.Create;
|
||||||
subscription := latch.State.Subscribe(mockSub); // Subscribing to the Latch's state
|
subscription := latch.State.Signal.Subscribe(mockSub); // Subscribing to the Latch's state
|
||||||
|
|
||||||
Assert.IsFalse(latch.State.IsSet, 'Latch should initially not be set.');
|
Assert.IsFalse(latch.State.IsSet, 'Latch should initially not be set.');
|
||||||
Assert.AreEqual(0, mockSub.NotifyCount, 'MockSub should not have been notified yet (before latch set).');
|
Assert.AreEqual(0, mockSub.NotifyCount, 'MockSub should not have been notified yet (before latch set).');
|
||||||
@@ -204,9 +204,9 @@ begin
|
|||||||
mockSub2 := TMockSubscriber.Create;
|
mockSub2 := TMockSubscriber.Create;
|
||||||
mockSub3 := TMockSubscriber.Create;
|
mockSub3 := TMockSubscriber.Create;
|
||||||
|
|
||||||
sub1 := latch.State.Subscribe(mockSub1);
|
sub1 := latch.State.Signal.Subscribe(mockSub1);
|
||||||
sub2 := latch.State.Subscribe(mockSub2);
|
sub2 := latch.State.Signal.Subscribe(mockSub2);
|
||||||
sub3 := latch.State.Subscribe(mockSub3);
|
sub3 := latch.State.Signal.Subscribe(mockSub3);
|
||||||
|
|
||||||
latch.Notify; // Latch becomes set
|
latch.Notify; // Latch becomes set
|
||||||
|
|
||||||
@@ -223,7 +223,7 @@ var
|
|||||||
begin
|
begin
|
||||||
latch := TLatch.CreateLatch(2); // Count = 2
|
latch := TLatch.CreateLatch(2); // Count = 2
|
||||||
mockSub := TMockSubscriber.Create;
|
mockSub := TMockSubscriber.Create;
|
||||||
subscription := latch.State.Subscribe(mockSub);
|
subscription := latch.State.Signal.Subscribe(mockSub);
|
||||||
|
|
||||||
Assert.AreEqual(0, mockSub.NotifyCount, 'MockSub should not be notified upon subscription if latch is not set.');
|
Assert.AreEqual(0, mockSub.NotifyCount, 'MockSub should not be notified upon subscription if latch is not set.');
|
||||||
latch.Notify; // Count becomes 1, still not set
|
latch.Notify; // Count becomes 1, still not set
|
||||||
@@ -238,7 +238,7 @@ var
|
|||||||
begin
|
begin
|
||||||
latch := TLatch.CreateLatch(1);
|
latch := TLatch.CreateLatch(1);
|
||||||
mockSub := TMockSubscriber.Create;
|
mockSub := TMockSubscriber.Create;
|
||||||
subscription := latch.State.Subscribe(mockSub);
|
subscription := latch.State.Signal.Subscribe(mockSub);
|
||||||
|
|
||||||
latch.Notify; // Sets latch, notifies MockSub. Subscribers are then unadvised by UnadviseAll.
|
latch.Notify; // Sets latch, notifies MockSub. Subscribers are then unadvised by UnadviseAll.
|
||||||
Assert.AreEqual(1, mockSub.NotifyCount, 'MockSub notify count should be 1 after latch set.');
|
Assert.AreEqual(1, mockSub.NotifyCount, 'MockSub notify count should be 1 after latch set.');
|
||||||
@@ -255,7 +255,7 @@ var
|
|||||||
begin
|
begin
|
||||||
latch := TLatch.CreateLatch(1); // Create with count 1
|
latch := TLatch.CreateLatch(1); // Create with count 1
|
||||||
mockSub1 := TMockSubscriber.Create;
|
mockSub1 := TMockSubscriber.Create;
|
||||||
subscription1 := latch.State.Subscribe(mockSub1); // Subscribe before set
|
subscription1 := latch.State.Signal.Subscribe(mockSub1); // Subscribe before set
|
||||||
|
|
||||||
latch.Notify; // Latch becomes set. mockSub1 is notified.
|
latch.Notify; // Latch becomes set. mockSub1 is notified.
|
||||||
|
|
||||||
@@ -264,7 +264,8 @@ begin
|
|||||||
|
|
||||||
// Attempt to subscribe mockSub2 after the latch is already set.
|
// Attempt to subscribe mockSub2 after the latch is already set.
|
||||||
mockSub2 := TMockSubscriber.Create;
|
mockSub2 := TMockSubscriber.Create;
|
||||||
subscription2 := latch.State.Subscribe(mockSub2); // TMycLatch.Subscribe (via TMycAbstractFlag) notifies immediately if already set.
|
subscription2 :=
|
||||||
|
latch.State.Signal.Subscribe(mockSub2); // TMycLatch.Subscribe (via TMycAbstractFlag) notifies immediately if already set.
|
||||||
|
|
||||||
Assert.AreEqual(1, mockSub2.NotifyCount, 'MockSub2 should be notified immediately upon subscribing to an already set latch.');
|
Assert.AreEqual(1, mockSub2.NotifyCount, 'MockSub2 should be notified immediately upon subscribing to an already set latch.');
|
||||||
|
|
||||||
@@ -285,9 +286,9 @@ begin
|
|||||||
latchB := TLatch.CreateLatch(1); // latchB is an TSignal.ISubscriber
|
latchB := TLatch.CreateLatch(1); // latchB is an TSignal.ISubscriber
|
||||||
mockSubForB := TMockSubscriber.Create;
|
mockSubForB := TMockSubscriber.Create;
|
||||||
|
|
||||||
subHandle_Mock_listens_B := latchB.State.Subscribe(mockSubForB);
|
subHandle_Mock_listens_B := latchB.State.Signal.Subscribe(mockSubForB);
|
||||||
// LatchA's state is an IMycSignal. LatchB (as TSignal.ISubscriber) subscribes to it.
|
// LatchA's state is an IMycSignal. LatchB (as TSignal.ISubscriber) subscribes to it.
|
||||||
subHandle_B_listens_A := latchA.State.Subscribe(latchB);
|
subHandle_B_listens_A := latchA.State.Signal.Subscribe(latchB);
|
||||||
|
|
||||||
Assert.IsFalse(latchA.State.IsSet, 'LatchA initially not set.');
|
Assert.IsFalse(latchA.State.IsSet, 'LatchA initially not set.');
|
||||||
Assert.IsFalse(latchB.State.IsSet, 'LatchB initially not set.');
|
Assert.IsFalse(latchB.State.IsSet, 'LatchB initially not set.');
|
||||||
@@ -311,9 +312,9 @@ begin
|
|||||||
latchC := TLatch.CreateLatch(1);
|
latchC := TLatch.CreateLatch(1);
|
||||||
mockSubForC := TMockSubscriber.Create;
|
mockSubForC := TMockSubscriber.Create;
|
||||||
|
|
||||||
sub_Mock_C := latchC.State.Subscribe(mockSubForC);
|
sub_Mock_C := latchC.State.Signal.Subscribe(mockSubForC);
|
||||||
sub_C_B := latchB.State.Subscribe(latchC); // LatchC subscribes to LatchB's state
|
sub_C_B := latchB.State.Signal.Subscribe(latchC); // LatchC subscribes to LatchB's state
|
||||||
sub_B_A := latchA.State.Subscribe(latchB); // LatchB subscribes to LatchA's state
|
sub_B_A := latchA.State.Signal.Subscribe(latchB); // LatchB subscribes to LatchA's state
|
||||||
|
|
||||||
latchA.Notify; // Call Notify on LatchA
|
latchA.Notify; // Call Notify on LatchA
|
||||||
|
|
||||||
@@ -335,8 +336,8 @@ begin
|
|||||||
latchB := TLatch.CreateLatch(1);
|
latchB := TLatch.CreateLatch(1);
|
||||||
mockSubForB := TMockSubscriber.Create;
|
mockSubForB := TMockSubscriber.Create;
|
||||||
|
|
||||||
sub_Mock_B := latchB.State.Subscribe(mockSubForB);
|
sub_Mock_B := latchB.State.Signal.Subscribe(mockSubForB);
|
||||||
sub_B_A := latchA.State.Subscribe(latchB);
|
sub_B_A := latchA.State.Signal.Subscribe(latchB);
|
||||||
|
|
||||||
latchA.Notify; // First notify for LatchA (count becomes 1)
|
latchA.Notify; // First notify for LatchA (count becomes 1)
|
||||||
Assert.IsFalse(latchA.State.IsSet, 'LatchA should not be set after first notify of two.');
|
Assert.IsFalse(latchA.State.IsSet, 'LatchA should not be set after first notify of two.');
|
||||||
@@ -361,7 +362,7 @@ begin
|
|||||||
latch := TLatch.CreateLatch(0); // Returns TMycLatch.Null which is set.
|
latch := TLatch.CreateLatch(0); // Returns TMycLatch.Null which is set.
|
||||||
mockSub := TMockSubscriber.Create;
|
mockSub := TMockSubscriber.Create;
|
||||||
|
|
||||||
subscription := latch.State.Subscribe(mockSub); // TMycLatch.Subscribe notifies immediately if already set.
|
subscription := latch.State.Signal.Subscribe(mockSub); // TMycLatch.Subscribe notifies immediately if already set.
|
||||||
|
|
||||||
Assert.IsTrue(latch.State.IsSet, 'Latch IsSet property mismatch (should be true for Null latch).');
|
Assert.IsTrue(latch.State.IsSet, 'Latch IsSet property mismatch (should be true for Null latch).');
|
||||||
Assert.AreEqual(1, mockSub.NotifyCount, 'MockSub should be notified immediately upon subscribing to an initially set latch.');
|
Assert.AreEqual(1, mockSub.NotifyCount, 'MockSub should be notified immediately upon subscribing to an initially set latch.');
|
||||||
@@ -376,7 +377,7 @@ begin
|
|||||||
latch := TLatch.CreateLatch(1);
|
latch := TLatch.CreateLatch(1);
|
||||||
mockSub := TMockSubscriber.Create;
|
mockSub := TMockSubscriber.Create;
|
||||||
|
|
||||||
subscription := latch.State.Subscribe(mockSub);
|
subscription := latch.State.Signal.Subscribe(mockSub);
|
||||||
subscription.Unsubscribe; // Explicitly call Unsubscribe on the record
|
subscription.Unsubscribe; // Explicitly call Unsubscribe on the record
|
||||||
|
|
||||||
latch.Notify; // Latch becomes set
|
latch.Notify; // Latch becomes set
|
||||||
@@ -399,7 +400,7 @@ begin
|
|||||||
Assert.IsFalse(latch.State.IsSet, 'Latch should not be set yet after partial notifies.');
|
Assert.IsFalse(latch.State.IsSet, 'Latch should not be set yet after partial notifies.');
|
||||||
|
|
||||||
mockSub := TMockSubscriber.Create;
|
mockSub := TMockSubscriber.Create;
|
||||||
subscription := latch.State.Subscribe(mockSub);
|
subscription := latch.State.Signal.Subscribe(mockSub);
|
||||||
Assert.AreEqual(0, mockSub.NotifyCount, 'MockSub should not be notified on subscribe if latch not yet set.');
|
Assert.AreEqual(0, mockSub.NotifyCount, 'MockSub should not be notified on subscribe if latch not yet set.');
|
||||||
|
|
||||||
latch.Notify; // Count -> 0, Latch becomes set and notifies mockSub
|
latch.Notify; // Count -> 0, Latch becomes set and notifies mockSub
|
||||||
@@ -419,8 +420,8 @@ begin
|
|||||||
mockSub1 := TMockSubscriber.Create;
|
mockSub1 := TMockSubscriber.Create;
|
||||||
mockSub2 := TMockSubscriber.Create;
|
mockSub2 := TMockSubscriber.Create;
|
||||||
|
|
||||||
subscription1 := latch.State.Subscribe(mockSub1);
|
subscription1 := latch.State.Signal.Subscribe(mockSub1);
|
||||||
subscription2 := latch.State.Subscribe(mockSub2);
|
subscription2 := latch.State.Signal.Subscribe(mockSub2);
|
||||||
|
|
||||||
Assert.AreEqual(0, mockSub1.NotifyCount, 'MockSub1 initial NotifyCount should be 0.');
|
Assert.AreEqual(0, mockSub1.NotifyCount, 'MockSub1 initial NotifyCount should be 0.');
|
||||||
Assert.AreEqual(0, mockSub2.NotifyCount, 'MockSub2 initial NotifyCount should be 0.');
|
Assert.AreEqual(0, mockSub2.NotifyCount, 'MockSub2 initial NotifyCount should be 0.');
|
||||||
@@ -450,7 +451,7 @@ begin
|
|||||||
try
|
try
|
||||||
begin // Inner block to control lifetime of 'subscription'
|
begin // Inner block to control lifetime of 'subscription'
|
||||||
var subscription: TSignal.TSubscription; // Local to this block
|
var subscription: TSignal.TSubscription; // Local to this block
|
||||||
subscription := latch.State.Subscribe(mockSub);
|
subscription := latch.State.Signal.Subscribe(mockSub);
|
||||||
|
|
||||||
latch.Notify; // Sets the latch, mockSub is notified, UnadviseAll is called internally.
|
latch.Notify; // Sets the latch, mockSub is notified, UnadviseAll is called internally.
|
||||||
Assert.AreEqual(1, mockSub.NotifyCount, 'MockSub was notified when latch set.');
|
Assert.AreEqual(1, mockSub.NotifyCount, 'MockSub was notified when latch set.');
|
||||||
@@ -481,7 +482,7 @@ var
|
|||||||
begin
|
begin
|
||||||
latch := TLatch.CreateLatch(1);
|
latch := TLatch.CreateLatch(1);
|
||||||
mockSubInitial := TMockSubscriber.Create;
|
mockSubInitial := TMockSubscriber.Create;
|
||||||
subscriptionInitial := latch.State.Subscribe(mockSubInitial);
|
subscriptionInitial := latch.State.Signal.Subscribe(mockSubInitial);
|
||||||
|
|
||||||
// Set the latch; mockSubInitial is notified, UnadviseAll is called.
|
// Set the latch; mockSubInitial is notified, UnadviseAll is called.
|
||||||
latch.Notify;
|
latch.Notify;
|
||||||
@@ -489,7 +490,7 @@ begin
|
|||||||
|
|
||||||
// Subscribe a new subscriber to the already set latch
|
// Subscribe a new subscriber to the already set latch
|
||||||
mockSubNew := TMockSubscriber.Create;
|
mockSubNew := TMockSubscriber.Create;
|
||||||
subscriptionNew := latch.State.Subscribe(mockSubNew);
|
subscriptionNew := latch.State.Signal.Subscribe(mockSubNew);
|
||||||
|
|
||||||
// New subscriber should be notified immediately upon subscription to an already set latch
|
// New subscriber should be notified immediately upon subscription to an already set latch
|
||||||
Assert.AreEqual(1, mockSubNew.NotifyCount, 'New subscriber should be notified immediately upon subscription.');
|
Assert.AreEqual(1, mockSubNew.NotifyCount, 'New subscriber should be notified immediately upon subscription.');
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
TTestDataArray = class(TObject)
|
TTestDataArray = class(TObject)
|
||||||
private
|
private
|
||||||
FArray: IDataArray<TAskBidItem>;
|
FArray: TDataSeries<TAskBidItem>;
|
||||||
procedure SetupSeriesWithData(ItemCount: Integer = 10; MaxLookBack: Int64 = 0);
|
procedure SetupSeriesWithData(ItemCount: Integer = 10; MaxLookBack: Int64 = 0);
|
||||||
public
|
public
|
||||||
[Setup]
|
[Setup]
|
||||||
@@ -67,7 +67,6 @@ type
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
TTestDataSeries = class(TObject)
|
TTestDataSeries = class(TObject)
|
||||||
private
|
private
|
||||||
FArray: IDataArray<TAskBidItem>;
|
|
||||||
FSeries: TDataSeries<TAskBidItem>;
|
FSeries: TDataSeries<TAskBidItem>;
|
||||||
procedure SetupSeriesWithData(ItemCount: Integer = 10; MaxLookBack: Int64 = 0);
|
procedure SetupSeriesWithData(ItemCount: Integer = 10; MaxLookBack: Int64 = 0);
|
||||||
public
|
public
|
||||||
@@ -111,7 +110,7 @@ implementation
|
|||||||
|
|
||||||
procedure TTestDataArray.Setup;
|
procedure TTestDataArray.Setup;
|
||||||
begin
|
begin
|
||||||
FArray := TDataSeries<TAskBidItem>.CreateArray(0);
|
FArray := TDataSeries<TAskBidItem>.CreateWriteable(0);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TTestDataArray.Teardown;
|
procedure TTestDataArray.Teardown;
|
||||||
@@ -125,7 +124,7 @@ var
|
|||||||
DataPoint: TDataPoint<TAskBidItem>;
|
DataPoint: TDataPoint<TAskBidItem>;
|
||||||
baseTime: TDateTime;
|
baseTime: TDateTime;
|
||||||
begin
|
begin
|
||||||
FArray := TDataSeries<TAskBidItem>.CreateArray(MaxLookBack);
|
FArray := TDataSeries<TAskBidItem>.CreateWriteable(MaxLookBack);
|
||||||
baseTime := EncodeDate(2020, 7, 7);
|
baseTime := EncodeDate(2020, 7, 7);
|
||||||
|
|
||||||
// Add data points with increasing timestamps.
|
// Add data points with increasing timestamps.
|
||||||
@@ -410,14 +409,12 @@ end;
|
|||||||
|
|
||||||
procedure TTestDataSeries.Setup;
|
procedure TTestDataSeries.Setup;
|
||||||
begin
|
begin
|
||||||
FArray := TDataSeries<TAskBidItem>.CreateArray(0);
|
FSeries := TDataSeries<TAskBidItem>.CreateWriteable(0);
|
||||||
FSeries := FArray;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TTestDataSeries.Teardown;
|
procedure TTestDataSeries.Teardown;
|
||||||
begin
|
begin
|
||||||
FSeries := Default(TDataSeries<TAskBidItem>);
|
FSeries := Default(TDataSeries<TAskBidItem>);
|
||||||
FArray := nil;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TTestDataSeries.SetupSeriesWithData(ItemCount: Integer = 10; MaxLookBack: Int64 = 0);
|
procedure TTestDataSeries.SetupSeriesWithData(ItemCount: Integer = 10; MaxLookBack: Int64 = 0);
|
||||||
@@ -426,15 +423,14 @@ var
|
|||||||
DataPoint: TDataPoint<TAskBidItem>;
|
DataPoint: TDataPoint<TAskBidItem>;
|
||||||
baseTime: TDateTime;
|
baseTime: TDateTime;
|
||||||
begin
|
begin
|
||||||
FArray := TDataSeries<TAskBidItem>.CreateArray(MaxLookBack);
|
FSeries := TDataSeries<TAskBidItem>.CreateWriteable(MaxLookBack);
|
||||||
FSeries := FArray;
|
|
||||||
baseTime := EncodeDate(2020, 7, 7);
|
baseTime := EncodeDate(2020, 7, 7);
|
||||||
|
|
||||||
// Add data points with increasing timestamps.
|
// Add data points with increasing timestamps.
|
||||||
for i := 0 to ItemCount - 1 do
|
for i := 0 to ItemCount - 1 do
|
||||||
begin
|
begin
|
||||||
DataPoint := TDataPoint<TAskBidItem>.Create(baseTime + i, TAskBidItem.Create(i * 1.0, i * 1.0 + 0.1));
|
DataPoint := TDataPoint<TAskBidItem>.Create(baseTime + i, TAskBidItem.Create(i * 1.0, i * 1.0 + 0.1));
|
||||||
FArray.Add(DataPoint);
|
FSeries.Add(DataPoint);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -445,7 +441,7 @@ var
|
|||||||
begin
|
begin
|
||||||
SetupSeriesWithData(15);
|
SetupSeriesWithData(15);
|
||||||
|
|
||||||
copy := FSeries.Copy;
|
copy := FSeries.Immutable;
|
||||||
|
|
||||||
Assert.AreEqual(FSeries.Count, copy.Count, 'Copy must have the same count as the original');
|
Assert.AreEqual(FSeries.Count, copy.Count, 'Copy must have the same count as the original');
|
||||||
Assert.AreEqual(FSeries.TotalCount, copy.TotalCount, 'Copy must have the same total count as the original');
|
Assert.AreEqual(FSeries.TotalCount, copy.TotalCount, 'Copy must have the same total count as the original');
|
||||||
@@ -467,21 +463,21 @@ begin
|
|||||||
SetupSeriesWithData(10);
|
SetupSeriesWithData(10);
|
||||||
|
|
||||||
// Create the copy
|
// Create the copy
|
||||||
copy := FSeries.Copy;
|
copy := FSeries.Immutable;
|
||||||
originalCount := copy.Count;
|
originalCount := copy.Count;
|
||||||
originalTotalCount := copy.TotalCount;
|
originalTotalCount := copy.TotalCount;
|
||||||
|
|
||||||
// Modify the original series
|
// Modify the original series
|
||||||
lastTime := FArray[0].Time;
|
lastTime := FSeries[0].Time;
|
||||||
newPoint := TDataPoint<TAskBidItem>.Create(lastTime + 1, TAskBidItem.Create(99, 99.1));
|
newPoint := TDataPoint<TAskBidItem>.Create(lastTime + 1, TAskBidItem.Create(99, 99.1));
|
||||||
FArray.Add(newPoint);
|
FSeries.Add(newPoint);
|
||||||
|
|
||||||
Assert.AreEqual(Int64(11), FArray.Count, 'Original array count should have increased');
|
Assert.AreEqual(Int64(11), FSeries.Count, 'Original array count should have increased');
|
||||||
|
|
||||||
// Verify the copy remains unchanged
|
// Verify the copy remains unchanged
|
||||||
Assert.AreEqual(originalCount, copy.Count, 'Copy count must not change after original is modified');
|
Assert.AreEqual(originalCount, copy.Count, 'Copy count must not change after original is modified');
|
||||||
Assert.AreEqual(originalTotalCount, copy.TotalCount, 'Copy total count must not change after original is modified');
|
Assert.AreEqual(originalTotalCount, copy.TotalCount, 'Copy total count must not change after original is modified');
|
||||||
Assert.AreNotEqual(FArray[0].Time, copy[0].Time, 'Newest item in copy should not be the new item from original');
|
Assert.AreNotEqual(FSeries[0].Time, copy[0].Time, 'Newest item in copy should not be the new item from original');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TTestDataSeries.TestCopyRespectsMaxLookback;
|
procedure TTestDataSeries.TestCopyRespectsMaxLookback;
|
||||||
@@ -495,16 +491,16 @@ begin
|
|||||||
Assert.AreEqual(Int64(15), FSeries.Count, 'Pre-condition: Series count should be capped by MaxLookback');
|
Assert.AreEqual(Int64(15), FSeries.Count, 'Pre-condition: Series count should be capped by MaxLookback');
|
||||||
Assert.AreEqual(Int64(20), FSeries.TotalCount, 'Pre-condition: Series total count should be 20');
|
Assert.AreEqual(Int64(20), FSeries.TotalCount, 'Pre-condition: Series total count should be 20');
|
||||||
|
|
||||||
copy := FSeries.Copy;
|
copy := FSeries.Immutable;
|
||||||
|
|
||||||
// Verify the copy reflects the MaxLookback state
|
// Verify the copy reflects the MaxLookback state
|
||||||
Assert.AreEqual(Int64(15), copy.Count, 'Copy count should be the MaxLookback value of the original');
|
Assert.AreEqual(Int64(15), copy.Count, 'Copy count should be the MaxLookback value of the original');
|
||||||
Assert.AreEqual(Int64(20), copy.TotalCount, 'Copy total count should be the total items added to the original');
|
Assert.AreEqual(Int64(20), copy.TotalCount, 'Copy total count should be the total items added to the original');
|
||||||
|
|
||||||
// Modify original
|
// Modify original
|
||||||
lastTime := FArray[0].Time;
|
lastTime := FSeries[0].Time;
|
||||||
newPoint := TDataPoint<TAskBidItem>.Create(lastTime + 1, TAskBidItem.Create(99, 99.1));
|
newPoint := TDataPoint<TAskBidItem>.Create(lastTime + 1, TAskBidItem.Create(99, 99.1));
|
||||||
FArray.Add(newPoint);
|
FSeries.Add(newPoint);
|
||||||
|
|
||||||
// Verify the copy is still unchanged
|
// Verify the copy is still unchanged
|
||||||
Assert.AreEqual(Int64(15), copy.Count, 'Copy count must remain unchanged after original is modified');
|
Assert.AreEqual(Int64(15), copy.Count, 'Copy count must remain unchanged after original is modified');
|
||||||
@@ -606,7 +602,7 @@ begin
|
|||||||
// Use default setup, but add one item
|
// Use default setup, but add one item
|
||||||
testTime := EncodeDate(2025, 1, 1);
|
testTime := EncodeDate(2025, 1, 1);
|
||||||
DataPoint := TDataPoint<TAskBidItem>.Create(testTime, TAskBidItem.Create(1.0, 2.0));
|
DataPoint := TDataPoint<TAskBidItem>.Create(testTime, TAskBidItem.Create(1.0, 2.0));
|
||||||
FArray.Add(DataPoint);
|
FSeries.Add(DataPoint);
|
||||||
|
|
||||||
Assert.AreEqual(Int64(1), FSeries.Count);
|
Assert.AreEqual(Int64(1), FSeries.Count);
|
||||||
Assert.AreEqual(Int64(0), FSeries.IndexOf(testTime), 'IndexOf for exact single item should be 0');
|
Assert.AreEqual(Int64(0), FSeries.IndexOf(testTime), 'IndexOf for exact single item should be 0');
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ type
|
|||||||
[IgnoreMemoryLeaks(true)]
|
[IgnoreMemoryLeaks(true)]
|
||||||
TTestMycGateFuncFuture = class(TObject)
|
TTestMycGateFuncFuture = class(TObject)
|
||||||
private
|
private
|
||||||
FTaskFactory: IMycThreadPool;
|
FTaskFactory: IThreadPool;
|
||||||
FProcExecutionCount: Integer; // Counter for side effects of AProc
|
FProcExecutionCount: Integer; // Counter for side effects of AProc
|
||||||
FSharedCounter: Integer; // For Fan-Out test side effects
|
FSharedCounter: Integer; // For Fan-Out test side effects
|
||||||
public
|
public
|
||||||
@@ -156,7 +156,7 @@ end;
|
|||||||
procedure TTestMycGateFuncFuture.Test_ExceptionInProc_HandledAsPlanned;
|
procedure TTestMycGateFuncFuture.Test_ExceptionInProc_HandledAsPlanned;
|
||||||
var
|
var
|
||||||
LFuture: TFuture<Integer>.IFuture;
|
LFuture: TFuture<Integer>.IFuture;
|
||||||
LLocalTaskFactory: IMycThreadPool;
|
LLocalTaskFactory: IThreadPool;
|
||||||
LInitStateAsState: TState.IState;
|
LInitStateAsState: TState.IState;
|
||||||
LResultValue: Integer;
|
LResultValue: Integer;
|
||||||
LExpectedExceptionRaisedByFactory: Boolean;
|
LExpectedExceptionRaisedByFactory: Boolean;
|
||||||
@@ -288,7 +288,7 @@ begin
|
|||||||
end
|
end
|
||||||
);
|
);
|
||||||
LSub1 := TLatchNotifierSubscriber.Create(LGateLatch);
|
LSub1 := TLatchNotifierSubscriber.Create(LGateLatch);
|
||||||
Subscriptions[1] := LPrerequisiteFuture1.Done.Subscribe(LSub1); // Subscribe to PF1's completion [cite: 56, 188, 306]
|
Subscriptions[1] := LPrerequisiteFuture1.Done.Signal.Subscribe(LSub1); // Subscribe to PF1's completion [cite: 56, 188, 306]
|
||||||
|
|
||||||
// PrerequisiteFuture2
|
// PrerequisiteFuture2
|
||||||
LInitStateP2 := TLatch.CreateLatch(1); // Controllable init state for PF2
|
LInitStateP2 := TLatch.CreateLatch(1); // Controllable init state for PF2
|
||||||
@@ -303,7 +303,7 @@ begin
|
|||||||
end
|
end
|
||||||
);
|
);
|
||||||
LSub2 := TLatchNotifierSubscriber.Create(LGateLatch);
|
LSub2 := TLatchNotifierSubscriber.Create(LGateLatch);
|
||||||
Subscriptions[2] := LPrerequisiteFuture2.Done.Subscribe(LSub2); // Subscribe to PF2's completion
|
Subscriptions[2] := LPrerequisiteFuture2.Done.Signal.Subscribe(LSub2); // Subscribe to PF2's completion
|
||||||
|
|
||||||
// --- Execution & Verification ---
|
// --- Execution & Verification ---
|
||||||
Assert.IsFalse(LMainFuture.Done.IsSet, 'MainFuture should not be done yet.');
|
Assert.IsFalse(LMainFuture.Done.IsSet, 'MainFuture should not be done yet.');
|
||||||
|
|||||||
+1
-72
@@ -17,7 +17,7 @@ type
|
|||||||
[IgnoreMemoryLeaks(true)]
|
[IgnoreMemoryLeaks(true)]
|
||||||
TMycTaskFactoryTests = class(TObject)
|
TMycTaskFactoryTests = class(TObject)
|
||||||
private
|
private
|
||||||
FFactory: IMycThreadPool;
|
FFactory: IThreadPool;
|
||||||
public
|
public
|
||||||
[Setup]
|
[Setup]
|
||||||
procedure Setup;
|
procedure Setup;
|
||||||
@@ -27,12 +27,6 @@ type
|
|||||||
[Test]
|
[Test]
|
||||||
procedure TestFactoryCreationAndTeardown;
|
procedure TestFactoryCreationAndTeardown;
|
||||||
[Test]
|
[Test]
|
||||||
procedure TestCreateThreadExecutesAndSignals;
|
|
||||||
[Test]
|
|
||||||
procedure TestRunImmediateJob;
|
|
||||||
[Test]
|
|
||||||
procedure TestRunDelayedJobExecutesAfterAllNotifies;
|
|
||||||
[Test]
|
|
||||||
procedure TestWaitForAlreadySetState;
|
procedure TestWaitForAlreadySetState;
|
||||||
[Test]
|
[Test]
|
||||||
procedure TestThreadInfoMethods;
|
procedure TestThreadInfoMethods;
|
||||||
@@ -67,71 +61,6 @@ begin
|
|||||||
Assert.IsNotNull(FFactory, 'Factory should be created');
|
Assert.IsNotNull(FFactory, 'Factory should be created');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMycTaskFactoryTests.TestCreateThreadExecutesAndSignals;
|
|
||||||
var
|
|
||||||
executed: Boolean;
|
|
||||||
threadState: TState.IState;
|
|
||||||
procWrapper: TProc;
|
|
||||||
begin
|
|
||||||
executed := False;
|
|
||||||
procWrapper := procedure begin executed := True; end;
|
|
||||||
|
|
||||||
threadState := FFactory.CreateThread(procWrapper); // CreateThread in TaskFactory now uses TMycLatch.CreateLatch
|
|
||||||
Assert.IsNotNull(threadState, 'CreateThread should return a valid state object');
|
|
||||||
|
|
||||||
FFactory.WaitFor(threadState);
|
|
||||||
Assert.IsTrue(executed, 'Procedure in created thread should have executed');
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TMycTaskFactoryTests.TestRunImmediateJob;
|
|
||||||
var
|
|
||||||
jobExecuted: Boolean;
|
|
||||||
jobCompletedLatch: TLatch.ILatch;
|
|
||||||
begin
|
|
||||||
jobExecuted := False;
|
|
||||||
jobCompletedLatch := TLatch.CreateLatch(1); // Changed from Signals.CreateLatch
|
|
||||||
|
|
||||||
FFactory
|
|
||||||
.Run(
|
|
||||||
procedure
|
|
||||||
begin
|
|
||||||
jobExecuted := True;
|
|
||||||
jobCompletedLatch.Notify;
|
|
||||||
end)
|
|
||||||
.Notify;
|
|
||||||
|
|
||||||
FFactory.WaitFor(jobCompletedLatch.State);
|
|
||||||
Assert.IsTrue(jobExecuted, 'Immediate job should have executed');
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TMycTaskFactoryTests.TestRunDelayedJobExecutesAfterAllNotifies;
|
|
||||||
var
|
|
||||||
jobExecuted: Boolean;
|
|
||||||
jobCompletedLatch: TLatch.ILatch;
|
|
||||||
subscriber: TSignal.ISubscriber;
|
|
||||||
begin
|
|
||||||
jobExecuted := False;
|
|
||||||
jobCompletedLatch := TLatch.CreateLatch(1); // Changed from Signals.CreateLatch
|
|
||||||
|
|
||||||
subscriber :=
|
|
||||||
FFactory.Run(
|
|
||||||
procedure
|
|
||||||
begin
|
|
||||||
jobExecuted := True;
|
|
||||||
jobCompletedLatch.Notify;
|
|
||||||
end
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert.IsNotNull(subscriber, 'Run should return a subscriber for delayed job');
|
|
||||||
// Use TMycLatch.Null for comparison
|
|
||||||
Assert.AreNotEqual<TSignal.ISubscriber>(TLatch.Null, subscriber, 'Subscriber should not be TMycLatch.Null for StartCount > 0');
|
|
||||||
|
|
||||||
subscriber.Notify;
|
|
||||||
|
|
||||||
FFactory.WaitFor(jobCompletedLatch.State);
|
|
||||||
Assert.IsTrue(jobExecuted, 'Delayed job should execute after all notifications');
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TMycTaskFactoryTests.TestWaitForAlreadySetState;
|
procedure TMycTaskFactoryTests.TestWaitForAlreadySetState;
|
||||||
var
|
var
|
||||||
alreadySetLatch: TLatch.ILatch;
|
alreadySetLatch: TLatch.ILatch;
|
||||||
|
|||||||
Reference in New Issue
Block a user