Skip to content

Move the JavaScript frontend onto unibind, and ship a SpiderMonkey build - #46

Open
ResurrectedTrader wants to merge 1 commit into
mainfrom
unibind
Open

ResurrectedTrader wants to merge 1 commit into
mainfrom
unibind

Conversation

@ResurrectedTrader

@ResurrectedTrader ResurrectedTrader commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

Moves the JavaScript frontend off V8's API onto unibind (ub::), a V8-shaped embedding API over V8 and SpiderMonkey. The frontend compiles once against unibind's headers - no engine header anywhere in the tree - and the engine is chosen when a DLL project links it:

DLL Project Links Output
V8 (unchanged for users) d2bs-v8.vcxproj unibind_backend_v8.lib + V8 15.6.8 Release\js-v8-lod114d\d2bs.dll, released as d2bs.dll
SpiderMonkey (new) d2bs-sm.vcxproj unibind_backend_spidermonkey.lib + SpiderMonkey 153.3.0esr Release\js-sm-lod114d\d2bs.dll, released as d2bs-spidermonkey.zip

What changed

The renames (#47, #48) and the return to plain V8 (#49) landed first, so this diff is the migration alone, and it keeps main's shape with unibind types in place of V8's:

  • Bindings are declared through api::ClassBase over ub::Class (api/core/Class.h), and the api/core/{Convert,Extract,Error,Function}.h helpers work in unibind values. Class properties are declared on the instance template, so they stay own properties of each instance, as on main.
  • Events are main's events with unibind types: MakeArgs(const ub::Context&), Execute(context, fns) with the handler loop inline, timers holding a ub::Global<ub::Function>, drawable click/hover handlers resolved on the script thread, and the same lenient broadcast serialisation.
  • Engine::GetPlatform() owns the ub::Platform, with one fault handler for both engines (fatal errors, V8 DCHECKs and SpiderMonkey MOZ_CRASH all arrive as EngineFault::Fatal). The console reads ub::HeapStatistics and ub::Platform::BackendName() / BackendVersion() directly.
  • Script owns its ScriptInspector directly, as on main. ub::Inspector is null on SpiderMonkey, which has none; the server is started, and the console's debugging settings shown, only where ub::Inspector::Supported().
  • Teardown collects until every wrapper is finalized and reports what is still referenced before the isolate goes, and waits for other holders of the isolate without spinning.
  • FetchUnibind and FetchSpiderMonkey sit beside FetchV8, through one scripts/fetch_archive.ps1 (unibind's archive is checked against its published SHA-256). The backend no longer compiles against V8. CI and the release build and ship both DLLs.
  • scripts/check_compile.ps1: compile one frontend file for errors without MSBuild.
  • On main's layout (lint: discover projects instead of listing them #50-Land #53 and #54 on main: namespaces + flattened layout #55): the frontend is src/runtime in d2bs::runtime::* namespaces, both DLL projects live under src/dlls/ (js-v8-lod114d, js-sm-lod114d), lint.ps1 finds a project's compile database by its TargetName (both DLL projects build d2bs.dll), and gen_enum_names.py parses against unibind's headers instead of V8's.

JS-visible differences

The extracted API surface (api.json: every class, member, readonly flag, constructability, global, constant, me member and event) is identical to v2.1.6's; only two descriptions now say "the engine" where they said "V8". Behaviour that differs:

  • Class properties are own properties of each instance as before, but real accessors rather than V8's data-looking native properties: Object.getOwnPropertyDescriptor shows get/set, assigning to a read-only one throws in strict code (it used to silently replace the value), and reading one through Object.create(unit) throws Illegal invocation.
  • A getter or method called on a receiver of the wrong class throws TypeError: Illegal invocation instead of crashing.
  • A drawable stays readable after remove() (it used to read undefined); writes do nothing.
  • getDialogLines() handlers are not constructable (new handler() throws).
  • Promise continuations run at the event loop's pump points (after the top level, after main(), inside delay()), not whenever a native call returns.
  • An HTTP body may again be any ArrayBufferView; sendPacket takes an ArrayBuffer or typed array as before.
  • Error reports with no known line print :0: rather than :-1:; syntax errors now report file, line and the offending source line.
  • The console's Settings row shows the engine's name and version ("V8 15.6.8", "SpiderMonkey 153.3.0esr") where main shows "V8 version".

Verification

  • build.ps1 Release (both DLLs, no linker warnings), build.ps1 Release -Platform x64, build.ps1 test (157/157), check-format, lint (126/126) - all clean, with unibind 0.6.3 fetched from its release. extract_api.py: API surface identical to v2.1.6.
  • Run in the game on the V8 build (kolbot SoloPlay, the console, DevTools pause and breakpoints). That run caught regressions fixed here: class properties had moved onto the prototype, so kolbot's copyObj (for...in + hasOwnProperty) copied nothing; and script teardown spun a core for ever when DevTools was enabled. unibind 0.6.2 / 0.6.3 also made wrapping an object with many members (copyUnit, getUnit) fast on SpiderMonkey. The SpiderMonkey DLL still needs its live run, and this rebuilt branch a fresh one on V8.

Licensing and analytics

  • THIRD-PARTY-NOTICES.txt (every component's own licence text, and the SpiderMonkey source location MPL-2.0 asks for) ships with each release: beside d2bs.dll and inside d2bs-spidermonkey.zip.
  • Analytics' session_start records engine / engineVersion; the inspector feature tag counts only where the engine has a debugger (docs/analytics.md).

🤖 Generated with Claude Code

The frontend compiles against unibind's headers (ub::) instead of V8's,
so the same library links into a V8 DLL and a SpiderMonkey one; which
engine a DLL runs is decided by the unibind backend and engine its DLL
project links. No engine header remains in the frontend.

The code keeps main's plain-V8 shape with unibind types in place of V8's:
- Bindings are declared through api::ClassBase over ub::Class, and the
  api/core helpers (Convert, Extract, Error, Function) work in unibind
  values. Class properties are declared on the instance template, so they
  stay own properties of each instance, as on main.
- Events build their arguments in MakeArgs(const ub::Context&) and run
  their handlers in Execute(context, fns); timers hold a
  ub::Global<ub::Function>; drawable click/hover events resolve the
  handler on the script thread; a broadcast argument that cannot be
  cloned arrives as undefined.
- Engine::GetPlatform() owns the ub::Platform, with one fault handler for
  both engines. The console reads ub::HeapStatistics and the backend's
  name and version directly.
- Script owns its ScriptInspector directly; ub::Inspector is null on an
  engine without one (SpiderMonkey), and the server and the console's
  debugging settings are used only where ub::Inspector::Supported().
- Teardown collects until every wrapper is finalized and reports what is
  still referenced before the isolate goes, and waits for other holders
  of the isolate without spinning.
- FetchUnibind and FetchSpiderMonkey sit beside FetchV8, through one
  fetch script; the V8 DLL project is d2bs-v8 and a d2bs-sm project
  builds the SpiderMonkey DLL, both as d2bs.dll in their own directory.
- THIRD-PARTY-NOTICES.txt ships with each release, and analytics records
  which engine a session runs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant