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:
- The editor creates the plugin system and
window.editorAPI. - The loader reads the saved files and executes them as regular scripts.
- The scene, services, and editor interface are created.
editorAPI.initGUI()prepares the GUI-dependent parts of the API.bde:startedis dispatched onwindow.
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 ownrequestAnimationFrameloops when they are no longer needed. - Remove handlers from
window,document, and other long-lived elements usingremoveEventListenerwith the same function, or anAbortController. - Cancel your own requests with
AbortControllerif 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
.jsfile 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
editorAPIor also accesseseditorand 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.