Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added EC6 module handling and fixed up Blueprints #98

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# Unreal.js-core
# Unreal.js-core kesselman dev fork

- core component of [Unreal.js](https://github.com/ncsoft/Unreal.js)
- Please visit [project repo](https://github.com/ncsoft/Unreal.js).

Sample Blueprint: https://imgur.com/nexH05q

2 changes: 1 addition & 1 deletion Source/JavascriptEditor/JavascriptEditorViewport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include "Engine/Canvas.h"
#include "Components/OverlaySlot.h"
#include "AssetViewerSettings.h"
#include "Launch/Resources/Version.h"
#include "Runtime/Launch/Resources/Version.h"
#include "Components/DirectionalLightComponent.h"
#if ENGINE_MAJOR_VERSION > 4
#include "UnrealWidget.h"
Expand Down
2 changes: 1 addition & 1 deletion Source/JavascriptEditor/JavascriptUICommands.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#include "JavascriptUICommands.h"
#include "JavascriptMenuLibrary.h"
#include "Framework/Commands/Commands.h"
#include "Launch/Resources/Version.h"
#include "Runtime/Launch/Resources/Version.h"

//PRAGMA_DISABLE_OPTIMIZATION

Expand Down
2 changes: 1 addition & 1 deletion Source/JavascriptHttp/JavascriptHttpRequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#include "Interfaces/IHttpRequest.h"
#include "Interfaces/IHttpResponse.h"
#include "HttpModule.h"
#include "Launch/Resources/Version.h"
#include "Runtime/Launch/Resources/Version.h"

#include "JavascriptHttpRequest.generated.h"

Expand Down
2 changes: 1 addition & 1 deletion Source/JavascriptUMG/JavascriptGameViewport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#include "SceneView.h"
#include "CanvasTypes.h"
#include "Widgets/SViewport.h"
#include "Launch/Resources/Version.h"
#include "Runtime/Launch/Resources/Version.h"

#define LOCTEXT_NAMESPACE "UMG"

Expand Down
2 changes: 1 addition & 1 deletion Source/JavascriptUMG/JavascriptWindow.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "JavascriptWindow.h"
#include "Widgets/SWindow.h"
#include "Launch/Resources/Version.h"
#include "Runtime/Launch/Resources/Version.h"

UJavascriptWindow::UJavascriptWindow(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
Expand Down
2 changes: 1 addition & 1 deletion Source/JavascriptWebSocket/JavascriptWebSocket.Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ private void HackWebSocketIncludeDir(String WebsocketPath, ReadOnlyTargetRules T
}
else if (Target.Platform == UnrealTargetPlatform.Linux)
{
PlatformSubdir = Path.Combine(PlatformSubdir, Target.Architecture);
PlatformSubdir = Path.Combine(PlatformSubdir, Target.Architecture.ToString());
}

PrivateDependencyModuleNames.Add("libWebSockets");
Expand Down
222 changes: 222 additions & 0 deletions Source/V8/Private/JavaScriptModuleCompiler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
#include "JavaScriptModuleCompiler.h"

#include "IV8.h"
#include "Misc/FileHelper.h"

/********************************************************************
* A static class that handles loading and execution EC6 module files
* to the V8 engine in the UE5 environment.
* Written by Professor Jeff Kesselman MS MFA
* Purdue University
* 11/23/2023
* [email protected]
* ***********************************/
namespace module_compiler
{
/*****************************************************************************
* char* readFile
* Reads file contents to a null-terminated string.
*****************************************************************************/
char* readFile(const char filename[]) {

// Opening file; ifstream::ate is use to determine file size
std::ifstream file;
file.open(filename, std::ifstream::ate);
char* contents;
if (!file) {
contents = new char[1];
return contents;
}

// Get file size
size_t file_size = file.tellg();

// Return file pointer from end of the file (set by ifstream::ate) to beginning
file.seekg(0);

// Reading file to char array and returing it
std::filebuf* file_buf = file.rdbuf();
contents = new char[file_size + 1]();
file_buf->sgetn(contents, file_size);
file.close();
return contents;
}

/*****************************************************************************
* void print
* Binding of simple console print function to the VM
*****************************************************************************/
void print(const v8::FunctionCallbackInfo<v8::Value>& args) {

// Getting arguments; error handling
v8::Isolate* isolate = args.GetIsolate();
v8::String::Utf8Value val(isolate, args[0]);
if (*val == nullptr)
isolate->ThrowException(
v8::String::NewFromUtf8(isolate, "First argument of function is empty")
.ToLocalChecked());

// Printing
printf("%s\n", *val);
}

/*****************************************************************************
* v8::MaybeLocal<v8::Module> loadModule
* Loads module from code[] without checking it
*****************************************************************************/
v8::MaybeLocal<v8::Module> JSModuleCompiler::loadModule(char code[],
char name[],
v8::Local<v8::Context> cx) {

// Convert char[] to VM's string type
v8::Local<v8::String> vcode =
v8::String::NewFromUtf8(cx->GetIsolate(), code).ToLocalChecked();

// Create script origin to determine if it is module or not.
// Only first and last argument matters; other ones are default values.
// First argument gives script name (useful in error messages), last
// informs that it is a module.
v8::ScriptOrigin origin(
v8::String::NewFromUtf8(cx->GetIsolate(), name).ToLocalChecked(),
v8::Integer::New(cx->GetIsolate(), 0),
v8::Integer::New(cx->GetIsolate(), 0), v8::False(cx->GetIsolate()),
v8::Local<v8::Integer>(), v8::Local<v8::Value>(),
v8::False(cx->GetIsolate()), v8::False(cx->GetIsolate()),
v8::True(cx->GetIsolate()));

// Compiling module from source (code + origin)
v8::Context::Scope context_scope(cx);
v8::ScriptCompiler::Source source(vcode, origin);
v8::MaybeLocal<v8::Module> mod;
mod = v8::ScriptCompiler::CompileModule(cx->GetIsolate(), &source);
if (mod.IsEmpty())
{
UE_LOG(LogTemp, Warning, TEXT("Script failed to load"));
}
// Returning non-checked module
return mod;
}

/*****************************************************************************
* v8::Local<v8::Module> checkModule
* Checks out module (if it isn't nullptr/empty)
*****************************************************************************/
v8::Local<v8::Module> JSModuleCompiler::checkModule(v8::MaybeLocal<v8::Module> maybeModule,
v8::Local<v8::Context> cx) {

// Checking out
v8::Local<v8::Module> mod;
if (!maybeModule.ToLocal(&mod)) {
printf("Error loading module!\n");
exit(EXIT_FAILURE);
}

// Instantianing (including checking out depedencies). It uses callResolve
// as callback: check #
v8::Maybe<bool> result = mod->InstantiateModule(cx, JSModuleCompiler::callResolve);
if (result.IsNothing()) {
printf("\nCan't instantiate module.\n");
exit(EXIT_FAILURE);
}

// Returning check-out module
return mod;
}

/*****************************************************************************
* v8::Local<v8::Value> execModule
* Executes module's code
*****************************************************************************/
v8::Local<v8::Value> JSModuleCompiler::execModule(v8::Local<v8::Module> mod,
v8::Local<v8::Context> cx,
bool nsObject) {

cx->GetIsolate()->SetHostImportModuleDynamicallyCallback(callDynamic);

// Executing module with return value
v8::Local<v8::Value> retValue;
if (!mod->Evaluate(cx).ToLocal(&retValue)) {
printf("Error evaluating module!\n");
exit(EXIT_FAILURE);
}

// nsObject determins, if module namespace or return value has to be returned.
// Module namespace is required during import callback; see lines # and #.
if (nsObject)
return mod->GetModuleNamespace();
else
return retValue;
}

/*****************************************************************************
* v8::MaybeLocal<v8::Module> callResolve
* Callback from static import.
*****************************************************************************/
v8::MaybeLocal<v8::Module> JSModuleCompiler::callResolve(v8::Local<v8::Context> context,
v8::Local<v8::String> specifier,
v8::Local<v8::Module> referrer) {

v8::String::Utf8Value filename(context->GetIsolate(), specifier);
v8::String::Utf8Value fileroot(context->GetIsolate(),
context->GetEmbedderData(1));
FString fqn(*fileroot);
fqn = fqn.Append(FString("/"))+FString(*filename);

// Return unchecked module
FString Text;
if (!FFileHelper::LoadFileToString(Text, *fqn))
{
std::string sfilename(*filename);
FString msg = TEXT("Failed to read script file '%s'");
UE_LOG(LogJavascript, Warning,
TEXT("Failed to read script file '%s'"),
sfilename.c_str());
}

return loadModule(TCHAR_TO_ANSI(*Text), *filename, context);
}

/*****************************************************************************
* v8::MaybeLocal<v8::Promise> callDynamic
* Callback from dynamic import.
*****************************************************************************/
v8::MaybeLocal<v8::Promise> JSModuleCompiler::callDynamic(v8::Local<v8::Context> context,
v8::Local<v8::ScriptOrModule> referrer,
v8::Local<v8::String> specifier,
v8::Local<v8::FixedArray> import_assertions) {

// Promise resolver: that way promise for dynamic import can be rejected
// or full-filed
v8::Local<v8::Promise::Resolver> resolver =
v8::Promise::Resolver::New(context).ToLocalChecked();
v8::MaybeLocal<v8::Promise> promise(resolver->GetPromise());

// Loading module (with checking)
v8::String::Utf8Value name(context->GetIsolate(), specifier);
v8::Local<v8::Module> mod =
checkModule(loadModule(readFile(*name), *name, context), context);
v8::Local<v8::Value> retValue = execModule(mod, context, true);

// Resolving (fulfilling) promise with module global namespace
resolver->Resolve(context, retValue);
return promise;
}

/*****************************************************************************
* void callMeta
* Callback for module metadata.
*****************************************************************************/
void JSModuleCompiler::callMeta(v8::Local<v8::Context> context,
v8::Local<v8::Module> module,
v8::Local<v8::Object> meta) {

// In this example, this is throw-away function. But it shows that you can
// bind module's url. Here, placeholder is used.
meta->Set(
context,
v8::String::NewFromUtf8(context->GetIsolate(), "url").ToLocalChecked(),
v8::String::NewFromUtf8(context->GetIsolate(), "https://something.sh")
.ToLocalChecked());
}

}
39 changes: 39 additions & 0 deletions Source/V8/Private/JavaScriptModuleCompiler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#pragma once
/**********************************************************
* Javascript EC6 (modules) compiler
* by Jeffrey Kesselman ([email protected])
*
* Heavily cribbed from https://gist.github.com/surusek/4c05e4dcac6b82d18a1a28e6742fc23e
*
**********************************************************/
#include <libplatform/libplatform.h>
#include <v8.h>

namespace module_compiler
{
class JSModuleCompiler
{
private:
static v8::MaybeLocal<v8::Module> callResolve(v8::Local<v8::Context> context, v8::Local<v8::String> specifier,
v8::Local<v8::Module> referrer);
static v8::MaybeLocal<v8::Promise> callDynamic(v8::Local<v8::Context> context, v8::Local<v8::ScriptOrModule> referrer,
v8::Local<v8::String> specifier,
v8::Local<v8::FixedArray> import_assertions);
static void callMeta(v8::Local<v8::Context> context, v8::Local<v8::Module> module, v8::Local<v8::Object> meta);

public:
static v8::MaybeLocal<v8::Module> loadModule(char code[],
char name[],
v8::Local<v8::Context> cx);

// Check, if module isn't empty (or pointer to it); line #221
static v8::Local<v8::Module> checkModule(v8::MaybeLocal<v8::Module> maybeModule,
v8::Local<v8::Context> cx);

// Executes module; line #247
static v8::Local<v8::Value> execModule(v8::Local<v8::Module> mod,
v8::Local<v8::Context> cx,
bool nsObject = false);

};
}
Loading