Перейти к содержимому

Отладка и корректное завершение работы

Отладка плагинов BDEngine: запуск после bde:started, повторное открытие окон, очистка ресурсов, история действий и отчёт об ошибке.

Префикс логов и воспроизводимый пример

В веб-редакторе откройте инструменты разработчика браузера и вкладку Console. Добавляйте к сообщениям короткий постоянный префикс, чтобы отличать их от логов редактора и других плагинов:

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

В текущем загрузчике скриптам назначаются адреса вида bde-plugin://namespace/version.js, поэтому стек ошибки помогает определить плагин и версию. Записывайте действие, входные параметры и стадию операции, а не только сообщение «не работает».

Для сложного сбоя сократите плагин до одного действия и подготовьте небольшой тестовый проект. Сохраните исходный проект отдельно. В публичный отчёт включайте только необходимые данные, без ключей, токенов и чужих материалов.

Порядок запуска и готовность редактора

При обычном запуске порядок следующий:

  1. Редактор создаёт систему плагинов и window.editorAPI.
  2. Загрузчик читает сохранённые файлы и выполняет их как обычные скрипты.
  3. Создаются сцена, сервисы и интерфейс редактора.
  4. editorAPI.initGUI() подготавливает GUI-зависимые части API.
  5. В window отправляется bde:started.

Поэтому кнопку Dock или окно добавляйте из обработчика bde:started, как в первом плагине. Наличие editorAPI в начале файла не даёт права обращаться к ещё не созданному GUI. Также событие не означает, что завершены ваши собственные асинхронные задания: редактор не ожидает произвольные Promise внутри плагина.

Для ручной отладки в уже загруженном редакторе вызовите свою функцию start() непосредственно, вместо подписки на прошедшее событие. Делайте это после появления рабочего интерфейса. Не отправляйте bde:started вручную: оно может повторно запустить обработчики других плагинов. В публичном API нет отдельного универсального ready-Promise для такого сценария.

Точные события: События и жизненный цикл.

Окна, кнопки и обработчики

Разделяйте инициализацию плагина и открытие его инструмента. Кнопка регистрируется один раз после готовности GUI. Её callback открывает окно или возвращает фокус уже существующему. После закрытия обычного окна создавайте новый экземпляр.

У обычного окна есть важная особенность: переданный в createWindow() callback вызывается нажатием крестика. Прямой вызов win.close() его не вызывает. Если плагин закрывает окно программно, он должен сам выполнить необходимую очистку. Не вызывайте close() повторно для уже закрываемого экземпляра.

Пример общей функции очистки для окна с таймером:

// Фрагмент внутри функции открытия окна после 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);

Здесь обе ветки закрытия освобождают один таймер. Обработчик крестика делает очистку, а удалением DOM занимается само окно. Для модальных окон жизненный цикл другой: обычное скрытие не удаляет их содержимое. Не пересоздавайте обработчики при каждом показе уже существующего модального окна.

addButtonDockMenu() возвращает DOM-элемент. Собственную кнопку можно удалить стандартным element.remove(). Для зарегистрированных действий импорта и экспорта используйте remove() возвращённого дескриптора. Это разные типы результатов; общего метода editorAPI.removeButton() нет.

Ресурсы и данные

За ресурсы, созданные кодом плагина, отвечает плагин:

  • Останавливайте setInterval, setTimeout и собственные циклы requestAnimationFrame, когда они больше не нужны.
  • Снимайте обработчики с window, document и других долгоживущих элементов через removeEventListener с той же функцией либо AbortController.
  • Отменяйте собственные запросы через AbortController, если результат закрытому инструменту больше не нужен.
  • Освобождайте созданные объектные URL через URL.revokeObjectURL().
  • Не храните ссылки на удалённые объекты сцены и содержимое закрытого окна без необходимости.
const events = new AbortController();
window.addEventListener('resize', onResize, { signal: events.signal });
function cleanupEvents() {
events.abort();
}

В этом фрагменте onResize - ваша заранее объявленная функция, а cleanupEvents() вызывается при завершении соответствующего инструмента. Это средства браузера, не специальный lifecycle-hook BDEngine.

Удаление записи из менеджера плагинов не вызывает автоматически функцию выгрузки плагина. После удаления требуется перезапуск редактора. Не рассчитывайте на несуществующий unload или на автоматическую очистку произвольных обработчиков.

С ресурсами Three.js сначала определите владельца. Не вызывайте dispose() у геометрии, материала или текстуры редактора только потому, что получили ссылку на них: ресурс может использоваться другими объектами. Правила собственных объектов и мешей описаны в Собственные объекты и глобальные зависимости и Проекты, списки и геометрия.

История и сбои

Нельзя считать все изменения через JavaScript одной отменяемой операцией. Например, editorAPI.add() по умолчанию использует сохранение в историю, delete() проходит через удаление редактора, а addObject() напрямую добавляет объект в родителя. Прямой сеттер свойства также не создаёт команду истории сам по себе.

editorAPI.update() обновляет отображение и при необходимости контроллер, но не превращает предыдущие изменения в транзакцию Undo. Если операция состоит из нескольких шагов, ошибка посередине может оставить частичный результат.

Проверяйте аргументы до изменения проекта, ожидайте асинхронные методы через await и блокируйте повторный запуск длительной операции до её завершения. После сбоя сообщите пользователю, что удалось изменить и как продолжить. Проверяйте Undo/Redo отдельно на конкретном методе; не обещайте единый откат всего генератора без реализации.

Отчёт об ошибке API

Сначала проверьте сигнатуру метода, момент запуска и входные данные по справочнику. Затем повторите проблему с минимальным плагином на небольшом проекте без других расширений, если это возможно.

В отчёте укажите:

  • Версию BDEngine, канал Release, браузер или версию приложения.
  • Версию плагина и минимальный .js, достаточный для воспроизведения.
  • Шаги от запуска редактора до ошибки, ожидаемый и фактический результат.
  • Текст ошибки и стек, а также тестовый проект, если без него проблема не возникает.
  • Используется ли только editorAPI или есть обращения к editor и внутренним объектам.

Путь отправки в редакторе: Настройки > Баг/ошибка. Дополнительные рекомендации: отчёт об ошибке. Сбой собственного кода и ошибка API могут выглядеть одинаково; минимальный пример помогает отделить одно от другого.