Package org.jdesktop.wonderland.server.spatial.impl

Source Code of org.jdesktop.wonderland.server.spatial.impl.SpatialCellImpl$WorldBoundsChangeListener

/**
* Open Wonderland
*
* Copyright (c) 2010 - 2011, Open Wonderland Foundation, All Rights Reserved
*
* Redistributions in source code form must reproduce the above
* copyright and this condition.
*
* The contents of this file are subject to the GNU General Public
* License, Version 2 (the "License"); you may not use this file
* except in compliance with the License. A copy of the License is
* available at http://www.opensource.org/licenses/gpl-license.php.
*
* The Open Wonderland Foundation designates this particular file as
* subject to the "Classpath" exception as provided by the Open Wonderland
* Foundation in the License file that accompanied this code.
*/

/**
* Project Wonderland
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., All Rights Reserved
*
* Redistributions in source code form must reproduce the above
* copyright and this condition.
*
* The contents of this file are subject to the GNU General Public
* License, Version 2 (the "License"); you may not use this file
* except in compliance with the License. A copy of the License is
* available at http://www.opensource.org/licenses/gpl-license.php.
*
* Sun designates this particular file as subject to the "Classpath"
* exception as provided by Sun in the License file that accompanied
* this code.
*/
package org.jdesktop.wonderland.server.spatial.impl;

import com.jme.bounding.BoundingSphere;
import com.jme.bounding.BoundingVolume;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.ManagedReference;
import com.sun.sgs.auth.Identity;
import com.sun.sgs.kernel.KernelRunnable;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Logger;
import org.jdesktop.wonderland.common.cell.CellID;
import org.jdesktop.wonderland.common.cell.CellTransform;
import org.jdesktop.wonderland.server.cell.CellMO;
import org.jdesktop.wonderland.server.cell.CellManagerMO;
import org.jdesktop.wonderland.server.cell.TransformChangeListenerSrv;
import org.jdesktop.wonderland.server.spatial.ViewUpdateListener;

/**
*
* @author paulby
*/
public class SpatialCellImpl implements SpatialCell, ViewUpdateListener {

    private BoundingVolume worldBounds = new BoundingSphere();
    private BoundingVolume localBounds = null;
    private CellTransform localTransform = null;
    private CellTransform worldTransform = null;
    private SpatialCellImpl rootNode = null;
    private SpatialCellImpl parent = null;
    private CellID cellID;
    private BigInteger dsID;        // FOR DEBUG, the DS ID of the cell

    private ArrayList<SpatialCellImpl> children = null;

    private ReentrantReadWriteLock readWriteLock = null;

    private TransformChangeListener worldTransformChangeListener=null;

    private HashSet<Space> spaces = null;

    private ViewCacheSet viewCacheSet = null;

    private ArrayList<TransformChangeListenerSrv> transformChangeListeners = null;
    private final Object transformChangeListenersSync = new Object();

    private CopyOnWriteArraySet<ViewUpdateListener> viewUpdateListeners = null;
    private final Object viewUpdateListenersSync = new Object();

    private boolean isRoot = false;

    public SpatialCellImpl(CellID id, BigInteger dsID) {
//        System.out.println("Creating SpatialCell "+id);
        this.cellID = id;
        this.dsID = dsID;
    }

    /**
     * Users should call acquireRootReadLock while they are using the results of this call
     * @return
     */
    public BoundingVolume getLocalBounds() {
        return localBounds;
    }

    public void setLocalBounds(BoundingVolume localBounds) {
        acquireRootWriteLock();
        try {
            this.localBounds = localBounds;

            if (rootNode!=null) {
                throw new RuntimeException("Updating bounds of live cell not supported yet");
            }
        } finally {
            releaseRootWriteLock();
        }
    }

    /**
     * Users should call acquireRootReadLock while they are using the results of this call
     * @return
     */
    public BoundingVolume getWorldBounds() {
        return worldBounds;
    }

