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

Browser support (take 2) #6821

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
Prev Previous commit
Next Next commit
Now building wasm target of Realm Core
  • Loading branch information
kraenhansen committed Aug 16, 2024
commit c07b995a7d4a4cdcda116891d49125871d11df69
7 changes: 5 additions & 2 deletions integration-tests/environments/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
"wireit": {
"test": {
"command": "mocha-remote -- concurrently npm:vite npm:runner",
"dependencies": ["../../../packages/realm:build:ts"]
"dependencies": [
"../../../packages/realm:build:ts",
"../../../packages/realm:prebuild-wasm"
]
}
},
"dependencies": {
Expand All @@ -33,4 +36,4 @@
"puppeteer": "^22.12.0",
"vite": "^5.2.0"
}
}
}
3 changes: 3 additions & 0 deletions integration-tests/tests/src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ if (typeof navigator.userAgent !== "string") {
throw new Error("This file is only supposed to be imported from a browser environment!");
}

import { ready } from "realm";
await ready;

// Import all the regular tests first
import "./setup-globals";

Expand Down
3 changes: 1 addition & 2 deletions integration-tests/tests/src/setup-globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,7 @@ describe("Test Harness", function (this: Mocha.Suite) {
Context.prototype.longTimeout = longTimeout;
});

import Realm, { ready } from "realm";
await ready;
import Realm from "realm";

// Disable the logger to avoid console flooding
const { defaultLogLevel = "off" } = environment;
Expand Down
61 changes: 61 additions & 0 deletions packages/realm/bindgen/src/realm_js_wasm_helpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#pragma once

#include <emscripten.h>
#include <realm_helpers.h>

