DataStream updated
This commit is contained in:
+13
-31
@@ -102,11 +102,8 @@ type
|
|||||||
private
|
private
|
||||||
FOnEvent: TNotifyEvent;
|
FOnEvent: TNotifyEvent;
|
||||||
{ Private declarations }
|
{ Private declarations }
|
||||||
{$ifdef TICKDATA}
|
FFileCache: IDataFileCache<TArray<TBytes>>;
|
||||||
FServer: IDataServer<TAuraAskBidFileItem>;
|
FServer: IDataServer;
|
||||||
{$else}
|
|
||||||
FServer: IDataServer<TOhlcItem>;
|
|
||||||
{$endif}
|
|
||||||
FSymbols: TFuture<TArray<String>>;
|
FSymbols: TFuture<TArray<String>>;
|
||||||
FTerminate: TEvent;
|
FTerminate: TEvent;
|
||||||
FProcessDone: TState;
|
FProcessDone: TState;
|
||||||
@@ -228,10 +225,11 @@ begin
|
|||||||
FTerminate := TEvent.CreateEvent;
|
FTerminate := TEvent.CreateEvent;
|
||||||
|
|
||||||
// Create an instance of the TAuraTABFileServer. The server can be reused for multiple stream creations. [364]
|
// Create an instance of the TAuraTABFileServer. The server can be reused for multiple stream creations. [364]
|
||||||
|
FFileCache := TDataFileCache<TArray<TBytes>>.Create;
|
||||||
{$ifdef TICKDATA}
|
{$ifdef TICKDATA}
|
||||||
FServer := TAskBidFileServer.Create('\\COFFEE\TickData\Pepperstone');
|
FServer := TTickFileServer.Create(FFileCache, '\\COFFEE\TickData\Pepperstone');
|
||||||
{$else}
|
{$else}
|
||||||
FServer := TM1FileServer.Create('\\COFFEE\TickData\Pepperstone');
|
FServer := TM1FileServer.Create(FFileCache, '\\COFFEE\TickData\Pepperstone');
|
||||||
{$endif}
|
{$endif}
|
||||||
|
|
||||||
SymbolsComboBox.Enabled := false;
|
SymbolsComboBox.Enabled := false;
|
||||||
@@ -339,7 +337,7 @@ begin
|
|||||||
var panel := pnlChart.AddPanel;
|
var panel := pnlChart.AddPanel;
|
||||||
panel.AddDoubleSeries(equity, TAlphaColors.Blue, 3);
|
panel.AddDoubleSeries(equity, TAlphaColors.Blue, 3);
|
||||||
|
|
||||||
FProcessDone := FProcessDone + FServer.ProcessData(Symbol, terminated, ticker.Consumer);
|
FProcessDone := FProcessDone + (FServer as IM1FileServer).ProcessData(Symbol, terminated, ticker.Consumer);
|
||||||
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -587,44 +585,28 @@ begin
|
|||||||
var terminated := TFlag.CreateObserver(FTerminate.Signal).State;
|
var terminated := TFlag.CreateObserver(FTerminate.Signal).State;
|
||||||
|
|
||||||
{$ifdef TICKDATA}
|
{$ifdef TICKDATA}
|
||||||
var ticker := TConverter.CreateTicker<TDataPoint<TAskBidItem>>;
|
var ticker := TConverter.CreateTicker<TDataPoint<TTickRecord>>;
|
||||||
|
|
||||||
var lastPrice :=
|
var lastPrice :=
|
||||||
ticker.Chain<TDataPoint<Double>>(
|
ticker.Producer.Chain<TDataPoint<Double>>(
|
||||||
function(const Tick: TDataPoint<TAskBidItem>): TDataPoint<Double>
|
function(const Tick: TDataPoint<TTickRecord>): TDataPoint<Double>
|
||||||
begin
|
begin
|
||||||
Result.Time := Tick.Time;
|
Result.Time := Tick.Time;
|
||||||
Result.Data := 0.5 * (Tick.Data.Ask + Tick.Data.Bid);
|
Result.Data := 0.5 * (Tick.Data.Ask + Tick.Data.Bid);
|
||||||
end
|
end
|
||||||
);
|
);
|
||||||
|
|
||||||
var OhlcPoint := lastPrice.Chain<TDataPoint<TOhlcItem>>(TConverter.CreateTickAggregation(Timeframe));
|
var OhlcPoint := lastPrice.Chain<TDataPoint<TOhlcItem>>(TTradeConverter.CreateTickAggregation(Timeframe));
|
||||||
OhlcPoint.Producer.Link(Consumer);
|
OhlcPoint.CreateLink(Consumer);
|
||||||
|
|
||||||
var Producer :=
|
FProcessDone := FProcessDone + (FServer as ITickFileServer).ProcessData(Symbol, terminated, ticker.Consumer);
|
||||||
TConverter<TArray<TDataPoint<TAuraAskBidFileItem>>, TArray<TDataPoint<TAskBidItem>>>.CreateGeneric(
|
|
||||||
function(const Values: TArray<TDataPoint<TAuraAskBidFileItem>>): TArray<TDataPoint<TAskBidItem>>
|
|
||||||
begin
|
|
||||||
SetLength(Result, Length(Values));
|
|
||||||
for var i := 0 to High(Result) do
|
|
||||||
begin
|
|
||||||
Result[i].Time := Values[i].Time;
|
|
||||||
Result[i].Data.Ask := Values[i].Data.Ask;
|
|
||||||
Result[i].Data.Bid := Values[i].Data.Bid;
|
|
||||||
end;
|
|
||||||
end
|
|
||||||
);
|
|
||||||
|
|
||||||
Producer.Producer.Link(ticker);
|
|
||||||
|
|
||||||
FProcessDone := FProcessDone + FServer.ProcessData(Symbol, terminated, Producer);
|
|
||||||
{$else}
|
{$else}
|
||||||
|
|
||||||
var ticker := TConverter.CreateTicker<TDataPoint<TOhlcItem>>;
|
var ticker := TConverter.CreateTicker<TDataPoint<TOhlcItem>>;
|
||||||
|
|
||||||
ticker.Producer.Chain(Consumer);
|
ticker.Producer.Chain(Consumer);
|
||||||
|
|
||||||
FProcessDone := FProcessDone + FServer.ProcessData(Symbol, terminated, ticker.Consumer);
|
FProcessDone := FProcessDone + (FServer as IM1FileServer).ProcessData(Symbol, terminated, ticker.Consumer);
|
||||||
{$endif}
|
{$endif}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|||||||
@@ -77,11 +77,12 @@ end;
|
|||||||
|
|
||||||
procedure TDataFileCache<T>.Clear;
|
procedure TDataFileCache<T>.Clear;
|
||||||
begin
|
begin
|
||||||
FLock.Enter;
|
|
||||||
try
|
|
||||||
// Wait for any pending load operations to finish before clearing.
|
// Wait for any pending load operations to finish before clearing.
|
||||||
for var cachedFile in FCachedFiles.Values do
|
for var cachedFile in FCachedFiles.Values do
|
||||||
cachedFile.Data.WaitFor;
|
cachedFile.Data.WaitFor;
|
||||||
|
|
||||||
|
FLock.Enter;
|
||||||
|
try
|
||||||
FCachedFiles.Clear;
|
FCachedFiles.Clear;
|
||||||
finally
|
finally
|
||||||
FLock.Leave;
|
FLock.Leave;
|
||||||
|
|||||||
+110
-65
@@ -34,29 +34,30 @@ type
|
|||||||
Bid: Double;
|
Bid: Double;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Interface for an instantiable data server.
|
IDataServer = interface
|
||||||
IDataServer<T: record> = interface
|
|
||||||
procedure ClearCache;
|
procedure ClearCache;
|
||||||
function ProcessData(const Symbol: String; const Terminated: TState; const Processor: IConsumer<TArray<TDataPoint<T>>>): TState;
|
|
||||||
function EnumerateSymbols: TFuture<TArray<String>>;
|
function EnumerateSymbols: TFuture<TArray<String>>;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// Interface for an instantiable data server.
|
||||||
|
IDataServer<T: record> = interface(IDataServer)
|
||||||
|
function ProcessData(const Symbol: String; const Terminated: TState; const Processor: IConsumer<TArray<TDataPoint<T>>>): TState;
|
||||||
|
end;
|
||||||
|
|
||||||
// Metadata for a monthly data file (SYMBOL_YYYY_MM.EXT)
|
// Metadata for a monthly data file (SYMBOL_YYYY_MM.EXT)
|
||||||
TDataFile = record
|
TDataFile = record
|
||||||
private
|
private
|
||||||
FExtension: String;
|
|
||||||
FPath: String;
|
FPath: String;
|
||||||
FSymbol: String;
|
FSymbol: String; // Includes extension, e.g. "EURUSD.M1"
|
||||||
FYear: Integer;
|
FYear: Integer;
|
||||||
FMonth: Integer;
|
FMonth: Integer;
|
||||||
FBasename: String;
|
FBasename: String;
|
||||||
function GetIsValid: Boolean;
|
function GetIsValid: Boolean;
|
||||||
public
|
public
|
||||||
constructor Create(const APath, ASymbol, AExtension: String; AYear, AMonth: Integer; const ABasename: String);
|
constructor Create(const APath, ASymbol: String; AYear, AMonth: Integer; const ABasename: String);
|
||||||
function GetBaseFileName: string;
|
function GetBaseFileName: string;
|
||||||
function GetFullFileName: string;
|
function GetFullFileName: string;
|
||||||
property IsValid: Boolean read GetIsValid;
|
property IsValid: Boolean read GetIsValid;
|
||||||
property Extension: String read FExtension;
|
|
||||||
property Path: String read FPath;
|
property Path: String read FPath;
|
||||||
property Symbol: String read FSymbol;
|
property Symbol: String read FSymbol;
|
||||||
property Year: Integer read FYear;
|
property Year: Integer read FYear;
|
||||||
@@ -64,22 +65,19 @@ type
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
// Generic server base handling the pipeline and preloading.
|
// Generic server base handling the pipeline and preloading.
|
||||||
TDataServer<T: record; R: record> = class(TInterfacedObject, IDataServer<T>)
|
TDataServer<T: record; R: record> = class(TInterfacedObject, IDataServer, IDataServer<T>)
|
||||||
private
|
private
|
||||||
FCachedFiles: TDataFileCache<TArray<TArray<TDataPoint<T>>>>;
|
// Cache stores binary blobs of the MAPPED data (TDataPoint<T>)
|
||||||
|
FCachedFiles: IDataFileCache<TArray<TBytes>>;
|
||||||
FPath: String;
|
FPath: String;
|
||||||
FSymbols: TFuture<TDictionary<String, TArray<TDataFile>>>;
|
FSymbols: TFuture<TDictionary<String, TArray<TDataFile>>>;
|
||||||
function GetPath: String;
|
function GetPath: String;
|
||||||
|
|
||||||
function ProcessChunks(
|
function ProcessChunks(const MappedBlobs: TArray<TBytes>; Terminated: TState; Processor: IConsumer<TArray<TDataPoint<T>>>): TState;
|
||||||
const DataChunks: TArray<TArray<TDataPoint<T>>>;
|
|
||||||
Terminated: TState;
|
|
||||||
Processor: IConsumer<TArray<TDataPoint<T>>>
|
|
||||||
): TState;
|
|
||||||
|
|
||||||
function ProcessFile(
|
function ProcessFile(
|
||||||
FileInfo: TDataFile;
|
FileInfo: TDataFile;
|
||||||
const DataFile: TFuture<TArray<TArray<TDataPoint<T>>>>;
|
const DataFile: TFuture<TArray<TBytes>>;
|
||||||
const Terminated: TState;
|
const Terminated: TState;
|
||||||
Processor: IConsumer<TArray<TDataPoint<T>>>
|
Processor: IConsumer<TArray<TDataPoint<T>>>
|
||||||
): TState;
|
): TState;
|
||||||
@@ -87,17 +85,22 @@ type
|
|||||||
strict private
|
strict private
|
||||||
class var
|
class var
|
||||||
FLoadGate: TLatch;
|
FLoadGate: TLatch;
|
||||||
|
class constructor CreateClass;
|
||||||
|
|
||||||
protected
|
protected
|
||||||
// Maps the raw file record to the internal TDataPoint<T>
|
// Maps the raw file record to the internal TDataPoint<T>
|
||||||
function MapRecord(const RecordBuffer: R): TDataPoint<T>; virtual; abstract;
|
function MapRecord(const RecordBuffer: R): TDataPoint<T>; virtual; abstract;
|
||||||
function DoLoad(const FileName: string; const Gate: TLatch): TFuture<TArray<TArray<TDataPoint<T>>>>;
|
|
||||||
|
// Returns Future of Processed Blobs (Serialized TDataPoint<T>)
|
||||||
|
function DoLoad(const FileName: string; const Gate: TLatch): TFuture<TArray<TBytes>>;
|
||||||
|
|
||||||
function GetNextFile(const Curr: TDataFile): TDataFile; virtual;
|
function GetNextFile(const Curr: TDataFile): TDataFile; virtual;
|
||||||
function ReadCompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<T>>>;
|
function ReadCompressedData(const InputStream: TStream): TArray<TBytes>;
|
||||||
function ReadUncompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<T>>>;
|
function ReadUncompressedData(const InputStream: TStream): TArray<TBytes>;
|
||||||
|
|
||||||
public
|
public
|
||||||
constructor Create(const APath: String);
|
// Cache is injected. It stores TArray<TBytes>.
|
||||||
|
constructor Create(const ACache: IDataFileCache<TArray<TBytes>>; const APath: String);
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
procedure AfterConstruction; override;
|
procedure AfterConstruction; override;
|
||||||
procedure ClearCache;
|
procedure ClearCache;
|
||||||
@@ -105,18 +108,27 @@ type
|
|||||||
function FindFirstFile(const Symbol: string): TFuture<TDataFile>;
|
function FindFirstFile(const Symbol: string): TFuture<TDataFile>;
|
||||||
function ParseFileName(const FileName: string): TDataFile;
|
function ParseFileName(const FileName: string): TDataFile;
|
||||||
function EnumerateSymbols: TFuture<TArray<String>>;
|
function EnumerateSymbols: TFuture<TArray<String>>;
|
||||||
function LoadDataFile(const DataFile: TDataFile): TFuture<TArray<TArray<TDataPoint<T>>>>;
|
|
||||||
|
function LoadDataFile(const DataFile: TDataFile): TFuture<TArray<TBytes>>;
|
||||||
function ProcessData(const Symbol: String; const Terminated: TState; const Processor: IConsumer<TArray<TDataPoint<T>>>): TState;
|
function ProcessData(const Symbol: String; const Terminated: TState; const Processor: IConsumer<TArray<TDataPoint<T>>>): TState;
|
||||||
|
|
||||||
property Path: String read GetPath;
|
property Path: String read GetPath;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TTickFileServer = class(TDataServer<TTickRecord, TTickRecord>)
|
ITickFileServer = interface(IDataServer<TTickRecord>)
|
||||||
|
['{A6DE8B82-B0FB-48DB-A39A-CE91D861C5D2}']
|
||||||
|
end;
|
||||||
|
|
||||||
|
TTickFileServer = class(TDataServer<TTickRecord, TTickRecord>, ITickFileServer)
|
||||||
protected
|
protected
|
||||||
function MapRecord(const RecordBuffer: TTickRecord): TDataPoint<TTickRecord>; override;
|
function MapRecord(const RecordBuffer: TTickRecord): TDataPoint<TTickRecord>; override;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TM1FileServer = class(TDataServer<TOhlcItem, TM1Record>)
|
IM1FileServer = interface(IDataServer<TOhlcItem>)
|
||||||
|
['{7BB980D9-1969-49A4-991F-908F2DDAC1A0}']
|
||||||
|
end;
|
||||||
|
|
||||||
|
TM1FileServer = class(TDataServer<TOhlcItem, TM1Record>, IM1FileServer)
|
||||||
protected
|
protected
|
||||||
function MapRecord(const RecordBuffer: TM1Record): TDataPoint<TOhlcItem>; override;
|
function MapRecord(const RecordBuffer: TM1Record): TDataPoint<TOhlcItem>; override;
|
||||||
end;
|
end;
|
||||||
@@ -135,11 +147,10 @@ const
|
|||||||
|
|
||||||
{ TDataFile }
|
{ TDataFile }
|
||||||
|
|
||||||
constructor TDataFile.Create(const APath, ASymbol, AExtension: String; AYear, AMonth: Integer; const ABasename: String);
|
constructor TDataFile.Create(const APath, ASymbol: String; AYear, AMonth: Integer; const ABasename: String);
|
||||||
begin
|
begin
|
||||||
FPath := APath;
|
FPath := APath;
|
||||||
FSymbol := ASymbol;
|
FSymbol := ASymbol;
|
||||||
FExtension := AExtension;
|
|
||||||
FYear := AYear;
|
FYear := AYear;
|
||||||
FMonth := AMonth;
|
FMonth := AMonth;
|
||||||
FBasename := ABasename;
|
FBasename := ABasename;
|
||||||
@@ -151,8 +162,11 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
function TDataFile.GetFullFileName: string;
|
function TDataFile.GetFullFileName: string;
|
||||||
|
var
|
||||||
|
ext: string;
|
||||||
begin
|
begin
|
||||||
Result := TPath.Combine(FPath, FBasename + FExtension);
|
ext := TPath.GetExtension(FSymbol);
|
||||||
|
Result := TPath.Combine(FPath, FBasename + ext);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TDataFile.GetIsValid: Boolean;
|
function TDataFile.GetIsValid: Boolean;
|
||||||
@@ -162,17 +176,20 @@ end;
|
|||||||
|
|
||||||
{ TDataServer<T, R> }
|
{ TDataServer<T, R> }
|
||||||
|
|
||||||
constructor TDataServer<T, R>.Create(const APath: String);
|
class constructor TDataServer<T, R>.CreateClass;
|
||||||
|
begin
|
||||||
|
FLoadGate := TLatch.CreateLatch(0);
|
||||||
|
end;
|
||||||
|
|
||||||
|
constructor TDataServer<T, R>.Create(const ACache: IDataFileCache<TArray<TBytes>>; const APath: String);
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
FPath := APath;
|
FPath := APath;
|
||||||
FCachedFiles := TDataFileCache<TArray<TArray<TDataPoint<T>>>>.Create;
|
FCachedFiles := ACache;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
destructor TDataServer<T, R>.Destroy;
|
destructor TDataServer<T, R>.Destroy;
|
||||||
begin
|
begin
|
||||||
ClearCache;
|
|
||||||
FCachedFiles.Free;
|
|
||||||
inherited Destroy;
|
inherited Destroy;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -184,6 +201,7 @@ end;
|
|||||||
|
|
||||||
procedure TDataServer<T, R>.ClearCache;
|
procedure TDataServer<T, R>.ClearCache;
|
||||||
begin
|
begin
|
||||||
|
if Assigned(FCachedFiles) then
|
||||||
FCachedFiles.Clear;
|
FCachedFiles.Clear;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -227,7 +245,7 @@ begin
|
|||||||
TComparer<TDataFile>.Construct(
|
TComparer<TDataFile>.Construct(
|
||||||
function(const Left, Right: TDataFile): Integer
|
function(const Left, Right: TDataFile): Integer
|
||||||
begin
|
begin
|
||||||
if Left.Year <> Right.Year then
|
if (Left.Year <> Right.Year) then
|
||||||
Result := Left.Year - Right.Year
|
Result := Left.Year - Right.Year
|
||||||
else
|
else
|
||||||
Result := Left.Month - Right.Month;
|
Result := Left.Month - Right.Month;
|
||||||
@@ -264,7 +282,7 @@ begin
|
|||||||
var
|
var
|
||||||
symbolFiles: TArray<TDataFile>;
|
symbolFiles: TArray<TDataFile>;
|
||||||
begin
|
begin
|
||||||
if Symbols.TryGetValue(symName, symbolFiles) and (Length(symbolFiles) > 0) then
|
if (Symbols.TryGetValue(symName, symbolFiles)) and (Length(symbolFiles) > 0) then
|
||||||
Result := symbolFiles[0]
|
Result := symbolFiles[0]
|
||||||
else
|
else
|
||||||
Result := Default(TDataFile);
|
Result := Default(TDataFile);
|
||||||
@@ -287,7 +305,7 @@ begin
|
|||||||
foundIndex := -1;
|
foundIndex := -1;
|
||||||
for var i := 0 to High(allSymbolFiles) do
|
for var i := 0 to High(allSymbolFiles) do
|
||||||
begin
|
begin
|
||||||
if allSymbolFiles[i].GetBaseFileName = Curr.GetBaseFileName then
|
if (allSymbolFiles[i].GetBaseFileName = Curr.GetBaseFileName) then
|
||||||
begin
|
begin
|
||||||
foundIndex := i;
|
foundIndex := i;
|
||||||
break;
|
break;
|
||||||
@@ -314,39 +332,37 @@ begin
|
|||||||
ext := TPath.GetExtension(fileNameNoPath);
|
ext := TPath.GetExtension(fileNameNoPath);
|
||||||
baseName := TPath.GetFileNameWithoutExtension(fileNameNoPath);
|
baseName := TPath.GetFileNameWithoutExtension(fileNameNoPath);
|
||||||
|
|
||||||
// Format: SYMBOL_YYYY_MM
|
|
||||||
parts := baseName.Split(['_']);
|
parts := baseName.Split(['_']);
|
||||||
if Length(parts) < 3 then
|
if (Length(parts) < 3) then
|
||||||
exit;
|
exit;
|
||||||
|
|
||||||
// Use reverse indexing for year and month
|
|
||||||
if (not TryStrToInt(parts[High(parts)], month)) or (not TryStrToInt(parts[High(parts) - 1], year)) then
|
if (not TryStrToInt(parts[High(parts)], month)) or (not TryStrToInt(parts[High(parts) - 1], year)) then
|
||||||
exit;
|
exit;
|
||||||
|
|
||||||
symbol := string.Join('_', Copy(parts, 0, Length(parts) - 2));
|
symbol := string.Join('_', Copy(parts, 0, Length(parts) - 2)) + ext;
|
||||||
Result := TDataFile.Create(TPath.GetDirectoryName(FileName), symbol, ext, year, month, baseName);
|
Result := TDataFile.Create(TPath.GetDirectoryName(FileName), symbol, year, month, baseName);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TDataServer<T, R>.LoadDataFile(const DataFile: TDataFile): TFuture<TArray<TArray<TDataPoint<T>>>>;
|
function TDataServer<T, R>.LoadDataFile(const DataFile: TDataFile): TFuture<TArray<TBytes>>;
|
||||||
begin
|
begin
|
||||||
if DataFile.IsValid then
|
if (DataFile.IsValid) then
|
||||||
Result :=
|
Result :=
|
||||||
FCachedFiles.GetOrAdd(
|
FCachedFiles.GetOrAdd(
|
||||||
DataFile.GetFullFileName,
|
DataFile.GetFullFileName,
|
||||||
function(const Filename: String): TFuture<TArray<TArray<TDataPoint<T>>>> begin Result := DoLoad(Filename, FLoadGate); end
|
function(const Filename: String): TFuture<TArray<TBytes>> begin Result := DoLoad(Filename, FLoadGate); end
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
Result := TFuture<TArray<TArray<TDataPoint<T>>>>.Construct(TArray<TArray<TDataPoint<T>>>(nil));
|
Result := TFuture<TArray<TBytes>>.Construct(TArray<TBytes>(nil));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TDataServer<T, R>.DoLoad(const FileName: string; const Gate: TLatch): TFuture<TArray<TArray<TDataPoint<T>>>>;
|
function TDataServer<T, R>.DoLoad(const FileName: string; const Gate: TLatch): TFuture<TArray<TBytes>>;
|
||||||
begin
|
begin
|
||||||
var capFileName := FileName;
|
var capFileName := FileName;
|
||||||
Result :=
|
Result :=
|
||||||
TFuture<TBytes>
|
TFuture<TBytes>
|
||||||
.Construct(Gate.Enqueue, function: TBytes begin Result := TFile.ReadAllBytes(capFileName); end)
|
.Construct(Gate.Enqueue, function: TBytes begin Result := TFile.ReadAllBytes(capFileName); end)
|
||||||
.Chain<TArray<TArray<TDataPoint<T>>>>(
|
.Chain<TArray<TBytes>>(
|
||||||
function(Bytes: TBytes): TArray<TArray<TDataPoint<T>>>
|
function(Bytes: TBytes): TArray<TBytes>
|
||||||
var
|
var
|
||||||
compStream: TBytesStream;
|
compStream: TBytesStream;
|
||||||
begin
|
begin
|
||||||
@@ -359,7 +375,7 @@ begin
|
|||||||
end);
|
end);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TDataServer<T, R>.ReadCompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<T>>>;
|
function TDataServer<T, R>.ReadCompressedData(const InputStream: TStream): TArray<TBytes>;
|
||||||
var
|
var
|
||||||
zip: TZipFile;
|
zip: TZipFile;
|
||||||
decompStream: TStream;
|
decompStream: TStream;
|
||||||
@@ -375,14 +391,14 @@ begin
|
|||||||
// Search for the .bin entry created by C#
|
// Search for the .bin entry created by C#
|
||||||
for var i := 0 to zip.FileCount - 1 do
|
for var i := 0 to zip.FileCount - 1 do
|
||||||
begin
|
begin
|
||||||
if zip.FileNames[i].EndsWith('.bin', true) then
|
if (zip.FileNames[i].EndsWith('.bin', true)) then
|
||||||
begin
|
begin
|
||||||
entryIdx := i;
|
entryIdx := i;
|
||||||
break;
|
break;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if entryIdx = -1 then
|
if (entryIdx = -1) then
|
||||||
exit;
|
exit;
|
||||||
|
|
||||||
zip.Read(entryIdx, decompStream, header);
|
zip.Read(entryIdx, decompStream, header);
|
||||||
@@ -396,37 +412,54 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TDataServer<T, R>.ReadUncompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<T>>>;
|
function TDataServer<T, R>.ReadUncompressedData(const InputStream: TStream): TArray<TBytes>;
|
||||||
var
|
var
|
||||||
totalRecords, chunkCount, i, j, recsInChunk: Integer;
|
totalRecords, chunkCount, i, j, recsInChunk: Integer;
|
||||||
rawBuffer: TArray<R>;
|
inputRecordSize, outputPointSize: Integer;
|
||||||
|
chunkBuffer: TArray<TDataPoint<T>>;
|
||||||
|
rawBufferChunk: TArray<R>; // Only buffer one chunk at a time
|
||||||
begin
|
begin
|
||||||
Result := nil;
|
Result := nil;
|
||||||
totalRecords := InputStream.Size div sizeof(R);
|
inputRecordSize := SizeOf(R);
|
||||||
if totalRecords = 0 then
|
outputPointSize := SizeOf(TDataPoint<T>);
|
||||||
exit;
|
|
||||||
|
|
||||||
// Fast block read into memory
|
// Ensure we are working with complete records
|
||||||
SetLength(rawBuffer, totalRecords);
|
if (InputStream.Size mod inputRecordSize <> 0) then
|
||||||
InputStream.Position := 0;
|
raise Exception.Create('Stream size is not a multiple of record size.');
|
||||||
InputStream.ReadBuffer(rawBuffer[0], InputStream.Size);
|
|
||||||
|
totalRecords := InputStream.Size div inputRecordSize;
|
||||||
|
if (totalRecords = 0) then
|
||||||
|
exit;
|
||||||
|
|
||||||
chunkCount := (totalRecords + ChunkSize - 1) div ChunkSize;
|
chunkCount := (totalRecords + ChunkSize - 1) div ChunkSize;
|
||||||
SetLength(Result, chunkCount);
|
SetLength(Result, chunkCount);
|
||||||
|
SetLength(rawBufferChunk, ChunkSize); // Reuse buffer
|
||||||
|
|
||||||
|
InputStream.Position := 0;
|
||||||
|
|
||||||
for i := 0 to chunkCount - 1 do
|
for i := 0 to chunkCount - 1 do
|
||||||
begin
|
begin
|
||||||
recsInChunk := Min(ChunkSize, totalRecords - (i * ChunkSize));
|
recsInChunk := Min(ChunkSize, totalRecords - (i * ChunkSize));
|
||||||
SetLength(Result[i], recsInChunk);
|
|
||||||
|
// Read only needed bytes for this chunk
|
||||||
|
InputStream.ReadBuffer(rawBufferChunk[0], recsInChunk * inputRecordSize);
|
||||||
|
|
||||||
|
// Temp buffer for mapped objects
|
||||||
|
SetLength(chunkBuffer, recsInChunk);
|
||||||
|
|
||||||
for j := 0 to recsInChunk - 1 do
|
for j := 0 to recsInChunk - 1 do
|
||||||
begin
|
begin
|
||||||
Result[i][j] := MapRecord(rawBuffer[i * ChunkSize + j]);
|
chunkBuffer[j] := MapRecord(rawBufferChunk[j]);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// Serialize mapped data to TBytes
|
||||||
|
SetLength(Result[i], recsInChunk * outputPointSize);
|
||||||
|
Move(chunkBuffer[0], Result[i][0], recsInChunk * outputPointSize);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TDataServer<T, R>.ProcessChunks(
|
function TDataServer<T, R>.ProcessChunks(
|
||||||
const DataChunks: TArray<TArray<TDataPoint<T>>>;
|
const MappedBlobs: TArray<TBytes>;
|
||||||
Terminated: TState;
|
Terminated: TState;
|
||||||
Processor: IConsumer<TArray<TDataPoint<T>>>
|
Processor: IConsumer<TArray<TDataPoint<T>>>
|
||||||
): TState;
|
): TState;
|
||||||
@@ -434,16 +467,28 @@ begin
|
|||||||
var callProcess :=
|
var callProcess :=
|
||||||
function(Idx: Integer): TFunc<TState>
|
function(Idx: Integer): TFunc<TState>
|
||||||
begin
|
begin
|
||||||
var cChunk := DataChunks[Idx];
|
// Capture the blob
|
||||||
|
var cBlob := MappedBlobs[Idx];
|
||||||
Result :=
|
Result :=
|
||||||
function: TState
|
function: TState
|
||||||
|
var
|
||||||
|
dataChunk: TArray<TDataPoint<T>>;
|
||||||
|
count: Integer;
|
||||||
begin
|
begin
|
||||||
if not Terminated.IsSet then
|
if not Terminated.IsSet then
|
||||||
Result := Processor.Consume(cChunk);
|
begin
|
||||||
|
// Quick deserialize (Mem Copy)
|
||||||
|
count := Length(cBlob) div SizeOf(TDataPoint<T>);
|
||||||
|
SetLength(dataChunk, count);
|
||||||
|
if count > 0 then
|
||||||
|
Move(cBlob[0], dataChunk[0], Length(cBlob));
|
||||||
|
|
||||||
|
Result := Processor.Consume(dataChunk);
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
for var i := 0 to High(DataChunks) do
|
for var i := 0 to High(MappedBlobs) do
|
||||||
begin
|
begin
|
||||||
if not Terminated.IsSet then
|
if not Terminated.IsSet then
|
||||||
begin
|
begin
|
||||||
@@ -455,7 +500,7 @@ end;
|
|||||||
|
|
||||||
function TDataServer<T, R>.ProcessFile(
|
function TDataServer<T, R>.ProcessFile(
|
||||||
FileInfo: TDataFile;
|
FileInfo: TDataFile;
|
||||||
const DataFile: TFuture<TArray<TArray<TDataPoint<T>>>>;
|
const DataFile: TFuture<TArray<TBytes>>;
|
||||||
const Terminated: TState;
|
const Terminated: TState;
|
||||||
Processor: IConsumer<TArray<TDataPoint<T>>>
|
Processor: IConsumer<TArray<TDataPoint<T>>>
|
||||||
): TState;
|
): TState;
|
||||||
@@ -469,9 +514,9 @@ begin
|
|||||||
|
|
||||||
Result :=
|
Result :=
|
||||||
DataFile.Chain(
|
DataFile.Chain(
|
||||||
function(const DataChunks: TArray<TArray<TDataPoint<T>>>): TState
|
function(const MappedBlobs: TArray<TBytes>): TState
|
||||||
begin
|
begin
|
||||||
var done := ProcessChunks(DataChunks, cTerminated, Processor);
|
var done := ProcessChunks(MappedBlobs, cTerminated, Processor);
|
||||||
Result :=
|
Result :=
|
||||||
TaskManager
|
TaskManager
|
||||||
.RunTask(done, function: TState begin Result := ProcessFile(nextFileInfo, nextFile, cTerminated, Processor); end);
|
.RunTask(done, function: TState begin Result := ProcessFile(nextFileInfo, nextFile, cTerminated, Processor); end);
|
||||||
|
|||||||
Reference in New Issue
Block a user