From db2c2e0b633994112278c7d568421fc0d5eabf8d Mon Sep 17 00:00:00 2001 From: NewboO Date: Wed, 31 Aug 2022 13:26:30 +0200 Subject: [PATCH 01/26] Add functions to find parents --- src/scene/graph-node.js | 115 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/src/scene/graph-node.js b/src/scene/graph-node.js index ceb1708604c..1d94d571fb0 100644 --- a/src/scene/graph-node.js +++ b/src/scene/graph-node.js @@ -589,6 +589,121 @@ class GraphNode extends EventHandler { return null; } + /** + * Search the graph node and all of its ascendants for the nodes that satisfy some search + * criteria. + * + * @param {FindNodeCallback|string} attr - This can either be a function or a string. If it's a + * function, it is executed for each ascendant node to test if node satisfies the search + * logic. Returning true from the function will include the node into the results. If it's a + * string then it represents the name of a field or a method of the node. If this is the name + * of a field then the value passed as the second argument will be checked for equality. If + * this is the name of a function then the return value of the function will be checked for + * equality against the valued passed as the second argument to this function. + * @param {object} [value] - If the first argument (attr) is a property name then this value + * will be checked against the value of the property. + * @returns {GraphNode[]} The array of graph nodes that match the search criteria. + * @example + * // Finds all nodes that have a group element component + * var groups = element.findInParent(function (node) { + * return node.element && node.element.type === pc.ELEMENTTYPE_GROUP; + * }); + * @example + * // Finds all nodes that have the name property set to 'Test' + * var entities = entity.findInParent('name', 'Test'); + */ + findInParent(attr, value) { + let result, results = []; + + if (attr instanceof Function) { + const fn = attr; + + result = fn(this); + if (result) + results.push(this); + + if(this._parent) { + results = results.concat(this._parent.findInParent(fn)); + } + } else { + let testValue; + + if (this[attr]) { + if (this[attr] instanceof Function) { + testValue = this[attr](); + } else { + testValue = this[attr]; + } + if (testValue === value) + results.push(this); + } + + if(this._parent) { + results = results.concat(this._parent.findInParent(attr, value)); + } + } + + return results; + } + + /** + * Search the graph node and all of its ascendants for the first node that satisfies some + * search criteria. + * + * @param {FindNodeCallback|string} attr - This can either be a function or a string. If it's a + * function, it is executed for each ascendant node to test if node satisfies the search + * logic. Returning true from the function will result in that node being returned from + * findOne. If it's a string then it represents the name of a field or a method of the node. If + * this is the name of a field then the value passed as the second argument will be checked for + * equality. If this is the name of a function then the return value of the function will be + * checked for equality against the valued passed as the second argument to this function. + * @param {object} [value] - If the first argument (attr) is a property name then this value + * will be checked against the value of the property. + * @returns {GraphNode|null} A graph node that match the search criteria. Returns null if no + * node is found. + * @example + * // Find the first node that is called `head` and has a model component + * var head = player.findOneInParent(function (node) { + * return node.model && node.name === 'head'; + * }); + * @example + * // Finds the first node that has the name property set to 'Test' + * var node = parent.findOneInParent('name', 'Test'); + */ + findOneInParent(attr, value) { + let result = null; + + if (attr instanceof Function) { + const fn = attr; + + result = fn(this); + if (result) + return this; + + if(this._parent) { + return this._parent.findOneInParent(fn); + } + } else { + let testValue; + if (this[attr]) { + if (this[attr] instanceof Function) { + testValue = this[attr](); + } else { + testValue = this[attr]; + } + if (testValue === value) { + return this; + } + } + + if(this._parent) { + return this._parent.findOneInParent(attr, value); + } + } + + return null; + } + /** * Return all graph nodes that satisfy the search query. Query can be simply a string, or comma * separated strings, to have inclusive results of assets that match at least one query. A From 60284132abb583236042d56e99037b838b55b1f5 Mon Sep 17 00:00:00 2001 From: NewboO Date: Wed, 31 Aug 2022 13:35:38 +0200 Subject: [PATCH 02/26] Add functions to find script instances and components --- src/framework/entity.js | 108 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/src/framework/entity.js b/src/framework/entity.js index 360f99c8bf5..d7fc64d2f84 100644 --- a/src/framework/entity.js +++ b/src/framework/entity.js @@ -408,6 +408,114 @@ class Entity extends GraphNode { }); } + /** + * Search the entity and all of its descendants for the first script instance of specified type. + * + * @param {string|Class} nameOrType - The name or type of {@link ScriptType}. + * @returns {ScriptType} A script instance of specified type, if the entity or any of its descendants + * has one. Returns undefined otherwise. + * @example + * // Get the first found "playerController" instance in the hierarchy tree that starts with this entity + * var controller = entity.findScript("playerController"); + */ + findScript(nameOrType) { + const entity = this.findOne(function (node) { + return node.c && node.c.script && node.c.script.has(nameOrType); + }); + return entity && entity.c.script.get(nameOrType); + } + + /** + * Search the entity and all of its descendants for all script instances of specified type. + * + * @param {string|Class} nameOrType - The name or type of {@link ScriptType}. + * @returns {ScriptType[]} All script instances of specified type in the entity or any of its + * descendants. Returns empty array if none found. + * @example + * // Get all "playerController" instances in the hierarchy tree that starts with this entity + * var controllers = entity.findScripts("playerController"); + */ + findScripts(nameOrType) { + const entities = this.find(function (node) { + return node.c && node.c.script && node.c.script.has(nameOrType); + }); + return entities.map(function (entity) { + return entity.c.script.get(nameOrType); + }); + } + + /** + * Search the entity and all of its ascendants for the first component of specified type. + * + * @param {string} type - The name of the component type to retrieve. + * @returns {Component} A component of specified type, if the entity or any of its ascendants + * has one. Returns undefined otherwise. + * @example + * // Get the first found light component in the ancestor tree that starts with this entity + * var light = entity.findComponentInParent("light"); + */ + findComponentInParent(type) { + const entity = this.findOneInParent(function (node) { + return node.c && node.c[type]; + }); + return entity && entity.c[type]; + } + + /** + * Search the entity and all of its ascendants for all components of specified type. + * + * @param {string} type - The name of the component type to retrieve. + * @returns {Component[]} All components of specified type in the entity or any of its + * ascendants. Returns empty array if none found. + * @example + * // Get all element components in the ancestor tree that starts with this entity + * var elements = entity.findComponentsInParent("element"); + */ + findComponentsInParent(type) { + const entities = this.findInParent(function (node) { + return node.c && node.c[type]; + }); + return entities.map(function (entity) { + return entity.c[type]; + }); + } + + /** + * Search the entity and all of its ascendants for the first script instance of specified type. + * + * @param {string|Class} nameOrType - The name or type of {@link ScriptType}. + * @returns {ScriptType} A script instance of specified type, if the entity or any of its ascendants + * has one. Returns undefined otherwise. + * @example + * // Get the first found "playerController" instance in the ancestor tree that starts with this entity + * var controller = entity.findScriptInParent("playerController"); + */ + findScriptInParent(nameOrType) { + const entity = this.findOneInParent(function (node) { + return node.c && node.c.script && node.c.script.has(nameOrType); + }); + return entity && entity.c.script.get(nameOrType); + } + + /** + * Search the entity and all of its ascendants for all script instances of specified type. + * + * @param {string|Class} nameOrType - The name or type of {@link ScriptType}. + * @returns {ScriptType[]} All script instances of specified type in the entity or any of its + * ascendants. Returns empty array if none found. + * @example + * // Get all "playerController" instance in the ancestor tree that starts with this entity + * var controllers = entity.findScriptsInParent("playerController"); + */ + findScriptsInParent(nameOrType) { + const entities = this.findInParent(function (node) { + return node.c && node.c.script && node.c.script.has(nameOrType); + }); + return entities.map(function (entity) { + return entity.c.script.get(nameOrType); + }); + } + /** * Get the GUID value for this Entity. * From d0722b91a02c81cf9f714526b81acdcce2f9c030 Mon Sep 17 00:00:00 2001 From: NewboO Date: Wed, 31 Aug 2022 13:37:18 +0200 Subject: [PATCH 03/26] Unit test for new graph node functions --- test/scene/graph-node.test.mjs | 88 ++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/test/scene/graph-node.test.mjs b/test/scene/graph-node.test.mjs index e30faa0612d..02b34863780 100644 --- a/test/scene/graph-node.test.mjs +++ b/test/scene/graph-node.test.mjs @@ -413,6 +413,94 @@ describe('GraphNode', function () { }); + describe('#findInParent()', function () { + + it('finds a node by property', function () { + const root = new GraphNode('Parent'); + const child = new GraphNode('Child'); + root.addChild(child); + + let res; + res = child.findInParent('name', 'Parent'); + expect(res).to.be.an('array').with.lengthOf(1); + expect(res[0]).to.equal(root); + + res = child.findInParent('name', 'Child'); + expect(res).to.be.an('array').with.lengthOf(1); + expect(res[0]).to.equal(child); + + res = child.findInParent('name', 'Not Found'); + expect(res).to.be.an('array').with.lengthOf(0); + }); + + it('finds a node by filter function', function () { + const root = new GraphNode('Parent'); + const child = new GraphNode('Child'); + root.addChild(child); + + let res; + res = child.findInParent(function (node) { + return node.name === 'Parent'; + }); + expect(res).to.be.an('array').with.lengthOf(1); + expect(res[0]).to.equal(root); + + res = child.findInParent(function (node) { + return node.name === 'Child'; + }); + expect(res).to.be.an('array').with.lengthOf(1); + expect(res[0]).to.equal(child); + + res = child.findInParent(function (node) { + return node.name === 'Not Found'; + }); + expect(res).to.be.an('array').with.lengthOf(0); + }); + + }); + + describe('#findOneInParent()', function () { + + it('finds a node by property', function () { + const root = new GraphNode('Parent'); + const child = new GraphNode('Child'); + root.addChild(child); + + let res; + res = child.findOneInParent('name', 'Parent'); + expect(res).to.equal(root); + + res = child.findOneInParent('name', 'Child'); + expect(res).to.equal(child); + + res = child.findOneInParent('name', 'Not Found'); + expect(res).to.be.null; + }); + + it('finds a node by filter function', function () { + const root = new GraphNode('Parent'); + const child = new GraphNode('Child'); + root.addChild(child); + + let res; + res = child.findOneInParent(function (node) { + return node.name === 'Parent'; + }); + expect(res).to.equal(root); + + res = child.findOneInParent(function (node) { + return node.name === 'Child'; + }); + expect(res).to.equal(child); + + res = child.findOneInParent(function (node) { + return node.name === 'Not Found'; + }); + expect(res).to.be.null; + }); + + }); + describe('#forEach()', function () { it('iterates over all nodes', function () { From 9a6cbbeb3edff2ceacc94b6097abf99b5a6804fd Mon Sep 17 00:00:00 2001 From: NewboO Date: Wed, 31 Aug 2022 14:28:11 +0200 Subject: [PATCH 04/26] Unit test for new find component functions --- test/framework/entity.test.mjs | 106 +++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/test/framework/entity.test.mjs b/test/framework/entity.test.mjs index 30dac412465..fb2951ba9a0 100644 --- a/test/framework/entity.test.mjs +++ b/test/framework/entity.test.mjs @@ -675,6 +675,112 @@ describe('Entity', function () { }); + describe('#findComponentInParent', function () { + + it('finds component on single entity', function () { + const e = new Entity(); + e.addComponent('anim'); + const component = e.findComponentInParent('anim'); + expect(component).to.be.an.instanceof(AnimComponent); + }); + + it('returns null when component is not found', function () { + const e = new Entity(); + e.addComponent('anim'); + const component = e.findComponentInParent('render'); + expect(component).to.be.null; + }); + + it('finds component on parent entity', function () { + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + root.addComponent('anim'); + const component = child.findComponentInParent('anim'); + expect(component).to.be.an.instanceof(AnimComponent); + }); + + it('finds component on grandparent entity', function () { + const root = new Entity(); + const child = new Entity(); + const grandchild = new Entity(); + root.addChild(child); + child.addChild(grandchild); + root.addComponent('anim'); + const component = grandchild.findComponentInParent('anim'); + expect(component).to.be.an.instanceof(AnimComponent); + }); + + it('does not find component on child entity', function () { + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + child.addComponent('anim'); + const component = root.findComponentInParent('anim'); + expect(component).to.be.null; + }); + + }); + + describe('#findComponentsInParent', function () { + + it('finds components on single entity', function () { + const e = new Entity(); + e.addComponent('anim'); + const components = e.findComponentsInParent('anim'); + expect(components).to.be.an('array'); + expect(components.length).to.equal(1); + expect(components[0]).to.be.an.instanceof(AnimComponent); + }); + + it('returns empty array when no components are found', function () { + const e = new Entity(); + e.addComponent('anim'); + const components = e.findComponentsInParent('render'); + expect(components).to.be.an('array'); + expect(components.length).to.equal(0); + }); + + it('finds components on parent entity', function () { + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + root.addComponent('anim'); + const components = child.findComponentsInParent('anim'); + expect(components).to.be.an('array'); + expect(components.length).to.equal(1); + expect(components[0]).to.be.an.instanceof(AnimComponent); + }); + + it('finds components on 3 entity hierarchy', function () { + const root = new Entity(); + const child = new Entity(); + const grandchild = new Entity(); + root.addChild(child); + child.addChild(grandchild); + root.addComponent('anim'); + child.addComponent('anim'); + grandchild.addComponent('anim'); + const components = grandchild.findComponentsInParent('anim'); + expect(components).to.be.an('array'); + expect(components.length).to.equal(3); + expect(components[0]).to.be.an.instanceof(AnimComponent); + expect(components[1]).to.be.an.instanceof(AnimComponent); + expect(components[2]).to.be.an.instanceof(AnimComponent); + }); + + it('does not find components on child entity', function () { + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + child.addComponent('anim'); + const components = root.findComponentsInParent('anim'); + expect(components).to.be.an('array'); + expect(components.length).to.equal(0); + }); + + }); + describe('#removeComponent', function () { it('removes a component from the entity', function () { From 6b3ece6c397546339c8a16b07fbc4872387f7ec1 Mon Sep 17 00:00:00 2001 From: NewboO Date: Wed, 31 Aug 2022 14:50:17 +0200 Subject: [PATCH 05/26] Unit test for new find script functions --- test/framework/entity.test.mjs | 282 +++++++++++++++++++++++++++++++++ 1 file changed, 282 insertions(+) diff --git a/test/framework/entity.test.mjs b/test/framework/entity.test.mjs index fb2951ba9a0..37fcc60d0fa 100644 --- a/test/framework/entity.test.mjs +++ b/test/framework/entity.test.mjs @@ -675,6 +675,147 @@ describe('Entity', function () { }); + describe('#findScript', function () { + + it('finds script on single entity', function () { + const MyScript = createScript('myScript'); + const e = new Entity(); + e.addComponent('script'); + e.script.create('myScript'); + const script = e.findScript('myScript'); + expect(script).to.be.an.instanceof(MyScript); + }); + + it('returns null when script is not found', function () { + const MyScript = createScript('myScript'); + const e = new Entity(); + e.addComponent('script'); + const script = e.findScript('myScript'); + expect(script).to.be.null; + }); + + it('returns null when script component is not found', function () { + const MyScript = createScript('myScript'); + const e = new Entity(); + const script = e.findScript('myScript'); + expect(script).to.be.null; + }); + + it('finds script on child entity', function () { + const MyScript = createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + child.addComponent('script'); + child.script.create('myScript'); + const script = root.findScript('myScript'); + expect(script).to.be.an.instanceof(MyScript); + }); + + it('finds script on grandchild entity', function () { + const MyScript = createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + const grandchild = new Entity(); + root.addChild(child); + child.addChild(grandchild); + grandchild.addComponent('script'); + grandchild.script.create('myScript'); + const script = root.findScript('myScript'); + expect(script).to.be.an.instanceof(MyScript); + }); + + it('does not find script on parent entity', function () { + const MyScript = createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + root.addComponent('script'); + root.script.create('myScript'); + const script = child.findScript('myScript'); + expect(script).to.be.null; + }); + + }); + + describe('#findScripts', function () { + + it('finds scripts on single entity', function () { + const MyScript = createScript('myScript'); + const e = new Entity(); + e.addComponent('script'); + e.script.create('myScript'); + const scripts = e.findScripts('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(1); + expect(scripts[0]).to.be.an.instanceof(MyScript); + }); + + it('returns empty array when no scripts are found', function () { + const MyScript = createScript('myScript'); + const e = new Entity(); + e.addComponent('script'); + const scripts = e.findScripts('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(0); + }); + + it('returns empty array when no script component are found', function () { + const MyScript = createScript('myScript'); + const e = new Entity(); + const scripts = e.findScripts('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(0); + }); + + it('finds scripts on child entity', function () { + const MyScript = createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + child.addComponent('script'); + child.script.create('myScript'); + const scripts = root.findScripts('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(1); + expect(scripts[0]).to.be.an.instanceof(MyScript); + }); + + it('finds scripts on 3 entity hierarchy', function () { + const MyScript = createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + const grandchild = new Entity(); + root.addChild(child); + child.addChild(grandchild); + root.addComponent('script'); + root.script.create('myScript'); + child.addComponent('script'); + child.script.create('myScript'); + grandchild.addComponent('script'); + grandchild.script.create('myScript'); + const scripts = root.findScripts('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(3); + expect(scripts[0]).to.be.an.instanceof(MyScript); + expect(scripts[1]).to.be.an.instanceof(MyScript); + expect(scripts[2]).to.be.an.instanceof(MyScript); + }); + + it('does not find scripts on parent entity', function () { + const MyScript = createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + root.addComponent('script'); + root.script.create('myScript'); + const scripts = child.findScripts('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(0); + }); + + }); + describe('#findComponentInParent', function () { it('finds component on single entity', function () { @@ -781,6 +922,147 @@ describe('Entity', function () { }); + describe('#findScriptInParent', function () { + + it('finds script on single entity', function () { + const MyScript = createScript('myScript'); + const e = new Entity(); + e.addComponent('script'); + e.script.create('myScript'); + const script = e.findScriptInParent('myScript'); + expect(script).to.be.an.instanceof(MyScript); + }); + + it('returns null when script is not found', function () { + const MyScript = createScript('myScript'); + const e = new Entity(); + e.addComponent('script'); + const script = e.findScriptInParent('myScript'); + expect(script).to.be.null; + }); + + it('returns null when script component is not found', function () { + const MyScript = createScript('myScript'); + const e = new Entity(); + const script = e.findScriptInParent('myScript'); + expect(script).to.be.null; + }); + + it('finds script on parent entity', function () { + const MyScript = createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + root.addComponent('script'); + root.script.create('myScript'); + const script = child.findScriptInParent('myScript'); + expect(script).to.be.an.instanceof(MyScript); + }); + + it('finds script on grandparent entity', function () { + const MyScript = createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + const grandchild = new Entity(); + root.addChild(child); + child.addChild(grandchild); + root.addComponent('script'); + root.script.create('myScript'); + const script = grandchild.findScriptInParent('myScript'); + expect(script).to.be.an.instanceof(MyScript); + }); + + it('does not find script on child entity', function () { + const MyScript = createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + child.addComponent('script'); + child.script.create('myScript'); + const script = root.findScriptInParent('myScript'); + expect(script).to.be.null; + }); + + }); + + describe('#findScriptsInParent', function () { + + it('finds scripts on single entity', function () { + const MyScript = createScript('myScript'); + const e = new Entity(); + e.addComponent('script'); + e.script.create('myScript'); + const scripts = e.findScriptsInParent('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(1); + expect(scripts[0]).to.be.an.instanceof(MyScript); + }); + + it('returns empty array when no scripts are found', function () { + const MyScript = createScript('myScript'); + const e = new Entity(); + e.addComponent('script'); + const scripts = e.findScriptsInParent('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(0); + }); + + it('returns empty array when no script component are found', function () { + const MyScript = createScript('myScript'); + const e = new Entity(); + const scripts = e.findScriptsInParent('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(0); + }); + + it('finds scripts on parent entity', function () { + const MyScript = createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + root.addComponent('script'); + root.script.create('myScript'); + const scripts = child.findScriptsInParent('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(1); + expect(scripts[0]).to.be.an.instanceof(MyScript); + }); + + it('finds scripts on 3 entity hierarchy', function () { + const MyScript = createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + const grandchild = new Entity(); + root.addChild(child); + child.addChild(grandchild); + root.addComponent('script'); + root.script.create('myScript'); + child.addComponent('script'); + child.script.create('myScript'); + grandchild.addComponent('script'); + grandchild.script.create('myScript'); + const scripts = grandchild.findScriptsInParent('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(3); + expect(scripts[0]).to.be.an.instanceof(MyScript); + expect(scripts[1]).to.be.an.instanceof(MyScript); + expect(scripts[2]).to.be.an.instanceof(MyScript); + }); + + it('does not find scripts on child entity', function () { + const MyScript = createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + child.addComponent('script'); + child.script.create('myScript'); + const scripts = root.findScriptsInParent('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(0); + }); + + }); + describe('#removeComponent', function () { it('removes a component from the entity', function () { From 3f2c6477941a86eb28ef94d162041cbd0f678c13 Mon Sep 17 00:00:00 2001 From: NewboO Date: Wed, 31 Aug 2022 15:26:13 +0200 Subject: [PATCH 06/26] graph-node.js lint fix --- src/scene/graph-node.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/scene/graph-node.js b/src/scene/graph-node.js index 1d94d571fb0..01dcf7ace68 100644 --- a/src/scene/graph-node.js +++ b/src/scene/graph-node.js @@ -622,7 +622,7 @@ class GraphNode extends EventHandler { if (result) results.push(this); - if(this._parent) { + if (this._parent) { results = results.concat(this._parent.findInParent(fn)); } } else { @@ -638,7 +638,7 @@ class GraphNode extends EventHandler { results.push(this); } - if(this._parent) { + if (this._parent) { results = results.concat(this._parent.findInParent(attr, value)); } } @@ -672,7 +672,7 @@ class GraphNode extends EventHandler { */ findOneInParent(attr, value) { let result = null; - + if (attr instanceof Function) { const fn = attr; @@ -680,7 +680,7 @@ class GraphNode extends EventHandler { if (result) return this; - if(this._parent) { + if (this._parent) { return this._parent.findOneInParent(fn); } } else { @@ -696,7 +696,7 @@ class GraphNode extends EventHandler { } } - if(this._parent) { + if (this._parent) { return this._parent.findOneInParent(attr, value); } } From 9505c6d14c1fc76f3b6b76804b72ee8a3af81a26 Mon Sep 17 00:00:00 2001 From: NewboO Date: Wed, 31 Aug 2022 15:32:55 +0200 Subject: [PATCH 07/26] entity.test.mjs lint fix --- test/framework/entity.test.mjs | 40 ++++++++++++++-------------------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/test/framework/entity.test.mjs b/test/framework/entity.test.mjs index 37fcc60d0fa..eccfd0364a2 100644 --- a/test/framework/entity.test.mjs +++ b/test/framework/entity.test.mjs @@ -678,7 +678,7 @@ describe('Entity', function () { describe('#findScript', function () { it('finds script on single entity', function () { - const MyScript = createScript('myScript'); + const MyScript = createScript('myScript'); const e = new Entity(); e.addComponent('script'); e.script.create('myScript'); @@ -687,7 +687,6 @@ describe('Entity', function () { }); it('returns null when script is not found', function () { - const MyScript = createScript('myScript'); const e = new Entity(); e.addComponent('script'); const script = e.findScript('myScript'); @@ -695,14 +694,13 @@ describe('Entity', function () { }); it('returns null when script component is not found', function () { - const MyScript = createScript('myScript'); const e = new Entity(); const script = e.findScript('myScript'); expect(script).to.be.null; }); it('finds script on child entity', function () { - const MyScript = createScript('myScript'); + const MyScript = createScript('myScript'); const root = new Entity(); const child = new Entity(); root.addChild(child); @@ -713,7 +711,7 @@ describe('Entity', function () { }); it('finds script on grandchild entity', function () { - const MyScript = createScript('myScript'); + const MyScript = createScript('myScript'); const root = new Entity(); const child = new Entity(); const grandchild = new Entity(); @@ -726,7 +724,7 @@ describe('Entity', function () { }); it('does not find script on parent entity', function () { - const MyScript = createScript('myScript'); + createScript('myScript'); const root = new Entity(); const child = new Entity(); root.addChild(child); @@ -741,7 +739,7 @@ describe('Entity', function () { describe('#findScripts', function () { it('finds scripts on single entity', function () { - const MyScript = createScript('myScript'); + const MyScript = createScript('myScript'); const e = new Entity(); e.addComponent('script'); e.script.create('myScript'); @@ -752,7 +750,6 @@ describe('Entity', function () { }); it('returns empty array when no scripts are found', function () { - const MyScript = createScript('myScript'); const e = new Entity(); e.addComponent('script'); const scripts = e.findScripts('myScript'); @@ -761,7 +758,6 @@ describe('Entity', function () { }); it('returns empty array when no script component are found', function () { - const MyScript = createScript('myScript'); const e = new Entity(); const scripts = e.findScripts('myScript'); expect(scripts).to.be.an('array'); @@ -769,7 +765,7 @@ describe('Entity', function () { }); it('finds scripts on child entity', function () { - const MyScript = createScript('myScript'); + const MyScript = createScript('myScript'); const root = new Entity(); const child = new Entity(); root.addChild(child); @@ -782,7 +778,7 @@ describe('Entity', function () { }); it('finds scripts on 3 entity hierarchy', function () { - const MyScript = createScript('myScript'); + const MyScript = createScript('myScript'); const root = new Entity(); const child = new Entity(); const grandchild = new Entity(); @@ -803,7 +799,7 @@ describe('Entity', function () { }); it('does not find scripts on parent entity', function () { - const MyScript = createScript('myScript'); + createScript('myScript'); const root = new Entity(); const child = new Entity(); root.addChild(child); @@ -925,7 +921,7 @@ describe('Entity', function () { describe('#findScriptInParent', function () { it('finds script on single entity', function () { - const MyScript = createScript('myScript'); + const MyScript = createScript('myScript'); const e = new Entity(); e.addComponent('script'); e.script.create('myScript'); @@ -934,7 +930,6 @@ describe('Entity', function () { }); it('returns null when script is not found', function () { - const MyScript = createScript('myScript'); const e = new Entity(); e.addComponent('script'); const script = e.findScriptInParent('myScript'); @@ -942,14 +937,13 @@ describe('Entity', function () { }); it('returns null when script component is not found', function () { - const MyScript = createScript('myScript'); const e = new Entity(); const script = e.findScriptInParent('myScript'); expect(script).to.be.null; }); it('finds script on parent entity', function () { - const MyScript = createScript('myScript'); + const MyScript = createScript('myScript'); const root = new Entity(); const child = new Entity(); root.addChild(child); @@ -960,7 +954,7 @@ describe('Entity', function () { }); it('finds script on grandparent entity', function () { - const MyScript = createScript('myScript'); + const MyScript = createScript('myScript'); const root = new Entity(); const child = new Entity(); const grandchild = new Entity(); @@ -973,7 +967,7 @@ describe('Entity', function () { }); it('does not find script on child entity', function () { - const MyScript = createScript('myScript'); + createScript('myScript'); const root = new Entity(); const child = new Entity(); root.addChild(child); @@ -988,7 +982,7 @@ describe('Entity', function () { describe('#findScriptsInParent', function () { it('finds scripts on single entity', function () { - const MyScript = createScript('myScript'); + const MyScript = createScript('myScript'); const e = new Entity(); e.addComponent('script'); e.script.create('myScript'); @@ -999,7 +993,6 @@ describe('Entity', function () { }); it('returns empty array when no scripts are found', function () { - const MyScript = createScript('myScript'); const e = new Entity(); e.addComponent('script'); const scripts = e.findScriptsInParent('myScript'); @@ -1008,7 +1001,6 @@ describe('Entity', function () { }); it('returns empty array when no script component are found', function () { - const MyScript = createScript('myScript'); const e = new Entity(); const scripts = e.findScriptsInParent('myScript'); expect(scripts).to.be.an('array'); @@ -1016,7 +1008,7 @@ describe('Entity', function () { }); it('finds scripts on parent entity', function () { - const MyScript = createScript('myScript'); + const MyScript = createScript('myScript'); const root = new Entity(); const child = new Entity(); root.addChild(child); @@ -1029,7 +1021,7 @@ describe('Entity', function () { }); it('finds scripts on 3 entity hierarchy', function () { - const MyScript = createScript('myScript'); + const MyScript = createScript('myScript'); const root = new Entity(); const child = new Entity(); const grandchild = new Entity(); @@ -1050,7 +1042,7 @@ describe('Entity', function () { }); it('does not find scripts on child entity', function () { - const MyScript = createScript('myScript'); + createScript('myScript'); const root = new Entity(); const child = new Entity(); root.addChild(child); From c3f30c23c678dc4a517db7f67ebf7a76f7baead8 Mon Sep 17 00:00:00 2001 From: NewboO Date: Wed, 31 Aug 2022 15:39:44 +0200 Subject: [PATCH 08/26] Fix doc --- src/framework/entity.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/framework/entity.js b/src/framework/entity.js index d7fc64d2f84..d270c3b0f5a 100644 --- a/src/framework/entity.js +++ b/src/framework/entity.js @@ -26,6 +26,7 @@ import { AppBase } from './app-base.js'; /** @typedef {import('./components/scroll-view/component.js').ScrollViewComponent} ScrollViewComponent */ /** @typedef {import('./components/sound/component.js').SoundComponent} SoundComponent */ /** @typedef {import('./components/sprite/component.js').SpriteComponent} SpriteComponent */ +/** @typedef {import('../script/script-type.js').ScriptType} ScriptType */ /** * @type {GraphNode[]} From 56577ec2e3c96e253e0fcc8f5371ad6e7189c190 Mon Sep 17 00:00:00 2001 From: NewboO Date: Wed, 31 Aug 2022 18:02:31 +0200 Subject: [PATCH 09/26] Fix doc --- types-fixup.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/types-fixup.mjs b/types-fixup.mjs index 42c6e72123d..fb3ec2d825b 100644 --- a/types-fixup.mjs +++ b/types-fixup.mjs @@ -3,6 +3,7 @@ import fs from 'fs'; // Create a regex that matches any string starting with Class< and ending with > const regex = /Class<(.*?)>/g; const paths = [ + './types/framework/entity.d.ts', './types/framework/components/script/component.d.ts', './types/script/script-attributes.d.ts', './types/script/script-registry.d.ts', From f9ee083fe8c58d6729eb9a84bd301f26110537ca Mon Sep 17 00:00:00 2001 From: NewboO Date: Wed, 31 Aug 2022 18:15:13 +0200 Subject: [PATCH 10/26] Const and array spreading in all find functions --- src/scene/graph-node.js | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/scene/graph-node.js b/src/scene/graph-node.js index 01dcf7ace68..5093197ce08 100644 --- a/src/scene/graph-node.js +++ b/src/scene/graph-node.js @@ -488,20 +488,19 @@ class GraphNode extends EventHandler { * var entities = parent.find('name', 'Test'); */ find(attr, value) { - let result, results = []; + const results = []; const len = this._children.length; if (attr instanceof Function) { const fn = attr; - result = fn(this); - if (result) + if (fn(this)) results.push(this); for (let i = 0; i < len; i++) { const descendants = this._children[i].find(fn); if (descendants.length) - results = results.concat(descendants); + results.push(...descendants); } } else { let testValue; @@ -519,7 +518,7 @@ class GraphNode extends EventHandler { for (let i = 0; i < len; ++i) { const descendants = this._children[i].find(attr, value); if (descendants.length) - results = results.concat(descendants); + results.push(...descendants); } } @@ -557,8 +556,7 @@ class GraphNode extends EventHandler { if (attr instanceof Function) { const fn = attr; - result = fn(this); - if (result) + if (fn(this)) return this; for (let i = 0; i < len; i++) { @@ -581,7 +579,7 @@ class GraphNode extends EventHandler { for (let i = 0; i < len; i++) { result = this._children[i].findOne(attr, value); - if (result !== null) + if (result) return result; } } @@ -613,17 +611,16 @@ class GraphNode extends EventHandler { * var entities = entity.findInParent('name', 'Test'); */ findInParent(attr, value) { - let result, results = []; + const results = []; if (attr instanceof Function) { const fn = attr; - result = fn(this); - if (result) + if (fn(this)) results.push(this); if (this._parent) { - results = results.concat(this._parent.findInParent(fn)); + results.push(...this._parent.findInParent(fn)); } } else { let testValue; @@ -639,7 +636,7 @@ class GraphNode extends EventHandler { } if (this._parent) { - results = results.concat(this._parent.findInParent(attr, value)); + results.push(...this._parent.findInParent(attr, value)); } } @@ -671,13 +668,10 @@ class GraphNode extends EventHandler { * var node = parent.findOneInParent('name', 'Test'); */ findOneInParent(attr, value) { - let result = null; - if (attr instanceof Function) { const fn = attr; - result = fn(this); - if (result) + if (fn(this)) return this; if (this._parent) { From 4c803a9b6a48acce6255b8b62357168dcd2b4b7a Mon Sep 17 00:00:00 2001 From: NewboO Date: Thu, 1 Sep 2022 10:57:05 +0200 Subject: [PATCH 11/26] Further refactoring of find functions --- src/scene/graph-node.js | 134 ++++++++++++++-------------------------- 1 file changed, 48 insertions(+), 86 deletions(-) diff --git a/src/scene/graph-node.js b/src/scene/graph-node.js index 5093197ce08..4601cd40d86 100644 --- a/src/scene/graph-node.js +++ b/src/scene/graph-node.js @@ -492,34 +492,22 @@ class GraphNode extends EventHandler { const len = this._children.length; if (attr instanceof Function) { - const fn = attr; - - if (fn(this)) + if (attr(this)) results.push(this); - - for (let i = 0; i < len; i++) { - const descendants = this._children[i].find(fn); - if (descendants.length) - results.push(...descendants); - } - } else { + } else if (this[attr]) { let testValue; - if (this[attr]) { - if (this[attr] instanceof Function) { - testValue = this[attr](); - } else { - testValue = this[attr]; - } - if (testValue === value) - results.push(this); + if (this[attr] instanceof Function) { + testValue = this[attr](); + } else { + testValue = this[attr]; } + if (testValue === value) + results.push(this); + } - for (let i = 0; i < len; ++i) { - const descendants = this._children[i].find(attr, value); - if (descendants.length) - results.push(...descendants); - } + for (let i = 0; i < len; ++i) { + results.push(...this._children[i].find(attr, value)); } return results; @@ -554,34 +542,24 @@ class GraphNode extends EventHandler { let result = null; if (attr instanceof Function) { - const fn = attr; - - if (fn(this)) + if (attr(this)) return this; - - for (let i = 0; i < len; i++) { - result = this._children[i].findOne(fn); - if (result) - return result; - } - } else { + } else if (this[attr]) { let testValue; - if (this[attr]) { - if (this[attr] instanceof Function) { - testValue = this[attr](); - } else { - testValue = this[attr]; - } - if (testValue === value) { - return this; - } - } - for (let i = 0; i < len; i++) { - result = this._children[i].findOne(attr, value); - if (result) - return result; + if (this[attr] instanceof Function) { + testValue = this[attr](); + } else { + testValue = this[attr]; } + if (testValue === value) + return this; + } + + for (let i = 0; i < len; ++i) { + result = this._children[i].findOne(attr, value); + if (result) + return result; } return null; @@ -614,30 +592,22 @@ class GraphNode extends EventHandler { const results = []; if (attr instanceof Function) { - const fn = attr; - - if (fn(this)) + if (attr(this)) results.push(this); - - if (this._parent) { - results.push(...this._parent.findInParent(fn)); - } - } else { + } else if (this[attr]) { let testValue; - if (this[attr]) { - if (this[attr] instanceof Function) { - testValue = this[attr](); - } else { - testValue = this[attr]; - } - if (testValue === value) - results.push(this); + if (this[attr] instanceof Function) { + testValue = this[attr](); + } else { + testValue = this[attr]; } + if (testValue === value) + results.push(this); + } - if (this._parent) { - results.push(...this._parent.findInParent(attr, value)); - } + if (this._parent) { + results.push(...this._parent.findInParent(attr, value)); } return results; @@ -669,30 +639,22 @@ class GraphNode extends EventHandler { */ findOneInParent(attr, value) { if (attr instanceof Function) { - const fn = attr; - - if (fn(this)) + if (attr(this)) return this; - - if (this._parent) { - return this._parent.findOneInParent(fn); - } - } else { + } else if (this[attr]) { let testValue; - if (this[attr]) { - if (this[attr] instanceof Function) { - testValue = this[attr](); - } else { - testValue = this[attr]; - } - if (testValue === value) { - return this; - } - } - if (this._parent) { - return this._parent.findOneInParent(attr, value); + if (this[attr] instanceof Function) { + testValue = this[attr](); + } else { + testValue = this[attr]; } + if (testValue === value) + return this; + } + + if (this._parent) { + return this._parent.findOneInParent(attr, value); } return null; From edb8038ad5418d466be4ebfd779e8ffa7fc3ad6f Mon Sep 17 00:00:00 2001 From: NewboO Date: Thu, 1 Sep 2022 11:06:35 +0200 Subject: [PATCH 12/26] Another refactoring in findOne --- src/scene/graph-node.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/scene/graph-node.js b/src/scene/graph-node.js index 4601cd40d86..503a0b8084a 100644 --- a/src/scene/graph-node.js +++ b/src/scene/graph-node.js @@ -539,7 +539,6 @@ class GraphNode extends EventHandler { */ findOne(attr, value) { const len = this._children.length; - let result = null; if (attr instanceof Function) { if (attr(this)) @@ -557,7 +556,7 @@ class GraphNode extends EventHandler { } for (let i = 0; i < len; ++i) { - result = this._children[i].findOne(attr, value); + const result = this._children[i].findOne(attr, value); if (result) return result; } From dc358925c59fb87290922fc4886c1d6fb4438d3a Mon Sep 17 00:00:00 2001 From: NewboO Date: Thu, 1 Sep 2022 18:51:38 +0200 Subject: [PATCH 13/26] Avoid creating and pushing empty arrays --- src/scene/graph-node.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/scene/graph-node.js b/src/scene/graph-node.js index 503a0b8084a..ac145ef3265 100644 --- a/src/scene/graph-node.js +++ b/src/scene/graph-node.js @@ -477,6 +477,7 @@ class GraphNode extends EventHandler { * equality against the valued passed as the second argument to this function. * @param {object} [value] - If the first argument (attr) is a property name then this value * will be checked against the value of the property. + * @param {GraphNode[]} [results] - Array where the results are appended to. * @returns {GraphNode[]} The array of graph nodes that match the search criteria. * @example * // Finds all nodes that have a model component and have `door` in their lower-cased name @@ -487,8 +488,7 @@ class GraphNode extends EventHandler { * // Finds all nodes that have the name property set to 'Test' * var entities = parent.find('name', 'Test'); */ - find(attr, value) { - const results = []; + find(attr, value, results = []) { const len = this._children.length; if (attr instanceof Function) { @@ -507,7 +507,7 @@ class GraphNode extends EventHandler { } for (let i = 0; i < len; ++i) { - results.push(...this._children[i].find(attr, value)); + this._children[i].find(attr, value, results); } return results; @@ -577,6 +577,7 @@ class GraphNode extends EventHandler { * equality against the valued passed as the second argument to this function. * @param {object} [value] - If the first argument (attr) is a property name then this value * will be checked against the value of the property. + * @param {GraphNode[]} [results] - Array where the results are appended to. * @returns {GraphNode[]} The array of graph nodes that match the search criteria. * @example * // Finds all nodes that have a group element component @@ -587,9 +588,7 @@ class GraphNode extends EventHandler { * // Finds all nodes that have the name property set to 'Test' * var entities = entity.findInParent('name', 'Test'); */ - findInParent(attr, value) { - const results = []; - + findInParent(attr, value, results = []) { if (attr instanceof Function) { if (attr(this)) results.push(this); @@ -606,7 +605,7 @@ class GraphNode extends EventHandler { } if (this._parent) { - results.push(...this._parent.findInParent(attr, value)); + this._parent.findInParent(attr, value, results); } return results; From 90fa55edc875989233d36dfb9da1f2fa627d1fac Mon Sep 17 00:00:00 2001 From: NewboO Date: Fri, 2 Sep 2022 00:26:17 +0200 Subject: [PATCH 14/26] Pluralize and make non-recursive when possible --- src/framework/entity.js | 24 +++--- src/scene/graph-node.js | 140 +++++++++++++++++++++------------ test/framework/entity.test.mjs | 52 ++++++------ test/scene/graph-node.test.mjs | 53 +++++++++---- 4 files changed, 167 insertions(+), 102 deletions(-) diff --git a/src/framework/entity.js b/src/framework/entity.js index d270c3b0f5a..6b49ac411c1 100644 --- a/src/framework/entity.js +++ b/src/framework/entity.js @@ -453,10 +453,10 @@ class Entity extends GraphNode { * has one. Returns undefined otherwise. * @example * // Get the first found light component in the ancestor tree that starts with this entity - * var light = entity.findComponentInParent("light"); + * var light = entity.findComponentInParents("light"); */ - findComponentInParent(type) { - const entity = this.findOneInParent(function (node) { + findComponentInParents(type) { + const entity = this.findOneInParents(function (node) { return node.c && node.c[type]; }); return entity && entity.c[type]; @@ -470,10 +470,10 @@ class Entity extends GraphNode { * ascendants. Returns empty array if none found. * @example * // Get all element components in the ancestor tree that starts with this entity - * var elements = entity.findComponentsInParent("element"); + * var elements = entity.findComponentsInParents("element"); */ - findComponentsInParent(type) { - const entities = this.findInParent(function (node) { + findComponentsInParents(type) { + const entities = this.findInParents(function (node) { return node.c && node.c[type]; }); return entities.map(function (entity) { @@ -489,10 +489,10 @@ class Entity extends GraphNode { * has one. Returns undefined otherwise. * @example * // Get the first found "playerController" instance in the ancestor tree that starts with this entity - * var controller = entity.findScriptInParent("playerController"); + * var controller = entity.findScriptInParents("playerController"); */ - findScriptInParent(nameOrType) { - const entity = this.findOneInParent(function (node) { + findScriptInParents(nameOrType) { + const entity = this.findOneInParents(function (node) { return node.c && node.c.script && node.c.script.has(nameOrType); }); return entity && entity.c.script.get(nameOrType); @@ -506,10 +506,10 @@ class Entity extends GraphNode { * ascendants. Returns empty array if none found. * @example * // Get all "playerController" instance in the ancestor tree that starts with this entity - * var controllers = entity.findScriptsInParent("playerController"); + * var controllers = entity.findScriptsInParents("playerController"); */ - findScriptsInParent(nameOrType) { - const entities = this.findInParent(function (node) { + findScriptsInParents(nameOrType) { + const entities = this.findInParents(function (node) { return node.c && node.c.script && node.c.script.has(nameOrType); }); return entities.map(function (entity) { diff --git a/src/scene/graph-node.js b/src/scene/graph-node.js index ac145ef3265..f12c392a591 100644 --- a/src/scene/graph-node.js +++ b/src/scene/graph-node.js @@ -477,7 +477,6 @@ class GraphNode extends EventHandler { * equality against the valued passed as the second argument to this function. * @param {object} [value] - If the first argument (attr) is a property name then this value * will be checked against the value of the property. - * @param {GraphNode[]} [results] - Array where the results are appended to. * @returns {GraphNode[]} The array of graph nodes that match the search criteria. * @example * // Finds all nodes that have a model component and have `door` in their lower-cased name @@ -488,28 +487,41 @@ class GraphNode extends EventHandler { * // Finds all nodes that have the name property set to 'Test' * var entities = parent.find('name', 'Test'); */ - find(attr, value, results = []) { - const len = this._children.length; + find(attr, value) { + const results = []; + let queryNode; if (attr instanceof Function) { - if (attr(this)) - results.push(this); - } else if (this[attr]) { - let testValue; + queryNode = (node) => { + if (attr(node)) + results.push(node); - if (this[attr] instanceof Function) { - testValue = this[attr](); - } else { - testValue = this[attr]; - } - if (testValue === value) - results.push(this); - } + for (let i = 0, len = node._children.length; i < len; ++i) { + queryNode(node._children[i]); + } + }; + } else { + queryNode = (node) => { + if (node[attr]) { + let testValue; + + if (node[attr] instanceof Function) { + testValue = node[attr](); + } else { + testValue = node[attr]; + } + if (testValue === value) + results.push(node); + } - for (let i = 0; i < len; ++i) { - this._children[i].find(attr, value, results); + for (let i = 0, len = node._children.length; i < len; ++i) { + queryNode(node._children[i]); + } + }; } + queryNode(this); + return results; } @@ -577,35 +589,39 @@ class GraphNode extends EventHandler { * equality against the valued passed as the second argument to this function. * @param {object} [value] - If the first argument (attr) is a property name then this value * will be checked against the value of the property. - * @param {GraphNode[]} [results] - Array where the results are appended to. * @returns {GraphNode[]} The array of graph nodes that match the search criteria. * @example * // Finds all nodes that have a group element component - * var groups = element.findInParent(function (node) { + * var groups = element.findInParents(function (node) { * return node.element && node.element.type === pc.ELEMENTTYPE_GROUP; * }); * @example * // Finds all nodes that have the name property set to 'Test' - * var entities = entity.findInParent('name', 'Test'); + * var entities = entity.findInParents('name', 'Test'); */ - findInParent(attr, value, results = []) { + findInParents(attr, value) { + const results = []; + let testNode, current = this; + if (attr instanceof Function) { - if (attr(this)) - results.push(this); - } else if (this[attr]) { - let testValue; + testNode = node => attr(node); + } else { + testNode = (node) => { + if (!node[attr]) + return false; - if (this[attr] instanceof Function) { - testValue = this[attr](); - } else { - testValue = this[attr]; - } - if (testValue === value) - results.push(this); + if (node[attr] instanceof Function) + return node[attr]() === value; + + return node[attr] === value; + }; } - if (this._parent) { - this._parent.findInParent(attr, value, results); + while (current) { + if (testNode(current)) + results.push(current); + + current = current._parent; } return results; @@ -628,31 +644,35 @@ class GraphNode extends EventHandler { * node is found. * @example * // Find the first node that is called `head` and has a model component - * var head = player.findOneInParent(function (node) { + * var head = player.findOneInParents(function (node) { * return node.model && node.name === 'head'; * }); * @example * // Finds the first node that has the name property set to 'Test' - * var node = parent.findOneInParent('name', 'Test'); + * var node = parent.findOneInParents('name', 'Test'); */ - findOneInParent(attr, value) { + findOneInParents(attr, value) { + let testNode, current = this; + if (attr instanceof Function) { - if (attr(this)) - return this; - } else if (this[attr]) { - let testValue; + testNode = node => attr(node); + } else { + testNode = (node) => { + if (!node[attr]) + return false; - if (this[attr] instanceof Function) { - testValue = this[attr](); - } else { - testValue = this[attr]; - } - if (testValue === value) - return this; + if (node[attr] instanceof Function) + return node[attr]() === value; + + return node[attr] === value; + }; } - if (this._parent) { - return this._parent.findOneInParent(attr, value); + while (current) { + if (testNode(current)) + return current; + + current = current._parent; } return null; @@ -715,6 +735,26 @@ class GraphNode extends EventHandler { return null; } + /** + * Get the first ancestor node found in the graph with the name. + * + * @param {string} name - The name of the graph. + * @returns {GraphNode|null} The first node to be found matching the supplied name. Returns + * null if no node is found. + */ + findByNameInParents(name) { + let current = this; + + while (current) { + if (current.name === name) + return current; + + current = current._parent; + } + + return null; + } + /** * Get the first node found in the graph by its full path in the graph. The full path has this * form 'parent/child/sub-child'. The search is depth first. diff --git a/test/framework/entity.test.mjs b/test/framework/entity.test.mjs index eccfd0364a2..4b55d8be7ef 100644 --- a/test/framework/entity.test.mjs +++ b/test/framework/entity.test.mjs @@ -812,19 +812,19 @@ describe('Entity', function () { }); - describe('#findComponentInParent', function () { + describe('#findComponentInParents', function () { it('finds component on single entity', function () { const e = new Entity(); e.addComponent('anim'); - const component = e.findComponentInParent('anim'); + const component = e.findComponentInParents('anim'); expect(component).to.be.an.instanceof(AnimComponent); }); it('returns null when component is not found', function () { const e = new Entity(); e.addComponent('anim'); - const component = e.findComponentInParent('render'); + const component = e.findComponentInParents('render'); expect(component).to.be.null; }); @@ -833,7 +833,7 @@ describe('Entity', function () { const child = new Entity(); root.addChild(child); root.addComponent('anim'); - const component = child.findComponentInParent('anim'); + const component = child.findComponentInParents('anim'); expect(component).to.be.an.instanceof(AnimComponent); }); @@ -844,7 +844,7 @@ describe('Entity', function () { root.addChild(child); child.addChild(grandchild); root.addComponent('anim'); - const component = grandchild.findComponentInParent('anim'); + const component = grandchild.findComponentInParents('anim'); expect(component).to.be.an.instanceof(AnimComponent); }); @@ -853,18 +853,18 @@ describe('Entity', function () { const child = new Entity(); root.addChild(child); child.addComponent('anim'); - const component = root.findComponentInParent('anim'); + const component = root.findComponentInParents('anim'); expect(component).to.be.null; }); }); - describe('#findComponentsInParent', function () { + describe('#findComponentsInParents', function () { it('finds components on single entity', function () { const e = new Entity(); e.addComponent('anim'); - const components = e.findComponentsInParent('anim'); + const components = e.findComponentsInParents('anim'); expect(components).to.be.an('array'); expect(components.length).to.equal(1); expect(components[0]).to.be.an.instanceof(AnimComponent); @@ -873,7 +873,7 @@ describe('Entity', function () { it('returns empty array when no components are found', function () { const e = new Entity(); e.addComponent('anim'); - const components = e.findComponentsInParent('render'); + const components = e.findComponentsInParents('render'); expect(components).to.be.an('array'); expect(components.length).to.equal(0); }); @@ -883,7 +883,7 @@ describe('Entity', function () { const child = new Entity(); root.addChild(child); root.addComponent('anim'); - const components = child.findComponentsInParent('anim'); + const components = child.findComponentsInParents('anim'); expect(components).to.be.an('array'); expect(components.length).to.equal(1); expect(components[0]).to.be.an.instanceof(AnimComponent); @@ -898,7 +898,7 @@ describe('Entity', function () { root.addComponent('anim'); child.addComponent('anim'); grandchild.addComponent('anim'); - const components = grandchild.findComponentsInParent('anim'); + const components = grandchild.findComponentsInParents('anim'); expect(components).to.be.an('array'); expect(components.length).to.equal(3); expect(components[0]).to.be.an.instanceof(AnimComponent); @@ -911,34 +911,34 @@ describe('Entity', function () { const child = new Entity(); root.addChild(child); child.addComponent('anim'); - const components = root.findComponentsInParent('anim'); + const components = root.findComponentsInParents('anim'); expect(components).to.be.an('array'); expect(components.length).to.equal(0); }); }); - describe('#findScriptInParent', function () { + describe('#findScriptInParents', function () { it('finds script on single entity', function () { const MyScript = createScript('myScript'); const e = new Entity(); e.addComponent('script'); e.script.create('myScript'); - const script = e.findScriptInParent('myScript'); + const script = e.findScriptInParents('myScript'); expect(script).to.be.an.instanceof(MyScript); }); it('returns null when script is not found', function () { const e = new Entity(); e.addComponent('script'); - const script = e.findScriptInParent('myScript'); + const script = e.findScriptInParents('myScript'); expect(script).to.be.null; }); it('returns null when script component is not found', function () { const e = new Entity(); - const script = e.findScriptInParent('myScript'); + const script = e.findScriptInParents('myScript'); expect(script).to.be.null; }); @@ -949,7 +949,7 @@ describe('Entity', function () { root.addChild(child); root.addComponent('script'); root.script.create('myScript'); - const script = child.findScriptInParent('myScript'); + const script = child.findScriptInParents('myScript'); expect(script).to.be.an.instanceof(MyScript); }); @@ -962,7 +962,7 @@ describe('Entity', function () { child.addChild(grandchild); root.addComponent('script'); root.script.create('myScript'); - const script = grandchild.findScriptInParent('myScript'); + const script = grandchild.findScriptInParents('myScript'); expect(script).to.be.an.instanceof(MyScript); }); @@ -973,20 +973,20 @@ describe('Entity', function () { root.addChild(child); child.addComponent('script'); child.script.create('myScript'); - const script = root.findScriptInParent('myScript'); + const script = root.findScriptInParents('myScript'); expect(script).to.be.null; }); }); - describe('#findScriptsInParent', function () { + describe('#findScriptsInParents', function () { it('finds scripts on single entity', function () { const MyScript = createScript('myScript'); const e = new Entity(); e.addComponent('script'); e.script.create('myScript'); - const scripts = e.findScriptsInParent('myScript'); + const scripts = e.findScriptsInParents('myScript'); expect(scripts).to.be.an('array'); expect(scripts.length).to.equal(1); expect(scripts[0]).to.be.an.instanceof(MyScript); @@ -995,14 +995,14 @@ describe('Entity', function () { it('returns empty array when no scripts are found', function () { const e = new Entity(); e.addComponent('script'); - const scripts = e.findScriptsInParent('myScript'); + const scripts = e.findScriptsInParents('myScript'); expect(scripts).to.be.an('array'); expect(scripts.length).to.equal(0); }); it('returns empty array when no script component are found', function () { const e = new Entity(); - const scripts = e.findScriptsInParent('myScript'); + const scripts = e.findScriptsInParents('myScript'); expect(scripts).to.be.an('array'); expect(scripts.length).to.equal(0); }); @@ -1014,7 +1014,7 @@ describe('Entity', function () { root.addChild(child); root.addComponent('script'); root.script.create('myScript'); - const scripts = child.findScriptsInParent('myScript'); + const scripts = child.findScriptsInParents('myScript'); expect(scripts).to.be.an('array'); expect(scripts.length).to.equal(1); expect(scripts[0]).to.be.an.instanceof(MyScript); @@ -1033,7 +1033,7 @@ describe('Entity', function () { child.script.create('myScript'); grandchild.addComponent('script'); grandchild.script.create('myScript'); - const scripts = grandchild.findScriptsInParent('myScript'); + const scripts = grandchild.findScriptsInParents('myScript'); expect(scripts).to.be.an('array'); expect(scripts.length).to.equal(3); expect(scripts[0]).to.be.an.instanceof(MyScript); @@ -1048,7 +1048,7 @@ describe('Entity', function () { root.addChild(child); child.addComponent('script'); child.script.create('myScript'); - const scripts = root.findScriptsInParent('myScript'); + const scripts = root.findScriptsInParents('myScript'); expect(scripts).to.be.an('array'); expect(scripts.length).to.equal(0); }); diff --git a/test/scene/graph-node.test.mjs b/test/scene/graph-node.test.mjs index 02b34863780..2ddfe14426d 100644 --- a/test/scene/graph-node.test.mjs +++ b/test/scene/graph-node.test.mjs @@ -413,7 +413,7 @@ describe('GraphNode', function () { }); - describe('#findInParent()', function () { + describe('#findInParents()', function () { it('finds a node by property', function () { const root = new GraphNode('Parent'); @@ -421,15 +421,15 @@ describe('GraphNode', function () { root.addChild(child); let res; - res = child.findInParent('name', 'Parent'); + res = child.findInParents('name', 'Parent'); expect(res).to.be.an('array').with.lengthOf(1); expect(res[0]).to.equal(root); - res = child.findInParent('name', 'Child'); + res = child.findInParents('name', 'Child'); expect(res).to.be.an('array').with.lengthOf(1); expect(res[0]).to.equal(child); - res = child.findInParent('name', 'Not Found'); + res = child.findInParents('name', 'Not Found'); expect(res).to.be.an('array').with.lengthOf(0); }); @@ -439,19 +439,19 @@ describe('GraphNode', function () { root.addChild(child); let res; - res = child.findInParent(function (node) { + res = child.findInParents(function (node) { return node.name === 'Parent'; }); expect(res).to.be.an('array').with.lengthOf(1); expect(res[0]).to.equal(root); - res = child.findInParent(function (node) { + res = child.findInParents(function (node) { return node.name === 'Child'; }); expect(res).to.be.an('array').with.lengthOf(1); expect(res[0]).to.equal(child); - res = child.findInParent(function (node) { + res = child.findInParents(function (node) { return node.name === 'Not Found'; }); expect(res).to.be.an('array').with.lengthOf(0); @@ -459,7 +459,7 @@ describe('GraphNode', function () { }); - describe('#findOneInParent()', function () { + describe('#findOneInParents()', function () { it('finds a node by property', function () { const root = new GraphNode('Parent'); @@ -467,13 +467,13 @@ describe('GraphNode', function () { root.addChild(child); let res; - res = child.findOneInParent('name', 'Parent'); + res = child.findOneInParents('name', 'Parent'); expect(res).to.equal(root); - res = child.findOneInParent('name', 'Child'); + res = child.findOneInParents('name', 'Child'); expect(res).to.equal(child); - res = child.findOneInParent('name', 'Not Found'); + res = child.findOneInParents('name', 'Not Found'); expect(res).to.be.null; }); @@ -483,17 +483,17 @@ describe('GraphNode', function () { root.addChild(child); let res; - res = child.findOneInParent(function (node) { + res = child.findOneInParents(function (node) { return node.name === 'Parent'; }); expect(res).to.equal(root); - res = child.findOneInParent(function (node) { + res = child.findOneInParents(function (node) { return node.name === 'Child'; }); expect(res).to.equal(child); - res = child.findOneInParent(function (node) { + res = child.findOneInParents(function (node) { return node.name === 'Not Found'; }); expect(res).to.be.null; @@ -501,6 +501,31 @@ describe('GraphNode', function () { }); + describe('#findByNameInParents()', function () { + + it('finds root by name', function () { + const root = new GraphNode('root'); + const child = new GraphNode('child'); + root.addChild(child); + expect(child.findByNameInParents('root')).to.equal(root); + }); + + it('finds child by name', function () { + const root = new GraphNode('root'); + const child = new GraphNode('child'); + root.addChild(child); + expect(child.findByNameInParents('child')).to.equal(child); + }); + + it('returns null if no node is found', function () { + const root = new GraphNode('root'); + const child = new GraphNode('child'); + root.addChild(child); + expect(child.findByNameInParents('not-found')).to.equal(null); + }); + + }); + describe('#forEach()', function () { it('iterates over all nodes', function () { From b4ecb307d80bc09b2d8b9ef7e508c6896e75df14 Mon Sep 17 00:00:00 2001 From: NewboO Date: Fri, 2 Sep 2022 09:07:40 +0200 Subject: [PATCH 15/26] Allow looking for falsey values --- src/scene/graph-node.js | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/scene/graph-node.js b/src/scene/graph-node.js index f12c392a591..cfaa4e897ca 100644 --- a/src/scene/graph-node.js +++ b/src/scene/graph-node.js @@ -502,17 +502,15 @@ class GraphNode extends EventHandler { }; } else { queryNode = (node) => { - if (node[attr]) { - let testValue; + let testValue; - if (node[attr] instanceof Function) { - testValue = node[attr](); - } else { - testValue = node[attr]; - } - if (testValue === value) - results.push(node); + if (node[attr] instanceof Function) { + testValue = node[attr](); + } else { + testValue = node[attr]; } + if (testValue === value) + results.push(node); for (let i = 0, len = node._children.length; i < len; ++i) { queryNode(node._children[i]); @@ -555,7 +553,7 @@ class GraphNode extends EventHandler { if (attr instanceof Function) { if (attr(this)) return this; - } else if (this[attr]) { + } else { let testValue; if (this[attr] instanceof Function) { @@ -607,9 +605,6 @@ class GraphNode extends EventHandler { testNode = node => attr(node); } else { testNode = (node) => { - if (!node[attr]) - return false; - if (node[attr] instanceof Function) return node[attr]() === value; @@ -658,9 +653,6 @@ class GraphNode extends EventHandler { testNode = node => attr(node); } else { testNode = (node) => { - if (!node[attr]) - return false; - if (node[attr] instanceof Function) return node[attr]() === value; From 4b4636a2ece410d359002bf3cd50114851024e1c Mon Sep 17 00:00:00 2001 From: NewboO Date: Fri, 2 Sep 2022 14:24:21 +0200 Subject: [PATCH 16/26] Renaming, factorizing and adding forEachParent --- src/framework/entity.js | 8 +-- src/scene/graph-node.js | 119 +++++++++++++++------------------ test/scene/graph-node.test.mjs | 56 +++++++++++----- 3 files changed, 96 insertions(+), 87 deletions(-) diff --git a/src/framework/entity.js b/src/framework/entity.js index 6b49ac411c1..5acf894b7d8 100644 --- a/src/framework/entity.js +++ b/src/framework/entity.js @@ -456,7 +456,7 @@ class Entity extends GraphNode { * var light = entity.findComponentInParents("light"); */ findComponentInParents(type) { - const entity = this.findOneInParents(function (node) { + const entity = this.findOneParent(function (node) { return node.c && node.c[type]; }); return entity && entity.c[type]; @@ -473,7 +473,7 @@ class Entity extends GraphNode { * var elements = entity.findComponentsInParents("element"); */ findComponentsInParents(type) { - const entities = this.findInParents(function (node) { + const entities = this.findParents(function (node) { return node.c && node.c[type]; }); return entities.map(function (entity) { @@ -492,7 +492,7 @@ class Entity extends GraphNode { * var controller = entity.findScriptInParents("playerController"); */ findScriptInParents(nameOrType) { - const entity = this.findOneInParents(function (node) { + const entity = this.findOneParent(function (node) { return node.c && node.c.script && node.c.script.has(nameOrType); }); return entity && entity.c.script.get(nameOrType); @@ -509,7 +509,7 @@ class Entity extends GraphNode { * var controllers = entity.findScriptsInParents("playerController"); */ findScriptsInParents(nameOrType) { - const entities = this.findInParents(function (node) { + const entities = this.findParents(function (node) { return node.c && node.c.script && node.c.script.has(nameOrType); }); return entities.map(function (entity) { diff --git a/src/scene/graph-node.js b/src/scene/graph-node.js index cfaa4e897ca..cfbb933b0a4 100644 --- a/src/scene/graph-node.js +++ b/src/scene/graph-node.js @@ -22,10 +22,16 @@ const invParentRot = new Quat(); const matrix = new Mat4(); const target = new Vec3(); const up = new Vec3(); +const checkAttr = (node, attr, value) => { + if (node[attr] instanceof Function) + return node[attr]() === value; + + return node[attr] === value; +}; /** - * Callback used by {@link GraphNode#find} and {@link GraphNode#findOne} to search through a graph - * node and all of its descendants. + * Callback used by {@link GraphNode#find}, {@link GraphNode#findOne}, {@link GraphNode#findParents} + * and {@link GraphNode#findOneParent} to search through a graph node and all of its descendants or ascendants. * * @callback FindNodeCallback * @param {GraphNode} node - The current graph node. @@ -34,8 +40,8 @@ const up = new Vec3(); */ /** - * Callback used by {@link GraphNode#forEach} to iterate through a graph node and all of its - * descendants. + * Callback used by {@link GraphNode#forEach} and {@link GraphNode#forEachParent} to iterate through + * a graph node and all of its descendants or ascendants. * * @callback ForEachNodeCallback * @param {GraphNode} node - The current graph node. @@ -489,37 +495,19 @@ class GraphNode extends EventHandler { */ find(attr, value) { const results = []; - let queryNode; if (attr instanceof Function) { - queryNode = (node) => { + this.forEach((node) => { if (attr(node)) results.push(node); - - for (let i = 0, len = node._children.length; i < len; ++i) { - queryNode(node._children[i]); - } - }; + }); } else { - queryNode = (node) => { - let testValue; - - if (node[attr] instanceof Function) { - testValue = node[attr](); - } else { - testValue = node[attr]; - } - if (testValue === value) + this.forEach((node) => { + if (checkAttr(node, attr, value)) results.push(node); - - for (let i = 0, len = node._children.length; i < len; ++i) { - queryNode(node._children[i]); - } - }; + }); } - queryNode(this); - return results; } @@ -553,16 +541,8 @@ class GraphNode extends EventHandler { if (attr instanceof Function) { if (attr(this)) return this; - } else { - let testValue; - - if (this[attr] instanceof Function) { - testValue = this[attr](); - } else { - testValue = this[attr]; - } - if (testValue === value) - return this; + } else if (checkAttr(this, attr, value)) { + return this; } for (let i = 0; i < len; ++i) { @@ -590,33 +570,26 @@ class GraphNode extends EventHandler { * @returns {GraphNode[]} The array of graph nodes that match the search criteria. * @example * // Finds all nodes that have a group element component - * var groups = element.findInParents(function (node) { + * var groups = element.findParents(function (node) { * return node.element && node.element.type === pc.ELEMENTTYPE_GROUP; * }); * @example * // Finds all nodes that have the name property set to 'Test' - * var entities = entity.findInParents('name', 'Test'); + * var entities = entity.findParents('name', 'Test'); */ - findInParents(attr, value) { + findParents(attr, value) { const results = []; - let testNode, current = this; if (attr instanceof Function) { - testNode = node => attr(node); + this.forEachParent((node) => { + if (attr(node)) + results.push(node); + }); } else { - testNode = (node) => { - if (node[attr] instanceof Function) - return node[attr]() === value; - - return node[attr] === value; - }; - } - - while (current) { - if (testNode(current)) - results.push(current); - - current = current._parent; + this.forEachParent((node) => { + if (checkAttr(node, attr, value)) + results.push(node); + }); } return results; @@ -639,25 +612,20 @@ class GraphNode extends EventHandler { * node is found. * @example * // Find the first node that is called `head` and has a model component - * var head = player.findOneInParents(function (node) { + * var head = player.findOneParent(function (node) { * return node.model && node.name === 'head'; * }); * @example * // Finds the first node that has the name property set to 'Test' - * var node = parent.findOneInParents('name', 'Test'); + * var node = parent.findOneParent('name', 'Test'); */ - findOneInParents(attr, value) { + findOneParent(attr, value) { let testNode, current = this; if (attr instanceof Function) { testNode = node => attr(node); } else { - testNode = (node) => { - if (node[attr] instanceof Function) - return node[attr]() === value; - - return node[attr] === value; - }; + testNode = node => checkAttr(node, attr, value); } while (current) { @@ -734,7 +702,7 @@ class GraphNode extends EventHandler { * @returns {GraphNode|null} The first node to be found matching the supplied name. Returns * null if no node is found. */ - findByNameInParents(name) { + findParentByName(name) { let current = this; while (current) { @@ -798,6 +766,27 @@ class GraphNode extends EventHandler { } } + /** + * Executes a provided function once on this graph node and all of its ascendants. + * + * @param {ForEachNodeCallback} callback - The function to execute on the graph node and each + * ascendant. + * @param {object} [thisArg] - Optional value to use as this when executing callback function. + * @example + * // Enable each node in ascendant tree + * current.forEachParent(function (node) { + * node.enabled = true; + * }); + */ + forEachParent(callback, thisArg) { + let current = this; + + while (current) { + callback.call(thisArg, current); + current = current._parent; + } + } + /** * Check if node is descendant of another node. * diff --git a/test/scene/graph-node.test.mjs b/test/scene/graph-node.test.mjs index 2ddfe14426d..9d90cffa82a 100644 --- a/test/scene/graph-node.test.mjs +++ b/test/scene/graph-node.test.mjs @@ -413,7 +413,7 @@ describe('GraphNode', function () { }); - describe('#findInParents()', function () { + describe('#findParents()', function () { it('finds a node by property', function () { const root = new GraphNode('Parent'); @@ -421,15 +421,15 @@ describe('GraphNode', function () { root.addChild(child); let res; - res = child.findInParents('name', 'Parent'); + res = child.findParents('name', 'Parent'); expect(res).to.be.an('array').with.lengthOf(1); expect(res[0]).to.equal(root); - res = child.findInParents('name', 'Child'); + res = child.findParents('name', 'Child'); expect(res).to.be.an('array').with.lengthOf(1); expect(res[0]).to.equal(child); - res = child.findInParents('name', 'Not Found'); + res = child.findParents('name', 'Not Found'); expect(res).to.be.an('array').with.lengthOf(0); }); @@ -439,19 +439,19 @@ describe('GraphNode', function () { root.addChild(child); let res; - res = child.findInParents(function (node) { + res = child.findParents(function (node) { return node.name === 'Parent'; }); expect(res).to.be.an('array').with.lengthOf(1); expect(res[0]).to.equal(root); - res = child.findInParents(function (node) { + res = child.findParents(function (node) { return node.name === 'Child'; }); expect(res).to.be.an('array').with.lengthOf(1); expect(res[0]).to.equal(child); - res = child.findInParents(function (node) { + res = child.findParents(function (node) { return node.name === 'Not Found'; }); expect(res).to.be.an('array').with.lengthOf(0); @@ -459,7 +459,7 @@ describe('GraphNode', function () { }); - describe('#findOneInParents()', function () { + describe('#findOneParent()', function () { it('finds a node by property', function () { const root = new GraphNode('Parent'); @@ -467,13 +467,13 @@ describe('GraphNode', function () { root.addChild(child); let res; - res = child.findOneInParents('name', 'Parent'); + res = child.findOneParent('name', 'Parent'); expect(res).to.equal(root); - res = child.findOneInParents('name', 'Child'); + res = child.findOneParent('name', 'Child'); expect(res).to.equal(child); - res = child.findOneInParents('name', 'Not Found'); + res = child.findOneParent('name', 'Not Found'); expect(res).to.be.null; }); @@ -483,17 +483,17 @@ describe('GraphNode', function () { root.addChild(child); let res; - res = child.findOneInParents(function (node) { + res = child.findOneParent(function (node) { return node.name === 'Parent'; }); expect(res).to.equal(root); - res = child.findOneInParents(function (node) { + res = child.findOneParent(function (node) { return node.name === 'Child'; }); expect(res).to.equal(child); - res = child.findOneInParents(function (node) { + res = child.findOneParent(function (node) { return node.name === 'Not Found'; }); expect(res).to.be.null; @@ -501,27 +501,27 @@ describe('GraphNode', function () { }); - describe('#findByNameInParents()', function () { + describe('#findParentByName()', function () { it('finds root by name', function () { const root = new GraphNode('root'); const child = new GraphNode('child'); root.addChild(child); - expect(child.findByNameInParents('root')).to.equal(root); + expect(child.findParentByName('root')).to.equal(root); }); it('finds child by name', function () { const root = new GraphNode('root'); const child = new GraphNode('child'); root.addChild(child); - expect(child.findByNameInParents('child')).to.equal(child); + expect(child.findParentByName('child')).to.equal(child); }); it('returns null if no node is found', function () { const root = new GraphNode('root'); const child = new GraphNode('child'); root.addChild(child); - expect(child.findByNameInParents('not-found')).to.equal(null); + expect(child.findParentByName('not-found')).to.equal(null); }); }); @@ -546,6 +546,26 @@ describe('GraphNode', function () { }); + describe('#forEachParent()', function () { + + it('iterates over all nodes', function () { + const root = new GraphNode(); + const child1 = new GraphNode(); + const child2 = new GraphNode(); + root.addChild(child1); + child1.addChild(child2); + const visited = []; + child2.forEachParent((node) => { + visited.push(node); + }); + expect(visited).to.be.an('array').with.lengthOf(3); + expect(visited[0]).to.equal(child2); + expect(visited[1]).to.equal(child1); + expect(visited[2]).to.equal(root); + }); + + }); + describe('#getEulerAngles()', function () { it('returns the euler angles', function () { From cffd4dc13e0f08708d189aa3faf8f663e8f426b0 Mon Sep 17 00:00:00 2001 From: NewboO Date: Fri, 2 Sep 2022 16:43:25 +0200 Subject: [PATCH 17/26] Simplifying a bit more some find functions --- src/scene/graph-node.js | 69 +++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 41 deletions(-) diff --git a/src/scene/graph-node.js b/src/scene/graph-node.js index cfbb933b0a4..fa508e88049 100644 --- a/src/scene/graph-node.js +++ b/src/scene/graph-node.js @@ -22,12 +22,19 @@ const invParentRot = new Quat(); const matrix = new Mat4(); const target = new Vec3(); const up = new Vec3(); -const checkAttr = (node, attr, value) => { - if (node[attr] instanceof Function) - return node[attr]() === value; - return node[attr] === value; -}; +function _createTest(attr, value) { + if (attr instanceof Function) { + return attr; + } + return (node) => { + let x = node[attr]; + if (x instanceof Function) { + x = x(); + } + return x === value; + }; +} /** * Callback used by {@link GraphNode#find}, {@link GraphNode#findOne}, {@link GraphNode#findParents} @@ -495,18 +502,12 @@ class GraphNode extends EventHandler { */ find(attr, value) { const results = []; + const test = _createTest(attr, value); - if (attr instanceof Function) { - this.forEach((node) => { - if (attr(node)) - results.push(node); - }); - } else { - this.forEach((node) => { - if (checkAttr(node, attr, value)) - results.push(node); - }); - } + this.forEach((node) => { + if (test(node)) + results.push(node); + }); return results; } @@ -536,17 +537,14 @@ class GraphNode extends EventHandler { * var node = parent.findOne('name', 'Test'); */ findOne(attr, value) { + const test = _createTest(attr, value); const len = this._children.length; - if (attr instanceof Function) { - if (attr(this)) - return this; - } else if (checkAttr(this, attr, value)) { + if (test(this)) return this; - } for (let i = 0; i < len; ++i) { - const result = this._children[i].findOne(attr, value); + const result = this._children[i].findOne(test); if (result) return result; } @@ -579,18 +577,12 @@ class GraphNode extends EventHandler { */ findParents(attr, value) { const results = []; + const test = _createTest(attr, value); - if (attr instanceof Function) { - this.forEachParent((node) => { - if (attr(node)) - results.push(node); - }); - } else { - this.forEachParent((node) => { - if (checkAttr(node, attr, value)) - results.push(node); - }); - } + this.forEachParent((node) => { + if (test(node)) + results.push(node); + }); return results; } @@ -620,16 +612,11 @@ class GraphNode extends EventHandler { * var node = parent.findOneParent('name', 'Test'); */ findOneParent(attr, value) { - let testNode, current = this; - - if (attr instanceof Function) { - testNode = node => attr(node); - } else { - testNode = node => checkAttr(node, attr, value); - } + const test = _createTest(attr, value); + let current = this; while (current) { - if (testNode(current)) + if (test(current)) return current; current = current._parent; From 582f2d4c44f233f6b25bbbeb255178854f627430 Mon Sep 17 00:00:00 2001 From: NewboO Date: Sun, 4 Sep 2022 20:57:55 +0200 Subject: [PATCH 18/26] More simplifications --- src/scene/graph-node.js | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/scene/graph-node.js b/src/scene/graph-node.js index fa508e88049..1e80b512029 100644 --- a/src/scene/graph-node.js +++ b/src/scene/graph-node.js @@ -673,13 +673,7 @@ class GraphNode extends EventHandler { * null if no node is found. */ findByName(name) { - if (this.name === name) return this; - - for (let i = 0; i < this._children.length; i++) { - const found = this._children[i].findByName(name); - if (found !== null) return found; - } - return null; + return this.findOne('name', name); } /** @@ -690,16 +684,7 @@ class GraphNode extends EventHandler { * null if no node is found. */ findParentByName(name) { - let current = this; - - while (current) { - if (current.name === name) - return current; - - current = current._parent; - } - - return null; + return this.findOneParent('name', name); } /** From 8419b282ed85f415675f80264523c6125fcfc33d Mon Sep 17 00:00:00 2001 From: NewboO Date: Wed, 7 Sep 2022 00:38:33 +0200 Subject: [PATCH 19/26] Always ignore self node + renaming parents to ancestors --- src/framework/entity.js | 72 ++++++------- src/scene/graph-node.js | 75 +++++--------- test/framework/entity.test.mjs | 184 ++++++++++++++++++--------------- test/scene/graph-node.test.mjs | 96 ++++++++--------- 4 files changed, 204 insertions(+), 223 deletions(-) diff --git a/src/framework/entity.js b/src/framework/entity.js index 5acf894b7d8..7d325c01e81 100644 --- a/src/framework/entity.js +++ b/src/framework/entity.js @@ -374,11 +374,11 @@ class Entity extends GraphNode { } /** - * Search the entity and all of its descendants for the first component of specified type. + * Search all the entity descendants for the first component of specified type. * * @param {string} type - The name of the component type to retrieve. - * @returns {Component} A component of specified type, if the entity or any of its descendants - * has one. Returns undefined otherwise. + * @returns {Component} A component of specified type, if any of entity descendants has one. + * Returns undefined otherwise. * @example * // Get the first found light component in the hierarchy tree that starts with this entity * var light = entity.findComponent("light"); @@ -391,11 +391,11 @@ class Entity extends GraphNode { } /** - * Search the entity and all of its descendants for all components of specified type. + * Search all the entity descendants for all components of specified type. * * @param {string} type - The name of the component type to retrieve. - * @returns {Component[]} All components of specified type in the entity or any of its - * descendants. Returns empty array if none found. + * @returns {Component[]} All components of specified type in all of entity descendants. + * Returns empty array if none found. * @example * // Get all light components in the hierarchy tree that starts with this entity * var lights = entity.findComponents("light"); @@ -410,11 +410,11 @@ class Entity extends GraphNode { } /** - * Search the entity and all of its descendants for the first script instance of specified type. + * Search all the entity descendants for the first script instance of specified type. * * @param {string|Class} nameOrType - The name or type of {@link ScriptType}. - * @returns {ScriptType} A script instance of specified type, if the entity or any of its descendants - * has one. Returns undefined otherwise. + * @returns {ScriptType} A script instance of specified type, if any of entity descendants has one. + * Returns undefined otherwise. * @example * // Get the first found "playerController" instance in the hierarchy tree that starts with this entity * var controller = entity.findScript("playerController"); @@ -427,11 +427,11 @@ class Entity extends GraphNode { } /** - * Search the entity and all of its descendants for all script instances of specified type. + * Search all the entity descendants for all script instances of specified type. * * @param {string|Class} nameOrType - The name or type of {@link ScriptType}. - * @returns {ScriptType[]} All script instances of specified type in the entity or any of its - * descendants. Returns empty array if none found. + * @returns {ScriptType[]} All script instances of specified type in all of entity descendants. + * Returns empty array if none found. * @example * // Get all "playerController" instances in the hierarchy tree that starts with this entity * var controllers = entity.findScripts("playerController"); @@ -446,34 +446,34 @@ class Entity extends GraphNode { } /** - * Search the entity and all of its ascendants for the first component of specified type. + * Search all the entity ascendants for the first component of specified type. * * @param {string} type - The name of the component type to retrieve. - * @returns {Component} A component of specified type, if the entity or any of its ascendants - * has one. Returns undefined otherwise. + * @returns {Component} A component of specified type, if any of entity ascendants has one. + * Returns undefined otherwise. * @example * // Get the first found light component in the ancestor tree that starts with this entity - * var light = entity.findComponentInParents("light"); + * var light = entity.findComponentInAncestors("light"); */ - findComponentInParents(type) { - const entity = this.findOneParent(function (node) { + findComponentInAncestors(type) { + const entity = this.findAncestor(function (node) { return node.c && node.c[type]; }); return entity && entity.c[type]; } /** - * Search the entity and all of its ascendants for all components of specified type. + * Search all the entity ascendants for all components of specified type. * * @param {string} type - The name of the component type to retrieve. - * @returns {Component[]} All components of specified type in the entity or any of its - * ascendants. Returns empty array if none found. + * @returns {Component[]} All components of specified type in all of entity ascendants. + * Returns empty array if none found. * @example * // Get all element components in the ancestor tree that starts with this entity - * var elements = entity.findComponentsInParents("element"); + * var elements = entity.findComponentsInAncestors("element"); */ - findComponentsInParents(type) { - const entities = this.findParents(function (node) { + findComponentsInAncestors(type) { + const entities = this.findAncestors(function (node) { return node.c && node.c[type]; }); return entities.map(function (entity) { @@ -482,34 +482,34 @@ class Entity extends GraphNode { } /** - * Search the entity and all of its ascendants for the first script instance of specified type. + * Search all the entity ascendants for the first script instance of specified type. * * @param {string|Class} nameOrType - The name or type of {@link ScriptType}. - * @returns {ScriptType} A script instance of specified type, if the entity or any of its ascendants - * has one. Returns undefined otherwise. + * @returns {ScriptType} A script instance of specified type, if any of entity ascendants has one. + * Returns undefined otherwise. * @example * // Get the first found "playerController" instance in the ancestor tree that starts with this entity - * var controller = entity.findScriptInParents("playerController"); + * var controller = entity.findScriptInAncestors("playerController"); */ - findScriptInParents(nameOrType) { - const entity = this.findOneParent(function (node) { + findScriptInAncestors(nameOrType) { + const entity = this.findAncestor(function (node) { return node.c && node.c.script && node.c.script.has(nameOrType); }); return entity && entity.c.script.get(nameOrType); } /** - * Search the entity and all of its ascendants for all script instances of specified type. + * Search all the entity ascendants for all script instances of specified type. * * @param {string|Class} nameOrType - The name or type of {@link ScriptType}. - * @returns {ScriptType[]} All script instances of specified type in the entity or any of its - * ascendants. Returns empty array if none found. + * @returns {ScriptType[]} All script instances of specified type in all of entity ascendants. + * Returns empty array if none found. * @example * // Get all "playerController" instance in the ancestor tree that starts with this entity - * var controllers = entity.findScriptsInParents("playerController"); + * var controllers = entity.findScriptsInAncestors("playerController"); */ - findScriptsInParents(nameOrType) { - const entities = this.findParents(function (node) { + findScriptsInAncestors(nameOrType) { + const entities = this.findAncestors(function (node) { return node.c && node.c.script && node.c.script.has(nameOrType); }); return entities.map(function (entity) { diff --git a/src/scene/graph-node.js b/src/scene/graph-node.js index 1e80b512029..c4d137f922b 100644 --- a/src/scene/graph-node.js +++ b/src/scene/graph-node.js @@ -478,8 +478,7 @@ class GraphNode extends EventHandler { } /** - * Search the graph node and all of its descendants for the nodes that satisfy some search - * criteria. + * Search all the graph node descendants for the nodes that satisfy some search criteria. * * @param {FindNodeCallback|string} attr - This can either be a function or a string. If it's a * function, it is executed for each descendant node to test if node satisfies the search @@ -513,8 +512,7 @@ class GraphNode extends EventHandler { } /** - * Search the graph node and all of its descendants for the first node that satisfies some - * search criteria. + * Search all the graph node descendants for the first node that satisfies some search criteria. * * @param {FindNodeCallback|string} attr - This can either be a function or a string. If it's a * function, it is executed for each descendant node to test if node satisfies the search @@ -540,10 +538,10 @@ class GraphNode extends EventHandler { const test = _createTest(attr, value); const len = this._children.length; - if (test(this)) - return this; - for (let i = 0; i < len; ++i) { + if (test(this._children[i])) + return this._children[i]; + const result = this._children[i].findOne(test); if (result) return result; @@ -553,8 +551,7 @@ class GraphNode extends EventHandler { } /** - * Search the graph node and all of its ascendants for the nodes that satisfy some search - * criteria. + * Search all the graph node ascendants for the nodes that satisfy some search criteria. * * @param {FindNodeCallback|string} attr - This can either be a function or a string. If it's a * function, it is executed for each ascendant node to test if node satisfies the search @@ -575,11 +572,11 @@ class GraphNode extends EventHandler { * // Finds all nodes that have the name property set to 'Test' * var entities = entity.findParents('name', 'Test'); */ - findParents(attr, value) { + findAncestors(attr, value) { const results = []; const test = _createTest(attr, value); - this.forEachParent((node) => { + this.forEachAncestor((node) => { if (test(node)) results.push(node); }); @@ -588,8 +585,7 @@ class GraphNode extends EventHandler { } /** - * Search the graph node and all of its ascendants for the first node that satisfies some - * search criteria. + * Search all the graph node ascendants for the first node that satisfies some search criteria. * * @param {FindNodeCallback|string} attr - This can either be a function or a string. If it's a * function, it is executed for each ascendant node to test if node satisfies the search @@ -611,9 +607,9 @@ class GraphNode extends EventHandler { * // Finds the first node that has the name property set to 'Test' * var node = parent.findOneParent('name', 'Test'); */ - findOneParent(attr, value) { + findAncestor(attr, value) { const test = _createTest(attr, value); - let current = this; + let current = this._parent; while (current) { if (test(current)) @@ -646,23 +642,8 @@ class GraphNode extends EventHandler { * // Return all assets that tagged by (`carnivore` AND `mammal`) OR (`carnivore` AND `reptile`) * var meatEatingMammalsAndReptiles = node.findByTag(["carnivore", "mammal"], ["carnivore", "reptile"]); */ - findByTag() { - const query = arguments; - const results = []; - - const queryNode = (node, checkNode) => { - if (checkNode && node.tags.has(...query)) { - results.push(node); - } - - for (let i = 0; i < node._children.length; i++) { - queryNode(node._children[i], true); - } - }; - - queryNode(this, false); - - return results; + findByTag(...query) { + return this.find(node => node.tags.has(...query)); } /** @@ -683,8 +664,8 @@ class GraphNode extends EventHandler { * @returns {GraphNode|null} The first node to be found matching the supplied name. Returns * null if no node is found. */ - findParentByName(name) { - return this.findOneParent('name', name); + findAncestorByName(name) { + return this.findAncestor('name', name); } /** @@ -718,10 +699,9 @@ class GraphNode extends EventHandler { } /** - * Executes a provided function once on this graph node and all of its descendants. + * Executes a provided function once on all of this graph node descendants. * - * @param {ForEachNodeCallback} callback - The function to execute on the graph node and each - * descendant. + * @param {ForEachNodeCallback} callback - The function to execute on each graph node descendant. * @param {object} [thisArg] - Optional value to use as this when executing callback function. * @example * // Log the path and name of each node in descendant tree starting with "parent" @@ -730,19 +710,17 @@ class GraphNode extends EventHandler { * }); */ forEach(callback, thisArg) { - callback.call(thisArg, this); - const children = this._children; for (let i = 0; i < children.length; i++) { + callback.call(thisArg, children[i]); children[i].forEach(callback, thisArg); } } /** - * Executes a provided function once on this graph node and all of its ascendants. + * Executes a provided function once on all of this graph node ascendants. * - * @param {ForEachNodeCallback} callback - The function to execute on the graph node and each - * ascendant. + * @param {ForEachNodeCallback} callback - The function to execute on each graph node ascendant. * @param {object} [thisArg] - Optional value to use as this when executing callback function. * @example * // Enable each node in ascendant tree @@ -750,8 +728,8 @@ class GraphNode extends EventHandler { * node.enabled = true; * }); */ - forEachParent(callback, thisArg) { - let current = this; + forEachAncestor(callback, thisArg) { + let current = this._parent; while (current) { callback.call(thisArg, current); @@ -770,14 +748,7 @@ class GraphNode extends EventHandler { * } */ isDescendantOf(node) { - let parent = this._parent; - while (parent) { - if (parent === node) - return true; - - parent = parent._parent; - } - return false; + return !!this.findAncestor(parent => parent === node); } /** diff --git a/test/framework/entity.test.mjs b/test/framework/entity.test.mjs index 4b55d8be7ef..f933a4ecc60 100644 --- a/test/framework/entity.test.mjs +++ b/test/framework/entity.test.mjs @@ -571,17 +571,19 @@ describe('Entity', function () { describe('#findComponent', function () { - it('finds component on single entity', function () { + it('does not find component on single entity', function () { const e = new Entity(); e.addComponent('anim'); const component = e.findComponent('anim'); - expect(component).to.be.an.instanceof(AnimComponent); + expect(component).to.be.null; }); it('returns null when component is not found', function () { - const e = new Entity(); - e.addComponent('anim'); - const component = e.findComponent('render'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + child.addComponent('anim'); + const component = root.findComponent('render'); expect(component).to.be.null; }); @@ -618,19 +620,20 @@ describe('Entity', function () { describe('#findComponents', function () { - it('finds components on single entity', function () { + it('does not find components on single entity', function () { const e = new Entity(); e.addComponent('anim'); const components = e.findComponents('anim'); expect(components).to.be.an('array'); - expect(components.length).to.equal(1); - expect(components[0]).to.be.an.instanceof(AnimComponent); + expect(components.length).to.equal(0); }); it('returns empty array when no components are found', function () { - const e = new Entity(); - e.addComponent('anim'); - const components = e.findComponents('render'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + child.addComponent('anim'); + const components = root.findComponents('render'); expect(components).to.be.an('array'); expect(components.length).to.equal(0); }); @@ -657,10 +660,9 @@ describe('Entity', function () { grandchild.addComponent('anim'); const components = root.findComponents('anim'); expect(components).to.be.an('array'); - expect(components.length).to.equal(3); + expect(components.length).to.equal(2); expect(components[0]).to.be.an.instanceof(AnimComponent); expect(components[1]).to.be.an.instanceof(AnimComponent); - expect(components[2]).to.be.an.instanceof(AnimComponent); }); it('does not find components on parent entity', function () { @@ -677,25 +679,29 @@ describe('Entity', function () { describe('#findScript', function () { - it('finds script on single entity', function () { - const MyScript = createScript('myScript'); + it('does not find script on single entity', function () { + createScript('myScript'); const e = new Entity(); e.addComponent('script'); e.script.create('myScript'); const script = e.findScript('myScript'); - expect(script).to.be.an.instanceof(MyScript); + expect(script).to.be.null; }); it('returns null when script is not found', function () { - const e = new Entity(); - e.addComponent('script'); - const script = e.findScript('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + child.addComponent('script'); + const script = root.findScript('myScript'); expect(script).to.be.null; }); it('returns null when script component is not found', function () { - const e = new Entity(); - const script = e.findScript('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + const script = root.findScript('myScript'); expect(script).to.be.null; }); @@ -738,28 +744,31 @@ describe('Entity', function () { describe('#findScripts', function () { - it('finds scripts on single entity', function () { - const MyScript = createScript('myScript'); + it('does not find scripts on single entity', function () { + createScript('myScript'); const e = new Entity(); e.addComponent('script'); e.script.create('myScript'); const scripts = e.findScripts('myScript'); expect(scripts).to.be.an('array'); - expect(scripts.length).to.equal(1); - expect(scripts[0]).to.be.an.instanceof(MyScript); + expect(scripts.length).to.equal(0); }); it('returns empty array when no scripts are found', function () { - const e = new Entity(); - e.addComponent('script'); - const scripts = e.findScripts('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + child.addComponent('script'); + const scripts = root.findScripts('myScript'); expect(scripts).to.be.an('array'); expect(scripts.length).to.equal(0); }); it('returns empty array when no script component are found', function () { - const e = new Entity(); - const scripts = e.findScripts('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + const scripts = root.findScripts('myScript'); expect(scripts).to.be.an('array'); expect(scripts.length).to.equal(0); }); @@ -792,10 +801,9 @@ describe('Entity', function () { grandchild.script.create('myScript'); const scripts = root.findScripts('myScript'); expect(scripts).to.be.an('array'); - expect(scripts.length).to.equal(3); + expect(scripts.length).to.equal(2); expect(scripts[0]).to.be.an.instanceof(MyScript); expect(scripts[1]).to.be.an.instanceof(MyScript); - expect(scripts[2]).to.be.an.instanceof(MyScript); }); it('does not find scripts on parent entity', function () { @@ -812,19 +820,21 @@ describe('Entity', function () { }); - describe('#findComponentInParents', function () { + describe('#findComponentInAncestors', function () { - it('finds component on single entity', function () { + it('does not find component on single entity', function () { const e = new Entity(); e.addComponent('anim'); - const component = e.findComponentInParents('anim'); - expect(component).to.be.an.instanceof(AnimComponent); + const component = e.findComponentInAncestors('anim'); + expect(component).to.be.null; }); it('returns null when component is not found', function () { - const e = new Entity(); - e.addComponent('anim'); - const component = e.findComponentInParents('render'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + root.addComponent('anim'); + const component = child.findComponentInAncestors('render'); expect(component).to.be.null; }); @@ -833,7 +843,7 @@ describe('Entity', function () { const child = new Entity(); root.addChild(child); root.addComponent('anim'); - const component = child.findComponentInParents('anim'); + const component = child.findComponentInAncestors('anim'); expect(component).to.be.an.instanceof(AnimComponent); }); @@ -844,7 +854,7 @@ describe('Entity', function () { root.addChild(child); child.addChild(grandchild); root.addComponent('anim'); - const component = grandchild.findComponentInParents('anim'); + const component = grandchild.findComponentInAncestors('anim'); expect(component).to.be.an.instanceof(AnimComponent); }); @@ -853,27 +863,28 @@ describe('Entity', function () { const child = new Entity(); root.addChild(child); child.addComponent('anim'); - const component = root.findComponentInParents('anim'); + const component = root.findComponentInAncestors('anim'); expect(component).to.be.null; }); }); - describe('#findComponentsInParents', function () { + describe('#findComponentsInAncestors', function () { - it('finds components on single entity', function () { + it('does not find components on single entity', function () { const e = new Entity(); e.addComponent('anim'); - const components = e.findComponentsInParents('anim'); + const components = e.findComponentsInAncestors('anim'); expect(components).to.be.an('array'); - expect(components.length).to.equal(1); - expect(components[0]).to.be.an.instanceof(AnimComponent); + expect(components.length).to.equal(0); }); it('returns empty array when no components are found', function () { - const e = new Entity(); - e.addComponent('anim'); - const components = e.findComponentsInParents('render'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + root.addComponent('anim'); + const components = root.findComponentsInAncestors('render'); expect(components).to.be.an('array'); expect(components.length).to.equal(0); }); @@ -883,7 +894,7 @@ describe('Entity', function () { const child = new Entity(); root.addChild(child); root.addComponent('anim'); - const components = child.findComponentsInParents('anim'); + const components = child.findComponentsInAncestors('anim'); expect(components).to.be.an('array'); expect(components.length).to.equal(1); expect(components[0]).to.be.an.instanceof(AnimComponent); @@ -898,12 +909,11 @@ describe('Entity', function () { root.addComponent('anim'); child.addComponent('anim'); grandchild.addComponent('anim'); - const components = grandchild.findComponentsInParents('anim'); + const components = grandchild.findComponentsInAncestors('anim'); expect(components).to.be.an('array'); - expect(components.length).to.equal(3); + expect(components.length).to.equal(2); expect(components[0]).to.be.an.instanceof(AnimComponent); expect(components[1]).to.be.an.instanceof(AnimComponent); - expect(components[2]).to.be.an.instanceof(AnimComponent); }); it('does not find components on child entity', function () { @@ -911,34 +921,38 @@ describe('Entity', function () { const child = new Entity(); root.addChild(child); child.addComponent('anim'); - const components = root.findComponentsInParents('anim'); + const components = root.findComponentsInAncestors('anim'); expect(components).to.be.an('array'); expect(components.length).to.equal(0); }); }); - describe('#findScriptInParents', function () { + describe('#findScriptInAncestors', function () { - it('finds script on single entity', function () { - const MyScript = createScript('myScript'); + it('does not find script on single entity', function () { + createScript('myScript'); const e = new Entity(); e.addComponent('script'); e.script.create('myScript'); - const script = e.findScriptInParents('myScript'); - expect(script).to.be.an.instanceof(MyScript); + const script = e.findScriptInAncestors('myScript'); + expect(script).to.be.null; }); it('returns null when script is not found', function () { - const e = new Entity(); - e.addComponent('script'); - const script = e.findScriptInParents('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + root.addComponent('script'); + const script = child.findScriptInAncestors('myScript'); expect(script).to.be.null; }); it('returns null when script component is not found', function () { - const e = new Entity(); - const script = e.findScriptInParents('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + const script = child.findScriptInAncestors('myScript'); expect(script).to.be.null; }); @@ -949,7 +963,7 @@ describe('Entity', function () { root.addChild(child); root.addComponent('script'); root.script.create('myScript'); - const script = child.findScriptInParents('myScript'); + const script = child.findScriptInAncestors('myScript'); expect(script).to.be.an.instanceof(MyScript); }); @@ -962,7 +976,7 @@ describe('Entity', function () { child.addChild(grandchild); root.addComponent('script'); root.script.create('myScript'); - const script = grandchild.findScriptInParents('myScript'); + const script = grandchild.findScriptInAncestors('myScript'); expect(script).to.be.an.instanceof(MyScript); }); @@ -973,36 +987,39 @@ describe('Entity', function () { root.addChild(child); child.addComponent('script'); child.script.create('myScript'); - const script = root.findScriptInParents('myScript'); + const script = root.findScriptInAncestors('myScript'); expect(script).to.be.null; }); }); - describe('#findScriptsInParents', function () { + describe('#findScriptsInAncestors', function () { - it('finds scripts on single entity', function () { - const MyScript = createScript('myScript'); + it('does not find scripts on single entity', function () { + createScript('myScript'); const e = new Entity(); e.addComponent('script'); e.script.create('myScript'); - const scripts = e.findScriptsInParents('myScript'); + const scripts = e.findScriptsInAncestors('myScript'); expect(scripts).to.be.an('array'); - expect(scripts.length).to.equal(1); - expect(scripts[0]).to.be.an.instanceof(MyScript); + expect(scripts.length).to.equal(0); }); it('returns empty array when no scripts are found', function () { - const e = new Entity(); - e.addComponent('script'); - const scripts = e.findScriptsInParents('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + root.addComponent('script'); + const scripts = child.findScriptsInAncestors('myScript'); expect(scripts).to.be.an('array'); expect(scripts.length).to.equal(0); }); it('returns empty array when no script component are found', function () { - const e = new Entity(); - const scripts = e.findScriptsInParents('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + const scripts = child.findScriptsInAncestors('myScript'); expect(scripts).to.be.an('array'); expect(scripts.length).to.equal(0); }); @@ -1014,7 +1031,7 @@ describe('Entity', function () { root.addChild(child); root.addComponent('script'); root.script.create('myScript'); - const scripts = child.findScriptsInParents('myScript'); + const scripts = child.findScriptsInAncestors('myScript'); expect(scripts).to.be.an('array'); expect(scripts.length).to.equal(1); expect(scripts[0]).to.be.an.instanceof(MyScript); @@ -1033,12 +1050,11 @@ describe('Entity', function () { child.script.create('myScript'); grandchild.addComponent('script'); grandchild.script.create('myScript'); - const scripts = grandchild.findScriptsInParents('myScript'); + const scripts = grandchild.findScriptsInAncestors('myScript'); expect(scripts).to.be.an('array'); - expect(scripts.length).to.equal(3); + expect(scripts.length).to.equal(2); expect(scripts[0]).to.be.an.instanceof(MyScript); expect(scripts[1]).to.be.an.instanceof(MyScript); - expect(scripts[2]).to.be.an.instanceof(MyScript); }); it('does not find scripts on child entity', function () { @@ -1048,7 +1064,7 @@ describe('Entity', function () { root.addChild(child); child.addComponent('script'); child.script.create('myScript'); - const scripts = root.findScriptsInParents('myScript'); + const scripts = root.findScriptsInAncestors('myScript'); expect(scripts).to.be.an('array'); expect(scripts.length).to.equal(0); }); diff --git a/test/scene/graph-node.test.mjs b/test/scene/graph-node.test.mjs index 9d90cffa82a..1dcdb8503b2 100644 --- a/test/scene/graph-node.test.mjs +++ b/test/scene/graph-node.test.mjs @@ -216,8 +216,7 @@ describe('GraphNode', function () { let res; res = root.find('name', 'Untitled'); - expect(res).to.be.an('array').with.lengthOf(1); - expect(res[0]).to.equal(root); + expect(res).to.be.an('array').with.lengthOf(0); res = root.find('name', 'Child'); expect(res).to.be.an('array').with.lengthOf(1); @@ -236,8 +235,7 @@ describe('GraphNode', function () { res = root.find(function (node) { return node.name === 'Untitled'; }); - expect(res).to.be.an('array').with.lengthOf(1); - expect(res[0]).to.equal(root); + expect(res).to.be.an('array').with.lengthOf(0); res = root.find(function (node) { return node.name === 'Child'; @@ -255,11 +253,11 @@ describe('GraphNode', function () { describe('#findByName()', function () { - it('finds root by name', function () { + it('does not search the root node', function () { const root = new GraphNode('root'); const child = new GraphNode('child'); root.addChild(child); - expect(root.findByName('root')).to.equal(root); + expect(root.findByName('root')).to.be.null; }); it('finds child by name', function () { @@ -273,7 +271,7 @@ describe('GraphNode', function () { const root = new GraphNode('root'); const child = new GraphNode('child'); root.addChild(child); - expect(root.findByName('not-found')).to.equal(null); + expect(root.findByName('not-found')).to.be.null; }); }); @@ -309,7 +307,7 @@ describe('GraphNode', function () { const root = new GraphNode('root'); const child = new GraphNode('child'); root.addChild(child); - expect(root.findByPath('not-found')).to.equal(null); + expect(root.findByPath('not-found')).to.be.null; }); }); @@ -380,7 +378,7 @@ describe('GraphNode', function () { let res; res = root.findOne('name', 'Untitled'); - expect(res).to.equal(root); + expect(res).to.be.null; res = root.findOne('name', 'Child'); expect(res).to.equal(child); @@ -398,7 +396,7 @@ describe('GraphNode', function () { res = root.findOne(function (node) { return node.name === 'Untitled'; }); - expect(res).to.equal(root); + expect(res).to.be.null; res = root.findOne(function (node) { return node.name === 'Child'; @@ -413,7 +411,7 @@ describe('GraphNode', function () { }); - describe('#findParents()', function () { + describe('#findAncestors()', function () { it('finds a node by property', function () { const root = new GraphNode('Parent'); @@ -421,15 +419,14 @@ describe('GraphNode', function () { root.addChild(child); let res; - res = child.findParents('name', 'Parent'); + res = child.findAncestors('name', 'Parent'); expect(res).to.be.an('array').with.lengthOf(1); expect(res[0]).to.equal(root); - res = child.findParents('name', 'Child'); - expect(res).to.be.an('array').with.lengthOf(1); - expect(res[0]).to.equal(child); + res = child.findAncestors('name', 'Child'); + expect(res).to.be.an('array').with.lengthOf(0); - res = child.findParents('name', 'Not Found'); + res = child.findAncestors('name', 'Not Found'); expect(res).to.be.an('array').with.lengthOf(0); }); @@ -439,19 +436,18 @@ describe('GraphNode', function () { root.addChild(child); let res; - res = child.findParents(function (node) { + res = child.findAncestors(function (node) { return node.name === 'Parent'; }); expect(res).to.be.an('array').with.lengthOf(1); expect(res[0]).to.equal(root); - res = child.findParents(function (node) { + res = child.findAncestors(function (node) { return node.name === 'Child'; }); - expect(res).to.be.an('array').with.lengthOf(1); - expect(res[0]).to.equal(child); + expect(res).to.be.an('array').with.lengthOf(0); - res = child.findParents(function (node) { + res = child.findAncestors(function (node) { return node.name === 'Not Found'; }); expect(res).to.be.an('array').with.lengthOf(0); @@ -459,7 +455,7 @@ describe('GraphNode', function () { }); - describe('#findOneParent()', function () { + describe('#findAncestor()', function () { it('finds a node by property', function () { const root = new GraphNode('Parent'); @@ -467,13 +463,13 @@ describe('GraphNode', function () { root.addChild(child); let res; - res = child.findOneParent('name', 'Parent'); + res = child.findAncestor('name', 'Parent'); expect(res).to.equal(root); - res = child.findOneParent('name', 'Child'); - expect(res).to.equal(child); + res = child.findAncestor('name', 'Child'); + expect(res).to.be.null; - res = child.findOneParent('name', 'Not Found'); + res = child.findAncestor('name', 'Not Found'); expect(res).to.be.null; }); @@ -483,17 +479,17 @@ describe('GraphNode', function () { root.addChild(child); let res; - res = child.findOneParent(function (node) { + res = child.findAncestor(function (node) { return node.name === 'Parent'; }); expect(res).to.equal(root); - res = child.findOneParent(function (node) { + res = child.findAncestor(function (node) { return node.name === 'Child'; }); - expect(res).to.equal(child); + expect(res).to.be.null; - res = child.findOneParent(function (node) { + res = child.findAncestor(function (node) { return node.name === 'Not Found'; }); expect(res).to.be.null; @@ -501,34 +497,34 @@ describe('GraphNode', function () { }); - describe('#findParentByName()', function () { + describe('#findAncestorByName()', function () { it('finds root by name', function () { const root = new GraphNode('root'); const child = new GraphNode('child'); root.addChild(child); - expect(child.findParentByName('root')).to.equal(root); + expect(child.findAncestorByName('root')).to.equal(root); }); - it('finds child by name', function () { + it('does not search the child node', function () { const root = new GraphNode('root'); const child = new GraphNode('child'); root.addChild(child); - expect(child.findParentByName('child')).to.equal(child); + expect(child.findAncestorByName('child')).to.be.null; }); it('returns null if no node is found', function () { const root = new GraphNode('root'); const child = new GraphNode('child'); root.addChild(child); - expect(child.findParentByName('not-found')).to.equal(null); + expect(child.findAncestorByName('not-found')).to.be.null; }); }); describe('#forEach()', function () { - it('iterates over all nodes', function () { + it('iterates over all nodes but root', function () { const root = new GraphNode(); const child1 = new GraphNode(); const child2 = new GraphNode(); @@ -538,30 +534,28 @@ describe('GraphNode', function () { root.forEach((node) => { visited.push(node); }); - expect(visited).to.be.an('array').with.lengthOf(3); - expect(visited[0]).to.equal(root); - expect(visited[1]).to.equal(child1); - expect(visited[2]).to.equal(child2); + expect(visited).to.be.an('array').with.lengthOf(2); + expect(visited[0]).to.equal(child1); + expect(visited[1]).to.equal(child2); }); }); - describe('#forEachParent()', function () { + describe('#forEachAncestor()', function () { - it('iterates over all nodes', function () { + it('iterates over all nodes but grand child', function () { const root = new GraphNode(); - const child1 = new GraphNode(); - const child2 = new GraphNode(); - root.addChild(child1); - child1.addChild(child2); + const child = new GraphNode(); + const grandchild = new GraphNode(); + root.addChild(child); + child.addChild(grandchild); const visited = []; - child2.forEachParent((node) => { + grandchild.forEachAncestor((node) => { visited.push(node); }); - expect(visited).to.be.an('array').with.lengthOf(3); - expect(visited[0]).to.equal(child2); - expect(visited[1]).to.equal(child1); - expect(visited[2]).to.equal(root); + expect(visited).to.be.an('array').with.lengthOf(2); + expect(visited[0]).to.equal(child); + expect(visited[1]).to.equal(root); }); }); From 5ef124b19bbe2a58203844fc36ea258d771187e7 Mon Sep 17 00:00:00 2001 From: NewboO Date: Wed, 7 Sep 2022 21:42:08 +0200 Subject: [PATCH 20/26] Let the user choose to include self node --- src/framework/entity.js | 40 ++++++++------ src/scene/graph-node.js | 99 ++++++++++++++++++++++++---------- test/framework/entity.test.mjs | 88 +++++++++++++++++++++++++++--- test/scene/graph-node.test.mjs | 94 +++++++++++++++++++++++++++++++- 4 files changed, 267 insertions(+), 54 deletions(-) diff --git a/src/framework/entity.js b/src/framework/entity.js index 7d325c01e81..d2e14286505 100644 --- a/src/framework/entity.js +++ b/src/framework/entity.js @@ -377,16 +377,17 @@ class Entity extends GraphNode { * Search all the entity descendants for the first component of specified type. * * @param {string} type - The name of the component type to retrieve. + * @param {boolean} [includeSelf=true] - True to include self entity in the search. * @returns {Component} A component of specified type, if any of entity descendants has one. * Returns undefined otherwise. * @example * // Get the first found light component in the hierarchy tree that starts with this entity * var light = entity.findComponent("light"); */ - findComponent(type) { + findComponent(type, includeSelf = true) { const entity = this.findOne(function (node) { return node.c && node.c[type]; - }); + }, includeSelf); return entity && entity.c[type]; } @@ -394,16 +395,17 @@ class Entity extends GraphNode { * Search all the entity descendants for all components of specified type. * * @param {string} type - The name of the component type to retrieve. + * @param {boolean} [includeSelf=true] - True to include self entity in the search. * @returns {Component[]} All components of specified type in all of entity descendants. * Returns empty array if none found. * @example * // Get all light components in the hierarchy tree that starts with this entity * var lights = entity.findComponents("light"); */ - findComponents(type) { + findComponents(type, includeSelf = true) { const entities = this.find(function (node) { return node.c && node.c[type]; - }); + }, includeSelf); return entities.map(function (entity) { return entity.c[type]; }); @@ -413,16 +415,17 @@ class Entity extends GraphNode { * Search all the entity descendants for the first script instance of specified type. * * @param {string|Class} nameOrType - The name or type of {@link ScriptType}. + * @param {boolean} [includeSelf=true] - True to include self entity in the search. * @returns {ScriptType} A script instance of specified type, if any of entity descendants has one. * Returns undefined otherwise. * @example * // Get the first found "playerController" instance in the hierarchy tree that starts with this entity * var controller = entity.findScript("playerController"); */ - findScript(nameOrType) { + findScript(nameOrType, includeSelf = true) { const entity = this.findOne(function (node) { return node.c && node.c.script && node.c.script.has(nameOrType); - }); + }, includeSelf); return entity && entity.c.script.get(nameOrType); } @@ -430,16 +433,17 @@ class Entity extends GraphNode { * Search all the entity descendants for all script instances of specified type. * * @param {string|Class} nameOrType - The name or type of {@link ScriptType}. + * @param {boolean} [includeSelf=true] - True to include self entity in the search. * @returns {ScriptType[]} All script instances of specified type in all of entity descendants. * Returns empty array if none found. * @example * // Get all "playerController" instances in the hierarchy tree that starts with this entity * var controllers = entity.findScripts("playerController"); */ - findScripts(nameOrType) { + findScripts(nameOrType, includeSelf = true) { const entities = this.find(function (node) { return node.c && node.c.script && node.c.script.has(nameOrType); - }); + }, includeSelf); return entities.map(function (entity) { return entity.c.script.get(nameOrType); }); @@ -449,16 +453,17 @@ class Entity extends GraphNode { * Search all the entity ascendants for the first component of specified type. * * @param {string} type - The name of the component type to retrieve. + * @param {boolean} [includeSelf=false] - True to include self entity in the search. * @returns {Component} A component of specified type, if any of entity ascendants has one. * Returns undefined otherwise. * @example * // Get the first found light component in the ancestor tree that starts with this entity * var light = entity.findComponentInAncestors("light"); */ - findComponentInAncestors(type) { + findComponentInAncestors(type, includeSelf = false) { const entity = this.findAncestor(function (node) { return node.c && node.c[type]; - }); + }, includeSelf); return entity && entity.c[type]; } @@ -466,16 +471,17 @@ class Entity extends GraphNode { * Search all the entity ascendants for all components of specified type. * * @param {string} type - The name of the component type to retrieve. + * @param {boolean} [includeSelf=false] - True to include self entity in the search. * @returns {Component[]} All components of specified type in all of entity ascendants. * Returns empty array if none found. * @example * // Get all element components in the ancestor tree that starts with this entity * var elements = entity.findComponentsInAncestors("element"); */ - findComponentsInAncestors(type) { + findComponentsInAncestors(type, includeSelf = false) { const entities = this.findAncestors(function (node) { return node.c && node.c[type]; - }); + }, includeSelf); return entities.map(function (entity) { return entity.c[type]; }); @@ -485,16 +491,17 @@ class Entity extends GraphNode { * Search all the entity ascendants for the first script instance of specified type. * * @param {string|Class} nameOrType - The name or type of {@link ScriptType}. + * @param {boolean} [includeSelf=false] - True to include self entity in the search. * @returns {ScriptType} A script instance of specified type, if any of entity ascendants has one. * Returns undefined otherwise. * @example * // Get the first found "playerController" instance in the ancestor tree that starts with this entity * var controller = entity.findScriptInAncestors("playerController"); */ - findScriptInAncestors(nameOrType) { + findScriptInAncestors(nameOrType, includeSelf = false) { const entity = this.findAncestor(function (node) { return node.c && node.c.script && node.c.script.has(nameOrType); - }); + }, includeSelf); return entity && entity.c.script.get(nameOrType); } @@ -502,16 +509,17 @@ class Entity extends GraphNode { * Search all the entity ascendants for all script instances of specified type. * * @param {string|Class} nameOrType - The name or type of {@link ScriptType}. + * @param {boolean} [includeSelf=false] - True to include self entity in the search. * @returns {ScriptType[]} All script instances of specified type in all of entity ascendants. * Returns empty array if none found. * @example * // Get all "playerController" instance in the ancestor tree that starts with this entity * var controllers = entity.findScriptsInAncestors("playerController"); */ - findScriptsInAncestors(nameOrType) { + findScriptsInAncestors(nameOrType, includeSelf = false) { const entities = this.findAncestors(function (node) { return node.c && node.c.script && node.c.script.has(nameOrType); - }); + }, includeSelf); return entities.map(function (entity) { return entity.c.script.get(nameOrType); }); diff --git a/src/scene/graph-node.js b/src/scene/graph-node.js index c4d137f922b..a0d1a393520 100644 --- a/src/scene/graph-node.js +++ b/src/scene/graph-node.js @@ -36,6 +36,13 @@ function _createTest(attr, value) { }; } +function _getIncludeSelf(attr, value, includeSelf) { + if (attr instanceof Function && typeof value === 'boolean') + return value; + + return includeSelf; +} + /** * Callback used by {@link GraphNode#find}, {@link GraphNode#findOne}, {@link GraphNode#findParents} * and {@link GraphNode#findOneParent} to search through a graph node and all of its descendants or ascendants. @@ -487,8 +494,10 @@ class GraphNode extends EventHandler { * of a field then the value passed as the second argument will be checked for equality. If * this is the name of a function then the return value of the function will be checked for * equality against the valued passed as the second argument to this function. - * @param {object} [value] - If the first argument (attr) is a property name then this value - * will be checked against the value of the property. + * @param {*} [value] - If the first argument (attr) is a property name then this value + * will be checked against the value of the property. If the first argument (attr) is a function + * then this argument can be skipped. + * @param {boolean} [includeSelf=true] - True to include self node in the search. * @returns {GraphNode[]} The array of graph nodes that match the search criteria. * @example * // Finds all nodes that have a model component and have `door` in their lower-cased name @@ -499,14 +508,14 @@ class GraphNode extends EventHandler { * // Finds all nodes that have the name property set to 'Test' * var entities = parent.find('name', 'Test'); */ - find(attr, value) { + find(attr, value, includeSelf = true) { const results = []; const test = _createTest(attr, value); this.forEach((node) => { if (test(node)) results.push(node); - }); + }, _getIncludeSelf(attr, value, includeSelf)); return results; } @@ -521,8 +530,10 @@ class GraphNode extends EventHandler { * this is the name of a field then the value passed as the second argument will be checked for * equality. If this is the name of a function then the return value of the function will be * checked for equality against the valued passed as the second argument to this function. - * @param {object} [value] - If the first argument (attr) is a property name then this value - * will be checked against the value of the property. + * @param {*} [value] - If the first argument (attr) is a property name then this value + * will be checked against the value of the property. If the first argument (attr) is a function + * then this argument can be skipped. + * @param {boolean} [includeSelf=true] - True to include self node in the search. * @returns {GraphNode|null} A graph node that match the search criteria. Returns null if no * node is found. * @example @@ -534,15 +545,15 @@ class GraphNode extends EventHandler { * // Finds the first node that has the name property set to 'Test' * var node = parent.findOne('name', 'Test'); */ - findOne(attr, value) { + findOne(attr, value, includeSelf = true) { const test = _createTest(attr, value); const len = this._children.length; - for (let i = 0; i < len; ++i) { - if (test(this._children[i])) - return this._children[i]; + if (_getIncludeSelf(attr, value, includeSelf) && test(this)) + return this; - const result = this._children[i].findOne(test); + for (let i = 0; i < len; ++i) { + const result = this._children[i].findOne(test, true); if (result) return result; } @@ -560,8 +571,10 @@ class GraphNode extends EventHandler { * of a field then the value passed as the second argument will be checked for equality. If * this is the name of a function then the return value of the function will be checked for * equality against the valued passed as the second argument to this function. - * @param {object} [value] - If the first argument (attr) is a property name then this value - * will be checked against the value of the property. + * @param {*} [value] - If the first argument (attr) is a property name then this value + * will be checked against the value of the property. If the first argument (attr) is a function + * then this argument can be skipped. + * @param {boolean} [includeSelf=false] - True to include self node in the search. * @returns {GraphNode[]} The array of graph nodes that match the search criteria. * @example * // Finds all nodes that have a group element component @@ -572,14 +585,14 @@ class GraphNode extends EventHandler { * // Finds all nodes that have the name property set to 'Test' * var entities = entity.findParents('name', 'Test'); */ - findAncestors(attr, value) { + findAncestors(attr, value, includeSelf = false) { const results = []; const test = _createTest(attr, value); this.forEachAncestor((node) => { if (test(node)) results.push(node); - }); + }, _getIncludeSelf(attr, value, includeSelf)); return results; } @@ -594,8 +607,10 @@ class GraphNode extends EventHandler { * this is the name of a field then the value passed as the second argument will be checked for * equality. If this is the name of a function then the return value of the function will be * checked for equality against the valued passed as the second argument to this function. - * @param {object} [value] - If the first argument (attr) is a property name then this value - * will be checked against the value of the property. + * @param {*} [value] - If the first argument (attr) is a property name then this value + * will be checked against the value of the property. If the first argument (attr) is a function + * then this argument can be skipped. + * @param {boolean} [includeSelf=false] - True to include self node in the search. * @returns {GraphNode|null} A graph node that match the search criteria. Returns null if no * node is found. * @example @@ -607,9 +622,12 @@ class GraphNode extends EventHandler { * // Finds the first node that has the name property set to 'Test' * var node = parent.findOneParent('name', 'Test'); */ - findAncestor(attr, value) { + findAncestor(attr, value, includeSelf = false) { const test = _createTest(attr, value); - let current = this._parent; + + let current = this; + if (!_getIncludeSelf(attr, value, includeSelf)) + current = current._parent; while (current) { if (test(current)) @@ -628,6 +646,7 @@ class GraphNode extends EventHandler { * of array. * * @param {...*} query - Name of a tag or array of tags. + * @param {boolean} [includeSelf=false] - True to include self node in the search. * @returns {GraphNode[]} A list of all graph nodes that match the query. * @example * // Return all graph nodes that tagged by `animal` @@ -643,29 +662,35 @@ class GraphNode extends EventHandler { * var meatEatingMammalsAndReptiles = node.findByTag(["carnivore", "mammal"], ["carnivore", "reptile"]); */ findByTag(...query) { - return this.find(node => node.tags.has(...query)); + let includeSelf = false; + if (typeof query[query.length - 1] === 'boolean') + includeSelf = query.pop(); + + return this.find(node => node.tags.has(...query), includeSelf); } /** * Get the first node found in the graph with the name. The search is depth first. * * @param {string} name - The name of the graph. + * @param {boolean} [includeSelf=true] - True to include self node in the search. * @returns {GraphNode|null} The first node to be found matching the supplied name. Returns * null if no node is found. */ - findByName(name) { - return this.findOne('name', name); + findByName(name, includeSelf = true) { + return this.findOne('name', name, includeSelf); } /** * Get the first ancestor node found in the graph with the name. * * @param {string} name - The name of the graph. + * @param {boolean} [includeSelf=false] - True to include self node in the search. * @returns {GraphNode|null} The first node to be found matching the supplied name. Returns * null if no node is found. */ - findAncestorByName(name) { - return this.findAncestor('name', name); + findAncestorByName(name, includeSelf = false) { + return this.findAncestor('name', name, includeSelf); } /** @@ -703,17 +728,25 @@ class GraphNode extends EventHandler { * * @param {ForEachNodeCallback} callback - The function to execute on each graph node descendant. * @param {object} [thisArg] - Optional value to use as this when executing callback function. + * @param {boolean} [includeSelf=true] - True to also execute function on self node. * @example * // Log the path and name of each node in descendant tree starting with "parent" * parent.forEach(function (node) { * console.log(node.path + "/" + node.name); * }); */ - forEach(callback, thisArg) { + forEach(callback, thisArg, includeSelf = true) { + if (typeof thisArg === 'boolean') { + includeSelf = thisArg; + thisArg = undefined; + } + const children = this._children; + if (includeSelf) + callback.call(thisArg, this); + for (let i = 0; i < children.length; i++) { - callback.call(thisArg, children[i]); - children[i].forEach(callback, thisArg); + children[i].forEach(callback, thisArg, true); } } @@ -722,14 +755,22 @@ class GraphNode extends EventHandler { * * @param {ForEachNodeCallback} callback - The function to execute on each graph node ascendant. * @param {object} [thisArg] - Optional value to use as this when executing callback function. + * @param {boolean} [includeSelf=false] - True to also execute function on self node. * @example * // Enable each node in ascendant tree * current.forEachParent(function (node) { * node.enabled = true; * }); */ - forEachAncestor(callback, thisArg) { - let current = this._parent; + forEachAncestor(callback, thisArg, includeSelf = false) { + if (typeof thisArg === 'boolean') { + includeSelf = thisArg; + thisArg = undefined; + } + + let current = this; + if (!includeSelf) + current = this._parent; while (current) { callback.call(thisArg, current); diff --git a/test/framework/entity.test.mjs b/test/framework/entity.test.mjs index f933a4ecc60..510af0760eb 100644 --- a/test/framework/entity.test.mjs +++ b/test/framework/entity.test.mjs @@ -571,10 +571,17 @@ describe('Entity', function () { describe('#findComponent', function () { - it('does not find component on single entity', function () { + it('find component on single entity', function () { const e = new Entity(); e.addComponent('anim'); const component = e.findComponent('anim'); + expect(component).to.be.an.instanceof(AnimComponent); + }); + + it('does not find component on single entity when excluded', function () { + const e = new Entity(); + e.addComponent('anim'); + const component = e.findComponent('anim', false); expect(component).to.be.null; }); @@ -625,6 +632,15 @@ describe('Entity', function () { e.addComponent('anim'); const components = e.findComponents('anim'); expect(components).to.be.an('array'); + expect(components.length).to.equal(1); + expect(components[0]).to.be.an.instanceof(AnimComponent); + }); + + it('does not find components on single entity when excluded', function () { + const e = new Entity(); + e.addComponent('anim'); + const components = e.findComponents('anim', false); + expect(components).to.be.an('array'); expect(components.length).to.equal(0); }); @@ -660,9 +676,10 @@ describe('Entity', function () { grandchild.addComponent('anim'); const components = root.findComponents('anim'); expect(components).to.be.an('array'); - expect(components.length).to.equal(2); + expect(components.length).to.equal(3); expect(components[0]).to.be.an.instanceof(AnimComponent); expect(components[1]).to.be.an.instanceof(AnimComponent); + expect(components[2]).to.be.an.instanceof(AnimComponent); }); it('does not find components on parent entity', function () { @@ -679,12 +696,21 @@ describe('Entity', function () { describe('#findScript', function () { - it('does not find script on single entity', function () { - createScript('myScript'); + it('find script on single entity', function () { + const MyScript = createScript('myScript'); const e = new Entity(); e.addComponent('script'); e.script.create('myScript'); const script = e.findScript('myScript'); + expect(script).to.be.an.instanceof(MyScript); + }); + + it('does not find script on single entity when excluded', function () { + createScript('myScript'); + const e = new Entity(); + e.addComponent('script'); + e.script.create('myScript'); + const script = e.findScript('myScript', false); expect(script).to.be.null; }); @@ -744,13 +770,24 @@ describe('Entity', function () { describe('#findScripts', function () { - it('does not find scripts on single entity', function () { - createScript('myScript'); + it('find scripts on single entity', function () { + const MyScript = createScript('myScript'); const e = new Entity(); e.addComponent('script'); e.script.create('myScript'); const scripts = e.findScripts('myScript'); expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(1); + expect(scripts[0]).to.be.an.instanceof(MyScript); + }); + + it('does not find scripts on single entity when excluded', function () { + createScript('myScript'); + const e = new Entity(); + e.addComponent('script'); + e.script.create('myScript'); + const scripts = e.findScripts('myScript', false); + expect(scripts).to.be.an('array'); expect(scripts.length).to.equal(0); }); @@ -801,9 +838,10 @@ describe('Entity', function () { grandchild.script.create('myScript'); const scripts = root.findScripts('myScript'); expect(scripts).to.be.an('array'); - expect(scripts.length).to.equal(2); + expect(scripts.length).to.equal(3); expect(scripts[0]).to.be.an.instanceof(MyScript); expect(scripts[1]).to.be.an.instanceof(MyScript); + expect(scripts[2]).to.be.an.instanceof(MyScript); }); it('does not find scripts on parent entity', function () { @@ -822,6 +860,13 @@ describe('Entity', function () { describe('#findComponentInAncestors', function () { + it('find component on single entity when included', function () { + const e = new Entity(); + e.addComponent('anim'); + const component = e.findComponentInAncestors('anim', true); + expect(component).to.be.an.instanceof(AnimComponent); + }); + it('does not find component on single entity', function () { const e = new Entity(); e.addComponent('anim'); @@ -871,6 +916,15 @@ describe('Entity', function () { describe('#findComponentsInAncestors', function () { + it('find components on single entity when included', function () { + const e = new Entity(); + e.addComponent('anim'); + const components = e.findComponentsInAncestors('anim', true); + expect(components).to.be.an('array'); + expect(components.length).to.equal(1); + expect(components[0]).to.be.an.instanceof(AnimComponent); + }); + it('does not find components on single entity', function () { const e = new Entity(); e.addComponent('anim'); @@ -930,6 +984,15 @@ describe('Entity', function () { describe('#findScriptInAncestors', function () { + it('find script on single entity when included', function () { + const MyScript = createScript('myScript'); + const e = new Entity(); + e.addComponent('script'); + e.script.create('myScript'); + const script = e.findScriptInAncestors('myScript', true); + expect(script).to.be.an.instanceof(MyScript); + }); + it('does not find script on single entity', function () { createScript('myScript'); const e = new Entity(); @@ -995,6 +1058,17 @@ describe('Entity', function () { describe('#findScriptsInAncestors', function () { + it('find scripts on single entity when included', function () { + const MyScript = createScript('myScript'); + const e = new Entity(); + e.addComponent('script'); + e.script.create('myScript'); + const scripts = e.findScriptsInAncestors('myScript', true); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(1); + expect(scripts[0]).to.be.an.instanceof(MyScript); + }); + it('does not find scripts on single entity', function () { createScript('myScript'); const e = new Entity(); diff --git a/test/scene/graph-node.test.mjs b/test/scene/graph-node.test.mjs index 1dcdb8503b2..aa85d39a35a 100644 --- a/test/scene/graph-node.test.mjs +++ b/test/scene/graph-node.test.mjs @@ -216,6 +216,10 @@ describe('GraphNode', function () { let res; res = root.find('name', 'Untitled'); + expect(res).to.be.an('array').with.lengthOf(1); + expect(res[0]).to.equal(root); + + res = root.find('name', 'Untitled', false); expect(res).to.be.an('array').with.lengthOf(0); res = root.find('name', 'Child'); @@ -235,6 +239,12 @@ describe('GraphNode', function () { res = root.find(function (node) { return node.name === 'Untitled'; }); + expect(res).to.be.an('array').with.lengthOf(1); + expect(res[0]).to.equal(root); + + res = root.find(function (node) { + return node.name === 'Untitled'; + }, false); expect(res).to.be.an('array').with.lengthOf(0); res = root.find(function (node) { @@ -253,11 +263,18 @@ describe('GraphNode', function () { describe('#findByName()', function () { + it('search the root node', function () { + const root = new GraphNode('root'); + const child = new GraphNode('child'); + root.addChild(child); + expect(root.findByName('root')).to.equal(root); + }); + it('does not search the root node', function () { const root = new GraphNode('root'); const child = new GraphNode('child'); root.addChild(child); - expect(root.findByName('root')).to.be.null; + expect(root.findByName('root', false)).to.be.null; }); it('finds child by name', function () { @@ -321,6 +338,14 @@ describe('GraphNode', function () { expect(result).to.be.an('array').with.lengthOf(0); }); + it('search the root node if included', function () { + const root = new GraphNode('root'); + root.tags.add('tag'); + const result = root.findByTag('tag', true); + expect(result).to.be.an('array').with.lengthOf(1); + expect(result[0]).to.equal(root); + }); + it('returns an array of nodes that have the query tag', function () { const root = new GraphNode('root'); const child = new GraphNode('child'); @@ -378,6 +403,9 @@ describe('GraphNode', function () { let res; res = root.findOne('name', 'Untitled'); + expect(res).to.equal(root); + + res = root.findOne('name', 'Untitled', false); expect(res).to.be.null; res = root.findOne('name', 'Child'); @@ -396,6 +424,11 @@ describe('GraphNode', function () { res = root.findOne(function (node) { return node.name === 'Untitled'; }); + expect(res).to.equal(root); + + res = root.findOne(function (node) { + return node.name === 'Untitled'; + }, false); expect(res).to.be.null; res = root.findOne(function (node) { @@ -426,6 +459,10 @@ describe('GraphNode', function () { res = child.findAncestors('name', 'Child'); expect(res).to.be.an('array').with.lengthOf(0); + res = child.findAncestors('name', 'Child', true); + expect(res).to.be.an('array').with.lengthOf(1); + expect(res[0]).to.equal(child); + res = child.findAncestors('name', 'Not Found'); expect(res).to.be.an('array').with.lengthOf(0); }); @@ -447,6 +484,12 @@ describe('GraphNode', function () { }); expect(res).to.be.an('array').with.lengthOf(0); + res = child.findAncestors(function (node) { + return node.name === 'Child'; + }, true); + expect(res).to.be.an('array').with.lengthOf(1); + expect(res[0]).to.equal(child); + res = child.findAncestors(function (node) { return node.name === 'Not Found'; }); @@ -469,6 +512,9 @@ describe('GraphNode', function () { res = child.findAncestor('name', 'Child'); expect(res).to.be.null; + res = child.findAncestor('name', 'Child', true); + expect(res).to.equal(child); + res = child.findAncestor('name', 'Not Found'); expect(res).to.be.null; }); @@ -489,6 +535,11 @@ describe('GraphNode', function () { }); expect(res).to.be.null; + res = child.findAncestor(function (node) { + return node.name === 'Child'; + }, true); + expect(res).to.equal(child); + res = child.findAncestor(function (node) { return node.name === 'Not Found'; }); @@ -513,6 +564,13 @@ describe('GraphNode', function () { expect(child.findAncestorByName('child')).to.be.null; }); + it('find the child node if included', function () { + const root = new GraphNode('root'); + const child = new GraphNode('child'); + root.addChild(child); + expect(child.findAncestorByName('child', true)).to.equal(child); + }); + it('returns null if no node is found', function () { const root = new GraphNode('root'); const child = new GraphNode('child'); @@ -524,7 +582,7 @@ describe('GraphNode', function () { describe('#forEach()', function () { - it('iterates over all nodes but root', function () { + it('iterates over all nodes including root', function () { const root = new GraphNode(); const child1 = new GraphNode(); const child2 = new GraphNode(); @@ -534,6 +592,22 @@ describe('GraphNode', function () { root.forEach((node) => { visited.push(node); }); + expect(visited).to.be.an('array').with.lengthOf(3); + expect(visited[0]).to.equal(root); + expect(visited[1]).to.equal(child1); + expect(visited[2]).to.equal(child2); + }); + + it('iterates over all nodes but root', function () { + const root = new GraphNode(); + const child1 = new GraphNode(); + const child2 = new GraphNode(); + root.addChild(child1); + root.addChild(child2); + const visited = []; + root.forEach((node) => { + visited.push(node); + }, false); expect(visited).to.be.an('array').with.lengthOf(2); expect(visited[0]).to.equal(child1); expect(visited[1]).to.equal(child2); @@ -558,6 +632,22 @@ describe('GraphNode', function () { expect(visited[1]).to.equal(root); }); + it('iterates over all nodes including grand child', function () { + const root = new GraphNode(); + const child = new GraphNode(); + const grandchild = new GraphNode(); + root.addChild(child); + child.addChild(grandchild); + const visited = []; + grandchild.forEachAncestor((node) => { + visited.push(node); + }, true); + expect(visited).to.be.an('array').with.lengthOf(3); + expect(visited[0]).to.equal(grandchild); + expect(visited[1]).to.equal(child); + expect(visited[2]).to.equal(root); + }); + }); describe('#getEulerAngles()', function () { From 78b52ca263a3f84dfa6261e1ee9a7bf241c09fb0 Mon Sep 17 00:00:00 2001 From: NewboO Date: Wed, 7 Sep 2022 22:26:07 +0200 Subject: [PATCH 21/26] Doc --- src/scene/graph-node.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/scene/graph-node.js b/src/scene/graph-node.js index a0d1a393520..f4cbc21abba 100644 --- a/src/scene/graph-node.js +++ b/src/scene/graph-node.js @@ -44,17 +44,17 @@ function _getIncludeSelf(attr, value, includeSelf) { } /** - * Callback used by {@link GraphNode#find}, {@link GraphNode#findOne}, {@link GraphNode#findParents} - * and {@link GraphNode#findOneParent} to search through a graph node and all of its descendants or ascendants. + * Callback used by {@link GraphNode#find}, {@link GraphNode#findOne}, {@link GraphNode#findAncestor} + * and {@link GraphNode#findAncestors} to search through a graph node and all of its descendants or ascendants. * * @callback FindNodeCallback * @param {GraphNode} node - The current graph node. * @returns {boolean} Returning `true` will result in that node being returned from - * {@link GraphNode#find} or {@link GraphNode#findOne}. + * {@link GraphNode#find}, {@link GraphNode#findOne}, {@link GraphNode#findAncestor} or {@link GraphNode#findAncestors}. */ /** - * Callback used by {@link GraphNode#forEach} and {@link GraphNode#forEachParent} to iterate through + * Callback used by {@link GraphNode#forEach} and {@link GraphNode#forEachAncestor} to iterate through * a graph node and all of its descendants or ascendants. * * @callback ForEachNodeCallback From 6e4597b8eae7f3c2ded726a0995ac0e1cf9dc2ba Mon Sep 17 00:00:00 2001 From: NewboO Date: Wed, 7 Sep 2022 22:32:05 +0200 Subject: [PATCH 22/26] Tests descriptions --- test/framework/entity.test.mjs | 16 ++++++++-------- test/scene/graph-node.test.mjs | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/test/framework/entity.test.mjs b/test/framework/entity.test.mjs index 510af0760eb..90b4cdbd6e6 100644 --- a/test/framework/entity.test.mjs +++ b/test/framework/entity.test.mjs @@ -571,7 +571,7 @@ describe('Entity', function () { describe('#findComponent', function () { - it('find component on single entity', function () { + it('finds component on single entity', function () { const e = new Entity(); e.addComponent('anim'); const component = e.findComponent('anim'); @@ -627,7 +627,7 @@ describe('Entity', function () { describe('#findComponents', function () { - it('does not find components on single entity', function () { + it('finds components on single entity', function () { const e = new Entity(); e.addComponent('anim'); const components = e.findComponents('anim'); @@ -696,7 +696,7 @@ describe('Entity', function () { describe('#findScript', function () { - it('find script on single entity', function () { + it('finds script on single entity', function () { const MyScript = createScript('myScript'); const e = new Entity(); e.addComponent('script'); @@ -770,7 +770,7 @@ describe('Entity', function () { describe('#findScripts', function () { - it('find scripts on single entity', function () { + it('finds scripts on single entity', function () { const MyScript = createScript('myScript'); const e = new Entity(); e.addComponent('script'); @@ -860,7 +860,7 @@ describe('Entity', function () { describe('#findComponentInAncestors', function () { - it('find component on single entity when included', function () { + it('finds component on single entity when included', function () { const e = new Entity(); e.addComponent('anim'); const component = e.findComponentInAncestors('anim', true); @@ -916,7 +916,7 @@ describe('Entity', function () { describe('#findComponentsInAncestors', function () { - it('find components on single entity when included', function () { + it('finds components on single entity when included', function () { const e = new Entity(); e.addComponent('anim'); const components = e.findComponentsInAncestors('anim', true); @@ -984,7 +984,7 @@ describe('Entity', function () { describe('#findScriptInAncestors', function () { - it('find script on single entity when included', function () { + it('finds script on single entity when included', function () { const MyScript = createScript('myScript'); const e = new Entity(); e.addComponent('script'); @@ -1058,7 +1058,7 @@ describe('Entity', function () { describe('#findScriptsInAncestors', function () { - it('find scripts on single entity when included', function () { + it('finds scripts on single entity when included', function () { const MyScript = createScript('myScript'); const e = new Entity(); e.addComponent('script'); diff --git a/test/scene/graph-node.test.mjs b/test/scene/graph-node.test.mjs index aa85d39a35a..edc6d600d76 100644 --- a/test/scene/graph-node.test.mjs +++ b/test/scene/graph-node.test.mjs @@ -263,7 +263,7 @@ describe('GraphNode', function () { describe('#findByName()', function () { - it('search the root node', function () { + it('finds root by name', function () { const root = new GraphNode('root'); const child = new GraphNode('child'); root.addChild(child); @@ -564,7 +564,7 @@ describe('GraphNode', function () { expect(child.findAncestorByName('child')).to.be.null; }); - it('find the child node if included', function () { + it('finds child by name if included', function () { const root = new GraphNode('root'); const child = new GraphNode('child'); root.addChild(child); @@ -582,7 +582,7 @@ describe('GraphNode', function () { describe('#forEach()', function () { - it('iterates over all nodes including root', function () { + it('iterates over all nodes', function () { const root = new GraphNode(); const child1 = new GraphNode(); const child2 = new GraphNode(); @@ -632,7 +632,7 @@ describe('GraphNode', function () { expect(visited[1]).to.equal(root); }); - it('iterates over all nodes including grand child', function () { + it('iterates over all nodes', function () { const root = new GraphNode(); const child = new GraphNode(); const grandchild = new GraphNode(); From f126819f1140bbdbf393c4587cf6221c76f2ef5a Mon Sep 17 00:00:00 2001 From: NewboO Date: Mon, 12 Sep 2022 20:36:12 +0200 Subject: [PATCH 23/26] Specific functions for descendants only search --- src/framework/entity.js | 144 +++++++++++---- src/scene/graph-node.js | 252 +++++++++++++++----------- test/framework/entity.test.mjs | 321 +++++++++++++++++++++++++-------- test/scene/graph-node.test.mjs | 229 ++++++++++++----------- 4 files changed, 610 insertions(+), 336 deletions(-) diff --git a/src/framework/entity.js b/src/framework/entity.js index d2e14286505..0d00c477ea6 100644 --- a/src/framework/entity.js +++ b/src/framework/entity.js @@ -374,76 +374,144 @@ class Entity extends GraphNode { } /** - * Search all the entity descendants for the first component of specified type. + * Search the entity and all of its descendants for the first component of specified type. * * @param {string} type - The name of the component type to retrieve. - * @param {boolean} [includeSelf=true] - True to include self entity in the search. - * @returns {Component} A component of specified type, if any of entity descendants has one. - * Returns undefined otherwise. + * @returns {Component} A component of specified type, if the entity or any of its descendants + * has one. Returns undefined otherwise. * @example * // Get the first found light component in the hierarchy tree that starts with this entity * var light = entity.findComponent("light"); */ - findComponent(type, includeSelf = true) { + findComponent(type) { const entity = this.findOne(function (node) { return node.c && node.c[type]; - }, includeSelf); + }); return entity && entity.c[type]; } /** - * Search all the entity descendants for all components of specified type. + * Search the entity and all of its descendants for all components of specified type. * * @param {string} type - The name of the component type to retrieve. - * @param {boolean} [includeSelf=true] - True to include self entity in the search. - * @returns {Component[]} All components of specified type in all of entity descendants. - * Returns empty array if none found. + * @returns {Component[]} All components of specified type in the entity or any of its + * descendants. Returns empty array if none found. * @example * // Get all light components in the hierarchy tree that starts with this entity * var lights = entity.findComponents("light"); */ - findComponents(type, includeSelf = true) { + findComponents(type) { const entities = this.find(function (node) { return node.c && node.c[type]; - }, includeSelf); + }); return entities.map(function (entity) { return entity.c[type]; }); } /** - * Search all the entity descendants for the first script instance of specified type. + * Search the entity and all of its descendants for the first script instance of specified type. * * @param {string|Class} nameOrType - The name or type of {@link ScriptType}. - * @param {boolean} [includeSelf=true] - True to include self entity in the search. - * @returns {ScriptType} A script instance of specified type, if any of entity descendants has one. - * Returns undefined otherwise. + * @returns {ScriptType} A script instance of specified type, if the entity or any of its descendants + * has one. Returns undefined otherwise. * @example * // Get the first found "playerController" instance in the hierarchy tree that starts with this entity * var controller = entity.findScript("playerController"); */ - findScript(nameOrType, includeSelf = true) { + findScript(nameOrType) { const entity = this.findOne(function (node) { return node.c && node.c.script && node.c.script.has(nameOrType); - }, includeSelf); + }); return entity && entity.c.script.get(nameOrType); } /** - * Search all the entity descendants for all script instances of specified type. + * Search the entity and all of its descendants for all script instances of specified type. * * @param {string|Class} nameOrType - The name or type of {@link ScriptType}. - * @param {boolean} [includeSelf=true] - True to include self entity in the search. - * @returns {ScriptType[]} All script instances of specified type in all of entity descendants. - * Returns empty array if none found. + * @returns {ScriptType[]} All script instances of specified type in the entity or any of its + * descendants. Returns empty array if none found. * @example * // Get all "playerController" instances in the hierarchy tree that starts with this entity * var controllers = entity.findScripts("playerController"); */ - findScripts(nameOrType, includeSelf = true) { + findScripts(nameOrType) { const entities = this.find(function (node) { return node.c && node.c.script && node.c.script.has(nameOrType); - }, includeSelf); + }); + return entities.map(function (entity) { + return entity.c.script.get(nameOrType); + }); + } + + /** + * Search all the entity descendants for the first component of specified type. + * + * @param {string} type - The name of the component type to retrieve. + * @returns {Component} A component of specified type, if any of entity descendants has one. + * Returns undefined otherwise. + * @example + * // Get the first found light component in the hierarchy tree excluding this entity + * var light = entity.findComponentInDescendants("light"); + */ + findComponentInDescendants(type) { + const entity = this.findDescendant(function (node) { + return node.c && node.c[type]; + }); + return entity && entity.c[type]; + } + + /** + * Search all the entity descendants for all components of specified type. + * + * @param {string} type - The name of the component type to retrieve. + * @returns {Component[]} All components of specified type in all of entity descendants. + * Returns empty array if none found. + * @example + * // Get all light components in the hierarchy tree excluding this entity + * var lights = entity.findComponentsInDescendants("lights"); + */ + findComponentsInDescendants(type) { + const entities = this.findDescendants(function (node) { + return node.c && node.c[type]; + }); + return entities.map(function (entity) { + return entity.c[type]; + }); + } + + /** + * Search all the entity descendants for the first script instance of specified type. + * + * @param {string|Class} nameOrType - The name or type of {@link ScriptType}. + * @returns {ScriptType} A script instance of specified type, if any of entity descendants has one. + * Returns undefined otherwise. + * @example + * // Get the first found "playerController" instance in the hierarchy tree excluding this entity + * var controller = entity.findScriptInDescendants("playerController"); + */ + findScriptInDescendants(nameOrType) { + const entity = this.findDescendant(function (node) { + return node.c && node.c.script && node.c.script.has(nameOrType); + }); + return entity && entity.c.script.get(nameOrType); + } + + /** + * Search all the entity descendants for all script instances of specified type. + * + * @param {string|Class} nameOrType - The name or type of {@link ScriptType}. + * @returns {ScriptType[]} All script instances of specified type in all of entity descendants. + * Returns empty array if none found. + * @example + * // Get all "playerController" instance in the hierarchy tree excluding this entity + * var controllers = entity.findScriptsInDescendants("playerController"); + */ + findScriptsInDescendants(nameOrType) { + const entities = this.findDescendants(function (node) { + return node.c && node.c.script && node.c.script.has(nameOrType); + }); return entities.map(function (entity) { return entity.c.script.get(nameOrType); }); @@ -453,17 +521,16 @@ class Entity extends GraphNode { * Search all the entity ascendants for the first component of specified type. * * @param {string} type - The name of the component type to retrieve. - * @param {boolean} [includeSelf=false] - True to include self entity in the search. * @returns {Component} A component of specified type, if any of entity ascendants has one. * Returns undefined otherwise. * @example - * // Get the first found light component in the ancestor tree that starts with this entity + * // Get the first found light component in the ancestor tree excluding this entity * var light = entity.findComponentInAncestors("light"); */ - findComponentInAncestors(type, includeSelf = false) { + findComponentInAncestors(type) { const entity = this.findAncestor(function (node) { return node.c && node.c[type]; - }, includeSelf); + }); return entity && entity.c[type]; } @@ -471,17 +538,16 @@ class Entity extends GraphNode { * Search all the entity ascendants for all components of specified type. * * @param {string} type - The name of the component type to retrieve. - * @param {boolean} [includeSelf=false] - True to include self entity in the search. * @returns {Component[]} All components of specified type in all of entity ascendants. * Returns empty array if none found. * @example - * // Get all element components in the ancestor tree that starts with this entity + * // Get all element components in the ancestor tree excluding this entity * var elements = entity.findComponentsInAncestors("element"); */ - findComponentsInAncestors(type, includeSelf = false) { + findComponentsInAncestors(type) { const entities = this.findAncestors(function (node) { return node.c && node.c[type]; - }, includeSelf); + }); return entities.map(function (entity) { return entity.c[type]; }); @@ -491,17 +557,16 @@ class Entity extends GraphNode { * Search all the entity ascendants for the first script instance of specified type. * * @param {string|Class} nameOrType - The name or type of {@link ScriptType}. - * @param {boolean} [includeSelf=false] - True to include self entity in the search. * @returns {ScriptType} A script instance of specified type, if any of entity ascendants has one. * Returns undefined otherwise. * @example - * // Get the first found "playerController" instance in the ancestor tree that starts with this entity + * // Get the first found "playerController" instance in the ancestor tree excluding this entity * var controller = entity.findScriptInAncestors("playerController"); */ - findScriptInAncestors(nameOrType, includeSelf = false) { + findScriptInAncestors(nameOrType) { const entity = this.findAncestor(function (node) { return node.c && node.c.script && node.c.script.has(nameOrType); - }, includeSelf); + }); return entity && entity.c.script.get(nameOrType); } @@ -509,17 +574,16 @@ class Entity extends GraphNode { * Search all the entity ascendants for all script instances of specified type. * * @param {string|Class} nameOrType - The name or type of {@link ScriptType}. - * @param {boolean} [includeSelf=false] - True to include self entity in the search. * @returns {ScriptType[]} All script instances of specified type in all of entity ascendants. * Returns empty array if none found. * @example - * // Get all "playerController" instance in the ancestor tree that starts with this entity + * // Get all "playerController" instance in the ancestor tree excluding this entity * var controllers = entity.findScriptsInAncestors("playerController"); */ - findScriptsInAncestors(nameOrType, includeSelf = false) { + findScriptsInAncestors(nameOrType) { const entities = this.findAncestors(function (node) { return node.c && node.c.script && node.c.script.has(nameOrType); - }, includeSelf); + }); return entities.map(function (entity) { return entity.c.script.get(nameOrType); }); diff --git a/src/scene/graph-node.js b/src/scene/graph-node.js index f4cbc21abba..ca60a1606b0 100644 --- a/src/scene/graph-node.js +++ b/src/scene/graph-node.js @@ -36,26 +36,20 @@ function _createTest(attr, value) { }; } -function _getIncludeSelf(attr, value, includeSelf) { - if (attr instanceof Function && typeof value === 'boolean') - return value; - - return includeSelf; -} - /** * Callback used by {@link GraphNode#find}, {@link GraphNode#findOne}, {@link GraphNode#findAncestor} * and {@link GraphNode#findAncestors} to search through a graph node and all of its descendants or ascendants. * * @callback FindNodeCallback * @param {GraphNode} node - The current graph node. - * @returns {boolean} Returning `true` will result in that node being returned from - * {@link GraphNode#find}, {@link GraphNode#findOne}, {@link GraphNode#findAncestor} or {@link GraphNode#findAncestors}. + * @returns {boolean} Returning `true` will result in that node being returned from {@link GraphNode#find}, + * {@link GraphNode#findOne}, {@link GraphNode#findDescendants}, {@link GraphNode#findDescendant}, + * {@link GraphNode#findAncestors} or {@link GraphNode#findAncestor}. */ /** - * Callback used by {@link GraphNode#forEach} and {@link GraphNode#forEachAncestor} to iterate through - * a graph node and all of its descendants or ascendants. + * Callback used by {@link GraphNode#forEach}, {@link GraphNode#forEachDescendant} and {@link GraphNode#forEachAncestor} + * to iterate through a graph node and all of its descendants or ascendants. * * @callback ForEachNodeCallback * @param {GraphNode} node - The current graph node. @@ -485,7 +479,8 @@ class GraphNode extends EventHandler { } /** - * Search all the graph node descendants for the nodes that satisfy some search criteria. + * Search the graph node and all of its descendants for the nodes that satisfy some search + * criteria. * * @param {FindNodeCallback|string} attr - This can either be a function or a string. If it's a * function, it is executed for each descendant node to test if node satisfies the search @@ -494,10 +489,8 @@ class GraphNode extends EventHandler { * of a field then the value passed as the second argument will be checked for equality. If * this is the name of a function then the return value of the function will be checked for * equality against the valued passed as the second argument to this function. - * @param {*} [value] - If the first argument (attr) is a property name then this value - * will be checked against the value of the property. If the first argument (attr) is a function - * then this argument can be skipped. - * @param {boolean} [includeSelf=true] - True to include self node in the search. + * @param {object} [value] - If the first argument (attr) is a property name then this value + * will be checked against the value of the property. * @returns {GraphNode[]} The array of graph nodes that match the search criteria. * @example * // Finds all nodes that have a model component and have `door` in their lower-cased name @@ -508,20 +501,89 @@ class GraphNode extends EventHandler { * // Finds all nodes that have the name property set to 'Test' * var entities = parent.find('name', 'Test'); */ - find(attr, value, includeSelf = true) { + find(attr, value) { const results = []; const test = _createTest(attr, value); this.forEach((node) => { if (test(node)) results.push(node); - }, _getIncludeSelf(attr, value, includeSelf)); + }); return results; } /** - * Search all the graph node descendants for the first node that satisfies some search criteria. + * Search all the graph node descendants for the nodes that satisfy some search criteria. + * + * @param {FindNodeCallback|string} attr - This can either be a function or a string. If it's a + * function, it is executed for each descendant node to test if node satisfies the search + * logic. Returning true from the function will include the node into the results. If it's a + * string then it represents the name of a field or a method of the node. If this is the name + * of a field then the value passed as the second argument will be checked for equality. If + * this is the name of a function then the return value of the function will be checked for + * equality against the valued passed as the second argument to this function. + * @param {*} [value] - If the first argument (attr) is a property name then this value + * will be checked against the value of the property. + * @returns {GraphNode[]} The array of graph nodes that match the search criteria. + * @example + * // Finds all nodes that have a model component and have `door` in their lower-cased name + * var doors = house.findDescendants(function (node) { + * return node.model && node.name.toLowerCase().indexOf('door') !== -1; + * }); + * @example + * // Finds all nodes that have the name property set to 'Test' + * var entities = parent.findDescendants('name', 'Test'); + */ + findDescendants(attr, value) { + const results = []; + const test = _createTest(attr, value); + + this.forEachDescendant((node) => { + if (test(node)) + results.push(node); + }); + + return results; + } + + /** + * Search all the graph node ascendants for the nodes that satisfy some search criteria. + * + * @param {FindNodeCallback|string} attr - This can either be a function or a string. If it's a + * function, it is executed for each ascendant node to test if node satisfies the search + * logic. Returning true from the function will include the node into the results. If it's a + * string then it represents the name of a field or a method of the node. If this is the name + * of a field then the value passed as the second argument will be checked for equality. If + * this is the name of a function then the return value of the function will be checked for + * equality against the valued passed as the second argument to this function. + * @param {*} [value] - If the first argument (attr) is a property name then this value + * will be checked against the value of the property. + * @returns {GraphNode[]} The array of graph nodes that match the search criteria. + * @example + * // Finds all nodes that have a group element component + * var groups = element.findAncestors(function (node) { + * return node.element && node.element.type === pc.ELEMENTTYPE_GROUP; + * }); + * @example + * // Finds all nodes that have the name property set to 'Test' + * var entities = entity.findAncestors('name', 'Test'); + */ + findAncestors(attr, value) { + const results = []; + const test = _createTest(attr, value); + + this.forEachAncestor((node) => { + if (test(node)) + results.push(node); + }); + + return results; + } + + /** + * Search the graph node and all of its descendants for the first node that satisfies some + * search criteria. * * @param {FindNodeCallback|string} attr - This can either be a function or a string. If it's a * function, it is executed for each descendant node to test if node satisfies the search @@ -530,10 +592,8 @@ class GraphNode extends EventHandler { * this is the name of a field then the value passed as the second argument will be checked for * equality. If this is the name of a function then the return value of the function will be * checked for equality against the valued passed as the second argument to this function. - * @param {*} [value] - If the first argument (attr) is a property name then this value - * will be checked against the value of the property. If the first argument (attr) is a function - * then this argument can be skipped. - * @param {boolean} [includeSelf=true] - True to include self node in the search. + * @param {object} [value] - If the first argument (attr) is a property name then this value + * will be checked against the value of the property. * @returns {GraphNode|null} A graph node that match the search criteria. Returns null if no * node is found. * @example @@ -545,15 +605,15 @@ class GraphNode extends EventHandler { * // Finds the first node that has the name property set to 'Test' * var node = parent.findOne('name', 'Test'); */ - findOne(attr, value, includeSelf = true) { + findOne(attr, value) { const test = _createTest(attr, value); const len = this._children.length; - if (_getIncludeSelf(attr, value, includeSelf) && test(this)) + if (test(this)) return this; for (let i = 0; i < len; ++i) { - const result = this._children[i].findOne(test, true); + const result = this._children[i].findOne(test); if (result) return result; } @@ -562,39 +622,42 @@ class GraphNode extends EventHandler { } /** - * Search all the graph node ascendants for the nodes that satisfy some search criteria. + * Search all the graph node descendants for the first node that satisfies some search criteria. * * @param {FindNodeCallback|string} attr - This can either be a function or a string. If it's a - * function, it is executed for each ascendant node to test if node satisfies the search - * logic. Returning true from the function will include the node into the results. If it's a - * string then it represents the name of a field or a method of the node. If this is the name - * of a field then the value passed as the second argument will be checked for equality. If - * this is the name of a function then the return value of the function will be checked for - * equality against the valued passed as the second argument to this function. + * function, it is executed for each descendant node to test if node satisfies the search + * logic. Returning true from the function will result in that node being returned from + * findDescendant. If it's a string then it represents the name of a field or a method of the node. + * If this is the name of a field then the value passed as the second argument will be checked for + * equality. If this is the name of a function then the return value of the function will be + * checked for equality against the valued passed as the second argument to this function. * @param {*} [value] - If the first argument (attr) is a property name then this value - * will be checked against the value of the property. If the first argument (attr) is a function - * then this argument can be skipped. - * @param {boolean} [includeSelf=false] - True to include self node in the search. - * @returns {GraphNode[]} The array of graph nodes that match the search criteria. + * will be checked against the value of the property. + * @returns {GraphNode|null} A graph node that match the search criteria. Returns null if no + * node is found. * @example - * // Finds all nodes that have a group element component - * var groups = element.findParents(function (node) { - * return node.element && node.element.type === pc.ELEMENTTYPE_GROUP; + * // Find the first node that is called `head` and has a model component + * var head = player.findDescendant(function (node) { + * return node.model && node.name === 'head'; * }); * @example - * // Finds all nodes that have the name property set to 'Test' - * var entities = entity.findParents('name', 'Test'); + * // Finds the first node that has the name property set to 'Test' + * var node = parent.findDescendant('name', 'Test'); */ - findAncestors(attr, value, includeSelf = false) { - const results = []; + findDescendant(attr, value) { const test = _createTest(attr, value); + const len = this._children.length; - this.forEachAncestor((node) => { - if (test(node)) - results.push(node); - }, _getIncludeSelf(attr, value, includeSelf)); + for (let i = 0; i < len; ++i) { + if (test(this._children[i])) + return this._children[i]; - return results; + const result = this._children[i].findDescendant(test); + if (result) + return result; + } + + return null; } /** @@ -603,32 +666,27 @@ class GraphNode extends EventHandler { * @param {FindNodeCallback|string} attr - This can either be a function or a string. If it's a * function, it is executed for each ascendant node to test if node satisfies the search * logic. Returning true from the function will result in that node being returned from - * findOne. If it's a string then it represents the name of a field or a method of the node. If - * this is the name of a field then the value passed as the second argument will be checked for + * findAncestor. If it's a string then it represents the name of a field or a method of the node. + * If this is the name of a field then the value passed as the second argument will be checked for * equality. If this is the name of a function then the return value of the function will be * checked for equality against the valued passed as the second argument to this function. * @param {*} [value] - If the first argument (attr) is a property name then this value - * will be checked against the value of the property. If the first argument (attr) is a function - * then this argument can be skipped. - * @param {boolean} [includeSelf=false] - True to include self node in the search. + * will be checked against the value of the property. * @returns {GraphNode|null} A graph node that match the search criteria. Returns null if no * node is found. * @example * // Find the first node that is called `head` and has a model component - * var head = player.findOneParent(function (node) { + * var head = player.findAncestor(function (node) { * return node.model && node.name === 'head'; * }); * @example * // Finds the first node that has the name property set to 'Test' - * var node = parent.findOneParent('name', 'Test'); + * var node = parent.findAncestor('name', 'Test'); */ - findAncestor(attr, value, includeSelf = false) { + findAncestor(attr, value) { const test = _createTest(attr, value); - let current = this; - if (!_getIncludeSelf(attr, value, includeSelf)) - current = current._parent; - + let current = this._parent; while (current) { if (test(current)) return current; @@ -646,7 +704,6 @@ class GraphNode extends EventHandler { * of array. * * @param {...*} query - Name of a tag or array of tags. - * @param {boolean} [includeSelf=false] - True to include self node in the search. * @returns {GraphNode[]} A list of all graph nodes that match the query. * @example * // Return all graph nodes that tagged by `animal` @@ -662,35 +719,18 @@ class GraphNode extends EventHandler { * var meatEatingMammalsAndReptiles = node.findByTag(["carnivore", "mammal"], ["carnivore", "reptile"]); */ findByTag(...query) { - let includeSelf = false; - if (typeof query[query.length - 1] === 'boolean') - includeSelf = query.pop(); - - return this.find(node => node.tags.has(...query), includeSelf); + return this.findDescendants(node => node.tags.has(...query)); } /** * Get the first node found in the graph with the name. The search is depth first. * * @param {string} name - The name of the graph. - * @param {boolean} [includeSelf=true] - True to include self node in the search. - * @returns {GraphNode|null} The first node to be found matching the supplied name. Returns - * null if no node is found. - */ - findByName(name, includeSelf = true) { - return this.findOne('name', name, includeSelf); - } - - /** - * Get the first ancestor node found in the graph with the name. - * - * @param {string} name - The name of the graph. - * @param {boolean} [includeSelf=false] - True to include self node in the search. * @returns {GraphNode|null} The first node to be found matching the supplied name. Returns * null if no node is found. */ - findAncestorByName(name, includeSelf = false) { - return this.findAncestor('name', name, includeSelf); + findByName(name) { + return this.findOne('name', name); } /** @@ -724,29 +764,42 @@ class GraphNode extends EventHandler { } /** - * Executes a provided function once on all of this graph node descendants. + * Executes a provided function once on this graph node and all of its descendants. * - * @param {ForEachNodeCallback} callback - The function to execute on each graph node descendant. + * @param {ForEachNodeCallback} callback - The function to execute on the graph node and each + * descendant. * @param {object} [thisArg] - Optional value to use as this when executing callback function. - * @param {boolean} [includeSelf=true] - True to also execute function on self node. * @example * // Log the path and name of each node in descendant tree starting with "parent" * parent.forEach(function (node) { * console.log(node.path + "/" + node.name); * }); */ - forEach(callback, thisArg, includeSelf = true) { - if (typeof thisArg === 'boolean') { - includeSelf = thisArg; - thisArg = undefined; - } + forEach(callback, thisArg) { + callback.call(thisArg, this); const children = this._children; - if (includeSelf) - callback.call(thisArg, this); + for (let i = 0; i < children.length; i++) { + children[i].forEach(callback, thisArg); + } + } + /** + * Executes a provided function once on all of this graph node descendants. + * + * @param {ForEachNodeCallback} callback - The function to execute on each graph node descendant. + * @param {object} [thisArg] - Optional value to use as this when executing callback function. + * @example + * // Log the path and name of each node in descendant tree + * parent.forEachDescendant(function (node) { + * console.log(node.path + "/" + node.name); + * }); + */ + forEachDescendant(callback, thisArg) { + const children = this._children; for (let i = 0; i < children.length; i++) { - children[i].forEach(callback, thisArg, true); + callback.call(thisArg, children[i]); + children[i].forEachDescendant(callback, thisArg); } } @@ -755,23 +808,14 @@ class GraphNode extends EventHandler { * * @param {ForEachNodeCallback} callback - The function to execute on each graph node ascendant. * @param {object} [thisArg] - Optional value to use as this when executing callback function. - * @param {boolean} [includeSelf=false] - True to also execute function on self node. * @example * // Enable each node in ascendant tree * current.forEachParent(function (node) { * node.enabled = true; * }); */ - forEachAncestor(callback, thisArg, includeSelf = false) { - if (typeof thisArg === 'boolean') { - includeSelf = thisArg; - thisArg = undefined; - } - - let current = this; - if (!includeSelf) - current = this._parent; - + forEachAncestor(callback, thisArg) { + let current = this._parent; while (current) { callback.call(thisArg, current); current = current._parent; diff --git a/test/framework/entity.test.mjs b/test/framework/entity.test.mjs index 90b4cdbd6e6..101a911fcd0 100644 --- a/test/framework/entity.test.mjs +++ b/test/framework/entity.test.mjs @@ -578,19 +578,10 @@ describe('Entity', function () { expect(component).to.be.an.instanceof(AnimComponent); }); - it('does not find component on single entity when excluded', function () { + it('returns null when component is not found', function () { const e = new Entity(); e.addComponent('anim'); - const component = e.findComponent('anim', false); - expect(component).to.be.null; - }); - - it('returns null when component is not found', function () { - const root = new Entity(); - const child = new Entity(); - root.addChild(child); - child.addComponent('anim'); - const component = root.findComponent('render'); + const component = e.findComponent('render'); expect(component).to.be.null; }); @@ -636,20 +627,10 @@ describe('Entity', function () { expect(components[0]).to.be.an.instanceof(AnimComponent); }); - it('does not find components on single entity when excluded', function () { + it('returns empty array when no components are found', function () { const e = new Entity(); e.addComponent('anim'); - const components = e.findComponents('anim', false); - expect(components).to.be.an('array'); - expect(components.length).to.equal(0); - }); - - it('returns empty array when no components are found', function () { - const root = new Entity(); - const child = new Entity(); - root.addChild(child); - child.addComponent('anim'); - const components = root.findComponents('render'); + const components = e.findComponents('render'); expect(components).to.be.an('array'); expect(components.length).to.equal(0); }); @@ -705,15 +686,6 @@ describe('Entity', function () { expect(script).to.be.an.instanceof(MyScript); }); - it('does not find script on single entity when excluded', function () { - createScript('myScript'); - const e = new Entity(); - e.addComponent('script'); - e.script.create('myScript'); - const script = e.findScript('myScript', false); - expect(script).to.be.null; - }); - it('returns null when script is not found', function () { const root = new Entity(); const child = new Entity(); @@ -781,16 +753,6 @@ describe('Entity', function () { expect(scripts[0]).to.be.an.instanceof(MyScript); }); - it('does not find scripts on single entity when excluded', function () { - createScript('myScript'); - const e = new Entity(); - e.addComponent('script'); - e.script.create('myScript'); - const scripts = e.findScripts('myScript', false); - expect(scripts).to.be.an('array'); - expect(scripts.length).to.equal(0); - }); - it('returns empty array when no scripts are found', function () { const root = new Entity(); const child = new Entity(); @@ -858,15 +820,255 @@ describe('Entity', function () { }); - describe('#findComponentInAncestors', function () { + describe('#findComponentInDescendants', function () { - it('finds component on single entity when included', function () { + it('does not find component on single entity', function () { const e = new Entity(); e.addComponent('anim'); - const component = e.findComponentInAncestors('anim', true); + const component = e.findComponentInDescendants('anim'); + expect(component).to.be.null; + }); + + it('returns null when component is not found', function () { + const e = new Entity(); + e.addComponent('anim'); + const component = e.findComponentInDescendants('render'); + expect(component).to.be.null; + }); + + it('finds component on child entity', function () { + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + child.addComponent('anim'); + const component = root.findComponentInDescendants('anim'); + expect(component).to.be.an.instanceof(AnimComponent); + }); + + it('finds component on grandchild entity', function () { + const root = new Entity(); + const child = new Entity(); + const grandchild = new Entity(); + root.addChild(child); + child.addChild(grandchild); + grandchild.addComponent('anim'); + const component = root.findComponentInDescendants('anim'); expect(component).to.be.an.instanceof(AnimComponent); }); + it('does not find component on parent entity', function () { + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + root.addComponent('anim'); + const component = child.findComponentInDescendants('anim'); + expect(component).to.be.null; + }); + + }); + + describe('#findComponentsInDescendants', function () { + + it('does not finds components on single entity', function () { + const e = new Entity(); + e.addComponent('anim'); + const components = e.findComponentsInDescendants('anim'); + expect(components).to.be.an('array'); + expect(components.length).to.equal(0); + }); + + it('returns empty array when no components are found', function () { + const e = new Entity(); + e.addComponent('anim'); + const components = e.findComponentsInDescendants('render'); + expect(components).to.be.an('array'); + expect(components.length).to.equal(0); + }); + + it('finds components on child entity', function () { + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + child.addComponent('anim'); + const components = root.findComponentsInDescendants('anim'); + expect(components).to.be.an('array'); + expect(components.length).to.equal(1); + expect(components[0]).to.be.an.instanceof(AnimComponent); + }); + + it('finds components on 3 entity hierarchy', function () { + const root = new Entity(); + const child = new Entity(); + const grandchild = new Entity(); + root.addChild(child); + child.addChild(grandchild); + root.addComponent('anim'); + child.addComponent('anim'); + grandchild.addComponent('anim'); + const components = root.findComponentsInDescendants('anim'); + expect(components).to.be.an('array'); + expect(components.length).to.equal(2); + expect(components[0]).to.be.an.instanceof(AnimComponent); + expect(components[1]).to.be.an.instanceof(AnimComponent); + }); + + it('does not find components on parent entity', function () { + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + root.addComponent('anim'); + const components = child.findComponentsInDescendants('anim'); + expect(components).to.be.an('array'); + expect(components.length).to.equal(0); + }); + + }); + + describe('#findScriptInDescendants', function () { + + it('does not find script on single entity', function () { + createScript('myScript'); + const e = new Entity(); + e.addComponent('script'); + e.script.create('myScript'); + const script = e.findScriptInDescendants('myScript'); + expect(script).to.be.null; + }); + + it('returns null when script is not found', function () { + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + child.addComponent('script'); + const script = root.findScriptInDescendants('myScript'); + expect(script).to.be.null; + }); + + it('returns null when script component is not found', function () { + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + const script = root.findScriptInDescendants('myScript'); + expect(script).to.be.null; + }); + + it('finds script on child entity', function () { + const MyScript = createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + child.addComponent('script'); + child.script.create('myScript'); + const script = root.findScriptInDescendants('myScript'); + expect(script).to.be.an.instanceof(MyScript); + }); + + it('finds script on grandchild entity', function () { + const MyScript = createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + const grandchild = new Entity(); + root.addChild(child); + child.addChild(grandchild); + grandchild.addComponent('script'); + grandchild.script.create('myScript'); + const script = root.findScriptInDescendants('myScript'); + expect(script).to.be.an.instanceof(MyScript); + }); + + it('does not find script on parent entity', function () { + createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + root.addComponent('script'); + root.script.create('myScript'); + const script = child.findScriptInDescendants('myScript'); + expect(script).to.be.null; + }); + + }); + + describe('#findScriptsInDescendants', function () { + + it('does not find scripts on single entity', function () { + createScript('myScript'); + const e = new Entity(); + e.addComponent('script'); + e.script.create('myScript'); + const scripts = e.findScriptsInDescendants('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(0); + }); + + it('returns empty array when no scripts are found', function () { + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + child.addComponent('script'); + const scripts = root.findScriptsInDescendants('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(0); + }); + + it('returns empty array when no script component are found', function () { + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + const scripts = root.findScriptsInDescendants('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(0); + }); + + it('finds scripts on child entity', function () { + const MyScript = createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + child.addComponent('script'); + child.script.create('myScript'); + const scripts = root.findScriptsInDescendants('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(1); + expect(scripts[0]).to.be.an.instanceof(MyScript); + }); + + it('finds scripts on 3 entity hierarchy', function () { + const MyScript = createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + const grandchild = new Entity(); + root.addChild(child); + child.addChild(grandchild); + root.addComponent('script'); + root.script.create('myScript'); + child.addComponent('script'); + child.script.create('myScript'); + grandchild.addComponent('script'); + grandchild.script.create('myScript'); + const scripts = root.findScriptsInDescendants('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(2); + expect(scripts[0]).to.be.an.instanceof(MyScript); + expect(scripts[1]).to.be.an.instanceof(MyScript); + }); + + it('does not find scripts on parent entity', function () { + createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + root.addComponent('script'); + root.script.create('myScript'); + const scripts = child.findScriptsInDescendants('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(0); + }); + + }); + + describe('#findComponentInAncestors', function () { + it('does not find component on single entity', function () { const e = new Entity(); e.addComponent('anim'); @@ -916,15 +1118,6 @@ describe('Entity', function () { describe('#findComponentsInAncestors', function () { - it('finds components on single entity when included', function () { - const e = new Entity(); - e.addComponent('anim'); - const components = e.findComponentsInAncestors('anim', true); - expect(components).to.be.an('array'); - expect(components.length).to.equal(1); - expect(components[0]).to.be.an.instanceof(AnimComponent); - }); - it('does not find components on single entity', function () { const e = new Entity(); e.addComponent('anim'); @@ -984,15 +1177,6 @@ describe('Entity', function () { describe('#findScriptInAncestors', function () { - it('finds script on single entity when included', function () { - const MyScript = createScript('myScript'); - const e = new Entity(); - e.addComponent('script'); - e.script.create('myScript'); - const script = e.findScriptInAncestors('myScript', true); - expect(script).to.be.an.instanceof(MyScript); - }); - it('does not find script on single entity', function () { createScript('myScript'); const e = new Entity(); @@ -1058,17 +1242,6 @@ describe('Entity', function () { describe('#findScriptsInAncestors', function () { - it('finds scripts on single entity when included', function () { - const MyScript = createScript('myScript'); - const e = new Entity(); - e.addComponent('script'); - e.script.create('myScript'); - const scripts = e.findScriptsInAncestors('myScript', true); - expect(scripts).to.be.an('array'); - expect(scripts.length).to.equal(1); - expect(scripts[0]).to.be.an.instanceof(MyScript); - }); - it('does not find scripts on single entity', function () { createScript('myScript'); const e = new Entity(); diff --git a/test/scene/graph-node.test.mjs b/test/scene/graph-node.test.mjs index edc6d600d76..a3e1a21dd19 100644 --- a/test/scene/graph-node.test.mjs +++ b/test/scene/graph-node.test.mjs @@ -219,9 +219,6 @@ describe('GraphNode', function () { expect(res).to.be.an('array').with.lengthOf(1); expect(res[0]).to.equal(root); - res = root.find('name', 'Untitled', false); - expect(res).to.be.an('array').with.lengthOf(0); - res = root.find('name', 'Child'); expect(res).to.be.an('array').with.lengthOf(1); expect(res[0]).to.equal(child); @@ -243,17 +240,56 @@ describe('GraphNode', function () { expect(res[0]).to.equal(root); res = root.find(function (node) { + return node.name === 'Child'; + }); + expect(res).to.be.an('array').with.lengthOf(1); + expect(res[0]).to.equal(child); + + res = root.find(function (node) { + return node.name === 'Not Found'; + }); + expect(res).to.be.an('array').with.lengthOf(0); + }); + + }); + + describe('#findDescendants()', function () { + + it('finds a node by property', function () { + const root = new GraphNode(); + const child = new GraphNode('Child'); + root.addChild(child); + + let res; + res = root.findDescendants('name', 'Untitled'); + expect(res).to.be.an('array').with.lengthOf(0); + + res = root.findDescendants('name', 'Child'); + expect(res).to.be.an('array').with.lengthOf(1); + expect(res[0]).to.equal(child); + + res = root.findDescendants('name', 'Not Found'); + expect(res).to.be.an('array').with.lengthOf(0); + }); + + it('finds a node by filter function', function () { + const root = new GraphNode(); + const child = new GraphNode('Child'); + root.addChild(child); + + let res; + res = root.findDescendants(function (node) { return node.name === 'Untitled'; - }, false); + }); expect(res).to.be.an('array').with.lengthOf(0); - res = root.find(function (node) { + res = root.findDescendants(function (node) { return node.name === 'Child'; }); expect(res).to.be.an('array').with.lengthOf(1); expect(res[0]).to.equal(child); - res = root.find(function (node) { + res = root.findDescendants(function (node) { return node.name === 'Not Found'; }); expect(res).to.be.an('array').with.lengthOf(0); @@ -261,20 +297,57 @@ describe('GraphNode', function () { }); - describe('#findByName()', function () { + describe('#findAncestors()', function () { - it('finds root by name', function () { - const root = new GraphNode('root'); - const child = new GraphNode('child'); + it('finds a node by property', function () { + const root = new GraphNode('Parent'); + const child = new GraphNode('Child'); root.addChild(child); - expect(root.findByName('root')).to.equal(root); + + let res; + res = child.findAncestors('name', 'Parent'); + expect(res).to.be.an('array').with.lengthOf(1); + expect(res[0]).to.equal(root); + + res = child.findAncestors('name', 'Child'); + expect(res).to.be.an('array').with.lengthOf(0); + + res = child.findAncestors('name', 'Not Found'); + expect(res).to.be.an('array').with.lengthOf(0); }); - it('does not search the root node', function () { + it('finds a node by filter function', function () { + const root = new GraphNode('Parent'); + const child = new GraphNode('Child'); + root.addChild(child); + + let res; + res = child.findAncestors(function (node) { + return node.name === 'Parent'; + }); + expect(res).to.be.an('array').with.lengthOf(1); + expect(res[0]).to.equal(root); + + res = child.findAncestors(function (node) { + return node.name === 'Child'; + }); + expect(res).to.be.an('array').with.lengthOf(0); + + res = child.findAncestors(function (node) { + return node.name === 'Not Found'; + }); + expect(res).to.be.an('array').with.lengthOf(0); + }); + + }); + + describe('#findByName()', function () { + + it('finds root by name', function () { const root = new GraphNode('root'); const child = new GraphNode('child'); root.addChild(child); - expect(root.findByName('root', false)).to.be.null; + expect(root.findByName('root')).to.equal(root); }); it('finds child by name', function () { @@ -324,7 +397,7 @@ describe('GraphNode', function () { const root = new GraphNode('root'); const child = new GraphNode('child'); root.addChild(child); - expect(root.findByPath('not-found')).to.be.null; + expect(root.findByPath('not-found')).to.equal(null); }); }); @@ -338,14 +411,6 @@ describe('GraphNode', function () { expect(result).to.be.an('array').with.lengthOf(0); }); - it('search the root node if included', function () { - const root = new GraphNode('root'); - root.tags.add('tag'); - const result = root.findByTag('tag', true); - expect(result).to.be.an('array').with.lengthOf(1); - expect(result[0]).to.equal(root); - }); - it('returns an array of nodes that have the query tag', function () { const root = new GraphNode('root'); const child = new GraphNode('child'); @@ -405,9 +470,6 @@ describe('GraphNode', function () { res = root.findOne('name', 'Untitled'); expect(res).to.equal(root); - res = root.findOne('name', 'Untitled', false); - expect(res).to.be.null; - res = root.findOne('name', 'Child'); expect(res).to.equal(child); @@ -426,11 +488,6 @@ describe('GraphNode', function () { }); expect(res).to.equal(root); - res = root.findOne(function (node) { - return node.name === 'Untitled'; - }, false); - expect(res).to.be.null; - res = root.findOne(function (node) { return node.name === 'Child'; }); @@ -444,56 +501,44 @@ describe('GraphNode', function () { }); - describe('#findAncestors()', function () { + describe('#findDescendant()', function () { it('finds a node by property', function () { - const root = new GraphNode('Parent'); + const root = new GraphNode(); const child = new GraphNode('Child'); root.addChild(child); let res; - res = child.findAncestors('name', 'Parent'); - expect(res).to.be.an('array').with.lengthOf(1); - expect(res[0]).to.equal(root); - - res = child.findAncestors('name', 'Child'); - expect(res).to.be.an('array').with.lengthOf(0); + res = root.findDescendant('name', 'Untitled'); + expect(res).to.be.null; - res = child.findAncestors('name', 'Child', true); - expect(res).to.be.an('array').with.lengthOf(1); - expect(res[0]).to.equal(child); + res = root.findDescendant('name', 'Child'); + expect(res).to.equal(child); - res = child.findAncestors('name', 'Not Found'); - expect(res).to.be.an('array').with.lengthOf(0); + res = root.findDescendant('name', 'Not Found'); + expect(res).to.be.null; }); it('finds a node by filter function', function () { - const root = new GraphNode('Parent'); + const root = new GraphNode(); const child = new GraphNode('Child'); root.addChild(child); let res; - res = child.findAncestors(function (node) { - return node.name === 'Parent'; + res = root.findDescendant(function (node) { + return node.name === 'Untitled'; }); - expect(res).to.be.an('array').with.lengthOf(1); - expect(res[0]).to.equal(root); + expect(res).to.be.null; - res = child.findAncestors(function (node) { + res = root.findDescendant(function (node) { return node.name === 'Child'; }); - expect(res).to.be.an('array').with.lengthOf(0); - - res = child.findAncestors(function (node) { - return node.name === 'Child'; - }, true); - expect(res).to.be.an('array').with.lengthOf(1); - expect(res[0]).to.equal(child); + expect(res).to.equal(child); - res = child.findAncestors(function (node) { + res = root.findDescendant(function (node) { return node.name === 'Not Found'; }); - expect(res).to.be.an('array').with.lengthOf(0); + expect(res).to.be.null; }); }); @@ -512,9 +557,6 @@ describe('GraphNode', function () { res = child.findAncestor('name', 'Child'); expect(res).to.be.null; - res = child.findAncestor('name', 'Child', true); - expect(res).to.equal(child); - res = child.findAncestor('name', 'Not Found'); expect(res).to.be.null; }); @@ -535,11 +577,6 @@ describe('GraphNode', function () { }); expect(res).to.be.null; - res = child.findAncestor(function (node) { - return node.name === 'Child'; - }, true); - expect(res).to.equal(child); - res = child.findAncestor(function (node) { return node.name === 'Not Found'; }); @@ -548,38 +585,6 @@ describe('GraphNode', function () { }); - describe('#findAncestorByName()', function () { - - it('finds root by name', function () { - const root = new GraphNode('root'); - const child = new GraphNode('child'); - root.addChild(child); - expect(child.findAncestorByName('root')).to.equal(root); - }); - - it('does not search the child node', function () { - const root = new GraphNode('root'); - const child = new GraphNode('child'); - root.addChild(child); - expect(child.findAncestorByName('child')).to.be.null; - }); - - it('finds child by name if included', function () { - const root = new GraphNode('root'); - const child = new GraphNode('child'); - root.addChild(child); - expect(child.findAncestorByName('child', true)).to.equal(child); - }); - - it('returns null if no node is found', function () { - const root = new GraphNode('root'); - const child = new GraphNode('child'); - root.addChild(child); - expect(child.findAncestorByName('not-found')).to.be.null; - }); - - }); - describe('#forEach()', function () { it('iterates over all nodes', function () { @@ -598,16 +603,20 @@ describe('GraphNode', function () { expect(visited[2]).to.equal(child2); }); - it('iterates over all nodes but root', function () { + }); + + describe('#forEachDescendant()', function () { + + it('iterates over all nodes', function () { const root = new GraphNode(); const child1 = new GraphNode(); const child2 = new GraphNode(); root.addChild(child1); root.addChild(child2); const visited = []; - root.forEach((node) => { + root.forEachDescendant((node) => { visited.push(node); - }, false); + }); expect(visited).to.be.an('array').with.lengthOf(2); expect(visited[0]).to.equal(child1); expect(visited[1]).to.equal(child2); @@ -617,7 +626,7 @@ describe('GraphNode', function () { describe('#forEachAncestor()', function () { - it('iterates over all nodes but grand child', function () { + it('iterates over all nodes but self', function () { const root = new GraphNode(); const child = new GraphNode(); const grandchild = new GraphNode(); @@ -632,22 +641,6 @@ describe('GraphNode', function () { expect(visited[1]).to.equal(root); }); - it('iterates over all nodes', function () { - const root = new GraphNode(); - const child = new GraphNode(); - const grandchild = new GraphNode(); - root.addChild(child); - child.addChild(grandchild); - const visited = []; - grandchild.forEachAncestor((node) => { - visited.push(node); - }, true); - expect(visited).to.be.an('array').with.lengthOf(3); - expect(visited[0]).to.equal(grandchild); - expect(visited[1]).to.equal(child); - expect(visited[2]).to.equal(root); - }); - }); describe('#getEulerAngles()', function () { From fdd3f98a6213ba110272a86e2a5800a5d6fa6fac Mon Sep 17 00:00:00 2001 From: NewboO Date: Mon, 12 Sep 2022 21:03:56 +0200 Subject: [PATCH 24/26] Doc --- src/scene/graph-node.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/scene/graph-node.js b/src/scene/graph-node.js index ca60a1606b0..1dd3ad55776 100644 --- a/src/scene/graph-node.js +++ b/src/scene/graph-node.js @@ -37,8 +37,9 @@ function _createTest(attr, value) { } /** - * Callback used by {@link GraphNode#find}, {@link GraphNode#findOne}, {@link GraphNode#findAncestor} - * and {@link GraphNode#findAncestors} to search through a graph node and all of its descendants or ascendants. + * Callback used by {@link GraphNode#find}, {@link GraphNode#findOne}, {@link GraphNode#findDescendants}, + * {@link GraphNode#findDescendant}, {@link GraphNode#findAncestors} or {@link GraphNode#findAncestor} + * to search through a graph node and all of its descendants or ascendants. * * @callback FindNodeCallback * @param {GraphNode} node - The current graph node. From 1c08147daff1f30e6b66a75d7abede0be480d809 Mon Sep 17 00:00:00 2001 From: NewboO Date: Tue, 13 Sep 2022 12:03:17 +0200 Subject: [PATCH 25/26] Doc --- src/scene/graph-node.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/scene/graph-node.js b/src/scene/graph-node.js index 1dd3ad55776..3047ae7d003 100644 --- a/src/scene/graph-node.js +++ b/src/scene/graph-node.js @@ -23,7 +23,15 @@ const matrix = new Mat4(); const target = new Vec3(); const up = new Vec3(); -function _createTest(attr, value) { +/** + * Helper function that handles signature overloading to receive a test function. + * + * @param {Function|string} attr - Attribute or lambda. + * @param {*} [value] - Optional value in case of `attr` being a `string` + * @returns {Function} Test function that receives a GraphNode and returns a boolean. + * @ignore + */ +function createTest(attr, value) { if (attr instanceof Function) { return attr; } @@ -504,7 +512,7 @@ class GraphNode extends EventHandler { */ find(attr, value) { const results = []; - const test = _createTest(attr, value); + const test = createTest(attr, value); this.forEach((node) => { if (test(node)) @@ -538,7 +546,7 @@ class GraphNode extends EventHandler { */ findDescendants(attr, value) { const results = []; - const test = _createTest(attr, value); + const test = createTest(attr, value); this.forEachDescendant((node) => { if (test(node)) @@ -572,7 +580,7 @@ class GraphNode extends EventHandler { */ findAncestors(attr, value) { const results = []; - const test = _createTest(attr, value); + const test = createTest(attr, value); this.forEachAncestor((node) => { if (test(node)) @@ -607,7 +615,7 @@ class GraphNode extends EventHandler { * var node = parent.findOne('name', 'Test'); */ findOne(attr, value) { - const test = _createTest(attr, value); + const test = createTest(attr, value); const len = this._children.length; if (test(this)) @@ -646,7 +654,7 @@ class GraphNode extends EventHandler { * var node = parent.findDescendant('name', 'Test'); */ findDescendant(attr, value) { - const test = _createTest(attr, value); + const test = createTest(attr, value); const len = this._children.length; for (let i = 0; i < len; ++i) { @@ -685,7 +693,7 @@ class GraphNode extends EventHandler { * var node = parent.findAncestor('name', 'Test'); */ findAncestor(attr, value) { - const test = _createTest(attr, value); + const test = createTest(attr, value); let current = this._parent; while (current) { From ee4f7adbb211f464203430627c4e8ed446e3783a Mon Sep 17 00:00:00 2001 From: NewboO Date: Tue, 13 Sep 2022 14:05:29 +0200 Subject: [PATCH 26/26] Update src/scene/graph-node.js Co-authored-by: Hermann Rolfes --- src/scene/graph-node.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scene/graph-node.js b/src/scene/graph-node.js index 3047ae7d003..710ca37a3f7 100644 --- a/src/scene/graph-node.js +++ b/src/scene/graph-node.js @@ -26,9 +26,9 @@ const up = new Vec3(); /** * Helper function that handles signature overloading to receive a test function. * - * @param {Function|string} attr - Attribute or lambda. + * @param {FindNodeCallback|string} attr - Attribute or lambda. * @param {*} [value] - Optional value in case of `attr` being a `string` - * @returns {Function} Test function that receives a GraphNode and returns a boolean. + * @returns {FindNodeCallback} Test function that receives a GraphNode and returns a boolean. * @ignore */ function createTest(attr, value) {