Memory hole in Data-Join fixed
This commit is contained in:
+80
-63
@@ -17,6 +17,7 @@ type
|
||||
function Consume(const Value: T): TState;
|
||||
end;
|
||||
|
||||
// A producer generates data and distributes it to linked consumers.
|
||||
IProducer<T> = interface
|
||||
function Link(const Consumer: IConsumer<T>): TTag;
|
||||
procedure Unlink(Tag: TTag);
|
||||
@@ -30,17 +31,18 @@ type
|
||||
property Consumer: IConsumer<S> read GetConsumer;
|
||||
end;
|
||||
|
||||
// Interface helper for IProducer providing the null object pattern.
|
||||
// Interface helper for IProducer providing the null object pattern nad subscriptions for linked consumers.
|
||||
TProducer<T> = record
|
||||
public
|
||||
type
|
||||
TSubscription = record
|
||||
private
|
||||
FProducer: IProducer<T>;
|
||||
FOwner: IProducer<T>;
|
||||
FTag: TTag;
|
||||
public
|
||||
procedure Unlink;
|
||||
property Producer: IProducer<T> read FProducer;
|
||||
class operator Implicit(A: TSubscription): TProducer<T>; overload;
|
||||
property Owner: IProducer<T> read FOwner;
|
||||
end;
|
||||
private
|
||||
FProducer: IProducer<T>;
|
||||
@@ -54,17 +56,14 @@ type
|
||||
class operator Implicit(const A: IProducer<T>): TProducer<T>; overload;
|
||||
class operator Implicit(const A: TProducer<T>): IProducer<T>; overload;
|
||||
|
||||
function Link(const Consumer: IConsumer<T>): TSubscription; inline;
|
||||
function CreateLink(const Consumer: IConsumer<T>): TSubscription; inline;
|
||||
function CreateEndpoint(Lookback: Int64; out Series: TLazy<TSeries<T>>): TSubscription;
|
||||
function CreateSequence(Count: Integer; out Seq: TArray<IProducer<T>>): TSubscription; overload; experimental;
|
||||
|
||||
// Chain consumers
|
||||
function Chain(const Next: IConsumer<T>): IConsumer<T>; overload; inline;
|
||||
|
||||
function Chain<R>(const Next: IConverter<T, R>): TProducer<R>; overload; inline;
|
||||
function Chain<R>(const Func: TConstFunc<T, R>): TProducer<R>; overload; inline;
|
||||
|
||||
function CreateEndpoint(Lookback: Int64): TLazy<TSeries<T>>;
|
||||
|
||||
function CreateSequence(Count: Integer): TArray<IProducer<T>>; overload; experimental;
|
||||
|
||||
// Extracts the field of a record by it's name (using RTTI).
|
||||
function Field<R>(const FieldName: String): TProducer<R>; inline;
|
||||
|
||||
@@ -93,7 +92,8 @@ type
|
||||
class operator Implicit(const A: TConverter<S, T>): IConverter<S, T>; overload;
|
||||
|
||||
class function Construct(const Consumer: IConsumer<S>; const Producer: TProducer<T>): TConverter<S, T>; static;
|
||||
class function CreateGeneric(const Func: TConstFunc<S, T>): TConverter<S, T>; static;
|
||||
|
||||
class function CreateConverter(const Func: TConstFunc<S, T>): TConverter<S, T>; static;
|
||||
class function CreateAggregation(const Func: TConstFunc<S, TBroadcastProc, TState>): TConverter<S, T>; static;
|
||||
|
||||
property Consumer: IConsumer<S> read GetConsumer;
|
||||
@@ -102,20 +102,21 @@ type
|
||||
class property Null: IConverter<S, T> read GetNull;
|
||||
end;
|
||||
|
||||
TDataRecordLayoutHelper = record helper for TDataRecord.TLayout
|
||||
function FieldAsRecord<T>(const Name: String): TConverter<T, TDataRecord>;
|
||||
function FieldOfRecord<T>(const Name: String): TConverter<TDataRecord, T>;
|
||||
end;
|
||||
|
||||
// Factory for creating specific converter instances.
|
||||
TConverter = record
|
||||
class function CreateCounter<T>: TConverter<T, Int64>; static;
|
||||
class function CreateTicker<T>: TConverter<TArray<T>, T>; static;
|
||||
class function CreateRecordField<S, T>(const FieldName: String): TConverter<S, T>; static;
|
||||
class function CreateIdentity<T>: TConverter<T, T>; static;
|
||||
|
||||
class function CreateTickAggregation(Timeframe: TTimeframe): TConverter<TDataPoint<Double>, TDataPoint<TOhlcItem>>; static;
|
||||
class function CreateOhlcAggregation(Timeframe: TTimeframe): TConverter<TDataPoint<TOhlcItem>, TDataPoint<TOhlcItem>>; static;
|
||||
|
||||
class function CreateDataPointConverter<S, T>(const Func: TConstFunc<S, T>): TConverter<TDataPoint<S>, TDataPoint<T>>; 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 CreateEndpoint<T>(Lookback: Int64; out Series: TLazy<TSeries<T>>): IConsumer<T>; static;
|
||||
|
||||
class function Join<T>(const Producers: TArray<TProducer<T>>): TProducer<TArray<T>>; static;
|
||||
|
||||
@@ -139,6 +140,13 @@ type
|
||||
): TConverter<TArray<TDataRecord>, TDataRecord>; static;
|
||||
end;
|
||||
|
||||
TDataRecordHelper = record helper for TArray<TProducer<TDataRecord>>
|
||||
function JoinRecords(
|
||||
const TargetLayout: TDataRecord.TLayout;
|
||||
const Mapping: TArray<TConverter.TRecordMapping>
|
||||
): TProducer<TDataRecord>; experimental;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
@@ -150,11 +158,16 @@ procedure TProducer<T>.TSubscription.Unlink;
|
||||
begin
|
||||
if FTag <> nil then
|
||||
begin
|
||||
FProducer.Unlink(FTag);
|
||||
FOwner.Unlink(FTag);
|
||||
FTag := nil;
|
||||
end;
|
||||
end;
|
||||
|
||||
class operator TProducer<T>.TSubscription.Implicit(A: TSubscription): TProducer<T>;
|
||||
begin
|
||||
Result := A.FOwner;
|
||||
end;
|
||||
|
||||
constructor TProducer<T>.Create(const AProducer: IProducer<T>);
|
||||
begin
|
||||
FProducer := AProducer;
|
||||
@@ -170,7 +183,6 @@ end;
|
||||
|
||||
function TProducer<T>.Chain<R>(const Next: IConverter<T, R>): TProducer<R>;
|
||||
begin
|
||||
// REFACTOR: Link to the consumer part, return the converter as the new producer
|
||||
FProducer.Link(Next.Consumer);
|
||||
Result := Next;
|
||||
end;
|
||||
@@ -180,14 +192,14 @@ begin
|
||||
Result := Chain<R>(TMycGenericConverter<T, R>.Create(Func));
|
||||
end;
|
||||
|
||||
function TProducer<T>.CreateEndpoint(Lookback: Int64): TLazy<TSeries<T>>;
|
||||
function TProducer<T>.CreateEndpoint(Lookback: Int64; out Series: TLazy<TSeries<T>>): TSubscription;
|
||||
begin
|
||||
Result := TMycDataEndpoint<T>.Create(FProducer, Lookback);
|
||||
Result := CreateLink(TMycDataEndpoint<T>.CreateDataEndpoint(Lookback, Series));
|
||||
end;
|
||||
|
||||
function TProducer<T>.CreateSequence(Count: Integer): TArray<IProducer<T>>;
|
||||
function TProducer<T>.CreateSequence(Count: Integer; out Seq: TArray<IProducer<T>>): TSubscription;
|
||||
begin
|
||||
FProducer.Link(TMycSequence<T>.CreateSequence(Count, Result));
|
||||
Result := CreateLink(TMycSequence<T>.CreateSequence(Count, Seq));
|
||||
end;
|
||||
|
||||
function TProducer<T>.Field<R>(const FieldName: String): TProducer<R>;
|
||||
@@ -215,9 +227,9 @@ begin
|
||||
Result := A.FProducer;
|
||||
end;
|
||||
|
||||
function TProducer<T>.Link(const Consumer: IConsumer<T>): TSubscription;
|
||||
function TProducer<T>.CreateLink(const Consumer: IConsumer<T>): TSubscription;
|
||||
begin
|
||||
Result.FProducer := FProducer;
|
||||
Result.FOwner := FProducer;
|
||||
Result.FTag := FProducer.Link(Consumer);
|
||||
end;
|
||||
|
||||
@@ -243,7 +255,7 @@ begin
|
||||
Result := TMycGenericAggregator<S, T>.Create(Func);
|
||||
end;
|
||||
|
||||
class function TConverter<S, T>.CreateGeneric(const Func: TConstFunc<S, T>): TConverter<S, T>;
|
||||
class function TConverter<S, T>.CreateConverter(const Func: TConstFunc<S, T>): TConverter<S, T>;
|
||||
begin
|
||||
Result := TMycGenericConverter<S, T>.Create(Func);
|
||||
end;
|
||||
@@ -290,17 +302,9 @@ begin
|
||||
Result := TMycDataCounter<T>.Create;
|
||||
end;
|
||||
|
||||
class function TConverter.CreateDataPointConverter<S, T>(const Func: TConstFunc<S, T>): TConverter<TDataPoint<S>, TDataPoint<T>>;
|
||||
class function TConverter.CreateEndpoint<T>(Lookback: Int64; out Series: TLazy<TSeries<T>>): IConsumer<T>;
|
||||
begin
|
||||
var cFunc: TConstFunc<S, T> := Func;
|
||||
Result :=
|
||||
TMycGenericConverter<TDataPoint<S>, TDataPoint<T>>.Create(
|
||||
function(const Value: TDataPoint<S>): TDataPoint<T>
|
||||
begin
|
||||
Result.Time := Value.Time;
|
||||
Result.Data := cFunc(Value.Data);
|
||||
end
|
||||
);
|
||||
Result := TMycDataEndpoint<T>.CreateDataEndpoint(Lookback, Series);
|
||||
end;
|
||||
|
||||
class function TConverter.CreateIdentity<T>: TConverter<T, T>;
|
||||
@@ -308,11 +312,6 @@ begin
|
||||
Result := TMycIdentityConverter<T>.Create;
|
||||
end;
|
||||
|
||||
class function TConverter.CreateRecordField<S, T>(const FieldName: String): TConverter<S, T>;
|
||||
begin
|
||||
Result := TMycRecordFieldReader<S, T>.Create(FieldName);
|
||||
end;
|
||||
|
||||
class function TConverter.CreateTicker<T>: TConverter<TArray<T>, T>;
|
||||
begin
|
||||
Result := TMycTicker<T>.Create;
|
||||
@@ -344,7 +343,7 @@ begin
|
||||
end;
|
||||
|
||||
Result :=
|
||||
TConverter<TArray<TDataRecord>, TDataRecord>.CreateGeneric(
|
||||
TConverter<TArray<TDataRecord>, TDataRecord>.CreateConverter(
|
||||
function(const Inputs: TArray<TDataRecord>): TDataRecord
|
||||
begin
|
||||
Result := TDataRecord.Create(Output);
|
||||
@@ -355,28 +354,15 @@ begin
|
||||
);
|
||||
end;
|
||||
|
||||
class function TConverter.FieldOfRecord<T>(const Layout: TDataRecord.TLayout; const Name: String): TConverter<TDataRecord, T>;
|
||||
begin
|
||||
var idx := Layout.IndexOf(Name);
|
||||
Result := TConverter<TDataRecord, T>.CreateGeneric(function(const Value: TDataRecord): T begin Value.GetValue(idx, Result); end);
|
||||
end;
|
||||
|
||||
class function TConverter.FieldToRecord<T>(const Layout: TDataRecord.TLayout; const Name: String): TConverter<T, TDataRecord>;
|
||||
begin
|
||||
var idx := Layout.IndexOf(Name);
|
||||
Result :=
|
||||
TConverter<T, TDataRecord>.CreateGeneric(
|
||||
function(const Value: T): TDataRecord
|
||||
begin
|
||||
Result := TDataRecord.Create(Layout);
|
||||
Result.SetValue(idx, Value);
|
||||
end
|
||||
);
|
||||
end;
|
||||
|
||||
class function TConverter.Join<T>(const Producers: TArray<TProducer<T>>): TProducer<TArray<T>>;
|
||||
var
|
||||
joiner: TMycDataJoin<T>;
|
||||
begin
|
||||
Result := TMycDataJoin<T>.Create(Producers);
|
||||
joiner := TMycDataJoin<T>.Create(Length(Producers));
|
||||
Result := joiner;
|
||||
|
||||
for var i := 0 to High(Producers) do
|
||||
Producers[i].Chain(joiner.Consumers[i]);
|
||||
end;
|
||||
|
||||
class function TConverter.JoinRecords(
|
||||
@@ -389,8 +375,39 @@ begin
|
||||
|
||||
Result := DataMapping(Mapping, TargetLayout);
|
||||
|
||||
// REFACTOR: Link to the consumer part of the mapping converter.
|
||||
RecProvider.Link(Result.Consumer);
|
||||
RecProvider.Chain(Result.Consumer);
|
||||
end;
|
||||
|
||||
function TDataRecordLayoutHelper.FieldAsRecord<T>(const Name: String): TConverter<T, TDataRecord>;
|
||||
begin
|
||||
var layout := Self;
|
||||
var idx := IndexOf(Name);
|
||||
Result :=
|
||||
TConverter<T, TDataRecord>.CreateConverter(
|
||||
function(const Value: T): TDataRecord
|
||||
begin
|
||||
Result := TDataRecord.Create(layout);
|
||||
Result.SetValue(idx, Value);
|
||||
end
|
||||
);
|
||||
end;
|
||||
|
||||
function TDataRecordLayoutHelper.FieldOfRecord<T>(const Name: String): TConverter<TDataRecord, T>;
|
||||
begin
|
||||
var idx := IndexOf(Name);
|
||||
Result := TConverter<TDataRecord, T>.CreateConverter(function(const Value: TDataRecord): T begin Value.GetValue(idx, Result); end);
|
||||
end;
|
||||
|
||||
function TDataRecordHelper.JoinRecords(
|
||||
const TargetLayout: TDataRecord.TLayout;
|
||||
const Mapping: TArray<TConverter.TRecordMapping>
|
||||
): TProducer<TDataRecord>;
|
||||
begin
|
||||
var map := TConverter.DataMapping(Mapping, TargetLayout);
|
||||
|
||||
TConverter.Join<TDataRecord>(Self).Chain(map.Consumer);
|
||||
|
||||
Result := map.Producer;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
Reference in New Issue
Block a user