Observer Pattern через Observable и Observer

В Babylon.js паттерн Observer реализован через объекты Observable и Observer, что позволяет эффективно управлять событиями и реакциями на изменения состояния. Основная идея заключается в том, что один объект (Observable) уведомляет подписчиков (Observers) о произошедших изменениях. Такой подход упрощает архитектуру приложений и обеспечивает гибкость при работе с событиями.


Observable

Объект Observable представляет собой контейнер для подписчиков. Он хранит список функций или объектов, которые должны быть уведомлены при изменении состояния.

Создание Observable

const observable = new BABYLON.Observable();

Observable можно параметризовать типом данных, передаваемых подписчикам:

const observableVector = new BABYLON.Observable<BABYLON.Vector3>();

Это позволяет обеспечить строгую типизацию, особенно полезную при работе с TypeScript.

Методы Observable

  1. add – добавляет нового подписчика:
const observer = observable.add((data) => {
    console.log("Данные обновлены:", data);
});
  1. remove – удаляет подписчика:
observable.remove(observer);
  1. notifyObservers – уведомляет всех подписчиков о событии:
observable.notifyObservers(new BABYLON.Vector3(1, 2, 3));

Observer

Observer в Babylon.js представляет собой объект, содержащий callback-функцию и, при необходимости, уникальный идентификатор. Каждый Observer связан с конкретным Observable.

Создание Observer

Observer создается автоматически при добавлении функции через observable.add, но его можно сохранить для последующего удаления:

const observer = observable.add((data) => {
    console.log("Изменения:", data);
});

Удаление осуществляется через метод observable.remove(observer). Это позволяет динамически управлять подписчиками и освобождать ресурсы.


Применение в 3D-сценах

Observable и Observer активно используются в Babylon.js для реагирования на события камеры, материалов, света и сцены в целом.

Пример: отслеживание движения камеры

const camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);

camera.onViewMatrixChangedObservable.add((cam) => {
    console.log("Камера изменила положение:", cam.position);
});

onViewMatrixChangedObservable – встроенный Observable, который уведомляет каждый раз при изменении матрицы вида камеры.

Пример: взаимодействие с объектами

const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {diameter: 2}, scene);

sphere.actionManager = new BABYLON.ActionManager(scene);
sphere.actionManager.registerAction(
    new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnPickTrigger, () => {
        console.log("Сфера выбрана");
    })
);

Внутри ActionManager используется собственный механизм Observable/Observer для управления событиями клика и взаимодействия.


Управление подписками и фильтрация уведомлений

Observable позволяет добавлять опции при регистрации Observer:

  • scope – контекст выполнения callback.
  • mask – позволяет фильтровать события по битовой маске.
  • insertFirst – размещает Observer в начале очереди уведомлений.
const observer = observable.add((data) => {
    console.log("Только важные данные:", data);
}, null, true);

Использование масок и insertFirst особенно важно в крупных сценах, где последовательность обработки событий критична для производительности.


Преимущества паттерна Observer

  • Слабая связанность объектов – Observable не знает деталей реализации подписчиков.
  • Гибкость архитектуры – легко добавлять или удалять реакцию на события.
  • Централизованное управление событиями – один объект может уведомлять множество подписчиков.
  • Оптимизация ресурсов – динамическое удаление Observer предотвращает утечки памяти.

Особенности реализации Babylon.js

  1. Все Observable имеют возможность передавать любые данные через метод notifyObservers.
  2. Каждый Observer автоматически получает ссылку на Observable, что позволяет удалять себя из списка подписчиков изнутри callback.
  3. Система событий Babylon.js интегрирована с ActionManager, Mesh, Camera и Scene, что обеспечивает единообразное управление событиями 3D-сцены.

Рекомендации по использованию

  • Для отслеживания изменений состояния объектов в сцене использовать встроенные Observable (например, onViewMatrixChangedObservable или onDisposeObservable).
  • Для кастомных событий создавать собственные Observable на уровне компонентов или систем.
  • Всегда хранить ссылки на Observer, если требуется их удаление, чтобы избежать утечек памяти.
  • Использовать insertFirst и маски для оптимизации обработки событий при больших сценах.

Этот подход позволяет строить масштабируемые 3D-приложения с четкой архитектурой событий, минимизируя жесткую связанность компонентов и обеспечивая эффективное управление ресурсами.