Testing TValue as Params
This commit is contained in:
@@ -51,6 +51,7 @@ uses
|
|||||||
FMX.ActnList,
|
FMX.ActnList,
|
||||||
DynamicFMXControl,
|
DynamicFMXControl,
|
||||||
Myc.FMX.Chart,
|
Myc.FMX.Chart,
|
||||||
|
Myc.Trade.Indicators,
|
||||||
Myc.Trade.Indicators.Common,
|
Myc.Trade.Indicators.Common,
|
||||||
StrategyTest,
|
StrategyTest,
|
||||||
TestMethodCallFromRecordParams;
|
TestMethodCallFromRecordParams;
|
||||||
@@ -255,6 +256,8 @@ begin
|
|||||||
);
|
);
|
||||||
|
|
||||||
NewWorkspace;
|
NewWorkspace;
|
||||||
|
|
||||||
|
IndicatorRegistry.LogRegistry(LogMemo.Lines);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TForm1.FormDestroy(Sender: TObject);
|
procedure TForm1.FormDestroy(Sender: TObject);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ interface
|
|||||||
uses
|
uses
|
||||||
System.SysUtils,
|
System.SysUtils,
|
||||||
System.Classes,
|
System.Classes,
|
||||||
|
System.Rtti,
|
||||||
Myc.Data.Records,
|
Myc.Data.Records,
|
||||||
Myc.Data.Pipeline,
|
Myc.Data.Pipeline,
|
||||||
Myc.Trade.Indicators;
|
Myc.Trade.Indicators;
|
||||||
@@ -52,8 +53,6 @@ type
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
procedure Test1(const Log: TStrings);
|
procedure Test1(const Log: TStrings);
|
||||||
procedure TestSma(const Log: TStrings);
|
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
uses
|
uses
|
||||||
@@ -81,20 +80,18 @@ procedure Test1(const Log: TStrings);
|
|||||||
begin
|
begin
|
||||||
var fact := TGenericIndicatorFactory.CreateFromTemplate<TMyWorker>;
|
var fact := TGenericIndicatorFactory.CreateFromTemplate<TMyWorker>;
|
||||||
|
|
||||||
var params := TDataRecord.FromRecord<TMyWorker.TParams>;
|
var params: TMyWorker.TParams;
|
||||||
params.SetValue<Int64>('Log', Int64(Log));
|
params.Log := Int64(Log);
|
||||||
params.SetValue<String>('text', 'The quick brown fox jumps...');
|
params.text := 'The quick brown fox jumps...';
|
||||||
|
|
||||||
var indi := fact.CreateIndicator(params);
|
var indi := fact.CreateIndicator<TMyWorker.TParams, TMyWorker.TArgs, TMyWorker.TResult>(params);
|
||||||
|
|
||||||
var args := TDataRecord.FromRecord<TMyWorker.TArgs>;
|
var args: TMyWorker.TArgs;
|
||||||
args.SetValue<Double>('xyz', 3.14159);
|
args.xyz := 3.14159;
|
||||||
|
|
||||||
var res := indi(args);
|
var res := indi(args);
|
||||||
|
|
||||||
var desc := res.GetValue<String>('desc');
|
Log.Add('Final result description: ' + res.desc);
|
||||||
Log.Add('Final result description: ' + desc);
|
|
||||||
Assert(desc = 'done');
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TSmaIndicator }
|
{ TSmaIndicator }
|
||||||
@@ -147,63 +144,4 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TestSma(const Log: TStrings);
|
|
||||||
var
|
|
||||||
fact: TGenericIndicatorFactory;
|
|
||||||
params: TDataRecord;
|
|
||||||
indi: TConvertFunc<TDataRecord, TDataRecord>;
|
|
||||||
args: TDataRecord;
|
|
||||||
res: TDataRecord;
|
|
||||||
smaValue: Double;
|
|
||||||
begin
|
|
||||||
Log.Add('');
|
|
||||||
Log.Add('--- Testing SMA ---');
|
|
||||||
|
|
||||||
fact := TGenericIndicatorFactory.CreateFromTemplate<TSmaIndicator>;
|
|
||||||
|
|
||||||
// 1. Setup indicator parameters.
|
|
||||||
params := TDataRecord.FromRecord<TSmaIndicator.TParams>;
|
|
||||||
params.SetValue<Integer>('Period', 3);
|
|
||||||
Log.Add(Format('Creating SMA with Period=%d', [params.GetValue<Integer>('Period')]));
|
|
||||||
|
|
||||||
// 2. Create the indicator instance.
|
|
||||||
indi := fact.CreateIndicator(params);
|
|
||||||
|
|
||||||
// 3. Create the arguments record (can be reused).
|
|
||||||
args := TDataRecord.FromRecord<TSmaIndicator.TArgs>;
|
|
||||||
|
|
||||||
// 4. Feed values and check results.
|
|
||||||
args.SetValue<Double>('Value', 10.0);
|
|
||||||
res := indi(args);
|
|
||||||
smaValue := res.GetValue<Double>('Sma');
|
|
||||||
Log.Add(Format('Input: 10.0 -> SMA: %f', [smaValue]));
|
|
||||||
Assert(SameValue(smaValue, 10.0)); // Avg of [10] is 10
|
|
||||||
|
|
||||||
args.SetValue<Double>('Value', 11.0);
|
|
||||||
res := indi(args);
|
|
||||||
smaValue := res.GetValue<Double>('Sma');
|
|
||||||
Log.Add(Format('Input: 11.0 -> SMA: %f', [smaValue]));
|
|
||||||
Assert(SameValue(smaValue, 10.5)); // Avg of [10, 11] is 10.5
|
|
||||||
|
|
||||||
args.SetValue<Double>('Value', 12.0);
|
|
||||||
res := indi(args);
|
|
||||||
smaValue := res.GetValue<Double>('Sma');
|
|
||||||
Log.Add(Format('Input: 12.0 -> SMA: %f', [smaValue]));
|
|
||||||
Assert(SameValue(smaValue, 11.0)); // Avg of [10, 11, 12] is 11
|
|
||||||
|
|
||||||
args.SetValue<Double>('Value', 13.0);
|
|
||||||
res := indi(args);
|
|
||||||
smaValue := res.GetValue<Double>('Sma');
|
|
||||||
Log.Add(Format('Input: 13.0 -> SMA: %f', [smaValue]));
|
|
||||||
Assert(SameValue(smaValue, 12.0)); // Avg of [11, 12, 13] is 12
|
|
||||||
|
|
||||||
args.SetValue<Double>('Value', 14.0);
|
|
||||||
res := indi(args);
|
|
||||||
smaValue := res.GetValue<Double>('Sma');
|
|
||||||
Log.Add(Format('Input: 14.0 -> SMA: %f', [smaValue]));
|
|
||||||
Assert(SameValue(smaValue, 13.0)); // Avg of [12, 13, 14] is 13
|
|
||||||
|
|
||||||
Log.Add('SMA test successful.');
|
|
||||||
end;
|
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ uses
|
|||||||
System.SysUtils,
|
System.SysUtils,
|
||||||
System.Math,
|
System.Math,
|
||||||
System.Rtti,
|
System.Rtti,
|
||||||
Myc.Data.Records,
|
|
||||||
Myc.Data.Pipeline,
|
Myc.Data.Pipeline,
|
||||||
Myc.Data.Series,
|
Myc.Data.Series,
|
||||||
Myc.Trade.Types,
|
Myc.Trade.Types,
|
||||||
@@ -1071,6 +1070,6 @@ initialization
|
|||||||
IndicatorRegistry.RegisterTemplate<TBollingerBands>;
|
IndicatorRegistry.RegisterTemplate<TBollingerBands>;
|
||||||
IndicatorRegistry.RegisterTemplate<TATR>;
|
IndicatorRegistry.RegisterTemplate<TATR>;
|
||||||
IndicatorRegistry.RegisterTemplate<TKeltnerChannels>;
|
IndicatorRegistry.RegisterTemplate<TKeltnerChannels>;
|
||||||
// IndicatorRegistry.RegisterTemplate<TMean>;
|
IndicatorRegistry.RegisterTemplate<TMean>;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
+164
-100
@@ -5,8 +5,9 @@ interface
|
|||||||
{$M+}
|
{$M+}
|
||||||
|
|
||||||
uses
|
uses
|
||||||
|
System.Rtti,
|
||||||
Myc.Data.Pipeline,
|
Myc.Data.Pipeline,
|
||||||
Myc.Data.Records;
|
System.Classes;
|
||||||
|
|
||||||
type
|
type
|
||||||
TIndicatorFactoryProc<TParams, TValue, TResult> = reference to function(const Params: TParams): TConvertFunc<TValue, TResult>;
|
TIndicatorFactoryProc<TParams, TValue, TResult> = reference to function(const Params: TParams): TConvertFunc<TValue, TResult>;
|
||||||
@@ -61,47 +62,56 @@ type
|
|||||||
property Hint: string read FHint;
|
property Hint: string read FHint;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
TFieldDef = record
|
||||||
|
public
|
||||||
|
Identifier: String;
|
||||||
|
TypeName: String;
|
||||||
|
end;
|
||||||
|
|
||||||
|
TFieldLayout = TArray<TFieldDef>;
|
||||||
|
|
||||||
// Interface for creating an indicator instance. Only contains functional aspects.
|
// Interface for creating an indicator instance. Only contains functional aspects.
|
||||||
IIndicatorFactory = interface
|
IIndicatorFactory = interface
|
||||||
{$region 'private'}
|
function GetParams: TFieldLayout;
|
||||||
function GetArgumentLayout: TDataRecord.TLayout;
|
function GetArgs: TFieldLayout;
|
||||||
function GetParameterLayout: TDataRecord.TLayout;
|
function GetResults: TFieldLayout;
|
||||||
function GetResultLayout: TDataRecord.TLayout;
|
|
||||||
{$endregion}
|
|
||||||
|
|
||||||
function CreateIndicator(const Params: TDataRecord): TConvertFunc<TDataRecord, TDataRecord>;
|
function CreateIndicator(const Params: TValue): TConvertFunc<TValue, TValue>;
|
||||||
|
|
||||||
property ParameterLayout: TDataRecord.TLayout read GetParameterLayout;
|
// Provides the layout for the indicator's parameters, arguments, and results.
|
||||||
property ArgumentLayout: TDataRecord.TLayout read GetArgumentLayout;
|
property Params: TFieldLayout read GetParams;
|
||||||
property ResultLayout: TDataRecord.TLayout read GetResultLayout;
|
property Args: TFieldLayout read GetArgs;
|
||||||
|
property Results: TFieldLayout read GetResults;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TGenericIndicatorFactory = class(TInterfacedObject, IIndicatorFactory)
|
TGenericIndicatorFactory = class(TInterfacedObject, IIndicatorFactory)
|
||||||
private
|
private
|
||||||
FParameterLayout: TDataRecord.TLayout;
|
FFactoryProc: TIndicatorFactoryProc<TValue, TValue, TValue>;
|
||||||
FFactoryProc: TIndicatorFactoryProc<TDataRecord, TDataRecord, TDataRecord>;
|
|
||||||
FName: String;
|
FName: String;
|
||||||
FArgumentLayout: TDataRecord.TLayout;
|
|
||||||
FResultLayout: TDataRecord.TLayout;
|
|
||||||
FShortName: String;
|
FShortName: String;
|
||||||
FHint: String;
|
FHint: String;
|
||||||
function GetArgumentLayout: TDataRecord.TLayout;
|
FParams: TFieldLayout;
|
||||||
function GetParameterLayout: TDataRecord.TLayout;
|
FArgs: TFieldLayout;
|
||||||
function GetResultLayout: TDataRecord.TLayout;
|
FResults: TFieldLayout;
|
||||||
|
|
||||||
|
class function LayoutFromRecordType(RecordType: TRttiType): TFieldLayout; static;
|
||||||
|
|
||||||
public
|
public
|
||||||
constructor Create(
|
constructor Create(
|
||||||
const AParameterLayout, AArgumentLayout, AResultLayout: TDataRecord.TLayout;
|
const AFactoryProc: TIndicatorFactoryProc<TValue, TValue, TValue>;
|
||||||
const AFactoryProc: TIndicatorFactoryProc<TDataRecord, TDataRecord, TDataRecord>;
|
const AShortName, AName, AHint: String;
|
||||||
const AShortName, AName, AHint: String
|
const AParams, AArgs, AResults: TFieldLayout
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// IIndicatorFactory
|
||||||
|
function GetParams: TFieldLayout;
|
||||||
|
function GetArgs: TFieldLayout;
|
||||||
|
function GetResults: TFieldLayout;
|
||||||
|
function CreateIndicator(const Params: TValue): TConvertFunc<TValue, TValue>; overload;
|
||||||
|
|
||||||
class function CreateFromTemplate<T>: TGenericIndicatorFactory;
|
class function CreateFromTemplate<T>: TGenericIndicatorFactory;
|
||||||
|
|
||||||
function CreateIndicator(const Params: TDataRecord): TConvertFunc<TDataRecord, TDataRecord>;
|
function CreateIndicator<TParams, TArgs, TResult>(const Params: TParams): TConvertFunc<TArgs, TResult>; overload;
|
||||||
|
|
||||||
property ParameterLayout: TDataRecord.TLayout read GetParameterLayout;
|
|
||||||
property ArgumentLayout: TDataRecord.TLayout read GetArgumentLayout;
|
|
||||||
property ResultLayout: TDataRecord.TLayout read GetResultLayout;
|
|
||||||
|
|
||||||
property Name: String read FName;
|
property Name: String read FName;
|
||||||
property ShortName: String read FShortName;
|
property ShortName: String read FShortName;
|
||||||
@@ -120,6 +130,7 @@ type
|
|||||||
FHint: string;
|
FHint: string;
|
||||||
public
|
public
|
||||||
constructor Create(const AFactory: IIndicatorFactory; const AShortName, AName, AHint: string);
|
constructor Create(const AFactory: IIndicatorFactory; const AShortName, AName, AHint: string);
|
||||||
|
function CreateIndicator<TParams, TArgs, TResult>(const Params: TParams): TConvertFunc<TArgs, TResult>; overload;
|
||||||
property Factory: IIndicatorFactory read FFactory;
|
property Factory: IIndicatorFactory read FFactory;
|
||||||
property Name: string read FName;
|
property Name: string read FName;
|
||||||
property ShortName: string read FShortName;
|
property ShortName: string read FShortName;
|
||||||
@@ -127,10 +138,14 @@ type
|
|||||||
end;
|
end;
|
||||||
private
|
private
|
||||||
FItems: TArray<TItem>;
|
FItems: TArray<TItem>;
|
||||||
|
|
||||||
public
|
public
|
||||||
constructor Create;
|
constructor Create;
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
|
|
||||||
|
// Writes the contents of the registry (just like the log)
|
||||||
|
procedure LogRegistry(const Log: TStrings);
|
||||||
|
|
||||||
// Register indicator from a template class
|
// Register indicator from a template class
|
||||||
procedure RegisterTemplate<TIndicatorTemplate>;
|
procedure RegisterTemplate<TIndicatorTemplate>;
|
||||||
|
|
||||||
@@ -151,8 +166,7 @@ implementation
|
|||||||
|
|
||||||
uses
|
uses
|
||||||
System.SysUtils,
|
System.SysUtils,
|
||||||
System.TypInfo,
|
System.TypInfo;
|
||||||
System.Rtti;
|
|
||||||
|
|
||||||
constructor IndicatorNameAttribute.Create(const AShortName, AName: string);
|
constructor IndicatorNameAttribute.Create(const AShortName, AName: string);
|
||||||
begin
|
begin
|
||||||
@@ -170,19 +184,40 @@ end;
|
|||||||
{ TGenericIndicatorFactory }
|
{ TGenericIndicatorFactory }
|
||||||
|
|
||||||
constructor TGenericIndicatorFactory.Create(
|
constructor TGenericIndicatorFactory.Create(
|
||||||
const AParameterLayout, AArgumentLayout, AResultLayout: TDataRecord.TLayout;
|
const AFactoryProc: TIndicatorFactoryProc<TValue, TValue, TValue>;
|
||||||
const AFactoryProc: TIndicatorFactoryProc<TDataRecord, TDataRecord, TDataRecord>;
|
const AShortName, AName, AHint: String;
|
||||||
const AShortName, AName, AHint: String
|
const AParams, AArgs, AResults: TFieldLayout
|
||||||
);
|
);
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
FParameterLayout := AParameterLayout;
|
|
||||||
FArgumentLayout := AArgumentLayout;
|
|
||||||
FResultLayout := AResultLayout;
|
|
||||||
FFactoryProc := AFactoryProc;
|
FFactoryProc := AFactoryProc;
|
||||||
FShortName := AShortName;
|
FShortName := AShortName;
|
||||||
FName := AName;
|
FName := AName;
|
||||||
FHint := AHint;
|
FHint := AHint;
|
||||||
|
FParams := AParams;
|
||||||
|
FArgs := AArgs;
|
||||||
|
FResults := AResults;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class function TGenericIndicatorFactory.LayoutFromRecordType(RecordType: TRttiType): TFieldLayout;
|
||||||
|
var
|
||||||
|
rttiRecordType: TRttiRecordType;
|
||||||
|
field: TRttiField;
|
||||||
|
i: Integer;
|
||||||
|
begin
|
||||||
|
if not RecordType.IsRecord then
|
||||||
|
raise EArgumentException.CreateFmt('Record type expected, but got %s', [RecordType.Name]);
|
||||||
|
|
||||||
|
rttiRecordType := RecordType as TRttiRecordType;
|
||||||
|
var fields := rttiRecordType.GetFields;
|
||||||
|
SetLength(Result, Length(fields));
|
||||||
|
i := 0;
|
||||||
|
for field in fields do
|
||||||
|
begin
|
||||||
|
Result[i].Identifier := field.Name;
|
||||||
|
Result[i].TypeName := field.FieldType.Name;
|
||||||
|
Inc(i);
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TGenericIndicatorFactory.CreateFromTemplate<T>: TGenericIndicatorFactory;
|
class function TGenericIndicatorFactory.CreateFromTemplate<T>: TGenericIndicatorFactory;
|
||||||
@@ -191,14 +226,14 @@ var
|
|||||||
rttiType: TRttiType;
|
rttiType: TRttiType;
|
||||||
paramsType, argsType, resultType: TRttiType;
|
paramsType, argsType, resultType: TRttiType;
|
||||||
templateFactoryMethod: TRttiMethod;
|
templateFactoryMethod: TRttiMethod;
|
||||||
parameterLayout, argumentLayout, resultLayout: TDataRecord.TLayout;
|
factoryProc: TIndicatorFactoryProc<TValue, TValue, TValue>;
|
||||||
factoryProc: TIndicatorFactoryProc<TDataRecord, TDataRecord, TDataRecord>;
|
|
||||||
shortName, name, hint: string;
|
shortName, name, hint: string;
|
||||||
|
paramsLayout, argsLayout, resultsLayout: TFieldLayout;
|
||||||
begin
|
begin
|
||||||
// This function creates a generic factory from a template class.
|
// This function creates a generic factory from a template class.
|
||||||
// It uses RTTI to find the necessary types by inspecting the factory method signature,
|
// It uses RTTI to find the necessary types by inspecting the factory method signature,
|
||||||
// and then constructs a set of wrappers to adapt the specific types of the template
|
// and then constructs a set of wrappers to adapt the specific types of the template
|
||||||
// to the generic TDataRecord used by this factory.
|
// to the generic TValue used by this factory.
|
||||||
Ctx := TRttiContext.Create;
|
Ctx := TRttiContext.Create;
|
||||||
rttiType := Ctx.GetType(TypeInfo(T));
|
rttiType := Ctx.GetType(TypeInfo(T));
|
||||||
|
|
||||||
@@ -265,10 +300,10 @@ begin
|
|||||||
Assert(argsType.TypeKind = tkRecord);
|
Assert(argsType.TypeKind = tkRecord);
|
||||||
Assert(resultType.TypeKind = tkRecord);
|
Assert(resultType.TypeKind = tkRecord);
|
||||||
|
|
||||||
// Create the layouts for parameters, arguments, and results.
|
// Generate layouts from RTTI types.
|
||||||
parameterLayout := TDataRecord.TLayout.FromRecord(paramsType.Handle);
|
paramsLayout := LayoutFromRecordType(paramsType);
|
||||||
argumentLayout := TDataRecord.TLayout.FromRecord(argsType.Handle);
|
argsLayout := LayoutFromRecordType(argsType);
|
||||||
resultLayout := TDataRecord.TLayout.FromRecord(resultType.Handle);
|
resultsLayout := LayoutFromRecordType(resultType);
|
||||||
|
|
||||||
// Extract metadata from attributes on the template type T.
|
// Extract metadata from attributes on the template type T.
|
||||||
shortName := '';
|
shortName := '';
|
||||||
@@ -300,95 +335,68 @@ begin
|
|||||||
// Create the main factory procedure. This is a double-nested anonymous method
|
// Create the main factory procedure. This is a double-nested anonymous method
|
||||||
// that wraps the template's specific factory and worker functions.
|
// that wraps the template's specific factory and worker functions.
|
||||||
factoryProc :=
|
factoryProc :=
|
||||||
function(const Params: TDataRecord): TConvertFunc<TDataRecord, TDataRecord>
|
function(const Params: TValue): TConvertFunc<TValue, TValue>
|
||||||
begin
|
begin
|
||||||
// Outer anonymous method: This is the factory proc.
|
// Outer anonymous method: This is the factory proc.
|
||||||
// It gets called with a TDataRecord of parameters.
|
// It gets called with a TValue wrapping the parameters record.
|
||||||
|
|
||||||
// 1. Invoke the template's static factory method (e.g., TMyWorker.CreateFactory)
|
// 1. Invoke the template's static factory method (e.g., TMyWorker.CreateFactory)
|
||||||
var factoryProcAsValue := templateFactoryMethod.Invoke(TValue.Empty, []);
|
var factoryProc := templateFactoryMethod.Invoke(TValue.Empty, []);
|
||||||
|
|
||||||
// 2. Invoke the factory proc itself to get the actual worker proc.
|
// 2. Invoke the factory proc itself to get the actual worker proc.
|
||||||
var rttiFactoryProc := Ctx.GetType(factoryProcAsValue.TypeInfo) as TRttiInterfaceType;
|
var rttiFactoryProc := Ctx.GetType(factoryProc.TypeInfo) as TRttiInterfaceType;
|
||||||
var currentFactoryInvoke := rttiFactoryProc.GetMethod('Invoke');
|
var currentFactoryInvoke := rttiFactoryProc.GetMethod('Invoke');
|
||||||
|
|
||||||
// The parameter for this 'Invoke' call is the TParams record.
|
// The parameter for this 'Invoke' call is the TParams record.
|
||||||
// Wrap the incoming TDataRecord 'Params' into a TValue for the call.
|
// Wrap the incoming TValue 'Params' into a TValue for the call.
|
||||||
var factoryParamTypeInfo := currentFactoryInvoke.GetParameters[0].ParamType.Handle;
|
var factoryParamTypeInfo := currentFactoryInvoke.GetParameters[0].ParamType.Handle;
|
||||||
Assert(factoryParamTypeInfo = paramsType.Handle);
|
Assert(factoryParamTypeInfo = paramsType.Handle);
|
||||||
|
|
||||||
var factoryArg: array[0..0] of TValue;
|
|
||||||
TValue.Make(Params.RawData, factoryParamTypeInfo, factoryArg[0]);
|
|
||||||
|
|
||||||
// This call returns the worker proc (e.g., a TIndicatorProc<TValue, TResult>) as a TValue.
|
// This call returns the worker proc (e.g., a TIndicatorProc<TValue, TResult>) as a TValue.
|
||||||
var workerProcAsValue := currentFactoryInvoke.Invoke(factoryProcAsValue, factoryArg);
|
var workerProc := currentFactoryInvoke.Invoke(factoryProc, [Params]);
|
||||||
|
|
||||||
// 3. Return a new anonymous method that wraps the worker proc.
|
// 3. Return a new anonymous method that wraps the worker proc.
|
||||||
// This wrapper conforms to the generic TIndicatorProc<TDataRecord, TDataRecord> signature.
|
// This wrapper conforms to the generic TIndicatorProc<TValue, TValue> signature.
|
||||||
var rttiWorkerProc := Ctx.GetType(workerProcAsValue.TypeInfo);
|
var rttiWorkerProc := Ctx.GetType(workerProc.TypeInfo);
|
||||||
var currentIndicatorInvoke := rttiWorkerProc.GetMethod('Invoke');
|
var currentIndicatorInvoke := rttiWorkerProc.GetMethod('Invoke');
|
||||||
var indicatorParamTypeInfo := currentIndicatorInvoke.GetParameters[0].ParamType.Handle;
|
|
||||||
|
|
||||||
Result :=
|
Result :=
|
||||||
function(const Args: TDataRecord): TDataRecord
|
function(const Args: TValue): TValue
|
||||||
begin
|
begin
|
||||||
// Sadly, it's not possible to inject a buffer into a TValue. So we need to copy both Args and Result.
|
|
||||||
|
|
||||||
// Inner anonymous method: This is the actual indicator proc wrapper.
|
// Inner anonymous method: This is the actual indicator proc wrapper.
|
||||||
// The layouts are captured from the outer scope.
|
|
||||||
Assert(Args.Layout = argumentLayout);
|
|
||||||
|
|
||||||
// Prepare argument for the indicator proc invocation.
|
|
||||||
// TValue just carries the data, ownership is held by the caller.
|
|
||||||
var argVal: array[0..0] of TValue;
|
|
||||||
TValue.MakeWithoutCopy(Args.RawData, indicatorParamTypeInfo, argVal[0], true);
|
|
||||||
|
|
||||||
// Invoke the actual indicator proc.
|
// Invoke the actual indicator proc.
|
||||||
var resultAsTValue := currentIndicatorInvoke.Invoke(workerProcAsValue, argVal);
|
Result := currentIndicatorInvoke.Invoke(workerProc, [Args]);
|
||||||
|
|
||||||
// The result is a TValue containing the result record.
|
|
||||||
// Raw copy and erase all data from the TValue, leaving it as an empty capsule. Ownership is taken
|
|
||||||
// over to the resulting TDataRecord. This works because the memory layouts are exactly the same.
|
|
||||||
Assert(TDataRecord.TLayout.FromRecord(resultAsTValue.TypeInfo) = resultLayout);
|
|
||||||
|
|
||||||
var buf: TBytes;
|
|
||||||
var resultSize := resultLayout.Size;
|
|
||||||
SetLength(buf, resultSize);
|
|
||||||
|
|
||||||
var src := resultAsTValue.GetReferenceToRawData;
|
|
||||||
Move(src^, buf[0], resultSize);
|
|
||||||
FillChar(src^, resultSize, 0);
|
|
||||||
|
|
||||||
Result.Create(resultLayout, buf);
|
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Create the final factory instance with all layouts and extracted metadata.
|
// Create the final factory instance with all layouts and extracted metadata.
|
||||||
Result := TGenericIndicatorFactory.Create(parameterLayout, argumentLayout, resultLayout, factoryProc, shortName, name, hint);
|
Result := TGenericIndicatorFactory.Create(factoryProc, shortName, name, hint, paramsLayout, argsLayout, resultsLayout);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TGenericIndicatorFactory.CreateIndicator(const Params: TDataRecord): TConvertFunc<TDataRecord, TDataRecord>;
|
function TGenericIndicatorFactory.GetParams: TFieldLayout;
|
||||||
|
begin
|
||||||
|
Result := FParams;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TGenericIndicatorFactory.GetArgs: TFieldLayout;
|
||||||
|
begin
|
||||||
|
Result := FArgs;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TGenericIndicatorFactory.GetResults: TFieldLayout;
|
||||||
|
begin
|
||||||
|
Result := FResults;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TGenericIndicatorFactory.CreateIndicator(const Params: TValue): TConvertFunc<TValue, TValue>;
|
||||||
begin
|
begin
|
||||||
Assert(
|
|
||||||
(not Assigned(FParameterLayout.Fields)) or (Params.Layout = FParameterLayout),
|
|
||||||
'Invalid parameter layout for indicator creation'
|
|
||||||
);
|
|
||||||
Result := FFactoryProc(Params);
|
Result := FFactoryProc(Params);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TGenericIndicatorFactory.GetArgumentLayout: TDataRecord.TLayout;
|
function TGenericIndicatorFactory.CreateIndicator<TParams, TArgs, TResult>(const Params: TParams): TConvertFunc<TArgs, TResult>;
|
||||||
begin
|
begin
|
||||||
Result := FArgumentLayout;
|
var cIndicator := FFactoryProc(TValue.From<TParams>(Params));
|
||||||
end;
|
Result := function(const Args: TArgs): TResult begin Result := cIndicator(TValue.From<TArgs>(Args)).AsType<TResult>; end;
|
||||||
|
|
||||||
function TGenericIndicatorFactory.GetParameterLayout: TDataRecord.TLayout;
|
|
||||||
begin
|
|
||||||
Result := FParameterLayout;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TGenericIndicatorFactory.GetResultLayout: TDataRecord.TLayout;
|
|
||||||
begin
|
|
||||||
Result := FResultLayout;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TIndicatorRegistry.TItem }
|
{ TIndicatorRegistry.TItem }
|
||||||
@@ -402,6 +410,12 @@ begin
|
|||||||
FHint := AHint;
|
FHint := AHint;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TIndicatorRegistry.TItem.CreateIndicator<TParams, TArgs, TResult>(const Params: TParams): TConvertFunc<TArgs, TResult>;
|
||||||
|
begin
|
||||||
|
var cIndicator := FFactory.CreateIndicator(TValue.From<TParams>(Params));
|
||||||
|
Result := function(const Args: TArgs): TResult begin Result := cIndicator(TValue.From<TArgs>(Args)).AsType<TResult>; end;
|
||||||
|
end;
|
||||||
|
|
||||||
{ TIndicatorRegistry }
|
{ TIndicatorRegistry }
|
||||||
|
|
||||||
constructor TIndicatorRegistry.Create;
|
constructor TIndicatorRegistry.Create;
|
||||||
@@ -433,13 +447,63 @@ begin
|
|||||||
Result := nil;
|
Result := nil;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TIndicatorRegistry.LogRegistry(const Log: TStrings);
|
||||||
|
var
|
||||||
|
item: TItem;
|
||||||
|
|
||||||
|
procedure DoLog(const Txt: String);
|
||||||
|
begin
|
||||||
|
Log.Add(Txt);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure LogLayout(const LayoutName: string; const Layout: TFieldLayout);
|
||||||
|
var
|
||||||
|
field: TFieldDef;
|
||||||
|
begin
|
||||||
|
DoLog(Format(' %s:', [LayoutName]));
|
||||||
|
if Length(Layout) = 0 then
|
||||||
|
begin
|
||||||
|
DoLog(' (none)');
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
for field in Layout do
|
||||||
|
DoLog(Format(' - %s: %s', [field.Identifier, field.TypeName]));
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
begin
|
||||||
|
if not Assigned(Log) then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
Log.Clear;
|
||||||
|
DoLog(Format('Indicator Registry Content (%d items)', [Length(FItems)]));
|
||||||
|
DoLog('==========================================');
|
||||||
|
DoLog('');
|
||||||
|
|
||||||
|
if Length(FItems) = 0 then
|
||||||
|
begin
|
||||||
|
DoLog('(Registry is empty)');
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
for item in FItems do
|
||||||
|
begin
|
||||||
|
DoLog(Format('Indicator "%s" (%s)', [item.Name, item.ShortName]));
|
||||||
|
if not item.Hint.IsEmpty then
|
||||||
|
DoLog(Format(' Hint: %s', [item.Hint]));
|
||||||
|
|
||||||
|
LogLayout('Params', item.Factory.Params);
|
||||||
|
LogLayout('Args', item.Factory.Args);
|
||||||
|
LogLayout('Result', item.Factory.Results);
|
||||||
|
DoLog('');
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TIndicatorRegistry.RegisterIndicator(const Factory: IIndicatorFactory; const ShortName, Name, Hint: String);
|
procedure TIndicatorRegistry.RegisterIndicator(const Factory: IIndicatorFactory; const ShortName, Name, Hint: String);
|
||||||
var
|
var
|
||||||
item: TItem;
|
item: TItem;
|
||||||
begin
|
begin
|
||||||
if not Assigned(Factory) then
|
|
||||||
raise EArgumentException.Create('Factory');
|
|
||||||
|
|
||||||
if Assigned(Find(ShortName)) then
|
if Assigned(Find(ShortName)) then
|
||||||
raise EArgumentException.CreateFmt('Indicator with ShortName "%s" is already registered.', [ShortName]);
|
raise EArgumentException.CreateFmt('Indicator with ShortName "%s" is already registered.', [ShortName]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user