From 08a110b02ff6e7a6fe2cd547b8f572b0f19f9099 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Sat, 24 May 2025 14:59:47 +0200 Subject: [PATCH] Heap Tests --- .gitignore | 16 +- Src/Myc.Atomic.pas | 108 ++ Src/Myc.Heap.pas | 144 ++ Src/Myc.Signals.pas | 1009 +++++++++++ Src/Myc.pas | 3848 ++++++++++++++++++++++++++++++++++++++++++ Test/MycTests.dpr | 72 + Test/MycTests.dproj | 1121 ++++++++++++ Test/MycTests.res | Bin 0 -> 152412 bytes Test/TestHeap.pas | 436 +++++ Test/TestSignals.pas | 49 + 10 files changed, 6795 insertions(+), 8 deletions(-) create mode 100644 Src/Myc.Atomic.pas create mode 100644 Src/Myc.Heap.pas create mode 100644 Src/Myc.Signals.pas create mode 100644 Src/Myc.pas create mode 100644 Test/MycTests.dpr create mode 100644 Test/MycTests.dproj create mode 100644 Test/MycTests.res create mode 100644 Test/TestHeap.pas create mode 100644 Test/TestSignals.pas diff --git a/.gitignore b/.gitignore index e9d123f..a63df36 100644 --- a/.gitignore +++ b/.gitignore @@ -30,14 +30,14 @@ # Default Delphi compiler directories # Content of this directories are generated with each Compile/Construct of a project. # Most of the time, files here have not there place in a code repository. -#Win32/ -#Win64/ -#OSX64/ -#OSXARM64/ -#Android/ -#Android64/ -#iOSDevice64/ -#Linux64/ +Win32/ +Win64/ +OSX64/ +OSXARM64/ +Android/ +Android64/ +iOSDevice64/ +Linux64/ # Delphi compiler-generated binaries (safe to delete) *.exe diff --git a/Src/Myc.Atomic.pas b/Src/Myc.Atomic.pas new file mode 100644 index 0000000..26a6d6a --- /dev/null +++ b/Src/Myc.Atomic.pas @@ -0,0 +1,108 @@ +unit Myc.Atomic; + +interface + +uses + Myc.Heap; + +type + IAtomicStack = interface + function Pop: T; + function TryPop( out Item: T ): Boolean; + procedure Push( const Data: T ); + end; + + TMycAtomicStack = class( TInterfacedObject, IAtomicStack ) + private + type + PItem = ^TItem; + TItem = packed record + Next: Pointer; + Data: T; + end; + + var + FSList: PSList; + + public + constructor Create; + destructor Destroy; override; + function Pop: T; inline; + function TryPop(out Item: T): Boolean; inline; + procedure Push( const Data: T ); inline; + function Alloc: PItem; inline; + procedure PushPtr(Item: PItem); inline; + function PopPtr: PItem; + end; + +implementation + +uses Winapi.Windows; + +constructor TMycAtomicStack.Create; +begin + inherited Create; + FSList := TSList.Create; +end; + +destructor TMycAtomicStack.Destroy; +var + Item: PItem; +begin + Item := PopPtr; + while Item <> nil do + begin + Item.Data := Default ( T ); + FreeMem( Item ); + Item := PopPtr; + end; + + FSList.Free; + inherited; +end; + +function TMycAtomicStack.Alloc: PItem; +begin + Result := AllocMem( sizeof( TItem ) ); +end; + +function TMycAtomicStack.Pop: T; +begin + if not TryPop( Result ) then + Result := Default ( T ); +end; + +function TMycAtomicStack.PopPtr: PItem; +begin + Result := PItem( FSList.Pop ); +end; + +procedure TMycAtomicStack.Push(const Data: T); +var + Item: PItem; +begin + Item := Alloc; + Item.Data := Data; + PushPtr( Item ); +end; + +procedure TMycAtomicStack.PushPtr(Item: PItem); +begin + FSList.Push( PSListEntry(Item) ); +end; + +function TMycAtomicStack.TryPop(out Item: T): Boolean; +var + P: PItem; +begin + P := PopPtr; + Result := P <> nil; + if Result then + begin + Item := P.Data; + P.Data := Default ( T ); + FreeMem( P ); + end; +end; + +end. diff --git a/Src/Myc.Heap.pas b/Src/Myc.Heap.pas new file mode 100644 index 0000000..9d2e547 --- /dev/null +++ b/Src/Myc.Heap.pas @@ -0,0 +1,144 @@ +unit Myc.Heap; + +interface + +{$align on} +uses + {$ifndef NO_FASTMM} + FastMM5, + {$endif} + Winapi.Windows; + +type + TSListEntry = Winapi.Windows.TSListEntry; + PSListEntry = Winapi.Windows.PSListEntry; + + PSList = ^TSList; + TSList = packed record + private + FList: TSListHeader; + + public + class function Create: PSList; static; + procedure Init; + procedure Free; + function Push( ListEntry: PSListEntry ): PSListEntry; + function Pop: PSListEntry; + function QueryDepth: Integer; + end; + +procedure GetMemAligned( var P; Size: NativeUInt ); +procedure FreeMemAligned( var P ); + +procedure InitializeSListHead( ListHead: PSListHeader ); stdcall; external kernel32; +function InterlockedPushEntrySList( ListHead: PSListHeader; ListEntry: PSListEntry ): PSListEntry; stdcall; external kernel32; +function InterlockedPopEntrySList( ListHead: PSListHeader ): PSListEntry; stdcall; external kernel32; +function QueryDepthSList( ListHead: PSListHeader ): Word; stdcall; external kernel32; + +implementation + +const + // Defines the alignment boundary (16 bytes for SList and common SIMD operations) + AlignmentBoundary = 16; + // Mask to achieve alignment, (AlignmentBoundary - 1) + AlignmentMask = AlignmentBoundary - 1; + +{$ifdef NO_FASTMM} + +const + MetaDataSize = SizeOf(Pointer); + +procedure GetMemAligned(var P; Size: NativeUInt); +var + rawAllocatedPtr: Pointer; + alignedUserPtrVal: NativeUInt; + rawAllocatedPtrVal: NativeUInt; + totalSizeToAllocate: NativeUInt; +begin + totalSizeToAllocate := Size + MetaDataSize + AlignmentMask; + System.GetMem(rawAllocatedPtr, totalSizeToAllocate); + if rawAllocatedPtr = nil then + begin + Pointer(P) := nil; + Exit; + end; + rawAllocatedPtrVal := NativeUInt(rawAllocatedPtr); + alignedUserPtrVal := (rawAllocatedPtrVal + MetaDataSize + AlignmentMask) and not AlignmentMask; + Pointer(P) := Pointer(alignedUserPtrVal); + PPointer(alignedUserPtrVal - MetaDataSize)^ := rawAllocatedPtr; + Assert((NativeUInt(P) and AlignmentMask) = 0, 'GetMemAligned did not return an aligned pointer.'); +end; + +procedure FreeMemAligned( var P ); +var + alignedUserPtrVal: NativeUInt; + rawAllocatedPtr: Pointer; + ptrToRawAllocatedPtr: PPointer; +begin + if Pointer(P) = nil then + Exit; + alignedUserPtrVal := NativeUInt(P); + ptrToRawAllocatedPtr := PPointer(alignedUserPtrVal - MetaDataSize); + rawAllocatedPtr := ptrToRawAllocatedPtr^; + System.FreeMem(rawAllocatedPtr); + Pointer(P) := nil; +end; + +{$else} + +procedure GetMemAligned(var P; Size: NativeUInt); inline; +begin + FastMM_EnterMinimumAddressAlignment(maa16Bytes); + try + GetMem( Pointer(P), Size ); + finally + FastMM_ExitMinimumAddressAlignment(maa16Bytes) + end; +end; + +procedure FreeMemAligned( var P ); inline; +begin + FreeMem( Pointer(P) ); +end; + +{$endif} + +{ TSList } + +class function TSList.Create: PSList; +begin + GetMemAligned( Result, sizeof( TSList ) ); + Result.Init; +end; + +procedure TSList.Free; +var + P: Pointer; +begin + P := @Self; + FreeMemAligned( P ); +end; + +procedure TSList.Init; +begin + Assert( NativeUInt( @FList ) and AlignmentMask = 0, 'ListHeader not aligned' ); + InitializeSListHead( @FList ); +end; + +function TSList.Pop: PSListEntry; +begin + Result := InterlockedPopEntrySList( @FList ); +end; + +function TSList.Push( ListEntry: PSListEntry ): PSListEntry; +begin + Assert( NativeUInt( ListEntry ) and AlignmentMask = 0, 'ListEntry not aligned' ); + Result := InterlockedPushEntrySList( @FList, ListEntry ); +end; + +function TSList.QueryDepth: Integer; +begin + Result := QueryDepthSList( @FList ); +end; + +end. diff --git a/Src/Myc.Signals.pas b/Src/Myc.Signals.pas new file mode 100644 index 0000000..f8c7a89 --- /dev/null +++ b/Src/Myc.Signals.pas @@ -0,0 +1,1009 @@ +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +/// +/// Mycelium Framework +/// ------------------ +/// +/// (c)2007-2019 Michael Schimmel +/// Ehrenhainstraße 40 +/// 42329 Wuppertal / Germany +/// info@mycelium.net +/// +/// All rights reserved. +/// +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +unit Myc.Signals; + +interface + +uses + System.SysUtils, System.SyncObjs, + Myc.Atomic; + +{.$define DEBUG_SIGNALS} + +type + IMyc2Sink = interface + function Notify: Boolean; + end; + + IMyc2Flag = interface( IMyc2Sink ) + function Reset: Boolean; + end; + + {$message hint 'Replace TSignalTag by IMycDeletable? '} + TSignalTag = NativeInt; + + IMyc2Signal = interface + function Advise( const Sink: IMyc2Sink ): TSignalTag; + procedure Unadvise( Tag: TSignalTag ); + end; + + IMyc2Notifier = interface( IMyc2Sink ) + {$region 'property access'} + function GetSignal: IMyc2Signal; + {$endregion} + property Signal: IMyc2Signal read GetSignal; + end; + + IMyc2State = interface( IMyc2Signal ) + {$region 'property access'} + function GetIsSet: Boolean; + {$endregion} + property IsSet: Boolean read GetIsSet; + end; + + IMyc2Condition = interface( IMyc2Sink ) + {$region 'property access'} + function GetState: IMyc2State; + {$endregion} + property State: IMyc2State read GetState; + end; + + IMyc2Event = interface( IMyc2Condition ) + function Reset: Boolean; + end; + + TMycSinkList = record + type + PItem = ^TItem; + + TItem = record + Next, Prev: PItem; + Sink: IMyc2Sink; + end; + + strict private + FFirst: NativeUInt; + FList: PItem; + + {$ifdef DEBUG_SIGNALS} + FCount: Integer; + {$endif} + class var + [volatile] FItems: TMycAtomicStack; + [volatile] + FPendingCnt: Int64; + [volatile] + FUsedCnt: Int64; + + class constructor CreateClass; + class destructor DestroyClass; + class function AllocItem: PItem; static; inline; + class procedure FreeItem( Item: PItem ); static; inline; + + public + procedure Create; + procedure Destroy; + function Advise( const Sink: IMyc2Sink ): TSignalTag; + procedure Unadvise( Tag: TSignalTag ); + procedure UnadviseAll; + procedure Finalize; + procedure Lock; inline; + procedure Release; inline; + function IsLocked: Boolean; inline; + function IsFinalized: Boolean; inline; + procedure Notify; + end; + + TMyc2Signal = class( TInterfacedObject, IMyc2Signal, IMyc2State, IMyc2Sink ) + private + FList: TMycSinkList; + + class var + FNull: IMyc2Condition; + + protected + class constructor CreateClass; + class destructor DestroyClass; + function GetIsSet: Boolean; virtual; + function DoNotify: Boolean; virtual; + public + constructor Create; + destructor Destroy; override; + + function Advise( const Sink: IMyc2Sink ): TSignalTag; + procedure Unadvise( Tag: TSignalTag ); + function Notify: Boolean; + + class function CreateAdvised( Count: Integer; const Getter: TFunc ): IMyc2Signal; overload; + class function IsStatic( const Signal: IMyc2Signal ): Boolean; overload; inline; + class function IsStatic( const Sink: IMyc2Sink ): Boolean; overload; inline; + + class function MakeValid( const Signal: IMyc2Signal ): IMyc2Signal; overload; static; inline; + class function MakeValid( const Signals: array of IMyc2Signal ): TArray; overload; static; + + class function CreateCounter( Count: Integer ): IMyc2Condition; static; inline; + class function CreateEvent( Init: Boolean ): IMyc2Event; static; inline; + class function CreateGroup( Count: Integer; const Getter: TFunc ): IMyc2Signal; overload; + class function CreateNotifier: IMyc2Notifier; static; inline; + + class function CreateUnion( const Sigs: array of IMyc2Signal ): IMyc2Signal; static; + class function CreateConcat( const States: array of IMyc2State ): IMyc2State; static; + + property IsSet: Boolean read GetIsSet; + class property Null: IMyc2Condition read FNull; + end; + + TMyc2Notifier = class( TMyc2Signal, IMyc2Notifier ) + protected + function GetSignal: IMyc2Signal; + end; + + TMyc2Condition = class( TMyc2Signal, IMyc2Condition ) + protected + function GetState: IMyc2State; + end; + + TMyc2Event = class( TMyc2Condition, IMyc2Event ) + private + [volatile] + FValue: Integer; + + protected + function DoNotify: Boolean; override; + function GetIsSet: Boolean; override; + + public + constructor Create( AInit: Boolean ); + function Reset: Boolean; virtual; + property IsSet: Boolean read GetIsSet; + end; + + TMyc2CountSignal = class sealed( TMyc2Condition ) + private + [volatile] + FCount: Integer; + + protected + function GetIsSet: Boolean; override; + function DoNotify: Boolean; override; + + public + constructor Create( ACount: Integer ); + end; + + TMyc2SinkProc = class sealed( TInterfacedObject, IMyc2Sink ) + private + FOnce: Boolean; + FProc: TProc; + + function Notify: Boolean; + + public + constructor Create( AOnce: Boolean; const AProc: TProc ); + class function CreateSink( Once: Boolean; const Proc: TProc ): IMyc2Sink; static; + end; + + TMyc2NullState = class sealed( TInterfacedObject, IMyc2Signal, IMyc2State ) + protected + function Advise( const Sink: IMyc2Sink ): TSignalTag; + function GetIsSet: Boolean; + procedure Unadvise( Tag: TSignalTag ); + procedure UnadviseAll; + end; + + TMyc2NullSink = class sealed( TInterfacedObject, IMyc2Sink, IMyc2Condition ) + private + FState: IMyc2State; + function GetState: IMyc2State; + + protected + function Notify: Boolean; + + public + constructor Create; + destructor Destroy; override; + end; + + TMyc2Flag = class( TInterfacedObject, IMyc2Sink, IMyc2Flag ) + private + FFlag: Integer; + function Notify: Boolean; + function Reset: Boolean; + + public + constructor Create( AFlag: Boolean ); + end; + + TMycObserver = class + type + TNull = class( TInterfacedObject, IMycObserver ) + private + FSink: T; + function GetItem: T; + + public + constructor Create( const ASink: T ); + end; + + TOne = class( TNull ) + private + FSignal: IMyc2Signal; + FTag: TSignalTag; + + public + constructor Create( const ASignal: IMyc2Signal; const ASink: T ); + destructor Destroy; override; + end; + + TArr = class( TNull ) + private + FSignals: TArray; + FTags: TArray; + + public + constructor Create( const ASignals: TArray; const ASink: T ); + destructor Destroy; override; + end; + + public + class function CreateObserver( const Signals: array of IMyc2Signal; const Item: T ): IMycObserver; static; + end; + + TMycSignalUnion = class( TInterfacedObject, IMyc2Signal ) + private + FNotifier: IMyc2Notifier; + FSignals: TArray; + FTags: TArray; + + function Advise( const Sink: IMyc2Sink ): TSignalTag; + procedure Unadvise( Tag: TSignalTag ); + + public + constructor Create( const ASignals: array of IMyc2Signal ); + destructor Destroy; override; + end; + +implementation + +{ TMyc2Signal } + +constructor TMyc2Signal.Create; +begin + inherited Create; + FList.Create; +end; + +destructor TMyc2Signal.Destroy; +begin + FList.Destroy; + inherited Destroy; +end; + +class function TMyc2Signal.CreateAdvised( Count: Integer; const Getter: TFunc ): IMyc2Signal; +var + i, j: Integer; + Sig: IMyc2Signal; + Res: IMyc2Condition; +begin + for i := 0 to Count - 1 do + begin + Sig := Getter( i ); + if not IsStatic( Sig ) then + begin + Res := TMyc2Condition.Create; + + Sig.Advise( Res ); + + for j := i + 1 to Count - 1 do + begin + Sig := Getter( j ); + if not IsStatic( Sig ) then + Sig.Advise( Res ); + end; + + exit( Res.State ); + end; + end; + + exit( Null.State ); +end; + +class constructor TMyc2Signal.CreateClass; +begin + FNull := TMyc2NullSink.Create; +end; + +class destructor TMyc2Signal.DestroyClass; +begin + FNull := nil; +end; + +class function TMyc2Signal.IsStatic( const Signal: IMyc2Signal ): Boolean; +begin + Result := ( Signal = nil ) or ( Signal = FNull.State ); +end; + +class function TMyc2Signal.MakeValid( const Signal: IMyc2Signal ): IMyc2Signal; +begin + Result := Signal; + if Result = nil then + Result := Null.State; +end; + +function TMyc2Signal.Advise( const Sink: IMyc2Sink ): TSignalTag; +begin + if IsStatic( Sink ) then + exit( 0 ); + + FList.Lock; + try + Result := FList.Advise( Sink ); + + if IsSet then + Sink.Notify; + finally + FList.Release; + end; +end; + +class function TMyc2Signal.CreateConcat( const States: array of IMyc2State ): IMyc2State; + + function CreatePair( const A, B: IMyc2State ): IMyc2State; + var + Counter: IMyc2Condition; + begin + if A=B then + begin + Result := A; + end + else if IsStatic( A ) then + begin + if IsStatic( B ) then + Result := Signals.Null.State + else + Result := B; + end + else if IsStatic( B ) then + begin + Result := A; + end + else + begin + Counter := CreateCounter( 2 ); + A.Advise( Counter ); + B.Advise( Counter ); + Result := Counter.State; + end; + end; + + function CreateSet( const States: array of IMyc2State ): IMyc2State; + var + Counter: IMyc2Condition; + i, n: Integer; + begin + n := 0; + for i := 0 to High( States ) do + if not IsStatic( States[i] ) then + inc( n ); + + Counter := CreateCounter( n ); + for i := 0 to High( States ) do + if not IsStatic( States[i] ) then + States[i].Advise( Counter ); + + Result := Counter.State; + end; + +begin + {TODO -oMS: Potentielles Speicherloch. Im Gegensatz zur Union werden hier die States nicht explizit un-advised. Andererseits + kann man eigentlich davon ausgehen, das States im Gegensatz zu einfachen Notifications irgendwann gesetzt und dann aufgeräumt + werden. Im Auge behalten! } + + case Length( States ) of + 0: + Result := Signals.Null.State; + 1: + Result := States[0]; + 2: + Result := CreatePair( States[0], States[1] ); + else + Result := CreateSet( States ); + end; +end; + +class function TMyc2Signal.CreateCounter( Count: Integer ): IMyc2Condition; +begin + Assert( Count >=0 ); + + if Count > 0 then + Result := TMyc2CountSignal.Create( Count ) + else + Result := Null; +end; + +class function TMyc2Signal.CreateEvent( Init: Boolean ): IMyc2Event; +begin + Result := TMyc2Event.Create( Init ) +end; + +class function TMyc2Signal.CreateGroup( Count: Integer; const Getter: TFunc ): IMyc2Signal; +var + i, n: Integer; + Sigs: TArray; + Res: IMyc2Condition; +begin + if Count = 0 then + exit( Null.State ); + + SetLength( Sigs, Count ); + n := 0; + for i := 0 to Count - 1 do + begin + Sigs[n] := Getter( i ); + if not IsStatic( Sigs[n] ) then + inc( n ); + end; + + if n = 0 then + exit( Null.State ); + + Res := CreateCounter( n ); + for i := 0 to n - 1 do + Sigs[i].Advise( Res ); + + exit( Res.State ); +end; + +class function TMyc2Signal.CreateNotifier: IMyc2Notifier; +begin + Result := TMyc2Notifier.Create; +end; + +class function TMyc2Signal.MakeValid( const Signals: array of IMyc2Signal ): TArray; +var + i, n: Integer; +begin + n := 0; + for i := 0 to High( Signals ) do + if not IsStatic( Signals[i] ) then + inc( n ); + + SetLength( Result, n ); + n := 0; + for i := 0 to High( Signals ) do + if not IsStatic( Signals[i] ) then + begin + Result[n] := Signals[i]; + inc( n ); + end; +end; + +class function TMyc2Signal.CreateUnion( const Sigs: array of IMyc2Signal ): IMyc2Signal; +begin + case Length( Sigs ) of + 0: + Result := Signals.Null.State; + 1: + Result := Sigs[0]; + else + Result := TMycSignalUnion.Create( Sigs ); + end; +end; + +function TMyc2Signal.DoNotify: Boolean; +begin + FList.Notify; + exit( true ); +end; + +function TMyc2Signal.GetIsSet: Boolean; +begin + Result := true; +end; + +class function TMyc2Signal.IsStatic( const Sink: IMyc2Sink ): Boolean; +begin + Result := ( Sink = nil ) or ( Sink = FNull ); +end; + +function TMyc2Signal.Notify: Boolean; +begin + FList.Lock; + try + Result := DoNotify; + if not Result then + FList.Finalize; + finally + FList.Release; + end; +end; + +procedure TMyc2Signal.Unadvise( Tag: TSignalTag ); +begin + if Tag=0 then + exit; + + FList.Lock; + try + FList.Unadvise( Tag ); + finally + FList.Release; + end; +end; + +{ TMyc2Event } + +constructor TMyc2Event.Create( AInit: Boolean ); +begin + inherited Create; + if AInit then + inc( FValue ); +end; + +function TMyc2Event.GetIsSet: Boolean; +begin + Result := FValue <> 0; +end; + +function TMyc2Event.DoNotify: Boolean; +begin + if TInterlocked.Exchange( FValue, 1 ) = 0 then + inherited; + exit( true ); +end; + +function TMyc2Event.Reset: Boolean; +begin + Result := TInterlocked.Exchange( FValue, 0 ) = 1; +end; + +{ TMyc2CountownSignal } + +constructor TMyc2CountSignal.Create( ACount: Integer ); +begin + inherited Create; + FCount := ACount; +end; + +function TMyc2CountSignal.GetIsSet: Boolean; +begin + Result := FCount <= 0; +end; + +function TMyc2CountSignal.DoNotify: Boolean; +var + n: Integer; +begin + n := TInterlocked.Decrement( FCount ); + if n < -MaxInt div 2 then + TInterlocked.Increment( FCount ); + + if n = 0 then + inherited; + + Result := n > 0; +end; + +{ TMyc2SinkProc } + +constructor TMyc2SinkProc.Create( AOnce: Boolean; const AProc: TProc ); +begin + inherited Create; + FProc := AProc; + FOnce := AOnce; +end; + +class function TMyc2SinkProc.CreateSink( Once: Boolean; const Proc: TProc ): IMyc2Sink; +begin + if Assigned( Proc ) then + Result := TMyc2SinkProc.Create( Once, Proc ) + else + Result := TMyc2Signal.Null; +end; + +function TMyc2SinkProc.Notify: Boolean; +begin + if Assigned( FProc ) then + begin + FProc( ); + if FOnce then + FProc := nil; + end; + + Result := Assigned( FProc ); +end; + +{ TMyc2NullState } + +function TMyc2NullState.Advise( const Sink: IMyc2Sink ): TSignalTag; +begin + Sink._AddRef; + try + Sink.Notify; + finally + Sink._Release; + end; + exit( 0 ); +end; + +function TMyc2NullState.GetIsSet: Boolean; +begin + exit( true ); +end; + +procedure TMyc2NullState.Unadvise( Tag: TSignalTag ); +begin +end; + +procedure TMyc2NullState.UnadviseAll; +begin + +end; + +constructor TMyc2NullSink.Create; +begin + inherited Create; + FState := TMyc2NullState.Create; +end; + +destructor TMyc2NullSink.Destroy; +begin + inherited Destroy; +end; + +function TMyc2NullSink.GetState: IMyc2State; +begin + Result := FState; +end; + +function TMyc2NullSink.Notify: Boolean; +begin + Result := false; +end; + +{ TMyc2Notifier } + +function TMyc2Notifier.GetSignal: IMyc2Signal; +begin + Result := Self; +end; + +{ TMyc2Condition } + +function TMyc2Condition.GetState: IMyc2State; +begin + Result := Self; +end; + +{ TMycSinkList } + +class constructor TMycSinkList.CreateClass; +begin + FItems := TMycAtomicStack.Create; + FPendingCnt := 0; + FUsedCnt := 0; +end; + +class destructor TMycSinkList.DestroyClass; +begin + Assert( FUsedCnt = 0 ); + Assert( FPendingCnt = 0 ); + + FItems.Free; +end; + +procedure TMycSinkList.Create; +begin + FFirst := 1; + FList := nil; + {$ifdef DEBUG_SIGNALS} + FCount := 0; + {$endif} +end; + +procedure TMycSinkList.Destroy; +begin + // Because refcounting is thread-safe, this will always be entered once after all references + // to Self are dropped. No locking needed! + Assert( not IsLocked, 'Signal must not be locked!' ); + FFirst := FFirst and not 3; + UnadviseAll; +end; + +function TMycSinkList.Advise( const Sink: IMyc2Sink ): TSignalTag; +var + Item: PItem; +begin + Assert( IsLocked, 'Signal has to be locked' ); + + if IsFinalized then + exit( 0 ); + + {$ifdef DEBUG_SIGNALS} + inc( FCount ); + Assert( FCount < 40000 ); + {$endif} + if FFirst=0 then + begin + IMyc2Sink( FFirst ) := Sink; + exit( TSignalTag( Sink ) ); + end; + + Item := AllocItem; + Item.Sink := Sink; + Item.Prev := nil; + Item.Next := FList; + if Item.Next<>nil then + Item.Next.Prev := Item; + + FList := Item; + + exit( TSignalTag( Item ) ); +end; + +procedure TMycSinkList.Lock; +begin + repeat + if FFirst and 1 = 0 then + begin + YieldProcessor; + continue; + end; + until TInterlocked.BitTestAndClear( PInteger( @FFirst )^, 0 ); + + Assert( IsLocked, 'Locking failed' ); +end; + +procedure TMycSinkList.Notify; +var + Item, P: PItem; +begin + Assert( IsLocked, 'Signal has to be locked' ); + + if IsFinalized then + exit; + + if FFirst<>0 then + if not IMyc2Sink( FFirst ).Notify then + IMyc2Sink( FFirst ) := nil; + + Item := FList; + while Item<>nil do + begin + P := Item.Next; + if not Item.Sink.Notify then + Unadvise( TSignalTag( Item ) ); + Item := P; + end; +end; + +class function TMycSinkList.AllocItem: PItem; +begin + GetMem( Result, sizeof( TItem ) ); + Pointer( Result.Sink ) := nil; +end; + +procedure TMycSinkList.Finalize; +begin + Assert( IsLocked, 'Signal has to be locked' ); + UnadviseAll; + FFirst := FFirst or 2; +end; + +class procedure TMycSinkList.FreeItem( Item: PItem ); +begin + FreeMem( Item, sizeof( TItem ) ); +end; + +function TMycSinkList.IsLocked: Boolean; +begin + Result := FFirst and 1 = 0; +end; + +function TMycSinkList.IsFinalized: Boolean; +begin + Result := FFirst and 2 <> 0; +end; + +procedure TMycSinkList.Release; +begin + Assert( IsLocked, 'Signal not locked' ); + TInterlocked.Exchange( Pointer( FFirst ), Pointer( FFirst or 1 ) ); +end; + +procedure TMycSinkList.Unadvise( Tag: TSignalTag ); +var + Item: PItem; +begin + Assert( IsLocked, 'Signal not locked' ); + + if IsFinalized then + exit; + + {$ifdef DEBUG_SIGNALS} + dec( FCount ); + {$endif} + if Tag = FFirst then + begin + IMyc2Sink( FFirst ) := nil; + exit; + end; + + Item := PItem( Tag ); + + if Item = FList then + FList := Item.Next; + + if Item.Prev<>nil then + Item.Prev.Next := Item.Next; + if Item.Next<>nil then + Item.Next.Prev := Item.Prev; + + Item.Sink := nil; + + FreeItem( Item ); +end; + +procedure TMycSinkList.UnadviseAll; +begin + Assert( IsLocked, 'Signal has to be locked' ); + + if IsFinalized then + exit; + + IMyc2Sink( FFirst ) := nil; + while FList<>nil do + Unadvise( TSignalTag( FList ) ); +end; + +{ TMycObserver } + +class function TMycObserver.CreateObserver( const Signals: array of IMyc2Signal; const Item: T ): IMycObserver; +var + Arr: TArray; +begin + {$message hint 'Design-Fehler!! Der Null-Observer ist nicht statisch!!'} + if Length( Signals ) = 0 then + Result := TNull.Create( Item ) + else if ( Length( Signals )=1 ) and not TMyc2Signal.IsStatic( Signals[0] ) then + Result := TOne.Create( Signals[0], Item ) + else + begin + Arr := TMyc2Signal.MakeValid( Signals ); + if Arr = nil then + Result := TNull.Create( Item ) + else if Length( Arr )=1 then + Result := TOne.Create( Arr[0], Item ) + else + Result := TArr.Create( Arr, Item ); + end; +end; + +{ TMycObserver.TNull } + +constructor TMycObserver.TNull.Create( const ASink: T ); +begin + inherited Create; + FSink := ASink; +end; + +function TMycObserver.TNull.GetItem: T; +begin + Result := FSink; +end; + +{ TMycObserver.TOne } + +constructor TMycObserver.TOne.Create( const ASignal: IMyc2Signal; const ASink: T ); +begin + inherited Create( ASink ); + FSignal := ASignal; + FTag := FSignal.Advise( FSink ); +end; + +destructor TMycObserver.TOne.Destroy; +begin + FSignal.Unadvise( FTag ); + inherited; +end; + +{ TMycObserver.TArr } + +constructor TMycObserver.TArr.Create( const ASignals: TArray; const ASink: T ); +var + i: Integer; +begin + inherited Create( ASink ); + FSignals := ASignals; + + SetLength( FTags, Length( FSignals ) ); + for i := 0 to High( FTags ) do + begin + if not Signals.IsStatic( FSignals[i] ) then + FTags[i] := FSignals[i].Advise( FSink ) + else + FTags[i] := 0; + end; +end; + +destructor TMycObserver.TArr.Destroy; +var + i: Integer; +begin + for i := High( FSignals ) downto 0 do + if FTags[i]<>0 then + FSignals[i].Unadvise( FTags[i] ); + inherited; +end; + +{ TMycSignalUnion } + +constructor TMycSignalUnion.Create( const ASignals: array of IMyc2Signal ); +var + i: Integer; +begin + inherited Create; + + FNotifier := Signals.CreateNotifier; + SetLength( FTags, Length( ASignals ) ); + SetLength( FSignals, Length( ASignals ) ); + for i := 0 to High( FSignals ) do + begin + FSignals[i] := ASignals[i]; + FTags[i] := FSignals[i].Advise( FNotifier ); + end; +end; + +destructor TMycSignalUnion.Destroy; +var + i: Integer; +begin + for i := High( FTags ) downto 0 do + FSignals[i].Unadvise( FTags[i] ); + + inherited; +end; + +function TMycSignalUnion.Advise( const Sink: IMyc2Sink ): TSignalTag; +begin + Result := FNotifier.Signal.Advise( Sink ); +end; + +procedure TMycSignalUnion.Unadvise( Tag: TSignalTag ); +begin + FNotifier.Signal.Unadvise( Tag ); +end; + +{ TMyc2Flag } + +constructor TMyc2Flag.Create( AFlag: Boolean ); +begin + inherited Create; + FFlag := Integer( AFlag ); +end; + +function TMyc2Flag.Notify: Boolean; +begin + Result := TInterlocked.Exchange( FFlag, 1 ) = 0; +end; + +function TMyc2Flag.Reset: Boolean; +begin + Result := TInterlocked.Exchange( FFlag, 0 ) = 1; +end; + +end. diff --git a/Src/Myc.pas b/Src/Myc.pas new file mode 100644 index 0000000..12dd378 --- /dev/null +++ b/Src/Myc.pas @@ -0,0 +1,3848 @@ +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +/// +/// Mycelium Scenes Library +/// ----------------------- +/// +/// (c)2007-2020 Michael Schimmel +/// Ehrenhainstr 40 +/// 42329 Wuppertal / Germany +/// info@mycelium.net +/// +/// All rights reserved. +/// +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +unit Myc; + +interface + +uses + {$ifdef FullDebugMode}FastMM5, {$endif} + System.SysUtils, System.TypInfo, System.Generics.Collections, + System.Generics.Defaults; + +{$minenumsize 4} + + +type + {$message hint 'Debug-Code! Const Params?'} + TMyc3Combiner = reference to function( const Values: TArray ): T; + + TMyc3Interpolator = reference to function( Delta: Double; const StartValue: T; out Value: T ): Boolean; + TMyc3TargetInterpolator = reference to function( Delta: Double; const StartValue, TargetValue: T; out Value: T ): Boolean; + + EMycInvalidParameter = class( Exception ) + end; + + EMycInvalidOperation = class( Exception ) + end; + + TConstFunc = reference to function( const aa: A ): T; + TConstFunc = reference to function( const aa: A; const bb: B ): T; + TConstFunc = reference to function( const aa: A; const bb: B; const cc: C ): T; + TConstFunc = reference to function( const aa: A; const bb: B; const cc: C ; const dd: D ): T; + TConstFunc = reference to function( const aa: A; const bb: B; const cc: C ; const dd: D; const ee: E ): T; + TConstProc = reference to procedure( const aa: A ); + TConstProc = reference to procedure( const aa: A; const bb: B ); + TConstProc = reference to procedure( const aa: A; const bb: B; const cc: C ); + + TCompareValues = reference to function( const A, B: T ): Boolean; + + ///////////////////////////////////////////////////////////////////////////// + + IMycEnumerator = interface + function GetCurrent: T; + function MoveNext: Boolean; + procedure Reset; + property Current: T read GetCurrent; + end; + + IMyc2Enumerable = interface + function GetEnumerator: IMycEnumerator; + end; + + IMycCountable = interface + {$region 'property access'} + function GetCount: Integer; + function GetItems( Idx: Integer ): T; + {$endregion} + property Count: Integer read GetCount; + property Items[Idx: Integer]: T read GetItems; default; + end; + + ///////////////////////////////////////////////////////////////////////////// + + IAtomicStack = interface + function Pop: T; + function TryPop( out Item: T ): Boolean; + procedure Push( const Data: T ); + end; + + TAtomicStack = record + class function Create: IAtomicStack; static; + end; + + TProtected = record + private + [volatile] + FItem: T; + + public + constructor Create( const AItem: T ); + function Exchange( const Value: T ): T; + class operator Implicit( const Value: T ): TProtected; + end; + + ///////////////////////////////////////////////////////////////////////////// + + IMyc2Sink = interface + function Notify: Boolean; + end; + + IMyc2Flag = interface( IMyc2Sink ) + function Reset: Boolean; + end; + + {$message hint 'Replace TSignalTag by IMycDeletable? '} + TSignalTag = NativeInt; + + IMyc2Signal = interface + function Advise( const Sink: IMyc2Sink ): TSignalTag; + procedure Unadvise( Tag: TSignalTag ); + end; + + IMyc2Notifier = interface( IMyc2Sink ) + {$region 'property access'} + function GetSignal: IMyc2Signal; + {$endregion} + property Signal: IMyc2Signal read GetSignal; + end; + + IMyc2State = interface( IMyc2Signal ) + {$region 'property access'} + function GetIsSet: Boolean; + {$endregion} + property IsSet: Boolean read GetIsSet; + end; + + IMyc2Condition = interface( IMyc2Sink ) + {$region 'property access'} + function GetState: IMyc2State; + {$endregion} + property State: IMyc2State read GetState; + end; + + IMyc2Event = interface( IMyc2Condition ) + function Reset: Boolean; + end; + + IMycObserver = interface + function GetItem: T; + property Item: T read GetItem; + end; + + IMycNotifyObserver = IMycObserver; + IMycEventObserver = IMycObserver; + + TMycStateQueue = record + private + FStack: IAtomicStack; + + public + constructor Create(const AStack: IAtomicStack); overload; + + class function Create: TMycStateQueue; overload; static; + + procedure Add( const S: IMyc2State ); + procedure WaitFor; + + end; + + Signals = record + private + class function GetNull: IMyc2Condition; static; + + public + class function CreateCounter( Count: Integer ): IMyc2Condition; static; + class function CreateEvent( Init: Boolean = false ): IMyc2Event; overload; static; + class function CreateNotifier: IMyc2Notifier; overload; static; + class function CreateSink(Once: Boolean; const Proc: TProc): IMyc2Sink; overload; static; deprecated; + class function CreateSink(const Proc: TProc): IMyc2Sink; overload; static; + class function CreateFlag(Init: Boolean): IMyc2Flag; static; + + class function CreateObserver( const Signals: array of IMyc2Signal; const Sink: T ): IMycObserver; static; + class function CreateNotifyObserver( const Signals: array of IMyc2Signal ): IMycNotifyObserver; overload; static; + class function CreateEventObserver( const Signals: array of IMyc2Signal ): IMycEventObserver; overload; static; + + class function CreateNotifyObserver( const Signals: IMycCountable ): IMycNotifyObserver; overload; static; + class function CreateEventObserver( const Signals: IMycCountable ): IMycEventObserver; overload; static; + + class function Concat( const States: array of IMyc2State ): IMyc2State; static; + class function Union( const Sigs: array of IMyc2Signal ): IMyc2Signal; static; + + class function IsStatic( const Signal: IMyc2Signal ): Boolean; static; inline; + class function MakeValid( const Signal: IMyc2Signal ): IMyc2Signal; overload; static; inline; + class function MakeValid( const Signals: array of IMyc2Signal ): TArray; overload; static; + + class property Null: IMyc2Condition read GetNull; + end; + + ///////////////////////////////////////////////////////////////////////////// + + IMyc3Value = interface + {$region 'property access'} + function GetValue: T; + {$endregion} + property Value: T read GetValue; + end; + + TImmutable = record + private + FThis: IMyc3Value; + function GetValue: T; inline; + function GetHasValue: Boolean; inline; + class function GetNull: TImmutable; static; inline; + public + constructor Create(const AValue: T); overload; + class operator NotEqual(ALeft, ARight: TImmutable): Boolean; + class operator Equal(ALeft, ARight: TImmutable): Boolean; + property Value: T read GetValue; + property HasValue: Boolean read GetHasValue; + class property Null: TImmutable read GetNull; + end; + + // IDEA: + // Mutable objects represent exactly *one* property of a dynamic system. + // As such, they provide a change signal and an immutable value + // that is *replaced*, when they change. + // Q: How do you maintain thread safety with such an object? + // A: Reading a mutable object needs locking. There's no way around it. + // But picking up a reference to an immutable can be done with almost no + // overhead using atomic operations. + // Given the nature of mutable objects, they should only be used within + // the scope of a single thread (which does not have to be the main thread!) + // Q: Is it getting any better this way? + // A: mmmh... at least it offloads the application from doing this kinda stuff. + // And it ensures separating data from logic. + // Q: Shouldn't IMutable then be derived from IValue or from ISignal? + // A: To be considered. Given that IMutable is *not* the value itself, because + // IValue is immutable by definition, it seems to make more sense to derive + // it from ISignal. OTOH, it isn't a signal either. Maybe it should really + // stand for it's own. + + // Mutable objects may change over time. + IMyc3Mutable = interface + {$region 'property access'} + function GetChanged: IMyc2Signal; + {$endregion} + property Changed: IMyc2Signal read GetChanged; + end; + + // The Values of typed mutable objects are typed immutables. (And thus should + // be IValue instead of T, see idea above). + IMyc3Mutable = interface( IMyc3Mutable ) + {$region 'property access'} + function GetValue: T; + {$endregion} + property Value: T read GetValue; + end; + + IMyc3ObjectLink = interface + {$region 'property access'} + function GetMutable: IMyc3Mutable; + {$endregion} + procedure Destroyed; + property Mutable: IMyc3Mutable read GetMutable; + end; + + IMyc3Validatable = interface + {$region 'property access'} + function GetInvalidate: IMyc2Signal; + {$endregion} + + function Validate: Boolean; + property Invalidate: IMyc2Signal read GetInvalidate; + end; + + IMyc3Validatable = interface( IMyc3Validatable ) + {$region 'property access'} + function GetValue: T; + {$endregion} + property Value: T read GetValue; + end; + + TValidatable = record + class function FromSignal(const Signal: IMyc2Signal): IMyc3Validatable; static; + end; + + // This is the generic base interface for futures. Futures are used to initialize + // and hold immutable data. + // + // After the initialization is finished, the future and its value are considered + // to be immutable, which means statically constant and accessing them is always + // thread-safe and non-blocking. + // + // Reading the data before initialization is finished is not allowed! Other + // threads have to wait for the initialization signal before accessing the value. + // This can be done explicitly by waiting or by pipelining other futures. + IMyc3Future = interface + + {$region 'property access'} + function GetInitialized: IMyc2State; + function GetValue: T; + {$endregion} + // Wait until initialization is finished. Being a blocking operation, this is only + // allowed from the main thread. + {$message hint 'Sollte nichts zurückgeben, damit es nicht in Conditionals verwendet wird!'} + function WaitFor: T; + + // Initialization state. This state is set exactly once in the lifetime of the + // future. It can be used to pipeline other futures. + // + property Initialized: IMyc2State read GetInitialized; + + // Reading the value is NOT ALLOWED until the initialization is finished, + // otherwise an exception is raised. + // + // Use [WaitFor] to block execution until the value is ready. + property Value: T read GetValue; + end; + + IMyc3Changeable = interface + {$region 'property access'} + function GetMutable: IMyc3Mutable; + {$endregion} + procedure SetChanged; + property Mutable: IMyc3Mutable read GetMutable; + end; + + IMyc3Generic = interface( IMyc3Changeable ) + {$region 'property access'} + function GetFunc: TFunc; + procedure SetFunc(const Value: TFunc); + {$endregion} + property Func: TFunc read GetFunc write SetFunc; + end; + + IMyc3Writeable = interface( IMyc3Changeable ) + function SetValue(const Value: T): Boolean; + end; + + IMyc3ValidateMutable = interface( IMyc3Mutable ) + procedure Validate; + end; + + IMyc3Property = interface( IMyc3Mutable ) + procedure Delete; overload; + procedure Delete( const DefaultValue: T ); overload; + end; + + IMyc3MutableProperty = interface( IMyc3Mutable ) + + {$region 'property access'} + procedure SetMutable( const Value: IMyc3Mutable ); + {$endregion} + property Mutable: IMyc3Mutable write SetMutable; + end; + + IMyc3MutablePropertyEx = interface( IMyc3Mutable ) + procedure Invalidate; + end; + + IMyc3Animator = interface + procedure Tick( const Ticks: Int64 ); + end; + + IMyc3Animator = interface( IMyc3Animator ) + function GetCurrent: IMyc3Mutable; + property Current: IMyc3Mutable read GetCurrent; + end; + + IMycDeletable = interface + procedure Delete; + end; + + IMycArray = interface( IMyc2Enumerable ) + {$region 'property access'} + function GetCount: Integer; + function GetItems( Idx: Integer ): T; + {$endregion} + function ToArray: TArray; + function Sort( const Comparer: IComparer = nil; First: Integer = 0; Count: Integer = -1 ): TArray; + function Divide( const Comparer: IComparer ): TArray>; + function Divide1( const Comparer: IComparer ): TArray>; + function Range( First: Integer; Count: Integer ): IMycArray; + function Rearrange( const Indices: TArray ): IMycArray; + property Count: Integer read GetCount; + property Items[Idx: Integer]: T read GetItems; default; + end; + + IMycChain = interface( IMyc2Enumerable ) + {$region 'property access'} + function GetChanged: IMyc2Signal; + {$endregion} + function AddFirst( const Item: T ): IMycDeletable; + function AddLast( const Item: T ): IMycDeletable; + function ToArray: TArray; + property Changed: IMyc2Signal read GetChanged; + end; + + TChain = record + public + class function Create: IMycChain; static; + end; + + TMycArray = class {helper for TArray} + public + class function Create( const Arr: array of T ): IMycArray; overload; static; + class function Create( Count: Integer; const Getter: TFunc ): IMycArray; overload; static; + + class function Create(const Src: IMyc2Enumerable): IMycArray; overload; static; + + class function Create( const Src: IMycArray; const Converter: TConstFunc ): IMycArray; overload; static; + + class procedure Divide1( const Src: IMycArray>; out Keys: TArray; out Values: + TArray>; const Comparer: IComparer ); overload; static; + + class function Divide( const Src: IMycArray>; const Comparer: IComparer ): TArray>>; overload; static; + + class function Divide( const Src: TArray; const Comparer: IComparer ): TArray>; overload; static; + class function Divide( const Src: TArray>; const Comparer: IComparer ): TArray>>; overload; static; + + class function GetKeys( const Arr: TArray> ): TArray; static; + + class function CreateConvert( const Enum: IMyc2Enumerable; const Conv: TConstFunc ): IMyc2Enumerable; + end; + + IMyc3Set = interface( IMyc3Mutable ) + function Add( const Item: S ): Integer; + procedure Remove( const Item: S ); + procedure Delete( Idx: Integer ); + procedure Clear; + end; + + IMycEnumerable = interface( IMyc3Mutable> ) + {$region 'property access'} + function GetCount: Integer; + {$endregion} + function GetEnumerator: IMycEnumerator; + property Count: Integer read GetCount; + end; + + IMycSetItem = interface( IMycDeletable ) + {$region 'property access'} + function GetValue: T; + {$endregion} + property Value: T read GetValue; + end; + + IMycSet = interface( IMycEnumerable ) + function Add( const Value: T ): IMycSetItem; + function Pop: T; + procedure Clear; + end; + + IMycEnumArray = interface( IMycEnumerable ) + {$region 'property access'} + function GetItems( Idx: Integer ): T; + {$endregion} + function Sort( const Comparer: IComparer = nil; First: Integer = 0; Count: Integer = -1 ): IMycArray; + property Items[Idx: Integer]: T read GetItems; default; + end; + + TSet = record + public + class function Create: IMycSet; overload; static; + class function Create( const Arr: IMyc3Mutable> ): IMycEnumArray; overload; static; + class function CreateEx( const Arr: TArray ): IMycEnumArray; overload; static; + end; + + IMycAssembly = interface( IMycSet>> ) + {$region 'property access'} + function GetItems: IMyc3Mutable>; + {$endregion} + function Add( const Value: T ): IMycDeletable; overload; + function Add( const Values: array of T ): IMycDeletable; overload; + property Items: IMyc3Mutable> read GetItems; + end deprecated; + + TAssembly = record + class function Create: IMycAssembly; static; + end deprecated; + + IMycStack = interface + {$region 'property access'} + function GetCount: Integer; + {$endregion} + procedure Push( const Value: T ); + function Pop: T; + procedure Clear; + function ToArray: TArray; + property Count: Integer read GetCount; + end; + + Stack = record + class function Create: IMycStack; static; + end; + + TFunc = reference to function( P1: U; P2: V; P3: W; P4: X; P5: Y ): T; + TFunc = reference to function( P1: U; P2: V; P3: W; P4: X; P5: Y; P6: Z ): T; + + ///////////////////////////////////////////////////////////////////////////// + // TValue + // + TValue = class + type + TCalculator = reference to procedure( var Value: T ); + + class var + FDefaultMutable: IMyc3Mutable; + FDefaultMutableFuture: IMyc3Mutable>; + + class constructor CreateClass; + class function GetDefaultFuture: IMyc3Future; static; + + public + class function AsImmutable( const Value: T ): IMyc3Value; overload; static; + + class function CreateLazy(const Func: TFunc): IMyc3Value; static; + + // Create a constant future. + class function AsFuture( const Value: T ): IMyc3Future; static; + + // Create a generic future. Its fully up to the calculator to handle + // synchronization. + class function CreateFuture( const StartSignal: IMyc2Signal; const Calculator: TFunc ): IMyc3Future; + overload; static; + class function CreateFuture( const StartSignal: IMyc2Signal; const Calculator: TFunc < IMyc3Future < T >> ) + : IMyc3Future; overload; static; + + class function CreateFutureArray( Count: Integer; const Calculator: TFunc ): IMyc3Future>; overload; static; + class function CreateFutureArray( const StartSignal: IMyc2Signal; Count: Integer; const Calculator: TFunc ): + IMyc3Future>; overload; static; + + class function CreateFutures( Count: Integer; const Src: IMyc3Future < TArray < T >> ): TArray>; overload; static; + + // Create a generic immutable + class function CreateFuture( const Calculator: TFunc ): IMyc3Future; overload; static; + + class function Convert( P: IMyc3Future; const Converter: TFunc ): IMyc3Future; static; + + // Create an immutable from one other immutables. + class function CreateFuture( P: IMyc3Future; Calculator: TFunc ): IMyc3Future; overload; static; + class function CreateFuture( P: IMyc3Future; Calculator: TFunc < U, IMyc3Future < T >> ): IMyc3Future; + overload; static; + class function CreateFuture( P1: IMyc3Future; P2: IMyc3Future; Calculator: TFunc ): IMyc3Future; + overload; static; + class function CreateFuture( P1: IMyc3Future; P2: IMyc3Future; Calculator: TFunc < U, V, IMyc3Future < T >> ) + : IMyc3Future; overload; static; + class function CreateFuture( P1: IMyc3Future; P2: IMyc3Future; P3: IMyc3Future; + Calculator: TFunc ): IMyc3Future; overload; static; + + // Create an immutable array from an array of immutables. + class function CreateFuture( const Values: TArray < IMyc3Future < T >> ): IMyc3Future>; overload; static; + class function CreateFuture( const Values: TArray>; const Calculator: TFunc ): IMyc3Future>; + overload; static; + + // Calculate an immutable from a set of other immutables. This is a convenient + // function that passes the parameter Values as typed array to the calculator. + class function CreateFuture( const Params: TArray>; Calculator: TFunc, T> ): IMyc3Future; + overload; static; + + // Create a future from the value of another future. + class function CreateFuture( Future: IMyc3Future; Creator: TFunc> ): IMyc3Future; + overload; static; + + // Execute on future init. + class function Execute( Future: IMyc3Future; Proc: TConstFunc ): IMyc2State; + + // Get value, if future is initialized. + class function TryGetValue( const Future: IMyc3Future; out Value: T ): Boolean; static; + + // Create a mutable from a constant value. The resulting mutable never changes. + class function AsConst( const Value: T ): IMyc3Mutable; overload; static; deprecated 'use TMutable()'; + class function CreateMutable( const Value: T ): IMyc3Mutable; overload; static; deprecated 'use TMutable()'; + + class function CreateMutable( Calculator: TFunc ): IMyc3Mutable; overload; static; + experimental; + + class function CreateMutable( const Changed: array of IMyc2Signal; const Calculator: TFunc ): IMyc3Mutable; overload; static; deprecated 'use TMutable.FromSignals()'; + class function CreateMutable(const Changed: array of IMyc2Signal; const Calculator: TFunc>): IMyc3Mutable; overload; static; deprecated 'use TMutable.FromSignals()'; + // Extend a mutable by a set of signals. + class function CreateMutable( const Changed: array of IMyc2Signal; const Mutable: IMyc3Mutable ): IMyc3Mutable; overload; static; + class function CreateMutable( const Event: IMycEventObserver; const Calculator: TFunc ): IMyc3Mutable; overload; static; + // Futures as mutables + class function AsMutable( const Value: IMyc3Mutable> ): IMyc3Mutable; overload; static; + class function AsMutable( const Value: IMyc3Mutable> ): IMyc3Mutable; overload; static; + + class function AsMutable(const Value: IMyc3Future): IMyc3Mutable; overload; static; + class function AsMutable(const Value: IMyc3Mutable>): IMyc3Mutable; overload; static; + + // Operators + class function CreateMutable( P: IMyc3Mutable; Calculator: TFunc ): IMyc3Mutable; overload; static; + class function CreateMutable( P1: IMyc3Mutable; P2: IMyc3Mutable; Calculator: TFunc ) + : IMyc3Mutable; overload; static; + class function CreateMutable( P1: IMyc3Mutable; P2: IMyc3Mutable; P3: IMyc3Mutable; + Calculator: TFunc ): IMyc3Mutable; overload; static; + class function CreateMutable( P1: IMyc3Mutable; P2: IMyc3Mutable; P3: IMyc3Mutable; + P4: IMyc3Mutable; Calculator: TFunc ): IMyc3Mutable; overload; static; + class function CreateMutable( P1: IMyc3Mutable; P2: IMyc3Mutable; P3: IMyc3Mutable; P4: IMyc3Mutable; P5: + IMyc3Mutable; Calculator: TFunc ): IMyc3Mutable; overload; static; + class function CreateMutable( P1: IMyc3Mutable; P2: IMyc3Mutable; P3: IMyc3Mutable; P4: IMyc3Mutable; P5: + IMyc3Mutable; P6: IMyc3Mutable; Calculator: TFunc ): IMyc3Mutable; overload; static; + + // Mutable arrays + class function CreateMutable( const Arr: TArray < IMyc3Mutable < T >> ): IMyc3Mutable>; overload; static; deprecated 'Mutable.CreateArray'; + class function CreateMutable( const Arr: TArray>; Calculator: TFunc ): + IMyc3Mutable>; overload; static; + class function CreateMutableEx( Params: TArray>; Calculator: TFunc, T> ): IMyc3Mutable; overload; static; + experimental; + + {$message hint 'Debug-Code! Eigentlich ist das hier das wahre Lazy-Mutable'} + class function CreateGeneric(const Calculator: TFunc = nil): IMyc3Generic; + + // Create a mutable, that supports direct changes to it's value. + class function CreateWriteable: IMyc3Writeable; overload; static; + class function CreateWriteable( const InitValue: T ): IMyc3Writeable; overload; static; + class function CreateWriteable( const InitValue: T; const EqualityComparison: TEqualityComparison ): IMyc3Writeable; + overload; static; + + class function CreateValidate( const Calculator: TFunc ): IMyc3ValidateMutable; static; + + class function CreateMutableProperty(const InitValue: IMyc3Mutable = nil): IMyc3MutableProperty; overload; static; experimental; + + class function CreateProperty( const Creator: TFunc < IMyc3Mutable < T >> ): IMyc3MutablePropertyEx; overload; static; + experimental; + + class function CreatePropertyEx( const Decorated: IMyc3Mutable ): IMyc3Property; overload; static; + + // Combining values + class function Combine( Values: TArray; Combiner: TMyc3Combiner ): IMyc3Future; overload; static; + class function Combine(const Values: TArray>; Combiner: TMyc3Combiner): IMyc3Mutable; overload; static; + class function Combine( const Values: TArray>; Combiner: TMyc3Combiner ): IMyc3Future; + overload; static; + class function CreateSet: IMycSet; overload; static; + + class function WaitFor( Future: IMyc3Future ): T; overload; static; + class procedure WaitFor( const Futures: TArray>; Exec: TProc ); overload; static; + + class function Capture( const Src: TArray ): TArray; + experimental; + + class function CreateAnimator( const Target: IMyc3Mutable; const Interpolator: TMyc3TargetInterpolator ) + : IMyc3Animator; overload; static; + class function CreateAnimator( const Init: T; const ChangeSignal: IMyc2Signal; const Interpolator: TMyc3Interpolator ) + : IMyc3Animator; overload; static; + + class function MakeValid( const Value: IMyc3Future ): IMyc3Future; overload; static; inline; + class function MakeValid( const Value: IMyc3Mutable ): IMyc3Mutable; overload; static; inline; + + class property DefaultFuture: IMyc3Future read GetDefaultFuture; + class property DefaultMutable: IMyc3Mutable read FDefaultMutable; + class property DefaultMutableFuture: IMyc3Mutable> read FDefaultMutableFuture; + end; + + TMutableFuture = record + private + class var + FNull: IMyc3Mutable>; + class function GetNull: IMyc3Mutable>; static; + class constructor CreateClass; + + public + class function Create( const P: IMyc3Mutable>; Calc: TFunc ): IMyc3Mutable>; static; + + class property Null: IMyc3Mutable> read GetNull; + end; + + IMyc3MutableCache = interface( IMyc3Mutable ) + {$region 'property access'} + function GetCount: Integer; + {$endregion} + function Add( const Value: IMyc3Mutable ): IMyc3Mutable; + procedure Clear; + procedure Validate; + function GetEnumerator: IMycEnumerator>; + property Count: Integer read GetCount; + end; + + IMyc3MutableTable = interface + procedure Clear; + end; + + IMyc3MutableTable = interface( IMyc3MutableTable ) + function Add( const Value: T ): IMyc3Mutable; + end; + + TMutableCache = record + private + FThis: IMyc3MutableTable; + + function GetItem(const Value: T): IMyc3Mutable; + + public + constructor Create( const AThis: IMyc3MutableTable ); + procedure Clear; + property Item[const Value: T]: IMyc3Mutable read GetItem; default; + end; + + TMutable = record + private + FThis: IMyc3Mutable; + + public + class function FromSignals(const Changed: array of IMyc2Signal; const Calculator: TFunc): IMyc3Mutable; overload; static; + class function FromSignals(const Changed: array of IMyc2Signal; const Calculator: TFunc>): IMyc3Mutable; overload; static; + + class function From(const Value: IMyc3Mutable; const Conv: TFunc>): TMutable; overload; static; + + class operator Explicit(const Value: T): TMutable; + class operator Implicit(const Item: IMyc3Mutable): TMutable; + class operator Implicit(const Item: TMutable): IMyc3Mutable; + end; + + TMutableFuture = record + + // Arrays + class function FromArray(const Value: TArray>>): IMyc3Mutable>>; overload; static; + end; + + TFuture = record + class function From(const Value: IMyc3Future; const Conv: TFunc): IMyc3Future; overload; static; + class function From(const Value: IMyc3Future; const Conv: TFunc>): IMyc3Future; overload; static; + + class function FromArray(const Value: TArray>): IMyc3Future>; overload; static; + + class function Combine( const Value: TArray>; const Calculator: TFunc, T> ): IMyc3Future; overload; static; + + class function Merge(const Parts: TArray>): TArray; overload; static; + class function Merge(const Parts: TArray>>): IMyc3Future>; overload; static; + end; + + Mutable = record + // convert + class function Convert(const Value: IMyc3Mutable; const Conv: TFunc): IMyc3Mutable; overload; static; + class function Convert(const Value: IMyc3Mutable>; const Conv: TFunc): IMyc3Mutable>; overload; static; + class function Convert(const Value: IMyc3Mutable>; const Conv: TFunc>): IMyc3Mutable>; overload; static; + + // enclose + class function From(const Value: T): IMyc3Mutable; overload; static; + class function From(const Value: IMyc3Mutable; const Conv: TFunc>): IMyc3Mutable; overload; static; + class function From(const ValueR: IMyc3Mutable; const ValueS: IMyc3Mutable; const Conv: TFunc>): IMyc3Mutable; overload; static; + + // Arrays + class function FromArray(const Value: TArray>): IMyc3Mutable>; overload; static; + class function FromArray(const Value: IMyc3Mutable>; const Conv: TFunc): IMyc3Mutable>; overload; static; + class function FromArray(const Value: IMyc3Mutable>; const Conv: TFunc>): IMyc3Mutable>; overload; static; + + class function FilterArray(const Values: IMyc3Mutable>; const FilterFunc: TConstFunc>): IMyc3Mutable>; static; + class function SortArray(const Values: IMyc3Mutable>; const Comparer: IComparer): IMyc3Mutable>; static; + class function TrimArray(const Values: IMyc3Mutable>): IMyc3Mutable>; static; + + // bool + class function Create( Value: Boolean ): IMyc3Mutable; overload; static; + + class function True: IMyc3Mutable; overload; static; deprecated 'use TMutableBool'; + class function False: IMyc3Mutable; overload; static; deprecated 'use TMutableBool'; + + class function OpOR(const Arr: TArray>): IMyc3Mutable; static; deprecated 'use TMutableBool'; + class function OpAND(const Arr: TArray>): IMyc3Mutable; static; deprecated 'use TMutableBool'; + class function OpNOR(const Arr: TArray>): IMyc3Mutable; static; deprecated 'use TMutableBool'; + class function OpNAND(const Arr: TArray>): IMyc3Mutable; static; deprecated 'use TMutableBool'; + class function OpNOT(const A: IMyc3Mutable): IMyc3Mutable; static; deprecated 'use TMutableBool'; + + class function IfThen(const Condition: IMyc3Mutable; const TrueValue: IMyc3Mutable): IMyc3Mutable; overload; static; + class function IfThen(const Condition: IMyc3Mutable; const TrueValue: IMyc3Mutable>): IMyc3Mutable>; overload; static; + class function IfNotThen(const Condition: IMyc3Mutable; const FalseValue: IMyc3Mutable): IMyc3Mutable; static; + class function IfThenElse(const Condition: IMyc3Mutable; const TrueValue, FalseValue: IMyc3Mutable): IMyc3Mutable; static; + + class function CreateOverride(const Value, Ovrride: IMyc3Mutable>): IMyc3Mutable>; overload; static; + + class function FromSignal(const Signal: IMyc2Signal): IMyc3Mutable; static; + class function FromState(const State: IMyc2State): IMyc3Mutable; static; + + class function CreateValidator( const Comparer: IComparer = nil ): IMyc3MutableCache; static; + class function CreateCache( const Comparer: IComparer = nil ): TMutableCache; static; + + class function CreateEventObserver(const Mutables: TArray>): IMycEventObserver; static; + class function CreateNotifyObserver(const Mutables: TArray>): IMycNotifyObserver; static; + + class function ExtractSignals(const Mutables: TArray>): IMyc2Signal; static; + + class function CreateAssembly(const Arr: IMyc3Mutable>>): IMyc3Mutable>; overload; static; + class function CreateAssembly(const Arr: IMyc3Mutable>>>): IMyc3Mutable>>; overload; static; + + class function CreateAssembly(const Arr: IMyc3Mutable>>; const Creator: TFunc, T>): IMyc3Mutable; overload; static; + + class function CreateAssembly(const Arr: IMyc3Mutable>>>; const Creator: TFunc, T>): + IMyc3Mutable>; overload; static; + + class function CreateAssembly(const Arr: IMyc3Mutable>>; const Creator: TFunc, S>): IMyc3Mutable; + overload; static; + + class function CreateAssembly(const Arr: IMyc3Mutable>>>; const Creator: TFunc, S>): + IMyc3Mutable>; overload; static; + + class function ConvertSet(const Arr: IMyc3Mutable>>; const Converter: TFunc): IMyc3Mutable>>; overload; static; + + class function ConvertSet(const Arr: IMyc3Mutable>>>; const Converter: TFunc): IMyc3Mutable< + TArray< IMyc3Mutable>>>; overload; static; + + class function Merge(const Parts: IMyc3Mutable>>): IMyc3Mutable>; overload; static; + class function Merge(const Parts: IMyc3Mutable>>>): IMyc3Mutable>>; overload; static; + + class function CreateObjectLink(Obj: T): IMyc3ObjectLink; static; + + class function CreateNullable: IMyc3Writeable>; static; + end; + + TMutableBool = record + private + FThis: IMyc3Mutable; + class function GetDefaults(Value: Boolean): IMyc3Mutable; static; inline; + + public + constructor Create(const AThis: IMyc3Mutable); + + class operator LogicalNot(const Value: TMutableBool): TMutableBool; + class operator LogicalAnd(const A, B: TMutableBool): TMutableBool; + class operator LogicalOr(const A, B: TMutableBool): TMutableBool; + + class operator Equal(const A, B: TMutableBool): TMutableBool; + class operator NotEqual(const A, B: TMutableBool): TMutableBool; + + class operator Implicit(const Value: IMyc3Mutable): TMutableBool; inline; + class operator Implicit(const Value: TMutableBool): IMyc3Mutable; inline; + + class property Defaults[Value: Boolean]: IMyc3Mutable read GetDefaults; + + class function True: IMyc3Mutable; static; inline; + class function False: IMyc3Mutable; static; inline; + + property This: IMyc3Mutable read FThis; + end; + + Mutable = class + private + class function GetComparer: IComparer>; static; inline; + + public + class function Default: IMyc3Mutable; static; inline; + + class function IfThen( const Condition: IMyc3Mutable; const TrueValue: IMyc3Mutable ): IMyc3Mutable; static; deprecated; + class function IfNotThen( const Condition: IMyc3Mutable; const FalseValue: IMyc3Mutable ): IMyc3Mutable; static; deprecated; + class function IfThenElse( const Condition: IMyc3Mutable; const TrueValue, FalseValue: IMyc3Mutable ): IMyc3Mutable; static; deprecated; + + class function CreateArray( const Arr: TArray> ): IMyc3Mutable>; overload; static; + class function CreateArray( const Arr: IMyc3Mutable>; Calculator: TFunc ): IMyc3Mutable>; overload; static; + + class property Comparer: IComparer> read GetComparer; + end; + + TMyc2ComputeProperty = record + private + FDone: IMyc2State; + FValue: T; + + public + constructor Create( const StartSignal: IMyc2Signal; const Creator: TFunc ); + function Value: T; inline; + procedure Terminate; inline; + property Done: IMyc2State read FDone; + end; + + ///////////////////////////////////////////////////////////////////////////// + // IMyc2TaskFactory + // + IMyc2TaskFactory = interface + {$region 'property access'} + function GetThreadCount: Integer; + {$endregion} + function CreateThread( const Proc: TProc ): IMyc2State; + function InMainThread: Boolean; + function InWorkerThread: Boolean; + function Run( StartCount: Integer; Proc: TProc ): IMyc2Sink; + procedure Teardown; + procedure WaitFor( State: IMyc2State ); + property ThreadCount: Integer read GetThreadCount; + end; + + TMyc3Tasks = class( TObject ) + private + FApi: IMyc2TaskFactory; + FEnabled: Boolean; + function GetInMainThread: Boolean; + function GetThreadCount: Integer; + procedure SetEnabled( const Value: Boolean ); + + procedure Run( const StartSignal: IMyc2Signal; const Proc: TProc ); overload; + procedure Run( const Proc: TProc ); overload; + public + constructor Create( const AApi: IMyc2TaskFactory ); + destructor Destroy; override; + function CreateThread( const Proc: TProc ): IMyc2State; + procedure ExpectSignaled( State: IMyc2State; const Msg: String = 'Expected state to be already set' ); + + function LoopUntil( const DoneSignal: IMyc2Signal; const Proc: TFunc ): IMyc2State; + + function ParallelFor(const StartSignal: IMyc2Signal; LowVal, HighVal, Slice: Integer; const Proc: TProc): IMyc2State; overload; + procedure ParallelFor(LowVal, HighVal, Slice: Integer; const Proc: TProc); overload; + + function Start( const StartSignal: IMyc2Signal; const Proc: TFunc ): IMyc2State; overload; + function Start( const StartSignal: IMyc2Signal; Proc: TProc ): IMyc2State; overload; + function Start( const StartSignal: IMyc2Signal; P1: T1; Proc: TProc ): IMyc2State; overload; + function Start( const StartSignal: IMyc2Signal; P1: T1; P2: T2; Proc: TProc ): IMyc2State; overload; + function Start( const Proc: TFunc ): IMyc2State; overload; + function Start( const Proc: TProc ): IMyc2State; overload; + + function Enqueue( var Queue: IMyc2State; const Proc: TFunc ): IMyc2State; overload; + function Enqueue( var Queue: IMyc2State; const Proc: TProc ): IMyc2State; overload; + + function Capture( P: T; Proc: TProc ): TProc; overload; + function Capture( P1: T; P2: R; Proc: TProc ): TProc; overload; + function Capture( P1: T; P2: R; P3: U; Proc: TProc ): TProc; overload; + + function Capture( P: T; Proc: TFunc ): TFunc; overload; + function Capture( P: T; Proc: TFunc ): TFunc; overload; + function Capture( P: T; Proc: TFunc ): TFunc; overload; + + procedure WaitFor( const State: IMyc2State ); + + property InMainThread: Boolean read GetInMainThread; + property ThreadCount: Integer read GetThreadCount; + + property Enabled: Boolean write SetEnabled; + end; + + TTag = record + strict private + FManaged: IInterface; + FOrdinal: Int64; + {$ifdef DEBUG} + FTypeInfo: PTypeInfo; + {$endif} + + public + class function From(const Data: T): TTag; static; + function AsType: T; + end; + + // Parameters + + TParameterValue = record + strict private + FMutable: IMyc3Mutable; + {$ifdef DEBUG} + FTypeInfo: PTypeInfo; + procedure CheckType(TI: PTypeInfo); + {$endif} + + public + constructor Create( ATypeInfo: PTypeInfo; const AMutable: IMyc3Mutable ); overload; + function Get: T; + class function Create( const Intf: T ): TParameterValue; overload; static; + class function Create( const Intf: IMyc3Mutable ): TParameterValue; overload; static; + class function Compare( const A, B: TParameterValue ): Integer; static; + class function FromArray( const Values: TArray ): TArray>; overload; static; + class function FromVarRec( const VarRec: TVarRec ): TParameterValue; static; + constructor Assign( const Src: TParameterValue ); + function AsType: IMyc3Mutable; + property Mutable: IMyc3Mutable read FMutable; + end; + + TFieldID = Integer; + + TParameterField = record + private + FID: TFieldID; + FValue: TParameterValue; + + public + constructor Create( AID: TFieldID; const AValue: TParameterValue ); overload; + + class function Create( ID: TFieldID; const Intf: T ): TParameterField; overload; static; + class function Create( ID: TFieldID; const Intf: IMyc3Mutable ): TParameterField; overload; static; + + property ID: TFieldID read FID; + property Value: TParameterValue read FValue write FValue; + end; + + IMycParameterRecordDefinition = interface + {$region 'property access'} + function GetByName(const Caption: String): Integer; + function GetCount: Integer; + function GetFields( Idx: Integer ): TFieldID; + function GetIdx: Integer; + {$endregion} + function IndexOf( Field: TFieldID ): Integer; + property ByName[const Caption: String]: Integer read GetByName; + property Count: Integer read GetCount; + property Fields[Idx: Integer]: TFieldID read GetFields; + property Idx: Integer read GetIdx; + end; + + IMyc3ParameterRecord = interface + {$region 'property access'} + function GetByName(const Caption: String): TParameterValue; + function GetDef: IMycParameterRecordDefinition; + function GetValues(ID: TFieldID): TParameterValue; + {$endregion} + function GetEnumerator: TEnumerator; + property ByName[const Caption: String]: TParameterValue read GetByName; + property Def: IMycParameterRecordDefinition read GetDef; + property Values[ID: TFieldID]: TParameterValue read GetValues; default; + end; + + TParameterRecord = record + private + FThis: IMyc3ParameterRecord; + + function GetValues(ID: TFieldID): TParameterValue; inline; + function GetByName( const Caption: String ): TParameterValue; inline; + function GetDef: IMycParameterRecordDefinition; inline; + + public + constructor Create( const AThis: IMyc3ParameterRecord ); + + function AsType( ID: TFieldID ): IMyc3Mutable; inline; + function Get(ID: TFieldID): T; inline; + + class operator Implicit(const Item: IMyc3ParameterRecord): TParameterRecord; + class operator Implicit(const Item: TParameterRecord): IMyc3ParameterRecord; + + property Def: IMycParameterRecordDefinition read GetDef; + + property Values[ID: TFieldID]: TParameterValue read GetValues; default; + property ByName[const Caption: String]: TParameterValue read GetByName; + end; + + IMycParameterSpace = interface + {$region 'property access'} + function GetFieldCount: Integer; + function GetDefaultValues( FieldID: TFieldID ): TParameterValue; + function GetCaptions( FieldID: TFieldID ): String; + function GetComparer( FieldID: TFieldID ): IComparer; + function GetByName(const Caption: String): TFieldID; + {$endregion} + function AddField( TypInfo: PTypeInfo; const Caption: String; const DefaultValue: IMyc3Mutable; const Comparer: IComparer ): TFieldID; + function CompareValues( FieldID: TFieldID; const A, B: TParameterValue ): Integer; + function CreateRecord(const Fields: TArray): IMyc3ParameterRecord; + function CreateDef(const Fields: TArray): IMycParameterRecordDefinition; + property DefaultValues[FieldID: TFieldID]: TParameterValue read GetDefaultValues; + property Captions[FieldID: TFieldID]: String read GetCaptions; + property Comparer[FieldID: TFieldID]: IComparer read GetComparer; + property ByName[const Caption: String]: TFieldID read GetByName; + property FieldCount: Integer read GetFieldCount; + end; + + TParameterSpace = record + private + FThis: IMycParameterSpace; + + function GetFieldCount: Integer; inline; + function GetDefaultValues( FieldID: TFieldID ): TParameterValue; inline; + function GetCaptions( FieldID: TFieldID ): String; inline; + function GetComparer( FieldID: TFieldID ): IComparer; inline; + + public + constructor Create( const AThis: IMycParameterSpace ); overload; + + class function Create: TParameterSpace; overload; static; + + { TODO : ->Factory (mit D11 managed records) } + function AddField(const Caption: String; const DefaultValue: T; const Comparer: IComparer = nil): TFieldID; + + function CompareValues( FieldID: TFieldID; const A, B: TParameterValue ): Integer; + function CreateRecord( const Fields: TArray ): TParameterRecord; + function CreateDef(const Fields: TArray): IMycParameterRecordDefinition; + + class operator Implicit( const Item: TParameterSpace ): IMycParameterSpace; + class operator Implicit( const Item: IMycParameterSpace ): TParameterSpace; + + property FieldCount: Integer read GetFieldCount; + property DefaultValues[FieldID: TFieldID]: TParameterValue read GetDefaultValues; + property Captions[FieldID: TFieldID]: String read GetCaptions; + property Comparer[FieldID: TFieldID]: IComparer read GetComparer; + end; + +type + TStackTrace = record + const + Len = 64; + var + Msg: String; + StackTrace: array [0..Len-1] of NativeUInt; + constructor Create(ASkipFrames: Cardinal; const AMsg: String = ''); + function ToText: String; + end; + +var + Tasks: TMyc3Tasks; + +procedure RaiseInvalidParameter( const Msg: String = '' ); +procedure RaiseNullPointer; +procedure RaiseInvalidOperation( const Msg: String = '' ); overload; +procedure RaiseInvalidOperation( const Msg: String; const Args: array of const ); overload; + +procedure ForEach( Count: Integer; Exec: TProc ); overload; +procedure ForEach( Count1, Count2: Integer; Exec: TProc ); overload; +procedure ForEach( Count1, Count2, Count3: Integer; Exec: TProc ); overload; +procedure ForEach( LowIdx, HighIdx: Integer; Exec: TProc ); overload; + +implementation + +uses + System.Classes, System.SyncObjs, System.SysConst, System.Math, + Myc.Signals, Myc.Tasks, Myc.Values, Myc.Values.Sets, Myc.Values.Validate, + Myc.Values.Properties, Myc.Values.Bool, Myc.Atomic, Myc.Future.Arrays, + Myc.Values.Parameter; + +procedure RaiseInvalidParameter( const Msg: String = '' ); +var + Str: String; +begin + Str := 'Invalid parameter'; + if Msg <> '' then + Str := Str + ' (' + Msg + ')'; + + raise EMycInvalidParameter.Create( Str ); +end; + +procedure RaiseNullPointer; +begin + RaiseInvalidParameter( 'null pointer' ); +end; + +procedure RaiseInvalidOperation( const Msg: String ); overload; +begin + RaiseInvalidOperation( Msg, [] ); +end; + +procedure RaiseInvalidOperation( const Msg: String; const Args: array of const ); overload; +begin + if Msg = '' then + raise EMycInvalidOperation.Create( 'Invalid operation' ) + else + raise EMycInvalidOperation.CreateFmt( 'Invalid operation: ' + Msg, Args ) +end; + +procedure ForEach( Count: Integer; Exec: TProc ); +var + i: Integer; +begin + for i := 0 to Count - 1 do + Exec( i ); +end; + +procedure ForEach( Count1, Count2: Integer; Exec: TProc ); +var + i, j: Integer; +begin + for i := 0 to Count1 - 1 do + for j := 0 to Count2 - 1 do + Exec( i, j ); +end; + +procedure ForEach( Count1, Count2, Count3: Integer; Exec: TProc ); +var + i, j, k: Integer; +begin + for i := 0 to Count1 - 1 do + for j := 0 to Count2 - 1 do + for k := 0 to Count3 - 1 do + Exec( i, j, k ); +end; + +procedure ForEach( LowIdx, HighIdx: Integer; Exec: TProc ); overload; +var + i: Integer; +begin + for i := LowIdx to HighIdx do + Exec( i ); +end; + +{ TMyc3Tasks } + +constructor TMyc3Tasks.Create( const AApi: IMyc2TaskFactory ); +begin + inherited Create; + FApi := AApi; + FEnabled := True; +end; + +destructor TMyc3Tasks.Destroy; +begin + FApi.Teardown; + inherited; +end; + +function TMyc3Tasks.Capture( P1: T; P2: R; P3: U; Proc: TProc ): TProc; +begin + Result := + procedure + begin + Proc( P1, P2, P3 ); + end; +end; + +function TMyc3Tasks.Capture( P: T; Proc: TFunc ): TFunc; +begin + Result := + function: R + begin + Result := Proc( P ); + end; +end; + +function TMyc3Tasks.Capture( P1: T; P2: R; Proc: TProc ): TProc; +begin + Result := + procedure + begin + Proc( P1, P2 ); + end; +end; + +function TMyc3Tasks.Capture( P: T; Proc: TFunc ): TFunc; +begin + Result := + function( Q: U ): R + begin + Result := Proc( P, Q ); + end; +end; + +function TMyc3Tasks.Capture( P: T; Proc: TFunc ): TFunc; +begin + Result := + function( Q: U; R: V ): R + begin + Result := Proc( P, Q, R ); + end; +end; + +function TMyc3Tasks.Capture( P: T; Proc: TProc ): TProc; +begin + Result := + procedure + begin + Proc( P ); + end; +end; + +function TMyc3Tasks.CreateThread( const Proc: TProc ): IMyc2State; +begin + Result := FApi.CreateThread( Proc ); +end; + +{ TSet } + +class function TSet.Create: IMycSet; +begin + Result := TMycSet.Create; +end; + +class function TSet.CreateEx( const Arr: TArray ): IMycEnumArray; +begin + Result := TMycEnumArray.CreateStatic( Arr ); +end; + +class function TSet.Create( const Arr: IMyc3Mutable> ): IMycEnumArray; +begin + Result := TMycEnumArray.CreateMutable( Arr ); +end; + +{ TFuture } + +class function TFuture.From(const Value: IMyc3Future; const Conv: TFunc): IMyc3Future; +begin + Result := TMyc3Future.CreateFuture( Value.Initialized, + function: T + begin + Result := Conv( Value.Value ); + end ); +end; + +class function TFuture.Combine(const Value: TArray>; + const Calculator: TFunc, T>): IMyc3Future; +begin + Result := TValue.CreateFuture( Value, Calculator ); +end; + +class function TFuture.From(const Value: IMyc3Future; const Conv: TFunc>): IMyc3Future; +begin + Result := TMyc3Future.CreateFuture( Value.Initialized, + function: IMyc3Future + begin + Result := Conv( Value.Value ); + end ); +end; + +class function TFuture.FromArray(const Value: TArray>): IMyc3Future>; +begin + Result := TMyc3FutureArrayEx.Create( Value ); +end; + +class function TFuture.Merge(const Parts: TArray>): TArray; +var + i, j, n: Integer; +begin + n := 0; + for i := 0 to High(Parts) do + inc( n, Length(Parts[i]) ); + + SetLength( Result, n ); + n := 0; + for i := 0 to High(Parts) do + begin + for j := 0 to High(Parts[i]) do + begin + Result[n] := Parts[i,j]; + inc(n); + end; + end; +end; + +class function TFuture.Merge(const Parts: TArray>>): IMyc3Future>; +begin + if Length(Parts) = 0 then + Result := TValue>.DefaultFuture + else if Length(Parts) = 1 then + Result := Parts[0] + else + Result := TValue>.CreateFuture>>( + TValue>.CreateFuture( Parts ), + function( Parts: TArray> ): TArray + begin + Result := Merge( Parts ); + end ); +end; + +{ TMutableFuture } + +class constructor TMutableFuture.CreateClass; +begin + FNull := TValue>.CreateMutable( TValue.DefaultFuture ); +end; + +class function TMutableFuture.Create( const P: IMyc3Mutable>; Calc: TFunc ): IMyc3Mutable>; +begin + Result := TValue>.CreateMutable>( + P, + function( P: IMyc3Future ): IMyc3Future + begin + Result := TValue.CreateFuture( + P, + function( P: T ): S + begin + Result := Calc( P ); + end ); + end ); +end; + +class function TMutableFuture.GetNull: IMyc3Mutable>; +begin + Result := FNull; +end; + +{ TImmutable } + +constructor TImmutable.Create(const AValue: T); +begin + FThis := TMyc3Value.Create( AValue ); +end; + +function TImmutable.GetValue: T; +begin + if FThis<>nil then + Result := FThis.Value + else + Result := Default(T); +end; + +function TImmutable.GetHasValue: Boolean; +begin + Result := FThis<>nil; +end; + +class function TImmutable.GetNull: TImmutable; +begin + Result := Default(TImmutable); +end; + +class operator TImmutable.Equal(ALeft, ARight: TImmutable): Boolean; +begin + if ALeft.HasValue and ARight.HasValue then + Result := TEqualityComparer.Default.Equals(ALeft.Value, ARight.Value) + else + Result := ALeft.HasValue = ARight.HasValue; +end; + +class operator TImmutable.NotEqual(ALeft, ARight: TImmutable): Boolean; +begin + if ALeft.HasValue and ARight.HasValue then + Result := not TEqualityComparer.Default.Equals(ALeft.Value, ARight.Value) + else + Result := ALeft.HasValue <> ARight.HasValue; +end; + +{ TMyc3Tasks } + +function TMyc3Tasks.Enqueue( var Queue: IMyc2State; const Proc: TFunc ): IMyc2State; +var + Exec: IMyc2Condition; + [unsafe] + Q: IMyc2State; +begin + Exec := Signals.CreateCounter( 1 ); + + Result := Exec.State; + + Result._AddRef; + Pointer( Q ) := TInterlocked.Exchange( Pointer( Queue ), Pointer( Result ) ); + try + Start( Q, Proc ).Advise( Exec ); + finally + Q._Release; + end; +end; + +function TMyc3Tasks.Enqueue( var Queue: IMyc2State; const Proc: TProc ): IMyc2State; +var + Exec: IMyc2Condition; + [unsafe] + Q: IMyc2State; +begin + Exec := Signals.CreateCounter( 1 ); + + Result := Exec.State; + + Result._AddRef; + Pointer( Q ) := TInterlocked.Exchange( Pointer( Queue ), Pointer( Result ) ); + try + Start( Q, Proc ).Advise( Exec ); + finally + Q._Release; + end; +end; + +procedure TMyc3Tasks.ExpectSignaled( State: IMyc2State; const Msg: String = 'Expected state to be already set' ); +begin + System.Assert( Signals.IsStatic( State ) or State.IsSet, Msg ); +end; + +function TMyc3Tasks.GetInMainThread: Boolean; +begin + Result := FApi.InMainThread; +end; + +function TMyc3Tasks.GetThreadCount: Integer; +begin + Result := FApi.ThreadCount; +end; + +function TMyc3Tasks.LoopUntil( const DoneSignal: IMyc2Signal; const Proc: TFunc ): IMyc2State; +var + Done: IMyc2Event; + CapturedProc: TFunc; +begin + if Signals.IsStatic( DoneSignal ) then + exit( Signals.Null.State ); + + DoneSignal._AddRef; + try + Done := Signals.CreateEvent( false ); + DoneSignal.Advise( Done ); + + CapturedProc := Proc; + + Result := FApi.CreateThread( + procedure + begin + try + while not Done.State.IsSet do + WaitFor( CapturedProc ); + finally + CapturedProc := nil; + end; + end ); + finally + DoneSignal._Release; + end; +end; + +function TMyc3Tasks.ParallelFor(const StartSignal: IMyc2Signal; LowVal, HighVal, Slice: Integer; const Proc: TProc): + IMyc2State; + + function CallFunc( f, l: Integer; Proc: TProc ): IMyc2State; + begin + Result := Start( + StartSignal, + procedure + begin + Proc( f, l ); + end ); + end; + +var + l, f, n: Integer; + Counter: IMyc2Condition; +begin + if not Assigned( Proc ) or ( LowVal > HighVal ) then + exit; + + if Slice <= 0 then + Slice := max( 1, (HighVal-LowVal+1) div ThreadCount ); + + n := 0; + f := LowVal; + while f <= HighVal do + begin + inc( f, Slice ); + inc( n ); + end; + + if n=0 then + exit; + + Counter := Signals.CreateCounter( n ); + + f := LowVal; + while f <= HighVal do + begin + l := f + Slice - 1; + if l > HighVal then + l := HighVal; + + CallFunc( f, l, Proc ).Advise( Counter ); + + f := l + 1; + end; + + Result := Counter.State; +end; + +procedure TMyc3Tasks.ParallelFor(LowVal, HighVal, Slice: Integer; const Proc: TProc); +begin + if Slice <= 0 then + Slice := max( 1, (HighVal-LowVal+1) div ThreadCount ); + + if HighVal - LowVal >= Slice then + WaitFor( ParallelFor( nil, LowVal, HighVal, Slice, Proc ) ) + else + Proc( LowVal, HighVal ); +end; + +procedure TMyc3Tasks.Run( const StartSignal: IMyc2Signal; const Proc: TProc ); +begin + if not Signals.IsStatic( StartSignal ) then + begin + if FEnabled then + StartSignal.Advise( FApi.Run( 1, Proc ) ) + else + StartSignal.Advise( Signals.CreateSink( True, Proc ) ); + end + else + Run( Proc ); +end; + +procedure TMyc3Tasks.Run( const Proc: TProc ); +begin + if FEnabled then + FApi.Run( 0, Proc ) + else + Proc( ); +end; + +procedure TMyc3Tasks.SetEnabled( const Value: Boolean ); +begin + {$ifndef DisableThreading} + FEnabled := Value; + {$endif} +end; + +function TMyc3Tasks.Start( const StartSignal: IMyc2Signal; const Proc: TFunc ): IMyc2State; +var + Done: IMyc2Condition; + CapturedProc: TFunc; +begin + if not Assigned( Proc ) then + exit( Signals.Null.State ); + + Done := Signals.CreateCounter( 1 ); + + CapturedProc := Proc; + + Run( StartSignal, + procedure + var + Sig: IMyc2Signal; + begin + try + Sig := CapturedProc( ); + finally + CapturedProc := nil; + + if not Signals.IsStatic( Sig ) then + Sig.Advise( Done ) + else + Done.Notify; + end; + end ); + + exit( Done.State ); +end; + +function TMyc3Tasks.Start( const StartSignal: IMyc2Signal; Proc: TProc ): IMyc2State; +var + Done: IMyc2Condition; +begin + Assert( Assigned( Proc ) ); + Done := Signals.CreateCounter( 1 ); + + Run( StartSignal, + procedure + begin + try + Proc; + finally + Done.Notify; + end; + end ); + + exit( Done.State ); +end; + +function TMyc3Tasks.Start( const Proc: TFunc ): IMyc2State; +begin + Result := Start( nil, Proc ); +end; + +function TMyc3Tasks.Start( const Proc: TProc ): IMyc2State; +begin + Result := Start( nil, Proc ); +end; + +function TMyc3Tasks.Start( const StartSignal: IMyc2Signal; P1: T1; P2: T2; Proc: TProc ): IMyc2State; +begin + Result := Start( StartSignal, + procedure + begin + Proc( P1, P2 ); + end ); +end; + +function TMyc3Tasks.Start( const StartSignal: IMyc2Signal; P1: T1; Proc: TProc ): IMyc2State; +begin + Result := Start( StartSignal, + procedure + begin + Proc( P1 ); + end ); +end; + +procedure TMyc3Tasks.WaitFor( const State: IMyc2State ); +begin + FApi.WaitFor( State ); +end; + +{ TValue } + +class constructor TValue.CreateClass; +begin + FDefaultMutable := TMyc3ConstantMutable.Create( Default(T) ); + FDefaultMutableFuture := TMyc3ConstantMutable>.Create( TMyc3ConstantFuture.Create( Default(T) ) ); +end; + +class function TValue.Capture( const Src: TArray ): TArray; +var + i: Integer; +begin + SetLength( Result, Length( Src ) ); + for i := 0 to High( Src ) do + Result[i] := Src[i]; +end; + +class function TValue.CreateFuture( Future: IMyc3Future; Creator: TFunc < T, IMyc3Future < T >> ): IMyc3Future; +begin + Result := TMyc3Future.CreateFuture( Future.Initialized, + function: IMyc3Future + begin + Result := Creator( Future.Value ); + end ); +end; + +class function TValue.CreateFuture( const StartSignal: IMyc2Signal; const Calculator: TFunc < IMyc3Future < T >> ) +: IMyc3Future; +begin + Result := TMyc3Future.CreateFuture( StartSignal, + function: IMyc3Future + begin + Result := Calculator( ); + end ); +end; + +class function TValue.Combine( const Values: TArray>; Combiner: TMyc3Combiner ): IMyc3Future; +begin + if Values=nil then + exit( DefaultFuture ); + + Result := CreateFuture( Values, + function( Arr: TArray ): T + begin + Result := Combiner( Arr ); + end ); +end; + +class function TValue.Combine( Values: TArray; Combiner: TMyc3Combiner ): IMyc3Future; +begin + if Values=nil then + exit( DefaultFuture ); + + Result := TValue.CreateFuture( + function: T + begin + Result := Combiner( Values ); + end ); +end; + +class function TValue.Combine(const Values: TArray>; Combiner: TMyc3Combiner): IMyc3Mutable; +var + Chg: TArray; + i: Integer; +begin + Result := Mutable.Convert, T>( + TValue.CreateMutable(Values), + function( Values: TArray ): T + begin + if not Assigned( Combiner ) then + exit( Default( T ) ); + Result := Combiner( Values ); + end ); +end; + +class function TValue.CreateAnimator( const Target: IMyc3Mutable; const Interpolator: TMyc3TargetInterpolator ) +: IMyc3Animator; +begin + Result := CreateAnimator( Target.Value, Target.Changed, + function( Delta: Double; const StartValue: T; out Value: T ): Boolean + begin + Result := Interpolator( Delta, StartValue, Target.Value, Value ); + if not Result then + Value := Target.Value; + end ); +end; + +class function TValue.CreateAnimator( const Init: T; const ChangeSignal: IMyc2Signal; +const Interpolator: TMyc3Interpolator ): IMyc3Animator; +begin + Result := TMyc3Animator.Create( Init, ChangeSignal, Interpolator ); +end; + +class function TValue.AsFuture( const Value: T ): IMyc3Future; +begin + Result := TMyc3ConstantFuture.Create( Value ); +end; + +class function TValue.CreateFuture( const StartSignal: IMyc2Signal; const Calculator: TFunc ): IMyc3Future; +begin + Result := TMyc3Future.CreateFuture( StartSignal, Calculator ); +end; + +class function TValue.CreateFuture( const Calculator: TFunc ): IMyc3Future; +begin + Result := TMyc3Future.CreateFuture( Calculator ); +end; + +class function TValue.CreateFuture( const Params: TArray>; Calculator: TFunc, T> ): IMyc3Future; +var + Counter: IMyc2Condition; + i: Integer; + Arr: TArray>; +begin + Counter := Signals.CreateCounter( Length( Params ) ); + SetLength( Arr, Length( Params ) ); + + for i := 0 to High( Params ) do + begin + if Params[i]<>nil then + begin + Arr[i] := Params[i]; + Arr[i].Initialized.Advise( Counter ); + end + else + Counter.Notify; + end; + + Result := TMyc3Future.CreateFuture( Counter.State, + function: T + var + Values: TArray; + i: Integer; + begin + SetLength( Values, Length( Arr ) ); + for i := 0 to High( Values ) do + if Arr[i]<>nil then + Values[i] := Arr[i].Value; + + Result := Calculator( Values ); + end ); +end; + +class function TValue.CreateFuture( P1: IMyc3Future; P2: IMyc3Future; Calculator: TFunc ): IMyc3Future; +var + Counter: IMyc2Condition; +begin + Assert( Assigned( P1 ) and Assigned( P2 ) ); + Counter := Signals.CreateCounter( 2 ); + + P1.Initialized.Advise( Counter ); + P2.Initialized.Advise( Counter ); + + Result := CreateFuture( Counter.State, + function: T + begin + Result := Calculator( P1.Value, P2.Value ); + end ); +end; + +class function TValue.CreateFuture( P1: IMyc3Future; P2: IMyc3Future; Calculator: TFunc < U, V, IMyc3Future < T >> + ): IMyc3Future; +var + Counter: IMyc2Condition; +begin + Assert( Assigned( P1 ) and Assigned( P2 ) ); + Counter := Signals.CreateCounter( 2 ); + + P1.Initialized.Advise( Counter ); + P2.Initialized.Advise( Counter ); + + Result := CreateFuture( Counter.State, + function: IMyc3Future + begin + Result := Calculator( P1.Value, P2.Value ); + end ); +end; + +class function TValue.CreateFuture( P: IMyc3Future; Calculator: TFunc ): IMyc3Future; +begin + Assert( Assigned( P ) ); + Result := CreateFuture( P.Initialized, + function: T + begin + Result := Calculator( P.Value ); + end ); +end; + +class function TValue.CreateFuture( P: IMyc3Future; Calculator: TFunc < U, IMyc3Future < T >> ): IMyc3Future; +begin + Assert( Assigned( P ) ); + Result := CreateFuture( P.Initialized, + function: IMyc3Future + begin + Result := Calculator( P.Value ); + end ); +end; + +class function TValue.AsImmutable( const Value: T ): IMyc3Value; +begin + Result := TMyc3Value.Create( Value ); +end; + +class function TValue.CreateLazy(const Func: TFunc): IMyc3Value; +begin + Result := TMyc3Lazy.Create( Func ); +end; + +class function TValue.CreateFuture( const Values: TArray < IMyc3Future < T >> ): IMyc3Future>; +begin + Result := TMyc3FutureArrayEx.Create( Values ); +end; + +class function TValue.CreateFuture( const Values: TArray>; const Calculator: TFunc ): + IMyc3Future>; +begin + Result := TMyc3FutureArrayConvert.Create( Values, Calculator ); +end; + +class function TValue.CreateFuture( P1: IMyc3Future; P2: IMyc3Future; P3: IMyc3Future; +Calculator: TFunc ): IMyc3Future; +var + Counter: IMyc2Condition; +begin + Assert( Assigned( P1 ) and Assigned( P2 ) and Assigned( P3 ) ); + Counter := Signals.CreateCounter( 3 ); + + P1.Initialized.Advise( Counter ); + P2.Initialized.Advise( Counter ); + P3.Initialized.Advise( Counter ); + + Result := CreateFuture( Counter.State, + function: T + begin + Result := Calculator( P1.Value, P2.Value, P3.Value ); + end ); +end; + +class function TValue.CreateFutures( Count: Integer; const Src: IMyc3Future < TArray < T >> ): TArray>; +begin + Result := TMyc3FutureArray.CreateFutures( Count, Src ); +end; + +class function TValue.CreateMutable( const Value: T ): IMyc3Mutable; +begin + Result := AsConst( Value ); +end; + +class function TValue.CreateMutable( const Changed: array of IMyc2Signal; const Calculator: TFunc ): IMyc3Mutable; +begin + Result := TMyc3Mutable.CreateMutable( Changed, Calculator ); +end; + +class function TValue.AsMutable( const Value: IMyc3Mutable> ): IMyc3Mutable; +begin + Result := TMyc3EnclosedMutable.Create( Value ); +end; + +class function TValue.AsMutable( const Value: IMyc3Mutable> ): IMyc3Mutable; +begin + Result := TValue.CreateMutable>( + Value, + function( Immutable: IMyc3Value ): T + begin + if Immutable<>nil then + Result := Immutable.Value + else + Result := default ( T ); + end ); +end; + +class function TValue.AsConst( const Value: T ): IMyc3Mutable; +begin + Result := TMyc3ConstantMutable.Create( Value ); +end; + +class function TValue.AsMutable(const Value: IMyc3Future): IMyc3Mutable; +begin + Result := TMyc3FutureAsMutable.CreateFutureAsMutable( Value ) +end; + +class function TValue.AsMutable(const Value: IMyc3Mutable>): IMyc3Mutable; +begin + Result := TMyc3EnclosedMutable.Create( + TMyc3Mutable>.CreateMutable( + Value.Changed, + function: IMyc3Mutable + begin + Result := AsMutable( Value.Value ); + end ) ); +end; + +class function TValue.Convert( P: IMyc3Future; const Converter: TFunc ): IMyc3Future; +begin + if P.Initialized.IsSet then + Result := AsFuture( Converter( P.Value ) ) + else + Result := CreateFuture( P, Converter ); +end; + +class function TValue.CreateGeneric(const Calculator: TFunc): IMyc3Generic; +begin + Result := TMyc3Changeable.CreateGeneric( Calculator ); +end; + +class function TValue.CreateFutureArray( const StartSignal: IMyc2Signal; Count: Integer; const Calculator: TFunc ): + IMyc3Future>; +var + Init: IMyc2State; + Arr: TArray; + Calc: TFunc; +begin + SetLength( Arr, Count ); + + Calc := Calculator; + Init := Tasks.ParallelFor( + StartSignal, + 0, Count-1, 1, + procedure( A, B: Integer ) + var + i: Integer; + begin + for i := A to B do + Calc( i ) + end ); + + Result := TMyc3Future>.CreateFuture( Init, Arr ); +end; + +class function TValue.CreateFutureArray( Count: Integer; const Calculator: TFunc ): IMyc3Future>; +begin + Result := CreateFutureArray( Signals.Null.State, Count, Calculator ); +end; + +class function TValue.CreateValidate( const Calculator: TFunc ): IMyc3ValidateMutable; +begin + Result := TMyc3ValidateMutable.Create( Calculator ); +end; + +class function TValue.CreateMutable( const Arr: TArray < IMyc3Mutable < T >> ): IMyc3Mutable>; +begin + Result := TMyc3Mutable.CreateArray( Arr ); +end; + +class function TValue.CreateMutable( Calculator: TFunc ): IMyc3Mutable; +begin + Result := CreateMutable( [], Calculator ); +end; + +class function TValue.CreateMutable( const Event: IMycEventObserver; const Calculator: TFunc ): IMyc3Mutable; +begin + Result := TMyc3Mutable.CreateMutable( Event, Calculator ); +end; + +class function TValue.CreateMutable( const Changed: array of IMyc2Signal; const Mutable: IMyc3Mutable ): IMyc3Mutable; +var + Notifier: IMyc2Notifier; + i: Integer; +begin + Notifier := Signals.CreateNotifier; + Mutable.Changed.Advise( Notifier ); + for i := 0 to High( Changed ) do + Changed[i].Advise( Notifier ); + + Result := TMyc3Mutable.CreateMutable( + Notifier.Signal, + function: T + begin + Result := Mutable.Value; + end ); +end; + +class function TValue.CreateMutable(const Changed: array of IMyc2Signal; const Calculator: TFunc>): IMyc3Mutable; +begin + Result := TMyc3EnclosedMutable.Create( TMyc3Mutable>.CreateMutable( Changed, Calculator ) ); +end; + +class function TValue.CreateMutable( P1: IMyc3Mutable; P2: IMyc3Mutable; P3: IMyc3Mutable; +Calculator: TFunc ): IMyc3Mutable; +begin + Assert( Assigned( P1 ) and Assigned( P2 ) and Assigned( P3 ) ); + + if not Signals.IsStatic(P1.Changed) or not Signals.IsStatic(P2.Changed) or not Signals.IsStatic(P3.Changed) then + begin + Result := TMyc3Mutable.CreateMutable( + [P1.Changed, P2.Changed, P3.Changed], + function: T + begin + Result := Calculator( P1.Value, P2.Value, P3.Value ); + end ); + end + else + Result := TMyc3ConstantMutable.Create( Calculator( P1.Value, P2.Value, P3.Value ) ); +end; + +class function TValue.CreateMutable( P1: IMyc3Mutable; P2: IMyc3Mutable; P3: IMyc3Mutable; +P4: IMyc3Mutable; Calculator: TFunc ): IMyc3Mutable; +begin + Assert( Assigned( P1 ) and Assigned( P2 ) and Assigned( P3 ) and Assigned( P4 ) ); + + if not Signals.IsStatic(P1.Changed) or not Signals.IsStatic(P2.Changed) or not Signals.IsStatic(P3.Changed) or not Signals.IsStatic(P4.Changed) then + begin + Result := TMyc3Mutable.CreateMutable( + [P1.Changed, P2.Changed, P3.Changed, P4.Changed], + function: T + begin + Result := Calculator( P1.Value, P2.Value, P3.Value, P4.Value ); + end ); + end + else + Result := TMyc3ConstantMutable.Create( Calculator( P1.Value, P2.Value, P3.Value, P4.Value ) ); +end; + +class function TValue.CreateMutableEx( Params: TArray>; Calculator: TFunc, T> ): IMyc3Mutable; +var + i: Integer; + Sigs: TArray; +begin + case Length( Params ) of + 0: + Result := CreateMutable( + function: T + begin + Result := Calculator( [] ); + end ); + 1: + Result := CreateMutable( Params[0], + function( P: S ): T + begin + Result := Calculator( [P] ); + end ); + 2: + Result := CreateMutable( Params[0], Params[1], + function( P1, P2: S ): T + begin + Result := Calculator( [P1, P2] ); + end ); + 3: + Result := CreateMutable( Params[0], Params[1], Params[2], + function( P1, P2, P3: S ): T + begin + Result := Calculator( [P1, P2, P3] ); + end ); + 4: + Result := CreateMutable( Params[0], Params[1], Params[2], Params[3], + function( P1, P2, P3, P4: S ): T + begin + Result := Calculator( [P1, P2, P3, P4] ); + end ); + else + begin + SetLength( Sigs, Length( Params ) ); + + for i := 0 to High( Sigs ) do + begin + Assert( Assigned( Params[i] ) ); + Sigs[i] := Params[i].Changed; + end; + + Result := TMyc3Mutable.CreateMutable( + Sigs, + function: T + var + Values: TArray; + Init: IMyc2Signal; + i: Integer; + begin + SetLength( Values, Length( Params ) ); + for i := 0 to High( Values ) do + Values[i] := Params[i].Value; + + Result := Calculator( Values ); + end ); + end; + end; +end; + +class function TValue.CreateMutable( const Arr: TArray>; Calculator: TFunc ): + IMyc3Mutable>; +begin + Result := TMyc3MutableArray.Create( Arr, Calculator ); +end; + +class function TValue.CreateMutable( P1: IMyc3Mutable; P2: IMyc3Mutable; P3: IMyc3Mutable; P4: + IMyc3Mutable; P5: IMyc3Mutable; P6: IMyc3Mutable; Calculator: TFunc ): IMyc3Mutable; +begin + Assert( Assigned( P1 ) and Assigned( P2 ) and Assigned( P3 ) and Assigned( P4 ) and Assigned( P5 ) and Assigned( P6 ) ); + + if not Signals.IsStatic(P1.Changed) or not Signals.IsStatic(P2.Changed) or not Signals.IsStatic(P3.Changed) + or not Signals.IsStatic(P4.Changed) or not Signals.IsStatic(P5.Changed) or not Signals.IsStatic(P6.Changed) then + begin + Result := TMyc3Mutable.CreateMutable( + [P1.Changed, P2.Changed, P3.Changed, P4.Changed, P5.Changed, P6.Changed], + function: T + begin + Result := Calculator( P1.Value, P2.Value, P3.Value, P4.Value, P5.Value, P6.Value ); + end ); + end + else + Result := TMyc3ConstantMutable.Create( Calculator( P1.Value, P2.Value, P3.Value, P4.Value, P5.Value, P6.Value ) ); +end; + +class function TValue.CreateMutable( P1: IMyc3Mutable; P2: IMyc3Mutable; P3: IMyc3Mutable; P4: IMyc3Mutable; +P5: IMyc3Mutable; Calculator: TFunc ): IMyc3Mutable; +begin + Assert( Assigned( P1 ) and Assigned( P2 ) and Assigned( P3 ) and Assigned( P4 ) and Assigned( P5 ) ); + + if not Signals.IsStatic(P1.Changed) or not Signals.IsStatic(P2.Changed) or not Signals.IsStatic(P3.Changed) + or not Signals.IsStatic(P4.Changed) or not Signals.IsStatic(P5.Changed) then + begin + Result := TMyc3Mutable.CreateMutable( + [P1.Changed, P2.Changed, P3.Changed, P4.Changed, P5.Changed], + function: T + begin + Result := Calculator( P1.Value, P2.Value, P3.Value, P4.Value, P5.Value ); + end ); + end + else + Result := TMyc3ConstantMutable.Create( Calculator( P1.Value, P2.Value, P3.Value, P4.Value, P5.Value ) ); +end; + +class function TValue.CreateMutable( P1: IMyc3Mutable; P2: IMyc3Mutable; Calculator: TFunc ) +: IMyc3Mutable; +begin + Assert( Assigned( P1 ) and Assigned( P1 ) ); + + if not Signals.IsStatic(P1.Changed) or not Signals.IsStatic(P2.Changed) then + begin + Result := TMyc3Mutable.CreateMutable( + [P1.Changed, P2.Changed], + function: T + begin + Result := Calculator( P1.Value, P2.Value ); + end ); + end + else + Result := TMyc3ConstantMutable.Create( Calculator( P1.Value, P2.Value ) ); +end; + +class function TValue.CreateMutable( P: IMyc3Mutable; Calculator: TFunc ): IMyc3Mutable; +begin + Assert( Assigned( P ) ); + + if not Signals.IsStatic(P.Changed) then + begin + Result := TMyc3Mutable.CreateMutable( + P.Changed, + function: T + begin + Result := Calculator( P.Value ); + end ); + end + else + Result := TMyc3ConstantMutable.Create( Calculator( P.Value ) ); +end; + +class function TValue.CreateWriteable( const InitValue: T ): IMyc3Writeable; +begin + Result := TMyc3Writeable.CreateWriteable( InitValue ); +end; + +class function TValue.CreatePropertyEx( const Decorated: IMyc3Mutable ): IMyc3Property; +begin + Result := TMycProperty.CreateProperty( Decorated ); +end; + +class function TValue.CreateProperty( const Creator: TFunc < IMyc3Mutable < T >> ): IMyc3MutablePropertyEx; +begin + Result := TMyc3MutablePropertyEx.Create( Creator ); +end; + +class function TValue.CreateMutableProperty(const InitValue: IMyc3Mutable): IMyc3MutableProperty; +begin + Result := TMyc3MutableProperty.Create( InitValue ); +end; + +class function TValue.CreateSet: IMycSet; +begin + Result := TMycSet.Create; +end; + +class function TValue.CreateWriteable( const InitValue: T; const EqualityComparison: TEqualityComparison ) +: IMyc3Writeable; +begin + Result := TMyc3Writeable.CreateWriteable( InitValue, EqualityComparison ); +end; + +class function TValue.CreateWriteable: IMyc3Writeable; +begin + Result := CreateWriteable( Default(T) ); +end; + +class function TValue.Execute( Future: IMyc3Future; Proc: TConstFunc ): IMyc2State; +begin + Result := Tasks.Start( Future.Initialized, + function: IMyc2Signal + begin + Result := Proc( Future.Value ); + end ); +end; + +class function TValue.GetDefaultFuture: IMyc3Future; +begin + Result := TMyc3Future.DefaultFuture; +end; + +class function TValue.MakeValid( const Value: IMyc3Future ): IMyc3Future; +begin + Result := Value; + if Result = nil then + Result := DefaultFuture; +end; + +class function TValue.MakeValid( const Value: IMyc3Mutable ): IMyc3Mutable; +begin + Result := Value; + if Result = nil then + Result := DefaultMutable; +end; + +class function TValue.TryGetValue( const Future: IMyc3Future; out Value: T ): Boolean; +begin + Result := Future <> nil; + if Result then + begin + Result := Future.Initialized = Signals.Null.State; + if Result then + begin + Result := Future.Initialized.IsSet; + if Result then + Value := Future.Value; + end; + end; +end; + +class function TValue.WaitFor( Future: IMyc3Future ): T; +begin + Tasks.WaitFor( Future.Initialized ); + exit( Future.Value ); +end; + +class procedure TValue.WaitFor( const Futures: TArray>; Exec: TProc ); +var + i: Integer; + Arr: TArray>; +begin + SetLength( Arr, Length( Futures ) ); + for i := 0 to High( Futures ) do + if Futures[i].Initialized.IsSet then + Exec( i, Futures[i].Value ) + else + Arr[i] := Futures[i]; + + for i := 0 to High( Arr ) do + if Arr[i] <> nil then + Exec( i, WaitFor( Futures[i] ) ); +end; + +{ TMyc2ComputeProperty } + +constructor TMyc2ComputeProperty.Create( const StartSignal: IMyc2Signal; const Creator: TFunc ); +var + P: ^T; +begin + FValue := default ( T ); + P := @FValue; + FDone := Tasks.Start( StartSignal, + procedure + begin + P^ := Creator( ); + end ); +end; + +procedure TMyc2ComputeProperty.Terminate; +begin + if FDone <> Signals.Null.State then + begin + Tasks.WaitFor( FDone ); + FDone := Signals.Null.State; + end; +end; + +function TMyc2ComputeProperty.Value: T; +begin + Terminate; + Result := FValue; +end; + +{ Signals } + +class function Signals.CreateCounter( Count: Integer ): IMyc2Condition; +begin + Result := TMyc2Signal.CreateCounter( Count ) +end; + +class function Signals.CreateEvent( Init: Boolean = false ): IMyc2Event; +begin + Result := TMyc2Signal.CreateEvent( Init ); +end; + +class function Signals.CreateNotifier: IMyc2Notifier; +begin + Result := TMyc2Signal.CreateNotifier; +end; + +class function Signals.CreateSink(Once: Boolean; const Proc: TProc): IMyc2Sink; +begin + Result := TMyc2SinkProc.CreateSink( Once, Proc ); +end; + +class function Signals.Concat( const States: array of IMyc2State ): IMyc2State; +begin + Result := TMyc2Signal.CreateConcat( States ); +end; + +class function Signals.CreateObserver( const Signals: array of IMyc2Signal; const Sink: T ): IMycObserver; +begin + Result := TMycObserver.CreateObserver( Signals, Sink ); +end; + +class function Signals.CreateEventObserver( const Signals: array of IMyc2Signal ): IMycEventObserver; +begin + Result := CreateObserver( Signals, CreateEvent( True ) ); +end; + +class function Signals.CreateNotifyObserver( const Signals: array of IMyc2Signal ): IMycNotifyObserver; +begin + Result := CreateObserver( Signals, CreateNotifier ); +end; + +class function Signals.CreateNotifyObserver( const Signals: IMycCountable ): IMycNotifyObserver; +var + Arr: TArray; + i: Integer; +begin + SetLength( Arr, Signals.Count ); + for i := 0 to High( Arr ) do + Arr[i] := Signals[i]; + + Result := CreateNotifyObserver( Arr ); +end; + +class function Signals.CreateEventObserver( const Signals: IMycCountable ): IMycEventObserver; +var + Arr: TArray; + i: Integer; +begin + SetLength( Arr, Signals.Count ); + for i := 0 to High( Arr ) do + Arr[i] := Signals[i]; + + Result := CreateEventObserver( Arr ); +end; + +class function Signals.CreateFlag(Init: Boolean): IMyc2Flag; +begin + Result := TMyc2Flag.Create( Init ); +end; + +class function Signals.CreateSink(const Proc: TProc): IMyc2Sink; +begin + Result := TMyc2SinkProc.CreateSink( false, Proc ); +end; + +class function Signals.GetNull: IMyc2Condition; +begin + exit( TMyc2Signal.Null ); +end; + +class function Signals.IsStatic( const Signal: IMyc2Signal ): Boolean; +begin + Result := ( Signal = nil ) or ( Signal = Null.State ); +end; + +class function Signals.MakeValid( const Signal: IMyc2Signal ): IMyc2Signal; +begin + if IsStatic( Signal ) then + exit( Null.State ) + else + exit( Signal ); +end; + +class function Signals.MakeValid( const Signals: array of IMyc2Signal ): TArray; +begin + Result := TMyc2Signal.MakeValid( Signals ); +end; + +class function Signals.Union( const Sigs: array of IMyc2Signal ): IMyc2Signal; +begin + Result := TMyc2Signal.CreateUnion( Sigs ); +end; + +{ TState } + +class function TAtomicStack.Create: IAtomicStack; +begin + Result := TMycAtomicStack.Create; +end; + +{ TProtected } + +constructor TProtected.Create( const AItem: T ); +begin + FItem := AItem; +end; + +function TProtected.Exchange( const Value: T ): T; +begin + if Value <> nil then + Value._AddRef; + + PPointer( @Result )^ := TInterlocked.Exchange( PPointer( @FItem )^, PPointer( @Value )^ ); +end; + +class operator TProtected.Implicit( const Value: T ): TProtected; +begin + Result.Create( Value ); +end; + +{ Mutable } + +class function Mutable.ConvertSet(const Arr: IMyc3Mutable>>; const Converter: TFunc): IMyc3Mutable< + TArray< IMyc3Mutable>>; +begin + Result := TValue>>.CreateMutable>>( + Arr, + function( Arr: TArray> ): TArray> + var + i: Integer; + begin + SetLength( Result, Length(Arr) ); + for i := 0 to High(Result) do + begin + Assert( Arr[i] <> nil ); + Result[i] := TValue.CreateMutable( Arr[i], Converter ); + end; + end ); +end; + +class function Mutable.ConvertSet(const Arr: IMyc3Mutable>>>; const Converter: TFunc): + IMyc3Mutable>>>; +begin + Result := ConvertSet, IMyc3Future>( + Arr, + function( Item: IMyc3Future ): IMyc3Future + begin + Assert( Item <> nil ); + Result := TValue.CreateFuture( Item, Converter ); + end ); +end; + +class function Mutable.Create( Value: Boolean ): IMyc3Mutable; +begin + Result := TMycMutableBoolean.CreateBoolean( Value ); +end; + +class function Mutable.CreateAssembly(const Arr: IMyc3Mutable>>): IMyc3Mutable>; +begin + Result := + Mutable.From>, TArray>( + Arr, + function( Arr: TArray> ): IMyc3Mutable> + begin + Result := TValue.CreateMutable( Arr ); + end ); +end; + +class function Mutable.CreateAssembly(const Arr: IMyc3Mutable>>>): IMyc3Mutable>>; +begin + Result := + Mutable.From>>, IMyc3Future>>( + Arr, + function( Arr: TArray>> ): IMyc3Mutable>> + begin + Result := TMutableFuture.FromArray( Arr ); + end ); +end; + +class function Mutable.CreateAssembly(const Arr: IMyc3Mutable>>; const Creator: TFunc, T>): IMyc3Mutable; +begin + Result := TValue.AsMutable( + TValue>.CreateMutable>>( + Arr, + function( Arr: TArray> ): IMyc3Mutable + begin + Result := TValue.CreateMutable>( TValue.CreateMutable( Arr ), Creator ) ; + end ) ); +end; + +class function Mutable.CreateAssembly( + const Arr: IMyc3Mutable>>>; + const Creator: TFunc, S>): IMyc3Mutable>; +begin + Result := CreateAssembly, IMyc3Future>( Arr, + function( Arr: TArray> ): IMyc3Future + begin + Result := TValue.CreateFuture>( TValue.CreateFuture( Arr ), Creator ); + end ); +end; + +class function Mutable.CreateAssembly(const Arr: IMyc3Mutable>>; const Creator: TFunc, S>): + IMyc3Mutable; +begin + Result := TValue.AsMutable( + TValue>.CreateMutable>>( + Arr, + function( Arr: TArray> ): IMyc3Mutable + begin + Result := TValue.CreateMutable>( TValue.CreateMutable( Arr ), Creator ) ; + end ) ); +end; + +class function Mutable.CreateAssembly(const Arr: IMyc3Mutable>>>; const Creator: TFunc, T>) + : IMyc3Mutable>; +begin + Result := CreateAssembly>( Arr, + function( Arr: TArray> ): IMyc3Future + begin + Result := TValue.CreateFuture>( TValue.CreateFuture( Arr ), Creator ); + end ); +end; + +class function Mutable.FromSignal(const Signal: IMyc2Signal): IMyc3Mutable; +begin + Result := TMycMutableSignal.Create( Signal ); +end; + +class function Mutable.CreateOverride(const Value, Ovrride: IMyc3Mutable>): IMyc3Mutable>; +begin + Result := TValue>.CreateMutable, TImmutable>( + Value, Ovrride, + function( Value, Ovrride: TImmutable ): TImmutable + begin + if not Ovrride.HasValue then + Result := Value + else + Result := Ovrride; + end ); +end; + +class function Mutable.CreateCache( const Comparer: IComparer = nil ): TMutableCache; +begin + Result.Create( TMycMutableTable.Create( Comparer ) ); +end; + +class function Mutable.CreateNotifyObserver(const Mutables: TArray>): IMycNotifyObserver; +var + Sigs: TArray; + i: Integer; +begin + SetLength( Sigs, Length(Mutables) ); + for i := 0 to High(Sigs) do + Sigs[i] := Mutables[i].Changed; + Result := Signals.CreateNotifyObserver(Sigs); +end; + +class function Mutable.CreateEventObserver(const Mutables: TArray>): IMycEventObserver; +var + Sigs: TArray; + i: Integer; +begin + SetLength( Sigs, Length(Mutables) ); + for i := 0 to High(Sigs) do + Sigs[i] := Mutables[i].Changed; + Result := Signals.CreateEventObserver(Sigs); +end; + +class function Mutable.CreateValidator( const Comparer: IComparer = nil ): IMyc3MutableCache; +begin + Result := TMycMutableCache.CreateCache( Comparer ); +end; + +class function Mutable.ExtractSignals(const Mutables: TArray>): IMyc2Signal; +var + i: Integer; + Arr: TArray; +begin + SetLength( Arr, Length(Mutables) ); + for i := 0 to High(Arr) do + Arr[i] := Mutables[i].Changed; + Result := Signals.Union( Arr ); +end; + +class function Mutable.False: IMyc3Mutable; +begin + Result := TMycMutableBoolean.CreateBoolean( System.False ); +end; + +class function Mutable.From(const Value: IMyc3Mutable; const Conv: TFunc>): IMyc3Mutable; +begin + Result := TMyc3EnclosedMutable.Create( + TValue>.CreateMutable( Value, Conv ) ); +end; + +class function Mutable.Convert(const Value: IMyc3Mutable; const Conv: TFunc): IMyc3Mutable; +begin + Result := TValue.CreateMutable( Value, Conv ); +end; + +class function Mutable.Convert(const Value: IMyc3Mutable>; const Conv: TFunc): IMyc3Mutable>; +begin + Result := Convert, IMyc3Future>( + Value, + function( Value: IMyc3Future ): IMyc3Future + begin + Result := TFuture.From( Value, Conv ) + end ); +end; + +class function Mutable.Convert(const Value: IMyc3Mutable>; const Conv: TFunc>): IMyc3Mutable>; +begin + Result := Convert, IMyc3Future>( + Value, + function( Value: IMyc3Future ): IMyc3Future + begin + Result := TFuture.From( Value, Conv ) + end ); +end; + +class function Mutable.CreateNullable: IMyc3Writeable>; +begin + Result := TValue>.CreateWriteable( TImmutable.Null ); +end; + +class function Mutable.CreateObjectLink(Obj: T): IMyc3ObjectLink; +begin + Result := TMyc3ObjectLink.Create( Obj ); +end; + +class function Mutable.TrimArray(const Values: IMyc3Mutable>): IMyc3Mutable>; +begin + Result := TValue>.CreateMutable>( + Values, + function( Values: TArray ): TArray + var + i, n: Integer; + begin + n := 0; + for i := 0 to High(Values) do + if Assigned(Values[i]) then + inc(n); + + SetLength( Result, n ); + n := 0; + for i := 0 to High(Values) do + begin + Result[n] := Values[i]; + inc(n); + end; + end ); +end; + +class function Mutable.FilterArray(const Values: IMyc3Mutable>; const FilterFunc: TConstFunc>): IMyc3Mutable>; +var + Sieve: IMyc3Mutable>>; +begin + Sieve := TValue>>.CreateMutable>( + Values, + function( Values: TArray ): TArray> + var + i: Integer; + begin + SetLength( Result, Length( Values ) ); + for i := 0 to High(Values) do + Result[i] := FilterFunc( Values[i] ); + end ); + + Result := TValue>.CreateMutable, TArray>( + Mutable.CreateAssembly( Sieve ), + Values, + function( Sieve: TArray; GeoArr: TArray ): TArray + var + i, n: Integer; + begin + n := 0; + for i := 0 to High(GeoArr) do + if Sieve[i] then + inc( n ); + SetLength( Result, n ); + n := 0; + for i := 0 to High(GeoArr) do + if Sieve[i] then + begin + Result[n] := GeoArr[i]; + inc(n); + end; + end ); +end; + +class function Mutable.From(const ValueR: IMyc3Mutable; const ValueS: IMyc3Mutable; const Conv: TFunc>): IMyc3Mutable; +begin + Result := TMyc3EnclosedMutable.Create( + TValue>.CreateMutable( ValueR, ValueS, Conv ) ); +end; + +class function Mutable.From(const Value: T): IMyc3Mutable; +begin + Result := TMyc3ConstantMutable.Create( Value ); +end; + +class function Mutable.FromArray(const Value: IMyc3Mutable>; const Conv: TFunc): IMyc3Mutable>; +begin + Result := + Convert, TArray>( Value, + function( Arr: TArray ): TArray + var + i: Integer; + begin + SetLength( Result, Length(Arr) ); + for i := 0 to High(Arr) do + Result[i] := Conv( Arr[i] ); + end ); +end; + +class function Mutable.FromArray(const Value: IMyc3Mutable>; const Conv: TFunc>): IMyc3Mutable>; +begin + Result := CreateAssembly( FromArray>( Value, Conv ) ); +end; + +class function Mutable.FromArray(const Value: TArray>): IMyc3Mutable>; +begin + Result := TMyc3MutableArray.Create( + Value, + function( Idx: Integer; Val: T ): T + begin + Result := Val; + end ); +end; + +class function Mutable.FromState(const State: IMyc2State): IMyc3Mutable; +begin + Result := TMycMutableState.Create( State ); +end; + +class function Mutable.IfNotThen(const Condition: IMyc3Mutable; const FalseValue: IMyc3Mutable): IMyc3Mutable; +begin + Assert( Assigned( Condition ) and Assigned( FalseValue ) ); + + if Signals.IsStatic(Condition.Changed) then + begin + if Condition.Value then + exit( TValue.DefaultMutable ) + else + exit( FalseValue ); + end; + + Result := TMyc3Mutable.CreateMutable( + [Condition.Changed, FalseValue.Changed], + function: T + begin + if Condition.Value then + Result := System.Default( T ) + else + Result := FalseValue.Value; + end ); +end; + +class function Mutable.IfThen(const Condition: IMyc3Mutable; const TrueValue: IMyc3Mutable): IMyc3Mutable; +begin + Assert( Assigned( Condition ) and Assigned( TrueValue ) ); + + if Signals.IsStatic(Condition.Changed) then + begin + if Condition.Value then + exit( TrueValue ) + else + exit( TValue.DefaultMutable ); + end; + + Result := TMyc3Mutable.CreateMutable( + [Condition.Changed, TrueValue.Changed], + function: T + begin + if Condition.Value then + Result := TrueValue.Value + else + Result := System.Default( T ); + end ); +end; + +class function Mutable.IfThen(const Condition: IMyc3Mutable; const TrueValue: IMyc3Mutable>): IMyc3Mutable< + IMyc3Future>; +begin + Result := IfThenElse>( Condition, TrueValue, TValue.DefaultMutableFuture ); +end; + +class function Mutable.IfThenElse(const Condition: IMyc3Mutable; const TrueValue, FalseValue: IMyc3Mutable): IMyc3Mutable; +begin + Assert( Assigned( Condition ) and Assigned( TrueValue ) and Assigned( FalseValue ) ); + + Result := TMyc3Mutable.CreateMutable( + [Condition.Changed, TrueValue.Changed, FalseValue.Changed], + function: T + begin + if Condition.Value then + Result := TrueValue.Value + else + Result := FalseValue.Value; + end ); +end; + +class function Mutable.Merge(const Parts: IMyc3Mutable>>): IMyc3Mutable>; +begin + Result := TValue>.CreateMutable>>( + Parts, + function( Parts: TArray> ): TArray + begin + Result := TFuture.Merge( Parts ); + end ); +end; + +class function Mutable.Merge(const Parts: IMyc3Mutable>>>): IMyc3Mutable>>; +begin + Result := TValue>>.CreateMutable>>>( + Parts, + function( Parts: TArray>> ): IMyc3Future> + begin + Result := TFuture.Merge( Parts ); + end ); +end; + +class function Mutable.OpAND(const Arr: TArray>): IMyc3Mutable; +begin + if Length(Arr)=2 then + Result := TValue.CreateMutable( + Arr[0], Arr[1], + function( A, B: Boolean ): Boolean + begin + Result := A and B; + end ) + else if Length(Arr)=1 then + Result := Arr[0] + else + Result := TValue.CreateMutable>( + TValue.CreateMutable(Arr), + function( Arr: TArray ): Boolean + var + i: Integer; + begin + for i := 0 to High(Arr) do + if not Arr[i] then + exit( System.False ); + exit( System.true ); + end ); +end; + +class function Mutable.OpNOT(const A: IMyc3Mutable): IMyc3Mutable; +begin + Result := TValue.CreateMutable( + A, + function( A: Boolean ): Boolean + begin + Result := not A; + end ); +end; + +class function Mutable.OpNOR(const Arr: TArray>): IMyc3Mutable; +begin + if Length(Arr)=2 then + Result := TValue.CreateMutable( + Arr[0], Arr[1], + function( A, B: Boolean ): Boolean + begin + Result := not (A or B); + end ) + else if Length(Arr)=1 then + Result := OpNOT( Arr[0] ) + else + Result := TValue.CreateMutable>( + TValue.CreateMutable(Arr), + function( Arr: TArray ): Boolean + var + i: Integer; + begin + for i := 0 to High(Arr) do + if not Arr[i] then + exit( System.true ); + exit( System.False ); + end ); +end; + +class function Mutable.OpNAND(const Arr: TArray>): IMyc3Mutable; +begin + if Length(Arr)=2 then + Result := TValue.CreateMutable( + Arr[0], Arr[1], + function( A, B: Boolean ): Boolean + begin + Result := not (A and B); + end ) + else if Length(Arr)=1 then + Result := OpNOT( Arr[0] ) + else + Result := TValue.CreateMutable>( + TValue.CreateMutable(Arr), + function( Arr: TArray ): Boolean + var + i: Integer; + begin + for i := 0 to High(Arr) do + if not Arr[i] then + exit( System.true ); + exit( System.False ); + end ); +end; + +class function Mutable.OpOR(const Arr: TArray>): IMyc3Mutable; +begin + if Length(Arr)=2 then + Result := TValue.CreateMutable( + Arr[0], Arr[1], + function( A, B: Boolean ): Boolean + begin + Result := A or B; + end ) + else if Length(Arr)=1 then + Result := Arr[0] + else + Result := TValue.CreateMutable>( + TValue.CreateMutable(Arr), + function( Arr: TArray ): Boolean + var + i: Integer; + begin + for i := 0 to High(Arr) do + if Arr[i] then + exit( System.true ); + exit( System.False ); + end ); +end; + +class function Mutable.SortArray(const Values: IMyc3Mutable>; const Comparer: IComparer): IMyc3Mutable>; +var + Cmp: IComparer; +begin + Cmp := Comparer; + if not Assigned(Cmp) then + Cmp := TComparer.Default; + + Result := TValue>.CreateMutable>( + Values, + function( Values: TArray ): TArray + begin + SetLength( Result, Length(Values) ); + TArray.Copy( Values, Result, Length(Values) ); + TArray.Sort( Result, Cmp ); + end ); +end; + +class function Mutable.True: IMyc3Mutable; +begin + Result := TMycMutableBoolean.CreateBoolean( System.True ); +end; + +class function Mutable.GetComparer: IComparer>; +begin + Result := TMyc3Mutable.Comparer; +end; + +class function Mutable.CreateArray( const Arr: TArray> ): IMyc3Mutable>; +begin + Result := TMyc3Mutable.CreateArray( Arr ); +end; + +class function Mutable.CreateArray( const Arr: IMyc3Mutable>; Calculator: TFunc ): IMyc3Mutable>; +begin + Result := TMyc3Mutable>.CreateMutable( + [Arr.Changed], + function: TArray + var + i: Integer; + begin + SetLength( Result, Length( Arr.Value ) ); + for i := 0 to High( Arr.Value ) do + Result[i] := Calculator( Arr.Value[i] ); + end ); +end; + +class function Mutable.Default: IMyc3Mutable; +begin + Result := TValue.DefaultMutable; +end; + +class function Mutable.IfThen( const Condition: IMyc3Mutable; const TrueValue: IMyc3Mutable ): IMyc3Mutable; +begin + Assert( Assigned( Condition ) and Assigned( TrueValue ) ); + + if Condition = Mutable.True then + exit( TrueValue ) + else if Condition = Mutable.False then + exit( default ); + + Result := TMyc3Mutable.CreateMutable( + [Condition.Changed, TrueValue.Changed], + function: T + begin + if Condition.Value then + Result := TrueValue.Value + else + Result := System.Default( T ); + end ); +end; + +class function Mutable.IfNotThen( const Condition: IMyc3Mutable; const FalseValue: IMyc3Mutable ): IMyc3Mutable; +begin + Assert( Assigned( Condition ) and Assigned( FalseValue ) ); + + if Condition = Mutable.True then + exit( default ) + else if Condition = Mutable.False then + exit( FalseValue ); + + Result := TMyc3Mutable.CreateMutable( + [Condition.Changed, FalseValue.Changed], + function: T + begin + if Condition.Value then + Result := System.Default( T ) + else + Result := FalseValue.Value; + end ); +end; + +class function Mutable.IfThenElse( const Condition: IMyc3Mutable; const TrueValue, FalseValue: IMyc3Mutable ): IMyc3Mutable; +begin + Assert( Assigned( Condition ) and Assigned( TrueValue ) and Assigned( FalseValue ) ); + + if Condition = Mutable.True then + exit( TrueValue ); + if Condition = Mutable.False then + exit( FalseValue ); + if TrueValue = default then + exit( IfNotThen( Condition, FalseValue ) ); + if FalseValue = default then + exit( IfThen( Condition, TrueValue ) ); + + Result := TMyc3Mutable.CreateMutable( + [Condition.Changed, TrueValue.Changed, FalseValue.Changed], + function: T + begin + if Condition.Value then + Result := TrueValue.Value + else + Result := FalseValue.Value; + end ); +end; + +{ TMutableBool } + +constructor TMutableBool.Create(const AThis: IMyc3Mutable); +begin + FThis := AThis; +end; + +class function TMutableBool.GetDefaults(Value: Boolean): IMyc3Mutable; +begin + Result := TMycMutableBoolean.CreateBoolean( Value ); +end; + +class function TMutableBool.True: IMyc3Mutable; +begin + Result := TMycMutableBoolean.CreateBoolean( System.true ); +end; + +class function TMutableBool.False: IMyc3Mutable; +begin + Result := TMycMutableBoolean.CreateBoolean( System.false ); +end; + +class operator TMutableBool.Equal(const A, B: TMutableBool): TMutableBool; +begin + Result := TValue.CreateMutable( + A, B, + function( A, B: Boolean ): Boolean + begin + Result := A = B; + end ); +end; + +class operator TMutableBool.Implicit(const Value: IMyc3Mutable): TMutableBool; +begin + Result.Create( Value ); +end; + +class operator TMutableBool.Implicit(const Value: TMutableBool): IMyc3Mutable; +begin + Result := Value.This; +end; + +class operator TMutableBool.LogicalAnd(const A, B: TMutableBool): TMutableBool; +begin + Result := TValue.CreateMutable( + A, B, + function( A, B: Boolean ): Boolean + begin + Result := A and B; + end ); +end; + +class operator TMutableBool.LogicalNot(const Value: TMutableBool): TMutableBool; +begin + Result := TValue.CreateMutable( + Value, + function( Value: Boolean ): Boolean + begin + Result := not Value; + end ); +end; + +class operator TMutableBool.LogicalOr(const A, B: TMutableBool): TMutableBool; +begin + Result := TValue.CreateMutable( + A, B, + function( A, B: Boolean ): Boolean + begin + Result := A or B; + end ); +end; + +class operator TMutableBool.NotEqual(const A, B: TMutableBool): TMutableBool; +begin + Result := TValue.CreateMutable( + A, B, + function( A, B: Boolean ): Boolean + begin + Result := A <> B; + end ); +end; + +{ TAssembly } + +class function TAssembly.Create: IMycAssembly; +begin + Result := TMycAssembly.Create; +end; + +{ TMycArray } + +class function TMycArray.Create( const Arr: array of T ): IMycArray; +begin + Result := TMycCustomArray.CreateArray( Arr ); +end; + +class function TMycArray.Create( Count: Integer; const Getter: TFunc ): IMycArray; +begin + Result := TMycGenericArray.Create( Count, Getter ); +end; + +class function TMycArray.Create( const Src: IMycArray; const Converter: TConstFunc ): IMycArray; +begin + Result := TMycCustomArray.CreateConvert( Src, Converter ); +end; + +class function TMycArray.Create(const Src: IMyc2Enumerable): IMycArray; +var + n: Integer; + E: IMycEnumerator; + Vals: TArray; +begin + E := Src.GetEnumerator; + + n := 0; + while E.MoveNext do + inc( n ); + + SetLength( Vals, n ); + + E.Reset; + n := 0; + while E.MoveNext do + begin + Vals[n] := E.Current; + inc(n); + end; + + Result := TMycCustomArray.CreateArray( Vals ); +end; + +class function TMycArray.CreateConvert( const Enum: IMyc2Enumerable; const Conv: TConstFunc ): IMyc2Enumerable; +begin + Result := TMyc2ConvertEnumerable.Create( Enum, Conv ); +end; + +class function TMycArray.Divide( const Src: TArray; const Comparer: IComparer ): TArray>; +var + i: Integer; + Arr: TArray>; +begin + Arr := Create( Src ).Divide( Comparer ); + + SetLength( Result, Length( Arr ) ); + for i := 0 to High( Result ) do + Result[i] := Arr[i].ToArray; +end; + +class procedure TMycArray.Divide1( const Src: IMycArray>; out Keys: TArray; out Values: + TArray>; const Comparer: IComparer ); +var + Arr: TArray>>; + i: Integer; +begin + Arr := Src.Divide( + TComparer>.Construct( + function( const A, B: TPair ): Integer + begin + Result := Comparer.Compare( A.Key, B.Key ); + end ) ); + + SetLength( Keys, Length( Arr ) ); + SetLength( Values, Length( Arr ) ); + for i := 0 to High( Arr ) do + begin + Keys[i] := Arr[i][0].Key; + + Values[i] := TMycCustomArray.CreateConvert>( + Arr[i], + function( const P: TPair ): TValue + begin + Result := P.Value; + end ); + end; +end; + +class function TMycArray.Divide( const Src: TArray>; const Comparer: IComparer ): TArray>>; +var + i, j: Integer; + Arr: TArray>>; +begin + Arr := Create>( Src ).Divide( + TComparer>.Construct( + function( const A, B: TPair ): Integer + begin + Result := Comparer.Compare( A.Key, B.Key ); + end ) ); + + SetLength( Result, Length( Arr ) ); + for i := 0 to High( Result ) do + begin + Result[i].Key := Arr[i][0].Key; + SetLength( Result[i].Value, Arr[i].Count ); + for j := 0 to High( Result[i].Value ) do + Result[i].Value[j] := Arr[i][j].Value; + end; +end; + +class function TMycArray.Divide( const Src: IMycArray>; const Comparer: IComparer ): + TArray>>; +var + Arr: TArray>>; + i: Integer; +begin + Arr := Src.Divide( + TComparer>.Construct( + function( const A, B: TPair ): Integer + begin + Result := Comparer.Compare( A.Key, B.Key ); + end ) ); + + SetLength( Result, Length( Arr ) ); + for i := 0 to High( Result ) do + begin + Result[i] := TPair>.Create( + Arr[i][0].Key, + TMycCustomArray.CreateConvert>( + Arr[i], + function( const P: TPair ): TValue + begin + Result := P.Value; + end ) ); + end; +end; + +class function TMycArray.GetKeys( const Arr: TArray> ): TArray; +var + i: Integer; +begin + SetLength( Result, Length( Arr ) ); + for i := 0 to High( Arr ) do + Result[i] := Arr[i].Key; +end; + +class function TChain.Create: IMycChain; +begin + Result := TMycChain.Create; +end; + +{ TMutableCache } + +constructor TMutableCache.Create( const AThis: IMyc3MutableTable ); +begin + FThis := AThis; +end; + +function TMutableCache.GetItem(const Value: T): IMyc3Mutable; +begin + Result := FThis.Add( Value ); +end; + +procedure TMutableCache.Clear; +begin + FThis.Clear; +end; + +class function Stack.Create: IMycStack; +begin + Result := TMycStack.Create; +end; + +{ TTag } + +class function TTag.From(const Data: T): TTag; +begin + {$ifdef DEBUG} + Result.FTypeInfo := TypeInfo(T); + Assert( (Result.FTypeInfo<>nil) and (Result.FTypeInfo.Name <> 'TTag'), 'Recursive tag declaration' ); + {$endif} + + case PTypeInfo(TypeInfo(T)).Kind of + tkInteger: + begin + case PTypeInfo(TypeInfo(T)).TypeData.OrdType of + otSByte: PShortInt(@Result.FOrdinal)^ := PShortInt(@Data)^; + otUByte: PByte(@Result.FOrdinal)^ := PByte(@Data)^; + otSWord: PSmallInt(@Result.FOrdinal)^ := PSmallInt(@Data)^; + otUWord: PWord(@Result.FOrdinal)^ := PWord(@Data)^; + otSLong: PInteger(@Result.FOrdinal)^ := PInteger(@Data)^; + otULong: PLongword(@Result.FOrdinal)^ := PLongword(@Data)^; + end; + end; + + tkInt64: + begin + Result.FOrdinal := PInt64(@Data)^; + end; + + tkFloat: + begin + case PTypeInfo(TypeInfo(T)).TypeData.FloatType of + ftSingle: PSingle(@Result.FOrdinal)^ := PSingle(@Data)^; + ftDouble: PDouble(@Result.FOrdinal)^ := PDouble(@Data)^; + else + begin + Result.FOrdinal := 0; + Result.FManaged := TMyc3Value.Create(Data); + end; + end; + end; + + {$IFNDEF AUTOREFCOUNT} + tkClass, + {$ENDIF} + tkPointer: + begin + PPointer(@Result.FOrdinal)^ := PPointer(@Data)^; + end; + + tkInterface: + begin + Result.FOrdinal := 0; + Result.FManaged := IInterface(PPointer(@Data)^); + end; + + else + begin + Result.FOrdinal := 0; + Result.FManaged := TMyc3Value.Create(Data); + end; + end; +end; + +function TTag.AsType: T; +begin + {$ifdef DEBUG} + if FTypeInfo=nil then + RaiseInvalidOperation( 'TTag not initialized, value of type %s expected', [PTypeInfo(TypeInfo(T)).Name] ); + + if FTypeInfo <> TypeInfo(T) then + RaiseInvalidOperation( 'TTag<%s> can''t cast to %s', [FTypeInfo.Name, PTypeInfo(TypeInfo(T)).Name] ); + {$endif} + + case PTypeInfo(TypeInfo(T)).Kind of + tkInteger: + begin + case PTypeInfo(TypeInfo(T)).TypeData.OrdType of + otSByte: PShortInt(@Result)^ := PShortInt(@FOrdinal)^; + otUByte: PByte(@Result)^ := PByte(@FOrdinal)^; + otSWord: PSmallInt(@Result)^ := PSmallInt(@FOrdinal)^; + otUWord: PWord(@Result)^ := PWord(@FOrdinal)^; + otSLong: PInteger(@Result)^ := PInteger(@FOrdinal)^; + otULong: PLongword(@Result)^ := PLongword(@FOrdinal)^; + end; + end; + + tkInt64: + begin + PInt64(@Result)^ := FOrdinal; + end; + + tkFloat: + begin + case PTypeInfo(TypeInfo(T)).TypeData.FloatType of + ftSingle: PSingle(@Result)^ := PSingle(@FOrdinal)^; + ftDouble: PDouble(@Result)^ := PDouble(@FOrdinal)^; + else + Result := IMyc3Value( FManaged ).Value; + end; + end; + + {$IFNDEF AUTOREFCOUNT} + tkClass, + {$ENDIF} + tkPointer: + begin + PPointer(@Result)^ := PPointer(@FOrdinal)^; + end; + + tkInterface: + begin + IInterface(PPointer(@Result)^) := FManaged; + end; + + else + begin + Result := IMyc3Value( FManaged ).Value; + end; + end; +end; + +{ TMycStateQueue } + +constructor TMycStateQueue.Create(const AStack: IAtomicStack); +begin + FStack := AStack; +end; + +procedure TMycStateQueue.Add(const S: IMyc2State); +begin + FStack.Push( S ); +end; + +class function TMycStateQueue.Create: TMycStateQueue; +begin + Result.Create( TAtomicStack.Create ); +end; + +procedure TMycStateQueue.WaitFor; +var + S: IMyc2State; +begin + S := FStack.Pop; + while S<>nil do + begin + Tasks.WaitFor( S ); + S := FStack.Pop; + end; +end; + +{ TMutable } + +class function TMutable.FromSignals(const Changed: array of IMyc2Signal; const Calculator: TFunc): IMyc3Mutable; +begin + Result := TMyc3Mutable.CreateMutable( Changed, Calculator ); +end; + +class function TMutable.FromSignals(const Changed: array of IMyc2Signal; const Calculator: TFunc>): IMyc3Mutable; +begin + Result := TMyc3EnclosedMutable.Create( TMyc3Mutable>.CreateMutable( Changed, Calculator ) ); +end; + +class function TMutable.From(const Value: IMyc3Mutable; const Conv: TFunc>): TMutable; +begin + Result := TMyc3EnclosedMutable.Create( TValue>.CreateMutable( Value, Conv ) ); +end; + +class operator TMutable.Explicit(const Value: T): TMutable; +begin + Result.FThis := TMyc3ConstantMutable.Create( Value ); +end; + +class operator TMutable.Implicit( + const Item: TMutable): IMyc3Mutable; +begin + Result := Item.FThis; +end; + +class operator TMutable.Implicit( + const Item: IMyc3Mutable): TMutable; +begin + Result.FThis := Item; +end; + +{ TParameterValue } + +constructor TParameterValue.Create( ATypeInfo: PTypeInfo; const AMutable: IMyc3Mutable ); +begin + FMutable := AMutable; + {$ifdef DEBUG} + FTypeInfo := ATypeInfo; + {$endif} +end; + +function TParameterValue.Get: T; +begin + Assert( FMutable<>nil, 'Value is NULL' ); + Result := AsType.Value; +end; + +function TParameterValue.AsType: IMyc3Mutable; +begin + {$ifdef DEBUG} + CheckType( TypeInfo( T ) ); + {$endif} + Result := IMyc3Mutable( FMutable ); +end; + +class function TParameterValue.Compare( const A, B: TParameterValue ): Integer; +begin + Result := CompareValue( NativeInt( A.Mutable ), NativeInt( B.Mutable ) ); +end; + +class function TParameterValue.FromArray( const Values: TArray ): TArray>; +var + i: Integer; +begin + SetLength( Result, Length( Values ) ); + for i := 0 to High( Result ) do + Result[i] := Values[i].AsType; +end; + +class function TParameterValue.Create( const Intf: T ): TParameterValue; +begin + Result.Create( TypeInfo( T ), TValue.CreateMutable( Intf ) ); +end; + +class function TParameterValue.Create( const Intf: IMyc3Mutable ): TParameterValue; +begin + Result.Create( TypeInfo( T ), Intf ); +end; + +constructor TParameterValue.Assign( const Src: TParameterValue ); +begin + {$ifdef DEBUG} + CheckType( Src.FTypeInfo ); + {$endif} + FMutable := Src.FMutable; +end; + +{$ifdef DEBUG} +procedure TParameterValue.CheckType(TI: PTypeInfo); +begin + Assert( FTypeInfo = TI, Format( 'Type mismatch (casting param %s to %s)', [FTypeInfo.Name, TI.Name] ) ); +end; +{$endif} + +class function TParameterValue.FromVarRec( const VarRec: TVarRec ): TParameterValue; +begin + case VarRec.VType of + vtInteger: + Result := TParameterValue.Create( VarRec.VInteger ); + vtBoolean: + Result := TParameterValue.Create( VarRec.VBoolean ); + vtWideChar: + Result := TParameterValue.Create( VarRec.VWideChar ); + vtInt64: + Result := TParameterValue.Create( VarRec.VInt64^ ); + vtChar: + Result := TParameterValue.Create( String( VarRec.VChar ) ); + vtPChar: + Result := TParameterValue.Create( String( VarRec.VPChar ) ); + vtPWideChar: + Result := TParameterValue.Create( String( VarRec.VPWideChar ) ); + vtString: + Result := TParameterValue.Create( String( VarRec.VString^ ) ); + vtWideString: + Result := TParameterValue.Create( String( VarRec.VWideString ) ); + vtAnsiString: + Result := TParameterValue.Create( String( VarRec.VAnsiString ) ); + vtUnicodeString: + Result := TParameterValue.Create( String( VarRec.VUnicodeString ) ); + vtObject: + Result := TParameterValue.Create( TObject( VarRec.VObject ) ); + vtClass: + Result := TParameterValue.Create( VarRec.VClass ); + vtVariant: + Result := TParameterValue.Create( VarRec.VVariant^ ); + vtPointer: + Result := TParameterValue.Create( VarRec.VPointer ); + vtExtended: + Result := TParameterValue.Create( VarRec.VExtended^ ); + vtCurrency: + Result := TParameterValue.Create( VarRec.VCurrency^ ); + vtInterface: + Result := TParameterValue.Create( IInterface( VarRec.VInterface ) ); + end; +end; + +{ TParameterField } + +constructor TParameterField.Create( AID: TFieldID; const AValue: TParameterValue ); +begin + FID := AID; + FValue := AValue; +end; + +class function TParameterField.Create( ID: TFieldID; const Intf: IMyc3Mutable ): TParameterField; +begin + Result := TParameterField.Create( ID, TParameterValue.Create( Intf ) ); +end; + +class function TParameterField.Create( ID: TFieldID; const Intf: T ): TParameterField; +begin + Result := TParameterField.Create( ID, TParameterValue.Create( Intf ) ); +end; + +{ TParameterRecord } + +constructor TParameterRecord.Create( const AThis: IMyc3ParameterRecord ); +begin + FThis := AThis; +end; + +function TParameterRecord.AsType( ID: TFieldID ): IMyc3Mutable; +begin + Result := GetValues( ID ).AsType; +end; + +function TParameterRecord.Get(ID: TFieldID): T; +begin + Result := GetValues( ID ).Get; +end; + +function TParameterRecord.GetByName( const Caption: String ): TParameterValue; +begin + Result := FThis.ByName[Caption]; +end; + +function TParameterRecord.GetDef: IMycParameterRecordDefinition; +begin + Result := FThis.Def; +end; + +function TParameterRecord.GetValues(ID: TFieldID): TParameterValue; +begin + Result := FThis.Values[ID]; +end; + +class operator TParameterRecord.Implicit(const Item: IMyc3ParameterRecord): TParameterRecord; +begin + Result.Create( Item ); +end; + +class operator TParameterRecord.Implicit(const Item: TParameterRecord): IMyc3ParameterRecord; +begin + Result := Item.FThis; +end; + +{ TParameterSpace } + +constructor TParameterSpace.Create( const AThis: IMycParameterSpace ); +begin + FThis := AThis; +end; + +function TParameterSpace.CompareValues( FieldID: TFieldID; const A, B: TParameterValue ): Integer; +begin + Result := FThis.CompareValues( FieldID, A, B ); +end; + +class function TParameterSpace.Create: TParameterSpace; +begin + Result.Create( TMycParameterSpace.Create ); +end; + +function TParameterSpace.CreateRecord( const Fields: TArray ): TParameterRecord; +begin + Result.Create( FThis.CreateRecord( Fields ) ); +end; + +function TParameterSpace.GetCaptions( FieldID: TFieldID ): String; +begin + Result := FThis.Captions[FieldID]; +end; + +function TParameterSpace.GetComparer( FieldID: TFieldID ): IComparer; +begin + Result := FThis.Comparer[FieldID]; +end; + +function TParameterSpace.GetDefaultValues( FieldID: TFieldID ): TParameterValue; +begin + Result := FThis.DefaultValues[FieldID]; +end; + +function TParameterSpace.GetFieldCount: Integer; +begin + Result := FThis.FieldCount; +end; + +function TParameterSpace.AddField(const Caption: String; const DefaultValue: T; const Comparer: IComparer = nil): TFieldID; +var + ValueComparer: IComparer; + Comp: IComparer; +begin + ValueComparer := Comparer; + if ValueComparer=nil then + ValueComparer := TComparer.Default; + + Comp := TComparer.Construct( + function( const A, B: TParameterValue ): Integer + begin + if ( A.Mutable <> nil ) and ( B.Mutable<>nil ) then + Result := ValueComparer.Compare( A.AsType.Value, B.AsType.Value ) + else if A.Mutable <> nil then + Result := 1 + else if B.Mutable <> nil then + Result := -1 + else + Result := 0; + end ); + + Result := FThis.AddField( TypeInfo( T ), Caption, TValue.CreateMutable( DefaultValue ), Comp ); +end; + +function TParameterSpace.CreateDef(const Fields: TArray): IMycParameterRecordDefinition; +begin + Result := FThis.CreateDef( Fields ); +end; + +class operator TParameterSpace.Implicit( const Item: TParameterSpace ): IMycParameterSpace; +begin + Result := Item.FThis; +end; + +class operator TParameterSpace.Implicit( const Item: IMycParameterSpace ): TParameterSpace; +begin + Result.Create( Item ); +end; + +{ TMutableFuture } + +class function TMutableFuture.FromArray(const Value: TArray>>): IMyc3Mutable>>; +begin + Result := Mutable.Convert>, IMyc3Future>>( + Mutable.FromArray>( Value ), + function( Value: TArray> ): IMyc3Future> + begin + Result := TFuture.FromArray( Value ); + end ); +end; + +constructor TStackTrace.Create(ASkipFrames: Cardinal; const AMsg: String = ''); +begin + Msg := AMsg; + {$ifdef FullDebugMode} + FastMM_EnterDebugMode; + FastMM_GetStackTrace(@StackTrace, Length(StackTrace), 0); + {$endif} +end; + +function TStackTrace.ToText: String; +{$ifdef FullDebugMode} +var + LStackTraceBuffer: array[0..Len * 160] of WideChar; +{$endif} +begin + Result := Msg; + if Result<>'' then + Result := Result + #13#10; + + {$ifdef FullDebugMode} + FastMM_ConvertStackTraceToText( + @StackTrace, Length(StackTrace), @LStackTraceBuffer, + @LStackTraceBuffer[High(LStackTraceBuffer)]); + + Result := Result + LStackTraceBuffer; + {$else} + Result := Result + 'StackTrace: FullDebugMode not enabled'; + {$endif} +end; + +class function TValidatable.FromSignal(const Signal: IMyc2Signal): IMyc3Validatable; +begin + Result := TMyc3ValidatableSignal.Create(Signal); +end; + +initialization + +Tasks := TMyc3Tasks.Create( TMyc2TaskFactory.Create ); + +finalization + +FreeAndNil( Tasks ); + +end. diff --git a/Test/MycTests.dpr b/Test/MycTests.dpr new file mode 100644 index 0000000..9fc7fdd --- /dev/null +++ b/Test/MycTests.dpr @@ -0,0 +1,72 @@ +program MycTests; + +{$IFNDEF TESTINSIGHT} +{$APPTYPE CONSOLE} +{$ENDIF} +{$STRONGLINKTYPES ON} +uses + FastMM5, + DUnitX.MemoryLeakMonitor.FastMM5, + System.SysUtils, + {$IFDEF TESTINSIGHT} + TestInsight.DUnitX, + {$ELSE} + DUnitX.Loggers.Console, + DUnitX.Loggers.Xml.NUnit, + {$ENDIF } + DUnitX.TestFramework, + TestSignals in 'TestSignals.pas', + TestHeap in 'TestHeap.pas'; + +{ keep comment here to protect the following conditional from being removed by the IDE when adding a unit } +{$IFNDEF TESTINSIGHT} +var + runner: ITestRunner; + results: IRunResults; + logger: ITestLogger; + nunitLogger : ITestLogger; +{$ENDIF} +begin +{$IFDEF TESTINSIGHT} + TestInsight.DUnitX.RunRegisteredTests; +{$ELSE} + try + //Check command line options, will exit if invalid + TDUnitX.CheckCommandLine; + //Create the test runner + runner := TDUnitX.CreateRunner; + //Tell the runner to use RTTI to find Fixtures + runner.UseRTTI := True; + //When true, Assertions must be made during tests; + runner.FailsOnNoAsserts := False; + + //tell the runner how we will log things + //Log to the console window if desired + if TDUnitX.Options.ConsoleMode <> TDunitXConsoleMode.Off then + begin + logger := TDUnitXConsoleLogger.Create(TDUnitX.Options.ConsoleMode = TDunitXConsoleMode.Quiet); + runner.AddLogger(logger); + end; + //Generate an NUnit compatible XML File + nunitLogger := TDUnitXXMLNUnitFileLogger.Create(TDUnitX.Options.XMLOutputFile); + runner.AddLogger(nunitLogger); + + //Run tests + results := runner.Execute; + if not results.AllPassed then + System.ExitCode := EXIT_ERRORS; + + {$IFNDEF CI} + //We don't want this happening when running under CI. + if TDUnitX.Options.ExitBehavior = TDUnitXExitBehavior.Pause then + begin + System.Write('Done.. press key to quit.'); + System.Readln; + end; + {$ENDIF} + except + on E: Exception do + System.Writeln(E.ClassName, ': ', E.Message); + end; +{$ENDIF} +end. diff --git a/Test/MycTests.dproj b/Test/MycTests.dproj new file mode 100644 index 0000000..2bb4434 --- /dev/null +++ b/Test/MycTests.dproj @@ -0,0 +1,1121 @@ + + + {CE86C146-5191-415B-8DA3-35BE03E7C046} + 20.3 + None + True + Debug + Win64 + MycTests + 3 + Console + MycTests.dpr + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + .\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + true + $(BDS)\bin\delphi_PROJECTICON.ico + $(BDS)\bin\delphi_PROJECTICNS.icns + $(DUnitX);..\Src;$(DCC_UnitSearchPath) + MycTests + 1031 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + --exitbehavior:Pause + + + vclwinx;fmx;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;FireDACCommonODBC;FireDACCommonDriver;IndyProtocols;vclx;Skia.Package.RTL;IndyIPClient;dbxcds;vcledge;bindcompvclwinx;FmxTeeUI;bindcompfmx;inetdb;FireDACSqliteDriver;DbxClientDriver;Tee;soapmidas;vclactnband;TeeUI;fmxFireDAC;dbexpress;DBXMySQLDriver;VclSmp;inet;vcltouch;fmxase;dbrtl;Skia.Package.FMX;fmxdae;TeeDB;FireDACMSAccDriver;CustomIPTransport;vcldsnap;DBXInterBaseDriver;IndySystem;Skia.Package.VCL;vcldb;vclFireDAC;bindcomp;FireDACCommon;inetstn;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;FireDACADSDriver;RESTComponents;DBXSqliteDriver;vcl;IndyIPServer;dsnapxml;dsnapcon;adortl;vclimg;FireDACPgDriver;FireDAC;inetdbxpress;xmlrtl;tethering;bindcompvcl;dsnap;CloudService;fmxobj;bindcompvclsmp;FMXTee;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + 1033 + + + vclwinx;fmx;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;FireDACCommonODBC;FireDACCommonDriver;IndyProtocols;vclx;IndyIPClient;dbxcds;vcledge;bindcompvclwinx;FmxTeeUI;bindcompfmx;inetdb;FireDACSqliteDriver;DbxClientDriver;Tee;soapmidas;vclactnband;TeeUI;fmxFireDAC;dbexpress;DBXMySQLDriver;VclSmp;inet;vcltouch;fmxase;dbrtl;fmxdae;TeeDB;FireDACMSAccDriver;CustomIPTransport;vcldsnap;DBXInterBaseDriver;IndySystem;Skia.Package.VCL;vcldb;vclFireDAC;bindcomp;FireDACCommon;inetstn;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;FireDACADSDriver;RESTComponents;DBXSqliteDriver;vcl;IndyIPServer;dsnapxml;dsnapcon;adortl;vclimg;FireDACPgDriver;FireDAC;inetdbxpress;xmlrtl;tethering;bindcompvcl;dsnap;CloudService;fmxobj;bindcompvclsmp;FMXTee;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + 1033 + + + DEBUG;$(DCC_Define) + true + false + true + true + true + true + true + + + false + + + 1033 + (None) + none + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + + MainSource + + + + + Base + + + Cfg_1 + Base + + + Cfg_2 + Base + + + + Delphi.Personality.12 + Application + + + + MycTests.dpr + + + + + + + true + + + + + true + + + + + true + + + + + MycTests.exe + true + + + + + MycTests.rsm + true + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi + 1 + + + library\lib\armeabi + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\mips + 1 + + + library\lib\mips + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-anydpi-v21 + 1 + + + res\drawable-anydpi-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values-v31 + 1 + + + res\values-v31 + 1 + + + + + res\values-v35 + 1 + + + res\values-v35 + 1 + + + + + res\drawable-anydpi-v26 + 1 + + + res\drawable-anydpi-v26 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-anydpi-v33 + 1 + + + res\drawable-anydpi-v33 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\values-night-v21 + 1 + + + res\values-night-v21 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-ldpi + 1 + + + res\drawable-ldpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-small + 1 + + + res\drawable-small + 1 + + + + + res\drawable-normal + 1 + + + res\drawable-normal + 1 + + + + + res\drawable-large + 1 + + + res\drawable-large + 1 + + + + + res\drawable-xlarge + 1 + + + res\drawable-xlarge + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable-anydpi-v24 + 1 + + + res\drawable-anydpi-v24 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-night-anydpi-v21 + 1 + + + res\drawable-night-anydpi-v21 + 1 + + + + + res\drawable-anydpi-v31 + 1 + + + res\drawable-anydpi-v31 + 1 + + + + + res\drawable-night-anydpi-v31 + 1 + + + res\drawable-night-anydpi-v31 + 1 + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + 0 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + ..\ + 1 + + + + + Contents + 1 + + + Contents + 1 + + + Contents + 1 + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + Contents\MacOS + 1 + + + Contents\MacOS + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen + 64 + + + ..\$(PROJECTNAME).launchscreen + 64 + + + + + 1 + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + + + + + + + + + + + + + True + True + + + 12 + + + + + diff --git a/Test/MycTests.res b/Test/MycTests.res new file mode 100644 index 0000000000000000000000000000000000000000..333684ad278e9b20d4042824f2f96e983b6e3a11 GIT binary patch literal 152412 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, TDictionary + +type + // Test data structure for TSList items + PTestListItem = ^TTestListItem; + TTestListItem = record + ListEntry: TSListEntry; // Using TSListEntry from Myc.Heap.pas + ID: Integer; + end; + + [TestFixture] + TMycTestHeap = class + private + const TestAlignmentMask = 15; // Corresponds to AlignmentBoundary = 16 in Myc.Heap.pas + + // Helpers made static to be callable from anonymous threads + class function CreateTestListItem(ID: Integer): PTestListItem; static; + class procedure FreeTestListItem(var Item: PTestListItem); static; + + public + [Setup] + procedure Setup; + [TearDown] + procedure TearDown; + + // Alignment Stress Test + [Test] + procedure TestStressAlignmentAndDataIntegrity; + + // --- TSList Single-Threaded Tests --- + [Test] + procedure TestTSList_CreateAndFree; + [Test] + procedure TestTSList_PushPopSingleEntry; + [Test] + procedure TestTSList_PushPopMultipleEntries_LIFO; + [Test] + procedure TestTSList_QueryDepth; + [Test] + procedure TestTSList_PopFromEmptyList; + + // --- TSList Parallel Access Test --- + [Test] + [Timeout(30000)] // Timeout in milliseconds + procedure TestTSList_ParallelPushPop; + + // Sample method (can be removed if not needed) + [Test] + procedure Test1; + end; + +implementation + +uses + System.Math, // For Random and Randomize + System.Classes, // For TThread + System.Types; // For TWaitResult + +// --- Helper Implementations (static) --- + +class function TMycTestHeap.CreateTestListItem(ID: Integer): PTestListItem; +begin + Myc.Heap.GetMemAligned(Result, SizeOf(TTestListItem)); + Assert.IsNotNull(Result, Format('Failed to allocate memory for TestListItem ID %d', [ID])); + FillChar(Result^, SizeOf(TTestListItem), 0); + Result.ID := ID; +end; + +class procedure TMycTestHeap.FreeTestListItem(var Item: PTestListItem); +var + tempPtr: Pointer; +begin + if Item <> nil then + begin + tempPtr := Item; + Myc.Heap.FreeMemAligned(tempPtr); + Item := nil; + end; +end; + +// --- Fixture Setup/TearDown --- + +procedure TMycTestHeap.Setup; +begin + Randomize; +end; + +procedure TMycTestHeap.TearDown; +begin +end; + +// --- Alignment Stress Test Implementation --- + +procedure TMycTestHeap.TestStressAlignmentAndDataIntegrity; +const + NumIterations = 300000; + MaxBlockSize = 1024 * 4; +var + i: Integer; + sizeToAllocate: System.NativeUInt; + ptr: Pointer; + bytePtr: System.PByte; // Variable renamed from pByte + j: System.NativeUInt; + fillValue: Byte; + isNilAfterFreeExpected: Boolean; + originalPtrBeforeFree: Pointer; +begin + for i := 1 to NumIterations do + begin + case i mod 10 of + 0: sizeToAllocate := 1; + 1: sizeToAllocate := TestAlignmentMask; + 2: sizeToAllocate := TestAlignmentMask + 1; + 3: sizeToAllocate := TestAlignmentMask + 2; + 4: sizeToAllocate := 0; + 5: sizeToAllocate := MaxBlockSize div 2; + 6: sizeToAllocate := MaxBlockSize; + else + sizeToAllocate := System.NativeUInt(Random(MaxBlockSize -1) + 1); + end; + + ptr := nil; + try + Myc.Heap.GetMemAligned(ptr, sizeToAllocate); + if sizeToAllocate = 0 then + begin + Assert.IsTrue(True, Format('Zero-size allocation processed (Iteration %d). Pointer: %p', [i, ptr])); + end + else if ptr = nil then + begin + Assert.Fail(Format('GetMemAligned returned nil for size %u (Iteration %d).', + [sizeToAllocate, i])); + end + else + begin + Assert.AreEqual(System.NativeUInt(0), System.NativeUInt(ptr) and TestAlignmentMask, + Format('Pointer %p not 16-byte aligned. Size: %u, Iteration: %d. (Value AND Mask = %u)', + [ptr, sizeToAllocate, i, System.NativeUInt(ptr) and TestAlignmentMask])); + + fillValue := Byte(i mod 256); + System.FillChar(ptr^, sizeToAllocate, fillValue); + + bytePtr := System.PByte(ptr); + for j := 0 to sizeToAllocate - 1 do + begin + if bytePtr[j] <> fillValue then + begin + Assert.Fail(Format('Data corruption at offset %u for pointer %p. Size: %u, Iteration: %d. Expected %u, got %u.', + [j, ptr, sizeToAllocate, i, fillValue, bytePtr[j]])); + Break; + end; + end; + end; + finally + Myc.Heap.FreeMemAligned(ptr); + end; + end; +end; + +// --- TSList Single-Threaded Test Implementations --- + +procedure TMycTestHeap.TestTSList_CreateAndFree; +var + sListPtr: PSList; +begin + sListPtr := TSList.Create; + Assert.IsNotNull(sListPtr, 'TSList.Create should return a non-nil pointer.'); + Assert.AreEqual(0, sListPtr.QueryDepth, 'Newly created TSList should have a depth of 0.'); + sListPtr.Free; +end; + +procedure TMycTestHeap.TestTSList_PushPopSingleEntry; +var + sListPtr: PSList; + item1: PTestListItem; + poppedInternalListEntry: PSListEntry; + poppedItem: PTestListItem; +begin + sListPtr := TSList.Create; + Assert.IsNotNull(sListPtr); + item1 := TMycTestHeap.CreateTestListItem(101); + Assert.IsNotNull(item1); + sListPtr.Push(@item1.ListEntry); + Assert.AreEqual(1, sListPtr.QueryDepth, 'Depth should be 1 after one push.'); + poppedInternalListEntry := sListPtr.Pop; + Assert.IsNotNull(poppedInternalListEntry, 'Pop should return the pushed item, not nil.'); + poppedItem := PTestListItem(poppedInternalListEntry); + Assert.AreEqual(NativeUInt(item1), NativeUInt(poppedItem), 'Popped item should be the same as pushed item.'); + Assert.AreEqual(item1.ID, poppedItem.ID, 'Popped item ID mismatch.'); + Assert.AreEqual(0, sListPtr.QueryDepth, 'Depth should be 0 after popping the item.'); + TMycTestHeap.FreeTestListItem(item1); + sListPtr.Free; +end; + +procedure TMycTestHeap.TestTSList_PushPopMultipleEntries_LIFO; +const + NumItems = 5; +var + sListPtr: PSList; + items: array[1..NumItems] of PTestListItem; + poppedInternalListEntry: PSListEntry; + poppedItem: PTestListItem; + i: Integer; +begin + sListPtr := TSList.Create; + Assert.IsNotNull(sListPtr); + for i := 1 to NumItems do + begin + items[i] := TMycTestHeap.CreateTestListItem(200 + i); + sListPtr.Push(@items[i].ListEntry); + Assert.AreEqual(i, sListPtr.QueryDepth, Format('Depth should be %d after %d pushes.', [i,i])); + end; + Assert.AreEqual(NumItems, sListPtr.QueryDepth, 'Final depth after all pushes incorrect.'); + for i := NumItems downto 1 do + begin + poppedInternalListEntry := sListPtr.Pop; + Assert.IsNotNull(poppedInternalListEntry, Format('Pop should return an item, not nil (iteration %d).', [i])); + poppedItem := PTestListItem(poppedInternalListEntry); + Assert.AreEqual(NativeUInt(items[i]), NativeUInt(poppedItem), + Format('LIFO order violated. Expected item ID %d (original index %d), got ID %d.', [items[i].ID, i, poppedItem.ID])); + Assert.AreEqual(items[i].ID, poppedItem.ID, 'Popped item ID mismatch.'); + Assert.AreEqual(i - 1, sListPtr.QueryDepth, Format('Depth incorrect after pop %d.', [NumItems - i + 1])); + end; + Assert.AreEqual(0, sListPtr.QueryDepth, 'Depth should be 0 after all items are popped.'); + for i := 1 to NumItems do + begin + TMycTestHeap.FreeTestListItem(items[i]); + end; + sListPtr.Free; +end; + +procedure TMycTestHeap.TestTSList_QueryDepth; +var + sListPtr: PSList; + item1, item2: PTestListItem; +begin + sListPtr := TSList.Create; + Assert.IsNotNull(sListPtr); + Assert.AreEqual(0, sListPtr.QueryDepth, 'Initial depth should be 0.'); + item1 := TMycTestHeap.CreateTestListItem(301); + sListPtr.Push(@item1.ListEntry); + Assert.AreEqual(1, sListPtr.QueryDepth, 'Depth should be 1 after one push.'); + item2 := TMycTestHeap.CreateTestListItem(302); + sListPtr.Push(@item2.ListEntry); + Assert.AreEqual(2, sListPtr.QueryDepth, 'Depth should be 2 after two pushes.'); + sListPtr.Pop; + Assert.AreEqual(1, sListPtr.QueryDepth, 'Depth should be 1 after one pop.'); + sListPtr.Pop; + Assert.AreEqual(0, sListPtr.QueryDepth, 'Depth should be 0 after two pops.'); + TMycTestHeap.FreeTestListItem(item1); + TMycTestHeap.FreeTestListItem(item2); + sListPtr.Free; +end; + +procedure TMycTestHeap.TestTSList_PopFromEmptyList; +var + sListPtr: PSList; + poppedInternalListEntry: PSListEntry; +begin + sListPtr := TSList.Create; + Assert.IsNotNull(sListPtr); + Assert.AreEqual(0, sListPtr.QueryDepth, 'Initial depth should be 0.'); + poppedInternalListEntry := sListPtr.Pop; + Assert.IsNull(poppedInternalListEntry, 'Pop from an empty list should return nil.'); + Assert.AreEqual(0, sListPtr.QueryDepth, 'Depth should still be 0 after pop from empty.'); + sListPtr.Free; +end; + +// --- TSList Parallel Access Test Implementation --- +procedure TMycTestHeap.TestTSList_ParallelPushPop; +const + NumThreads = 12; + OperationsPerThread = 50000; + QueueCapacityFactor = 2; // Factor to ensure log queues have enough space +var + threads: array of TThread; // TThread from System.Classes + i: Integer; + sharedSList: PSList; + pushedIDs: TThreadedQueue; + poppedIDs: TThreadedQueue; + globalItemID: Integer; + pushedItemsFinal: TDictionary; + currentPushedID: Integer; // Renamed from poppedItemValue for clarity in its context + currentPoppedID: Integer; // Renamed from poppedItemValue for clarity in its context + waitResult: TWaitResult; + remainingItem: PTestListItem; + remainingInternalEntry: PSListEntry; + logQueueCapacity: Integer; +begin + sharedSList := TSList.Create; + Assert.IsNotNull(sharedSList, 'Failed to create shared TSList for parallel test.'); + + logQueueCapacity := NumThreads * OperationsPerThread * QueueCapacityFactor; + pushedIDs := TThreadedQueue.Create(logQueueCapacity, INFINITE, 0); // PopTimeout = 0 + poppedIDs := TThreadedQueue.Create(logQueueCapacity, INFINITE, 0); // PopTimeout = 0 + globalItemID := 0; + + SetLength(threads, NumThreads); + + for i := 0 to High(threads) do + begin + threads[i] := TThread.CreateAnonymousThread( + procedure + var + j: Integer; + item: PTestListItem; + poppedInternalEntry: PSListEntry; + itemID: Integer; + actionRand: Double; + pushLogResult: TWaitResult; // Renamed from pushResult for clarity + begin + for j := 1 to OperationsPerThread do + begin + actionRand := Random; + if actionRand < 0.5 then // Attempt to Push + begin + itemID := TInterlocked.Increment(globalItemID); // TInterlocked from System.SysUtils or System + item := TMycTestHeap.CreateTestListItem(itemID); + if item <> nil then + begin + sharedSList.Push(@item.ListEntry); + pushLogResult := pushedIDs.PushItem(itemID); // Use PushItem for TThreadedQueue + Assert.AreEqual(TWaitResult.wrSignaled, pushLogResult, + Format('Failed to push item ID %d to pushedIDs logging queue.', [itemID])); + end; + end + else // Attempt to Pop + begin + poppedInternalEntry := sharedSList.Pop; + if poppedInternalEntry <> nil then + begin + item := PTestListItem(poppedInternalEntry); + pushLogResult := poppedIDs.PushItem(item.ID); // Use PushItem for TThreadedQueue + Assert.AreEqual(TWaitResult.wrSignaled, pushLogResult, + Format('Failed to push item ID %d to poppedIDs logging queue.', [item.ID])); + TMycTestHeap.FreeTestListItem(item); + end; + end; + if j mod (OperationsPerThread div 10) = 0 then // Occasional sleep + TThread.Sleep(1); // TThread.Sleep from System.Classes + end; + end); + threads[i].FreeOnTerminate := false; + end; + + for i := 0 to High(threads) do + threads[i].Start; + + for i := 0 to High(threads) do + begin + threads[i].WaitFor; + end; + + for i := 0 to High(threads) do + threads[i].Free; + + while True do + begin + remainingInternalEntry := sharedSList.Pop; + if remainingInternalEntry = nil then Break; + + remainingItem := PTestListItem(remainingInternalEntry); + waitResult := poppedIDs.PushItem(remainingItem.ID); // Use PushItem + Assert.AreEqual(TWaitResult.wrSignaled, waitResult, + Format('Failed to push remaining item ID %d to poppedIDs logging queue during drain.', [remainingItem.ID])); + TMycTestHeap.FreeTestListItem(remainingItem); + end; + + Assert.AreEqual(0, sharedSList.QueryDepth, 'TSList should be empty after all operations and draining.'); + + + Assert.AreEqual(pushedIDs.TotalItemsPushed, poppedIDs.TotalItemsPushed, // Compare total items PUSHED to each log queue + Format('Mismatch between total logged pushed items (%u) and total logged popped items (%u).', + [pushedIDs.TotalItemsPushed, poppedIDs.TotalItemsPushed])); + + pushedItemsFinal := TDictionary.Create; + try + // Drain pushedIDs queue using PopItem with timeout = 0 behavior + var qz: Integer; + while pushedIDs.PopItem(qz, currentPushedID) = TWaitResult.wrSignaled do + begin + if pushedItemsFinal.ContainsKey(currentPushedID) then + pushedItemsFinal.Items[currentPushedID] := pushedItemsFinal.Items[currentPushedID] + 1 + else + pushedItemsFinal.Add(currentPushedID, 1); + if qz=0 then break; + end; + + // Drain poppedIDs queue using PopItem with timeout = 0 behavior + while poppedIDs.PopItem(qz, currentPoppedID) = TWaitResult.wrSignaled do + begin + Assert.IsTrue(pushedItemsFinal.ContainsKey(currentPoppedID), + Format('Item ID %d was logged as popped but never logged as pushed.', [currentPoppedID])); + if pushedItemsFinal.ContainsKey(currentPoppedID) then // Re-check for safety before decrementing + begin + pushedItemsFinal.Items[currentPoppedID] := pushedItemsFinal.Items[currentPoppedID] - 1; + end; + if qz=0 then break; + end; + + // Verify that all pushed items were accounted for (counts should be zero) + for currentPushedID in pushedItemsFinal.Keys do + begin + Assert.AreEqual(0, pushedItemsFinal.Items[currentPushedID], + Format('Item ID %d count mismatch. Final count in reconciliation dictionary is not zero (is %d).', + [currentPushedID, pushedItemsFinal.Items[currentPushedID]])); + end; + finally + pushedItemsFinal.Free; + end; + + // Final cleanup + sharedSList.Free; + pushedIDs.Free; + poppedIDs.Free; +end; + +// --- Existing Sample Test --- +procedure TMycTestHeap.Test1; +begin + // Example: Assert.IsTrue(True, 'Test1 passed'); +end; + +initialization + TDUnitX.RegisterTestFixture(TMycTestHeap); +end. diff --git a/Test/TestSignals.pas b/Test/TestSignals.pas new file mode 100644 index 0000000..312bc9f --- /dev/null +++ b/Test/TestSignals.pas @@ -0,0 +1,49 @@ +unit TestSignals; + +interface + +uses + DUnitX.TestFramework, + Myc.Atomic; + +type + [TestFixture] + TMycTest = class + public + [Setup] + procedure Setup; + [TearDown] + procedure TearDown; + // Sample Methods + // Simple single Test + [Test] + procedure Test1; + // Test with TestCase Attribute to supply parameters. + [Test] + [TestCase('TestA','1,2')] + [TestCase('TestB','3,4')] + procedure Test2(const AValue1 : Integer;const AValue2 : Integer); + end; + +implementation + +procedure TMycTest.Setup; +begin +end; + +procedure TMycTest.TearDown; +begin +end; + +procedure TMycTest.Test1; +begin +end; + +procedure TMycTest.Test2(const AValue1 : Integer;const AValue2 : Integer); +begin +end; + +initialization + TDUnitX.RegisterTestFixture(TMycTest); + +end.