* Preload files

This commit is contained in:
Michaël Van Canneyt 2024-06-06 14:41:30 +02:00
parent b84e5c60bf
commit 7c9edfcac7
3 changed files with 225 additions and 21 deletions

View File

@ -41,6 +41,23 @@ Const
type
TMemBufferArray = Array of TJSUint8Array;
TPreLoadFile = record
url : String;
localname : string;
end;
TPreLoadFileDynArray = Array of TPreLoadFile;
TLoadFileFailure = record
url : String;
error : string;
end;
TLoadFileFailureDynArray = Array of TLoadFileFailure;
TPreLoadFilesResult = record
failedurls : TLoadFileFailureDynArray;
loadcount : integer;
end;
EWasiError = Class(Exception);
EWasiFSError = class(Exception)
@ -76,6 +93,8 @@ type
TPas2JSWASIEnvironment = class (TObject,IWASI)
Private
FArguments: TStrings;
FEnvironment: TStrings;
FExitCode: Nativeint;
FImportObject : TJSObject;
Finstance: TJSWebAssemblyInstance;
@ -98,6 +117,8 @@ type
function GetTotalIOVsLen(iovs: TMemBufferArray): Integer;
function GetIOVsAsBytes(iovs, iovsLen: NativeInt): TJSUInt8array;
function GetMemory: TJSWebassemblyMemory;
procedure SetArguments(AValue: TStrings);
procedure SetEnvironment(AValue: TStrings);
procedure SetInstance(AValue: TJSWebAssemblyInstance);
procedure SetLogAPI(AValue: Boolean);
procedure WriteFileStatToMem(BufPtr: TWasmMemoryLocation;
@ -122,12 +143,12 @@ type
// IWASI calls
// !! Please keep these sorted !!
function args_get(argv, argvBuf : NativeInt) : NativeInt; virtual;
function args_sizes_get(argc, argvBufSize : NativeInt) : NativeInt; virtual;
function args_get(argv, argvBuf : TWasmMemoryLocation) : NativeInt; virtual;
function args_sizes_get(argc, argvBufSize : TWasmMemoryLocation) : NativeInt; virtual;
function clock_res_get(clockId, resolution: NativeInt): NativeInt; virtual;
function clock_time_get(clockId, precision : NativeInt; time: TWasmMemoryLocation): NativeInt; virtual;
function environ_get(environ, environBuf : NativeInt) : NativeInt; virtual;
function environ_sizes_get(environCount, environBufSize : NativeInt) : NativeInt; virtual;
function environ_get(environ, environBuf : TWasmMemoryLocation) : NativeInt; virtual;
function environ_sizes_get(environCount, environBufSize : TWasmMemoryLocation) : NativeInt; virtual;
function fd_advise (fd, offset, len, advice : NativeInt) : NativeInt; virtual;
function fd_allocate (fd, offset, len : NativeInt) : NativeInt; virtual;
function fd_close(fd : NativeInt) : NativeInt; virtual;
@ -189,6 +210,11 @@ type
Procedure AddImports(aObject: TJSObject);
Property ImportObject : TJSObject Read GetImportObject;
Property IsLittleEndian : Boolean Read FIsLittleEndian Write FIsLittleEndian;
// Filesystem
function PreLoadFiles(aFiles: array of string): TPreLoadFilesResult; async;
function PreLoadFiles(aFiles: TPreLoadFileDynArray): TPreLoadFilesResult; async;
function PreLoadFilesIntoDirectory(aDirectory : String; aFiles: array of string): TPreLoadFilesResult; async;
Property OnStdOutputWrite : TWASIWriteEvent Read FOnStdOutputWrite Write FOnStdOutputWrite;
Property OnStdErrorWrite : TWASIWriteEvent Read FOnStdErrorWrite Write FOnStdErrorWrite;
Property OnGetConsoleInputBuffer : TGetConsoleInputBufferEvent Read FOnGetConsoleInputBuffer Write FOnGetConsoleInputBuffer;
@ -200,6 +226,8 @@ type
Property WASIImportName : String Read FWASIImportName Write FWASIImportName;
Property LogAPI : Boolean REad FLogAPI Write SetLogAPI;
Property FS : IWASIFS Read FWasiFS Write FWasiFS;
Property Arguments : TStrings Read FArguments Write SetArguments;
Property Environment : TStrings Read FEnvironment Write SetEnvironment;
end;
{ TImportExtension }
@ -747,6 +775,7 @@ begin
FImportExtensions.Remove(aExtension);
end;
function TPas2JSWASIEnvironment.getModuleMemoryDataView: TJSDataView;
begin
Result:=TJSDataView.New(Memory.buffer);
@ -827,55 +856,78 @@ begin
end;
function TPas2JSWASIEnvironment.environ_sizes_get(environCount,
environBufSize: NativeInt): NativeInt;
environBufSize: TWasmMemoryLocation): NativeInt;
Var
View : TJSDataView;
Size : integer;
begin
{$IFNDEF NO_WASI_DEBUG}
if LogAPI then
DoLog('TPas2JSWASIEnvironment.environ_sizes_get(%d,%d)',[environCount,environBufSize]);
DoLog('TPas2JSWASIEnvironment.environ_sizes_get([%x],[%x])',[environCount,environBufSize]);
{$ENDIF}
view:=getModuleMemoryDataView();
view.setUint32(environCount, 0, IsLittleEndian);
view.setUint32(environBufSize, 0, IsLittleEndian);
view.setUint32(environCount, Environment.Count, IsLittleEndian);
Size:=0;
// the LF will be counted for null terminators
if Environment.Count>0 then
Size:=Length(Environment.Text)+1;
view.setUint32(environBufSize, Size, IsLittleEndian);
Result:= WASI_ESUCCESS;
end;
function TPas2JSWASIEnvironment.environ_get(environ, environBuf: NativeInt
): NativeInt;
function TPas2JSWASIEnvironment.environ_get(environ, environBuf: TWasmMemoryLocation): NativeInt;
begin
{$IFNDEF NO_WASI_DEBUG}
if LogAPI then
DoLog('TPas2JSWASIEnvironment.environ_get(%d,%d)',[environ,environBuf]);
DoLog('TPas2JSWASIEnvironment.environ_get([%x],[%x])',[environ,environBuf]);
{$ENDIF}
Result:= WASI_ESUCCESS;
end;
function TPas2JSWASIEnvironment.args_sizes_get(argc, argvBufSize: NativeInt
): NativeInt;
function TPas2JSWASIEnvironment.args_sizes_get(argc, argvBufSize: TWasmMemoryLocation): NativeInt;
Var
View : TJSDataView;
Size : Integer;
begin
{$IFNDEF NO_WASI_DEBUG}
if LogAPI then
DoLog('TPas2JSWASIEnvironment.args_sizes_get(%d,%d)',[argc,argvbufsize]);
DoLog('TPas2JSWASIEnvironment.args_sizes_get([%x],[%x])',[argc,argvbufsize]);
{$ENDIF}
view:=getModuleMemoryDataView();
view.setUint32(argc, 0, IsLittleEndian);
view.setUint32(argvBufSize, 0, IsLittleEndian);
view.setUint32(argc, Arguments.Count, IsLittleEndian);
// the LF will be counted for null terminators
Size:=0;
if Arguments.Count>0 then
Size:=Length(Arguments.Text)+1;
view.setUint32(argvBufSize, Size , IsLittleEndian);
Result:=WASI_ESUCCESS;
end;
function TPas2JSWASIEnvironment.args_get(argv, argvBuf: NativeInt): NativeInt;
function TPas2JSWASIEnvironment.args_get(argv, argvBuf: TWasmMemoryLocation): NativeInt;
var
Ptr : TWasmMemoryLocation;
PtrV : TWasmMemoryLocation;
S : String;
i : Integer;
begin
{$IFNDEF NO_WASI_DEBUG}
if LogAPI then
DoLog('TPas2JSWASIEnvironment.args_get(%d,%d)',[argv, argvBuf]);
DoLog('TPas2JSWASIEnvironment.args_get([%x],[%x])',[argv, argvBuf]);
{$ENDIF}
Ptr:=ArgvBuf;
PtrV:=ArgV;
for I:=0 to Arguments.Count-1 do
begin
S:=Arguments[I];
PtrV:=SetMemInfoUInt32(PtrV,Ptr);
Ptr:=Ptr+SetUTF8StringInMem(Ptr,Length(S),S);
Ptr:=SetMemInfoUInt8(Ptr,0);
end;
Result:=WASI_ESUCCESS;
end;
@ -1078,6 +1130,18 @@ begin
Result:= FModuleInstanceExports.Memory;
end;
procedure TPas2JSWASIEnvironment.SetArguments(AValue: TStrings);
begin
if FArguments=AValue then Exit;
FArguments.Assign(AValue);
end;
procedure TPas2JSWASIEnvironment.SetEnvironment(AValue: TStrings);
begin
if FEnvironment=AValue then Exit;
FEnvironment.Assign(AValue);
end;
function TPas2JSWASIEnvironment.GetIOVsAsBytes(iovs, iovsLen : NativeInt) : TJSUInt8array;
var
@ -2062,10 +2126,14 @@ begin
FIsLittleEndian:=True;
// Default expected by FPC runtime
WASIImportName:='wasi_snapshot_preview1';
FArguments:=TStringList.Create;
FEnvironment:=TStringList.Create;
end;
destructor TPas2JSWASIEnvironment.Destroy;
begin
FreeAndNil(FEnvironment);
FreeAndNil(FArguments);
FreeAndNil(FImportExtensions);
inherited Destroy;
end;
@ -2185,6 +2253,115 @@ begin
Result:=aLoc+SizeUint64;
end;
function TPas2JSWASIEnvironment.PreLoadFiles(aFiles: array of string): TPreLoadFilesResult;
var
I,Idx,Len : Integer;
FileArray : TPreLoadFileDynArray;
begin
if not assigned(FS) then
Raise EWasiError.Create('No filesystem available');
Len:=Length(aFiles);
if (Len mod 2)=1 then
Raise EWasiError.Create('Number of arguments must be even: pairs of url, local');
SetLength(FileArray,Len div 2);
I:=0;
Idx:=0;
while I<Len do
begin
FileArray[Idx].Url:=aFiles[i];
FileArray[Idx].localname:=aFiles[i+1];
Inc(I,2);
Inc(Idx);
end;
Result:=Await(PreloadFiles(FileArray));
end;
function TPas2JSWASIEnvironment.PreLoadFiles(aFiles: TPreLoadFileDynArray): TPreLoadFilesResult;
var
I,res,failcount : Integer;
Resp: TJSResponse;
blob : TJSBlob;
buf : TJSarrayBuffer;
Data : TJSUint8Array;
Fails : TLoadFileFailureDynArray;
procedure AddFailure(aUrl,aError: String);
begin
fails[FailCount].url:=aUrl;
fails[FailCount].error:=aError;
inc(Failcount);
end;
begin
if not assigned(FS) then
Raise EWasiError.Create('No filesystem available');
Res:=0;
failcount:=0;
SetLength(Fails,Length(aFiles));
For I:=0 to Length(afiles)-1 do
try
resp:=await(fetch(aFiles[I].url));
blob:=await(resp.blob);
buf:=await(TJSArrayBuffer,blob.arrayBuffer);
FS.PreloadFile(aFiles[i].localname,TJSDataView.new(Buf));
inc(Res);
except
on E : Exception do
AddFailure(aFiles[i].Url,E.Message);
on JE : TJSError do
AddFailure(aFiles[i].Url,JE.Message);
on OE : TJSObject do
AddFailure(aFiles[i].Url,TJSJSON.Stringify(OE));
end;
SetLength(Fails,FailCount);
Result.failedurls:=Fails;
Result.LoadCount:=Res;
end;
function TPas2JSWASIEnvironment.PreLoadFilesIntoDirectory(aDirectory: String; aFiles: array of string): TPreLoadFilesResult;
function ExtractFileFromURL(aURL : String) : string;
var
S : String;
URLObj : TJSURL;
begin
if aUrl.StartsWith('http://',true) or aUrl.StartsWith('https://',true) then
begin
UrlObj:=TJSURL.new(aURL);
S:=UrlObj.PathName
end
else
S:=aURL;
Result:=ExtractFileName(S);
end;
var
I,Len : Integer;
FileArray : TPreLoadFileDynArray;
begin
if not assigned(FS) then
Raise EWasiError.Create('No filesystem available');
Len:=Length(aFiles);
SetLength(FileArray,Len);
aDirectory:=IncludeTrailingPathDelimiter(aDirectory);
I:=0;
while I<Len do
begin
FileArray[I].Url:=aFiles[i];
FileArray[I].localname:=aDirectory+ExtractFileFromURL(aFiles[i]);
Inc(I);
end;
Result:=Await(PreloadFiles(FileArray));
end;
initialization
end.

