Heya,
During my coding adventures I noticed that there are quite a few "scratch" vectors declared/constructed as private instance variables. Makes sense, creating objects in the update loop should be limited. As my code got more complex, I started to create more and more of these temp variables also.
I wondered if an object pool of scratch vectors might clean up all of my private "Vector3f temp1" nice enough so I decided to give it a shot. First I made a thread-local Queue to cache objects (thread-local queue means zero thread synchronization hits, and the pools seem to stay very small for me). I then made some additions to Vector3f to test, and updated my code that used temp vectors to see how it worked out.
I thought it was kind of cool that all "new Vector3f(…)" code could be replaced with "Vector3f.getInstance(…)" without any ill effects. Could even make the existing Vector3f constructors private after a while, that way a Vector3f would only ever be created when necessary through getInstance. Returning temporary vectors to the pool is one simple call (not sure if I like "done()" but it was the first thing that came to mind If no vectors are returned it just means the application is behaving exactly as it did before getInstance was used.
Good? Bad? Horrifying? It's 3:30am here so let me know if none of this makes sense
Here's the thread-local Queue:
package com.jme.util;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
public class ThreadLocalQueue<E> implements Queue<E> {
private ThreadLocal<Queue<E>> instancePool = new ThreadLocal<Queue<E>>();
public Queue<E> getInstance() {
Queue<E> pool = instancePool.get();
if (pool == null) {
pool = new LinkedList<E>();
instancePool.set(pool);
}
return pool;
}
public boolean add(E e) {
return getInstance().add(e);
}
public E element() {
return getInstance().element();
}
public boolean offer(E e) {
return getInstance().offer(e);
}
public E peek() {
return getInstance().peek();
}
public E poll() {
return getInstance().poll();
}
public E remove() {
return getInstance().remove();
}
public boolean addAll(Collection<? extends E> c) {
return getInstance().addAll(c);
}
public void clear() {
getInstance().clear();
}
public boolean contains(Object o) {
return getInstance().contains(o);
}
public boolean containsAll(Collection<?> c) {
return getInstance().containsAll(c);
}
public boolean isEmpty() {
return getInstance().isEmpty();
}
public Iterator<E> iterator() {
return getInstance().iterator();
}
public boolean remove(Object o) {
return getInstance().remove(o);
}
public boolean removeAll(Collection<?> c) {
return getInstance().removeAll(c);
}
public boolean retainAll(Collection<?> c) {
return getInstance().retainAll(c);
}
public int size() {
return getInstance().size();
}
public Object[] toArray() {
return getInstance().toArray();
}
public <T> T[] toArray(T[] a) {
return getInstance().toArray(a);
}
}
Here are my additions to Vector3f:
/**
* the pool of temporary vectors for use during calculations
*/
private static ThreadLocalQueue<Vector3f> instancePool = new ThreadLocalQueue<Vector3f>();
/**
* returns a Vector3f instance from the pool, or creates a new instance if the pool is empty.
*
* @return the next available vector instance, set to (0, 0, 0)
*
* @see #done()
*/
public static Vector3f getInstance() {
Vector3f v = instancePool.poll();
if (v == null) {
v = new Vector3f();
} else {
v.zero();
}
return v;
}
/**
* returns a Vector3f instance from the pool, or creates a new instance if the pool is empty.
*
* @param x
* the x value of the vector.
* @param y
* the y value of the vector.
* @param z
* the z value of the vector.
* @return the next available vector instance
*
* @see #done()
*/
public static Vector3f getInstance(float x, float y, float z) {
Vector3f v = instancePool.poll();
if (v == null) {
v = new Vector3f(x, y, z);
} else {
v.set(x, y, z);
}
return v;
}
/**
* returns a Vector3f instance from the pool, or creates a new instance if the pool is empty.
*
* @param copy
* The Vector3f to copy
* @return the next available vector instance
*
* @see #done()
*/
public static Vector3f getInstance(Vector3f copy) {
Vector3f v = instancePool.poll();
if (v == null) {
v = new Vector3f(copy);
} else {
v.set(copy);
}
return v;
}
/**
* returns this vector to the instance pool.
*
* @see #getInstance()
* @see #getInstance(Vector3f)
* @see #getInstance(float, float, float)
*/
public void done() {
instancePool.offer(this);
}
My very simple camera code to follow a spatial, using getInstance:
Vector3f cameraPos = Vector3f.getInstance(target.getWorldTranslation());
Vector3f rotationVec = Vector3f.getInstance();
Quaternion targetWorldRot = target.getWorldRotation();
if (targetOffset.x != 0) {
targetWorldRot.getRotationColumn(0, rotationVec);
cameraPos.addLocal(rotationVec.normalizeLocal().multLocal(targetOffset.x));
}
if (targetOffset.y != 0) {
targetWorldRot.getRotationColumn(1, rotationVec);
cameraPos.addLocal(rotationVec.normalizeLocal().multLocal(targetOffset.y));
}
if (targetOffset.z != 0) {
targetWorldRot.getRotationColumn(2, rotationVec);
cameraPos.addLocal(rotationVec.normalizeLocal().multLocal(targetOffset.z));
}
camera.setLocation(cameraPos);
target.getLocalRotation().getRotationColumn(1, rotationVec);
camera.lookAt(target.getWorldTranslation(), rotationVec);
camera.update();
cameraPos.done();
rotationVec.done();