* Refactored

This commit is contained in:
Michael Van Canneyt 2025-04-24 16:28:46 +02:00
parent bf0768632f
commit ed2558bd86

View File

@ -1,5 +1,5 @@
{$IFNDEF FPC_DOTTEDUNITS}
unit wasiworkerthreadhost;
unit wasiworkerthreadhost deprecated 'use rtl.threadrunner or rtl.workerthreadhost';
{$ENDIF}
{$mode ObjFPC}
@ -10,918 +10,36 @@ interface
uses
{$IFDEF FPC_DOTTEDUNITS}
System.Classes, System.SysUtils, JSApi.JS, Fcl.CustApp, BrowserApi.WebOrWorker,
BrowserApi.Worker, BrowserApi.WebAssembly, Wasi.Env, System.WebThreads,
System.SysUtils, JSApi.JS, System.WebThreads,
Rtl.ThreadController;
{$ELSE}
Classes, SysUtils, JS, custapp, weborworker, webworker, webassembly, wasienv,
Rtl.WebThreads, Rtl.ThreadController;
SysUtils, JS, Rtl.WebThreads, Rtl.ThreadController, Rtl.WorkerCommands, WasiWorkerApp,
rtl.workerthreadhost, rtl.threadrunner;
{$ENDIF}
const
// no longer used
ThreadRunnerScript = 'wasm_worker_runner.js';
ThreadCount = 4;
Type
TWorkerThreadSupport = Class;
{ TWASIThreadHost }
TWASIThreadHost = class(TWASIHost)
private
FSendOutputToBrowserWindow: Boolean;
FThreadEntryPoint: String;
FThreadSupport: TWorkerThreadSupport;
procedure SetThreadSupport(AValue: TWorkerThreadSupport);
Protected
Procedure RunWebAssemblyThread(aProc : TRunWebassemblyProc); virtual;
procedure DoStdWrite(Sender: TObject; const aOutput: String); override;
Public
constructor Create(aOwner: TComponent); override;
// Prepare webassembly thread
procedure PrepareWebAssemblyThread(aDescr: TWebAssemblyStartDescriptor);
// Thread entry point name for the WASI Host.
Property ThreadEntryPoint : String Read FThreadEntryPoint Write FThreadEntryPoint;
// Send output to main window
Property SendOutputToBrowserWindow : Boolean Read FSendOutputToBrowserWindow Write FSendOutputToBrowserWindow;
// our thread
Property ThreadSupport : TWorkerThreadSupport Read FThreadSupport Write SetThreadSupport;
end;
// This object has the thread support that is needed by the worker that runs a thread.
{ TWorkerThreadSupport }
TWorkerThreadSupport = class(TWasmThreadSupport)
Private
Type
TWorkerState = (wsNeutral, wsLoading, wsLoaded, wsRunWaiting, wsRunning);
Private
FState: TWorkerState;
FCurrentThreadInfo : TThreadinfo;
FModule : TJSWebAssemblyModule;
FMemory : TJSWebAssemblyMemory;
FWasiHost: TWASIThreadHost;
Protected
// Incoming messages
procedure DoRunThread(aExports: TWASIExports); virtual;
procedure LoadWasmModule(aCommand: TWorkerLoadCommand); virtual;
procedure RunWasmModule(aCommand: TWorkerRunCommand); virtual;
procedure CancelWasmModule(aCommand: TWorkerCancelCommand); virtual;
procedure SendLoaded; virtual;
Procedure SendConsoleMessage(aMessage : String); overload;
Procedure SendConsoleMessage(aFmt : String; const aArgs : array of const); overload;
Procedure SendConsoleMessage(const aArgs : array of JSValue); overload;
procedure SendException(aError: Exception); overload;
procedure SendException(aError: TJSError); overload;
Protected
function thread_spawn(start_arg : longint) : longint; override;
Function thread_detach(thread_id : Integer) : Integer; override;
Function thread_cancel(thread_id : Integer) : Integer; override;
Public
// Handle incoming command
Procedure HandleCommand(aCommand : TWorkerCommand); override;
// Current thread info.
Property CurrentThreadInfo : TThreadInfo Read FCurrentThreadInfo;
// The WASI host, used to run routines.
Property Host : TWASIThreadHost Read FWasiHost Write FWasiHost;
end;
{ TWorkerWASIHostApplication }
TWorkerWASIHostApplication = class(TCustomApplication)
private
FHost : TWASIHost;
FSendOutputToBrowser: Boolean;
FConsoleChannel: TJSBroadcastChannel;
FSendOutputToConsole: Boolean;
function GetAfterStart: TAfterStartEvent;
function GetBeforeStart: TBeforeStartEvent;
function GetcPredefinedConsoleInput: TStrings;
function GetEnv: TPas2JSWASIEnvironment;
function GetExported: TWASIExports;
function GetOnConsoleRead: TConsoleReadEvent;
function GetOnConsoleWrite: TConsoleWriteEvent;
function GetRunEntryFunction: String;
procedure SetAfterStart(AValue: TAfterStartEvent);
procedure SetBeforeStart(AValue: TBeforeStartEvent);
procedure SetOnConsoleRead(AValue: TConsoleReadEvent);
procedure SetOnConsoleWrite(AValue: TConsoleWriteEvent);
procedure SetPredefinedConsoleInput(AValue: TStrings);
procedure SetRunEntryFunction(AValue: String);
protected
procedure HandleMessage(aEvent: TJSEvent); virtual; abstract;
procedure DoOnSendCommand(Sender : TObject; aCommand : TWorkerCommand);
function CreateHost: TWASIHost; virtual; abstract;
procedure DoRun; override;
procedure DoLog(EventType: TEventType; const Msg: String); override;
function GetConsoleApplication: boolean; override;
function GetLocation: String; override;
property ConsoleChannel : TJSBroadCastChannel Read FConsoleChannel;
Property Host : TWASIHost Read FHost;
public
Constructor Create(aOwner : TComponent); override;
Destructor Destroy; override;
// Send a command to the process that started the worker.
procedure SendCommand(aCommand: TWorkerCommand); virtual;
// Get the list of environment variables.
procedure GetEnvironmentList(List: TStrings; NamesOnly: Boolean); override;
// 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.
Procedure StartWebAssembly(aPath: string; DoRun : Boolean = True; aBeforeStart : TBeforeStartCallback = Nil; aAfterStart : TAfterStartCallback = Nil);
// Environment to be used
Property WasiEnvironment : TPas2JSWASIEnvironment Read GetEnv;
// Exported functions. Also available in start descriptor.
Property Exported : TWASIExports Read GetExported;
// Name of function to run, if empty default _start symbol is used.
Property RunEntryFunction : String Read GetRunEntryFunction Write SetRunEntryFunction;
// Called after webassembly start was run. Not called if webassembly was not run.
Property AfterStart : TAfterStartEvent Read GetAfterStart Write SetAfterStart;
// Called before running webassembly. If aAllowRun is false, running is disabled
Property BeforeStart : TBeforeStartEvent Read GetBeforeStart Write SetBeforeStart;
// Send output to browser window process?
Property SendOutputToBrowser : Boolean Read FSendOutputToBrowser Write FSendOutputToBrowser;
// Send output to console ?
Property SendOutputToConsole : Boolean Read FSendOutputToConsole Write FSendOutputToConsole;
// Default console input
Property PredefinedConsoleInput : TStrings Read GetcPredefinedConsoleInput Write SetPredefinedConsoleInput;
// Called when reading from console (stdin). If not set, PredefinedConsoleinput is used.
property OnConsoleRead : TConsoleReadEvent Read GetOnConsoleRead Write SetOnConsoleRead;
// Called when writing to console (stdout). If not set, console.log is used.
property OnConsoleWrite : TConsoleWriteEvent Read GetOnConsoleWrite Write SetOnConsoleWrite;
end;
{ TWorkerThreadRunnerApplication }
TWorkerThreadRunnerApplication = class(TWorkerWASIHostApplication)
Private
FThreadSupport : TWorkerThreadSupport;
Protected
procedure HandleConsoleWrite(Sender: TObject; aOutput: string);
function HandleCustomCommand(aData: TWorkerCommand): Boolean; virtual;
function CreateHost: TWASIHost; override;
function CreateWorkerThreadSupport(aEnv: TPas2JSWasiEnvironment): TWorkerThreadSupport; virtual;
procedure HandleMessage(aEvent: TJSEvent); override;
Public
procedure ShowException(aError: Exception); override;
// Our thread support object
Property ThreadSupport : TWorkerThreadSupport Read FThreadSupport Write FThreadSupport;
end;
{ TWASIThreadHost }
{ TWASIThreadControllerHost }
TWASIThreadControllerHost = class(TWASIHost)
private
FSendOutputToBrowserWindow: Boolean;
FThreadSupport: TThreadController;
procedure SetThreadSupport(AValue: TThreadController);
Protected
procedure DoAfterInstantiate; override;
Public
constructor Create(aOwner: TComponent); override;
// Send output to main window
Property SendOutputToBrowserWindow : Boolean Read FSendOutputToBrowserWindow Write FSendOutputToBrowserWindow;
// our thread
Property ThreadSupport : TThreadController Read FThreadSupport Write SetThreadSupport;
end;
{ TWorkerThreadControllerApplication }
TWorkerThreadControllerApplication = class(TWorkerWASIHostApplication)
Private
FThreadSupport : TThreadController;
function GetThreadHost: TWASIThreadControllerHost;
Protected
function CreateThreadSupport(aEnv: TPas2JSWASIEnvironment): TThreadController; virtual;
procedure HandleConsoleWrite(Sender: TObject; aOutput: string); virtual;
procedure HandleExecuteCommand(aCmd: TWorkerExecuteCommand); virtual;
procedure HandleRpcCommand(aCmd: TWorkerRpcCommand); virtual;
function HandleCustomCommand(aData: TWorkerCommand): Boolean; virtual;
procedure HandleMessage(aEvent: TJSEvent); override;
function CreateHost: TWASIHost; override;
Public
procedure ShowException(aError: Exception); override;
property ThreadSupport : TThreadController Read FThreadSupport;
property ThreadHost : TWASIThreadControllerHost read GetThreadHost;
end;
TWASIThreadHost = TWASIThreadControllerHost;
TWorkerThreadSupport = class(TWasmThreadSupportApi);
TWorkerWASIHostApplication = Class(WasiWorkerApp.TWorkerWASIHostApplication);
TWorkerThreadRunnerApplication = Class(Rtl.threadRunner.TWorkerThreadRunnerApplication);
TWorkerThreadControllerHost = Class(TWASIThreadControllerHost);
TWorkerThreadControllerApplication = class(rtl.workerthreadhost.TWorkerThreadControllerApplication);
function GetJSClassName(aObj : TJSObject) : string;
implementation
uses
{$IFDEF FPC_DOTTEDUNITS}
System.Types, System.TypInfo;
{$ELSE}
TypInfo, Types;
{$ENDIF}
var
EnvNames: TJSObject;
procedure ReloadEnvironmentStrings;
var
I : Integer;
S,N : String;
A,P : TStringDynArray;
begin
if Assigned(EnvNames) then
FreeAndNil(EnvNames);
EnvNames:=TJSObject.new;
S:=self_.Location.search;
S:=Copy(S,2,Length(S)-1);
A:=TJSString(S).split('&');
for I:=0 to Length(A)-1 do
begin
P:=TJSString(A[i]).split('=');
N:=LowerCase(decodeURIComponent(P[0]));
if Length(P)=2 then
EnvNames[N]:=decodeURIComponent(P[1])
else if Length(P)=1 then
EnvNames[N]:=''
end;
end;
function MyGetEnvironmentVariable(Const EnvVar: String): String;
Var
aName : String;
begin
aName:=Lowercase(EnvVar);
if EnvNames.hasOwnProperty(aName) then
Result:=String(EnvNames[aName])
else
Result:='';
end;
function MyGetEnvironmentVariableCount: Integer;
begin
Result:=length(TJSOBject.getOwnPropertyNames(envNames));
end;
function MyGetEnvironmentString(Index: Integer): String;
begin
Result:=String(EnvNames[TJSOBject.getOwnPropertyNames(envNames)[Index]]);
end;
{ TWASIThreadHost }
procedure TWASIThreadHost.SetThreadSupport(AValue: TWorkerThreadSupport);
begin
if FThreadSupport=AValue then Exit;
if Assigned(FThreadSupport) then
FThreadSupport.Host:=Nil;
FThreadSupport:=AValue;
if Assigned(FThreadSupport) then
FThreadSupport.Host:=Self;
end;
procedure TWASIThreadHost.RunWebAssemblyThread(aProc : TRunWebassemblyProc);
begin
// Writeln('TWASIThreadHost.Entering RunWebAssemblyThread ');
RunWebAssemblyInstance(Nil,Nil,aProc);
end;
procedure TWASIThreadHost.PrepareWebAssemblyThread( aDescr: TWebAssemblyStartDescriptor);
begin
PrepareWebAssemblyInstance(aDescr);
end;
procedure TWASIThreadHost.DoStdWrite(Sender: TObject; const aOutput: String);
begin
inherited DoStdWrite(Sender, aOutput);
if FSendOutputToBrowserWindow and assigned(FThreadSupport) then
FThreadSupport.SendConsoleMessage(aOutput);
end;
constructor TWASIThreadHost.Create(aOwner: TComponent);
begin
inherited Create(aOwner);
FThreadEntryPoint:=DefaultThreadEntryPoint;
FSendOutputToBrowserWindow:=True;
end;
{ TWorkerThreadSupport }
function TWorkerThreadSupport.thread_spawn(start_arg: longint): longint;
Var
P : TWorkerSpawnThreadCommand;
begin
P:=TWorkerSpawnThreadCommand.Create(start_arg,start_arg);
SendCommand(P);
Result:=start_arg;
end;
function TWorkerThreadSupport.thread_detach(thread_id: Integer): Integer;
begin
Result:=0;
if thread_id=0 then ;
end;
function TWorkerThreadSupport.thread_cancel(thread_id: Integer): Integer;
begin
Result:=0;
if thread_id=0 then ;
end;
procedure TWorkerThreadSupport.SendLoaded;
Var
L : TWorkerLoadedCommand;
begin
L:=TWorkerLoadedCommand.Create();
SendCommand(L);
end;
procedure TWorkerThreadSupport.SendConsoleMessage(aMessage: String);
Var
L : TWorkerConsoleCommand;
begin
L:=TWorkerConsoleCommand.Create(aMessage,FCurrentThreadInfo.ThreadId);
SendCommand(L);
end;
procedure TWorkerThreadSupport.SendConsoleMessage(aFmt: String;
const aArgs: array of const);
begin
SendConsoleMessage(Format(aFmt,aArgs));
end;
procedure TWorkerThreadSupport.SendConsoleMessage(const aArgs: array of JSValue);
Var
L : TWorkerConsoleCommand;
begin
L:=TWorkerConsoleCommand.Create(aArgs,FCurrentThreadInfo.ThreadId);
SendCommand(L);
end;
procedure TWorkerThreadSupport.CancelWasmModule(aCommand : TWorkerCancelCommand);
begin
if (aCommand<>Nil) then ;
// todo
end;
procedure TWorkerThreadSupport.SendException(aError : Exception);
Var
E : TWorkerExceptionCommand;
begin
E:=TWorkerExceptionCommand.CreateNew(aError.ClassName,aError.Message,FCurrentThreadInfo.ThreadId);
SendCommand(E);
end;
procedure TWorkerThreadSupport.SendException(aError: TJSError);
Var
aMessage,aClass : String;
E : TWorkerExceptionCommand;
begin
aClass:='Error';
aMessage:=aError.Message;
E:=TWorkerExceptionCommand.CreateNew(aClass,aMessage,FCurrentThreadInfo.ThreadId);
SendCommand(E);
end;
Procedure TWorkerThreadSupport.DoRunThread(aExports : TWASIExports);
Var
aResult : Integer;
begin
try
FState:=wsRunning;
// Writeln('About to run webassembly entry point (',Host.ThreadEntryPoint,') for thread ID ',aCommand.ThreadID);
aResult:=TThreadEntryPointFunction(aExports[Host.ThreadEntryPoint])(FCurrentThreadInfo.ThreadID,FCurrentThreadInfo.Arguments);
FState:=wsLoaded;
if aResult>0 then
SendConsoleMessage('Thread run function result= %d ',[aResult]);
SendCommand(TWorkerCleanupCommand.Create(Self.FCurrentThreadInfo.ThreadID,aResult));
except
on E : Exception do
SendException(E);
on JE : TJSError do
SendException(JE);
on JE : TJSError do
SendException(JE)
end;
end;
procedure TWorkerThreadSupport.RunWasmModule(aCommand : TWorkerRunCommand);
begin
if (FState=wsNeutral) then
begin
{$IFNDEF NOLOGAPICALLS}
DoLog('No webassembly loaded');
{$ENDIF}
exit; // Todo: send error back
end;
if (FState in [wsRunning,wsRunWaiting]) then
begin
{$IFNDEF NOLOGAPICALLS}
DoLog('Webassembly already running');
{$ENDIF}
exit; // Todo: send error back
end;
// Writeln('Entering TWorkerThreadSupport.RunWasmModule '+TJSJSON.Stringify(aCommand));
// initialize current thread info
FCurrentThreadInfo.ThreadID:=aCommand.ThreadID;
FCurrentThreadInfo.Arguments:=aCommand.Args;
if FState=wsLoaded then
Host.RunWebAssemblyThread(@DoRunThread)
else
FState:=wsRunWaiting;
end;
procedure TWorkerThreadSupport.LoadWasmModule(aCommand: TWorkerLoadCommand);
Var
WASD : TWebAssemblyStartDescriptor;
aTable : TJSWebAssemblyTable;
function doOK(aValue: JSValue): JSValue;
// We are using the overload that takes a compiled module.
// In that case the promise resolves to a WebAssembly.Instance, not to a InstantiateResult !
Var
aInstance : TJSWebAssemblyInstance absolute aValue;
begin
Result:=True;
WASD.Instance:=aInstance;
WASD.Exported:=TWASIExports(TJSObject(aInstance.exports_));
WASD.CallRun:=Nil;
Host.PrepareWebAssemblyThread(WASD);
SendLoaded;
if FState=wsRunWaiting then
Host.RunWebAssemblyThread(@DoRunThread)
else
FState:=wsLoaded;
end;
function DoFail(aValue: JSValue): JSValue;
var
E: Exception;
begin
FState:=wsNeutral;
Result:=True;
E:=Exception.Create('Failed to create webassembly. Reason: '+TJSJSON.Stringify(aValue));
SendException(E);
E.Free;
end;
begin
FState:=wsLoading;
FMemory:=aCommand.Memory;
FModule:=aCommand.Module;
try
aTable:=TJSWebAssemblyTable.New(Host.TableDescriptor);
WASD:=Host.InitStartDescriptor(FMemory,aTable,Nil);
TJSWebAssembly.Instantiate(FModule,WASD.Imports)._then(@DoOK,@DoFail).Catch(@DoFail);
except
on E : Exception do
SendException(E);
on JE : TJSError do
SendException(JE);
end;
end;
procedure TWorkerThreadSupport.HandleCommand(aCommand: TWorkerCommand);
begin
case aCommand.Command of
cmdload : LoadWasmModule(TWorkerLoadCommand(aCommand));
cmdRun : RunWasmModule(TWorkerRunCommand(aCommand));
cmdCancel : CancelWasmModule(TWorkerCancelCommand(aCommand));
end;
end;
{ TWorkerWASIHostApplication }
function TWorkerWASIHostApplication.GetAfterStart: TAfterStartEvent;
begin
Result:=FHost.AfterStart;
end;
function TWorkerWASIHostApplication.GetBeforeStart: TBeforeStartEvent;
begin
Result:=FHost.BeforeStart;
end;
function TWorkerWASIHostApplication.GetcPredefinedConsoleInput: TStrings;
begin
Result:=FHost.PredefinedConsoleInput;
end;
function TWorkerWASIHostApplication.GetEnv: TPas2JSWASIEnvironment;
begin
Result:=FHost.WasiEnvironment;
end;
function TWorkerWASIHostApplication.GetExported: TWASIExports;
begin
Result:=FHost.Exported;
end;
function TWorkerWASIHostApplication.GetOnConsoleRead: TConsoleReadEvent;
begin
Result:=FHost.OnConsoleRead;
end;
function TWorkerWASIHostApplication.GetOnConsoleWrite: TConsoleWriteEvent;
begin
Result:=FHost.OnConsoleWrite;
end;
function TWorkerWASIHostApplication.GetRunEntryFunction: String;
begin
Result:=FHost.RunEntryFunction;
end;
procedure TWorkerWASIHostApplication.SetAfterStart(AValue: TAfterStartEvent);
begin
FHost.AfterStart:=aValue;
end;
procedure TWorkerWASIHostApplication.SetBeforeStart(AValue: TBeforeStartEvent);
begin
FHost.BeforeStart:=aValue;
end;
procedure TWorkerWASIHostApplication.SetOnConsoleRead(AValue: TConsoleReadEvent
);
begin
FHost.OnConsoleRead:=aValue;
end;
procedure TWorkerWASIHostApplication.SetOnConsoleWrite(
AValue: TConsoleWriteEvent);
begin
FHost.OnConsoleWrite:=aValue;
end;
procedure TWorkerWASIHostApplication.SetPredefinedConsoleInput(AValue: TStrings);
begin
FHost.PredefinedConsoleInput:=aValue;
end;
procedure TWorkerWASIHostApplication.SetRunEntryFunction(AValue: String);
begin
FHost.RunEntryFunction:=aValue;
end;
procedure TWorkerWASIHostApplication.DoRun;
begin
Self_.addEventListener('message',@HandleMessage);
end;
procedure TWorkerWASIHostApplication.DoLog(EventType: TEventType; const Msg: String);
var
S : String;
begin
S:=GetEnumName(TypeInfo(TEventType),Ord(EventType));
ConsoleChannel.PostMessage(TWorkerConsoleCommand.Create(Format('[%s] %s',[S,Msg])));
end;
procedure TWorkerWASIHostApplication.DoOnSendCommand(Sender: TObject;
aCommand: TWorkerCommand);
begin
SendCommand(aCommand);
end;
procedure TWorkerWASIHostApplication.SendCommand(aCommand: TWorkerCommand);
begin
Self_.PostMessage(aCommand);
end;
function TWorkerWASIHostApplication.GetConsoleApplication: boolean;
begin
Result:=true;
end;
function TWorkerWASIHostApplication.GetLocation: String;
begin
Result:={$IFDEF FPC_DOTTEDUNITS}BrowserApi.Worker.{$ELSE}WebWorker.{$ENDIF}Location.pathname;
end;
constructor TWorkerWASIHostApplication.Create(aOwner: TComponent);
begin
inherited Create(aOwner);
FHost:=CreateHost;
FConsoleChannel:=TJSBroadcastChannel.new(channelConsole);
FSendOutputToConsole:=true;
end;
destructor TWorkerWASIHostApplication.Destroy;
begin
FConsoleChannel.Close;
FreeAndNil(FHost);
inherited Destroy;
end;
procedure TWorkerWASIHostApplication.GetEnvironmentList(List: TStrings;
NamesOnly: Boolean);
var
Names: TStringDynArray;
i: Integer;
begin
Names:=TJSObject.getOwnPropertyNames(EnvNames);
for i:=0 to length(Names)-1 do
begin
if NamesOnly then
List.Add(Names[i])
else
List.Add(Names[i]+'='+String(EnvNames[Names[i]]));
end;
end;
procedure TWorkerWASIHostApplication.StartWebAssembly(aPath: string; DoRun: Boolean;
aBeforeStart: TBeforeStartCallback = nil; aAfterStart: TAfterStartCallback = nil);
begin
FHost.StartWebAssembly(aPath,DoRun,aBeforeStart,aAfterStart);
end;
{ TWorkerThreadRunnerApplication }
procedure TWorkerThreadRunnerApplication.HandleConsoleWrite(Sender: TObject; aOutput: string);
begin
if SendOutputToConsole then
Writeln(aOutput);
if SendOutputToBrowser then
ConsoleChannel.postMessage(TWorkerConsoleCommand.Create(aOutput));
end;
function TWorkerThreadRunnerApplication.CreateWorkerThreadSupport(aEnv : TPas2JSWasiEnvironment) : TWorkerThreadSupport;
begin
Result:=TWorkerThreadSupport.Create(aEnv);
end;
function TWorkerThreadRunnerApplication.CreateHost: TWASIHost;
var
TH : TWasiThreadHost;
begin
TH:=TWASIThreadHost.Create(Self);
TH.OnConsoleWrite:=@HandleConsoleWrite;
FThreadSupport:=CreateWorkerThreadSupport(TH.WasiEnvironment);
FThreadSupport.OnSendCommand:=@DoOnSendCommand;
TH.ThreadSupport:=FThreadSupport; // Sets FThreadSupport.host
Result:=TH;
end;
procedure TWorkerThreadRunnerApplication.ShowException(aError: Exception);
Var
Ex : TWorkerExceptionCommand;
begin
Ex:=TWorkerExceptionCommand.CreateNew(aError.ClassName,aError.Message);
SendCommand(Ex);
end;
function TWorkerThreadRunnerApplication.HandleCustomCommand(aData : TWorkerCommand) : Boolean;
begin
if aData<>Nil then;
Result:=False;
end;
procedure TWorkerThreadRunnerApplication.HandleMessage(aEvent: TJSEvent);
Var
aMessageEvent : TJSMessageEvent absolute aEvent;
aData : TWorkerCommand;
begin
{$IFNDEF NOLOGAPICALLS}
DoLog(etDebug,'Message received in worker');
{$ENDIF}
if IsObject(aMessageEvent.Data) and TJSObject(aMessageEvent.Data).hasOwnProperty('Command') then
begin
aData:=TWorkerCommand(aMessageEvent.Data);
{$IFNDEF NOLOGAPICALLS}
DoLog(etDebug,'Command Message received in worker: '+TJSJSON.Stringify(aData));
{$ENDIF}
if not HandleCustomCommand(aData) then
FThreadSupport.HandleCommand(aData);
end
else
FThreadSupport.SendConsoleMessage('Unknown message received: '+TJSJSON.Stringify(aMessageEvent.Data));
end;
{ TWASIThreadControllerHost }
procedure TWASIThreadControllerHost.SetThreadSupport(AValue: TThreadController);
begin
if Assigned(FThreadSupport) then
FThreadSupport.Host:=Nil;
FThreadSupport:=AValue;
if Assigned(FThreadSupport) then
FThreadSupport.Host:=Self;
end;
procedure TWASIThreadControllerHost.DoAfterInstantiate;
begin
inherited DoAfterInstantiate;
If Assigned(FThreadSupport) then
FThreadSupport.SendLoadCommands;
end;
constructor TWASIThreadControllerHost.Create(aOwner: TComponent);
begin
inherited Create(aOwner);
end;
{ TWorkerThreadControllerApplication }
procedure TWorkerThreadControllerApplication.ShowException(aError: Exception);
Var
Ex : TWorkerExceptionCommand;
begin
Ex:=TWorkerExceptionCommand.CreateNew(aError.ClassName,aError.Message);
SendCommand(Ex);
end;
procedure TWorkerThreadControllerApplication.HandleExecuteCommand(aCmd : TWorkerExecuteCommand);
(*
Function DoPrepare(Sender : TObject; aDescriptor : TWebAssemblyStartDescriptor) : Boolean;
begin
// aDescriptor.
Result:=True;
end;
*)
begin
if isObject(aCmd.Env) then
EnvNames:=aCmd.Env;
if isString(aCmd.executeFunc) then
FHost.RunEntryFunction:=aCmd.executeFunc;
StartWebAssembly(aCmd.Url,True,Nil {@DoPrepare}, Nil)
end;
function GetJSClassName(aObj : TJSObject) : string;
begin
Result:='';
if aObj=Nil then
exit;
asm
return aObj.constructor.name;
end;
end;
procedure TWorkerThreadControllerApplication.HandleRpcCommand(aCmd: TWorkerRpcCommand);
var
res : TWorkerRpcResultCommand;
data : JSValue;
errClass : String;
errMessage : String;
begin
if aCmd.Id='' then
Res:=TWorkerRpcResultCommand.CreateError(aCmd.id,-32600,'Invalid request: No json-rpc ID')
else if aCmd.jsonrpc<>'2.0' then
Res:=TWorkerRpcResultCommand.CreateError(aCmd.id,-32600,'Invalid request: no jsonrpc version')
else if Not Assigned(Exported.functions[aCmd.method]) then
Res:=TWorkerRpcResultCommand.CreateError(aCmd.id,-32601,'Method "'+aCmd.method+'" not found')
else
begin
try
if isArray(aCmd.Params) then
data:=Exported.functions[aCmd.method].Apply(nil,TJSValueDynArray(aCmd.Params))
else
data:=Exported.functions[aCmd.method].call(nil);
Res:=TWorkerRpcResultCommand.Create(aCmd.id,Data);
except
on JE : TJSError do
begin
errClass:=GetJSClassName(JE);
errMessage:=JE.message;
end;
on E : Exception do
begin
errClass:=E.ClassName;
errMessage:=E.Message;
end;
end;
if not assigned(Res) then
Res:=TWorkerRpcResultCommand.CreateError(aCmd.id,-32603,'Exception '+ErrClass+' while executing "'+aCmd.method+'" : '+ErrMessage);
end;
Self_.postMessage(Res);
end;
procedure TWorkerThreadControllerApplication.HandleConsoleWrite(Sender: TObject; aOutput: string);
begin
if SendOutputToConsole then
Writeln(aOutput);
if SendOutputToBrowser then
FConsoleChannel.postMessage(TWorkerConsoleCommand.Create(aOutput,0));
end;
function TWorkerThreadControllerApplication.HandleCustomCommand(aData : TWorkerCommand) : Boolean;
begin
if not Assigned(aData) then
Exit(True);
Result:=False;
end;
procedure TWorkerThreadControllerApplication.HandleMessage(aEvent: TJSEvent);
var
aMessageEvent : TJSMessageEvent absolute aEvent;
aData: TWorkerCommand;
begin
Writeln('Thread Message received');
if IsObject(aMessageEvent.Data) and TJSObject(aMessageEvent.Data).hasOwnProperty('Command') then
begin
aData:=TWorkerCommand(aMessageEvent.Data);
Writeln('Command message received: '+TJSJSON.Stringify(aData));
case aData.Command of
cmdExecute : HandleExecuteCommand(TWorkerExecuteCommand(aData));
cmdRpc : HandleRPCCommand(TWorkerRpcCommand(aData));
else
if not HandleCustomCommand(aData) then
FThreadSupport.HandleCommand(aData);
end;
end;
end;
function TWorkerThreadControllerApplication.GetThreadHost: TWASIThreadControllerHost;
begin
Result:=(Host as TWASIThreadControllerHost)
end;
function TWorkerThreadControllerApplication.CreateThreadSupport(aEnv : TPas2JSWASIEnvironment) : TThreadController;
begin
Result:=TThreadController.Create(aEnv,ThreadRunnerScript,ThreadCount);
end;
function TWorkerThreadControllerApplication.CreateHost: TWASIHost;
var
TH : TWASIThreadControllerHost;
Mem : TJSWebAssemblyMemoryDescriptor;
begin
TH:=TWASIThreadControllerHost.Create(Self);
TH.OnConsoleWrite:=@HandleConsoleWrite;
FThreadSupport:=CreateThreadSupport(TH.WasiEnvironment);
Mem.Initial:=256;
Mem.maximum:=512;
Mem.shared:=True;
TH.MemoryDescriptor:=Mem;
FThreadSupport.OnSendCommand:=@DoOnSendCommand;
TH.ThreadSupport:=FThreadSupport; // Sets FThreadSupport.host
Result:=TH;
Result:=JSClassName(aObj);
end;
Initialization
ReloadEnvironmentStrings;
OnGetEnvironmentVariable:=@MyGetEnvironmentVariable;
OnGetEnvironmentVariableCount:=@MyGetEnvironmentVariableCount;
OnGetEnvironmentString:=@MyGetEnvironmentString;
TWasmThreadController.SetInstanceClass(TWorkerThreadRunner);
end.