Reworking DataRecords and DataFlow

This commit is contained in:
Michael Schimmel
2025-07-22 14:44:48 +02:00
parent bf4ef71cba
commit 5b8b475da7
7 changed files with 191 additions and 234 deletions
+2 -2
View File
@@ -467,7 +467,7 @@ begin
//////////// ////////////
var EMAFactory := TEMA.Create; var EMAFactory := TEMA.Create;
var Params := TDataRecord.CreateFrom(EMAFactory.Params); var Params := TDataRecord.Create(EMAFactory.Params);
Params.SetValue<Integer>('Period', 20); Params.SetValue<Integer>('Period', 20);
var indi := EMAFActory.CreateIndicator(Params); var indi := EMAFActory.CreateIndicator(Params);
@@ -676,7 +676,7 @@ begin
//////////// ////////////
var EMAFactory := TEMA.Create; var EMAFactory := TEMA.Create;
var Params := TDataRecord.CreateFrom(EMAFactory.Params); var Params := TDataRecord.Create(EMAFactory.Params);
Params.SetValue<Integer>('Period', 20); Params.SetValue<Integer>('Period', 20);
var indi := EMAFActory.CreateIndicator(Params); var indi := EMAFActory.CreateIndicator(Params);
-89
View File
@@ -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<TDataRecord.TField>)
strict private
class var
FDefaultComparer: IComparer<TDataRecord.TField>;
class constructor CreateClass;
public
function Compare(const Left, Right: TDataRecord.TField): Integer; override;
class property DefaultComparer: IComparer<TDataRecord.TField> 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<string>.Default.Compare(Left.Name, Right.Name);
end;
end.
+144 -102
View File
@@ -9,56 +9,72 @@ uses
System.TypInfo; System.TypInfo;
type type
TDataFieldType = (dfFloat, dfInteger, dfString, dfTimestamp);
TDataRecord = record TDataRecord = record
public public
type type
TFieldType = (dfFloat, dfInteger, dfString, dfTimestamp);
TField = record TField = record
private private
FFieldType: TDataFieldType; FFieldType: TFieldType;
FName: string; FName: string;
FOffset: Integer; FOffset: Integer;
FSize: Integer;
procedure Write(SrcType: PTypeInfo; const Src; P: Pointer); procedure FromType(const [ref] Buffer: TBytes; SrcType: PTypeInfo; const Src);
procedure Read(Src: Pointer; DstType: PTypeInfo; var Dst); procedure ToType(const [ref] Buffer: TBytes; DstType: PTypeInfo; var Dst);
procedure Finalize(P: Pointer);
procedure Assign(const [ref] Buffer: TBytes; const Src); overload;
procedure Finalize(const [ref] Buffer: TBytes);
property Offset: Integer read FOffset; 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 public
constructor Create(const AName: string; AFieldType: TDataFieldType; AOffset, ASize: Integer); property FieldType: TFieldType read FFieldType;
class function From<T>(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 Name: string read FName; property Name: string read FName;
property Size: Integer read GetSize;
property AlignedSize: Integer read GetAlignedSize;
end;
TFieldDef = record
public
Name: String;
FieldType: TFieldType
end; end;
TLayout = record TLayout = record
private private
FFields: TArray<TField>; FFields: TArray<TField>;
public
constructor Create(const AFields: TArray<TField>); constructor Create(const AFields: TArray<TField>);
class function CreateFrom<T>: TLayout; static; public
function GetBufferSize: Integer; class function FromRecord<T>: TLayout; static;
class function Construct(const Def: TArray<TFieldDef>): TLayout; static;
function IndexOf(const Name: String): Integer; function IndexOf(const Name: String): Integer;
property Fields: TArray<TField> read FFields; property Fields: TArray<TField> read FFields;
end; end;
const
Align = 8;
private private
FLayout: TLayout; FLayout: TLayout;
FBuffer: TBytes; FBuffer: TBytes;
public const
constructor Create(const ALayout: TLayout; const ABuffer: TBytes); DataSize: array[TFieldType] of Integer = (sizeof(Double), sizeof(Int64), sizeof(String), sizeof(TDateTime));
class function CreateFrom<T>: TDataRecord; overload; static; public
class function CreateFrom<T>(const Src: T): TDataRecord; overload; static; constructor Create(const ALayout: TLayout; const ABuffer: TBytes = nil);
class function CreateFrom(const Layout: TLayout): TDataRecord; overload; static; class operator Finalize(var Dest: TDataRecord);
procedure CopyFrom<T>(const Src: T);
class function FromRecord<T>: TDataRecord; overload; static;
class function FromRecord<T>(const Src: T): TDataRecord; overload; static;
procedure SetValue<T>(const Name: String; const Value: T); overload; procedure SetValue<T>(const Name: String; const Value: T); overload;
procedure SetValue(Idx: Integer; const Value); overload; procedure SetValue(Idx: Integer; const Value); overload;
@@ -66,7 +82,7 @@ type
function GetValue<T>(const Name: String): T; overload; function GetValue<T>(const Name: String): T; overload;
procedure GetValue(Idx: Integer; out Value); 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; property Layout: TLayout read FLayout;
end; end;
@@ -74,17 +90,41 @@ type
implementation implementation
uses uses
System.Classes, System.Classes;
Myc.DataRecord.Impl;
constructor TDataRecord.Create(const ALayout: TLayout; const ABuffer: TBytes); constructor TDataRecord.Create(const ALayout: TLayout; const ABuffer: TBytes = nil);
begin begin
FLayout := ALayout; FLayout := ALayout;
FBuffer := ABuffer; 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; end;
procedure TDataRecord.CopyFrom<T>(const Src: T); procedure TDataRecord.CopyField(Idx: Integer; Dst: TDataRecord; DstIdx: Integer);
begin 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<T>: TDataRecord;
begin
Result := TDataRecord.Create(TLayout.FromRecord<T>);
end;
class function TDataRecord.FromRecord<T>(const Src: T): TDataRecord;
begin
Result := FromRecord<T>;
var ctx := TRttiContext.Create; var ctx := TRttiContext.Create;
var rttiType := ctx.GetType(TypeInfo(T)); var rttiType := ctx.GetType(TypeInfo(T));
var rttiFields := rttiType.GetFields; var rttiFields := rttiType.GetFields;
@@ -93,41 +133,17 @@ begin
for var i := 0 to High(rttiFields) do for var i := 0 to High(rttiFields) do
begin begin
var rf := rttiFields[i]; var rf := rttiFields[i];
var idx := FLayout.IndexOf(rf.Name); var idx := Result.Layout.IndexOf(rf.Name);
if (idx >= 0) then if (idx >= 0) then
begin begin
var fld := FLayout.Fields[idx];
var S: PByte := @Src; var S: PByte := @Src;
inc(S, rf.Offset); inc(S, rf.Offset);
var P := @FBuffer[FLayout.Fields[idx].Offset]; Result.Layout.Fields[idx].FromType(Result.FBuffer, rf.FieldType.Handle, S^);
fld.Write(rf.FieldType.Handle, S^, P);
end; end;
end; 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<T>: TDataRecord;
begin
Result := CreateFrom(TLayout.CreateFrom<T>);
end;
class function TDataRecord.CreateFrom<T>(const Src: T): TDataRecord;
begin
Result := CreateFrom<T>;
Result.CopyFrom<T>(Src);
end;
procedure TDataRecord.GetValue(Idx: Integer; out Value); procedure TDataRecord.GetValue(Idx: Integer; out Value);
begin begin
var P := @FBuffer[FLayout.Fields[Idx].Offset]; var P := @FBuffer[FLayout.Fields[Idx].Offset];
@@ -158,51 +174,56 @@ function TDataRecord.GetValue<T>(const Name: String): T;
begin begin
var idx := FLayout.IndexOf(Name); var idx := FLayout.IndexOf(Name);
if (idx >= 0) then 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; end;
procedure TDataRecord.SetValue<T>(const Name: String; const Value: T); procedure TDataRecord.SetValue<T>(const Name: String; const Value: T);
begin begin
var idx := FLayout.IndexOf(Name); var idx := FLayout.IndexOf(Name);
if (idx >= 0) then 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; end;
class operator TDataRecord.Finalize(var Dest: TDataRecord); class operator TDataRecord.Finalize(var Dest: TDataRecord);
begin begin
for var i := 0 to High(Dest.FLayout.Fields) do 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; 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 begin
FName := AName; FName := AName;
FFieldType := AFieldType; FFieldType := AFieldType;
FOffset := AOffset; FOffset := AOffset;
FSize := ASize;
end; end;
procedure TDataRecord.TField.Finalize(P: Pointer); procedure TDataRecord.TField.Finalize(const [ref] Buffer: TBytes);
begin begin
if not (FFieldType in [dfString]) then
exit;
Assert(FOffset + Size <= Length(Buffer));
var P := @Buffer[FOffset];
case FFieldType of case FFieldType of
dfFloat: PDouble(P)^ := 0;
dfInteger: PInt64(P)^ := 0;
dfString: PString(P)^ := ''; dfString: PString(P)^ := '';
dfTimestamp: PDateTime(P)^ := 0;
else
Assert(false);
end; end;
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 begin
Assert(FOffset + Size <= Length(Buffer));
Finalize(Buffer);
var Dst := @Buffer[FOffset];
case FFieldType of case FFieldType of
dfFloat: dfFloat:
begin begin
Assert(SrcType.Kind = tkFloat); Assert(SrcType.Kind = tkFloat);
case GetTypeData(SrcType).FloatType of case GetTypeData(SrcType).FloatType of
ftSingle: PDouble(P)^ := PSingle(@Src)^; ftSingle: PDouble(Dst)^ := PSingle(@Src)^;
ftDouble: PDouble(P)^ := PDouble(@Src)^; ftDouble: PDouble(Dst)^ := PDouble(@Src)^;
else else
Assert(false); Assert(false);
end; end;
@@ -210,8 +231,8 @@ begin
dfInteger: dfInteger:
begin begin
case SrcType.Kind of case SrcType.Kind of
tkInteger: PInt64(P)^ := PInteger(@Src)^; tkInteger: PInt64(Dst)^ := PInteger(@Src)^;
tkInt64: PInt64(P)^ := PInt64(@Src)^; tkInt64: PInt64(Dst)^ := PInt64(@Src)^;
else else
Assert(false); Assert(false);
end; end;
@@ -219,41 +240,31 @@ begin
dfString: dfString:
begin begin
Assert(SrcType.Kind in [tkString, tkLString, tkUString, tkWString]); Assert(SrcType.Kind in [tkString, tkLString, tkUString, tkWString]);
PString(P)^ := PString(@Src)^; PString(Dst)^ := PString(@Src)^;
end; end;
dfTimestamp: dfTimestamp:
begin begin
Assert(SrcType.Kind = tkFloat); Assert(SrcType.Kind = tkFloat);
PDateTime(P)^ := PDateTime(@Src)^; PDateTime(Dst)^ := PDateTime(@Src)^;
end; end;
else else
Assert(false); Assert(false);
end; end;
end; end;
class function TDataRecord.TField.From(const AName: string; const RttiType: TRttiType; Offset: Integer): TField; function TDataRecord.TField.GetSize: Integer;
begin begin
case rttiType.TypeKind of Result := DataSize[FFieldType];
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;
end; end;
class function TDataRecord.TField.From<T>(const AName: string; Offset: Integer): TField; function TDataRecord.TField.GetAlignedSize: Integer;
begin begin
var ctx := TRttiContext.Create; Result := (GetSize + (Align - 1)) and not (Align - 1);
Result := From(AName, ctx.GetType(TypeInfo(T)), Offset);
end; end;
procedure TDataRecord.TField.Read(Src: Pointer; DstType: PTypeInfo; var Dst); procedure TDataRecord.TField.ToType(const [ref] Buffer: TBytes; DstType: PTypeInfo; var Dst);
begin begin
var Src := @Buffer[FOffset];
case FFieldType of case FFieldType of
dfFloat: dfFloat:
begin begin
@@ -289,14 +300,44 @@ begin
end; end;
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<TField>); constructor TDataRecord.TLayout.Create(const AFields: TArray<TField>);
begin begin
FFields := AFields; FFields := AFields;
end; end;
class function TDataRecord.TLayout.Construct(const Def: TArray<TFieldDef>): TLayout;
var
Fields: TArray<TField>;
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 } { TLayout }
class function TDataRecord.TLayout.CreateFrom<T>: TLayout; class function TDataRecord.TLayout.FromRecord<T>: TLayout;
begin begin
var ctx := TRttiContext.Create; var ctx := TRttiContext.Create;
var rttiType := ctx.GetType(TypeInfo(T)); var rttiType := ctx.GetType(TypeInfo(T));
@@ -309,24 +350,25 @@ begin
for var i := 0 to High(rttiFields) do for var i := 0 to High(rttiFields) do
begin begin
var rf := rttiFields[i]; var rf := rttiFields[i];
fields[i] := TField.From(rf.Name, rf.FieldType, ofs);
inc(ofs, fields[i].Size); case rf.FieldType.TypeKind of
ofs := (ofs + 7) and not 7; 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; end;
Result.Create(fields); Result.Create(fields);
end; 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; function TDataRecord.TLayout.IndexOf(const Name: String): Integer;
begin begin
for var i := 0 to High(FFields) do for var i := 0 to High(FFields) do
-7
View File
@@ -217,13 +217,6 @@ type
// Endpoint that collects data into a series. // Endpoint that collects data into a series.
TMycDataJoin<T> = class(TInterfacedObject, TDataProvider<TArray<T>>.IDataProvider) TMycDataJoin<T> = class(TInterfacedObject, TDataProvider<TArray<T>>.IDataProvider)
type
PItem = ^TItem;
TItem = record
Next: PItem;
Value: T;
end;
private private
FReceivers: array of record FReceivers: array of record
DataProvider: TDataProvider<T>; DataProvider: TDataProvider<T>;
+23 -11
View File
@@ -124,6 +124,8 @@ type
class function FieldToRecord<T>(const Layout: TDataRecord.TLayout; const Name: String): TConverter<T, TDataRecord>; static; class function FieldToRecord<T>(const Layout: TDataRecord.TLayout; const Name: String): TConverter<T, TDataRecord>; static;
class function FieldOfRecord<T>(const Layout: TDataRecord.TLayout; const Name: String): TConverter<TDataRecord, T>; static; class function FieldOfRecord<T>(const Layout: TDataRecord.TLayout; const Name: String): TConverter<TDataRecord, T>; static;
class function Join<T>(const DataProviders: TArray<TDataProvider<T>>): TDataProvider<TArray<T>>; static;
type type
TRecordMapping = record TRecordMapping = record
Layout: TDataRecord.TLayout; Layout: TDataRecord.TLayout;
@@ -137,7 +139,11 @@ type
const Output: TDataRecord.TLayout const Output: TDataRecord.TLayout
): TConverter<TArray<TDataRecord>, TDataRecord>; static; ): TConverter<TArray<TDataRecord>, TDataRecord>; static;
class function Join<T>(const DataProviders: TArray<TDataProvider<T>>): TDataProvider<TArray<T>>; static; class function JoinRecords(
const TargetLayout: TDataRecord.TLayout;
const Mapping: TArray<TRecordMapping>;
const DataProviders: TArray<TDataProvider<TDataRecord>>
): TConverter<TArray<TDataRecord>, TDataRecord>; static;
end; end;
implementation implementation
@@ -357,18 +363,11 @@ begin
Result := Result :=
TConverter<TArray<TDataRecord>, TDataRecord>.CreateGeneric( TConverter<TArray<TDataRecord>, TDataRecord>.CreateGeneric(
function(const Inputs: TArray<TDataRecord>): TDataRecord function(const Inputs: TArray<TDataRecord>): TDataRecord
var
tmp: array[0..63] of Byte;
begin begin
Result := TDataRecord.CreateFrom(Output); Result := TDataRecord.Create(Output);
for var i := 0 to High(Idxs) do for var i := 0 to High(Idxs) do
begin
for var j := 0 to High(Idxs[i]) do for var j := 0 to High(Idxs[i]) do
begin Inputs[i].CopyField(Idxs[i][j].FromIdx, Result, Idxs[i][j].ToIdx);
Inputs[i].GetValue(Idxs[i][j].FromIdx, tmp);
Result.SetValue(Idxs[i][j].ToIdx, tmp);
end;
end;
end end
); );
end; end;
@@ -386,7 +385,7 @@ begin
TConverter<T, TDataRecord>.CreateGeneric( TConverter<T, TDataRecord>.CreateGeneric(
function(const Value: T): TDataRecord function(const Value: T): TDataRecord
begin begin
Result := TDataRecord.CreateFrom(Layout); Result := TDataRecord.Create(Layout);
Result.SetValue(idx, Value); Result.SetValue(idx, Value);
end end
); );
@@ -397,6 +396,19 @@ begin
Result := TMycDataJoin<T>.Create(DataProviders); Result := TMycDataJoin<T>.Create(DataProviders);
end; end;
class function TConverter.JoinRecords(
const TargetLayout: TDataRecord.TLayout;
const Mapping: TArray<TRecordMapping>;
const DataProviders: TArray<TDataProvider<TDataRecord>>
): TConverter<TArray<TDataRecord>, TDataRecord>;
begin
var RecProvider := Join<TDataRecord>(DataProviders);
Result := DataMapping(Mapping, TargetLayout);
RecProvider.Link(Result);
end;
class function TConverter.Parallel<T>(Parent: TDataProvider<T>): TConverter<T, T>; class function TConverter.Parallel<T>(Parent: TDataProvider<T>): TConverter<T, T>;
begin begin
Result := TMycParallelConverter<T>.Create; Result := TMycParallelConverter<T>.Create;
+4 -4
View File
@@ -579,17 +579,17 @@ end;
constructor TEMA.Create; constructor TEMA.Create;
begin begin
inherited inherited
Create(TDataRecord.TLayout.CreateFrom<TParam>, TDataRecord.TLayout.CreateFrom<TInput>, TDataRecord.TLayout.CreateFrom<TResult>) Create(TDataRecord.TLayout.FromRecord<TParam>, TDataRecord.TLayout.FromRecord<TInput>, TDataRecord.TLayout.FromRecord<TResult>)
end; end;
function TEMA.CreateIndicator(const Params: TDataRecord): TIndicatorFactory.TFunc; function TEMA.CreateIndicator(const Params: TDataRecord): TIndicatorFactory.TFunc;
begin begin
var Period := Params.GetValue<Integer>('Period'); var Period := Params.GetValue<Integer>('Period');
var Input := TDataRecord.TLayout.CreateFrom<TInput>; var Input := TDataRecord.TLayout.FromRecord<TInput>;
var inPrice := Input.IndexOf('Price'); var inPrice := Input.IndexOf('Price');
var Output := TDataRecord.TLayout.CreateFrom<TResult>; var Output := TDataRecord.TLayout.FromRecord<TResult>;
var outMA := Output.IndexOf('MA'); var outMA := Output.IndexOf('MA');
var CalcEMA := TIndicators.CreateEMA(Period); var CalcEMA := TIndicators.CreateEMA(Period);
@@ -602,7 +602,7 @@ begin
var MA := CalcEMA(Price); var MA := CalcEMA(Price);
Result := TDataRecord.CreateFrom(Output); Result := TDataRecord.FromRecord<TResult>;
Result.SetValue(outMA, MA); Result.SetValue(outMA, MA);
end; end;
end; end;
+18 -19
View File
@@ -6,6 +6,7 @@ uses
DunitX.TestFramework, DunitX.TestFramework,
System.SysUtils, System.SysUtils,
System.DateUtils, System.DateUtils,
System.Rtti,
Myc.DataRecord; Myc.DataRecord;
type type
@@ -21,56 +22,54 @@ type
[TestFixture] [TestFixture]
TTestDataRecord = class(TObject) TTestDataRecord = class(TObject)
private
FCtx: TRttiContext;
public public
[Test] [Test]
[IgnoreMemoryLeaks]
procedure TestLayoutCreation; procedure TestLayoutCreation;
[Test] [Test]
[IgnoreMemoryLeaks]
procedure TestCreateFromInstanceAndGetValue; procedure TestCreateFromInstanceAndGetValue;
[Test] [Test]
[IgnoreMemoryLeaks]
procedure TestSetValueAndGetValue; procedure TestSetValueAndGetValue;
end; end;
implementation implementation
uses
System.Rtti;
{ TTestDataRecord } { TTestDataRecord }
procedure TTestDataRecord.TestLayoutCreation; procedure TTestDataRecord.TestLayoutCreation;
var var
layout: TDataRecord.TRecordLayout; layout: TDataRecord.TLayout;
idx: Integer; idx: Integer;
expectedSize: Integer;
begin begin
// Test: Create layout from a record type // Test: Create layout from a record type
layout := TDataRecord.TRecordLayout.CreateFrom<TMyTestRecord>; layout := TDataRecord.TLayout.FromRecord<TMyTestRecord>;
// Assert: Check field count // Assert: Check field count
Assert.IsTrue(Length(layout.Fields) = 6, 'Layout should have 6 fields'); Assert.IsTrue(Length(layout.Fields) = 6, 'Layout should have 6 fields');
// Assert: Check specific fields for correct type mapping // 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'); idx := layout.IndexOf('MyInteger');
Assert.IsTrue(idx > -1, 'Field MyInteger not found'); 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'); idx := layout.IndexOf('MyString');
Assert.IsTrue(idx > -1, 'Field MyString not found'); 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'); idx := layout.IndexOf('MySingle');
Assert.IsTrue(idx > -1, 'Field MySingle not found'); Assert.IsTrue(idx > -1, 'Field MySingle not found');
Assert.AreEqual(TDataFieldType.dfFloat, layout.Fields[idx].FieldType, 'MySingle should be mapped to dfFloat'); Assert.AreEqual(TDataRecord.TFieldType.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');
end; end;
procedure TTestDataRecord.TestCreateFromInstanceAndGetValue; procedure TTestDataRecord.TestCreateFromInstanceAndGetValue;
@@ -87,7 +86,7 @@ begin
srcRecord.MyTimestamp := EncodeDateTime(2024, 7, 19, 10, 30, 0, 0); srcRecord.MyTimestamp := EncodeDateTime(2024, 7, 19, 10, 30, 0, 0);
// Test: Create a TDataRecord from the source record instance // Test: Create a TDataRecord from the source record instance
dataRecord := TDataRecord.CreateFrom<TMyTestRecord>(srcRecord); dataRecord := TDataRecord.FromRecord<TMyTestRecord>(srcRecord);
// Assert: Retrieve each value and check for correctness // Assert: Retrieve each value and check for correctness
Assert.AreEqual(srcRecord.MyInt64, dataRecord.GetValue<Int64>('MyInt64'), 'Int64 value mismatch'); Assert.AreEqual(srcRecord.MyInt64, dataRecord.GetValue<Int64>('MyInt64'), 'Int64 value mismatch');
@@ -114,7 +113,7 @@ var
testTimestamp: TDateTime; testTimestamp: TDateTime;
begin begin
// Setup: Create an empty TDataRecord with the layout of TMyTestRecord // Setup: Create an empty TDataRecord with the layout of TMyTestRecord
dataRecord := TDataRecord.CreateFrom<TMyTestRecord>; dataRecord := TDataRecord.FromRecord<TMyTestRecord>;
// Setup: Define test values // Setup: Define test values
testInt := 54321; testInt := 54321;