All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javax.media.j3d.MasterControl Maven / Gradle / Ivy

/*
 * Copyright 1998-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 *
 */

/*
 * Portions of this code were derived from work done by the Blackdown
 * group (www.blackdown.org), who did the initial Linux implementation
 * of the Java 3D API.
 */

package javax.media.j3d;

import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.logging.Level;
import java.util.logging.Logger;

class MasterControl {

    /**
     * Options for the runMonitor
     */
    static final int CHECK_FOR_WORK = 0;
    static final int SET_WORK       = 1;
    static final int RUN_THREADS    = 2;
    static final int THREAD_DONE    = 3;
    static final int SET_WORK_FOR_REQUEST_RENDERER   = 5;
    static final int RUN_RENDERER_CLEANUP            = 6;

    // The thread states for MC
    static final int SLEEPING            = 0;
    static final int RUNNING             = 1;
    static final int WAITING_FOR_THREADS = 3;
    static final int WAITING_FOR_CPU     = 4;
    static final int WAITING_FOR_RENDERER_CLEANUP = 5;

    // Constants used in renderer thread argument
    static final Integer REQUESTRENDER = new Integer(Renderer.REQUESTRENDER);
    static final Integer RENDER = new Integer(Renderer.RENDER);
    static final Integer SWAP = new Integer(Renderer.SWAP);

    // Constants used for request from user threads
    static final Integer ACTIVATE_VIEW = new Integer(1);
    static final Integer DEACTIVATE_VIEW = new Integer(2);
    static final Integer START_VIEW = new Integer(3);
    static final Integer STOP_VIEW = new Integer(4);
    static final Integer REEVALUATE_CANVAS = new Integer(5);
    static final Integer UNREGISTER_VIEW = new Integer(6);
    static final Integer PHYSICAL_ENV_CHANGE = new Integer(7);
    static final Integer INPUTDEVICE_CHANGE = new Integer(8);
    static final Integer EMPTY_UNIVERSE = new Integer(9);
    static final Integer START_RENDERER = new Integer(10);
    static final Integer STOP_RENDERER = new Integer(11);
    static final Integer RENDER_ONCE = new Integer(12);
    static final Integer FREE_CONTEXT = new Integer(13);
    static final Integer FREE_DRAWING_SURFACE = new Integer(14);
    static final Integer FREE_MESSAGE = new Integer(15);
    static final Integer RESET_CANVAS = new Integer(16);
    static final Integer GETBESTCONFIG = new Integer(17);
    static final Integer ISCONFIGSUPPORT = new Integer(18);
    static final Integer SET_GRAPHICSCONFIG_FEATURES = new Integer(19);
    static final Integer SET_QUERYPROPERTIES = new Integer(20);
    static final Integer SET_VIEW = new Integer(21);

    // Developer logger for reporting informational messages; see getDevLogger()
    private static boolean devLoggerEnabled = false;
    private static Logger devLogger;

    // Stats logger for reporting runtime statistics; see getStatsLogger()
    private static boolean statsLoggerEnabled = false;
    private static Logger statsLogger;

    // Core logger for reporting internal errors, warning, and
    // informational messages; see getCoreLogger()
    private static boolean coreLoggerEnabled = false;
    private static Logger coreLogger;

    // Flag indicating that the rendering pipeline libraries are loaded
    private static boolean librariesLoaded = false;

    /**
     * reference to MasterControl thread
     */
    private MasterControlThread mcThread = null;

    /**
     * The list of views that are currently registered
     */
    private UnorderList views = new UnorderList(1, View.class);


    /**
     * by MIK OF CLASSX
     *
     * the flag to indicate whether the background of the offscreen
     * canvas must be transparent or not false by default
     */
    boolean transparentOffScreen = false;

    /**
     * Flag to indicate whether Pbuffers are used for off-screen
     * rendering; true by default.  Set by the "j3d.usePbuffer"
     * property, When this flag is set to false, Bitmap (Windows) or
     * Pixmap (UNIX) rendering will be used
     */
    boolean usePbuffer = true;

    /**
     * Flag to indicate whether should renderer view frustum culling is done;
     * true by default.
     * Set by the -Dj3d.viewFrustumCulling property, When this flag is
     * set to false, the renderer view frustum culling is turned off.
     */
    boolean viewFrustumCulling = true;

    /**
     * the flag to indicate whether the geometry should be locked or not
     */

    private boolean lockGeometry = false;

    /**
     * The number of registered views that are active
     */
    private int numActiveViews = 0;

    /**
     * The list of active universes get from View
     */
    private UnorderList activeUniverseList = new UnorderList(VirtualUniverse.class);

    /**
     * The list of universes register from View
     */
    private UnorderList regUniverseList = new UnorderList(VirtualUniverse.class);

    /**
     * A lock used for accessing time structures.
     */
    private Object timeLock = new Object();


    /**
     * The current "time" value
     */
    private long time = 0;

    /**
     * Use to assign threadOpts in Renderer thread.
     */
    private long waitTimestamp = 0;

    /**
     * The current list of work threads
     */
    private UnorderList stateWorkThreads =
                               new UnorderList(J3dThreadData.class);
    private UnorderList renderWorkThreads =
                               new UnorderList(J3dThreadData.class);
    private UnorderList requestRenderWorkThreads =
			       new UnorderList(J3dThreadData.class);

    /**
     * The current list of work threads
     */
    private UnorderList renderThreadData = new UnorderList(J3dThreadData.class);

    /**
     * The list of input device scheduler thread
     */
    private UnorderList inputDeviceThreads =
                             new UnorderList(1, InputDeviceScheduler.class);

    /**
     * A flag that is true when the thread lists need updating
     */
    private boolean threadListsChanged;


    /**
     * Markers for the last transform structure update thread
     * and the last update thread.
     */
    private int lastTransformStructureThread = 0;
    private int lastStructureUpdateThread = 0;

    /**
     * The current time snapshots
     */
    private long currentTime;

    // Only one Timer thread in the system.
    TimerThread timerThread;

    // Only one Notification thread in the system.
    private NotificationThread notificationThread;

    /**
     * This flag indicates that MC is running
     */
    volatile boolean running = true;

    /**
     * This flag indicates that MC has work to do
     */
    private boolean workToDo = false;

    /**
     * This flag indicates that there is work for requestRenderer
     */
    private boolean requestRenderWorkToDo = false;

   /**
     * The number of THREAD_DONE messages pending
     */
    private int threadPending = 0;
    private int renderPending = 0;
    private int statePending = 0;

    /**
     * State variables for work lists
     */
    private boolean renderWaiting = false;
    private boolean stateWaiting = false;

    /**
     * The current state of the MC thread
     */
    private int state = SLEEPING;

    // time for sleep in order to met the minimum frame duration
    private long sleepTime = 0;


    /**
     * The number of cpu's Java 3D may use
     */
    private int cpuLimit;

    /**
     * A list of mirror objects to be updated
     */
    private UnorderList mirrorObjects = new UnorderList(ObjectUpdate.class);

    /**
     * The renderingAttributesStructure for updating node component
     * objects
     */
    private RenderingAttributesStructure renderingAttributesStructure =
                        new RenderingAttributesStructure();

    /**
     * The default rendering method
     */
    private DefaultRenderMethod defaultRenderMethod = null;

    /**
     * The text3D rendering method
     */
    private Text3DRenderMethod text3DRenderMethod = null;

    /**
     * The vertex array rendering method
     */
    private VertexArrayRenderMethod vertexArrayRenderMethod = null;

    /**
     * The displayList rendering method
     */
    private DisplayListRenderMethod displayListRenderMethod = null;

    /**
     * The compressed geometry rendering method
     */
    private CompressedGeometryRenderMethod compressedGeometryRenderMethod = null;

    /**
     * The oriented shape3D rendering method
     */
    private OrientedShape3DRenderMethod orientedShape3DRenderMethod = null;

    /**
     * This is the start time upon which alpha's and behaviors
     * are synchronized to. It is initialized once, the first time
     * that a MasterControl object is created.
     */
    static long systemStartTime = 0L;

    // This is a time stamp used when context is created
    private long contextTimeStamp = 0;

    // This is an array of  canvasIds in used
    private boolean[] canvasIds = null;
    private int canvasFreeIndex = 0;
    private Object canvasIdLock = new Object();

    // This is a counter for rendererBit
    private int rendererCount = 0;

    // Flag that indicates whether to shared display context or not
    boolean isSharedCtx = false;

    // Flag that tells us to use NV_register_combiners
    boolean useCombiners = false;

    // Flag that indicates whether compile is disabled or not
    boolean disableCompile = false;

    // Flag that indicates whether or not compaction occurs
    boolean doCompaction = true;

    // Flag that indicates whether separate specular color is disabled or not
    boolean disableSeparateSpecularColor = false;

    // Flag that indicates whether DisplayList is used or not
    boolean isDisplayList = true;

    // If this flag is set, then by-ref geometry will not be
    // put in display list
    boolean buildDisplayListIfPossible = false;

    // If this flag is set, then geometry arrays with vertex attributes can
    // be in display list.
    boolean vertexAttrsInDisplayList = false;

    // Issue 249 - flag that indicates whether the soleUser optimization is permitted
    boolean allowSoleUser = false;

    // Issue 266 - Flag indicating whether null graphics configs are allowed
    // Set by -Dj3d.allowNullGraphicsConfig property
    // Setting this flag causes Canvas3D to allow a null GraphicsConfiguration
    // for on-screen canvases. This is only for backward compatibility with
    // legacy applications.
    boolean allowNullGraphicsConfig = false;

    // Issue 239 - Flag indicating whether the stencil buffer is cleared by
    // default each frame when the color and depth buffers are cleared.
    // Note that this is a partial solution, since we eventually want an API
    // to control this.
    boolean stencilClear = false;

    // REQUESTCLEANUP messages argument
    static Integer REMOVEALLCTXS_CLEANUP = new Integer(1);
    static Integer REMOVECTX_CLEANUP     = new Integer(2);
    static Integer REMOVENOTIFY_CLEANUP  = new Integer(3);
    static Integer RESETCANVAS_CLEANUP   = new Integer(4);
    static Integer FREECONTEXT_CLEANUP   = new Integer(5);

    // arguments for renderer resource cleanup run
    Object rendererCleanupArgs[] = {new Integer(Renderer.REQUESTCLEANUP),
				    null, null};


    // Context creation should obtain this lock, so that
    // first_time and all the extension initilialization
    // are done in the MT safe manner
    Object contextCreationLock = new Object();

    // Flag that indicates whether to lock the DSI while rendering
    boolean doDsiRenderLock = false;

    // Flag that indicates the pre-1.5 behavior of enforcing power-of-two
    // textures. If set, then any non-power-of-two textures will throw an
    // exception.
    boolean enforcePowerOfTwo = false;

    // Flag that indicates whether the framebuffer is sharing the
    // Z-buffer with both the left and right eyes when in stereo mode.
    // If this is true, we need to clear the Z-buffer between rendering
    // to the left and right eyes.
    boolean sharedStereoZBuffer = true;

    // True to disable all underlying multisampling API so it uses
    // the setting in the driver.
    boolean implicitAntialiasing = false;

    // False to disable compiled vertex array extensions if support
    boolean isCompiledVertexArray = true;

    // Number of reserved vertex attribute locations for GLSL (must be at
    // least 1).
    // Issue 269 - need to reserve up to 6 vertex attribtue locations to ensure
    // that we don't collide with a predefined gl_* attribute on nVidia cards.
    int glslVertexAttrOffset = 6;

    // Hashtable that maps a GraphicsDevice to its associated
    // Screen3D--this is only used for on-screen Canvas3Ds
    Hashtable deviceScreenMap = new Hashtable();

    // Use to store all requests from user threads.
    UnorderList requestObjList = new UnorderList();
    private UnorderList requestTypeList = new UnorderList(Integer.class);

    // Temporary storage to store stop request for requestViewList
    private UnorderList tempViewList = new UnorderList();
    private UnorderList renderOnceList = new UnorderList();

    // This flag is true when there is pending request
    // i.e. false when the above requestxxx Lists are all empty.
    private boolean pendingRequest = false;

    // Root ThreadGroup for creating Java 3D threads
    private static ThreadGroup rootThreadGroup;

    // Thread priority for all Java 3D threads
    private static int threadPriority;

    static private Object mcThreadLock = new Object();

	private ArrayList timestampUpdateList = new ArrayList(3);

    private UnorderList freeMessageList = new UnorderList(8);

    // Maximum number of lights
    int maxLights;

