From b46973f33f17cabe8067285b8a3740825ea3f803 Mon Sep 17 00:00:00 2001 From: Bebul Date: Tue, 9 Dec 2014 15:04:42 +0100 Subject: [PATCH] Optimize Node.collideWith with no additional CollisionResults allocation --- .../jme3/collision/DummyCollisionResults.java | 77 +++++++++++++++++++ .../src/main/java/com/jme3/scene/Node.java | 9 ++- 2 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 jme3-core/src/main/java/com/jme3/collision/DummyCollisionResults.java diff --git a/jme3-core/src/main/java/com/jme3/collision/DummyCollisionResults.java b/jme3-core/src/main/java/com/jme3/collision/DummyCollisionResults.java new file mode 100644 index 000000000..f0f1777bd --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/collision/DummyCollisionResults.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2009-2014 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.collision; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * DummyCollisionResults is an empty "dummy" collection returned as a result of a + * collision detection operation done by {@link Collidable}. + * Its purpose is to avoid allocation of CollisionResults and its content, + * when collisionResults are out of our interest. + * Using the same API Collidable.collideWith() instead of possibly some new countCollideWith() implementation + * is chosen to keep the collision detection code "DRY", @see Don't repeat yourself + * + * @author Bebul + */ +public class DummyCollisionResults extends CollisionResults { + // the singleton for DummyCollisionResults is sufficient + private static DummyCollisionResults instance = null; + + // exists only to defeat instantiation + protected DummyCollisionResults() {} + + // classic singleton instance + public static DummyCollisionResults getInstance() { + if ( instance == null ) { + instance = new DummyCollisionResults(); + } + return instance; + } + + // Dummy implementation of CollisionResults + // All methods can be dummy except for iterator + @Override public void clear(){} + // even dummy iterator should return valid iterator + @Override public Iterator iterator() { + List dumbCompiler = Collections.emptyList(); + return dumbCompiler.iterator(); + } + @Override public void addCollision(CollisionResult result){} + @Override public int size(){ return 0; } + @Override public CollisionResult getClosestCollision(){ return null; } + @Override public CollisionResult getFarthestCollision(){ return null; } + @Override public CollisionResult getCollision(int index){ return null; } + @Override public CollisionResult getCollisionDirect(int index){ return null; } +} diff --git a/jme3-core/src/main/java/com/jme3/scene/Node.java b/jme3-core/src/main/java/com/jme3/scene/Node.java index b8d379315..330ebaf4c 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Node.java +++ b/jme3-core/src/main/java/com/jme3/scene/Node.java @@ -34,6 +34,7 @@ package com.jme3.scene; import com.jme3.bounding.BoundingVolume; import com.jme3.collision.Collidable; import com.jme3.collision.CollisionResults; +import com.jme3.collision.DummyCollisionResults; import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; import com.jme3.export.Savable; @@ -491,13 +492,17 @@ public class Node extends Spatial implements Savable { int total = 0; // optimization: try collideWith BoundingVolume to avoid possibly redundant tests on children + // number 4 in condition is somewhat arbitrary. When there is only one child, the boundingVolume test is redundant at all. + // The idea is when there are few children, it can be too expensive to test boundingVolume first. if (children.size() > 4) { BoundingVolume bv = this.getWorldBound(); if (bv==null) return 0; - CollisionResults bvColRes = new CollisionResults(); - if (bv.collideWith(other, bvColRes) == 0) return 0; + // use DummyCollisionResults to avoid allocation while making the call to any BoundingVolume.collideWith possible + // moreover, the DummyCollisionResults operations are empty, so faster + DummyCollisionResults dummyColRes = DummyCollisionResults.getInstance(); + if (bv.collideWith(other, dummyColRes) == 0) return 0; } for (Spatial child : children.getArray()){ total += child.collideWith(other, results);