    /**
     * Users should call acquireRootReadLock while they are using the results of this call
     * @return
     */
    public CellTransform getLocalTransform() {
        return localTransform;
    }

    public void setLocalTransform(CellTransform transform, Identity identity) {
        acquireRootWriteLock();

        BoundingVolume newWorldBounds = null;

        try {
            this.localTransform = transform;

            if (rootNode!=null) {
                newWorldBounds = updateWorldTransform(identity);
            }
        } finally {
            releaseRootWriteLock();
        }

        // OWL issue #96: to avoid deadlock, call after releasing the root
        // lock.
        if (isRoot && newWorldBounds != null) {
            computeSpaces(newWorldBounds);
        }
    }

    public CellTransform getWorldTransform() {
        return worldTransform;
    }

    public void addChild(SpatialCell child, Identity identity) {
        if (((SpatialCellImpl)child).parent!=null) {
            throw new RuntimeException("Multiple parent exception, current parent "+((SpatialCellImpl)child).parent.cellID);
        }

        acquireRootWriteLock();
        try {
            if (children==null) {
                children = new ArrayList();
            }

            children.add((SpatialCellImpl) child);
            ((SpatialCellImpl)child).setParent(this);

            if (rootNode!=null) {
                worldBounds.mergeLocal(((SpatialCellImpl)child).updateWorldTransform(identity));
            }

            notifyCacheChildAddedOrRemoved(this, (SpatialCellImpl)child, true);

            // No need to revalidate the parent here -- adding a child can't
            // change security parameters
            // revalidate(); // Security revalidation, optimize this
        } finally {
            releaseRootWriteLock();
        }
    }

    void addTransformChangeListener(TransformChangeListenerSrv listener) {

        synchronized(transformChangeListenersSync) {
            if (transformChangeListeners==null)
                transformChangeListeners = new ArrayList();
            transformChangeListeners.add(listener);

            // OWL issue #61: notify the listener immediately on
            // addition, to make sure that no updates are missed.
            // Only do this if the world root has already been set
            if (worldTransform != null) {
                Collection<TransformChangeListenerSrv> listeners =
                        Collections.singleton(listener);
                UniverseImpl.getUniverse().scheduleTransaction(new TransformChangeNotificationTask(listeners, cellID, localTransform, worldTransform), null);
            }
        }
    }

    void removeTransformChangeListener(TransformChangeListenerSrv listener) {
        if (transformChangeListeners==null)
            return;

        synchronized(transformChangeListenersSync) {
            transformChangeListeners.remove(listener);
        }
    }

    private void notifyTransformChangeListeners(Identity identity) {
        if (transformChangeListeners==null)
            return;

        synchronized(transformChangeListenersSync) {
            UniverseImpl.getUniverse().scheduleTransaction(new TransformChangeNotificationTask(transformChangeListeners, cellID, localTransform, worldTransform), identity);
        }
    }

    /**
     * Listener for changes to any view to which this cell is close
     * @param viewUpdateListener
     */
    public void addViewUpdateListener(ViewUpdateListener viewUpdateListener) {

        synchronized(viewUpdateListenersSync) {
            if (viewUpdateListeners==null)
                viewUpdateListeners = new CopyOnWriteArraySet();
            viewUpdateListeners.add(viewUpdateListener);
        }
    }

    /**
     * @param listener
     */
    public void removeViewUpdateListener(ViewUpdateListener viewUpdateListener) {
        if (viewUpdateListeners==null)
            return;

        synchronized(viewUpdateListenersSync) {
            viewUpdateListeners.remove(viewUpdateListener);
        }
    }

    Iterator<ViewUpdateListener> getViewUpdateListeners() {
        if (viewUpdateListeners==null)
            return null;

        return viewUpdateListeners.iterator();
    }

    /**
     * Re-notify each listener of the position of this cell
     */
    public void revalidateListeners(Identity identity) {
        notifyTransformChangeListeners(identity);
    }