    // Set by the -Dj3d.sortShape3DBounds property, When this flag is
    // set to true, the bounds of the Shape3D node will be used in
    // place of the computed GeometryArray bounds for transparency
    // sorting for those Shape3D nodes whose boundsAutoCompute
    // attribute is set to false.
    boolean sortShape3DBounds = false;

    //Set by -Dj3d.forceReleaseView property.
    //Setting this flag as true disables the bug fix 4267395 in View deactivate().
    //The bug 4267395 can lock-up *some* systems, but the bug fix can
    //produce memory leaks in applications which creates and destroy Canvas3D
    //from time to time.
    //Set as true if you have memory leaks after disposing Canvas3D.
    //Default false value does affect Java3D View dispose behavior.
    boolean forceReleaseView = false;

    // Issue 480: Cache the bounds of nodes so that getBounds does not
    // recompute the boounds of the entire graph per call
    boolean cacheAutoComputedBounds = false;

    // issue 544
    boolean useBoxForGroupBounds = false;

    /**
     * Constructs a new MasterControl object.  Note that there is
     * exatly one MasterControl object, created statically by
     * VirtualUniverse.
     */
    MasterControl() {
        assert librariesLoaded;

        // Initialize the start time upon which alpha's and behaviors
        // are synchronized to (if it isn't already set).
        if (systemStartTime == 0L) {
            systemStartTime = J3dClock.currentTimeMillis();
        }

	if(J3dDebug.devPhase) {
	    // Check to see whether debug mode is allowed
	    J3dDebug.debug = getBooleanProperty("j3d.debug", false,
						"J3dDebug.debug");
	}

	// Check to see whether shared contexts are allowed
	isSharedCtx = getBooleanProperty("j3d.sharedctx", isSharedCtx, "shared contexts");

	doCompaction = getBooleanProperty("j3d.docompaction", doCompaction,
					  "compaction");

	// by MIK OF CLASSX
	transparentOffScreen = getBooleanProperty("j3d.transparentOffScreen", transparentOffScreen, "transparent OffScreen");

	usePbuffer = getBooleanProperty("j3d.usePbuffer",
					usePbuffer,
					"Off-screen Pbuffer");

	viewFrustumCulling = getBooleanProperty("j3d.viewFrustumCulling", viewFrustumCulling,"View frustum culling in the renderer is");

	sortShape3DBounds =
	    getBooleanProperty("j3d.sortShape3DBounds", sortShape3DBounds,
			       "Shape3D bounds enabled for transparency sorting",
			       "Shape3D bounds *ignored* for transparency sorting");

	forceReleaseView =
	    getBooleanProperty("j3d.forceReleaseView", forceReleaseView,
			       "forceReleaseView  after Canvas3D dispose enabled",
			       "forceReleaseView  after Canvas3D dispose disabled");

// FIXME: GL_NV_register_combiners
//	useCombiners = getBooleanProperty("j3d.usecombiners", useCombiners,
//					  "Using NV_register_combiners if available",
//					  "NV_register_combiners disabled");

	if (getProperty("j3d.disablecompile") != null) {
	    disableCompile = true;
	    System.err.println("Java 3D: BranchGroup.compile disabled");
	}

	if (getProperty("j3d.disableSeparateSpecular") != null) {
	    disableSeparateSpecularColor = true;
	    System.err.println("Java 3D: separate specular color disabled if possible");
	}

	isDisplayList = getBooleanProperty("j3d.displaylist", isDisplayList,
					   "display list");

	implicitAntialiasing =
	    getBooleanProperty("j3d.implicitAntialiasing",
			       implicitAntialiasing,
			       "implicit antialiasing");

	isCompiledVertexArray =
	    getBooleanProperty("j3d.compiledVertexArray",
			       isCompiledVertexArray,
			       "compiled vertex array");

        boolean j3dOptimizeSpace =
	    getBooleanProperty("j3d.optimizeForSpace", true,
			       "optimize for space");

        if (isDisplayList) {
            // Build Display list for by-ref geometry
            // ONLY IF optimizeForSpace is false
            if (!j3dOptimizeSpace) {
                buildDisplayListIfPossible = true;
            }

            // Build display lists for geometry with vertex attributes
            // ONLY if we are in GLSL mode and GLSL shaders are available
            vertexAttrsInDisplayList = true;
        }

        // Check to see whether Renderer can run without DSI lock
	doDsiRenderLock = getBooleanProperty("j3d.renderLock",
					     doDsiRenderLock,
					     "render lock");

        // Check to see whether we enforce power-of-two textures
        enforcePowerOfTwo = getBooleanProperty("j3d.textureEnforcePowerOfTwo",
					       enforcePowerOfTwo,
					       "checking power-of-two textures");

        // Issue 249 - check to see whether the soleUser optimization is permitted
        allowSoleUser = getBooleanProperty("j3d.allowSoleUser",
					   allowSoleUser,
					   "sole-user mode");

        // Issue 266 - check to see whether null graphics configs are allowed
        allowNullGraphicsConfig = getBooleanProperty("j3d.allowNullGraphicsConfig",
						     allowNullGraphicsConfig,
						     "null graphics configs");

        // Issue 239 - check to see whether per-frame stencil clear is enabled
        stencilClear = getBooleanProperty("j3d.stencilClear",
                                          stencilClear,
                                          "per-frame stencil clear");

        // Check to see if stereo mode is sharing the Z-buffer for both eyes.
	sharedStereoZBuffer =
	    getBooleanProperty("j3d.sharedstereozbuffer",
			       sharedStereoZBuffer,
			       "shared stereo Z buffer");

	// Get the maximum number of concurrent threads (CPUs)
	final int defaultThreadLimit = getNumberOfProcessors() + 1;
	Integer threadLimit = java.security.AccessController.doPrivileged(
	new java.security.PrivilegedAction() {
		@Override
		public Integer run() {
			return Integer.getInteger("j3d.threadLimit", defaultThreadLimit);
		}
	});

	cpuLimit = threadLimit.intValue();
	if (cpuLimit < 1)
	    cpuLimit = 1;
	if (J3dDebug.debug || cpuLimit != defaultThreadLimit) {
	    System.err.println("Java 3D: concurrent threadLimit = " +
			       cpuLimit);
	}

	// Get the input device scheduler sampling time
	Integer samplingTime = java.security.AccessController.doPrivileged(
	new java.security.PrivilegedAction() {
		@Override
		public Integer run() {
			return Integer.getInteger("j3d.deviceSampleTime", 0);
		}
	});

	if (samplingTime.intValue() > 0) {
	    InputDeviceScheduler.samplingTime =
		samplingTime.intValue();
	    System.err.println("Java 3D: Input device sampling time = "
			       + samplingTime + " ms");
	}

	// Get the glslVertexAttrOffset
	final int defaultGLSLVertexAttrOffset = glslVertexAttrOffset;
	Integer vattrOffset = java.security.AccessController.doPrivileged(
	new java.security.PrivilegedAction() {
		@Override
		public Integer run() {
			return Integer.getInteger("j3d.glslVertexAttrOffset",
					defaultGLSLVertexAttrOffset);
		}
	});

	glslVertexAttrOffset = vattrOffset.intValue();
        if (glslVertexAttrOffset < 1) {
            glslVertexAttrOffset = 1;
        }
	if (J3dDebug.debug || glslVertexAttrOffset != defaultGLSLVertexAttrOffset) {
	    System.err.println("Java 3D: glslVertexAttrOffset = " +
			       glslVertexAttrOffset);
	}

        // Issue 480 : Cache bounds returned by getBounds()
        cacheAutoComputedBounds =
                getBooleanProperty("j3d.cacheAutoComputeBounds",
                cacheAutoComputedBounds,
                "Cache AutoCompute Bounds, accelerates getBounds()");

        // Issue 544
        useBoxForGroupBounds =
                getBooleanProperty("j3d.useBoxForGroupBounds",
                useBoxForGroupBounds,
                "Use of BoundingBox for group geometric bounds");

        // Check for obsolete properties
        String[] obsoleteProps = {
            "j3d.backgroundtexture",
            "j3d.forceNormalized",
            "j3d.g2ddrawpixel",
            "j3d.simulatedMultiTexture",
            "j3d.useFreeLists",
        };
        for (int i = 0; i < obsoleteProps.length; i++) {
            if (getProperty(obsoleteProps[i]) != null) {
                System.err.println("Java 3D: " + obsoleteProps[i] + " property ignored");
            }
        }

	// Get the maximum Lights
	maxLights = Pipeline.getPipeline().getMaximumLights();

	// create the freelists
	FreeListManager.createFreeLists();

	// create an array canvas use registers
	// The 32 limit can be lifted once the
	// resourceXXXMasks in other classes
	// are change not to use integer.
	canvasIds = new boolean[32];
	for(int i=0; i() {
                @Override
                public Object run() {
                    coreLoggerEnabled = initLogger(coreLogger, null);
                    devLoggerEnabled = initLogger(devLogger, Level.OFF);
                    statsLoggerEnabled = initLogger(statsLogger, Level.OFF);
                    return null;
                }
        });
    }

    /**
     * Get the developer logger -- OFF by default
     *
     * WARNING - for probable incorrect or inconsistent api usage
     * INFO - for informational messages such as performance hints (less verbose than FINE)
     * FINE - for informational messages from inner loops
     * FINER - using default values which may not be optimal
     */
    static Logger getDevLogger() {
        return devLogger;
    }

    static boolean isDevLoggable(Level level) {
        return devLoggerEnabled && devLogger.isLoggable(level);
    }

    /**
     * Get the stats logger -- OFF by default
     *
     * WARNING - statistical anomalies
     * INFO - basic performance stats - not too verbose and minimally intrusive
     * FINE - somewhat verbose and intrusive
     * FINER - more verbose and intrusive
     * FINEST - most verbose and intrusive
     */
    static Logger getStatsLogger() {
        return statsLogger;
    }

    static boolean isStatsLoggable(Level level) {
        return statsLoggerEnabled && statsLogger.isLoggable(level);
    }

    /**
     * Get the core logger -- level is INFO by default
     *
     * SEVERE - Serious internal errors
     * WARNING - Possible internal errors or anomalies
     * INFO - General informational messages
     * FINE - Internal debugging information - somewhat verbose
     * FINER - Internal debugging information - more verbose
     * FINEST - Internal debugging information - most verbose
     */
    static Logger getCoreLogger() {
        return coreLogger;
    }

    static boolean isCoreLoggable(Level level) {
        return coreLoggerEnabled && coreLogger.isLoggable(level);
    }

private static String getProperty(final String prop) {
	return java.security.AccessController.doPrivileged(
		new java.security.PrivilegedAction() {
			@Override
			public String run() {
				return System.getProperty(prop);
			}
		});
}

	static int getIntegerProperty(String prop, int defaultValue) {
		int value = defaultValue;
		String propValue = getProperty(prop);

		if (propValue != null) {
			try {
				value = Integer.parseInt(propValue);
			}
			catch (NumberFormatException e) {}
		}
		if (J3dDebug.debug)
			System.err.println("Java 3D: " + prop + "=" + value);

		return value;
	}

    static boolean getBooleanProperty(String prop,
					      boolean defaultValue,
					      String trueMsg,
					      String falseMsg) {
	boolean value = defaultValue;
	String propValue = getProperty(prop);

	if (propValue != null) {
	    value = Boolean.valueOf(propValue).booleanValue();
		if (J3dDebug.debug)
			System.err.println("Java 3D: " + (value ? trueMsg : falseMsg));
	}
	return value;
    }

    static boolean getBooleanProperty(String prop,
					      boolean defaultValue,
					      String msg) {
	return getBooleanProperty(prop,
				  defaultValue,
				  (msg + " enabled"),
				  (msg + " disabled"));
    }

    /**
     * Method to create and initialize the rendering Pipeline object,
     * and to load the native libraries needed by Java 3D. This is
     * called by the static initializer in VirtualUniverse before
     * the MasterControl object is created.
     */
    static void loadLibraries() {
        assert !librariesLoaded;

        // Initialize the Pipeline object associated with the
        // renderer specified by the "j3d.rend" system property.
        //
        // XXXX : We should consider adding support for a more flexible,
        // dynamic selection scheme via an API call.

        // Default rendering pipeline is the JOGL pipeline
        Pipeline.Type pipelineType = Pipeline.Type.JOGL;

        final String rendStr = getProperty("j3d.rend");
        if (rendStr == null) {
            // Use default pipeline
        } else if (rendStr.equals("jogl")) {
            pipelineType = Pipeline.Type.JOGL;
        } else if (rendStr.equals("noop")) {
            pipelineType = Pipeline.Type.NOOP;
        } else {
            System.err.println("Java 3D: Unrecognized renderer: " + rendStr);
            // Use default pipeline
        }

		// Java 3D cannot run in headless mode unless using the noop renderer
		if (java.awt.GraphicsEnvironment.isHeadless() && pipelineType != Pipeline.Type.NOOP) {
			throw new java.awt.HeadlessException();
		}

        // Construct the singleton Pipeline instance
		Pipeline.createPipeline(pipelineType);

        librariesLoaded = true;
    }


