import { Box3 } from '@uino/base-thing';
import { Utils } from '../common/Utils';
import { ObjectExpression } from '../common/ObjectExpression';
import { AsyncSelector } from './AsyncSelector';
import { StyleSelector } from './StyleSelector';
import { InheritSelector } from './InheritSelector';
const _styleSelector = new StyleSelector();
const _inheritSelector = new InheritSelector();
// #region ObjectExpression-Cache
let _expressions = new Map();
function _getExpression(condition, mode) {
let key = mode ? mode + ':' + condition : condition;
let expression = _expressions.get(key);
if (!expression) {
expression = new ObjectExpression(condition, mode);
_expressions.set(key, expression);
}
return expression;
}
// #endregion
// #region Private
function _selectObjectsByExpression(expression, objects, result) {
objects.forEach(object => {
if (expression.evaluate(object)) {
result.push(object);
}
});
}
function _selectObjectsByRegExpName(condition, objects, result) {
objects.forEach(object => {
if (!condition.test(object.name)) {
return;
}
result.push(object);
});
}
function _selectObjectsByFunction(condition, objects, result) {
objects.forEach(object => {
if (!condition(object)) {
return;
}
result.push(object);
});
}
function _findObjectsByExpression(expression, objects) {
for (let i = 0, l = objects.length; i < l; i++) {
let object = objects[i];
if (expression.evaluate(object)) {
return object;
}
}
return null;
}
function _findObjectsByRegExpName(condition, objects) {
for (let i = 0, l = objects.length; i < l; i++) {
let object = objects[i];
if (condition.test(object.name)) {
return object;
}
}
return null;
}
function _findObjectsByFunction(condition, objects) {
for (let i = 0, l = objects.length; i < l; i++) {
let object = objects[i];
if (condition(object)) {
return object;
}
}
return null;
}
// Get objects by condition.
function _getObjectsByCondition(condition, objects) {
if (condition) {
if (Utils.isString(condition)) {
return Selector.select(condition, objects);
}
else if (Utils.isArray(condition)) {
return condition;
}
else if (condition.isSelector) {
return condition.objects;
}
else {
return [condition];
}
}
return null;
}
function _parseObjects(param) {
if (Utils.isArray(param)) {
return param;
}
if (Utils.isObject(param)) {
return Utils.parseValue(param['objects'], []);
}
return [];
}
function _pushArray(array, arrayLength, target) {
for (let i = 0; i < target.length; i++) {
array[arrayLength] = target[i];
++arrayLength;
}
return arrayLength;
}
// #endregion
/**
* @class Selector
* 用于根据条件查找对象的选择器。
* @memberof THING
* @public
*/
class Selector {
static asyncSelector = null;
constructor(param = {}) {
this._app = param['app'] || Utils.getCurrentApp();
this._objects = _parseObjects(param);
// Hook [index] to implement accessor by index
let handler = {
get: (target, prop) => {
let object = this._objects[prop];
if (object && object.isBaseObject) {
return object;
}
return target[prop];
},
};
let proxy = new Proxy(this, handler);
Object.defineProperty(proxy, 'customFormatters', {
enumerable: false,
configurable: false,
get: () => {
return ['object', { object: this._objects }];
}
});
return proxy;
}
static update(deltaTime) {
ObjectExpression.update(deltaTime);
}
// #region Private
_queryInWorker(condition, root, recursive) {
return new Promise((resolve, reject) => {
// Query in normal async mode if it's just simple expression
if (condition == '*') {
let result;
if (root) {
result = root.query(condition, { recursive });
}
else {
result = this.onCreateInstance({ objects: this.objects });
}
resolve(result);
}
else {
let promise;
if (root) {
promise = Selector.asyncSelector.queryAsync(root, recursive, condition);
}
else {
promise = Selector.asyncSelector.selectAsync(this.objects, condition);
}
promise.then(result => {
let objects = this._app.objectManager.objects;
let filteredObjects = result.orderIDs.map(id => {
return objects.get(id);
});
let selector = this.onCreateInstance({ objects: filteredObjects });
resolve(selector);
});
}
});
}
// #endregion
/**
* 使用对象初始化。
* @param {Array<THING.BaseObject>} objects 对象数组。
* @private
* @returns {THING.Selector} 当前选择器实例。
* @example
* let selector = new Selector([object1, object2, object3]);
*/
init(objects) {
// Clear previous objects
this.clear();
// Set members as Array
this._objects = objects.slice(0);
return this;
}
applyObjects(funcName, args) {
this._objects.forEach(object => {
object[funcName].apply(object, args);
});
}
onCreateInstance() {
return new Selector(arguments[0]);
}
// #region Objects Query Operations
/**
* 根据条件选择对象。
* @param {string} condition 条件。
* @param {Array<object>} objects 要查询的对象数组。
* @returns {Array<object>} 选择的对象数组。
* @example
* // 从对象中选择实体
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let entities = selector.select('.Entity', objects);
* let entity = entities[0];
* let ret = entity.isEntity;
* // @expect(ret == true)
*/
select(condition, objects) {
let result = [];
objects = objects || this.objects;
if (objects.length) {
if (Utils.isString(condition)) {
// All
if (condition == '*') {
result = objects.slice(0);
}
else {
let expression = _getExpression(condition);
_selectObjectsByExpression(expression, objects, result);
}
}
// Reg Expression
else if (Utils.isRegExp(condition)) {
_selectObjectsByRegExpName(condition, objects, result);
}
// Function
else if (Utils.isFunction(condition)) {
_selectObjectsByFunction(condition, objects, result);
}
}
return result;
}
_selectByCondition(condition, objects, mode) {
let result = [];
objects = objects || this.objects;
if (objects.length) {
if (Utils.isString(condition)) {
let expression = _getExpression(condition, mode);
_selectObjectsByExpression(expression, objects, result);
}
}
return result;
}
_outputLogger(funcName, condition) {
let msg = { "condition": condition };
Utils.logger.api("Selector_" + funcName, { msg: JSON.stringify(msg) });
}
/**
* 测试单个对象是否符合指定条件(不考虑对象的查询状态)。
* @param {string} condition 条件。
* @param {THING.BaseObject} object 要查询的对象。
* @returns {boolean} 是否符合条件。
* @example
* // 测试对象是否符合指定条件
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let object = new THING.Entity();
* let ret = selector.test('.Entity', object);
* // @expect(ret == true);
* let box = new THING.Box();
* ret = selector.test('.Entity', box);
* // @expect(ret == false);
*/
test(condition, object) {
if (Utils.isString(condition)) {
// All
if (condition == '*') {
return true;
}
else {
let expression = _getExpression(condition);
return expression.evaluate(object);
}
}
// Reg Expression
else if (Utils.isRegExp(condition)) {
return condition.test(object.name);
}
// Function
else if (Utils.isFunction(condition)) {
return condition(object);
}
return false;
}
/**
* 通过一些条件查找对象并将其存储在结果中。
* @param {string} condition 条件。
* @param {THING.BaseObject} objects 要查询的对象。
* @param {THING.Selector} target 目标选择器。
* @returns {THING.Selector} 目标的引用或新选择器。
* @example
* // 通过指定条件查询/选择对象
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let entitys = selector.query('.Entity');
* let entity = entitys[0];
* // @expect(entity.isEntity == true);
*/
query(condition, objects, target) {
let objs = objects || this._objects;
let result = this.select(condition, objs).filter(object => {
return object.queryable;
});
target = target || this.onCreateInstance();
if (_NEED_LOGGER) {
this._outputLogger('query', condition.toString());
}
return target.init(result);
}
/**
* 根据某些条件查找对象,不考虑queryable属性。
* @param {string} condition 条件。
* @param {THING.BaseObject[]} objects 要查询的对象数组。
* @param {THING.Selector} target 目标选择器。
* @returns {THING.Selector} 目标选择器的引用或新选择器。
* @example
* // 通过指定条件查询/选择对象,忽略queryable
* let objects = app.queryAll('.BaseObject');
* let selector = new THING.Selector(objects);
* let entitys = selector.queryAll('.Entity');
* let entity = entitys[0];
* // @expect(entity.isEntity == true);
*/
queryAll(condition, objects, target) {
let objs = objects || this._objects;
let result = this.select(condition, objs);
target = target || this.onCreateInstance();
if (_NEED_LOGGER) {
this._outputLogger('queryAll', condition.toString());
}
return target.init(result);
}
_queryByAttribute(objects, cb, target) {
var objs = (objects || this._objects).filter((c) => {
return cb(c) && c.queryable;
});
target = target || this.onCreateInstance();
return target.init(objs);
}
_queryByCondition(condition, objects, target, mode) {
let objs = objects || this._objects;
let result = this._selectByCondition(condition, objs, mode).filter(object => {
return object.queryable;
});
target = target || this.onCreateInstance();
return target.init(result);
}
/**
* 使用正则表达式查询子对象。
* @param {RegExp} condition 选择对象的条件,作为正则表达式。
* @param {THING.BaseObject[]} objects 要查询的对象数组。
* @param {THING.Selector} target 目标选择器。
* @returns {THING.Selector} 返回包含查询结果的选择器实例。
* @example
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let car = selector.queryByRegExp(/car/);
* let ret = car.length != 0;
* // @expect(ret == true)
* @public
*/
queryByRegExp(condition, objects, target) {
if (_NEED_LOGGER) {
this._outputLogger('queryByRegExp', condition.toString());
}
return this.query(condition, objects, target);
}
/**
* 使用正则表达式查询子对象。
* @param {RegExp} condition 选择对象的条件,作为正则表达式。
* @param {THING.BaseObject[]} objects 要查询的对象数组。
* @param {THING.Selector} target 目标选择器。
* @returns {THING.Selector} 返回包含查询结果的选择器实例。
* @example
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let car = selector.queryByReg(/car/);
* console.log(car);
* @deprecated 2.7
* @private
*/
queryByReg(condition, objects, target) {
return this.queryByRegExp(condition, objects, target);
}
/**
* 根据标签查询子对象。
* @param {string} condition 选择对象的条件。
* @param {THING.BaseObject[]} objects 要查询的对象数组。
* @param {THING.Selector} target 目标选择器。
* @returns {THING.Selector} 返回包含查询结果的选择器实例。
* @example
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let car = selector.queryByTags('Entity');
* let ret = car.length != 0;
* // @expect(ret == true)
* @public
*/
queryByTags(condition, objects, target) {
if (_NEED_LOGGER) {
this._outputLogger('queryByTags', condition);
}
return this._queryByCondition('tags(' + condition + ')', objects, target, 'tags');
}
/**
* 根据名称查询子对象。
* @param {string} condition 选择对象的条件。
* @param {THING.BaseObject[]} objects 要查询的对象数组。
* @param {THING.Selector} target 目标选择器。
* @returns {THING.Selector} 返回包含查询结果的选择器实例。
* @example
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let car = selector.queryByName('car1');
* let ret = car.length != 0 && car[0].name == 'car1';
* // @expect(ret == true)
* @public
*/
queryByName(condition, objects, target) {
if (_NEED_LOGGER) {
this._outputLogger('queryByName', condition);
}
return this._queryByAttribute(objects, (c) => { return c.name == condition }, target);
}
/**
* 根据UUID查询子对象。
* @param {string} condition 选择对象的条件。
* @param {THING.BaseObject[]} objects 要查询的对象数组。
* @param {THING.Selector} target 目标选择器。
* @returns {THING.Selector} 返回包含查询结果的选择器实例。
* @example
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let car = selector.queryByUUID('1605');
* let ret = car.length != 0 && car[0].uuid == '1605';
* // @expect(ret == true)
* @public
*/
queryByUUID(condition, objects, target) {
if (_NEED_LOGGER) {
this._outputLogger('queryByUUID', condition);
}
return this._queryByAttribute(objects, (c) => { return c.uuid == condition }, target);
}
/**
* 根据id查询子对象。
* @param {string} condition 选择对象的条件。
* @param {THING.BaseObject[]} objects 要查询的对象数组。
* @param {THING.Selector} target 目标选择器。
* @returns {THING.Selector} 返回包含查询结果的选择器实例。
* @example
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let car = selector.queryById('car01');
* let ret = car.length != 0 && car[0].id == 'car01';
* // @expect(ret == true)
* @public
*/
queryById(condition, objects, target) {
if (_NEED_LOGGER) {
this._outputLogger('queryById', condition);
}
return this._queryByAttribute(objects, (c) => { return c.id == condition }, target);
}
/**
* 根据类型查询子对象。
* @param {string} condition 选择对象的条件。
* @param {THING.BaseObject[]} objects 要查询的对象数组。
* @param {THING.Selector} target 目标选择器。
* @returns {THING.Selector} 返回包含查询结果的选择器实例。
* @example
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let entitys = selector.queryByType('Entity');
* let ret = entitys.length != 0 && entitys[0].type == 'Entity';
* // @expect(ret == true)
* @public
*/
queryByType(condition, objects, target) {
if (_NEED_LOGGER) {
this._outputLogger('queryByType', condition);
}
var key = "is" + condition;
return this._queryByAttribute(objects, (c) => { return c[key] == true }, target);
}
/**
* 根据孪生体类型查询子对象。
* @param {string} condition 选择对象的条件。
* @param {THING.BaseObject[]} objects 要查询的对象数组。
* @param {THING.Selector} target 目标选择器。
* @returns {THING.Selector} 返回包含查询结果的选择器实例。
* @example
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let equipment = selector.queryByDTType('Equipment');
* let ret = equipment.length != 0 && equipment[0].dtType == 'Equipment';
* // @expect(ret == true)
* @public
*/
queryByDTType(condition, objects, target) {
if (_NEED_LOGGER) {
this._outputLogger('queryByDTType', condition);
}
return this._queryByAttribute(objects, (c) => { return c.dtType === condition }, target);
}
/**
* 根据userData查询子对象。
* @param {string} condition 选择对象的条件。
* @param {THING.BaseObject[]} objects 要查询的对象数组。
* @param {THING.Selector} target 目标选择器。
* @returns {THING.Selector} 返回包含查询结果的选择器实例。
* @example
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let car = selector.queryByUserData('id="666"')
* let ret = car.length != 0 && car[0].userData.id == '666';
* // @expect(ret == true)
* @public
*/
queryByUserData(condition, objects, target) {
if (_NEED_LOGGER) {
this._outputLogger('queryByUserData', condition);
}
return this._queryByCondition(condition, objects, target, 'userData');
}
/**
* 根据userData查询子对象。
* @param {string} condition 选择对象的条件。
* @param {THING.BaseObject[]} objects 要查询的对象数组。
* @param {THING.Selector} target 目标选择器。
* @returns {THING.Selector} 返回包含查询结果的选择器实例。
* @example
* let car = selector.queryByData('test=1')
* console.log(car);
* @deprecated 2.7
* @private
*/
queryByData(condition, objects, target) {
return this.queryByUserData(condition, objects, target);
}
/**
* 根据某些条件查找对象,并将其存储在结果中,以异步模式查询。
* @param {string} condition 条件。
* @param {THING.BaseObject} root 根对象。
* @param {boolean} recursive 布尔值,表示是否递归查询。
* @returns {Promise<THING.Selector>} 返回包含查询结果的选择器实例。
* @example
* // 从根对象异步查询/选择对象,指定条件
* selector.queryAsync('.Entity', rootObject, true).then((result) => {
* console.log(result);
* });
* @private
*/
queryAsync(condition, root, recursive) {
// Try to query in worker by async selector
if (Selector.asyncSelector) {
return this._queryInWorker(condition, root, recursive);
}
else {
return new Promise((resolve, reject) => {
let result;
if (root) {
result = root.query(condition, { recursive });
}
else {
result = this.query(condition);
}
resolve(result);
});
}
}
/**
* 根据条件查找对象并返回第一个对象。
* @param {string} condition 条件。
* @param {THING.BaseObject[]} objects 要查询的对象。
* @returns {THING.BaseObject} 返回第一个对象。
* @example
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let entity = selector.find('.Entity')
* let ret = entity instanceof THING.Entity;;
* // @expect(ret == true)
* @public
*/
find(condition, objects) {
let objs = objects || this._objects;
if (!objs.length) {
return null;
}
if (Utils.isString(condition)) {
// All
if (condition == '*') {
return objs[0];
}
else {
let expression = _getExpression(condition);
return _findObjectsByExpression(expression, objs);
}
}
// Reg Expression
else if (Utils.isRegExp(condition)) {
return _findObjectsByRegExpName(condition, objs);
}
// Function
else if (Utils.isFunction(condition)) {
return _findObjectsByFunction(condition, objs);
}
return null;
}
/**
* 根据条件查找对象并返回第一个对象的索引。
* @param {string|RegExp|Function} condition 条件。
* @param {THING.BaseObject[]} objects 要查询的对象。
* @returns {number} 返回找到的对象的索引,如果未找到则返回 -1。
* @example
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let index = selector.findIndex('.Entity');
* // @expect(index >= 0);
* @public
*/
findIndex(condition, objects) {
let objs = objects || this._objects;
if (!objs.length) {
return -1;
}
if (Utils.isString(condition)) {
// All
if (condition == '*') {
return 0;
}
else {
let expression = _getExpression(condition);
for (let i = 0; i < objs.length; i++) {
if (expression.evaluate(objs[i])) {
return i;
}
}
}
}
// Reg Expression
else if (Utils.isRegExp(condition)) {
for (let i = 0; i < objs.length; i++) {
if (condition.test(objs[i].name)) {
return i;
}
}
}
// Function
else if (Utils.isFunction(condition)) {
for (let i = 0; i < objs.length; i++) {
if (condition(objs[i])) {
return i;
}
}
}
return -1;
}
/**
* 遍历所有对象并执行回调函数。
* @param {Function} callback 回调函数。
* @example
* selector.every(object => {
* return object.isVisible; // 检查每个对象是否可见
* });
* @returns {boolean} 如果所有对象都满足条件,则返回true,否则返回false。
* @public
*/
every(callback) {
return this._objects.every((object, index) => {
return callback(object, index, this._objects);
});
}
/**
* 使用flat将对象数组扁平化。
* @param {number} depth 深度,默认为1。
* @returns {THING.Selector} 扁平化后的新选择器。
* @example
* let flatSelector = selector.flat();
* console.log(flatSelector);
* @public
*/
flat(depth = 1) {
return this.onCreateInstance({ objects: this._objects.flat(depth) });
}
/**
* 使用flatMap遍历对象并返回一个新选择器。
* @param {Function} callback 回调函数。
* @returns {THING.Selector} 新选择器,包含回调函数返回的结果。
* @example
* let results = selector.flatMap(object => {
* return object.children; // 返回每个对象的子对象
* });
* @public
*/
flatMap(callback) {
let results = [];
this._objects.forEach((object, index) => {
const mapped = callback(object, index, this._objects);
if (Utils.isArray(mapped)) {
results.push(...mapped);
}
else {
results.push(mapped);
}
});
return this.onCreateInstance({ objects: results });
}
// #endregion
// #region Objects Array Operations
/**
* 将对象推入数组。
* @param {...THING.BaseObject|Array<THING.BaseObject>|THING.Selector} args 要推入的对象。
* @returns {number} 推入对象后的数组长度。
* @example
* let objects = app.query('.Entity');
* let selector = new THING.Selector(objects);
* let count1 = selector.length;
* let object = new THING.BaseObject();
* let count2 = selector.push(object);
* let ret = count2 - count1 == 1;
* // @expect(ret == true)
* @public
*/
push(...args) {
let arrayLength = this._objects.length;
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (!arg) {
continue;
}
if (arg.isSelector) {
arrayLength = _pushArray(this._objects, arrayLength, arg.objects);
}
else if (Utils.isArray(arg)) {
arrayLength = _pushArray(this._objects, arrayLength, arg);
}
else {
this._objects[arrayLength] = arg;
++arrayLength;
}
}
return arrayLength;
}
/**
* 根据选择器添加对象,并返回包含结果的新选择器。
* @param {THING.Selector|Array<THING.BaseObject>|THING.BaseObject} selector 要添加的选择器或对象。
* @returns {THING.Selector} 返回包含结果的选择器实例。
* @example
* let objects = app.query('.Entity');
* let selector = new THING.Selector(objects);
* let count1 = selector.length;
* let object1 = new THING.BaseObject();
* let object2 = new THING.BaseObject();
* let newSelector = selector.add([object1,object2]);
* let count2 = newSelector.length;
* let ret = count2 - count1 == 2;
* // @expect(ret == true)
* @public
*/
add(selector) {
// Get source objects
let srcObjects = [];
if (selector.isSelector) {
srcObjects = selector.objects;
}
else if (Utils.isArray(selector)) {
srcObjects = selector;
}
else if (selector.isBaseObject) {
srcObjects = [selector];
}
// Add objects
let objects = this._objects.slice(0);
for (let i = 0; i < srcObjects.length; i++) {
let index = objects.indexOf(srcObjects[i]);
if (index === -1) {
objects.push(srcObjects[i]);
}
}
return this.onCreateInstance({ objects });
}
/**
* 根据条件删除对象,并返回包含结果的新选择器。
* @param {string} condition 条件。
* @returns {THING.Selector} 返回包含结果的选择器实例。
* @example
* let objects = app.query('.Entity');
* let selector = new THING.Selector(objects);
* selector.remove('.Entity');
* let count = selector.length;
* let ret = count == 0;
* // @expect(ret == true)
* @public
*/
remove(condition) {
let candidates = _getObjectsByCondition(condition, this._objects);
if (!candidates) {
return this.onCreateInstance({ objects: this.objects });
}
// 删除对象
let objects = this._objects.slice(0);
for (let i = 0; i < candidates.length; i++) {
let index = objects.indexOf(candidates[i]);
if (index !== -1) {
objects._removeAt(index);
}
}
return this.onCreateInstance({ objects });
}
/**
* 根据条件删除对象,并返回包含结果的新选择器。
* @param {string} condition 条件。
* @returns {THING.Selector} 返回包含结果的选择器实例。
* @example
* // 通过删除选择器/对象返回新的选择器
* let objects = selector.not([obj1, obj2, obj3]);
* console.log(objects);
* @public
*/
not(condition) {
return this.remove(condition);
}
/**
* 清空对象。
* @example
* let objects = app.query('.Entity');
* let selector = new THING.Selector(objects);
* selector.clear();
* let ret = count == 0;
* // @expect(ret == true)
* @public
*/
clear() {
this._objects.length = 0;
}
/**
* 通过forEach遍历对象。
* @callback OnTraverseObjectsInSelector
* @param {THING.BaseObject} object 对象。
* @param {number} index 对象的索引。
* @param {Array<THING.BaseObject>} objects 对象数组。
*/
/**
* 遍历对象。
* @param {OnTraverseObjectsInSelector} callback 回调函数(返回false表示中断遍历,否则继续处理)。
* @example
* // 遍历选择器中的所有对象
* selector.forEach((object) => {
* console.log(object);
* });
* @public
*/
forEach(callback) {
let objects = this._objects;
for (let i = 0, l = objects.length; i < l; i++) {
callback(objects[i], i, objects);
}
}
/**
* 异步模式下遍历对象。
* @param {OnTraverseObjectsInSelector} callback 回调函数(返回false表示中断遍历,否则继续处理)。
* @example
* // 对象逐个飞行(会等待前一个对象飞行完成后再开始)
* selector.forEachAsync(object => {
* return THING.App.current.camera.flyToAsync({
* target: object,
* duration: 1 * 1000,
* distance: 10,
* delayTime: THING.Math.randomFloat(0, 1000),
* complete: function (ev) {
* }
* });
* });
* @public
*/
forEachAsync(callback) {
this._objects.forEachAsync(callback);
}
/**
* Traverse objects in async mode.
* @param {OnTraverseObjectsInSelector} callback The callback function(returns false indicates break it, otherwise continue to process it).
* @deprecated 2.7
* @private
* @example
* selector.forEachSync(object => {
* console.log(object);
* });
*/
forEachSync(callback) {
this.forEachAsync(callback);
}
/**
* Traverse self and all children.
* @param {Function} callback The callback function.
* @example
* selector.traverse(object =>{
* console.log(object)
* });
* @private
*/
traverse(callback) {
var objects = this._objects;
for (let i = 0, l = objects.length; i < l; i++) {
objects[i].traverse(callback);
}
}
/**
* Traverse self and all children. (Support for exit at traverse runtime)
* @param {Function} callback The callback function. (Return false to exit)
* @example
* selector.traverseBranch(object =>{
* if(object.children.length){
* return false;
* }
* return true;
* })
* @private
*/
traverseBranch(callback) {
for (let i = 0, l = this._objects.length; i < l; i++) {
const isBranch = this._objects[i].traverseBranch(callback);
if (!isBranch) {
return;
}
}
}
/**
* unique the selector. process the repeat and inherit object
* @private
* @example
* selector.uniqueObjects();
*/
unique() {
this._objects = Utils.uniqueObjects(this._objects);
}
/**
* The function to call when to get objects by map.
* @callback OnMapObjectsInSelector
* @param {THING.BaseObject} object The object.
* @param {number} index The index of objects.
* @param {Array<THING.BaseObject>} objects The objects.
* @returns {THING.BaseObject}
*/
/**
* 遍历对象并返回一个新的选择器。
* @param {OnMapObjectsInSelector} callback 回调函数。
* @returns {THING.Selector} 返回包含结果的选择器实例。
* @example
* // 返回每个对象的第一个子对象
* let objects = selector.map((object) => {
* return object.children[0];
* });
* @public
*/
map(callback) {
if (!callback) {
return null;
}
let objects = this._objects.map((object, i) => {
return callback(object, i, this._objects);
}).filter(object => {
return !!object;
});
return this.onCreateInstance({ objects });
}
/**
* some 检查是否至少有一个对象满足提供的条件。
* @param {Function} callback 回调函数。
* @returns {boolean} 如果至少有一个对象满足条件,则返回true,否则返回false。
* @example
* selector.some(object => {
* return object.isVisible; // 检查是否至少有一个对象可见
* });
* @public
*/
some(callback) {
return this._objects.some((object, index) => {
return callback(object, index, this._objects);
});
}
/**
* @callback OnReduceObjectsInSelector
* @param {*} accumulator 累加器。
* @param {THING.BaseObject} object 对象。
* @param {number} index 索引。
* @param {Array<THING.BaseObject>} objects 对象数组。
* @returns {*} 新的累加值。
*/
/**
* 在选择器中执行用户提供的"reducer"回调函数,按顺序对数组的每个元素进行计算,并传入前一个元素的计算结果。
* 在数组的所有元素上运行reducer的最终结果是一个单一的值。
* @param {OnReduceObjectsInSelector} callback 回调函数。
* @param {number} initialValue 初始值。
* @returns {number} 返回计算结果。
* @example
* // 获取实体的平均高度
* let entities = app.query('.Entity');
* let height = entities.reduce((value, entity) => {
* return value + entity.position[1];
* }, 0) / entities.length;
* @public
*/
reduce(callback, initialValue = 0) {
if (!callback) {
return 0;
}
return this._objects.reduce((accumulator, object, currentIndex) => {
return callback(accumulator, object, currentIndex, this._objects);
}, initialValue);
}
/**
* 返回指定范围内的元素。
* @param {number} start 起始索引。
* @param {number} end 结束索引。
* @returns {THING.Selector} 返回一个新的选择器,包含指定范围内的元素。
* @example
* // 返回 [1, 4] 范围内的对象
* let objects1 = selector.slice(1, 4);
*
* // 返回 [1, ...] 范围内的对象
* let objects2 = selector.slice(1);
* @public
*/
slice(start, end) {
return this.onCreateInstance({ objects: this._objects.slice(start, end) });
}
/**
* 用于筛选对象的回调函数。
* @callback OnFilterObjectsInSelector
* @param {THING.BaseObject} object 对象。
* @param {number} index 对象的索引。
* @param {Array<THING.BaseObject>} objects 对象数组。
* @returns {boolean}
* @public
*/
/**
* 筛选对象。
* @param {OnFilterObjectsInSelector} callback 回调函数。
* @returns {THING.Selector} 返回一个新的选择器,包含通过回调函数筛选后的对象。
* @example
* // 根据名称筛选对象
* let objects = selector.filter((object) => {
* return !!object.name;
* });
* @public
*/
filter(callback) {
if (!callback) {
return null;
}
let objects = this._objects.filter((object, i) => {
return callback(object, i, this._objects);
});
return this.onCreateInstance({ objects });
}
/**
* 用于排序对象的回调函数。
* @callback OnSortObjectsInSelector
* @param {THING.BaseObject} obj1 第一个对象。
* @param {THING.BaseObject} obj2 第二个对象。
* @returns {number}
* < 0: obj1 < obj2
* == 0: obj1 == obj2
* > 0: obj1 > obj2
* @public
*/
/**
* 按照回调函数的结果,从低到高对对象进行排序。
* @param {OnSortObjectsInSelector} callback 用于排序的回调函数。
* @returns {THING.Selector} 返回排序后的选择器实例。
* @example
* // 按照名称对对象进行排序
* selector.sort((obj1, obj2) => {
* return obj1.name.localeCompare(obj2.name);
* })
* @public
*/
sort(callback) {
let objects = this._objects;
objects.sort(callback);
return this;
}
/**
* 转换为数组(返回一个新的数组)。
* @returns {Array<THING.BaseObject>} 返回一个包含选择器中所有对象的新数组。
* @example
* // 获取/转换为数组模式的对象
* let objects = selector.objects;
* console.log(objects);
* @public
*/
toArray() {
return this._objects.slice(0);
}
/**
* 检查是否包含指定元素。
* @param {object} object 对象。
* @returns {boolean} 如果选择器包含指定对象,则返回true,否则返回false。
* @example
* // 检查是否包含指定对象
* let exists = selector.includes(object);
* @public
*/
includes(object) {
return this._objects.includes(object);
}
/**
* 获取对象在数组中的索引。
* @param {object} object 对象。
* @returns {number} 对象在选择器中的索引,如果对象不存在,则返回-1。
* @example
* // 获取对象在选择器中的索引
* let index = selector.indexOf(object);
* @public
*/
indexOf(object) {
return this._objects.indexOf(object);
}
/**
* 删除对象。
* @param {number} index 起始索引。
* @param {number} number 要删除的对象数量。
* @returns {THING.Selector} 被删除的对象。
* @example
* // 删除 [1, 4] 范围内的对象
* let removeObjects = selector.splice(1, 4);
* @public
*/
splice(index, number) {
return this.onCreateInstance({ objects: this._objects.splice(...arguments) });
}
/**
* 按索引插入对象。
* @param {number} index 要插入的索引。
* @param {THING.BaseObject} object 要插入的对象。
* @returns {THING.Selector} 返回当前选择器实例,用于链式调用。
* @example
* // 在选择器的开头插入对象
* selector.insert(0, [obj1, obj2]);
* @public
*/
insert(index, object) {
this._objects.splice(index, 0, object);
return this;
}
/**
* 移除指定索引处的对象。
* @param {number} index 要移除的对象的索引。
* @returns {THING.Selector} 返回当前选择器实例,用于链式调用。
* @example
* // 移除选择器中的第一个对象。
* selector.removeAt(0);
* @public
*/
removeAt(index) {
this._objects.splice(index, 1);
return this;
}
/**
* 交换指定索引处的对象。
* @param {number} index0 第一个对象的索引。
* @param {number} index1 第二个对象的索引。
* @returns {THING.Selector} 返回当前选择器实例,用于链式调用。
* @example
* // 交换选择器中索引为0和索引为3的对象。
* selector.swap(0, 3);
* @public
*/
swap(index0, index1) {
this._objects._swap(index0, index1);
return this;
}
/**
* 合并数组。
* @param {THING.Selector|Array<THING.BaseObject>} selector 对象数组或选择器。
* @returns {THING.Selector} 返回一个新的选择器实例,包含合并后的对象。
* @example
* // 将其他对象/选择器与当前选择器合并并创建新的选择器。
* let newSelector = selector.concat([obj1, obj2]);
* @public
*/
concat(selector) {
// 将对象和自身合并到新的选择器中
if (selector) {
let candidates = this._objects.concat(selector.isSelector ? selector.objects : selector);
return this.onCreateInstance({ objects: Array.from(new Set(candidates)) });
}
// 返回自身作为新的选择器
else {
return this.onCreateInstance({ objects: this._objects.slice() });
}
}
/**
* 反转对象。
* @returns {THING.Selector} 返回当前选择器实例,用于链式调用。
* @example
* // 反转选择器中的对象。
* selector.reverse();
* @public
*/
reverse() {
this._objects.reverse();
return this;
}
/**
* 检查是否包含指定对象。
* @param {object} object 对象。
* @returns {boolean} 如果选择器包含指定对象,则返回true,否则返回false。
* @example
* // 检查选择器是否包含指定对象。
* let exists = selector.has(object);
* @public
*/
has(object) {
return this._objects.indexOf(object) !== -1;
}
/**
* 检查是否为相同的选择器。
* @param {Array<object>|THING.Selector} objects 对象数组或选择器。
* @returns {boolean} 如果当前选择器与传入的对象数组或选择器相同,则返回true,否则返回false。
* @example
* let isSame = selector.equals([obj1, obj2]);
* @public
*/
equals(objects) {
if (this.length != objects.length) {
return false;
}
for (let i = 0; i < this.length; i++) {
if (this[i] != objects[i]) {
return false;
}
}
return true;
}
/**
* 获取/设置对象的数量。
* @type {number}
* @example
* let length = selector.length;
* @public
*/
get length() {
return this._objects.length;
}
set length(value) {
this._objects.length = value;
}
/**
* Get the objects.
* @type {Array<THING.BaseObject>}
* @example
* let objects = selector.objects;
* @private
*/
get objects() {
return this._objects;
}
// #endregion
// #region Objects State Operations
/**
* 获取已加载的对象。
* @returns {Array<THING.BaseObject>} 已加载的对象数组。
* @private
* @example
* let loadedObjects = selector.getLoadedObjects();
*/
getLoadedObjects() {
return this._objects.filter(object => {
return object.loaded;
});
}
/**
* 获取未加载的对象。
* @returns {Array<THING.BaseObject>} 未加载的对象数组。
* @private
* @example
* let unloadedObjects = selector.getUnloadedObjects();
*/
getUnloadedObjects() {
return this._objects.filter(object => {
return !object.loaded;
});
}
/**
* 获取可见的对象。
* @returns {Array<THING.BaseObject>} 可见的对象数组。
* @private
* @example
* let visibleObjects = selector.getVisibleObjects();
*/
getVisibleObjects() {
return this._objects.filter(object => {
return object.visible;
});
}
/**
* 获取不可见的对象。
* @returns {Array<THING.BaseObject>} 不可见的对象数组。
* @private
* @example
* let invisibleObjects = selector.getInvisibleObjects();
*/
getInvisibleObjects() {
return this._objects.filter(object => {
return !object.visible;
});
}
/**
* 将对象设置为实例化绘制模式。
* @param {boolean} value 如果为true,则启用实例化绘制模式。
* @param {object} options 选项。
* @example
* // 启用对象的实例化绘制
* selector.makeInstancedDrawing(true);
*
* // 禁用对象的实例化绘制
* selector.makeInstancedDrawing(false);
* @public
*/
makeInstancedDrawing(value, options) {
this._objects.forEach(object => {
if (!object.isObject3D) {
return;
}
object.makeInstancedDrawing(value, options);
});
}
/**
* 获取启用实例化绘制的对象。
* @returns {Array<THING.BaseObject>} 启用实例化绘制的对象数组。
* @example
* // 获取启用实例化绘制的对象
* let instancedDrawingObjects = selector.getInstancedDrawingObjects();
* @public
*/
getInstancedDrawingObjects() {
return this._objects.filter(object => {
return object.isInstancedDrawing;
});
}
/**
* 设置可见状态。
* @param {boolean} value 如果为true,则显示,否则隐藏。
* @param {boolean} [recursive=false] 如果为true,则对所有子对象进行处理。
* @example
* // 将选择器中所有对象的可见属性设置为true
* selector.setVisible(true);
*
* // 将选择器中所有对象及其子对象的可见属性设置为true
* selector.setVisible(true, true);
* @public
*/
setVisible(value, recursive = false) {
this._objects.forEach(object => {
object.setVisible(value, recursive);
});
}
/**
* 设置所有对象及其子对象的可见状态。
* @type {boolean}
* @example
* // 将选择器中所有对象及其子对象的可见属性设置为true
* selector.visible = true;
* @public
*/
set visible(value) {
this._objects.forEach(object => {
object.visible = value;
});
}
/**
* 设置所有对象及其子对象的可选状态。
* @type {boolean}
* @example
* // 将选择器中所有对象及其子对象的可选属性设置为true
* selector.pickable = true;
* @public
*/
set pickable(value) {
this._objects.forEach(object => {
object.pickable = value;
});
}
/**
* 设置所有对象及其子对象的激活状态。
* @type {boolean}
* @example
* // 将选择器中所有对象及其子对象的激活状态设置为true
* selector.active = true;
* @public
*/
set active(value) {
this._objects.forEach(object => {
object.active = value;
})
}
// #endregion
// #region Objects Resource Operations
/**
* 等待所有对象加载完成。
* @returns {Promise<any>} 返回一个Promise,resolve时传入已加载完成的对象数组。
* @example
* // 等待所有对象加载完成,然后打印它们的名称
* selector.waitForComplete().then((objects) => {
* objects.forEach(object => {
* console.log(object.name);
* });
* });
* @public
*/
waitForComplete() {
return this._objects.waitForComplete();
}
/**
* 按顺序等待所有对象的同步操作完成。
* @param {Function} callback 对每个对象执行的回调函数。
* @returns {Promise<any>} 返回一个Promise,resolve时传入已完成同步操作的对象数组。
* @example
* // 按顺序加载每个对象的资源
* selector.waitForEachAsync(async (object) => {
* return object.loadResource();
* });
* @private
*/
waitForEachAsync(callback) {
return this._objects.waitForEachAsync(callback);
}
/**
* 按顺序等待所有对象的同步操作完成。
* @param {Function} callback 对每个对象执行的回调函数。
* @returns {Promise<any>} 返回一个Promise,resolve时传入已完成同步操作的对象数组。
* @deprecated 2.7
* @private
* @example
* // 按顺序加载每个对象的资源
* selector.waitForEachSync(async (object) => {
* return object.loadResource();
* });
*/
waitForEachSync(callback) {
return this.waitForEachAsync(callback);
}
/**
* 销毁所有对象。
* @example
* selector.destroy();
* @public
*/
destroy() {
let objects = this._objects.slice(0);
objects.forEach(object => {
object.destroy();
});
}
/**
* 销毁所有对象 (兼容1.0)。
* @example
* selector.destroyAll();
* @private
*/
destroyAll() {
this.destroy();
}
/**
* 加载资源。
* @param {object} options 加载选项。
* @example
* selector.loadResource();
* @public
*/
loadResource(options) {
this._objects.forEach(object => {
object.loadResource(false, options);
});
}
/**
* 卸载资源。
* @example
* selector.unloadResource();
* @public
*/
unloadResource() {
this._objects.forEach(object => {
object.unloadResource();
});
}
// #endregion
// #region 对象事件操作
/**
* 注册所有对象事件。
* @param {string} type 事件类型。
* @param {string} condition 选择对象的条件。
* @param {Function} callback 回调函数。
* @param {string} tag 事件标签。
* @param {number} priority 优先级值(默认为0,值越高越先处理)。
* @param {ObjectEventOptions} options 选项。
* @example
* // 注册所有实体的'click'事件监听器
* let mark = 0;
* let entity = new THING.Entity();
* entity.on('test', ()=> {
* mark = 1;
* })
* let selector = new THING.Selector([entity]);
* let markOn = 0;
* selector.on('testOn', function(ev) {
* markOn = 1;
* });
* selector.trigger('testOn');
* // @expect(markOn == 1 && mark == 1);
* @public
*/
on(type, condition, callback, tag, priority, options) {
this.applyObjects('on', arguments);
}
/**
* 注册所有对象事件,只触发一次。
* @param {string} type 事件类型。
* @param {string} condition 选择对象的条件。
* @param {Function} callback 回调函数。
* @param {string} tag 事件标签。
* @param {number} priority 优先级值(默认为0,值越高越先处理)。
* @param {ObjectEventOptions} options 选项。
* @example
* // 注册所有实体的'click'事件监听器
* let mark = 0;
* let entity = new THING.Entity();
* entity.on('test', ()=> {
* mark = 1;
* })
* let selector = new THING.Selector([entity]);
* let markOnce = 0;
* selector.once('testOnce', function(ev) {
* markOnce = 1;
* });
* selector.trigger('testOnce');
* // @expect(markOnce == 1 && mark == 1);
* markOnce = 0;
* mark = 0;
* selector.trigger('testOnce');
* // @expect(markOnce == 0 && mark == 0);
* @public
*/
once(type, condition, callback, tag, priority, options) {
this.applyObjects('once', arguments);
}
/**
* 注销所有对象事件。
* @param {string} type 事件类型。
* @param {string} condition 选择对象的条件。
* @param {string} tag 事件标签。
* @example
* // 通过标签名注销所有具有'click'事件监听器的实体
* let mark = 0;
* let entity = new THING.Entity();
* entity.on('test', ()=> {
* mark = 1;
* })
* let selector = new THING.Selector([entity]);
* let markOff = 0;
* selector.on('testOff', function(ev) {
* markOff = 1;
* });
* selector.trigger('testOff');
* // @expect(markOff == 1 && mark == 1);
* markOff = 0;
* mark = 0;
* selector.off('testOff');
* selector.trigger('testOff');
* // @expect(markOff == 0 && mark == 0);
* @public
*/
off(type, condition, tag) {
this.applyObjects('off', arguments);
}
/**
* 暂停所有对象的事件。
* @param {string} type 事件类型。
* @param {string} condition 选择对象的条件。
* @param {string} tag 事件标签。
* @example
* // 通过标签名暂停所有具有'click'事件监听器的实体
* let mark = 0;
* let entity = new THING.Entity();
* entity.on('test', ()=> {
* mark = 1;
* })
* let selector = new THING.Selector([entity]);
* let markPause = 0;
* selector.on('testPause', function(ev) {
* markPause = 1;
* });
* selector.trigger('testPause');
* // @expect(markPause == 1 && mark == 1);
* markPause = 0;
* mark = 0;
* selector.PauseEvent('testPause');
* selector.trigger('testPause');
* // @expect(markPause == 0 && mark == 0);
* @public
*/
pauseEvent(type, condition, tag) {
this.applyObjects('pauseEvent', arguments);
}
/**
* 恢复所有对象的事件。
* @param {string} type 事件类型。
* @param {string} condition 选择对象的条件。
* @param {string} tag 事件标签。
* @example
* // 通过标签名恢复所有具有'click'事件监听器的实体
* let mark = 0;
* let entity = new THING.Entity();
* entity.on('test', ()=> {
* mark = 1;
* })
* let selector = new THING.Selector([entity]);
* let markResume = 0;
* selector.on('testPause', function(ev) {
* markResume = 1;
* });
* selector.trigger('testPause');
* // @expect(markResume == 1 && mark == 1);
* markResume = 0;
* mark = 0;
* selector.PauseEvent('testPause');
* selector.trigger('testPause');
* // @expect(markResume == 0 && mark == 0);
* selector.resumeEvent('testPause');
* selector.trigger('testPause');
* // @expect(markResume == 1 && mark == 1);
* @public
*/
resumeEvent(type, condition, tag) {
this.applyObjects('resumeEvent', arguments);
}
/**
* 触发所有对象的事件。
* @param {string} type 事件类型。
* @param {object} ev 事件信息。
* @param {object} options 选项。
* @param {string} options.tag 标签名称。
* @example
* // 通过标签名触发所有具有'click'事件监听器的实体
* let mark = 0;
* let entity = new THING.Entity();
* entity.on('test', ()=> {
* mark = 1;
* })
* let selector = new THING.Selector([entity]);
* selector.trigger('test');
* // @expect(mark == 1)
* @public
*/
trigger(type, ev, options) {
this._objects.forEach(object => {
object.trigger(type, Utils.cloneObject(ev), options?.tag);
});
}
/**
* 触发所有对象的事件。
* @param {string} type 事件类型。
* @param {object} ev 事件信息。
* @param {object} options 选项。
* @param {string} options.tag 标签名称。
* @returns {Promise<any>} 返回一个Promise对象,包含所有对象的事件触发结果。
* @example
* let entity = new THING.Entity();
* let selector = new THING.Selector([entity]);
* let mark = 0;
* selector.on('testInvoke', function(ev) {
* mark = ev.value;
* });
* selector.invoke('testInvoke', { value: 5 });
* // @expect(mark == 5);
* @public
*/
invoke(type, ev, options) {
return Promise.all(this._objects.map(object => {
return object.invoke(type, Utils.cloneObject(ev), options?.tag);
}));
}
// #endregion
// #region Objects Lerp Operations
stopMoving() {
this.applyObjects('stopMoving', arguments);
}
moveTo() {
this.applyObjects('moveTo', arguments);
}
movePath() {
this.applyObjects('movePath', arguments);
}
stopRotating() {
this.applyObjects('stopRotating', arguments);
}
rotateTo() {
this.applyObjects('rotateTo', arguments);
}
stopScaling() {
this.applyObjects('stopScaling', arguments);
}
scaleTo() {
this.applyObjects('scaleTo', arguments);
}
stopFading() {
this.applyObjects('stopFading', arguments);
}
fadeIn() {
this.applyObjects('fadeIn', arguments);
}
fadeOut() {
this.applyObjects('fadeOut', arguments);
}
stopAnimation() {
this.applyObjects('stopAnimation', arguments);
}
stopAllAnimations() {
this.applyObjects('stopAllAnimations', arguments);
}
playAnimation() {
this.applyObjects('playAnimation', arguments);
}
// #endregion
// #region Objects Component Operations
addComponent() {
if (!Utils.isClass(arguments[0])) {
Utils.error('Please enter a class!');
return;
}
this.applyObjects('addComponent', arguments);
}
removeComponent() {
this.applyObjects('removeComponent', arguments);
}
// #endregion
// #region Objects Wrapper Operations
get style() {
return _styleSelector.init(this._objects);
}
get inherit() {
return _inheritSelector.init(this._objects);
}
// #endregion
// #region Objects Bounding Operations
/**
* 获取轴对齐的边界框(AABB)。
* @returns {BASE.THING.Box3} 当前选择器内所有对象合并后的 AABB。
* @example
* let selector = new THING.Selector();
* let box = selector.getBoundingBox();
* let ret = box.size[0] == 0;
* // @expect(ret == true);
* @public
*/
getBoundingBox() {
let aabb = new Box3();
this._objects.forEach(object => {
let boundingBox = object.boundingBox;
aabb.expandByPoint(boundingBox.min);
aabb.expandByPoint(boundingBox.max);
});
return aabb;
}
// #endregion
/**
* Check class type.
* @type {boolean}
* @example
* let selector = new THING.Selector();
* // @expect(selector.isSelector == true);
*/
get isSelector() {
return true;
}
}
// #region Static Interface
let _selector = new Selector();
Selector.init = function (param = {}) {
let workerUrl = param['workerUrl'];
if (workerUrl) {
Selector.asyncSelector = new AsyncSelector({ workerUrl });
}
}
Selector.buildExpression = function (condition, mode) {
return new ObjectExpression(condition, mode);
}
Selector.testByExpression = function (expression, object) {
return expression.evaluate(object);
}
Selector.testByRegExpName = function (condition, object) {
return condition.test(object.name);
}
Selector.testByFunction = function (condition, object) {
return condition(object);
}
Selector.test = function (condition, object) {
return _selector.test(condition, object);
}
Selector.select = function (condition, objects) {
return _selector.select(condition, objects);
}
Selector.query = function (condition, objects) {
return _selector.query(condition, objects);
}
Selector.queryAsync = function (condition, root, recursive) {
return _selector.queryAsync(condition, root, recursive);
}
Selector.updateObjectAttribute = function (object, key, value) {
if (Selector.asyncSelector) {
Selector.asyncSelector.updateObjectAttribute(object, key, value);
}
}
Selector.addObject = function (object) {
if (Selector.asyncSelector) {
Selector.asyncSelector.addObject(object);
}
}
Selector.removeObject = function (object) {
if (Selector.asyncSelector) {
Selector.asyncSelector.removeObject(object);
}
}
Selector.updateObjectAttribute = function (object, key, value) {
if (Selector.asyncSelector) {
Selector.asyncSelector.updateObjectAttribute(object, key, value);
}
}
// #endregion
export { Selector };