    /**
     * Update the world transform of this node and all it's children iterating
     * down the graph, then coming back up set the world bounds correctly
     * Return the world bounds
     * @return
     */
    private BoundingVolume updateWorldTransform(Identity identity) {
        CellTransform oldWorld;
        boolean transformChanged = false;
        if (worldTransform==null)
            oldWorld=null;
        else
            oldWorld = worldTransform.clone(null);

        if (parent!=null) {
            CellTransform parentWorld = parent.worldTransform.clone(null);
            worldTransform = parentWorld.mul(localTransform);
        } else {
            worldTransform = localTransform.clone(null);
        }

        if (!worldTransform.equals(oldWorld)) {         // TODO should be epsilonEquals
            if (worldTransformChangeListener!=null)     // For view cells
                worldTransformChangeListener.transformChanged(this);
            transformChanged = true;
        }

        computeWorldBounds();

        if (children!=null) {
            for(SpatialCellImpl s : children) {
                worldBounds.mergeLocal(s.updateWorldTransform(identity));
            }
        }

        if (transformChanged) {
            notifyViewCaches(worldTransform);
            notifyTransformChangeListeners(identity);
        }
       
        return worldBounds;
    }
   
    /**
     * Compute the world bounds for this node from the local bounds and world transform
     */
    private void computeWorldBounds() {
        worldBounds = localBounds.clone(worldBounds);
        worldTransform.transform(worldBounds);
    }

    /**
     * Update the set of spaces this cell is a part of. Only called on root
     * cells.
     */
     private void computeSpaces(BoundingVolume worldBounds) {
        if (!isRoot) {
            return;
        }

        // OWL issue #96: to avoid deadlock, make sure the root lock is
        // not held while calling this method.
        synchronized (spaces) {
            HashSet<Space> oldSpaces = (HashSet<Space>) spaces.clone();

            // Check which spaces the bounds intersect with
            Iterable<Space> it = UniverseImpl.getUniverse().getSpaceManager().getEnclosingSpace(worldBounds);
            for(Space s : it) {
                if (!spaces.contains(s)) {
                    s.addRootSpatialCell(this);
                    spaces.add(s);
                } else {
                    oldSpaces.remove(s);
                }
            }

            // Remove this cell from spaces it no longer intersects with
            for(Space s : oldSpaces) {
                s.removeRootSpatialCell(this);
                spaces.remove(s);
            }
        }
    }

    /** Return the children, or null
     *
     * @return
     */
    Iterable<SpatialCellImpl> getChildren() {
        return children;
    }

    public void removeChild(SpatialCell child) {
        if (children==null)
            return;

        acquireRootWriteLock();
        try {
            children.remove(child);
            notifyCacheChildAddedOrRemoved(this, (SpatialCellImpl)child, false);
            ((SpatialCellImpl)child).setParent(null); // Must be called after notifyCacheChildAddedOrRemoved so rootNode is still valid
        } finally {
            releaseRootWriteLock();
        }
    }

    public void setAttribute(Object attr) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public CellID getCellID() {
        return cellID;
    }

    /**
     * Return the root node of the graph that contains this node
     * @return
     */
    SpatialCell getRoot() {
        return rootNode;
    }