    /**
     * Invoke from InputDeviceScheduler to create an
     * InputDeviceBlockingThread.
     */
    InputDeviceBlockingThread getInputDeviceBlockingThread(
					   final InputDevice device) {

	return java.security.AccessController.doPrivileged(
		new java.security.PrivilegedAction() {
			@Override
			public InputDeviceBlockingThread run() {
				synchronized (rootThreadGroup) {
					InputDeviceBlockingThread thread = new InputDeviceBlockingThread(
							rootThreadGroup, device);
					thread.setPriority(threadPriority);
					return thread;
				}
			}
		});
    }

    /**
     * Set thread priority to all threads under Java3D thread group.
     */
    void setThreadPriority(final int pri) {
	synchronized (rootThreadGroup) {
	    threadPriority = pri;
	    java.security.AccessController.doPrivileged(
                new java.security.PrivilegedAction() {
                    @Override
                    public Object run() {
			Thread list[] = new
			    Thread[rootThreadGroup.activeCount()];
			int count = rootThreadGroup.enumerate(list);
			for (int i=count-1; i >=0; i--) {
			    list[i].setPriority(pri);
			}
			return null;
		    }
	    });
	}
    }


    /**
     * Return Java3D thread priority
     */
    int getThreadPriority() {
	return threadPriority;
    }

    /**
     * This returns the a unused renderer bit
     */
    int getRendererBit() {
        return (1 << rendererCount++);
    }


    /**
     * This returns the a unused renderer bit
     */
    int getRendererId() {
        return rendererCount++;
    }

    /**
     * This returns a context creation time stamp
     * Note: this has to be called under the contextCreationLock
     */
    long getContextTimeStamp() {
	return (++contextTimeStamp);
    }


    /**
     * This returns the a unused displayListId
     */
    Integer getDisplayListId() {
        return (Integer) FreeListManager.getObject(FreeListManager.DISPLAYLIST);
    }

    void freeDisplayListId(Integer id) {
	FreeListManager.freeObject(FreeListManager.DISPLAYLIST, id);
    }

    int getCanvasId() {
        int i;

	synchronized(canvasIdLock) {
	    // Master control need to keep count itself
	    for(i=canvasFreeIndex; i= canvasIds.length) {
                throw new RuntimeException("Cannot render to more than 32 Canvas3Ds");
	    }

	    canvasIds[i] = true;
	    canvasFreeIndex = i + 1;
	}

        return i;

    }

    void freeCanvasId(int canvasId) {
        // Valid range is [0, 31]
	synchronized(canvasIdLock) {

	    canvasIds[canvasId] = false;
	    if(canvasFreeIndex > canvasId) {
		canvasFreeIndex = canvasId;
	    }
	}
    }


    /**
     * Create a Renderer if it is not already done so.
     * This is used for GraphicsConfigTemplate3D passing
     * graphics call to RequestRenderer, and for creating
     * an off-screen buffer for an off-screen Canvas3D.
     */
    private Renderer createRenderer(GraphicsConfiguration gc) {
	final GraphicsDevice gd = gc.getDevice();

	Renderer rdr = Screen3D.deviceRendererMap.get(gd);
	if (rdr != null) {
	    return rdr;
	}


	java.security.AccessController.doPrivileged(
	     new java.security.PrivilegedAction() {
                    @Override
                    public Object run() {
			Renderer r;
		        synchronized (rootThreadGroup) {
			    r = new Renderer(rootThreadGroup);
			    r.initialize();
			    r.setPriority(threadPriority);
			    Screen3D.deviceRendererMap.put(gd, r);
			}
			return null;
		   }
	});

	threadListsChanged = true;

	return Screen3D.deviceRendererMap.get(gd);
    }

    /**
     * Post the request in queue
     */
    void postRequest(Integer type, Object obj) {

	synchronized (mcThreadLock) {
	    synchronized (requestObjList) {
		if (mcThread == null) {
		    if ((type == ACTIVATE_VIEW) ||
			(type == GETBESTCONFIG) ||
			(type == SET_VIEW) ||
			(type == ISCONFIGSUPPORT) ||
			(type == SET_QUERYPROPERTIES) ||
			(type == SET_GRAPHICSCONFIG_FEATURES)) {
			createMasterControlThread();
			requestObjList.add(obj);
			requestTypeList.add(type);
			pendingRequest = true;
		    } else if (type == EMPTY_UNIVERSE) {
			destroyUniverseThreads((VirtualUniverse) obj);
		    } else if (type == STOP_VIEW) {
			View v = (View) obj;
			v.stopViewCount = -1;
			v.isRunning = false;
		    } else if (type == STOP_RENDERER) {
			if (obj instanceof Canvas3D) {
			    ((Canvas3D) obj).isRunningStatus = false;
			} else {
			    ((Renderer) obj).userStop = true;
			}
		    } else if (type == UNREGISTER_VIEW) {
			((View) obj).doneUnregister = true;
		    } else {
			requestObjList.add(obj);
			requestTypeList.add(type);
			pendingRequest = true;
		    }
		} else {
		    requestObjList.add(obj);
		    requestTypeList.add(type);
		    pendingRequest = true;
		}
	    }
	}

	setWork();
    }




    /**
     * This procedure is invoked when isRunning is false.
     * Return true when there is no more pending request so that
     * Thread can terminate. Otherwise we have to recreate
     * the MC related threads.
     */
    boolean mcThreadDone() {
	synchronized (mcThreadLock) {
	    synchronized (requestObjList) {
		if (!pendingRequest) {
		    mcThread = null;
		    if (renderingAttributesStructure.updateThread !=
			null) {
			renderingAttributesStructure.updateThread.finish();
			renderingAttributesStructure.updateThread =
			    null;
		    }
		    renderingAttributesStructure = new RenderingAttributesStructure();
		    if (timerThread != null) {
			timerThread.finish();
			timerThread = null;
		    }
		    if (notificationThread != null) {
			notificationThread.finish();
			notificationThread = null;
		    }
		    requestObjList.clear();
		    requestTypeList.clear();
		    return true;
		}
		running = true;
		createMCThreads();
		return false;
	    }
	}
    }

    /**
     * This method increments and returns the next time value
     * timeLock must get before this procedure is invoked
     */
    final long getTime() {
	return (time++);
    }


    /**
     * This takes a given message and parses it out to the structures and
     * marks its time value.
     */
    void processMessage(J3dMessage message) {

        synchronized (timeLock) {
	    message.time = getTime();
	    sendMessage(message);
	}
	setWork();
    }

    /**
     * This takes an array of messages and parses them out to the structures and
     * marks the time value. Make sure, setWork() is done at the very end
     * to make sure all the messages will be processed in the same frame
     */
    void processMessage(J3dMessage[] messages) {

        synchronized (timeLock) {
	    long time = getTime();

	    for (int i = 0; i < messages.length; i++) {
		messages[i].time = time;
		sendMessage(messages[i]);
	    }
	}
	setWork();
    }

    /**
     * This takes the specified notification message and sends it to the
     * notification thread for processing.
     */
    void sendNotification(J3dNotification notification) {
        notificationThread.addNotification(notification);
    }

    /**
     * Create and start the MasterControl Thread.
     */
    void createMasterControlThread() {
        // Issue 364: don't create master control thread if already created
        if (mcThread != null) {
            return;
        }

	running = true;
	workToDo = true;
	state = RUNNING;
	java.security.AccessController.doPrivileged(
	    new java.security.PrivilegedAction() {
                @Override
                public Object run() {
		    synchronized (rootThreadGroup) {
			mcThread = new
			    MasterControlThread(rootThreadGroup);
			mcThread.setPriority(threadPriority);
		    }
		    return null;
		}
	});
    }

    // assuming the timeLock is already acquired

    /**
     * Send a message to another Java 3D thread.
     */
    void sendMessage(J3dMessage message) {

 	  synchronized (message) {
	    VirtualUniverse u = message.universe;
	    int targetThreads = message.threads;

            if (isCoreLoggable(Level.FINEST)) {
                dumpMessage("sendMessage", message);
            }

	    if ((targetThreads & J3dThread.UPDATE_RENDERING_ATTRIBUTES) != 0) {
		renderingAttributesStructure.addMessage(message);
	    }

	    // GraphicsContext3D send message with universe = null
	    if (u != null) {
		if ((targetThreads & J3dThread.UPDATE_GEOMETRY) != 0) {
		    u.geometryStructure.addMessage(message);
		}
		if ((targetThreads & J3dThread.UPDATE_TRANSFORM) != 0) {
		    u.transformStructure.addMessage(message);
		}
		if ((targetThreads & J3dThread.UPDATE_BEHAVIOR) != 0) {
		    u.behaviorStructure.addMessage(message);
		}
		if ((targetThreads & J3dThread.UPDATE_SOUND) != 0) {
		    u.soundStructure.addMessage(message);
		}
		if ((targetThreads & J3dThread.UPDATE_RENDERING_ENVIRONMENT) != 0) {
		    u.renderingEnvironmentStructure.addMessage(message);
		}
	    }

            if ((targetThreads & J3dThread.SOUND_SCHEDULER) != 0) {
                // Note that we don't check for active view
                if (message.view != null && message.view.soundScheduler != null ) {
                    // This make sure that message won't lost even
                    // though this view not yet register
                    message.view.soundScheduler.addMessage(message);
                } else {
		    synchronized (views) {
			View v[] = (View []) views.toArray(false);
			int i = views.arraySize()-1;
			if (u == null) {
			    while (i>=0) {
				v[i--].soundScheduler.addMessage(message);
			    }
			} else {
			    while (i>=0) {
				if (v[i].universe == u) {
				    v[i].soundScheduler.addMessage(message);
				}
				i--;
			    }
			}
		    }
                }
            }

	    if ((targetThreads & J3dThread.UPDATE_RENDER) != 0) {
		// Note that we don't check for active view
		if (message.view != null && message.view.renderBin != null) {
		    // This make sure that message won't lost even
		    // though this view not yet register
		    message.view.renderBin.addMessage(message);
		} else {
		    synchronized (views) {
			View v[] = (View []) views.toArray(false);
			int i = views.arraySize()-1;
			if (u == null) {
			    while (i>=0) {
				v[i--].renderBin.addMessage(message);
			    }
			}
			else {
			    while (i>=0) {
				if (v[i].universe == u) {
				    v[i].renderBin.addMessage(message);
				}
				i--;
			    }
			}
		    }
		}
	    }

	    if (message.getRefcount() == 0) {
		message.clear();
	    }
	  }
    }


    /**
     * Send a message to another Java 3D thread.
     * This variant is only call by TimerThread for Input Device Scheduler
     * or to redraw all View for RenderThread
     */
    void sendRunMessage(int targetThreads) {

	synchronized (timeLock) {

	    long time = getTime();

	    if ((targetThreads & J3dThread.INPUT_DEVICE_SCHEDULER) != 0) {
		synchronized (inputDeviceThreads) {
		    InputDeviceScheduler ds[] = (InputDeviceScheduler [])
			inputDeviceThreads.toArray(false);
		    for (int i=inputDeviceThreads.size()-1; i >=0; i--) {
			if (ds[i].physicalEnv.activeViewRef > 0) {
			    ds[i].getThreadData().lastUpdateTime =
				time;
			}
		    }

		    // timerThread instance in MC will set to null in
		    // destroyUniverseThreads() so we need to check if
		    // TimerThread kick in to sendRunMessage() after that.
		    // It happens because TimerThread is the only thread run
		    // asychronizously with MasterControl thread.

		    if (timerThread != null) {
			// Notify TimerThread to wakeup this procedure
			// again next time.
			timerThread.addInputDeviceSchedCond();
		    }
		}
	    }
	    if ((targetThreads & J3dThread.RENDER_THREAD) != 0) {
		synchronized (renderThreadData) {
		    J3dThreadData[] threads = (J3dThreadData [])
			renderThreadData.toArray(false);
		    int i=renderThreadData.arraySize()-1;
		    J3dThreadData thr;
		    while (i>=0) {
			thr = threads[i--];
			if ( thr.view.renderBinReady) {
			    thr.lastUpdateTime = time;
			}
		    }
		}
	    }
	}
	setWork();
    }

