* Hosting example for webassembly module

This commit is contained in:
Michaël Van Canneyt 2024-06-19 17:52:09 +02:00
parent 0e11fe0fc5
commit ec21c75e05
11 changed files with 439 additions and 0 deletions

View File

@ -0,0 +1,18 @@
This directory contains a generic HTML loader page for a WebAssembly module
that needs a WASI and JOB hosting environment.
Compile the .lpr with pas2js, and load the page in your browser.
By default the page will attempt to load a demo.wasm module.
You can specify the module to load in 1 of 3 ways:
1. from external variable wasmFilename.
To this end, copy hostconfig-template.js to hostconfig.js.
In this file, change the name of the webassembly to load.
2. from the first part of hash: #moduleName/
3. from query varable wasmmodule: ?wasmmodule=x
the extension .wasm must be specified.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,5 @@
/*
* copy this template as hostconfig.js and adapt to your situation.
*/
var
wasmFilename = 'promisedemo.wasm';

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Wasm Javascript Object Bindings - Test bed</title>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"> <!-- Prevents caching -->
<meta http-equiv="Pragma" content="no-cache"> <!-- Legacy HTTP 1.0 backward compatibility -->
<meta http-equiv="Expires" content="0"> <!-- Proxies -->
<link href="bulma.min.css" rel="stylesheet">
<link type="stylesheet" src="bulma.min.css">
<script src="hostconfig.js"></script>
<script src="webhost.js"></script>
</head>
<body>
<div class="box">
<p class="title is-3">Webassembly JOB test bed</p>
<p class="subtitle is-5">Console output:</p>
<div id="pasjsconsole">
</div>
</div>
<script>
rtl.showUncaughtExceptions=true;
rtl.run();
</script>
</body>
</html>

View File

@ -0,0 +1,4 @@
[Server]
Port=3030
Directory=/home/tixeo/fpc/packages/wasm-job/examples

View File

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
<ProjectOptions>
<Version Value="12"/>
<General>
<Flags>
<MainUnitHasCreateFormStatements Value="False"/>
<MainUnitHasTitleStatement Value="False"/>
<MainUnitHasScaledStatement Value="False"/>
<Runnable Value="False"/>
</Flags>
<SessionStorage Value="InProjectDir"/>
<Title Value="Browser loader for Webassembly module with JOB functionality."/>
<UseAppBundle Value="False"/>
<ResourceType Value="res"/>
</General>
<CustomData Count="4">
<Item0 Name="MaintainHTML" Value="1"/>
<Item1 Name="Pas2JSProject" Value="1"/>
<Item2 Name="PasJSLocation" Value="BrowserTixeoDom"/>
<Item3 Name="PasJSWebBrowserProject" Value="1"/>
</CustomData>
<BuildModes>
<Item Name="Default" Default="True"/>
</BuildModes>
<PublishOptions>
<Version Value="2"/>
<UseFileFilters Value="True"/>
</PublishOptions>
<RunParams>
<FormatVersion Value="2"/>
</RunParams>
<Units>
<Unit>
<Filename Value="webhost.lpr"/>
<IsPartOfProject Value="True"/>
</Unit>
<Unit>
<Filename Value="../index.html"/>
<IsPartOfProject Value="True"/>
<CustomData Count="1">
<Item0 Name="PasJSIsProjectHTMLFile" Value="1"/>
</CustomData>
</Unit>
<Unit>
<Filename Value="../../../job/job_browser.pp"/>
<IsPartOfProject Value="True"/>
<UnitName Value="JOB_Browser"/>
</Unit>
<Unit>
<Filename Value="../../../job/job_shared.pp"/>
<IsPartOfProject Value="True"/>
<UnitName Value="JOB_Shared"/>
</Unit>
</Units>
</ProjectOptions>
<CompilerOptions>
<Version Value="11"/>
<Target FileExt=".js">
<Filename Value="webhost"/>
</Target>
<SearchPaths>
<IncludeFiles Value="$(ProjOutDir)"/>
<OtherUnitFiles Value="../../../job"/>
<UnitOutputDirectory Value="js"/>
</SearchPaths>
<Parsing>
<SyntaxOptions>
<AllowLabel Value="False"/>
<UseAnsiStrings Value="False"/>
<CPPInline Value="False"/>
</SyntaxOptions>
</Parsing>
<CodeGeneration>
<TargetOS Value="browser"/>
</CodeGeneration>
<Linking>
<Debugging>
<GenerateDebugInfo Value="False"/>
<UseLineInfoUnit Value="False"/>
</Debugging>
</Linking>
<Other>
<CustomOptions Value="-Jeutf-8
-Jirtl.js
-Jc
-Jminclude"/>
<CompilerPath Value="$(pas2js)"/>
</Other>
</CompilerOptions>
<Debugging>
<Exceptions>
<Item>
<Name Value="EAbort"/>
</Item>
<Item>
<Name Value="ECodetoolError"/>
</Item>
<Item>
<Name Value="EFOpenError"/>
</Item>
</Exceptions>
</Debugging>
</CONFIG>