    /**
     * Set the root for this node and all it's children
     * @param root
     */
    void setRoot(SpatialCell root, ViewCacheSet viewCacheSet, Identity identity) {
        this.rootNode = (SpatialCellImpl) root;

        if (isRoot && root == null) {
            // We are a root node and we are being removed
            synchronized (spaces) {
                for(Space s : spaces) {
                    s.removeRootSpatialCell(this);
                }
                spaces.clear();
                spaces = null;
            }
                   
            isRoot = false;
            readWriteLock = null;
            this.viewCacheSet = null;
            return;
        }

        if (root == this) {
            // Adding a new root cell to the universe
            if (!isRoot) {
                // Fisrt time, so setup internal root cell structures
                readWriteLock = new ReentrantReadWriteLock(true);
                this.viewCacheSet = new ViewCacheSet();
                isRoot = true;
                spaces = new HashSet();
            }
               
            BoundingVolume newWorldBounds;
            try {
                acquireRootWriteLock();

                // This node is the root of a graph, so set the world transform & bounds
                newWorldBounds = updateWorldTransform(identity);
            } finally {
                releaseRootWriteLock();
            }

            // OWL issue #96: make sure to release the root lock before
            // updating the spaces
            computeSpaces(newWorldBounds);
        } else {
            this.viewCacheSet = viewCacheSet;
           
            // OWL issue #167: add listeners to all views. This will be cleaned
            // up properly when the cell is removed and ViewCacheSet.destroy()
            // is called
            viewCachesAddedOrRemoved(viewCacheSet.getCaches(), true, false, this);
        }

        if (children != null) {
            for(SpatialCellImpl s : children) {
                s.setRoot(root, this.viewCacheSet, identity);
            }
        }
    }

    void setParent(SpatialCellImpl parent) {
        this.parent = parent;
        if (parent != null) {
            setRoot(parent.getRoot(), parent.viewCacheSet, null);
        }
    }

    SpatialCell getParent() {
        return parent;
    }

    private void acquireRootWriteLock() {
        if (rootNode!=null)
            rootNode.readWriteLock.writeLock().lock();
    }
   
    private void releaseRootWriteLock() {
        if (rootNode!=null)
            rootNode.readWriteLock.writeLock().unlock();
    }

    public void acquireRootReadLock() {
        if (rootNode!=null)
            rootNode.readWriteLock.readLock().lock();
    }
   
    public void releaseRootReadLock() {
        if (rootNode!=null)
            rootNode.readWriteLock.readLock().unlock();
    }

    /**
     * Only called for root cells
     * The cell has entered a space, record all the view caches that
     * are now interested in this cell.
     *
     * A view cache can span a number of spaces, so the viewCache structure
     * maintains the set of spaces per ViewCache (effectively a reference count)
     *
     * @param caches
     * @param space
     */
    void addViewCache(Collection<ViewCache> caches, Space space) {
        viewCacheSet.addViewCache(caches, space);
       
        // Notify all cells in the graph that caches have
        // been added. TODO this could be optimized to only notify
        // children that have expressed an interest
        try {
            acquireRootReadLock();
            viewCachesAddedOrRemoved(caches, true, true, this);
        } finally {
            releaseRootReadLock();
        }
    }

    void removeViewCache(Collection<ViewCache> caches, Space space) {
        if (viewCacheSet!=null)
            viewCacheSet.removeViewCache(caches, space);

        // Notify all cells in the graph that caches have
        // been removed. TODO this could be optimized to only notify
        // children that have expressed an interest
        try {
            acquireRootReadLock();
            viewCachesAddedOrRemoved(caches, false, true, this);
        } finally {
            releaseRootReadLock();
        }
    }

    static void viewCachesAddedOrRemoved(Collection<ViewCache> caches,
            boolean added, boolean notifyChildren, SpatialCellImpl cell)
    {
        // Issue #860: add a single listener to each cache to notify this
        // cell of any view updates. This cell will then forward that
        // updates to all the registered listeners.
        for (ViewCache c : caches) {
            if (added) {
                c.addViewUpdateListener(cell.getCellID(), cell);
            } else {
                c.removeViewUpdateListener(cell.getCellID(), cell);
            }
        }

        if (notifyChildren && cell.getChildren() != null) {
            for(SpatialCellImpl child : cell.getChildren()) {
                viewCachesAddedOrRemoved(caches, added, true, child);
            }
        }
    }