    /**
     * Send a message to another Java 3D thread.
     * This variant is only call by TimerThread for Sound Scheduler
     */
    void sendRunMessage(long waitTime, View view, int targetThreads) {

	synchronized (timeLock) {

	    long time = getTime();

	    if ((targetThreads & J3dThread.SOUND_SCHEDULER) != 0) {
		if (view.soundScheduler != null)  {
		    view.soundScheduler.threadData.lastUpdateTime = time;
		}
		// wakeup this procedure next time
		// QUESTION: waitTime calculated some milliseconds BEFORE
		//     this methods getTime() called - shouldn't actual
		//     sound Complete time be passed by SoundScheduler
		// QUESTION: will this wake up only soundScheduler associated
		//     with this view?? (since only it's lastUpdateTime is set)
		//     or all soundSchedulers??
		timerThread.addSoundSchedCond(time+waitTime);
	    }
	}
	setWork();
    }

    /**
     * Send a message to another Java 3D thread.
     * This variant is only called to update Render Thread
     */
    void sendRunMessage(View v, int targetThreads) {

	synchronized (timeLock) {
	    long time = getTime();

	    if ((targetThreads & J3dThread.RENDER_THREAD) != 0) {
		synchronized (renderThreadData) {
		    J3dThreadData[] threads = (J3dThreadData [])
			renderThreadData.toArray(false);
		    int i=renderThreadData.arraySize()-1;
		    J3dThreadData thr;
		    while (i>=0) {
			thr = threads[i--];
			if (thr.view == v && v.renderBinReady) {
			    thr.lastUpdateTime = time;
			}
		    }
		}
	    }
	}
	setWork();
    }


    /**
     * This sends a run message to the given threads.
     */
    void sendRunMessage(VirtualUniverse u, int targetThreads) {
	// We don't sendRunMessage to update structure except Behavior

	synchronized (timeLock) {
	    long time = getTime();

	    if ((targetThreads & J3dThread.BEHAVIOR_SCHEDULER) != 0) {
		if (u.behaviorScheduler != null) {
		    u.behaviorScheduler.getThreadData(null,
						      null).lastUpdateTime = time;
		}
	    }

	    if ((targetThreads & J3dThread.UPDATE_BEHAVIOR) != 0) {
		u.behaviorStructure.threadData.lastUpdateTime = time;
	    }

	    if ((targetThreads & J3dThread.UPDATE_GEOMETRY) != 0) {
		u.geometryStructure.threadData.lastUpdateTime = time;
	    }

	    if ((targetThreads & J3dThread.UPDATE_SOUND) != 0) {
		u.soundStructure.threadData.lastUpdateTime = time;
	    }

	    if ((targetThreads & J3dThread.SOUND_SCHEDULER) != 0) {
		synchronized (views) {
		    View v[] = (View []) views.toArray(false);
		    for (int i= views.arraySize()-1; i >=0; i--) {
			if ((v[i].soundScheduler != null) &&
			    (v[i].universe == u)) {
			    v[i].soundScheduler.threadData.lastUpdateTime = time;
			}
		    }
		}
	    }

	    if ((targetThreads & J3dThread.RENDER_THREAD) != 0) {

		synchronized (renderThreadData) {
		    J3dThreadData[] threads = (J3dThreadData [])
			renderThreadData.toArray(false);
		    int i=renderThreadData.arraySize()-1;
		    J3dThreadData thr;
		    while (i>=0) {
			thr = threads[i--];
			if (thr.view.universe == u && thr.view.renderBinReady) {
			    thr.lastUpdateTime = time;
			}
		    }
		}
	    }
	}

	setWork();
    }


    /**
     * Return a clone of View, we can't access
     * individual element of View after getting the size
     * in separate API call without synchronized views.
     */
    UnorderList cloneView() {
	return (UnorderList) views.clone();
    }

    /**
     * Return true if view is already registered with MC
     */
    boolean isRegistered(View view) {
	return views.contains(view);
    }

    /**
     * This snapshots the time values to be used for this iteration.
     * Note that this method is called without the timeLock held.
     * We must synchronize on timeLock to prevent updating
     * thread.lastUpdateTime from user thread in sendMessage()
     * or sendRunMessage().
     */
    private void updateTimeValues() {
        synchronized (timeLock) {
            int i=0;
            J3dThreadData lastThread=null;
            J3dThreadData thread=null;
            long lastTime = currentTime;

            currentTime = getTime();

            J3dThreadData threads[] = (J3dThreadData [])
                                    stateWorkThreads.toArray(false);
            int size = stateWorkThreads.arraySize();

            while (i thread.lastRunTime) &&
                    !thread.thread.userStop) {
                    lastThread = thread;
                    thread.needsRun = true;
                    thread.threadOpts = J3dThreadData.CONT_THREAD;
                    thread.lastRunTime =  currentTime;
                } else {
                    thread.needsRun = false;
                }
            }

            if (lastThread != null) {
                lastThread.threadOpts =  J3dThreadData.WAIT_ALL_THREADS;
                lastThread = null;
            }

