From 468adcf203fc486a137cdabecf6af2ea931719b4 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Sun, 27 Jul 2025 08:39:16 +0200 Subject: [PATCH] Testing data processing --- AuraTrader/AuraTrader.dpr | 3 +- AuraTrader/AuraTrader.dproj | 1 + AuraTrader/MainForm.fmx | 1 + AuraTrader/MainForm.pas | 26 ++-- AuraTrader/TestMethodCallFromRecordParams.pas | 134 +++++++++++++++++ Src/Myc.Data.Records.pas | 132 ++++++++++------- Src/Myc.Trade.Indicators.pas | 135 ++++-------------- Test/TestDataRecord.pas | 2 +- 8 files changed, 261 insertions(+), 173 deletions(-) create mode 100644 AuraTrader/TestMethodCallFromRecordParams.pas diff --git a/AuraTrader/AuraTrader.dpr b/AuraTrader/AuraTrader.dpr index 19e7cf9..b482af9 100644 --- a/AuraTrader/AuraTrader.dpr +++ b/AuraTrader/AuraTrader.dpr @@ -7,7 +7,8 @@ uses MainForm in 'MainForm.pas' {Form1}, TestModule in 'TestModule.pas', DynamicFMXControl in 'DynamicFMXControl.pas', - Myc.Trade.Pipeline.Impl in '..\Src\Myc.Trade.Pipeline.Impl.pas'; + Myc.Trade.Pipeline.Impl in '..\Src\Myc.Trade.Pipeline.Impl.pas', + TestMethodCallFromRecordParams in 'TestMethodCallFromRecordParams.pas'; {$R *.res} diff --git a/AuraTrader/AuraTrader.dproj b/AuraTrader/AuraTrader.dproj index 15ce6c0..cc7a5be 100644 --- a/AuraTrader/AuraTrader.dproj +++ b/AuraTrader/AuraTrader.dproj @@ -138,6 +138,7 @@ + Base diff --git a/AuraTrader/MainForm.fmx b/AuraTrader/MainForm.fmx index 20bd505..2e0137d 100644 --- a/AuraTrader/MainForm.fmx +++ b/AuraTrader/MainForm.fmx @@ -205,6 +205,7 @@ object Form1: TForm1 TabOrder = 0 Text = 'Button1' TextSettings.Trimming = None + OnClick = Button1Click end end end diff --git a/AuraTrader/MainForm.pas b/AuraTrader/MainForm.pas index 9a33a0b..3103136 100644 --- a/AuraTrader/MainForm.pas +++ b/AuraTrader/MainForm.pas @@ -52,7 +52,8 @@ uses DynamicFMXControl, Myc.FMX.Chart, Myc.Trade.Indicators, - StrategyTest; + StrategyTest, + TestMethodCallFromRecordParams; type TForm1 = class(TForm) @@ -87,6 +88,7 @@ type procedure StopButtonClick(Sender: TObject); procedure TreeViewDblClick(Sender: TObject); procedure AddWorkspaceActionExecute(Sender: TObject); + procedure Button1Click(Sender: TObject); procedure Strat2ButtonClick(Sender: TObject); procedure TestActionExecute(Sender: TObject); procedure StrategyButtonClick(Sender: TObject); @@ -285,6 +287,11 @@ begin Control.Align := TAlignLayout.Top; end; +procedure TForm1.Button1Click(Sender: TObject); +begin + Test1(LogMemo.Lines); +end; + function TForm1.CreateStrategy2(Timeframe: TTimeframe): IConsumer>; type TSignal = record @@ -478,21 +485,20 @@ begin pnlChart.SetXAxisCounter(equity.Producer); //////////// - var EMAFactory := TEMA.Create; - var Params := TDataRecord.Create(EMAFactory.Params); - Params.SetValue('Period', 20); + var Params: TEMA.TParam; + Params.Period := 20; - var indi := EMAFActory.CreateIndicator(Params); + var indi := TEMA.CreateEMA(Params); - var EMAConv := TConverter.CreateConverter(indi); + var EMAConv := TConverter.CreateConverter(indi); var equityEMA := equity .Producer - .Chain(EMAFactory.Input.FieldAsRecord('Price')) - .Chain(EMAConv) - .Chain(EMAFactory.Output.FieldOfRecord('MA')); + .Chain(function(const Value: Double): TEMA.TInput begin Result.Price := Value end) + .Chain(EMAConv) + .Chain(function(const Value: TEMA.TResult): Double begin Result := Value.MA end); ////////////// @@ -675,7 +681,7 @@ begin exit; var timeframe := TTimeframe.M15; - //ExecuteStrategy(Symbol, timeframe, CreateStrategy2(timeframe)); + ExecuteStrategy(Symbol, timeframe, CreateStrategy2(timeframe)); var tstStrat := StrategyTest.CreateStrategy1(timeframe); ExecuteStrategy(Symbol, timeframe, tstStrat.Consumer); diff --git a/AuraTrader/TestMethodCallFromRecordParams.pas b/AuraTrader/TestMethodCallFromRecordParams.pas new file mode 100644 index 0000000..4fc90ad --- /dev/null +++ b/AuraTrader/TestMethodCallFromRecordParams.pas @@ -0,0 +1,134 @@ +unit TestMethodCallFromRecordParams; + +interface + +// We need RTTI here! +{$M+} + +uses + System.Rtti, + System.SysUtils, + System.TypInfo, + System.Classes, + Myc.Data.Records; + +type + InvokeAttribute = class(TCustomAttribute); + FactoryAttribute = class(TCustomAttribute); + + TMyProc = reference to function(const [ref] Value: TValue): TResult; + TFactoryProc = reference to function(const [ref] Params: TParams): TMyProc; + + TMyWorker = record + type + TParams = record + Log: Int64; + text: String; + end; + + TValue = record + xyz: Double; + end; + + TResult = record + val: Int64; + desc: String; + end; + + [Factory] + class function CreateFactory: TFactoryProc; static; + end; + +procedure Test1(const Log: TStrings); + +implementation + +class function TMyWorker.CreateFactory: TFactoryProc; +begin + Result := + function(const [ref] Params: TParams): TMyProc + begin + var Log := TStrings(Params.Log); + var text := Params.text; + + Result := + function(const [ref] Value: TValue): TResult + begin + // Use Format to avoid locale issues with float conversion + Log.Add(Format('Val=%f (...%s)', [Value.xyz, text])); + Result.desc := 'done'; + end; + end; +end; + +type + TInvokeProc = reference to procedure(const Value, Res: TDataRecord); + +function CreateInvoker(Ctx: TRttiContext; CallFunc: TValue): TInvokeProc; +begin + var rttiMyProc := Ctx.GetType(CallFunc.TypeInfo); + var workerInvokeMethod := rttiMyProc.GetMethod('Invoke'); + var workerParamTypeInfo := workerInvokeMethod.GetParameters[0].ParamType.Handle; + var args: array[0..0] of TValue; + Result := + procedure(const Value, Res: TDataRecord) + begin + TValue.MakeWithoutCopy(Value.RawData, workerParamTypeInfo, args[0], true); + workerInvokeMethod.Invoke(CallFunc, args).ExtractRawData(Res.RawData); + end; +end; + +procedure Test1(const Log: TStrings); +begin + var params := TDataRecord.FromRecord; + params.SetValue('Log', Int64(Log)); + params.SetValue('text', 'The quick brown fox jumps...'); + + var workerValueParam := TDataRecord.FromRecord; + workerValueParam.SetValue('xyz', 3.14159); + + var finalResultRec := TDataRecord.FromRecord; + + var invoke: TInvokeProc; + + //////////// + + var Ctx := TRttiContext.Create; + var rttiType := Ctx.GetType(TypeInfo(TMyWorker)); + + // Find and invoke the method marked with [Factory] attribute. + for var method in rttiType.GetMethods do + begin + if method.HasAttribute then + begin + // First invocation returns the factory procedure itself (an interface). + var createFuncValue := method.Invoke(TValue.Empty, []); + // Anonymous methods are interfaces, so we get the TRttiInterfaceType. + var rttiCreateFunc := Ctx.GetType(createFuncValue.TypeInfo) as TRttiInterfaceType; + // The actual code is in the 'Invoke' method of that interface. + var factoryInvokeMethod := rttiCreateFunc.GetMethod('Invoke'); + + // Prepare the argument for the factory proc: a pointer to 'params' + var factoryArg: array[0..0] of TValue; + var factoryParamTypeInfo := factoryInvokeMethod.GetParameters[0].ParamType.Handle; + TValue.Make(params.RawData, factoryParamTypeInfo, factoryArg[0]); + + var myProcValue := factoryInvokeMethod.Invoke(createFuncValue, factoryArg); + + invoke := CreateInvoker(Ctx, myProcValue); + break; + end; + end; + + ////////// + + Assert(Assigned(invoke)); + + invoke(workerValueParam, finalResultRec); + + var desc := finalResultRec.GetValue('desc'); + Log.Add('Final result description: ' + desc); + Assert(desc = 'done'); +end; + +end. diff --git a/Src/Myc.Data.Records.pas b/Src/Myc.Data.Records.pas index fa35036..41378c1 100644 --- a/Src/Myc.Data.Records.pas +++ b/Src/Myc.Data.Records.pas @@ -15,11 +15,12 @@ type TField = record private FFieldType: TFieldType; + FTypeInfo: PtypeInfo; FName: string; FOffset: Integer; - procedure FromType(const [ref] Buffer: TBytes; SrcType: PTypeInfo; const Src); - procedure ToType(const [ref] Buffer: TBytes; DstType: PTypeInfo; var Dst); + procedure FromType(const [ref] Buffer: TBytes; const Src); + procedure ToType(const [ref] Buffer: TBytes; var Dst); procedure InitField(const [ref] Buffer: TBytes); procedure AssignField(const [ref] Dest: TBytes; const [ref] Source: TBytes); @@ -29,7 +30,7 @@ type property Offset: Integer read FOffset; - constructor Create(const AName: string; AFieldType: TFieldType; AOffset: Integer); + constructor Create(const AName: string; AFieldType: TFieldType; ATypeInfo: PTypeInfo; AOffset: Integer); function GetSize: Integer; inline; function GetAlignedSize: Integer; inline; @@ -39,6 +40,7 @@ type property Name: string read FName; property Size: Integer read GetSize; property AlignedSize: Integer read GetAlignedSize; + property TypeInfo: PTypeInfo read FTypeInfo; end; TFieldDef = record @@ -67,6 +69,7 @@ type private FLayout: TLayout; FBuffer: TBytes; + function GetRawData: Pointer; inline; public constructor Create(const ALayout: TLayout); @@ -77,14 +80,18 @@ type class function FromRecord: TDataRecord; overload; static; class function FromRecord(const Src: T): TDataRecord; overload; static; + procedure CopyFrom(const Src: T); + procedure SetValue(const Name: String; const Value: T); overload; procedure SetValue(Idx: Integer; const Value); overload; function GetValue(const Name: String): T; overload; procedure GetValue(Idx: Integer; out Value); overload; + function GetValueRef(Idx: Integer): Pointer; overload; procedure CopyValue(const SrcRec: TDataRecord; SrcIdx, DstIdx: Integer); + property RawData: Pointer read GetRawData; property Layout: TLayout read FLayout; end; @@ -113,6 +120,19 @@ begin FLayout.Fields[i].InitField(FBuffer); end; +procedure TDataRecord.CopyFrom(const Src: T); +begin + var ctx := TRttiContext.Create; + var rttiType := ctx.GetType(TypeInfo(T)); + + for var i := 0 to High(FLayout.FFields) do + begin + var P: PByte := @Src; + inc(P, FLayout.Fields[i].Offset); + FLayout.Fields[i].FromType(FBuffer, Src); + end; +end; + procedure TDataRecord.CopyValue(const SrcRec: TDataRecord; SrcIdx, DstIdx: Integer); begin Assert(SrcRec.Layout.Fields[SrcIdx].FieldType = FLayout.Fields[SrcIdx].FieldType); @@ -131,24 +151,12 @@ end; class function TDataRecord.FromRecord(const Src: T): TDataRecord; begin Result.Create(TLayout.FromRecord); + Result.CopyFrom(Src); +end; - var ctx := TRttiContext.Create; - var rttiType := ctx.GetType(TypeInfo(T)); - var rttiFields := rttiType.GetFields; - var fields: TArray; - - for var i := 0 to High(rttiFields) do - begin - var rf := rttiFields[i]; - var idx := Result.Layout.IndexOf(rf.Name); - - if (idx >= 0) then - begin - var S: PByte := @Src; - inc(S, rf.Offset); - Result.Layout.Fields[idx].FromType(Result.FBuffer, rf.FieldType.Handle, S^); - end; - end; +function TDataRecord.GetRawData: Pointer; +begin + Result := @FBuffer[0]; end; procedure TDataRecord.GetValue(Idx: Integer; out Value); @@ -183,14 +191,25 @@ function TDataRecord.GetValue(const Name: String): T; begin var idx := FLayout.IndexOf(Name); if (idx >= 0) then - FLayout.Fields[idx].ToType(FBuffer, TypeInfo(T), Result); + begin + Assert(FLayout.Fields[idx].TypeInfo = TypeInfo(T)); + FLayout.Fields[idx].ToType(FBuffer, Result); + end; +end; + +function TDataRecord.GetValueRef(Idx: Integer): Pointer; +begin + Result := @FBuffer[FLayout.Fields[Idx].Offset]; end; procedure TDataRecord.SetValue(const Name: String; const Value: T); begin var idx := FLayout.IndexOf(Name); if (idx >= 0) then - FLayout.Fields[idx].FromType(FBuffer, TypeInfo(T), Value); + begin + Assert(FLayout.Fields[idx].TypeInfo = TypeInfo(T)); + FLayout.Fields[idx].FromType(FBuffer, Value); + end; end; class operator TDataRecord.Assign(var Dest: TDataRecord; const [ref] Src: TDataRecord); @@ -211,11 +230,12 @@ begin Dest.Layout.Fields[i].FinalizeField(Dest.FBuffer); end; -constructor TDataRecord.TField.Create(const AName: string; AFieldType: TFieldType; AOffset: Integer); +constructor TDataRecord.TField.Create(const AName: string; AFieldType: TFieldType; ATypeInfo: PTypeInfo; AOffset: Integer); begin FName := AName; FFieldType := AFieldType; FOffset := AOffset; + FTypeInfo := ATypeInfo; end; procedure TDataRecord.TField.InitField(const [ref] Buffer: TBytes); @@ -246,7 +266,7 @@ begin end; end; -procedure TDataRecord.TField.FromType(const [ref] Buffer: TBytes; SrcType: PTypeInfo; const Src); +procedure TDataRecord.TField.FromType(const [ref] Buffer: TBytes; const Src); begin Assert(FOffset + Size <= Length(Buffer)); @@ -254,9 +274,9 @@ begin case FFieldType of dfFloat: begin - Assert(SrcType.Kind = tkFloat); - case GetTypeData(SrcType).FloatType of - ftSingle: PDouble(Dst)^ := PSingle(@Src)^; + Assert(FTypeInfo.Kind = tkFloat); + case GetTypeData(FTypeInfo).FloatType of + ftSingle: PSingle(Dst)^ := PSingle(@Src)^; ftDouble: PDouble(Dst)^ := PDouble(@Src)^; else Assert(false); @@ -264,8 +284,8 @@ begin end; dfInteger: begin - case SrcType.Kind of - tkInteger: PInt64(Dst)^ := PInteger(@Src)^; + case FTypeInfo.Kind of + tkInteger: PInteger(Dst)^ := PInteger(@Src)^; tkInt64: PInt64(Dst)^ := PInt64(@Src)^; else Assert(false); @@ -273,19 +293,19 @@ begin end; dfString: begin - Assert(SrcType.Kind in [tkString, tkLString, tkUString, tkWString]); + Assert(FTypeInfo.Kind in [tkString, tkLString, tkUString, tkWString]); PString(Dst)^ := PString(@Src)^; end; dfTimestamp: begin - Assert(SrcType.Kind = tkFloat); - Assert(SrcType.Name = PTypeInfo(TypeInfo(TDateTime)).Name); + Assert(FTypeInfo.Kind = tkFloat); + Assert(FTypeInfo.Name = PTypeInfo(System.TypeInfo(TDateTime)).Name); PDateTime(Dst)^ := PDateTime(@Src)^; end; dfRecord: begin - Assert(SrcType = TypeInfo(TDataRecord)); - Assert(SrcType.Name = PTypeInfo(TypeInfo(TDataRecord)).Name); + Assert(FTypeInfo = System.TypeInfo(TDataRecord)); + Assert(FTypeInfo.Name = PTypeInfo(System.TypeInfo(TDataRecord)).Name); TDataRecord(Dst^) := TDataRecord(Src); end; else @@ -303,15 +323,15 @@ begin Result := (GetSize + (Align - 1)) and not (Align - 1); end; -procedure TDataRecord.TField.ToType(const [ref] Buffer: TBytes; DstType: PTypeInfo; var Dst); +procedure TDataRecord.TField.ToType(const [ref] Buffer: TBytes; var Dst); begin var Src := @Buffer[FOffset]; case FFieldType of dfFloat: begin - Assert(DstType.Kind = tkFloat); - case GetTypeData(DstType).FloatType of - ftSingle: PSingle(@Dst)^ := PDouble(Src)^; + Assert(FTypeInfo.Kind = tkFloat); + case GetTypeData(FTypeInfo).FloatType of + ftSingle: PSingle(@Dst)^ := PSingle(Src)^; ftDouble: PDouble(@Dst)^ := PDouble(Src)^; else Assert(false); @@ -319,8 +339,8 @@ begin end; dfInteger: begin - case DstType.Kind of - tkInteger: PInteger(@Dst)^ := PInt64(Src)^; + case FTypeInfo.Kind of + tkInteger: PInteger(@Dst)^ := PInteger(Src)^; tkInt64: PInt64(@Dst)^ := PInt64(Src)^; else Assert(false); @@ -328,18 +348,18 @@ begin end; dfString: begin - Assert(DstType.Kind in [tkString, tkLString, tkUString, tkWString]); + Assert(FTypeInfo.Kind in [tkString, tkLString, tkUString, tkWString]); PString(@Dst)^ := PString(Src)^; end; dfTimestamp: begin - Assert(DstType.Kind = tkFloat); - Assert(DstType.Name = PTypeInfo(TypeInfo(TDateTime)).Name); + Assert(FTypeInfo.Kind = tkFloat); + Assert(FTypeInfo.Name = PTypeInfo(System.TypeInfo(TDateTime)).Name); PDateTime(@Dst)^ := PDateTime(Src)^; end; dfRecord: begin - Assert(DstType = TypeInfo(TDataRecord)); + Assert(FTypeInfo = System.TypeInfo(TDataRecord)); TDataRecord(Dst) := TDataRecord(Src^); end; else @@ -383,9 +403,20 @@ begin var ofs := 0; for var i := 0 to High(Fields) do begin - Fields[i] := TField.Create(Def[i].Name, Def[i].FieldType, ofs); + var ti: PTypeInfo; + case Def[i].FieldType of + dfFloat: ti := TypeInfo(Double); + dfInteger: ti := TypeInfo(Int64); + dfString: ti := TypeInfo(String); + dfTimestamp: ti := TypeInfo(TDateTime); + dfRecord: ti := TypeInfo(TDataRecord); + end; + + Fields[i] := TField.Create(Def[i].Name, Def[i].FieldType, ti, ofs); inc(ofs, Fields[i].AlignedSize); end; + + Result.Create(Fields); end; { TLayout } @@ -398,7 +429,7 @@ begin var fields: TArray; // Add each field and its value to the builder - var ofs := 0; + // The internal layout has to be the same as the original record SetLength(fields, Length(rttiFields)); for var i := 0 to High(rttiFields) do begin @@ -407,12 +438,14 @@ begin var supported := true; case rf.FieldType.TypeKind of - tkInteger, tkInt64: ft := dfInteger; + tkInt64: ft := dfInteger; tkFloat: if rf.FieldType.HasName(GetTypeName(TypeInfo(TDateTime))) then ft := dfTimestamp + else if GetTypeData(rf.FieldType.Handle).FloatType = ftDouble then + ft := dfFloat else - ft := dfFloat; + supported := false; tkString, tkWString, tkLString, tkUString: ft := dfString; tkMRecord: begin @@ -428,8 +461,7 @@ begin if not supported then raise Exception.Create('Type ' + rf.FieldType.Name + ' not supported in data records'); - fields[i] := TField.Create(rf.Name, ft, ofs); - inc(ofs, fields[i].AlignedSize); + fields[i] := TField.Create(rf.Name, ft, rf.FieldType.Handle, rf.Offset); end; Result.Create(fields); diff --git a/Src/Myc.Trade.Indicators.pas b/Src/Myc.Trade.Indicators.pas index e4e6140..ef07e11 100644 --- a/Src/Myc.Trade.Indicators.pas +++ b/Src/Myc.Trade.Indicators.pas @@ -86,25 +86,7 @@ type class function CreateMean: TConvertFunc, Double>; static; end; - TIndicatorFactory = class - type - TFunc = TConvertFunc; - private - FParams: TDataRecord.TLayout; - FInput: TDataRecord.TLayout; - FOutput: TDataRecord.TLayout; - - public - constructor Create(const AParams, AInput, AOutput: TDataRecord.TLayout); - - function CreateIndicator(const Params: TDataRecord): TFunc; virtual; abstract; - - property Params: TDataRecord.TLayout read FParams; - property Input: TDataRecord.TLayout read FInput; - property Output: TDataRecord.TLayout read FOutput; - end; - - TEMA = class(TIndicatorFactory) + TEMA = class type TParam = record Period: Integer; @@ -119,35 +101,9 @@ type end; public - constructor Create; - function CreateIndicator(const Params: TDataRecord): TIndicatorFactory.TFunc; override; + class function CreateEMA(const Param: TParam): TConvertFunc; static; end; - TMACD = class - type - TParam = record - Fast: TConvertFunc; - Slow: TConvertFunc; - Signal: TConvertFunc; - end; - - TInput = record - Price: Double; - end; - - TResult = record - MacdLine: Double; - SignalLine: Double; - Histogram: Double; - end; - - public - class function CreateMACD(const Param: TParam): TConvertFunc; static; - end; - -var - Registry: TList; - implementation { TIndicators } @@ -548,79 +504,36 @@ begin end; end; -// Creates a MACD indicator from three provided moving average functions. -class function TMACD.CreateMACD(const Param: TParam): TConvertFunc; +class function TEMA.CreateEMA(const Param: TParam): TConvertFunc; begin - Result := - function(const Input: TInput): TResult - var - fastVal, slowVal: Double; - begin - fastVal := Param.Fast(Input.Price); - slowVal := Param.Slow(Input.Price); + var Period := Param.Period; + var lastEma: Double := Double.NaN; + var sourceData: TSeries; + var multiplier := 2 / (Period + 1); - if IsNan(slowVal) then // slowVal will be the last one to become non-NaN + Result := + function(const Value: TInput): TResult + begin + sourceData.Add(Value.Price, Period); + + if (sourceData.Count < Period) then begin - Result.MacdLine := Double.NaN; - Result.SignalLine := Double.NaN; - Result.Histogram := Double.NaN; + Result.MA := Double.NaN; + Exit; + end; + + if not IsNan(lastEma) then + begin + // Subsequent EMA calculation + lastEma := (Value.Price - lastEma) * multiplier + lastEma; end else begin - Result.MacdLine := fastVal - slowVal; - Result.SignalLine := Param.Signal(Result.MacdLine); - if not IsNan(Result.SignalLine) then - Result.Histogram := Result.MacdLine - Result.SignalLine - else - Result.Histogram := Double.NaN; + // First EMA is a SMA of the initial period + lastEma := TIndicators.CalculateSMA(sourceData, Period); end; + Result.MA := lastEma; end; end; -constructor TEMA.Create; -begin - inherited - Create(TDataRecord.TLayout.FromRecord, TDataRecord.TLayout.FromRecord, TDataRecord.TLayout.FromRecord) -end; - -function TEMA.CreateIndicator(const Params: TDataRecord): TIndicatorFactory.TFunc; -begin - var Period := Params.GetValue('Period'); - - var Input := TDataRecord.TLayout.FromRecord; - var inPrice := Input.IndexOf('Price'); - - var Output := TDataRecord.TLayout.FromRecord; - var outMA := Output.IndexOf('MA'); - - var CalcEMA := TIndicators.CreateEMA(Period); - - Result := - function(const Input: TDataRecord): TDataRecord - begin - var Price: Double; - Input.GetValue(inPrice, Price); - - var MA := CalcEMA(Price); - - Result := TDataRecord.FromRecord; - Result.SetValue(outMA, MA); - end; -end; - -constructor TIndicatorFactory.Create(const AParams, AInput, AOutput: TDataRecord.TLayout); -begin - inherited Create; - FParams := AParams; - FInput := AInput; - FOutput := AOutput; -end; - -initialization - Registry := TList.Create; - Registry.Add(TEMA.Create); - -finalization - Registry.Free; - end. diff --git a/Test/TestDataRecord.pas b/Test/TestDataRecord.pas index 18a017f..8aad4ef 100644 --- a/Test/TestDataRecord.pas +++ b/Test/TestDataRecord.pas @@ -219,7 +219,7 @@ begin // Setup: Create the outer record containing the nested TDataRecord outerSrc.MyNestedRecord := nestedDataRecord; - // Place a record on the heap. FAILS: This erases the nested record data!! + // Place a record on the heap. FAILS: This erases the nested record data!! -- Fixed 25.07.2025! TDataRecord.FromRecord(outerSrc); // Test: Create the main TDataRecord from the outer record instance