Skip to content

Debugging and cleanup

Debug BDEngine plugins: initialize after bde:started, reopen windows, release resources, handle action history, and report API issues.

Log prefixes and a reproducible example

In the web editor, open your browser’s developer tools and the Console tab. Give your messages a short, consistent prefix so they can be distinguished from editor logs and messages from other plugins:

const log = (...args) => console.log('[my-useful-plugin]', ...args);
const fail = error => console.error('[my-useful-plugin] Action failed', error);

The current loader assigns scripts URLs such as bde-plugin://namespace/version.js, so the error stack helps identify the plugin and version. Record the action, input parameters, and operation stage, rather than just reporting that something does not work.

For a complex failure, reduce the plugin to one action and prepare a small test project. Keep the original project separately. Include only necessary data in a public report, without keys, tokens, or other people’s content.

Startup order and editor readiness

Normal startup follows this order:

  1. The editor creates the plugin system and window.editorAPI.
  2. The loader reads the saved files and executes them as regular scripts.
  3. The scene, services, and editor interface are created.
  4. editorAPI.initGUI() prepares the GUI-dependent parts of the API.
  5. bde:started is dispatched on window.

Add Dock buttons or windows from a bde:started handler, as shown in the first plugin. The presence of editorAPI at the start of the file does not mean you can access a GUI that has not been created yet. The event also does not mean that your own asynchronous tasks are complete: the editor does not wait for arbitrary Promise objects inside a plugin.

For manual debugging in an editor that has already loaded, call your start() function directly instead of subscribing to an event that has already fired. Do this after the working interface appears. Do not dispatch bde:started manually: it may run other plugins’ handlers again. The public API does not provide a separate, universal ready Promise for this scenario.

For the exact events, see Events and lifecycle.

Windows, buttons, and handlers

Separate plugin initialization from opening its tool. Register the button once when the GUI is ready. Its callback opens a window or focuses the existing one. After a regular window has closed, create a new instance.

A regular window has an important detail: the callback passed to createWindow() is called by clicking the close button. Calling win.close() directly does not invoke it. If the plugin closes the window programmatically, it must perform the necessary cleanup itself. Do not call close() again on an instance that is already closing.

Here is a shared cleanup function for a window with a timer:

// Snippet inside a window-opening function, after bde:started.
let timer = null;
let closed = false;
let win;
function cleanup() {
if (closed) return;
closed = true;
clearInterval(timer);
timer = null;
win = null;
}
function closeFromPlugin() {
if (closed) return;
const current = win;
cleanup();
current.close();
}
win = window.editorAPI.createWindow(
'Session', 'my-useful-plugin-session', 'icon-clock',
480, 300, 300, 200, cleanup
);
const status = document.createElement('p');
win.container.appendChild(status);
timer = setInterval(() => {
status.textContent = new Date().toLocaleTimeString();
}, 1000);
const closeButton = document.createElement('button');
closeButton.textContent = 'Close';
closeButton.addEventListener('click', closeFromPlugin);
win.container.appendChild(closeButton);

Both closing paths release the same timer. The close-button handler performs cleanup, while the window itself removes its DOM. Modal windows have a different lifecycle: normal hiding does not remove their content. Do not recreate handlers every time you show an existing modal window.

addButtonDockMenu() returns a DOM element. Remove your own button with the standard element.remove() method. For registered import and export actions, use remove() on the returned handle. These are different result types; there is no general editorAPI.removeButton() method.

Resources and data

The plugin is responsible for resources its code creates:

  • Stop setInterval, setTimeout, and your own requestAnimationFrame loops when they are no longer needed.
  • Remove handlers from window, document, and other long-lived elements using removeEventListener with the same function, or an AbortController.
  • Cancel your own requests with AbortController if the closed tool no longer needs their results.
  • Release object URLs you created using URL.revokeObjectURL().
  • Do not retain references to deleted scene objects or closed window content unnecessarily.
const events = new AbortController();
window.addEventListener('resize', onResize, { signal: events.signal });
function cleanupEvents() {
events.abort();
}

In this snippet, onResize is a function you have already declared, and cleanupEvents() is called when the relevant tool finishes. These are browser features, not a special BDEngine lifecycle hook.

Removing an entry from the plugin manager does not automatically call a plugin unload function. The editor must be restarted after removal. Do not rely on a nonexistent unload hook or automatic cleanup of arbitrary handlers.

For Three.js resources, establish ownership first. Do not call dispose() on an editor geometry, material, or texture just because you have a reference to it: other objects may share it. Rules for custom objects and meshes are described in Custom objects and global dependencies and Projects, lists, and geometry.

History and failures

Do not treat all JavaScript changes as one undoable operation. For example, editorAPI.add() records history by default, delete() uses the editor’s deletion path, and addObject() directly attaches an object to its parent. A direct property setter does not create a history command on its own either.

editorAPI.update() refreshes the display and, when requested, the transform controller. It does not turn previous changes into an Undo transaction. If an operation has several steps, a failure midway through may leave a partial result.

Validate arguments before changing the project, await asynchronous methods, and prevent a long-running operation from starting again before it finishes. After a failure, tell the user what changed and how to continue. Check Undo/Redo separately for the specific method; do not promise a single rollback for an entire generator unless you have implemented it.

Report an API issue

First, check the method signature, startup timing, and input against the reference. Then reproduce the issue with a minimal plugin and a small project, without other extensions if possible.

Include the following in the report:

  • The BDEngine version, Release channel, and browser or app version.
  • The plugin version and a minimal .js file sufficient to reproduce the issue.
  • Steps from editor startup to the error, plus the expected and actual result.
  • The error message and stack, and a test project if it is needed to reproduce the problem.
  • Whether the plugin uses only editorAPI or also accesses editor and internal objects.

In the editor, open Settings > Bug/Error to submit a report. For additional guidance, see reporting an issue. A bug in your code and an API error can look the same; a minimal example helps distinguish between them.