            while (i thread.lastRunTime) &&
                    !thread.thread.userStop) {
                    lastThread = thread;
                    thread.needsRun = true;
                    thread.threadOpts = J3dThreadData.CONT_THREAD;
                    thread.lastRunTime = currentTime;
                } else {
                    thread.needsRun = false;
                }
            }
            if (lastThread != null) {
                lastThread.threadOpts = J3dThreadData.WAIT_ALL_THREADS;
                lastThread = null;
            }

            while (i thread.lastRunTime) &&
                    !thread.thread.userStop) {
                    lastThread = thread;
                    thread.needsRun = true;
                    thread.threadOpts = J3dThreadData.CONT_THREAD;
                    thread.lastRunTime = currentTime;
                } else {
                    thread.needsRun = false;
                }
            }
            if (lastThread != null) {
                lastThread.threadOpts = J3dThreadData.WAIT_ALL_THREADS;
                lastThread = null;
            }


            threads = (J3dThreadData []) renderWorkThreads.toArray(false);
            size = renderWorkThreads.arraySize();
            View v;
            J3dThreadData lastRunThread = null;
            waitTimestamp++;
            sleepTime = 0L;

            boolean threadToRun = false; // Not currently used

            // Fix for Issue 12: loop through the list of threads, calling
            // computeCycleTime() exactly once per view. This ensures that
            // all threads for a given view see consistent values for
            // isMinCycleTimeAchieve and sleepTime.
            v = null;
            for (i=0; i sleepTime) {
                        sleepTime = thread.view.sleepTime;
                    }
                }
                v = thread.view;
            }

            v = null;
            for (i=0; i thread.lastRunTime) &&
                    !thread.thread.userStop) {

                    if (thread.thread.lastWaitTimestamp == waitTimestamp) {
                        // This renderer thread is repeated. We must wait
                        // until all previous renderer threads done before
                        // allowing this thread to continue. Note that
                        // lastRunThread can't be null in this case.
                        waitTimestamp++;
                        if (thread.view != v) {
                            // A new View is start
                            v = thread.view;
                            threadToRun = true;
                            lastRunThread.threadOpts =
                                (J3dThreadData.STOP_TIMER |
                                 J3dThreadData.WAIT_ALL_THREADS);
                            ((Object []) lastRunThread.threadArgs)[3] = lastRunThread.view;
                            thread.threadOpts = (J3dThreadData.START_TIMER |
                                                 J3dThreadData.CONT_THREAD);
                        } else {
                            if ((lastRunThread.threadOpts &
                                 J3dThreadData.START_TIMER) != 0) {
                                lastRunThread.threadOpts =
                                    (J3dThreadData.START_TIMER |
                                     J3dThreadData.WAIT_ALL_THREADS);

                            } else {
                                lastRunThread.threadOpts =
                                    J3dThreadData.WAIT_ALL_THREADS;
                            }
                            thread.threadOpts = J3dThreadData.CONT_THREAD;

                        }
                    } else {
                        if (thread.view != v) {
                            v = thread.view;
                            threadToRun = true;
                            // Although the renderer thread is not
                            // repeated. We still need to wait all
                            // previous renderer threads if new View
                            // start.
                            if (lastRunThread != null) {
                                lastRunThread.threadOpts =
                                    (J3dThreadData.STOP_TIMER |
                                     J3dThreadData.WAIT_ALL_THREADS);
                                ((Object []) lastRunThread.threadArgs)[3]
                                    = lastRunThread.view;
                            }
                            thread.threadOpts = (J3dThreadData.START_TIMER |
                                                 J3dThreadData.CONT_THREAD);
                        } else {
                            thread.threadOpts = J3dThreadData.CONT_THREAD;
                        }
                    }
                    thread.thread.lastWaitTimestamp = waitTimestamp;
                    thread.needsRun = true;
                    thread.lastRunTime = currentTime;
                    lastRunThread = thread;
                } else {
                    thread.needsRun = false;
                }
            }


            if (lastRunThread != null) {
                lastRunThread.threadOpts =
                                    (J3dThreadData.STOP_TIMER |
                                     J3dThreadData.WAIT_ALL_THREADS|
                                     J3dThreadData.LAST_STOP_TIMER);
                lockGeometry = true;
                ((Object []) lastRunThread.threadArgs)[3] = lastRunThread.view;
            }  else {
                lockGeometry = false;
            }
        }

        // Issue 275 - go to sleep without holding timeLock
	// Sleep for the amount of time needed to satisfy the minimum
	// cycle time for all views.
	if (sleepTime > 0) {
	    // System.err.println("MasterControl: sleep(" + sleepTime + ")");
	    try {
		Thread.sleep(sleepTime);
	    } catch (InterruptedException e) {
		System.err.println(e);
	    }
	    // System.err.println("MasterControl: done sleeping");
	}
    }

    private void createUpdateThread(J3dStructure structure) {
	final J3dStructure s = structure;

	if (s.updateThread == null) {
	    java.security.AccessController.doPrivileged(
	        new java.security.PrivilegedAction() {
                   @Override
                   public Object run() {
		       synchronized (rootThreadGroup) {
                           s.updateThread = new StructureUpdateThread(
                   	        rootThreadGroup, s, s.threadType);
			   s.updateThread.setPriority(threadPriority);
		       }
		       return null;
		   }
	    });
	    s.updateThread.initialize();
	    s.threadData.thread = s.updateThread;
	    // This takes into accout for thread that just destroy and
	    // create again. In this case the threadData may receive
	    // message before the thread actually created. We don't want
	    // the currentTime to overwrite the update time of which
	    // is set by threadData when get message.
	    s.threadData.lastUpdateTime = Math.max(currentTime,
						   s.threadData.lastUpdateTime);
	}
    }

    private void emptyMessageList(J3dStructure structure, View v) {
	if (structure != null) {
	    if (v == null) {
		if (structure.threadData != null) {
		    structure.threadData.thread = null;
		}

		if (structure.updateThread != null) {
		    structure.updateThread.structure = null;
		}
		structure.updateThread = null;
	    }
	    boolean otherViewExist = false;
	    if ((v != null) && (v.universe != null)) {
		// Check if there is any other View register with the
		// same universe
		for (int i=views.size()-1; i >= 0; i--) {
		    if (((View) views.get(i)).universe == v.universe) {
			otherViewExist = true;
			break;
		    }
		}
	    }


	    UnorderList mlist = structure.messageList;
	    // Note that message is add at the end of array
	    synchronized (mlist) {
		int size = mlist.size();
		if (size > 0) {
		    J3dMessage mess[] = (J3dMessage []) mlist.toArray(false);
		    J3dMessage m;
		    int i = 0;

		    while (i < size) {
			m = mess[i];
			if ((v == null) || (m.view == v) ||
			    ((m.view == null) && !otherViewExist)) {
			    if (m.type == J3dMessage.INSERT_NODES) {
				// There is another View register request
				// immediately following, so no need
				// to remove message.
				break;
			    }
			    // Some other thread may still using this
			    // message so we should not directly
			    // add this message to free lists
			    m.decRefcount();
			    mlist.removeOrdered(i);
			    size--;
			} else {
			    i++;
			}
		    }
		}
	    }
	}
    }

    private void destroyUpdateThread(J3dStructure structure) {
	// If unregisterView message got before EMPTY_UNIVERSE
	// message, then updateThread is already set to null.
	if (structure.updateThread != null) {
	    structure.updateThread.finish();
	    structure.updateThread.structure = null;
	    structure.updateThread = null;
	}
	structure.threadData.thread = null;
	structure.clearMessages();
    }

    /**
     * This register a View with MasterControl.
     * The View has at least one Canvas3D added to a container.
     */
    private void registerView(View v) {
	final VirtualUniverse univ = v.universe;

	if (views.contains(v) && regUniverseList.contains(univ)) {
	    return;  // already register
	}

	if (timerThread == null) {
	    // This handle the case when MC shutdown and restart in
	    // a series of pending request
	    running = true;
	    createMCThreads();
	}
	// If viewId is null, assign one ..
	v.assignViewId();

	// Create thread if not done before
	createUpdateThread(univ.behaviorStructure);
	createUpdateThread(univ.geometryStructure);
	createUpdateThread(univ.soundStructure);
	createUpdateThread(univ.renderingEnvironmentStructure);
	createUpdateThread(univ.transformStructure);

	// create Behavior scheduler
	J3dThreadData threadData = null;

	if (univ.behaviorScheduler == null) {
	    java.security.AccessController.doPrivileged(
		new java.security.PrivilegedAction() {
                       @Override
                       public Object run() {
			   synchronized (rootThreadGroup) {
			       univ.behaviorScheduler = new BehaviorScheduler(
						      rootThreadGroup, univ);
			       univ.behaviorScheduler.setPriority(threadPriority);
			   }
			   return null;
		       }
	    });
	    univ.behaviorScheduler.initialize();
	    univ.behaviorScheduler.userStop = v.stopBehavior;
	    threadData = univ.behaviorScheduler.getThreadData(null, null);
	    threadData.thread = univ.behaviorScheduler;
	    threadData.threadType = J3dThread.BEHAVIOR_SCHEDULER;
	    threadData.lastUpdateTime = Math.max(currentTime,
						 threadData.lastUpdateTime);
	}

	createUpdateThread(v.renderBin);
	createUpdateThread(v.soundScheduler);

	if (v.physicalEnvironment != null) {
	    v.physicalEnvironment.addUser(v);
	}
	// create InputDeviceScheduler
	evaluatePhysicalEnv(v);

	regUniverseList.addUnique(univ);
	views.addUnique(v);
    }



    /**
     * This unregister a View with MasterControl.
     * The View no longer has any Canvas3Ds in a container.
     */
    private void unregisterView(View v) {

	if (!views.remove(v)) {
	    v.active = false;
	    v.doneUnregister = true;
	    return; // already unregister
	}

	if (v.active) {
	    viewDeactivate(v);
	}

	if(J3dDebug.devPhase) {
	    J3dDebug.doDebug(J3dDebug.masterControl, J3dDebug.LEVEL_1,
			     "MC: Destroy Sound Scheduler and RenderBin Update thread");
	}

	v.soundScheduler.updateThread.finish();
	v.renderBin.updateThread.finish();
	v.soundScheduler.updateThread = null;
	v.renderBin.updateThread = null;

	// remove VirtualUniverse related threads if Universe
	// is empty
        VirtualUniverse univ = v.universe;

	synchronized (timeLock) {
	    // The reason we need to sync. with timeLock is because we
	    // don't want user thread running sendMessage() to
	    // dispatch it in different structure queue when
	    // part of the structure list is empty at the same time.
	    // This will cause inconsistence in the message reference
	    // count.
	    emptyMessageList(v.soundScheduler, v);
	    emptyMessageList(v.renderBin, v);

	    if (univ.isEmpty()) {
		destroyUniverseThreads(univ);
	    }  else {
		emptyMessageList(univ.behaviorStructure, v);
		emptyMessageList(univ.geometryStructure, v);
		emptyMessageList(univ.soundStructure, v);
		emptyMessageList(univ.renderingEnvironmentStructure, v);
		emptyMessageList(univ.transformStructure, v);
	    }
	}

	if (v.physicalEnvironment != null) {
	    v.physicalEnvironment.removeUser(v);
	}

	// remove all InputDeviceScheduler if this is the last View
	ArrayList list = new ArrayList();
	for (Enumeration e = PhysicalEnvironment.physicalEnvMap.keys(); e.hasMoreElements();) {
		PhysicalEnvironment phyEnv = e.nextElement();
		InputDeviceScheduler sched = PhysicalEnvironment.physicalEnvMap.get(phyEnv);
		boolean phyEnvHasUser = false;
		for (int i = 0; i < phyEnv.users.size(); i++) {
			if (views.contains(phyEnv.users.get(i))) {
				// at least one registered view refer to it.
				phyEnvHasUser = true;
				break;
			}
		}

		if (!phyEnvHasUser) {
			if (J3dDebug.devPhase) {
				J3dDebug.doDebug(J3dDebug.masterControl, J3dDebug.LEVEL_1,
						"MC: Destroy InputDeviceScheduler thread "
								+ sched);
			}
			sched.finish();
			phyEnv.inputsched = null;
			list.add(phyEnv);
		}
	}
	for (int i = 0; i < list.size(); i++) {
		PhysicalEnvironment.physicalEnvMap.remove(list.get(i));
	}

	freeContext(v);

	if (views.isEmpty()) {
	    if(J3dDebug.devPhase) {
		J3dDebug.doDebug(J3dDebug.masterControl, J3dDebug.LEVEL_1,
				 "MC: Destroy all Renderers");
	    }
	    // remove all Renderers if this is the last View
	    for (Enumeration e = Screen3D.deviceRendererMap.elements();
		 e.hasMoreElements(); ) {
		Renderer rdr = e.nextElement();
		Screen3D scr;

		rendererCleanupArgs[2] = REMOVEALLCTXS_CLEANUP;
		runMonitor(RUN_RENDERER_CLEANUP, null, null, null, rdr);
		scr = rdr.onScreen;
		if (scr != null) {
		    if (scr.renderer != null) {
			rendererCleanupArgs[2] = REMOVEALLCTXS_CLEANUP;
			runMonitor(RUN_RENDERER_CLEANUP, null, null,
				   null, scr.renderer);
			scr.renderer = null;
		    }

		}
		scr = rdr.offScreen;
		if (scr != null) {
		    if (scr.renderer != null) {
			rendererCleanupArgs[2] = REMOVEALLCTXS_CLEANUP;
			runMonitor(RUN_RENDERER_CLEANUP, null, null,
				   null, scr.renderer);
			scr.renderer = null;
		    }
		}
		rdr.onScreen = null;
		rdr.offScreen = null;
	    }

	    // cleanup ThreadData corresponds to the view in renderer
	    for (Enumeration e = Screen3D.deviceRendererMap.elements();
		 e.hasMoreElements(); ) {
	    	e.nextElement().cleanup();
	    }
	    // We have to reuse renderer even though MC exit
	    // see bug 4363279
	    //  Screen3D.deviceRendererMap.clear();

	} else {
	    // cleanup ThreadData corresponds to the view in renderer
	    for (Enumeration e = Screen3D.deviceRendererMap.elements();
		 e.hasMoreElements(); ) {
		e.nextElement().cleanupView();
	    }
	}


	freeMessageList.add(univ);
	freeMessageList.add(v);

	evaluateAllCanvases();
	stateWorkThreads.clear();
	renderWorkThreads.clear();
	requestRenderWorkThreads.clear();
	threadListsChanged = true;

	// This notify VirtualUniverse waitForMC() thread to continue
	v.doneUnregister = true;
    }


    /**
     * This procedure create MC thread that start together with MC.
     */
    void createMCThreads() {

	// There is only one renderingAttributesUpdate Thread globally
	createUpdateThread(renderingAttributesStructure);

	// Create timer thread
	java.security.AccessController.doPrivileged(
			    new java.security.PrivilegedAction() {
              @Override
              public Object run() {
		  synchronized (rootThreadGroup) {
		      timerThread = new TimerThread(rootThreadGroup);
		      timerThread.setPriority(threadPriority);
		  }
		  return null;
	      }
	});
	timerThread.start();

        // Create notification thread
	java.security.AccessController.doPrivileged(
			    new java.security.PrivilegedAction() {
              @Override
              public Object run() {
		  synchronized (rootThreadGroup) {
		      notificationThread = new NotificationThread(rootThreadGroup);
		      notificationThread.setPriority(threadPriority);
		  }
		  return null;
	      }
	});
	notificationThread.start();
    }

    /**
     * Destroy all VirtualUniverse related threads.
     * This procedure may call two times when Locale detach in a
     * live viewPlatform.
     */
    private void destroyUniverseThreads(VirtualUniverse univ) {

	if (regUniverseList.contains(univ)) {
	    if (J3dDebug.devPhase) {
		J3dDebug.doDebug(J3dDebug.masterControl, J3dDebug.LEVEL_1,
				 "MC: Destroy universe threads " + univ);
	    }
	    destroyUpdateThread(univ.behaviorStructure);
	    destroyUpdateThread(univ.geometryStructure);
	    destroyUpdateThread(univ.soundStructure);
	    destroyUpdateThread(univ.renderingEnvironmentStructure);
	    destroyUpdateThread(univ.transformStructure);
	    univ.behaviorScheduler.finish();
	    univ.behaviorScheduler.free();
	    univ.behaviorScheduler = null;
	    univ.initMCStructure();
	    activeUniverseList.remove(univ);
	    regUniverseList.remove(univ);
	} else {
	    emptyMessageList(univ.behaviorStructure, null);
	    emptyMessageList(univ.geometryStructure, null);
	    emptyMessageList(univ.soundStructure, null);
	    emptyMessageList(univ.renderingEnvironmentStructure, null);
	    emptyMessageList(univ.transformStructure, null);
	}

	if (regUniverseList.isEmpty() && views.isEmpty()) {
	    if(J3dDebug.devPhase) {
		J3dDebug.doDebug(J3dDebug.masterControl, J3dDebug.LEVEL_1,
				 "MC: Destroy RenderingAttributes Update and Timer threads");
	    }
	    if (renderingAttributesStructure.updateThread != null) {
		renderingAttributesStructure.updateThread.finish();
		renderingAttributesStructure.updateThread = null;
	    }
	    renderingAttributesStructure.messageList.clear();
	    renderingAttributesStructure.objList = new ArrayList();
	    renderingAttributesStructure = new RenderingAttributesStructure();
	    if (timerThread != null) {
		timerThread.finish();
		timerThread = null;
	    }
            if (notificationThread != null) {
                notificationThread.finish();
                notificationThread = null;
            }

	    // shouldn't all of these be synchronized ???
	    synchronized (VirtualUniverse.mc.deviceScreenMap) {
		deviceScreenMap.clear();
	    }

	    mirrorObjects.clear();
	    // Note: We should not clear the DISPLAYLIST/TEXTURE
	    // list here because other structure may release them
	    // later

	    for(int i=0; i=0; i--) {
		viewArr[i].getCanvasList(true); // force canvas cache update
		Screen3D screens[] = viewArr[i].getScreens();
		for (int j=screens.length-1; j>=0; j--) {
		    screens[j].canvasCount = 0;
		}
	    }


	    // Third create render thread and message thread
	    for (int i=views.size()-1; i>=0; i--) {
		View v = viewArr[i];
		Canvas3D canvasList[][] = v.getCanvasList(false);
		if (!v.active) {
		    continue;
		}

		for (int j=canvasList.length-1; j>=0; j--) {
		    boolean added = false;

		    for (int k=canvasList[j].length-1; k>=0; k--) {
			Canvas3D cv = canvasList[j][k];

			final Screen3D screen  = cv.screen;

			if (cv.active) {
			    if (screen.canvasCount++ == 0) {
				// Create Renderer, one per screen
				if (screen.renderer == null) {
                    	        // get the renderer created for the graphics
			        // device of the screen of the canvas
				// No need to synchronized since only
				// MC use it.
				    Renderer rdr = Screen3D.deviceRendererMap.get(cv.screen.graphicsDevice);
				    if (rdr == null) {
					java.security.AccessController.doPrivileged(
					    new java.security.PrivilegedAction() {
                                              @Override
                                              public Object run() {

						  synchronized (rootThreadGroup) {
						      screen.renderer
							  = new Renderer(
 							      rootThreadGroup);
						      screen.renderer.setPriority(threadPriority);
						  }
                                                 return null;
                                          }
					});
					screen.renderer.initialize();
					Screen3D.deviceRendererMap.put(screen.graphicsDevice, screen.renderer);
				    } else {
					screen.renderer = rdr;
				    }
				}
			    }
		    	    // offScreen canvases will be handled by the
			    // request renderer, so don't add offScreen canvas
			    // the render list
                            //
                            // Issue 131: Automatic offscreen canvases need to
                            // be added to onscreen list. Special case.
                            //
                            // TODO KCR Issue 131: this should probably be
                            // changed to a list of screens since multiple
                            // off-screen canvases (either auto or manual) can
                            // be used by the same renderer
		            if (!cv.manualRendering) {
				screen.renderer.onScreen = screen;
			    } else {
				screen.renderer.offScreen = screen;
				continue;
			    }

			    if (!added) {
				// Swap message data thread, one per
				// screen only. Note that we don't set
				// lastUpdateTime for this thread so
				// that it won't run in the first round
			        J3dThreadData renderData =
			      	    screen.renderer.getThreadData(v, null);
				renderThreadData.add(renderData);

				// only if renderBin is ready then we
				// update the lastUpdateTime to make it run
				if (v.renderBinReady) {
	    		    	    renderData.lastUpdateTime =
					Math.max(currentTime,
						   renderData.lastUpdateTime);
				}
				added = true;
			    }
			    // Renderer message data thread
			    J3dThreadData renderData =
			      	    screen.renderer.getThreadData(v, cv);
			    renderThreadData.add(renderData);
			    if (v.renderBinReady) {
	    		    	renderData.lastUpdateTime =
					Math.max(currentTime,
						   renderData.lastUpdateTime);
			    }
			}
		    }
		}

	    }
	}

	threadListsChanged = true;
    }

    private void evaluatePhysicalEnv(View v) {
	final PhysicalEnvironment env = v.physicalEnvironment;

	if (env.inputsched == null) {
	    java.security.AccessController.doPrivileged(
		 new java.security.PrivilegedAction() {
                      @Override
                      public Object run() {
			  synchronized (rootThreadGroup) {
			      env.inputsched = new InputDeviceScheduler(
							rootThreadGroup,
							env);
			      env.inputsched.setPriority(threadPriority);
			  }
			  return null;
		      }
	    });
	    env.inputsched.start();
	    PhysicalEnvironment.physicalEnvMap.put(env, env.inputsched);
	}
	threadListsChanged = true;
    }

    final private void addToStateThreads(J3dThreadData threadData) {
	if (threadData.thread.active) {
	    stateWorkThreads.add(threadData);
	}
    }


    private void assignNewPrimaryView(VirtualUniverse univ) {

	View currentPrimary = univ.getCurrentView();

	if (currentPrimary != null) {
	    currentPrimary.primaryView = false;
	}

	View v[] = (View []) views.toArray(false);
	int nviews = views.size();
	for (int i=0; i=0; j--) {
	    for (int k=canvasList[j].length-1; k>=0; k--) {
		Canvas3D cv = canvasList[j][k];
		if (!cv.validCanvas) {
		    if ((cv.screen != null) &&
			(cv.screen.renderer != null)) {
			rendererCleanupArgs[1] = cv;
			rendererCleanupArgs[2] = FREECONTEXT_CLEANUP;
			runMonitor(RUN_RENDERER_CLEANUP, null, null, null,
				   cv.screen.renderer);
			rendererCleanupArgs[1] = null;
		    }
		}
	    }
	}
    }

    /**
     * This notifies MasterControl that the given view has been deactivated
     */
    private void viewDeactivate(View v) {

	if (!views.contains(v) || !v.active) {
	    v.active = false;
	    evaluateAllCanvases();
	    return;
	}

	VirtualUniverse univ = v.universe;

	if (v.isRunning) {
	    // if stopView() invoke before, no need to decrement count
	    --numActiveViews;
	    --univ.activeViewCount;
	}

	if (numActiveViews == 0) {
	    renderingAttributesStructure.updateThread.active = false;
	}

	if (univ.activeViewCount == 0) {
	    // check if destroyUniverseThread invoked before
	    if (univ.behaviorScheduler != null) {
		univ.behaviorScheduler.deactivate();
		univ.transformStructure.updateThread.active = false;
		univ.geometryStructure.updateThread.active = false;
		univ.behaviorStructure.updateThread.active = false;
		univ.soundStructure.updateThread.active = false;
		univ.renderingEnvironmentStructure.updateThread.active
		    = false;
		activeUniverseList.remove(univ);
	    }
	}

	v.soundScheduler.deactivate();
	v.renderBin.updateThread.active = false;
	v.active = false;
	if (--v.physicalEnvironment.activeViewRef == 0) {
	    v.physicalEnvironment.inputsched.deactivate();
	}
	assignNewPrimaryView(univ);


	evaluateAllCanvases();

	freeContext(v);

	v.inRenderThreadData = false;
	threadListsChanged = true;
    }


   /**
     * This notifies MasterControl to start given view
     */
    private void startView(View v) {

	if (!views.contains(v) || v.isRunning || !v.active) {
	    v.isRunning = true;
	    return;
	}

	numActiveViews++;
	renderingAttributesStructure.updateThread.active = true;

	VirtualUniverse univ = v.universe;

	univ.activeViewCount++;
	univ.transformStructure.updateThread.active = true;
	univ.geometryStructure.updateThread.active = true;
	univ.soundStructure.updateThread.active = true;
	univ.renderingEnvironmentStructure.updateThread.active = true;
	v.renderBin.updateThread.active = true;
	v.soundScheduler.activate();
	v.isRunning = true;
	if (univ.getCurrentView() == null) {
	    assignNewPrimaryView(univ);
	}
	threadListsChanged = true;
    }


   /**
     * This notifies MasterControl to stop given view
     */
    private void stopView(View v) {
	if (!views.contains(v) || !v.isRunning || !v.active) {
	    v.isRunning = false;
	    return;
	}

	if (--numActiveViews == 0) {
	    renderingAttributesStructure.updateThread.active = false;
	}
	VirtualUniverse univ = v.universe;

	if (--univ.activeViewCount == 0) {
		univ.transformStructure.updateThread.active = false;
		univ.geometryStructure.updateThread.active = false;
		univ.renderingEnvironmentStructure.updateThread.active = false;
		univ.soundStructure.updateThread.active = false;
	}

	v.renderBin.updateThread.active = false;
	v.soundScheduler.deactivate();
	v.isRunning = false;
	assignNewPrimaryView(univ);
	threadListsChanged = true;
    }

    // Call from user thread
    void addInputDeviceScheduler(InputDeviceScheduler ds) {
	synchronized (inputDeviceThreads) {
	    inputDeviceThreads.add(ds);
	    if (inputDeviceThreads.size() == 1) {
		timerThread.addInputDeviceSchedCond();
	    }
	}
	postRequest(INPUTDEVICE_CHANGE, null);
    }

    // Call from user thread
    void removeInputDeviceScheduler(InputDeviceScheduler ds) {
	inputDeviceThreads.remove(ds);
	postRequest(INPUTDEVICE_CHANGE, null);
    }

    /**
     * Add an object to the mirror object list
     */
    void addMirrorObject(ObjectUpdate o) {
	mirrorObjects.add(o);
    }

    /**
     * This updates any mirror objects.  It is called when threads
     * are done.
     */
    void updateMirrorObjects() {
	ObjectUpdate objs[] = (ObjectUpdate []) mirrorObjects.toArray(false);
	int sz = mirrorObjects.arraySize();

	for (int i = 0; i< sz; i++) {
	    objs[i].updateObject();
	}
	mirrorObjects.clear();
    }


    /**
     * This fun little method does all the hard work of setting up the
     * work thread list.
     */
    private void updateWorkThreads() {

	stateWorkThreads.clear();
	renderWorkThreads.clear();
	requestRenderWorkThreads.clear();

	// First the global rendering attributes structure update
	if (numActiveViews > 0) {
	    addToStateThreads(renderingAttributesStructure.getUpdateThreadData());
	}

	// Next, each of the transform structure updates
	VirtualUniverse universes[] = (VirtualUniverse [])
	                         activeUniverseList.toArray(false);
	VirtualUniverse univ;
	int i;
	int size = activeUniverseList.arraySize();

	for (i=size-1; i>=0; i--) {
	    addToStateThreads(universes[i].transformStructure.getUpdateThreadData());
	}
	lastTransformStructureThread = stateWorkThreads.size();

	// Next, the GeometryStructure, BehaviorStructure,
	//       RenderingEnvironmentStructure, and SoundStructure
	for (i=size-1; i>=0; i--) {
	    univ = universes[i];
	    addToStateThreads(univ.geometryStructure.getUpdateThreadData());
	    addToStateThreads(univ.behaviorStructure.getUpdateThreadData());
	    addToStateThreads(univ.renderingEnvironmentStructure.getUpdateThreadData());
	    addToStateThreads(univ.soundStructure.getUpdateThreadData());
	}

	lastStructureUpdateThread = stateWorkThreads.size();

	// Next, the BehaviorSchedulers
	for (i=size-1; i>=0; i--) {
	    addToStateThreads(universes[i].behaviorScheduler.
			      getThreadData(null, null));
	}


	// Now InputDeviceScheduler

	InputDeviceScheduler ds[] = (InputDeviceScheduler [])
	                              inputDeviceThreads.toArray(true);
	for (i=inputDeviceThreads.size()-1; i >=0; i--) {
	    J3dThreadData threadData = ds[i].getThreadData();
	    threadData.thread.active = true;
	    addToStateThreads(threadData);
	}

	// Now the RenderBins and SoundSchedulers
	View viewArr[] = (View []) views.toArray(false);
	J3dThreadData thread;

	for (i=views.size()-1; i>=0; i--) {
	    View v = viewArr[i];
	    if (v.active && v.isRunning) {
		addToStateThreads(v.renderBin.getUpdateThreadData());
		addToStateThreads(v.soundScheduler.getUpdateThreadData());
	        Canvas3D canvasList[][] = v.getCanvasList(false);
	        int longestScreenList = v.getLongestScreenList();
		Object args[] = null;
		// renderer render
	        for (int j=0; j e = Screen3D.deviceRendererMap.elements();
	     e.hasMoreElements(); ) {
	    Renderer rdr = e.nextElement();
	    thread = rdr.getThreadData(null, null);
	    requestRenderWorkThreads.add(thread);
	    thread.threadOpts = J3dThreadData.CONT_THREAD;
	    ((Object[]) thread.threadArgs)[0] = REQUESTRENDER;
	}

	if (thread != null) {
	    thread.threadOpts |= J3dThreadData.WAIT_ALL_THREADS;
	}

	threadListsChanged = false;

	//	 dumpWorkThreads();
    }


    void dumpWorkThreads() {
	System.err.println("-----------------------------");
	System.err.println("MasterControl/dumpWorkThreads");

	J3dThreadData threads[];
	int size = 0;

	for (int k=0; k<3; k++) {
	    switch (k) {
	    case 0:
		threads = (J3dThreadData []) stateWorkThreads.toArray(false);
		size = stateWorkThreads.arraySize();
		break;
	    case 1:
		threads = (J3dThreadData []) renderWorkThreads.toArray(false);
		size = renderWorkThreads.arraySize();
		break;
	    default:
		threads = (J3dThreadData []) requestRenderWorkThreads.toArray(false);
		size = requestRenderWorkThreads.arraySize();
		break;
	    }

	    for (int i=0; i=0; i--) {
	    if (v[i].active) {
		v[i].updateViewCache();
		// update OrientedShape3D
		if ((v[i].viewCache.vcDirtyMask != 0 &&
		     !v[i].renderBin.orientedRAs.isEmpty()) ||
		    (v[i].renderBin.cachedDirtyOrientedRAs != null &&
		     !v[i].renderBin.cachedDirtyOrientedRAs.isEmpty())) {
		    v[i].renderBin.updateOrientedRAs();
		}
	    }
	}

	runMonitor(RUN_THREADS, stateWorkThreads, renderWorkThreads,
		   requestRenderWorkThreads, null);

	if (renderOnceList.size() > 0) {
	    clearRenderOnceList();
	}

	manageMemory();

    }

    private void handlePendingRequest() {

	Object objs[];
	Integer types[];
	int size;
	boolean rendererRun = false;

	objs = requestObjList.toArray(false);
	types = (Integer []) requestTypeList.toArray(false);
	size = requestObjList.size();

	for (int i=0; i < size; i++) {
	    // need to process request in order
	    Integer type = types[i];
	    Object o = objs[i];
           if (type == RESET_CANVAS) {
               Canvas3D cv = (Canvas3D) o;
		if ((cv.screen != null) &&
		    (cv.screen.renderer != null)) {
		    rendererCleanupArgs[1] = o;
		    rendererCleanupArgs[2] = RESETCANVAS_CLEANUP;
		    runMonitor(RUN_RENDERER_CLEANUP, null, null, null,
			       cv.screen.renderer);
		    rendererCleanupArgs[1] = null;
		}
               cv.reset();
	       cv.view = null;
	       cv.computeViewCache();
           }
	   else if (type == ACTIVATE_VIEW) {
		viewActivate((View) o);
	   }
	   else if (type == DEACTIVATE_VIEW) {
		viewDeactivate((View) o);
	    } else if (type == REEVALUATE_CANVAS) {
		evaluateAllCanvases();
	    } else if (type == INPUTDEVICE_CHANGE) {
		inputDeviceThreads.clearMirror();
		threadListsChanged = true;
	    } else if (type == START_VIEW) {
		startView((View) o);
	    } else if (type == STOP_VIEW) {
		View v = (View) o;
		// Collision takes 3 rounds to finish its request
		if (++v.stopViewCount > 4) {
		    v.stopViewCount = -1; // reset counter
		    stopView(v);
		} else {
		    tempViewList.add(v);
		}
	    } else if (type == UNREGISTER_VIEW) {
		unregisterView((View) o);
	    } else if (type == PHYSICAL_ENV_CHANGE) {
		evaluatePhysicalEnv((View) o);
	    } else if (type == EMPTY_UNIVERSE) {
		// Issue 81: We need to process this message as long
		// as there are no views associated with this
		// universe. Previously, this message was ignored if
		// there were views associated with *any* universe,
		// which led to a memory / thread leak.
	        boolean foundView = false;
		VirtualUniverse univ = (VirtualUniverse) o;
		View v[] = (View []) views.toArray(false);
		for (int j = views.size() - 1; j >= 0; j--) {
		    if (v[j].universe == univ) {
			foundView = true;
			break;
		    }
		}
		if (!foundView) {
		    destroyUniverseThreads(univ);
		    threadListsChanged = true;
		}
	    } else if (type == START_RENDERER) {
                if (o instanceof Canvas3D) {
                    Canvas3D c3d = (Canvas3D) o;
                    if (!c3d.isFatalError()) {
                        c3d.isRunningStatus = true;
                    }
                } else {
                    ((Renderer) o).userStop = false;
                }
		threadListsChanged = true;
	    } else if (type == STOP_RENDERER) {
		if (o instanceof Canvas3D) {
		    ((Canvas3D) o).isRunningStatus = false;
		} else {
		    ((Renderer) o).userStop = true;
		}
		threadListsChanged = true;
	    } else if (type == RENDER_ONCE) {
		View v = (View) o;
		// temporary start View for renderonce
		// it will stop afterwards
		startView(v);
		renderOnceList.add(v);
		sendRunMessage(v, J3dThread.UPDATE_RENDER);
		threadListsChanged = true;
		rendererRun = true;
	    } else if (type == FREE_CONTEXT) {
		Canvas3D cv = (Canvas3D ) ((Object []) o)[0];
		if ((cv.screen != null) &&
		    (cv.screen.renderer != null)) {
		    rendererCleanupArgs[1] = o;
		    rendererCleanupArgs[2] = REMOVECTX_CLEANUP;
		    runMonitor(RUN_RENDERER_CLEANUP, null, null, null,
			       cv.screen.renderer);
		    rendererCleanupArgs[1] = null;
		}
		rendererRun = true;
            } else if (type == FREE_DRAWING_SURFACE) {
                Pipeline.getPipeline().freeDrawingSurfaceNative(o);
            } else if (type == GETBESTCONFIG) {
		GraphicsConfiguration gc = ((GraphicsConfiguration [])
			  ((GraphicsConfigTemplate3D) o).testCfg)[0];
		sendRenderMessage(gc, o, type);
		rendererRun = true;
	    } else if (type == ISCONFIGSUPPORT) {
		GraphicsConfiguration  gc = (GraphicsConfiguration)
			  ((GraphicsConfigTemplate3D) o).testCfg;
		sendRenderMessage(gc, o, type);
		rendererRun = true;
	    } else if ((type == SET_GRAPHICSCONFIG_FEATURES) ||
		       (type == SET_QUERYPROPERTIES)) {
		GraphicsConfiguration gc = ((Canvas3D)o).graphicsConfiguration;
		sendRenderMessage(gc, o, type);
		rendererRun = true;
	    } else if (type == SET_VIEW) {
		Canvas3D cv = (Canvas3D) o;
		cv.view = cv.pendingView;
		cv.computeViewCache();
	    }
	}

	// Do it only after all universe/View is register
	for (int i=0; i < size; i++) {
	    Integer type = types[i];
	    if (type == FREE_MESSAGE) {
		if (objs[i] instanceof VirtualUniverse) {
		    VirtualUniverse u = (VirtualUniverse) objs[i];
		    if (!regUniverseList.contains(u)) {
			emptyMessageList(u.behaviorStructure, null);
			emptyMessageList(u.geometryStructure, null);
			emptyMessageList(u.soundStructure, null);
			emptyMessageList(u.renderingEnvironmentStructure, null);
		    }
		} else if (objs[i] instanceof View) {
		    View v = (View) objs[i];
		    if (!views.contains(v)) {
			emptyMessageList(v.soundScheduler, v);
			emptyMessageList(v.renderBin, v);
			if (v.resetUnivCount == v.universeCount) {
			    v.reset();
			    v.universe = null;
			    if (running == false) {
				// MC is about to terminate

				/*
				// Don't free list cause there may
				// have some other thread returning ID
				// after it.
				FreeListManager.clearList(FreeListManager.DISPLAYLIST);
				FreeListManager.clearList(FreeListManager.TEXTURE2D);
				FreeListManager.clearList(FreeListManager.TEXTURE3D);

				synchronized (textureIdLock) {
				    textureIdCount = 0;
				}
				*/
			    }
			}
		    }
		}

	    }

	}
	requestObjList.clear();
	requestTypeList.clear();

	size = tempViewList.size();
	if (size > 0) {
	    if (running) {
		for (int i=0; i < size; i++) {
		    requestTypeList.add(STOP_VIEW);
		    requestObjList.add(tempViewList.get(i));
		}
		setWork();
	    } else { // MC will shutdown
		for (int i=0; i < size; i++) {
		    View v = (View) tempViewList.get(i);
		    v.stopViewCount = -1;
		    v.isRunning = false;
		}
	    }
	    tempViewList.clear();
	    pendingRequest = true;
	} else {
	    pendingRequest = rendererRun || (requestObjList.size() > 0);

	}

	size = freeMessageList.size();
	if (size > 0) {
	    for (int i=0; i < size; i++) {
		requestTypeList.add(FREE_MESSAGE);
		requestObjList.add(freeMessageList.get(i));
	    }
	    pendingRequest = true;
	    freeMessageList.clear();
	}
	if (!running && (renderOnceList.size() > 0)) {
	    clearRenderOnceList();
	}

	if (pendingRequest) {
	    setWork();
	}

	if (rendererRun || requestRenderWorkToDo) {
	    running = true;
	}

    }

    private void clearRenderOnceList() {
	for (int i=renderOnceList.size()-1; i>=0; i--) {
	    View v = (View) renderOnceList.get(i);
	    v.renderOnceFinish = true;
	    // stop after render once
	    stopView(v);
	}
	renderOnceList.clear();
	threadListsChanged = true;

    }

    synchronized void runMonitor(int action,
				 UnorderList stateThreadList,
				 UnorderList renderThreadList,
				 UnorderList requestRenderThreadList,
				 J3dThread nthread) {

        switch (action) {
	case RUN_THREADS:
	    int currentStateThread = 0;
	    int currentRenderThread = 0;
	    int currentRequestRenderThread = 0;
	    View view;
	    boolean done;
	    J3dThreadData thread;
	    J3dThreadData renderThreads[] = (J3dThreadData [])
		                             renderThreadList.toArray(false);
	    J3dThreadData stateThreads[] = (J3dThreadData [])
		                            stateThreadList.toArray(false);
	    J3dThreadData requestRenderThreads[] = (J3dThreadData [])
		                       requestRenderThreadList.toArray(false);
	    int renderThreadSize = renderThreadList.arraySize();
	    int stateThreadSize = stateThreadList.arraySize();
	    int requestRenderThreadSize = requestRenderThreadList.arraySize();

	    done = false;

	    //lock all the needed geometry and image component
	    View[] allView = (View []) views.toArray(false);
	    View currentV;
	    int i;

	    if (lockGeometry)
	    {
		for( i = views.arraySize()-1; i >= 0; i--) {
		    currentV = allView[i];
		    currentV.renderBin.lockGeometry();
		}
	    }

	    while (!done) {
		// First try a RenderThread
		while (!renderWaiting &&
		       currentRenderThread != renderThreadSize) {
		    thread = renderThreads[currentRenderThread++];
		    if (!thread.needsRun) {
			continue;
		    }
		    if ((thread.threadOpts & J3dThreadData.START_TIMER) != 0) {
		        view = (View)((Object[])thread.threadArgs)[2];
		        view.frameNumber++;
		        view.startTime = J3dClock.currentTimeMillis();
		    }


		    renderPending++;

		    if (cpuLimit == 1) {
			thread.thread.args = (Object[])thread.threadArgs;
			thread.thread.doWork(currentTime);
		    } else {
			threadPending++;
			thread.thread.runMonitor(J3dThread.RUN,
						 currentTime,
						 (Object[])thread.threadArgs);
		    }

		    if ((thread.threadOpts & J3dThreadData.STOP_TIMER) != 0) {
			view = (View)((Object[])thread.threadArgs)[3];
			timestampUpdateList.add(view);
		    }

		    if ((thread.threadOpts & J3dThreadData.LAST_STOP_TIMER) != 0) {
			// release lock on locked geometry and image component
			for( i = 0; i < views.arraySize(); i++) {
			    currentV = allView[i];
			    currentV.renderBin.releaseGeometry();
			}
		    }

		    if ((cpuLimit != 1) &&
			(thread.threadOpts &
			 J3dThreadData.WAIT_ALL_THREADS) != 0) {

			renderWaiting = true;
		    }


		    if ((cpuLimit != 1) && (cpuLimit <= threadPending)) {
			state = WAITING_FOR_CPU;
			try {
			    wait();
			} catch (InterruptedException e) {
			    System.err.println(e);
			}
			state = RUNNING;
		    }

		}
		// Now try state threads
		while (!stateWaiting &&
		       currentStateThread != stateThreadSize) {
		    thread = stateThreads[currentStateThread++];

		    if (!thread.needsRun) {
			continue;
		    }

		    statePending++;

		    if (cpuLimit == 1) {
			thread.thread.args = (Object[])thread.threadArgs;
			thread.thread.doWork(currentTime);
		    } else {
			threadPending++;
			thread.thread.runMonitor(J3dThread.RUN,
						 currentTime,
						 (Object[])thread.threadArgs);
		    }
		    if (cpuLimit != 1 && (thread.threadOpts &
			 J3dThreadData.WAIT_ALL_THREADS) != 0) {
			stateWaiting = true;
		    }

		    if ((cpuLimit != 1) && (cpuLimit <= threadPending)) {
			// Fix bug 4686766 - always allow
			// renderer thread to continue if not finish
			// geomLock can release for Behavior thread to
			// continue.
			if (currentRenderThread == renderThreadSize) {
			    state = WAITING_FOR_CPU;
			    try {
				wait();
			    } catch (InterruptedException e) {
				System.err.println(e);
			    }
			    state = RUNNING;
			} else {
			    // Run renderer thread next time
			    break;
			}

		    }
		}

		// Now try requestRender threads
                if (!renderWaiting &&
                     (currentRenderThread == renderThreadSize)) {
                    currentRequestRenderThread = 0;
                    while (!renderWaiting &&
                           (currentRequestRenderThread !=
                                requestRenderThreadSize)) {

                        thread =
			   requestRenderThreads[currentRequestRenderThread++];

                        renderPending++;

                        if (cpuLimit == 1) {
                            thread.thread.args = (Object[])thread.threadArgs;
                            thread.thread.doWork(currentTime);
                        } else {
                            threadPending++;
                            thread.thread.runMonitor(J3dThread.RUN,
                                                currentTime,
                                                (Object[])thread.threadArgs);
                        }
                        if (cpuLimit != 1 && (thread.threadOpts &
                                J3dThreadData.WAIT_ALL_THREADS) != 0) {
                            renderWaiting = true;
                        }
                        if (cpuLimit != 1 && cpuLimit <= threadPending) {
                            state = WAITING_FOR_CPU;
                            try {
                                wait();
                            } catch (InterruptedException e) {
                                System.err.println(e);
                            }
			    state = RUNNING;
                        }
                    }
                }

		if (cpuLimit != 1) {
		    if ((renderWaiting &&
			 (currentStateThread == stateThreadSize)) ||
		          (stateWaiting &&
		            currentRenderThread == renderThreadSize) ||
		    	    (renderWaiting && stateWaiting)) {
			if (!requestRenderWorkToDo) {
		            state = WAITING_FOR_THREADS;
		            try {
			        wait();
		            } catch (InterruptedException e) {
			        System.err.println(e);
		            }
			    state = RUNNING;
			}
			requestRenderWorkToDo = false;
		    }
		}

                if ((currentStateThread == stateThreadSize) &&
                    (currentRenderThread == renderThreadSize) &&
                    (currentRequestRenderThread == requestRenderThreadSize) &&
		    (threadPending == 0)) {
			for (int k = timestampUpdateList.size() - 1; k >= 0; k--) {
				View v = timestampUpdateList.get(k);
				v.setFrameTimingValues();
				v.universe.behaviorStructure.incElapsedFrames();
			}
		    timestampUpdateList.clear();
		    updateMirrorObjects();
		    done = true;

                    if (isStatsLoggable(Level.INFO)) {
                        // Instrumentation of Java 3D renderer
                        logTimes();
                    }
		}
	    }
	    break;

	case THREAD_DONE:
	    if (state != WAITING_FOR_RENDERER_CLEANUP) {

		threadPending--;
                assert threadPending >= 0 : ("threadPending = " + threadPending);
                if (nthread.type == J3dThread.RENDER_THREAD) {
		    View v = (View) nthread.args[3];
		    if (v != null) { // STOP_TIMER
			v.stopTime = J3dClock.currentTimeMillis();
		    }

		    if (--renderPending == 0) {
			renderWaiting = false;
		    }
                    assert renderPending >= 0 : ("renderPending = " + renderPending);
		} else {
		    if (--statePending == 0) {
			stateWaiting = false;
		    }
                    assert statePending >= 0 : ("statePending = " + statePending);
		}
		if (state == WAITING_FOR_CPU || state == WAITING_FOR_THREADS) {
		    notify();
		}
	    } else {
		notify();
		state = RUNNING;
	    }
	    break;

	case CHECK_FOR_WORK:
	    if (!workToDo) {
		state = SLEEPING;
                // NOTE: this could wakeup spuriously (see issue 279), but it
                // will not cause any problems.
		try {
		    wait();
		} catch (InterruptedException e) {
		    System.err.println(e);
		}
	        state = RUNNING;
	    }
	    workToDo = false;
	    break;

	case SET_WORK:
	    workToDo = true;
	    if (state == SLEEPING) {
		notify();
	    }
	    break;

        case SET_WORK_FOR_REQUEST_RENDERER:
	    requestRenderWorkToDo = true;
	    workToDo = true;
	    if (state == WAITING_FOR_CPU || state == WAITING_FOR_THREADS ||
			state == SLEEPING) {
		notify();
	    }
	    break;

	case RUN_RENDERER_CLEANUP:
	    nthread.runMonitor(J3dThread.RUN, currentTime,
			       rendererCleanupArgs);
	    state = WAITING_FOR_RENDERER_CLEANUP;
            // Issue 279 - loop until state is set to running
            while (state != RUNNING) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    System.err.println(e);
                }
            }
	    break;

        default:
            // Should never get here
            assert false : "missing case in switch statement";
        }
    }

    // Static initializer
    static {
        // create ThreadGroup
        java.security.AccessController.doPrivileged(
            new java.security.PrivilegedAction() {
                @Override
                public Object run() {
                    ThreadGroup parent;
                    Thread thread = Thread.currentThread();
                    threadPriority = thread.getPriority();
                    rootThreadGroup = thread.getThreadGroup();
                    while ((parent = rootThreadGroup.getParent()) != null) {
                        rootThreadGroup = parent;
                    }
                    rootThreadGroup = new ThreadGroup(rootThreadGroup,
                            "Java3D");
                    // use the default maximum group priority
                    return null;
                }
        });

        // Initialize loggers
        try {
            initLoggers();
        } catch (RuntimeException ex) {
            System.err.println(ex);
        }
    }


    static String mtype[] = {
        "INSERT_NODES",
        "REMOVE_NODES",
        "RUN",
        "TRANSFORM_CHANGED",
        "UPDATE_VIEW",
        "STOP_THREAD",
        "COLORINGATTRIBUTES_CHANGED",
        "LINEATTRIBUTES_CHANGED",
        "POINTATTRIBUTES_CHANGED",
        "POLYGONATTRIBUTES_CHANGED",
        "RENDERINGATTRIBUTES_CHANGED",
        "TEXTUREATTRIBUTES_CHANGED",
        "TRANSPARENCYATTRIBUTES_CHANGED",
        "MATERIAL_CHANGED",
        "TEXCOORDGENERATION_CHANGED",
        "TEXTURE_CHANGED",
        "MORPH_CHANGED",
        "GEOMETRY_CHANGED",
        "APPEARANCE_CHANGED",
        "LIGHT_CHANGED",
        "BACKGROUND_CHANGED",
        "CLIP_CHANGED",
        "FOG_CHANGED",
        "BOUNDINGLEAF_CHANGED",
        "SHAPE3D_CHANGED",
        "TEXT3D_TRANSFORM_CHANGED",
        "TEXT3D_DATA_CHANGED",
        "SWITCH_CHANGED",
        "COND_MET",
        "BEHAVIOR_ENABLE",
        "BEHAVIOR_DISABLE",
        "INSERT_RENDERATOMS",
        "ORDERED_GROUP_INSERTED",
        "ORDERED_GROUP_REMOVED",
        "COLLISION_BOUND_CHANGED",
        "REGION_BOUND_CHANGED",
        "MODELCLIP_CHANGED",
        "BOUNDS_AUTO_COMPUTE_CHANGED",
        "SOUND_ATTRIB_CHANGED",
        "AURALATTRIBUTES_CHANGED",
        "SOUNDSCAPE_CHANGED",
        "ALTERNATEAPPEARANCE_CHANGED",
        "RENDER_OFFSCREEN",
        "RENDER_RETAINED",
        "RENDER_IMMEDIATE",
        "SOUND_STATE_CHANGED",
        "ORIENTEDSHAPE3D_CHANGED",
        "TEXTURE_UNIT_STATE_CHANGED",
        "UPDATE_VIEWPLATFORM",
        "BEHAVIOR_ACTIVATE",
        "GEOMETRYARRAY_CHANGED",
        "MEDIA_CONTAINER_CHANGED",
        "RESIZE_CANVAS",
        "TOGGLE_CANVAS",
        "IMAGE_COMPONENT_CHANGED",
        "SCHEDULING_INTERVAL_CHANGED",
        "VIEWSPECIFICGROUP_CHANGED",
        "VIEWSPECIFICGROUP_INIT",
        "VIEWSPECIFICGROUP_CLEAR",
        "ORDERED_GROUP_TABLE_CHANGED",
        "BEHAVIOR_REEVALUATE",
        "CREATE_OFFSCREENBUFFER",
        "DESTROY_CTX_AND_OFFSCREENBUFFER",
        "SHADER_ATTRIBUTE_CHANGED",
        "SHADER_ATTRIBUTE_SET_CHANGED",
        "SHADER_APPEARANCE_CHANGED",
        "ALLOCATE_CANVASID",
        "FREE_CANVASID",
    };

    private String dumpThreads(int threads) {
        StringBuffer strBuf = new StringBuffer();
        strBuf.append("threads:");
	//dump Threads type
        if ((threads & J3dThread.BEHAVIOR_SCHEDULER) != 0) {
            strBuf.append(" BEHAVIOR_SCHEDULER");
        }
        if ((threads & J3dThread.SOUND_SCHEDULER) != 0) {
            strBuf.append(" SOUND_SCHEDULER");
        }
        if ((threads & J3dThread.INPUT_DEVICE_SCHEDULER) != 0) {
            strBuf.append(" INPUT_DEVICE_SCHEDULER");
        }
        if ((threads & J3dThread.RENDER_THREAD) != 0) {
            strBuf.append(" RENDER_THREAD");
        }
        if ((threads & J3dThread.UPDATE_GEOMETRY) != 0) {
            strBuf.append(" UPDATE_GEOMETRY");
        }
        if ((threads & J3dThread.UPDATE_RENDER) != 0) {
            strBuf.append(" UPDATE_RENDER");
        }
        if ((threads & J3dThread.UPDATE_BEHAVIOR) != 0) {
            strBuf.append(" UPDATE_BEHAVIOR");
        }
        if ((threads & J3dThread.UPDATE_SOUND) != 0) {
            strBuf.append(" UPDATE_SOUND");
        }
        if ((threads & J3dThread.UPDATE_RENDERING_ATTRIBUTES) != 0) {
            strBuf.append(" UPDATE_RENDERING_ATTRIBUTES");
        }
        if ((threads & J3dThread.UPDATE_RENDERING_ENVIRONMENT) != 0) {
            strBuf.append(" UPDATE_RENDERING_ENVIRONMENT");
        }
        if ((threads & J3dThread.UPDATE_TRANSFORM) != 0) {
            strBuf.append(" UPDATE_TRANSFORM");
        }

        return strBuf.toString();
    }

    // Method to log the specified message. Note that the caller
    // should check for isCoreLoggable(FINEST) before calling
    private void dumpMessage(String str, J3dMessage m) {
        StringBuffer strBuf = new StringBuffer();
        strBuf.append(str).append(" ");
        if (m.type >= 0 && m.type < mtype.length) {
            strBuf.append(mtype[m.type]);
        } else {
            strBuf.append("");
        }
        strBuf.append("  ").append(dumpThreads(m.threads));
        getCoreLogger().finest(strBuf.toString());
    }


    int frameCount = 0;
    private int frameCountCutoff = 100;

    private void manageMemory() {
	if (++frameCount > frameCountCutoff) {
	    FreeListManager.manageLists();
	    frameCount = 0;
	}
    }

    /**
     * Yields the current thread, by sleeping for a small amount of
     * time.  Unlike Thread.yield(), this method
     * guarantees that the current thread will yield to another thread
     * waiting to run.  It also ensures that the other threads will
     * run for at least a small amount of time before the current
     * thread runs again.
     */
    static final void threadYield() {
	// Note that we can't just use Thread.yield(), since it
	// doesn't guarantee that it will actually yield the thread
	// (and, in fact, it appears to be a no-op under Windows). So
	// we will sleep for 1 msec instead. Since most threads use
	// wait/notify, and only use this when they are waiting for
	// another thread to finish something, this shouldn't be a
	// performance concern.

	//Thread.yield();
	try {
	    Thread.sleep(1);
	}
	catch (InterruptedException e) {
	    // Do nothing, since we really don't care how long (or
	    // even whether) we sleep
	}
    }

    // Return the number of available processors
    private int getNumberOfProcessors() {
        return Runtime.getRuntime().availableProcessors();
    }

    //
    // The following framework supports code instrumentation. To use it,
    // add code of the following form to areas that you want to enable for
    // timing:
    //
    //     long startTime = System.nanoTime();
    //     sortTransformGroups(tSize, tgs);
    //     long deltaTime = System.nanoTime() - startTime;
    //     VirtualUniverse.mc.recordTime(MasterControl.TimeType.XXXXX, deltaTime);
    //
    // where "XXXXX" is the enum representing the code segment being timed.
    // Additional enums can be defined for new subsystems.
    //

    static enum TimeType {
        TOTAL_FRAME,
        RENDER,
        BEHAVIOR,
        // TRANSFORM_UPDATE,
        // ...
    }

    private long[] statTimes = new long[TimeType.values().length];
    private int[] statCounts = new int[TimeType.values().length];
    private boolean[] statSeen = new boolean[TimeType.values().length];
    private int frameCycleTick = 0;
    private long frameCycleNumber = 0L;

    // Method to record times -- should not be called unless the stats logger
    // level is set to INFO or lower
    synchronized void recordTime(TimeType type, long deltaTime) {
        int idx = type.ordinal();
        statTimes[idx] += deltaTime;
        statCounts[idx]++;
        statSeen[idx] = true;
    }

    // Method to record times -- this is not called unless the stats logger
    // level is set to INFO or lower
    private synchronized void logTimes() {
        ++frameCycleNumber;
        if (++frameCycleTick >= 10) {
            StringBuffer strBuf = new StringBuffer();
            strBuf.append("----------------------------------------------\n").
                    append("    Frame Number = ").
                    append(frameCycleNumber).
                    append("\n");
            for (int i = 0; i < statTimes.length; i++) {
                if (statSeen[i]) {
                    strBuf.append("    ");
                    if (statCounts[i] > 0) {
                        strBuf.append(TimeType.values()[i]).
                                append(" [").
                                append(statCounts[i]).
                                append("] = ").
                                append((double)statTimes[i] / 1000000.0 / (double)statCounts[i]).
                                append(" msec per call\n");
                        statTimes[i] = 0L;
                        statCounts[i] = 0;
                    } else {
                        assert statTimes[i] == 0L;
                        strBuf.append(TimeType.values()[i]).
                                append(" [0] = 0.0 msec\n");
                    }
                }
            }
            getStatsLogger().info(strBuf.toString());
            frameCycleTick = 0;
        }
    }

}