    /**
     * Notify the ViewCaches that the worldTransform of this root cell
     * has changed
     * @param worldTransform
     */
    private void notifyViewCaches(CellTransform worldTransform) {
        // Called from updateWorldBounds so we have a write lock on the graph
        SpatialCellImpl root = (SpatialCellImpl) getRoot();
        if (root==null)
            return;

        viewCacheSet.notifyViewCaches(this, worldTransform);
    }

    /**
     * Notify the view caches that this cell needs to be revalidated
     */
    public void revalidate() {
        // Called from updateWorldBounds so we have a write lock on the graph
        SpatialCellImpl root = (SpatialCellImpl) getRoot();
        if (root==null)
            return;

        viewCacheSet.revalidate(this);
    }

    private void notifyCacheChildAddedOrRemoved(SpatialCellImpl parent, SpatialCellImpl child, boolean added) {
        SpatialCellImpl root = (SpatialCellImpl) parent.getRoot();
        if (root==null) {
            Logger.getLogger(SpatialCellImpl.class.getName()).severe("notifyCacheChildAddedOrRemoved has NULL root "+parent);
            return;
        }

        viewCacheSet.notifyCacheChildAddedOrRemoved(parent, child, added);
    }

    public void destroy() {
        acquireRootWriteLock();
       
        boolean wasRoot = isRoot;

        try {
            SpatialCellImpl root = (SpatialCellImpl) getRoot();
            if (root == null) {
                return;
            }

            viewCacheSet.destroy(this);
            viewCacheSet = null;

            if (isRoot) {
                isRoot = false;
                this.viewCacheSet = null;
            }
        } finally {
            releaseRootWriteLock();
        }

        // OWL issue #96: be sure to all after we release the root lock
        // to avoid deadlock
        if (wasRoot) {
            synchronized (spaces) {
                // This is a root node
                for(Space space : spaces) {
                    space.removeRootSpatialCell(this);
                }
                spaces.clear();
                spaces = null;
            }
        }
    }

    public void viewLoggedIn(CellID cell, CellID viewCellID) {
        // issue #860: forward on any view updates to our listeners
        if (viewUpdateListeners != null) {
            for(ViewUpdateListener viewUpdateListener : viewUpdateListeners) {
                viewUpdateListener.viewLoggedIn(cell, viewCellID);
            }
        }
    }

    public void viewTransformChanged(CellID cell, CellID viewCellID, CellTransform viewWorldTransform) {
        // issue #860: forward on any view updates to our listeners
        if (viewUpdateListeners != null) {
            for(ViewUpdateListener viewUpdateListener : viewUpdateListeners) {
                viewUpdateListener.viewTransformChanged(cell, viewCellID, viewWorldTransform);
            }
        }
    }

    public void viewLoggedOut(CellID cell, CellID viewCellID) {
        // issue #860: forward on any view updates to our listeners
        if (viewUpdateListeners != null) {
            for(ViewUpdateListener viewUpdateListener : viewUpdateListeners) {
                viewUpdateListener.viewLoggedOut(cell, viewCellID);
            }
        }
    }

    public interface WorldBoundsChangeListener {
        public void worldBoundsChanged(SpatialCell cell);
    }

    public interface TransformChangeListener {
        public void transformChanged(SpatialCell cell);
    }

    class TransformChangeNotificationTask implements KernelRunnable {

        private TransformChangeListenerSrv[] listeners;
        private CellID cellID;
        private CellTransform localTransform;
        private CellTransform worldTransform;

        public TransformChangeNotificationTask(Collection<TransformChangeListenerSrv> transformListeners, CellID cellID, CellTransform localTransform, CellTransform worldTransform) {
            listeners = transformListeners.toArray(new TransformChangeListenerSrv[transformListeners.size()]);
            this.cellID = cellID;
            this.localTransform = localTransform.clone(null);
            this.worldTransform = worldTransform.clone(null);
        }

        public String getBaseTaskType() {
            return KernelRunnable.class.getName();
        }

