/*
* Copyright (c) 2009-2012 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.util;
import com.jme3.renderer.Renderer;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* GLObjectManager tracks all GLObjects used by the Renderer. Using a
* <code>ReferenceQueue</code> the <code>GLObjectManager</code> can delete
* unused objects from GPU when their counterparts on the CPU are no longer used.
*
* On restart, the renderer may request the objects to be reset, thus allowing
* the GLObjects to re-initialize with the new display context.
*/
public class NativeObjectManager {
private static final Logger logger = Logger.getLogger(NativeObjectManager.class.getName());
/**
* Set to <code>true</code> to enable deletion of native buffers together with GL objects
* when requested. Note that usage of object after deletion could cause undefined results
* or native crashes, therefore by default this is set to <code>false</code>.
*/
public static boolean UNSAFE = false;
/**
* The maximum number of objects that should be removed per frame.
* If the limit is reached, no more objects will be removed for that frame.
*/
private static final int MAX_REMOVES_PER_FRAME = 100;
/**
* Reference queue for {@link NativeObjectRef native object references}.
*/
private ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();
/**
* List of currently active GLObjects.
*/
private HashMap<Long, NativeObjectRef> refMap = new HashMap<Long, NativeObjectRef>();
/**
* List of real objects requested by user for deletion.
*/
private ArrayDeque<NativeObject> userDeletionQueue = new ArrayDeque<NativeObject>();
private static class NativeObjectRef extends PhantomReference<Object> {
private NativeObject objClone;
private WeakReference<NativeObject> realObj;
public NativeObjectRef(ReferenceQueue<Object> refQueue, NativeObject obj){
super(obj.handleRef, refQueue);
assert obj.handleRef != null;
this.realObj = new WeakReference<NativeObject>(obj);
this.objClone = obj.createDestructableClone();
assert objClone.getId() == obj.getId();
}
}
/**
* (Internal use only) Register a <code>NativeObject</code> with the manager.
*/
public void registerObject(NativeObject obj) {
if (obj.getId() <= 0) {
throw new IllegalArgumentException("object id must be greater than zero");
}
NativeObjectRef ref = new NativeObjectRef(refQueue, obj);
refMap.put(obj.getUniqueId(), ref);
obj.setNativeObjectManager(this);
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "Registered: {0}", new String[]{obj.toString()});
}
}
private void deleteNativeObject(Object rendererObject, NativeObject obj, NativeObjectRef ref,
boolean deleteGL, boolean deleteBufs) {
assert rendererObject != null;
// "obj" is considered the real object (with buffers and everything else)
// if "ref" is null.
NativeObject realObj = ref != null ?
ref.realObj.get() :
obj;
assert realObj == null || obj.getId() == realObj.getId();
if (deleteGL) {
if (obj.getId() <= 0) {
logger.log(Level.WARNING, "Object already deleted: {0}", obj.getClass().getSimpleName() + "/" + obj.getId());
} else {
// Unregister it from cleanup list.
NativeObjectRef ref2 = refMap.remove(obj.getUniqueId());
if (ref2 == null) {
throw new IllegalArgumentException("This NativeObject is not " +
"registered in this NativeObjectManager");
}
assert ref == null || ref == ref2;
int id = obj.getId();
// Delete object from the GL driver
obj.deleteObject(rendererObject);
assert obj.getId() == NativeObject.INVALID_ID;
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "Deleted: {0}", obj.getClass().getSimpleName() + "/" + id);
}
if (realObj != null){
// Note: make sure to reset them as well
// They may get used in a new renderer in the future
realObj.resetObject();
}
}
}
if (deleteBufs && UNSAFE && realObj != null) {
// Only the real object has native buffers.
// The destructable clone has nothing and cannot be used in this case.
realObj.deleteNativeBuffersInternal();
}
}
/**
* (Internal use only) Deletes unused NativeObjects.
* Will delete at most {@link #MAX_REMOVES_PER_FRAME} objects.
*
* @param rendererObject The renderer object.
* For graphics objects, {@link Renderer} is used, for audio, {#link AudioRenderer} is used.
*/
public void deleteUnused(Object rendererObject){
int removed = 0;
while (removed < MAX_REMOVES_PER_FRAME && !userDeletionQueue.isEmpty()) {
// Remove user requested objects.
NativeObject obj = userDeletionQueue.pop();
deleteNativeObject(rendererObject, obj, null, true, true);
removed++;
}
while (removed < MAX_REMOVES_PER_FRAME) {
// Remove objects reclaimed by GC.
NativeObjectRef ref = (NativeObjectRef) refQueue.poll();
if (ref == null) {
break;
}
deleteNativeObject(rendererObject, ref.objClone, ref, true, false);
removed++;
}
if (removed >= 1) {
logger.log(Level.FINE, "NativeObjectManager: {0} native objects were removed from native", removed);
}
}
/**
* (Internal use only) Deletes all objects.
* Must only be called when display is destroyed.
*/
public void deleteAllObjects(Object rendererObject){
deleteUnused(rendererObject);
ArrayList<NativeObjectRef> refMapCopy = new ArrayList<NativeObjectRef>(refMap.values());
for (NativeObjectRef ref : refMapCopy) {
deleteNativeObject(rendererObject, ref.objClone, ref, true, false);
}
assert refMap.size() == 0;
}
/**
* Marks the given <code>NativeObject</code> as unused,
* to be deleted on the next frame.
* Usage of this object after deletion will cause an exception.
* Note that native buffers are only reclaimed if
* {@link #UNSAFE} is set to <code>true</code>.
*
* @param obj The object to mark as unused.
*/
void enqueueUnusedObject(NativeObject obj) {
userDeletionQueue.push(obj);
}
/**
* (Internal use only) Resets all {@link NativeObject}s.
* This is typically called when the context is restarted.
*/
public void resetObjects(){
for (NativeObjectRef ref : refMap.values()) {
// Must use the real object here, for this to be effective.
NativeObject realObj = ref.realObj.get();
if (realObj == null) {
continue;
}
realObj.resetObject();
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "Reset: {0}", realObj);
}
}
refMap.clear();
refQueue = new ReferenceQueue<Object>();
}
// public void printObjects(){
// System.out.println(" ------------------- ");
// System.out.println(" GL Object count: "+ objectList.size());
// for (GLObject obj : objectList){
// System.out.println(obj);
// }
// }
}