namespace realm::js::wasm {
namespace {

REALM_NOINLINE inline emscripten::val toEmscriptenErrorCode(const std::error_code& e) noexcept
{
REALM_ASSERT_RELEASE(e);
auto jsErr = emscripten::val::global("Error")(emscripten::val(e.message()));
jsErr.set("code", e.value());
jsErr.set("category", e.category().name());

return jsErr;
}
REALM_NOINLINE inline emscripten::val toEmscriptenException(const std::exception& e) noexcept
{
return emscripten::val::global("Error")(std::string(e.what()));
}
REALM_NOINLINE inline emscripten::val toEmscriptenException(const std::exception_ptr& e) noexcept
{
try {
std::rethrow_exception(e);
}
catch (const std::exception& e) {
return toEmscriptenException(e);
}
catch (...) {
return emscripten::val::global("Error")(std::string("Unknown error"));
}
}
// Allocate a new C++ buffer big enough to fit the JS buffer
// Create a JS memory view around the C++ buffer
// Call TypedArray.prototype.set to efficiently copy the JS buffer into the C++ buffer via the view
REALM_NOINLINE inline std::string toBinaryData(const emscripten::val array_buffer) noexcept
{
REALM_ASSERT(array_buffer.instanceof (emscripten::val::global("ArrayBuffer")));
std::string buf;
buf.resize(array_buffer["byteLength"].as<int32_t>());

emscripten::val mv(emscripten::typed_memory_view(buf.length(), buf.data()));
mv.call<void>("set", emscripten::val::global("Uint8Array").new_(array_buffer));

return buf;
}
REALM_NOINLINE inline OwnedBinaryData toOwnedBinaryData(const emscripten::val array_buffer) noexcept
{
REALM_ASSERT(array_buffer.instanceof (emscripten::val::global("ArrayBuffer")));
auto length = array_buffer["byteLength"].as<int32_t>();

std::unique_ptr<char[]> buf(new char[length]);

emscripten::val mv(emscripten::typed_memory_view(length, buf.get()));
mv.call<void>("set", emscripten::val::global("Uint8Array").new_(array_buffer));

return OwnedBinaryData(std::move(buf), length);
}
} // namespace
} // namespace realm::js::wasm
23 changes: 15 additions & 8 deletions packages/realm/bindgen/src/templates/wasm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ function convertPrimToEmscripten(addon: BrowserAddon, type: string, expr: string
case "uint64_t":
return `emscripten::val(${expr})`;

case "std::chrono::milliseconds":
return `emscripten::val(std::chrono::milliseconds(${expr}).count())`;

case "StringData":
case "std::string_view":
case "std::string":
Expand Down Expand Up @@ -259,6 +262,9 @@ function convertPrimFromEmscripten(addon: BrowserAddon, type: string, expr: stri
case "uint64_t":
return `${expr}.as<uint64_t>()`;

case "std::chrono::milliseconds":
return `std::chrono::milliseconds(${expr}.as<uint64_t>())`;

case "std::string":
return `${addon.get()}->wrapString((${expr}).as<std::string>())`;

Expand Down Expand Up @@ -341,7 +347,7 @@ function convertToEmscripten(addon: BrowserAddon, type: Type, expr: string): str
case "Nullable": {
return `[&] (auto&& val) { return !val ? emscripten::val::null() : ${c(inner, "FWD(val)")}; }(${expr})`;
}
case "util::Optional":
case "std::optional":
return `[&] (auto&& opt) { return !opt ? emscripten::val::undefined() : ${c(inner, "*FWD(opt)")}; }(${expr})`;
case "std::vector":
return `[&] (auto&& vec) {
Expand Down Expand Up @@ -454,7 +460,7 @@ function convertFromEmscripten(addon: BrowserAddon, type: Type, expr: string): s
inner,
"val",
)}; }(${expr})`;
case "util::Optional":
case "std::optional":
return `[&] (emscripten::val val) {
return val.isUndefined() ? ${type.toCpp()}() : ${c(inner, "val")};
}(${expr})`;
Expand Down Expand Up @@ -498,6 +504,7 @@ function convertFromEmscripten(addon: BrowserAddon, type: Type, expr: string): s
return out;
}(${expr})`;
case "AsyncCallback":
case "util::UniqueFunction":
case "std::function":
return `${type.toCpp()}(${c(inner, expr)})`;
}
Expand Down Expand Up @@ -876,7 +883,7 @@ class BrowserCppDecls extends CppDecls {
outputDefsTo(out: (...parts: string[]) => void) {
super.outputDefsTo(out);
out(`
void browser_init()
void wasm_init()
{
if (!RealmAddon::self) {
RealmAddon::self = std::make_unique<RealmAddon>();
Expand All @@ -900,15 +907,15 @@ class BrowserCppDecls extends CppDecls {
// });

out(`\nfunction("_internal_iterator", &_internal_iterator);`);
out(`\nfunction("browserInit", &browser_init);`);
out(`\nfunction("wasmInit", &wasm_init);`);
out(`\nfunction("injectInjectables", &injectExternalTypes);`);

out("\n}");
}
}

export function generate({ rawSpec, spec, file: makeFile }: TemplateContext): void {
const out = makeFile("browser_init.cpp", clangFormat);
const out = makeFile("wasm_init.cpp", clangFormat);

// HEADER
out(`// This file is generated: Update the spec instead of editing this file directly`);
Expand All @@ -920,16 +927,16 @@ export function generate({ rawSpec, spec, file: makeFile }: TemplateContext): vo
out(`
#include <emscripten/bind.h>
#include <realm_helpers.h>
#include <realm_js_browser_helpers.h>
#include <realm_js_wasm_helpers.h>

namespace realm::js::browser {
namespace realm::js::wasm {
namespace {
`);

new BrowserCppDecls(doJsPasses(spec)).outputDefsTo(out);

out(`
} // namespace
} // namespace realm::js::browser
} // namespace realm::js::wasm
`);
}
2 changes: 1 addition & 1 deletion packages/realm/binding/wasm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ bindgen(
target_sources(realm-js PRIVATE wasm_init.cpp ${CMAKE_JS_SRC} ${BINDING_DIR}/wasm/platform.cpp)

add_executable(realm-js-wasm)
target_link_options(realm-js-wasm PRIVATE -d -sALLOW_MEMORY_GROWTH=1 -sLLD_REPORT_UNDEFINED -sFETCH=1 -lembind -fwasm-exceptions -sEXPORT_ES6=1 -sWASM_BIGINT=1 -sENVIRONMENT=web -sSTACK_SIZE=131072 --pre-js=../web_polyfill.js)
target_link_options(realm-js-wasm PRIVATE -d -sALLOW_MEMORY_GROWTH=1 -sLLD_REPORT_UNDEFINED -sFETCH=1 -lembind -fwasm-exceptions -sEXPORT_ES6=1 -sWASM_BIGINT=1 -sENVIRONMENT=web -sSTACK_SIZE=131072)
target_link_libraries(realm-js-wasm realm-js)

set_target_properties(realm-js-wasm PROPERTIES
Expand Down
15 changes: 14 additions & 1 deletion packages/realm/binding/wasm/platform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,27 @@
#include <stdexcept>
#include <stdarg.h>
#include <stdio.h>
#include <string>

#include "../platform.hpp"

static std::string s_default_realm_directory;

namespace realm {

void JsPlatformHelpers::set_default_realm_file_directory(std::string dir)
{
s_default_realm_directory = dir;
}

std::string JsPlatformHelpers::default_realm_file_directory()
{
return std::string("");
if (!s_default_realm_directory.empty()) {
return s_default_realm_directory;
}
else {
return std::string("");
}
}

void JsPlatformHelpers::ensure_directory_exists_for_file(const std::string&)
Expand Down
21 changes: 21 additions & 0 deletions packages/realm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,27 @@
"src/binding/wrapper.generated.ts"
]
},
"bindgen:generate:wasm-wrapper": {
"command": "realm-bindgen --template bindgen/src/templates/wasm-wrapper.ts --spec bindgen/vendor/realm-core/bindgen/spec.yml --spec bindgen/js_spec.yml --opt-in bindgen/wasm_opt_in_spec.yml --output binding/generated",
"dependencies": [
"bindgen:generate:spec-schema"
],
"files": [
"bindgen/vendor/realm-core/bindgen/spec.yml",
"bindgen/vendor/realm-core/bindgen/src",
"bindgen/js_spec.yml",
"bindgen/wasm_opt_in_spec.yml",
"bindgen/src",
"!bindgen/src/templates",
"bindgen/src/templates/base-wrapper.ts",
"bindgen/src/templates/wasm-wrapper.ts"
],
"output": [
"binding/generated/native.wasm.mjs",
"binding/generated/native.wasm.d.mts",
"binding/generated/native.wasm.d.cts"
]
},
"bindgen:generate:spec-schema": {
"command": "typescript-json-schema bindgen/vendor/realm-core/bindgen/tsconfig.json RelaxedSpec --include bindgen/vendor/realm-core/bindgen/src/spec/relaxed-model.ts --out bindgen/vendor/realm-core/bindgen/generated/spec.schema.json --required --noExtraProps",
"files": [
Expand Down
22 changes: 22 additions & 0 deletions packages/realm/src/platform/browser/binding.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2024 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

import { injectAndPatch } from "../binding";
const bindingPromise = import("../../../binding/generated/native.wasm.cjs");

bindingPromise.then(injectAndPatch);
2 changes: 1 addition & 1 deletion packages/realm/src/platform/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
//
////////////////////////////////////////////////////////////////////////////

// import "./binding";
import "./binding";
import "./fs";
import "./device-info";
// import "./sync-proxy-config";
Expand Down
Loading