summaryrefslogtreecommitdiffstats
path: root/src/net/java/joglutils/msg/misc/State.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/net/java/joglutils/msg/misc/State.java')
-rw-r--r--src/net/java/joglutils/msg/misc/State.java220
1 files changed, 220 insertions, 0 deletions
diff --git a/src/net/java/joglutils/msg/misc/State.java b/src/net/java/joglutils/msg/misc/State.java
new file mode 100644
index 0000000..c718f71
--- /dev/null
+++ b/src/net/java/joglutils/msg/misc/State.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2007 Sun Microsystems, Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
+ * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
+ * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
+ * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+ * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
+ * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
+ * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
+ * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
+ * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
+ * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed or intended for use
+ * in the design, construction, operation or maintenance of any nuclear
+ * facility.
+ *
+ */
+
+package net.java.joglutils.msg.misc;
+
+import java.util.*;
+
+import net.java.joglutils.msg.elements.*;
+
+/** Represents a collection of state elements, which are updated by
+ actions during scene graph traversal. */
+
+public class State {
+ // Provides each concrete Element subclass a unique slot in the State.
+ // Note that subclasses of concrete Element subclasses share the
+ // slot with their parent class. Which one is used for a given
+ // Action is decided by which elements are enabled for that action.
+ private static int curStateIndex = 0;
+
+ // The representation of the State.
+ private List<Element> elements = new ArrayList<Element>();
+
+ // The default enabled elements for this State. The State instances
+ // created by each Action instance point to the default enabled
+ // elements; this is where the individual Action's State is
+ // initialized from.
+ // Note that this mechanism is needed because different actions
+ // install different Element subclasses (i.e., ModelMatrixElement
+ // vs. GLModelMatrixElement) into the same element slot.
+ private State defaults;
+
+ // A linked list of elements touched since the last push, used to
+ // reduce the number of elements that need to be accessed during a
+ // pop() operation.
+ private Element topElement;
+
+ // The depth at which we are operating, to implement lazy pushing
+ // and popping of state elements
+ private int depth;
+
+ /** This constructor should only be used for the default State for a
+ given Action subclass. */
+ public State() {
+ }
+
+ /** This constructor should be used to create the concrete State
+ instances for each Action instance. The default State given
+ should be that for the particular Action class. */
+ public State(State defaults) {
+ this.defaults = defaults;
+ // Do a push() to ensure that we always have a non-null and
+ // pristine entry at the top of each stack
+ push();
+ }
+
+ /** Gets the state element at the given index. */
+ public Element getElement(StateIndex index) {
+ // The comments in the Open Inventor implementation indicate that
+ // a bug that was found and fixed was that this method must not be
+ // called in the process of popping the state. This assert
+ // attempts to guard against that happening.
+ assert depth >= ((topElement == null) ? 0 : topElement.getDepth()) :
+ "Elements must not be changed while the state is being popped (element being changed: " +
+ elements.get(index.getIndex()).getClass().getName() + ").";
+
+ int idx = index.getIndex();
+
+ if (defaults == null) {
+ // This State contains the defaults for a particular Action
+ // class. Don't do anything fancy -- just return the element at
+ // the particular index.
+ if (idx >= elements.size()) {
+ return null;
+ }
+ return elements.get(idx);
+ }
+
+ if (idx >= elements.size()) {
+ // Expand list to the needed size
+ while (idx >= elements.size()) {
+ elements.add(null);
+ }
+ }
+
+ Element elt = elements.get(idx);
+ if (elt == null) {
+ // Lazily create a copy of the default and put it in place
+ elt = defaults.getElement(index);
+ if (elt == null) {
+ throw new RuntimeException("Error in initialization of default element for state index " + idx);
+ }
+ elt = elt.newInstance();
+ elt.setDepth(0);
+ elements.set(idx, elt);
+ }
+
+ // If element is not at current depth, we have to push a new
+ // element on the stack
+ if (elt.getDepth() < depth) {
+ // FIXME: consider doubly-linked-list scheme as in Inventor to
+ // avoid excessive object creation during scene graph traversal
+ Element newElt = elt.newInstance();
+ newElt.setNextInStack(elt);
+ newElt.setDepth(depth);
+ // Add newly-created element to the all-element stack
+ newElt.setNext(topElement);
+ topElement = newElt;
+ elements.set(idx, newElt);
+ // Call push on new element in case it has side effects
+ newElt.push(this);
+ // Return new element
+ elt = newElt;
+ }
+
+ return elt;
+ }
+
+ /** Sets the element at the given state index. This should only be
+ used by Action, Element and Node subclasses to initialize the
+ default state for a given Action class. */
+ public void setElement(StateIndex index, Element element) {
+ if (defaults != null) {
+ throw new RuntimeException("Misuse of setElement(); should only be used to initialize default State for an Action");
+ }
+ int idx = index.getIndex();
+ if (idx >= elements.size()) {
+ while (idx >= elements.size()) {
+ elements.add(null);
+ }
+ }
+ elements.set(idx, element);
+ }
+
+ /** Pushes (saves) the current state until a pop() restores it. The
+ push is done lazily: this just increments the depth in the
+ state. When an element is accessed with getElement() and its
+ depth is less than the current depth, it is then pushed
+ individually. */
+ public void push() {
+ ++depth;
+ }
+
+ /** Pops the state, restoring the state to just before the last push(). */
+ public void pop() {
+ --depth;
+
+ Element poppedElt = null;
+ Element nextInStack = null;
+
+ // As in Open Inventor, the popping is done in two passes. This is
+ // apparently needed if Open Inventor-style caching is added in
+ // the future. The first pass calls pop() on all of the elements
+ // that will be popped; the second pass actually removes the
+ // elements from their respective stacks.
+ for (poppedElt = topElement;
+ poppedElt != null && poppedElt.getDepth() > depth;
+ poppedElt = poppedElt.getNext()) {
+
+ // Find the next element in the same stack as the element being
+ // popped. This element will become the new top of that stack.
+ nextInStack = poppedElt.getNextInStack();
+
+ // Give the new top element in the stack a chance to update
+ // things. Pass old element instance just in case.
+ poppedElt.getNextInStack().pop(this, poppedElt);
+ }
+
+ // Remove all such elements from their respective stacks
+ while (topElement != null && topElement.getDepth() > depth) {
+ poppedElt = topElement;
+
+ // Remove from main element list
+ topElement = topElement.getNext();
+
+ // Remove from element stack
+ elements.set(poppedElt.getStateIndex().getIndex(), poppedElt.getNextInStack());
+ }
+ }
+
+ /** Should be called by Element subclasses to register themselves
+ with the State class. This provides them a StateIndex with which
+ they can index into the State. */
+ public static synchronized StateIndex registerElementType() {
+ return new StateIndex(curStateIndex++);
+ }
+}