Reworking DataRecords and DataFlow
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user