import { Utils } from '../common/Utils';
import { MathUtils } from '../math/MathUtils';
import { RelationshipDirection } from '../const';
import { Selector } from '../selector/Selector';
const __ = {
private: Symbol('private'),
}
/**
* @class Relationship
* The Relationship between objects.
* @memberof THING
* @public
*/
class Relationship {
/**
* The relationship between objects.
* @param {Object} param
* @param {String} param.type The relationship type.
* @param {String} param.name The relationship name.
* @param {THING.BaseObject|Array<THING.BaseObject>|THING.Selector} param.source The source object(s) of relation.
* @param {THING.BaseObject|Array<THING.BaseObject>|THING.Selector} param.target The target object(s) of relation.
* @param {String} [param.queryDirection] The default query relationship direction.
*/
constructor(param = {}) {
this[__.private] = {};
let _private = this[__.private];
_private.app = Utils.getCurrentApp();
let source = this._checkObject(param['source']);
let target = this._checkObject(param['target']);
this._type = param['type'];
this._name = Utils.parseValue(param['name'], '');
this._source = source;
this._target = target;
this._uuid = param['uuid'] || MathUtils.generateUUID();
this._queryDirection = Utils.parseValue(param['queryDirection'], RelationshipDirection.Out);
_private.app.relationshipManager.addRelationship(this);
}
// #region Set
/**
* check object
* @private
*/
_checkObject(object) {
if (Utils.isNull(object)) {
return null;
}
else if (object.isSelector) {
return [...new Set(object.objects)];
}
else if (Utils.isArray(object)) {
return [...new Set(object)];
}
else if (object.isBaseObject) {
return object;
}
else {
Utils.error('The object is not an allowed value.');
return null;
}
}
// #endregion
// #region test
/**
* Test whether the relationship meets the condition.
* @param {Object} options test condition
* @returns {Boolean}
* @private
* @example
* app.queryRelationships().forEach((relstionship) => {
* relationship.test({ type: 'BelongTo' });
* });
*/
test(options = {}) {
let type = options['type'];
let name = options['name'];
if (type) {
if (name) {
return (this.type == type && this.name == name);
}
else {
return this.type == type;
}
}
else if (name) {
return this.name == name;
}
return false;
}
/**
* Query other objects in the relationship.
* @param {RelationshipDirection} queryDirection Query direction.
* @returns {THING.Selector}
* @private
* @example
* let lightSwitch = new Box();
* let light = new Box();
* let rel = new THING.Relationship({
* type: 'control',
* source: light,
* target: lightSwitch
* });
* app.queryRelationships().forEach((relstionship) => {
* relationship.queryObjects()
* });
*/
queryObjects(object, queryDirection) {
let target = this.target;
let source = this.source;
let objects = new Selector();
queryDirection = Utils.parseValue(queryDirection, this.queryDirection);
switch (queryDirection) {
case RelationshipDirection.Out:
if (this._includeObject(source, object)) {
objects.push(target);
}
break;
case RelationshipDirection.In:
if (this._includeObject(target, object)) {
objects.push(source);
}
break;
case RelationshipDirection.InOut:
if (this._includeObject(source, object)) {
objects.push(target);
}
if (this._includeObject(target, object)) {
objects.push(source);
}
break;
case RelationshipDirection.None:
if (this._includeObject(source, object)) {
objects.push(source);
}
if (this._includeObject(target, object)) {
objects.push(target);
}
break;
}
return objects;
}
_includeObject(object, target) {
if (Utils.isNull(object)) {
return false;
}
else if (Utils.isArray(object)) {
return object.includes(target);
}
else if (object.isBaseObject) {
return object == target;
}
}
// #endregion
// #region destroy
/**
* When destroy.
* @private
*/
onDestroy() {
let _private = this[__.private];
_private.app.relationshipManager.removeRelationship(this);
}
/**
* destroy relationship
* @public
* @example
* let source = new THING.Object3D()();
* let target = new THING.Object3D()();
* let rel = new THING.Relationship({
* type: 'control',
* source: source,
* target: target
* });
* rel.destroy()
* // @expect(source.relationships.length == 0)
*/
destroy() {
this.onDestroy();
this._type = null;
this._name = null;
this._uuid = null;
this._source = null;
this._target = null;
this._queryDirection = null;
}
/**
* object destroy release relationship
* @param {THING.BaseObject} object Remove the object from the relationship.
* @private
*/
releaseObject(object) {
let source = this.source;
let target = this.target;
if (object == target) {
this.destroy();
}
else if (Utils.isArray(target) && target.indexOf(object) !== -1) {
Utils.removeFromArray(target, object);
if (target.length == 0 || (target.length <= 1 && !source)) {
this.destroy();
}
}
else if (object == source) {
this.destroy();
}
else if (Utils.isArray(source) && source.indexOf(object) !== -1) {
Utils.removeFromArray(source, object);
if (source.length == 0 || (source.length <= 1 && !target)) {
this.destroy();
}
}
}
// #endregion
// #region Accessor
/**
* Get/Set the relationship type.
* @type {String}
* @example
* let source = new THING.Object3D()();
* let target = new THING.Object3D()();
* let rel = new THING.Relationship({
* type: 'control',
* source: source,
* target: target
* });
* // @expect(rel.type == 'control')
* @public
*/
get type() {
return this._type;
}
set type(value) {
this._type = value;
}
/**
* Get/Set the relationship name.
* @type {String}
* @example
* let source = new THING.Object3D()();
* let target = new THING.Object3D()();
* let rel = new THING.Relationship({
* type: 'control',
* name: 'myRelationship',
* source: source,
* target: target
* });
* // @expect(rel.name == 'myRelationship')
* @public
*/
get name() {
return this._name;
}
set name(value) {
this._name = value;
}
/**
* Get/Set uuid.
* @type {String}
* @example
* relationship.uuid = THING.Math.generateUUID();
* console.log(object.uuid);
*/
get uuid() {
return this._uuid;
}
set uuid(value) {
this._uuid = value;
}
/**
* Get/Set the relationship source object.
* @type {THING.BaseObject|Array<THING.BaseObject>|THING.Selector}
* @public
* @example
* let source = new THING.Object3D()();
* let target = new THING.Object3D()();
* let rel = new THING.Relationship({
* type: 'control',
* name: 'myRelationship',
* source: source,
* target: target
* });
* // @expect(rel.source == source)
*/
get source() {
return this._source;
}
set source(value) {
let _private = this[__.private];
value = this._checkObject(value);
_private.app.relationshipManager.notifyObjectRemoveRelationship(this._source, this);
_private.app.relationshipManager.notifyObjectAddRelationship(value, this);
this._source = value;
}
/**
* Get/Set the relationship target.
* @type {THING.BaseObject|Array<THING.BaseObject>|THING.Selector}
* @public
* @example
* let source = new THING.Object3D()();
* let target = new THING.Object3D()();
* let rel = new THING.Relationship({
* type: 'control',
* name: 'myRelationship',
* source: source,
* target: target
* });
* // @expect(rel.target == target)
*/
get target() {
return this._target;
}
set target(value) {
let _private = this[__.private];
value = this._checkObject(value);
_private.app.relationshipManager.notifyObjectRemoveRelationship(this._target, this);
_private.app.relationshipManager.notifyObjectAddRelationship(value, this);
this._target = value;
}
/**
* Get/Set the relationship default query direction.
* @type {RelationshipDirection}
* @public
* @example
* let source = new THING.Object3D()();
* let target = new THING.Object3D()();
* let rel = new THING.Relationship({
* type: 'control',
* name: 'myRelationship',
* source: source,
* target: target
* });
* // @expect(rel.queryDirection == THING.RelationshipDirection.OUT)
*/
get queryDirection() {
return this._queryDirection;
}
set queryDirection(value) {
this._queryDirection = value;
}
// #endregion
}
export { Relationship }