Source: managers/ObjectManager.js

import { Utils } from '../common/Utils';

const __ = {
	private: Symbol('private'),
}

/**
 * @class ObjectManager
 * The object manager.
 * @memberof THING
 */
class ObjectManager {

	/**
	 * The object manager to manage object(s) life.
	 */
	constructor() {
		this[__.private] = {};
		let _private = this[__.private];

		_private.objects = [];
		_private.objectIdMaps = new Map();
		_private.objectUUIDMaps = new Map();
		_private.tickableObjects = [];
		_private.resizableObjects = [];
		_private.dirtyObjects = [];
		_private.lateUpdateObjects = [];
		_private.keepAliveObjects = [];

		if (_DEBUG) {
			_private.gcObjects = new Set();
			_private.app = Utils.getCurrentApp();

			_private.onGCDestroyObjectCallback = (key) => {
				_private.gcObjects.delete(key);
			};

			_private.app.global.gc.registerDestroyObjectCallback(_private.onGCDestroyObjectCallback);
		}
	}

	// #region Private

	_buildObjectGCKey(object) {
		let { type, name, id, uuid } = object;

		return `type: '${type}', name: '${name}', id: '${id}', uuid: '${uuid}'`;
	}

	// #endregion

	dispose() {
		let _private = this[__.private];

		_private.objects = [];
		_private.objectIdMaps.clear();
		_private.objectUUIDMaps.clear();
		_private.tickableObjects = [];
		_private.resizableObjects = [];
		_private.dirtyObjects = [];
		_private.lateUpdateObjects = [];
		_private.keepAliveObjects = [];

		if (_DEBUG) {
			_private.app.global.gc.unregisterDestroyObjectCallback(_private.onGCDestroyObjectCallback);

			_private.app = null;
		}
	}

	resize(width, height) {
		let _private = this[__.private];

		let resizableObjects = _private.resizableObjects;
		for (let i = 0, l = resizableObjects.length; i < l; i++) {
			resizableObjects[i].onResize(width, height);
		}
	}

	update(deltaTime) {
		let _private = this[__.private];

		// Update dirty objects
		let dirtyObjects = _private.dirtyObjects;
		if (dirtyObjects.length) {
			for (let i = 0, l = dirtyObjects.length; i < l; i++) {
				dirtyObjects[i].onRefresh();
			}

			dirtyObjects.length = 0;
		}

		// Update tickable objects
		let tickableObjects = _private.tickableObjects;
		for (let i = 0, l = tickableObjects.length; i < l; i++) {
			let object = tickableObjects[i];

			if (object.isWaitingForUpdate) {
				continue;
			}

			object.onUpdate(deltaTime);
		}
	}

	lateUpdate(deltaTime) {
		let _private = this[__.private];

		let objects = _private.lateUpdateObjects;
		for (let i = 0, l = objects.length; i < l; i++) {
			objects[i].onLateUpdate(deltaTime);
		}
	}

	getBaseObjectFromOrderID(orderId) {
		let _private = this[__.private];

		return _private.objectIdMaps.get(orderId);
	}

	getBaseObjectFromUUID(uuid) {
		let _private = this[__.private];

		return _private.objectUUIDMaps.get(uuid);
	}

	getBaseObjectFromNode(node) {
		let _private = this[__.private];

		let rootNode = node;
		while (rootNode) {
			let userData = rootNode.getUserData();
			let orderId = userData['baseObject_orderID'];
			if (orderId) {
				return _private.objectIdMaps.get(orderId);
			}

			rootNode = rootNode.getParent();
		}

		return null;
	}

	addObject(object) {
		let _private = this[__.private];

		_private.objectIdMaps.set(object.orderId, object);
		_private.objectUUIDMaps.set(object.uuid, object);
		_private.objects.push(object);

		// Watch GC to check memory leak
		if (_DEBUG) {
			let key = this._buildObjectGCKey(object);

			_private.gcObjects.add(key);

			_private.app.global.gc.register(object, key);
		}
	}

	removeObject(object) {
		let _private = this[__.private];

		_private.objectIdMaps.delete(object.orderId);
		_private.objectUUIDMaps.delete(object.uuid);
		Utils.removeFromArray(_private.objects, object);
		Utils.removeFromArray(_private.tickableObjects, object);
		Utils.removeFromArray(_private.resizableObjects, object);
	}

	addDirtyObject(object) {
		let _private = this[__.private];

		_private.dirtyObjects.push(object);
	}

	removeDirtyObject(object) {
		let _private = this[__.private];

		Utils.removeFromArray(_private.dirtyObjects, object);
	}

	addTickableObject(object) {
		let _private = this[__.private];

		_private.tickableObjects.push(object);
	}

	insertTickableObject(index, object) {
		let _private = this[__.private];

		_private.tickableObjects._insert(index, object);
	}

	removeTickableObject(object) {
		let _private = this[__.private];

		Utils.removeFromArray(_private.tickableObjects, object);
	}

	addResizableObject(object) {
		let _private = this[__.private];

		_private.resizableObjects.push(object);
	}

	removeResizableObject(object) {
		let _private = this[__.private];

		Utils.removeFromArray(_private.resizableObjects, object);
	}

	addLateUpdateObject(object) {
		let _private = this[__.private];

		_private.lateUpdateObjects.push(object);
	}

	removeLateUpdateObject(object) {
		let _private = this[__.private];

		Utils.removeFromArray(_private.lateUpdateObjects, object);
	}

	addKeepAliveObject(object) {
		let _private = this[__.private];

		_private.keepAliveObjects.push(object);
	}

	removeKeepAliveObject(object) {
		let _private = this[__.private];

		Utils.removeFromArray(_private.keepAliveObjects, object);
	}

	clearKeepAliveObjects() {
		let _private = this[__.private];
		_private.keepAliveObjects.length = 0;
	}

	get gc() {
		if (_DEBUG) {
			let _private = this[__.private];

			return {
				objects: _private.gcObjects
			};
		}
		else {
			return null;
		}
	}

	get objects() {
		let _private = this[__.private];

		return _private.objects;
	}

	get tickableObjects() {
		let _private = this[__.private];

		return _private.tickableObjects;
	}

	get lateUpdateObjects() {
		let _private = this[__.private];

		return _private.lateUpdateObjects;
	}

	get resizableObjects() {
		let _private = this[__.private];

		return _private.resizableObjects;
	}

	get dirtyObjects() {
		let _private = this[__.private];

		return _private.dirtyObjects;
	}

	get keepAliveObjects() {
		let _private = this[__.private];

		return _private.keepAliveObjects;
	}

}

export { ObjectManager }