diff --git a/AuraTrader/MainForm.pas b/AuraTrader/MainForm.pas index 82fcf1a..653c0f2 100644 --- a/AuraTrader/MainForm.pas +++ b/AuraTrader/MainForm.pas @@ -467,7 +467,7 @@ begin //////////// var EMAFactory := TEMA.Create; - var Params := TDataRecord.CreateFrom(EMAFactory.Params); + var Params := TDataRecord.Create(EMAFactory.Params); Params.SetValue('Period', 20); var indi := EMAFActory.CreateIndicator(Params); @@ -676,7 +676,7 @@ begin //////////// var EMAFactory := TEMA.Create; - var Params := TDataRecord.CreateFrom(EMAFactory.Params); + var Params := TDataRecord.Create(EMAFactory.Params); Params.SetValue('Period', 20); var indi := EMAFActory.CreateIndicator(Params); diff --git a/Src/Myc.DataRecord.Impl.pas b/Src/Myc.DataRecord.Impl.pas deleted file mode 100644 index 39285ae..0000000 --- a/Src/Myc.DataRecord.Impl.pas +++ /dev/null @@ -1,89 +0,0 @@ -unit Myc.DataRecord.Impl; - -interface - -uses - System.SysUtils, - System.Rtti, - System.TypInfo, - System.Generics.Collections, - System.Generics.Defaults, - Myc.DataRecord; - -type - TFieldLayoutComparer = class(TComparer) - strict private - class var - FDefaultComparer: IComparer; - class constructor CreateClass; - public - function Compare(const Left, Right: TDataRecord.TField): Integer; override; - class property DefaultComparer: IComparer read FDefaultComparer; - end; - -implementation - -(* -procedure TIntegerField.GetRaw(const [ref] Buffer: TBytes; Dst: Pointer); -begin - PInteger(Dst)^ := PInteger(GetRawRef(Buffer))^; -end; - -procedure TIntegerField.SetRaw(const [ref] Buffer: TBytes; Src: Pointer); -begin - PInteger(GetRawRef(Buffer))^ := PInteger(Src)^; -end; - -procedure TSingleField.GetRaw(const [ref] Buffer: TBytes; Dst: Pointer); -begin - PSingle(Dst)^ := PSingle(GetRawRef(Buffer))^; -end; - -procedure TSingleField.SetRaw(const [ref] Buffer: TBytes; Src: Pointer); -begin - PSingle(GetRawRef(Buffer))^ := PSingle(Src)^; -end; - -procedure TDoubleField.GetRaw(const [ref] Buffer: TBytes; Dst: Pointer); -begin - PDouble(Dst)^ := PDouble(GetRawRef(Buffer))^; -end; - -procedure TDoubleField.SetRaw(const [ref] Buffer: TBytes; Src: Pointer); -begin - PDouble(GetRawRef(Buffer))^ := PDouble(Src)^; -end; - -procedure TInt64Field.GetRaw(const [ref] Buffer: TBytes; Dst: Pointer); -begin - PInt64(Dst)^ := PInt64(GetRawRef(Buffer))^; -end; - -procedure TInt64Field.SetRaw(const [ref] Buffer: TBytes; Src: Pointer); -begin - PInt64(GetRawRef(Buffer))^ := PInt64(Src)^; -end; - -procedure TStringField.GetRaw(const [ref] Buffer: TBytes; Dst: Pointer); -begin - PString(Dst)^ := PString(GetRawRef(Buffer))^; -end; - -procedure TStringField.SetRaw(const [ref] Buffer: TBytes; Src: Pointer); -begin - PString(GetRawRef(Buffer))^ := PString(Src)^; -end; -*) -{ TFieldLayoutComparer } - -class constructor TFieldLayoutComparer.CreateClass; -begin - FDefaultComparer := TFieldLayoutComparer.Create; -end; - -function TFieldLayoutComparer.Compare(const Left, Right: TDataRecord.TField): Integer; -begin - Result := TComparer.Default.Compare(Left.Name, Right.Name); -end; - -end. diff --git a/Src/Myc.DataRecord.pas b/Src/Myc.DataRecord.pas index e7dd3af..1d6c790 100644 --- a/Src/Myc.DataRecord.pas +++ b/Src/Myc.DataRecord.pas @@ -9,56 +9,72 @@ uses System.TypInfo; type - TDataFieldType = (dfFloat, dfInteger, dfString, dfTimestamp); - TDataRecord = record public type + TFieldType = (dfFloat, dfInteger, dfString, dfTimestamp); + TField = record private - FFieldType: TDataFieldType; + FFieldType: TFieldType; FName: string; FOffset: Integer; - FSize: Integer; - procedure Write(SrcType: PTypeInfo; const Src; P: Pointer); - procedure Read(Src: Pointer; DstType: PTypeInfo; var Dst); - procedure Finalize(P: Pointer); + procedure FromType(const [ref] Buffer: TBytes; SrcType: PTypeInfo; const Src); + procedure ToType(const [ref] Buffer: TBytes; DstType: PTypeInfo; var Dst); + + procedure Assign(const [ref] Buffer: TBytes; const Src); overload; + procedure Finalize(const [ref] Buffer: TBytes); + property Offset: Integer read FOffset; - property Size: Integer read FSize; + + constructor Create(const AName: string; AFieldType: TFieldType; AOffset: Integer); + + function GetSize: Integer; inline; + function GetAlignedSize: Integer; inline; public - constructor Create(const AName: string; AFieldType: TDataFieldType; AOffset, ASize: Integer); - - class function From(const AName: string; Offset: Integer): TField; overload; static; - class function From(const AName: string; const RttiType: TRttiType; Offset: Integer): TField; overload; static; - - property FieldType: TDataFieldType read FFieldType; + property FieldType: TFieldType read FFieldType; property Name: string read FName; + property Size: Integer read GetSize; + property AlignedSize: Integer read GetAlignedSize; + end; + + TFieldDef = record + public + Name: String; + FieldType: TFieldType end; TLayout = record private FFields: TArray; - public + constructor Create(const AFields: TArray); - class function CreateFrom: TLayout; static; - function GetBufferSize: Integer; + public + class function FromRecord: TLayout; static; + class function Construct(const Def: TArray): TLayout; static; + function IndexOf(const Name: String): Integer; property Fields: TArray read FFields; end; + const + Align = 8; + private FLayout: TLayout; FBuffer: TBytes; - public - constructor Create(const ALayout: TLayout; const ABuffer: TBytes); + const + DataSize: array[TFieldType] of Integer = (sizeof(Double), sizeof(Int64), sizeof(String), sizeof(TDateTime)); - class function CreateFrom: TDataRecord; overload; static; - class function CreateFrom(const Src: T): TDataRecord; overload; static; - class function CreateFrom(const Layout: TLayout): TDataRecord; overload; static; - procedure CopyFrom(const Src: T); + public + constructor Create(const ALayout: TLayout; const ABuffer: TBytes = nil); + class operator Finalize(var Dest: TDataRecord); + + class function FromRecord: TDataRecord; overload; static; + class function FromRecord(const Src: T): TDataRecord; overload; static; procedure SetValue(const Name: String; const Value: T); overload; procedure SetValue(Idx: Integer; const Value); overload; @@ -66,7 +82,7 @@ type function GetValue(const Name: String): T; overload; procedure GetValue(Idx: Integer; out Value); overload; - class operator Finalize(var Dest: TDataRecord); + procedure CopyField(Idx: Integer; Dst: TDataRecord; DstIdx: Integer); property Layout: TLayout read FLayout; end; @@ -74,17 +90,41 @@ type implementation uses - System.Classes, - Myc.DataRecord.Impl; + System.Classes; -constructor TDataRecord.Create(const ALayout: TLayout; const ABuffer: TBytes); +constructor TDataRecord.Create(const ALayout: TLayout; const ABuffer: TBytes = nil); begin FLayout := ALayout; FBuffer := ABuffer; + + var bufSize := 0; + if Length(FLayout.Fields) > 0 then + with FLayout.Fields[High(FLayout.Fields)] do + bufSize := Offset + AlignedSize; + + if FBuffer = nil then + SetLength(FBuffer, bufSize) + else + Assert(Length(FBuffer) >= bufSize); end; -procedure TDataRecord.CopyFrom(const Src: T); +procedure TDataRecord.CopyField(Idx: Integer; Dst: TDataRecord; DstIdx: Integer); begin + Assert(Dst.Layout.Fields[DstIdx].FieldType = FLayout.Fields[Idx].FieldType); + Dst.Layout.Fields[DstIdx].Assign(Dst.FBuffer, FBuffer[FLayout.Fields[DstIdx].Offset]) +end; + +{ TDataRecord } + +class function TDataRecord.FromRecord: TDataRecord; +begin + Result := TDataRecord.Create(TLayout.FromRecord); +end; + +class function TDataRecord.FromRecord(const Src: T): TDataRecord; +begin + Result := FromRecord; + var ctx := TRttiContext.Create; var rttiType := ctx.GetType(TypeInfo(T)); var rttiFields := rttiType.GetFields; @@ -93,41 +133,17 @@ begin for var i := 0 to High(rttiFields) do begin var rf := rttiFields[i]; - var idx := FLayout.IndexOf(rf.Name); + var idx := Result.Layout.IndexOf(rf.Name); if (idx >= 0) then begin - var fld := FLayout.Fields[idx]; - var S: PByte := @Src; inc(S, rf.Offset); - var P := @FBuffer[FLayout.Fields[idx].Offset]; - - fld.Write(rf.FieldType.Handle, S^, P); + Result.Layout.Fields[idx].FromType(Result.FBuffer, rf.FieldType.Handle, S^); end; end; end; -class function TDataRecord.CreateFrom(const Layout: TLayout): TDataRecord; -begin - var buf: TBytes; - SetLength(buf, Layout.GetBufferSize); - Result.Create(Layout, buf); -end; - -{ TDataRecord } - -class function TDataRecord.CreateFrom: TDataRecord; -begin - Result := CreateFrom(TLayout.CreateFrom); -end; - -class function TDataRecord.CreateFrom(const Src: T): TDataRecord; -begin - Result := CreateFrom; - Result.CopyFrom(Src); -end; - procedure TDataRecord.GetValue(Idx: Integer; out Value); begin var P := @FBuffer[FLayout.Fields[Idx].Offset]; @@ -158,51 +174,56 @@ function TDataRecord.GetValue(const Name: String): T; begin var idx := FLayout.IndexOf(Name); if (idx >= 0) then - FLayout.Fields[idx].Read(@FBuffer[FLayout.Fields[idx].Offset], TypeInfo(T), Result); + FLayout.Fields[idx].ToType(FBuffer, TypeInfo(T), Result); end; procedure TDataRecord.SetValue(const Name: String; const Value: T); begin var idx := FLayout.IndexOf(Name); if (idx >= 0) then - FLayout.Fields[idx].Write(TypeInfo(T), Value, @FBuffer[FLayout.Fields[idx].Offset]); + FLayout.Fields[idx].FromType(FBuffer, TypeInfo(T), Value); end; class operator TDataRecord.Finalize(var Dest: TDataRecord); begin for var i := 0 to High(Dest.FLayout.Fields) do - Dest.FLayout.Fields[i].Finalize(@Dest.FBuffer[Dest.FLayout.Fields[i].Offset]); + Dest.FLayout.Fields[i].Finalize(Dest.FBuffer); end; -constructor TDataRecord.TField.Create(const AName: string; AFieldType: TDataFieldType; AOffset, ASize: Integer); +constructor TDataRecord.TField.Create(const AName: string; AFieldType: TFieldType; AOffset: Integer); begin FName := AName; FFieldType := AFieldType; FOffset := AOffset; - FSize := ASize; end; -procedure TDataRecord.TField.Finalize(P: Pointer); +procedure TDataRecord.TField.Finalize(const [ref] Buffer: TBytes); begin + if not (FFieldType in [dfString]) then + exit; + + Assert(FOffset + Size <= Length(Buffer)); + + var P := @Buffer[FOffset]; case FFieldType of - dfFloat: PDouble(P)^ := 0; - dfInteger: PInt64(P)^ := 0; dfString: PString(P)^ := ''; - dfTimestamp: PDateTime(P)^ := 0; - else - Assert(false); end; end; -procedure TDataRecord.TField.Write(SrcType: PTypeInfo; const Src; P: Pointer); +procedure TDataRecord.TField.FromType(const [ref] Buffer: TBytes; SrcType: PTypeInfo; const Src); begin + Assert(FOffset + Size <= Length(Buffer)); + + Finalize(Buffer); + + var Dst := @Buffer[FOffset]; case FFieldType of dfFloat: begin Assert(SrcType.Kind = tkFloat); case GetTypeData(SrcType).FloatType of - ftSingle: PDouble(P)^ := PSingle(@Src)^; - ftDouble: PDouble(P)^ := PDouble(@Src)^; + ftSingle: PDouble(Dst)^ := PSingle(@Src)^; + ftDouble: PDouble(Dst)^ := PDouble(@Src)^; else Assert(false); end; @@ -210,8 +231,8 @@ begin dfInteger: begin case SrcType.Kind of - tkInteger: PInt64(P)^ := PInteger(@Src)^; - tkInt64: PInt64(P)^ := PInt64(@Src)^; + tkInteger: PInt64(Dst)^ := PInteger(@Src)^; + tkInt64: PInt64(Dst)^ := PInt64(@Src)^; else Assert(false); end; @@ -219,41 +240,31 @@ begin dfString: begin Assert(SrcType.Kind in [tkString, tkLString, tkUString, tkWString]); - PString(P)^ := PString(@Src)^; + PString(Dst)^ := PString(@Src)^; end; dfTimestamp: begin Assert(SrcType.Kind = tkFloat); - PDateTime(P)^ := PDateTime(@Src)^; + PDateTime(Dst)^ := PDateTime(@Src)^; end; else Assert(false); end; end; -class function TDataRecord.TField.From(const AName: string; const RttiType: TRttiType; Offset: Integer): TField; +function TDataRecord.TField.GetSize: Integer; begin - case rttiType.TypeKind of - tkInteger, tkInt64: Result.Create(AName, dfInteger, Offset, sizeof(Int64)); - tkFloat: - if (rttiType.Name = 'TDateTime') then - Result.Create(AName, dfTimestamp, Offset, sizeof(TDateTime)) - else - Result.Create(AName, dfFloat, Offset, sizeof(Double)); - tkString, tkWString, tkLString, tkUString: Result.Create(AName, dfString, Offset, sizeof(String)); - else - raise Exception.Create('Type not supported'); - end; + Result := DataSize[FFieldType]; end; -class function TDataRecord.TField.From(const AName: string; Offset: Integer): TField; +function TDataRecord.TField.GetAlignedSize: Integer; begin - var ctx := TRttiContext.Create; - Result := From(AName, ctx.GetType(TypeInfo(T)), Offset); + Result := (GetSize + (Align - 1)) and not (Align - 1); end; -procedure TDataRecord.TField.Read(Src: Pointer; DstType: PTypeInfo; var Dst); +procedure TDataRecord.TField.ToType(const [ref] Buffer: TBytes; DstType: PTypeInfo; var Dst); begin + var Src := @Buffer[FOffset]; case FFieldType of dfFloat: begin @@ -289,14 +300,44 @@ begin end; end; +procedure TDataRecord.TField.Assign(const [ref] Buffer: TBytes; const Src); +begin + Assert(FOffset + Size <= Length(Buffer)); + + Finalize(Buffer); + + var Dst := @Buffer[FOffset]; + case FFieldType of + dfFloat: PDouble(Dst)^ := PDouble(@Src)^; + dfInteger: PInt64(Dst)^ := PInt64(@Src)^; + dfString: PString(Dst)^ := PString(@Src)^; + dfTimestamp: PDateTime(Dst)^ := PDateTime(@Src)^; + else + Assert(false); + end; +end; + constructor TDataRecord.TLayout.Create(const AFields: TArray); begin FFields := AFields; end; +class function TDataRecord.TLayout.Construct(const Def: TArray): TLayout; +var + Fields: TArray; +begin + SetLength(Fields, Length(Def)); + var ofs := 0; + for var i := 0 to High(Fields) do + begin + Fields[i] := TField.Create(Def[i].Name, Def[i].FieldType, ofs); + inc(ofs, Fields[i].AlignedSize); + end; +end; + { TLayout } -class function TDataRecord.TLayout.CreateFrom: TLayout; +class function TDataRecord.TLayout.FromRecord: TLayout; begin var ctx := TRttiContext.Create; var rttiType := ctx.GetType(TypeInfo(T)); @@ -309,24 +350,25 @@ begin for var i := 0 to High(rttiFields) do begin var rf := rttiFields[i]; - fields[i] := TField.From(rf.Name, rf.FieldType, ofs); - inc(ofs, fields[i].Size); - ofs := (ofs + 7) and not 7; + + case rf.FieldType.TypeKind of + tkInteger, tkInt64: fields[i] := TField.Create(rf.Name, dfInteger, ofs); + tkFloat: + if SameText(rf.FieldType.Name, 'TDateTime') then + fields[i] := TField.Create(rf.Name, dfTimestamp, ofs) + else + fields[i] := TField.Create(rf.Name, dfFloat, ofs); + tkString, tkWString, tkLString, tkUString: fields[i] := TField.Create(rf.Name, dfString, ofs); + else + raise Exception.Create('Type ' + rf.FieldType.Name + ' not supported in data records'); + end; + + inc(ofs, fields[i].AlignedSize); end; Result.Create(fields); end; -function TDataRecord.TLayout.GetBufferSize: Integer; -begin - if (Length(FFields) = 0) then - exit(0); - - var n := High(FFields); - Result := FFields[n].FOffset + FFields[n].Size; - Result := (Result + 7) and not 7; -end; - function TDataRecord.TLayout.IndexOf(const Name: String): Integer; begin for var i := 0 to High(FFields) do diff --git a/Src/Myc.Trade.DataPoint.Impl.pas b/Src/Myc.Trade.DataPoint.Impl.pas index f2fa550..cf08671 100644 --- a/Src/Myc.Trade.DataPoint.Impl.pas +++ b/Src/Myc.Trade.DataPoint.Impl.pas @@ -217,13 +217,6 @@ type // Endpoint that collects data into a series. TMycDataJoin = class(TInterfacedObject, TDataProvider>.IDataProvider) - type - PItem = ^TItem; - TItem = record - Next: PItem; - Value: T; - end; - private FReceivers: array of record DataProvider: TDataProvider; diff --git a/Src/Myc.Trade.DataPoint.pas b/Src/Myc.Trade.DataPoint.pas index 060eb50..0ca7d3c 100644 --- a/Src/Myc.Trade.DataPoint.pas +++ b/Src/Myc.Trade.DataPoint.pas @@ -124,6 +124,8 @@ type class function FieldToRecord(const Layout: TDataRecord.TLayout; const Name: String): TConverter; static; class function FieldOfRecord(const Layout: TDataRecord.TLayout; const Name: String): TConverter; static; + class function Join(const DataProviders: TArray>): TDataProvider>; static; + type TRecordMapping = record Layout: TDataRecord.TLayout; @@ -137,7 +139,11 @@ type const Output: TDataRecord.TLayout ): TConverter, TDataRecord>; static; - class function Join(const DataProviders: TArray>): TDataProvider>; static; + class function JoinRecords( + const TargetLayout: TDataRecord.TLayout; + const Mapping: TArray; + const DataProviders: TArray> + ): TConverter, TDataRecord>; static; end; implementation @@ -357,18 +363,11 @@ begin Result := TConverter, TDataRecord>.CreateGeneric( function(const Inputs: TArray): TDataRecord - var - tmp: array[0..63] of Byte; begin - Result := TDataRecord.CreateFrom(Output); + Result := TDataRecord.Create(Output); for var i := 0 to High(Idxs) do - begin for var j := 0 to High(Idxs[i]) do - begin - Inputs[i].GetValue(Idxs[i][j].FromIdx, tmp); - Result.SetValue(Idxs[i][j].ToIdx, tmp); - end; - end; + Inputs[i].CopyField(Idxs[i][j].FromIdx, Result, Idxs[i][j].ToIdx); end ); end; @@ -386,7 +385,7 @@ begin TConverter.CreateGeneric( function(const Value: T): TDataRecord begin - Result := TDataRecord.CreateFrom(Layout); + Result := TDataRecord.Create(Layout); Result.SetValue(idx, Value); end ); @@ -397,6 +396,19 @@ begin Result := TMycDataJoin.Create(DataProviders); end; +class function TConverter.JoinRecords( + const TargetLayout: TDataRecord.TLayout; + const Mapping: TArray; + const DataProviders: TArray> +): TConverter, TDataRecord>; +begin + var RecProvider := Join(DataProviders); + + Result := DataMapping(Mapping, TargetLayout); + + RecProvider.Link(Result); +end; + class function TConverter.Parallel(Parent: TDataProvider): TConverter; begin Result := TMycParallelConverter.Create; diff --git a/Src/Myc.Trade.Indicators.pas b/Src/Myc.Trade.Indicators.pas index 3508e1a..99e0484 100644 --- a/Src/Myc.Trade.Indicators.pas +++ b/Src/Myc.Trade.Indicators.pas @@ -579,17 +579,17 @@ end; constructor TEMA.Create; begin inherited - Create(TDataRecord.TLayout.CreateFrom, TDataRecord.TLayout.CreateFrom, TDataRecord.TLayout.CreateFrom) + 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.CreateFrom; + var Input := TDataRecord.TLayout.FromRecord; var inPrice := Input.IndexOf('Price'); - var Output := TDataRecord.TLayout.CreateFrom; + var Output := TDataRecord.TLayout.FromRecord; var outMA := Output.IndexOf('MA'); var CalcEMA := TIndicators.CreateEMA(Period); @@ -602,7 +602,7 @@ begin var MA := CalcEMA(Price); - Result := TDataRecord.CreateFrom(Output); + Result := TDataRecord.FromRecord; Result.SetValue(outMA, MA); end; end; diff --git a/Test/TestDataRecord.pas b/Test/TestDataRecord.pas index e17f62d..0c0e22b 100644 --- a/Test/TestDataRecord.pas +++ b/Test/TestDataRecord.pas @@ -6,6 +6,7 @@ uses DunitX.TestFramework, System.SysUtils, System.DateUtils, + System.Rtti, Myc.DataRecord; type @@ -21,56 +22,54 @@ type [TestFixture] TTestDataRecord = class(TObject) + private + FCtx: TRttiContext; + public [Test] + [IgnoreMemoryLeaks] procedure TestLayoutCreation; [Test] + [IgnoreMemoryLeaks] procedure TestCreateFromInstanceAndGetValue; [Test] + [IgnoreMemoryLeaks] procedure TestSetValueAndGetValue; end; implementation -uses - System.Rtti; - { TTestDataRecord } procedure TTestDataRecord.TestLayoutCreation; var - layout: TDataRecord.TRecordLayout; + layout: TDataRecord.TLayout; idx: Integer; - expectedSize: Integer; begin // Test: Create layout from a record type - layout := TDataRecord.TRecordLayout.CreateFrom; + layout := TDataRecord.TLayout.FromRecord; // Assert: Check field count Assert.IsTrue(Length(layout.Fields) = 6, 'Layout should have 6 fields'); // Assert: Check specific fields for correct type mapping + idx := layout.IndexOf('MyTimestamp'); + Assert.IsTrue(idx > -1, 'Field MyTimestamp not found'); + Assert.AreEqual(TDataRecord.TFieldType.dfTimestamp, layout.Fields[idx].FieldType, 'MyTimestamp should be mapped to dfTimestamp'); + idx := layout.IndexOf('MyInteger'); Assert.IsTrue(idx > -1, 'Field MyInteger not found'); - Assert.AreEqual(TDataFieldType.dfInteger, layout.Fields[idx].FieldType, 'MyInteger should be mapped to dfInteger'); + Assert.AreEqual(TDataRecord.TFieldType.dfInteger, layout.Fields[idx].FieldType, 'MyInteger should be mapped to dfInteger'); idx := layout.IndexOf('MyString'); Assert.IsTrue(idx > -1, 'Field MyString not found'); - Assert.AreEqual(TDataFieldType.dfString, layout.Fields[idx].FieldType, 'MyString should be mapped to dfString'); + Assert.AreEqual(TDataRecord.TFieldType.dfString, layout.Fields[idx].FieldType, 'MyString should be mapped to dfString'); idx := layout.IndexOf('MySingle'); Assert.IsTrue(idx > -1, 'Field MySingle not found'); - Assert.AreEqual(TDataFieldType.dfFloat, layout.Fields[idx].FieldType, 'MySingle should be mapped to dfFloat'); - - idx := layout.IndexOf('MyTimestamp'); - Assert.IsTrue(idx > -1, 'Field MyTimestamp not found'); - Assert.AreEqual(TDataFieldType.dfTimestamp, layout.Fields[idx].FieldType, 'MyTimestamp should be mapped to dfTimestamp'); - - // Assert: Check total buffer size (6 fields * 8 bytes each on x64 due to type promotion and alignment) - expectedSize := (sizeof(Int64) * 2) + (sizeof(Double) * 2) + sizeof(String) + sizeof(TDateTime); - Assert.AreEqual(expectedSize, layout.GetBufferSize, 'Buffer size calculation is incorrect'); + Assert.AreEqual(TDataRecord.TFieldType.dfFloat, layout.Fields[idx].FieldType, 'MySingle should be mapped to dfFloat'); end; procedure TTestDataRecord.TestCreateFromInstanceAndGetValue; @@ -87,7 +86,7 @@ begin srcRecord.MyTimestamp := EncodeDateTime(2024, 7, 19, 10, 30, 0, 0); // Test: Create a TDataRecord from the source record instance - dataRecord := TDataRecord.CreateFrom(srcRecord); + dataRecord := TDataRecord.FromRecord(srcRecord); // Assert: Retrieve each value and check for correctness Assert.AreEqual(srcRecord.MyInt64, dataRecord.GetValue('MyInt64'), 'Int64 value mismatch'); @@ -114,7 +113,7 @@ var testTimestamp: TDateTime; begin // Setup: Create an empty TDataRecord with the layout of TMyTestRecord - dataRecord := TDataRecord.CreateFrom; + dataRecord := TDataRecord.FromRecord; // Setup: Define test values testInt := 54321;