View File

@ -0,0 +1,74 @@
program webhost;
{$mode objfpc}
uses
BrowserConsole, JS, Types, Classes, SysUtils, Web, WasiEnv, WasiHostApp, JOB_Browser, JOB_Shared;
var
wasmFilename : string; external name 'wasmFilename';
Type
{ TMyApplication }
TMyApplication = class(TBrowserWASIHostApplication)
Private
FJOB : TJSObjectBridge;
function GetWasmModuleName: String;
Public
constructor Create(aOwner : TComponent); override;
procedure DoRun; override;
end;
{ TMyApplication }
constructor TMyApplication.Create(aOwner: TComponent);
begin
inherited Create(aOwner);
FJOB:=TJSObjectBridge.Create(WasiEnvironment);
RunEntryFunction:='_initialize';
end;
function TMyApplication.GetWasmModuleName : String;
{ Determine webassembly module to run
1. from external variable wasmFilename
2. from first part of hash: #moduleName/
3. from query varable wasmmodule: ?wasmmodule=x
4. Hardcoded 'demo.wasm';
}
begin
Result:='';
if IsString(wasmFilename) then
Result:=wasmFilename;
if (Result='') then
Result:=ParamStr(1);
if (Result='') then
Result:=GetEnvironmentVar('wasmmodule');
if Result='' then
Result:='demo.wasm';
end;
procedure TMyApplication.DoRun;
var
WasmModule : String;
begin
Terminate;
WasmModule:=GetWasmModuleName;
Writeln('Loading & starting webassembly module :' ,WasmModule);
StartWebAssembly('WasmModule.wasm',true);
end;
var
Application : TMyApplication;
begin
ConsoleStyle:=DefaultCRTConsoleStyle;
HookConsole;
Application:=TMyApplication.Create(nil);
Application.Initialize;
Application.Run;
end.

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
<ProjectOptions>
<Version Value="12"/>
<General>
<Flags>
<MainUnitHasCreateFormStatements Value="False"/>
<MainUnitHasTitleStatement Value="False"/>
<MainUnitHasScaledStatement Value="False"/>
</Flags>
<SessionStorage Value="InProjectDir"/>
<Title Value="promise demo: delayed resolve"/>
<UseAppBundle Value="False"/>
<ResourceType Value="res"/>
</General>
<BuildModes>
<Item Name="Default" Default="True"/>
</BuildModes>
<PublishOptions>
<Version Value="2"/>
<UseFileFilters Value="True"/>
</PublishOptions>
<RunParams>
<FormatVersion Value="2"/>
</RunParams>
<Units>
<Unit>
<Filename Value="promisedemo2.lpr"/>
<IsPartOfProject Value="True"/>
<UnitName Value="promisedemo"/>
</Unit>
</Units>
</ProjectOptions>
<CompilerOptions>
<Version Value="11"/>
<Target>
<Filename Value="promisedemo2.wasm" ApplyConventions="False"/>
</Target>
<SearchPaths>
<IncludeFiles Value="$(ProjOutDir)"/>
<OtherUnitFiles Value="../src"/>
<UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
</SearchPaths>
<CodeGeneration>
<TargetCPU Value="wasm32"/>
<TargetOS Value="wasi"/>
</CodeGeneration>
<Linking>
<Debugging>
<GenerateDebugInfo Value="False"/>
</Debugging>
<Options>
<ExecutableType Value="Library"/>
</Options>
</Linking>
<Other>
<CustomOptions Value="-Ur"/>
</Other>
</CompilerOptions>
<Debugging>
<Exceptions>
<Item>
<Name Value="EAbort"/>
</Item>
<Item>
<Name Value="ECodetoolError"/>
</Item>
<Item>
<Name Value="EFOpenError"/>
</Item>
</Exceptions>
</Debugging>
</CONFIG>

View File

