DataRecord
This commit is contained in:
+105
-18
@@ -8,7 +8,7 @@ uses
|
||||
System.TypInfo;
|
||||
|
||||
type
|
||||
// A record that provides field-level access to data, stored in a packed byte buffer.
|
||||
// A record that provides field-level access to managed data, stored in a byte buffer.
|
||||
TDataRecord = record
|
||||
public
|
||||
type
|
||||
@@ -32,42 +32,47 @@ type
|
||||
TypeInfo: PTypeInfo;
|
||||
end;
|
||||
|
||||
IBuilder = interface
|
||||
procedure AddField(const Name: String; const Value: TValue); overload;
|
||||
procedure SetupRecord(out Layout: TArray<TFieldLayout>; out Buffer: TBytes);
|
||||
end;
|
||||
|
||||
// The interface helper record that provides the generic AddField<T> method.
|
||||
TBuilder = record
|
||||
type
|
||||
IBuilder = interface
|
||||
procedure AddField(const Name: String; const Value: TValue); overload;
|
||||
procedure SetupRecord(out Layout: TArray<TFieldLayout>; out Buffer: TBytes);
|
||||
end;
|
||||
|
||||
private
|
||||
FBuilder: IBuilder;
|
||||
public
|
||||
constructor Create(const ABuilder: IBuilder);
|
||||
|
||||
// This generic method is the reason for the helper's existence.
|
||||
procedure AddField<T>(const Name: String; const Value: T); inline;
|
||||
|
||||
// Wrapper for methods on the underlying interface.
|
||||
function CreateRec: TDataRecord; inline;
|
||||
|
||||
// Implicit operators for seamless casting between the helper and the interface.
|
||||
class operator Implicit(const AValue: IBuilder): TBuilder; overload;
|
||||
class operator Implicit(const AValue: TBuilder): IBuilder; overload;
|
||||
|
||||
procedure AddField<T>(const Name: String); overload; inline;
|
||||
procedure AddField<T>(const Name: String; const Value: T); overload; inline;
|
||||
|
||||
function CreateRec: TDataRecord; inline;
|
||||
end;
|
||||
|
||||
private
|
||||
FLayout: TArray<TFieldLayout>;
|
||||
FBuffer: TBytes;
|
||||
|
||||
public
|
||||
class operator Initialize(out Dest: TDataRecord);
|
||||
class operator Finalize(var Dest: TDataRecord);
|
||||
class operator Assign(var Dest: TDataRecord; const [ref] Src: TDataRecord);
|
||||
|
||||
// Factory method now returns the helper record instance.
|
||||
class function CreateBuilder: TBuilder; static;
|
||||
|
||||
class function CreateFrom<T>(const Rec: T): TDataRecord; static;
|
||||
procedure AssignTo<T>(var Rec: T);
|
||||
function FitsRecord<T>: Boolean;
|
||||
|
||||
function IndexOf(const Name: String): Integer;
|
||||
function GetField(const Name: String): IField; overload;
|
||||
function GetField<T>(const Name: String): IField<T>; overload;
|
||||
|
||||
property Layout: TArray<TFieldLayout> read FLayout;
|
||||
end;
|
||||
|
||||
@@ -90,6 +95,11 @@ begin
|
||||
FBuilder.AddField(Name, TValue.From<T>(Value));
|
||||
end;
|
||||
|
||||
procedure TDataRecord.TBuilder.AddField<T>(const Name: String);
|
||||
begin
|
||||
AddField<T>(Name, Default(T));
|
||||
end;
|
||||
|
||||
function TDataRecord.TBuilder.CreateRec: TDataRecord;
|
||||
begin
|
||||
FBuilder.SetupRecord(Result.FLayout, Result.FBuffer);
|
||||
@@ -125,6 +135,76 @@ begin
|
||||
Result := TDataRecord.TBuilder.Create(TDataRecordBuilder.Create);
|
||||
end;
|
||||
|
||||
class function TDataRecord.CreateFrom<T>(const Rec: T): TDataRecord;
|
||||
var
|
||||
ctx: TRttiContext;
|
||||
rttiType: TRttiType;
|
||||
builder: TBuilder.IBuilder;
|
||||
begin
|
||||
// Create a builder to construct the TDataRecord
|
||||
builder := CreateBuilder;
|
||||
|
||||
// Use RTTI to iterate over the fields of the source record/object
|
||||
ctx := TRttiContext.Create;
|
||||
rttiType := ctx.GetType(TypeInfo(T));
|
||||
|
||||
// Add each field and its value to the builder
|
||||
for var field in rttiType.GetFields do
|
||||
begin
|
||||
builder.AddField(field.Name, field.GetValue(Pointer(@Rec)));
|
||||
end;
|
||||
|
||||
// Create the final TDataRecord from the builder
|
||||
builder.SetupRecord(Result.FLayout, Result.FBuffer);
|
||||
end;
|
||||
|
||||
procedure TDataRecord.AssignTo<T>(var Rec: T);
|
||||
var
|
||||
ctx: TRttiContext;
|
||||
rttiType: TRttiType;
|
||||
fieldValue: TValue;
|
||||
fieldIndex: Integer;
|
||||
layout: TFieldLayout;
|
||||
begin
|
||||
Assert(FitsRecord<T>);
|
||||
|
||||
// Use RTTI to iterate over the fields of the destination record/object
|
||||
ctx := TRttiContext.Create;
|
||||
rttiType := ctx.GetType(TypeInfo(T));
|
||||
|
||||
for var rttiField in rttiType.GetFields do
|
||||
begin
|
||||
// Find the corresponding field in the TDataRecord by name
|
||||
fieldIndex := IndexOf(rttiField.Name);
|
||||
if fieldIndex >= 0 then
|
||||
begin
|
||||
layout := FLayout[fieldIndex];
|
||||
// Create a TValue from the data in our buffer
|
||||
TValue.Make(@FBuffer[layout.Offset], layout.TypeInfo, fieldValue);
|
||||
// Assign the value to the destination record's field
|
||||
rttiField.SetValue(Pointer(@Rec), fieldValue);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TDataRecord.FitsRecord<T>: Boolean;
|
||||
begin
|
||||
var ctx := TRttiContext.Create;
|
||||
var rttiType := ctx.GetType(TypeInfo(T));
|
||||
|
||||
for var rttiField in rttiType.GetFields do
|
||||
begin
|
||||
// Find the corresponding field in the TDataRecord by name
|
||||
var fieldIndex := IndexOf(rttiField.Name);
|
||||
if fieldIndex < 0 then
|
||||
exit(false);
|
||||
|
||||
if rttiField.DataType.Handle <> FLayout[fieldIndex].TypeInfo then
|
||||
exit(false);
|
||||
end;
|
||||
exit(true);
|
||||
end;
|
||||
|
||||
function TDataRecord.GetField(const Name: String): IField;
|
||||
var
|
||||
idx: Integer;
|
||||
@@ -162,12 +242,18 @@ begin
|
||||
|
||||
Dest.FLayout := Src.FLayout;
|
||||
SetLength(Dest.FBuffer, Length(Src.FBuffer));
|
||||
for var layout in Dest.FLayout do
|
||||
for var i := 0 to High(Dest.FLayout) do
|
||||
begin
|
||||
var P: PByte := @Dest.FBuffer[layout.Offset];
|
||||
var Q: PByte := @Src.FBuffer[layout.Offset];
|
||||
Assert(Dest.FLayout[i].Name = Src.Layout[i].Name);
|
||||
Assert(Dest.FLayout[i].Offset = Src.Layout[i].Offset);
|
||||
Assert(Dest.FLayout[i].Size = Src.Layout[i].Size);
|
||||
Assert(Dest.FLayout[i].TypeInfo = Src.Layout[i].TypeInfo);
|
||||
|
||||
var ofs := Dest.FLayout[i].Offset;
|
||||
var P: PByte := @Dest.FBuffer[ofs];
|
||||
var Q: PByte := @Src.FBuffer[ofs];
|
||||
var Val: TValue;
|
||||
TValue.Make(Q, layout.TypeInfo, val);
|
||||
TValue.Make(Q, Dest.FLayout[i].TypeInfo, val);
|
||||
val.ExtractRawData(P);
|
||||
end;
|
||||
end;
|
||||
@@ -182,6 +268,7 @@ class operator TDataRecord.Finalize(var Dest: TDataRecord);
|
||||
begin
|
||||
if (Dest.FBuffer = nil) or (Dest.FLayout = nil) then
|
||||
Exit;
|
||||
|
||||
for var layout in Dest.FLayout do
|
||||
begin
|
||||
var P: PByte := @Dest.FBuffer[layout.Offset];
|
||||
|
||||
Reference in New Issue
Block a user