Source: components/DynamicLoadComponent.js

import { Flags } from '@uino/base-thing';
import { BaseComponent } from './BaseComponent';
import { EventType } from '../const';

const Flag = {
	Enable: 1 << 0,
	AutoRelease: 1 << 1,
}

const cDynamicLoadComponentTagName = '__cDynamicLoadComponentTagName__';

/**
 * @class DynamicLoadComponent
 * The dynamic load component.
 * @memberof THING
 * @extends THING.BaseComponent
 */
class DynamicLoadComponent extends BaseComponent {

	static mustCopyWithInstance = true;

	_flags = new Flags();

	_aliveTime = 60 * 1000; // Milliseconds

	_pendingUnloadObjects = new Map();

	/**
	 * Load or unload object's resources when show or hide it automaticly.
	 */
	constructor() {
		super();
	}

	// #region Private Functions

	_loadResource(object) {
		let pendingUnloadObjects = this._pendingUnloadObjects;

		if (object.active && object.visible) {
			if (!object.loading) {
				object.loadResource(false);
			}

			pendingUnloadObjects.delete(object);
		}
		else if (object.loaded) {
			let pendingUnloadObject = pendingUnloadObjects.get(object);
			if (!pendingUnloadObject) {
				pendingUnloadObjects.set(object, { aliveTime: this._aliveTime });
			}
		}
	}

	// #endregion

	// #region BaseComponent

	onAdd(object) {
		super.onAdd(object);

		this.enable = true;
	}

	onRemove() {
		this.enable = false;
		this.autoRelease = false;

		this._pendingUnloadObjects.clear();

		super.onRemove();
	}

	// #endregion

	/**
	 * Enable/Disable dynamic load.
	 * @type {Boolean}
	 * @private
	 */
	get enable() {
		return this._flags.has(Flag.Enable);
	}
	set enable(value) {
		if (this._flags.enable(Flag.Enable, value)) {
			if (value) {
				let pendingUnloadObjects = this._pendingUnloadObjects;

				// Destroy
				this.object.on(EventType.AfterDestroy, '.Object3D', (ev) => {
					let object = ev.object;

					pendingUnloadObjects.delete(object);
				}, cDynamicLoadComponentTagName);

				// Load/Unload resource when visible change
				this.object.on(EventType.VisibleChange, '.Object3D', (ev) => {
					this._loadResource(ev.object);
				}, cDynamicLoadComponentTagName);
			}
			else {
				this.object.off(EventType.AfterDestroy, cDynamicLoadComponentTagName);
				this.object.off(EventType.VisibleChange, cDynamicLoadComponentTagName);
			}
		}
	}

	/**
	 * Get/Set alive time in milliseconds.
	 * @type {Number}
	 * @private
	 */
	get aliveTime() {
		return this._aliveTime;
	}
	set aliveTime(value) {
		this._aliveTime = value;
	}

	/**
	 * Enable/Disable auto release resources.
	 * @type {Boolean}
	 * @private
	 */
	get autoRelease() {
		return this._flags.has(Flag.AutoRelease);
	}
	set autoRelease(value) {
		if (this._flags.enable(Flag.AutoRelease, value)) {
			if (value) {
				this.object.app.on(EventType.Update, (ev) => {
					// Conver seconds to milliseconds
					let deltaTime = ev.deltaTime * 1000;

					// Try to unload objects resource by alive time
					let pendingUnloadObjects = this._pendingUnloadObjects;
					pendingUnloadObjects.forEach((pendingUnloadObject, object) => {
						if (pendingUnloadObject.aliveTime >= deltaTime) {
							pendingUnloadObject.aliveTime -= deltaTime;
						}
						else {
							object.unloadResource(false);

							pendingUnloadObjects.delete(object);
						}
					});
				}, cDynamicLoadComponentTagName);
			}
			else {
				this.object.app.off(EventType.Update, cDynamicLoadComponentTagName);
			}
		}
	}
}

export { DynamicLoadComponent }