@ -0,0 +1,134 @@
library promisedemo;
{$mode objfpc}
{$h+}
uses nothreads, sysutils, job.js, variants;
Type
TFunction = function (const arguments: Variant): Variant of object;
IJSWindow = interface(IJSObject)
function setTimeout(const aHandler: TFunction; aTimeout: LongInt; const aArguments: Variant): LongInt{; ToDo:varargs};
function setTimeout(const aHandler: TFunction): LongInt{; ToDo:varargs};
function setTimeout(const aHandler: UnicodeString; aTimeout: LongInt; const aUnused: Variant): LongInt{; ToDo:varargs};
function setTimeout(const aHandler: UnicodeString): LongInt{; ToDo:varargs};
end;
TJSWindow = class(TJSObject,IJSWindow)
function setTimeout(const aHandler: TFunction; aTimeout: LongInt; const aArguments: Variant): LongInt{; ToDo:varargs};
function setTimeout(const aHandler: TFunction): LongInt{; ToDo:varargs};
function setTimeout(const aHandler: UnicodeString; aTimeout: LongInt; const aUnused: Variant): LongInt{; ToDo:varargs};
function setTimeout(const aHandler: UnicodeString): LongInt{; ToDo:varargs};
end;
TApp = Class(TObject)
private
FOnResolve: TJSPromiseResolver;
FOnReject: TJSPromiseResolver;
public
function DoTimeout(const arguments: Variant): Variant;
function DoResolve(const aValue: Variant): Variant;
procedure DoPromiseExecutor(const OnResolve, OnReject:TJSPromiseResolver);
function ResolveTest: TJSPromise;
procedure Run;
end;
var
JSWindow : IJSWindow;
function JOBCallFunction_(const aMethod: TMethod; var H: TJOBCallbackHelper): PByte;
var
arguments: Variant;
begin
arguments:=H.GetVariant;
Result:=H.AllocVariant(TFunction(aMethod)(arguments));
end;
function TJSWindow.setTimeout(const aHandler: TFunction; aTimeout: LongInt; const aArguments: Variant): LongInt{; ToDo:varargs};
var
m: TJOB_Method;
begin
m:=TJOB_Method.Create(TMethod(aHandler),@JOBCallFunction_);
try
Result:=InvokeJSLongIntResult('setTimeout',[m,aTimeout,aArguments]);
finally
m.free;
end;
end;
function TJSWindow.setTimeout(const aHandler: TFunction): LongInt{; ToDo:varargs};
var
m: TJOB_Method;
begin
m:=TJOB_Method.Create(TMethod(aHandler),@JOBCallFunction_);
try
Result:=InvokeJSLongIntResult('setTimeout',[m]);
finally
m.free;
end;
end;
function TJSWindow.setTimeout(const aHandler: UnicodeString; aTimeout: LongInt; const aUnused: Variant): LongInt{; ToDo:varargs};
begin
Result:=InvokeJSLongIntResult('setTimeout',[aHandler,aTimeout,aUnused]);
end;
function TJSWindow.setTimeout(const aHandler: UnicodeString): LongInt{; ToDo:varargs};
begin
Result:=InvokeJSLongIntResult('setTimeout',[aHandler]);
end;
{ TApp }
function TApp.DoTimeout(const arguments: Variant): Variant;
begin
if not Assigned(FOnResolve) then
Writeln('Wasm ERROR: no resolve callback');
if not Assigned(FOnReject) then
Writeln('Wasm ERROR: no reject callback');
if Assigned(FOnResolve) then
FOnResolve('resolved');
end;
function TApp.DoResolve(const aValue: Variant): Variant;
begin
Writeln('Wasm: in DoResolve: success. Argument vartype: ', vartype(aValue));
if vartype(aValue)=varOleStr then
Writeln('Wasm: DoResolve received value: ', VarToStr(aValue));
result:=unassigned;
end;
procedure TApp.DoPromiseExecutor(const OnResolve, OnReject: TJSPromiseResolver);
begin
FOnResolve := OnResolve;
FOnReject := OnReject;
JSWindow.setTimeout(@DoTimeout, 1000, nil);
end;
function TApp.ResolveTest: TJSPromise;
begin
Result:=TJSPromise.Create(@DoPromiseExecutor);
end;
procedure TApp.Run;
Var
P : TJSPromise;
begin
try
P:=ResolveTest;
P._then(@DoResolve);
except
on E: Exception do
Writeln(e.Message);
end;
end;
var
App : TApp;
begin
JSWindow:=TJSWindow.JOBCreateGlobal('window');
App:=TApp.Create;
App.Run;
end.