View File

@ -8,9 +8,9 @@ interface
uses
{$IFDEF FPC_DOTTEDUNITS}
System.Classes, System.SysUtils, Fcl.App.Browser, JSApi.JS, BrowserApi.WebAssembly, Wasi.Env;
System.Classes, System.SysUtils, Fcl.App.Browser, JSApi.JS, BrowserApi.WebAssembly, Wasi.Env, BrowserApi.Web, System.Types;
{$ELSE}
Classes, SysUtils, browserapp, js, webassembly, wasienv;
Classes, SysUtils, browserapp, js, webassembly, wasienv, web, types;
{$ENDIF}
Type
@ -44,6 +44,9 @@ Type
public
Constructor Create(aOwner : TComponent); override;
Destructor Destroy; override;
function PreLoadFiles(aFiles : Array of string) : TPreLoadFilesResult; async;
function PreLoadFiles(aFiles : TPreLoadFileDynArray) : TPreLoadFilesResult; async;
function PreLoadFilesIntoDirectory(aDirectory: String; aFiles: array of string): TPreLoadFilesResult; async;
// Load and start webassembly. If DoRun is true, then Webassembly entry point is called.
// If aBeforeStart is specified, then it is called prior to calling run, and can disable running.
// If aAfterStart is specified, then it is called after calling run. It is not called is running was disabled.
@ -185,6 +188,24 @@ begin
inherited Destroy;
end;
function TBrowserWASIHostApplication.PreLoadFiles(aFiles: array of string): TPreLoadFilesResult;
begin
Result:=Await(WasiEnvironment.PreloadFiles(aFiles));
end;
function TBrowserWASIHostApplication.PreLoadFiles(aFiles : TPreLoadFileDynArray) : TPreLoadFilesResult;
begin
Result:=Await(WasiEnvironment.PreloadFiles(aFiles));
end;
function TBrowserWASIHostApplication.PreLoadFilesIntoDirectory(aDirectory : String; aFiles: array of string): TPreLoadFilesResult;
begin
Result:=Await(WasiEnvironment.PreloadFilesIntoDirectory(aDirectory,aFiles));
end;
function TBrowserWASIHostApplication.StartWebAssembly(aPath: string; DoRun: Boolean;
aBeforeStart: TBeforeStartCallback = nil; aAfterStart: TAfterStartCallback = nil) : TJSPromise;

View File

@ -45,6 +45,7 @@ Type
Function Read(FD : Integer; Data : TJSUint8Array; AtPos : Integer; Out BytesRead : Integer) : NativeInt;
function ReadDir(FD: Integer; Cookie: NativeInt; out DirEnt: TWasiFSDirent): NativeInt;
Function GetPrestat(FD: Integer) : String;
Procedure PreLoadFile(aPath : String; aData : TJSDataView);
end;
implementation
@ -430,6 +431,11 @@ begin
end;
end;
procedure TWASIZenFS.PreLoadFile(aPath: String; aData: TJSDataView);
begin
ZenFS.WriteFileSync(aPath,aData);
end;
function TWASIZenFS.UnLinkAt(FD: Integer; const aPath: String): NativeInt;
var