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
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
@@ -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": {
@@ -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
@@ -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";

3 changes: 1 addition & 2 deletions integration-tests/tests/src/setup-globals.ts
Original file line number Diff line number Diff line change
@@ -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;
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
@@ -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":
@@ -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>())`;

@@ -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) {
@@ -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})`;
@@ -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)})`;
}
@@ -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>();
@@ -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`);
@@ -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
@@ -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
15 changes: 14 additions & 1 deletion packages/realm/binding/wasm/platform.cpp
Original file line number Diff line number Diff line change
@@ -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&)
21 changes: 21 additions & 0 deletions packages/realm/package.json
Original file line number Diff line number Diff line change
@@ -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": [
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
@@ -16,7 +16,7 @@
//
////////////////////////////////////////////////////////////////////////////

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