        public void run() throws Exception {
            ManagedReference<CellMO> cellRef = AppContext.getDataManager().createReference(CellManagerMO.getCell(cellID));
            for(int i=0; i<listeners.length; i++) {
                TransformChangeListenerSrv listener = listeners[i];
                if (listener instanceof ManagedReference)
                    ((ManagedReference<TransformChangeListenerSrv>)listener).get().transformChanged(cellRef, localTransform, worldTransform);
                else
                    listener.transformChanged(cellRef, localTransform, worldTransform);
            }
        }

    }

    /**
     * The set of ViewCaches in which the cell is visible. Currently all the SpatialCell below a
     * root node have the same ViewCacheSet object
     */
    class ViewCacheSet {
        private final Map<ViewCache, HashSet<Space>> viewCache = new HashMap();

        public ViewCacheSet() {
        }

        public Collection<ViewCache> getCaches() {
            // return a copy
            synchronized (viewCache) {
                return new ArrayList<ViewCache>(viewCache.keySet());
            }
        }

        /**
         * Only called for root cells
         * The cell has entered a space, record all the view caches that
         * are now interested in this cell.
         *
         * A view cache can span a number of spaces, so the viewCache structure
         * maintains the set of spaces per ViewCache (effectively a reference count)
         *
         * @param caches
         * @param space
         */
        void addViewCache(Collection<ViewCache> caches, Space space) {
            synchronized(viewCache) {
                for(ViewCache c : caches) {
                    HashSet<Space> s = viewCache.get(c);
                    if (s==null) {
                        s = new HashSet();
                        s.add(space);
                        viewCache.put(c, s);
                    } else {
                        s.add(space);
                    }

                }
            }
        }

        void removeViewCache(Collection<ViewCache> caches, Space space) {
            synchronized(viewCache) {
                for(ViewCache c : caches) {
                    HashSet<Space> s = viewCache.get(c);
                    if (s==null) {
                        throw new RuntimeException("ERROR, cache not in set");
                    } else {
                        s.remove(space);
                    }

                    if (s.size()==0) {
                        viewCache.remove(c);
                    }
                }
            }

        }

        /**
         * Notify the ViewCaches that the worldTransform of this root cell
         * has changed
         * @param worldTransform
         */
        void notifyViewCaches(SpatialCellImpl cell, CellTransform worldTransform) {
            // make a copy to avoid concurrent modification
            ViewCache[] caches;
            synchronized (viewCache) {
                caches = viewCache.keySet().toArray(new ViewCache[viewCache.size()]);
            }

            for(ViewCache cache : caches) {
                cache.cellMoved(cell, worldTransform);
            }
        }

        /**
         * Notify the view caches that this cell needs to be revalidated
         */
        void revalidate(SpatialCellImpl cell) {
            // make a copy to avoid concurrent modification
            ViewCache[] caches;
            synchronized (viewCache) {
                caches = viewCache.keySet().toArray(new ViewCache[viewCache.size()]);
            }

            for(ViewCache cache : caches) {
                cache.cellRevalidated(cell);
            }
        }

        void notifyCacheChildAddedOrRemoved(SpatialCellImpl parent, SpatialCellImpl child, boolean added) {
            // make a copy to avoid concurrent modification
            ViewCache[] caches;
            synchronized (viewCache) {
                caches = viewCache.keySet().toArray(new ViewCache[viewCache.size()]);
            }

            for(ViewCache cache : caches) {
                if (added)
                    cache.childCellAdded(child);
                else
                    cache.childCellRemoved(parent, child);
            }

        }

        void destroy(SpatialCellImpl cell) {
            // make a copy to avoid concurrent modification
            ViewCache[] caches;
            synchronized (viewCache) {
                caches = viewCache.keySet().toArray(new ViewCache[viewCache.size()]);
            }

            for(ViewCache cache : caches) {
                cache.cellDestroyed(cell);
            }
        }
    }
}
TOP

Related Classes of org.jdesktop.wonderland.server.spatial.impl.SpatialCellImpl$WorldBoundsChangeListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.