DepthManager

Alright, so I’ve been really hesitant to give this out… but I decided what the hell why not.

This DepthManager is a little odd in how it works… so what I will do is post it and an example, then I will answer questions.

Node.as


class Node
{
    // the node that is linked to the right
    public var next:Node;

    // the objec this node contains
    public var data:Object;

    /**
     * Constructor
     *
     * @param The object this node contains
     * @param The node linked to the right
     */
    public function Node( data:Object, next:Node )
    {
        this.data = data;

        this.next = (next == undefined) ? null : next;
    }
}

DepthManager.as


import Node;
/** 
 * A class used for managing the depths for movieclips.
 * This class relies heavy on linked structures for efficiency and speed.
 *
 * @author Michael Avila
 * @version 1.0.0
 */

class DepthManager
{
    // The depth that this manager will begin allocating at
    private static var startDepth:Number = 100;

    // The first node in the list
    private var firstNode:Node = null;
    // The last node in thelist
    private var lastNode:Node = null;

    // Whether or not to apply the depths to the movieclips after each change
    private var _autoApply:Boolean = false;

    // Gets and sets the auto apply property
    public function get autoApply():Boolean { return _autoApply; }
    public function set autoApply( apply:Boolean )
    {
       _autoApply = (apply == null) ? false : apply;
    }

    /**
     * Constructor
     */
    public function DepthManager( autoApplyDepths:Boolean ) 
    {
       if (autoApplyDepths != undefined || autoApplyDepths != null) _autoApply = autoApplyDepths;
    }
    
    /**
     * Returns whether this manager is empty or not
     */
    public function isEmpty():Boolean
    {
        return (firstNode == null);
    }

    /**
     * Find and occupy the depth before the specified clip, if the clip you specified does not exist within the depth manager
     * the clip will be placed at the beginning.
     */
    public function occupyBefore( beforeClip:MovieClip, clip:MovieClip )
    {
           if ( isEmpty() )
       {
        occupyFirst( clip );
        return;
       }
       if ( beforeClip == undefined )  
       {
        occupyFirst( clip );
        return
       }

       var newNode:Node = new Node(clip, null);

       for (var node:Node = firstNode; node != null; node = node.next)
       {
          if ( node.next.data == beforeClip )
              {
                 newNode.next = node.next;
         node.next = newNode;
         _autoApply ? applyDepths() : return;
         return;
              }
       }
    }

    /**
     * Find and occupy the depth after the specified clip, if the clip you specified does not exist within the depth manager
     * the clip will be placed at the end.
     */
    public function occupyAfter ( afterClip:MovieClip, clip:MovieClip )
    {
           if ( isEmpty() )
       {
        occupyFirst( clip );
        return;
       }
       if ( afterClip == undefined ) 
       {
        occupyLast( clip );       
        return;
       }

       var newNode:Node = new Node(clip, null);

       for (var node:Node = firstNode; node != null; node = node.next)
       {
          if ( node.data == afterClip )
              {
                 newNode.next = node.next;
         node.next = newNode;
         _autoApply ? applyDepths() : return;
         return;
              }
       }
    }

    /**
     * Occupy the first depth
     */
    public function occupyFirst( clip:MovieClip )
    {
        var newNode:Node = new Node(clip, null);

        if (isEmpty())
        {
           firstNode = newNode;
           lastNode = newNode;
           _autoApply ? applyDepths() : return;
           return;
        }
    
        newNode.next = firstNode;
        firstNode = newNode;
        _autoApply ? applyDepths() : return;
    }

    /**
     * Find and occupy the highest depth
     */
    public function occupyLast( clip:MovieClip )
    {
        var newNode:Node = new Node(clip, null);
    
        if (isEmpty())
        {
           firstNode = newNode;
           lastNode = newNode;
                    _autoApply ? applyDepths() : return;
           return;
        }

        lastNode.next = newNode;
        lastNode = newNode;    
        _autoApply ? applyDepths() : return;
    }

    /**
     * Removes the clip from the manager
     */
    public function remove( clip:MovieClip )
    {
       for (var node:Node = firstNode; node != null; node = node.next)
       {
          if (node.next.data == clip)
          {
             removeNode( node ); 
         _autoApply ? applyDepths() : return;
         return;
          }
       }
    }

    /**
     * Applys the model of depths, for each clip, to the actual depths of the clips
     */
    public function applyDepths()
    {
        var count:Number = 0;
        for (var node:Node = firstNode; node != null; node = node.next)
        {
           node.data.swapDepths( DepthManager.startDepth + count);
           count++;
        }
    }
    
    /**
     * Pass it a node and it will remove the node after
     */
    private function removeNode( node:Node ):Void
    {
       node.next = node.next.next;
    }

    /** DIAGNOSTICS **/
    public function showAll()
    {
        for (var node:Node = firstNode; node != null; node = node.next)
        {
           trace( node.data );
        }
    }
}


fla


var dm = new DepthManager();

var box = this.createEmptyMovieClip("box", 100);
var box2 = this.createEmptyMovieClip("box2", 101);
var box3 = this.createEmptyMovieClip("box3", 102);
var box4 = this.createEmptyMovieClip("box4", 103);
var box5 = this.createEmptyMovieClip("box5", 104);
var box6 = this.createEmptyMovieClip("box6", 105);
var box7 = this.createEmptyMovieClip("box7", 106);

dm.occupyLast( box );
dm.occupyLast( box2 );
dm.occupyFirst( box3 );

dm.occupyBefore( box2, box4 );
dm.occupyAfter( box, box5 );

// Examples of non-existent befores and afters
dm.occupyBefore( boxx, box6 );
dm.occupyAfter( boxx, box7 );

dm.remove( box3 );

dm.applyDepths();
dm.showAll();

trace( box.getDepth() );

Okay, so if you have questions ask away, I have to go out for a while tonight so I will start answering if there’s any questions, when I get back.

Take Care.
_Michael

I will be doing a bit of optimizing when I get home tonight… specifically with the applyDepths method. I have a few ideas of how to work this more efficiently. In it’s current state I can swapDepths on 15 mc’s 150,000 times with about 500 miliseconds of delay… which is obviously unacceptable, but it’s not bad considering the fact that you’ll NEVER reach those high of numbers.

TakeCare.
_Michael

Would you mind posting an swf?

Honestly, there’s no clarity that an swf is going going to give you. What do you not understand, feel free to ask and I will do my best to answer. TakeCare.

_Michael

That’s quite nice. You even made the Node generic (Object) to use for other projects (I assume).[COLOR=#000000]

[/COLOR]Under the circumstances, even though its a LL, it’ll does nicely. [COLOR=#000000]I can’t think of anything more appropriate (and more often there is when it comes to LLs - BST, DLL, hash table, array, etc.)

[/COLOR]Theres only a return in occupyBeforeCOLOR=#000000 without a semicolon.[/COLOR]
[COLOR=#000000][/COLOR][COLOR=#000000]
Nice job, and thanks, I’ll probably use it :slight_smile:
[/COLOR]

Yeah this is a generic node I use for most implementations of linked data structures that I have, like I said tonight I will be doing some optimizations with the applyDepths() method. I am very grateful that you took the time to leave feedback. I’ll keep up with the updates of this class both here, and on my blog. Feel free to post any issues or suggestions you have.

Take Care.
_Michael

Um… The DepthManager class should actually read…


/**
 * A class used for managing the depths for movieclips.
 * This class relies heavy on linked structures for efficiency and speed.
 *
 * @author Michael Avila
 * @version 1.0.0
 */
class DepthManager
{
	// The depth that this manager will begin allocating at
	private static var startDepth:Number = 100;
	// The first node in the list
	private var firstNode:Node = null;
	// The last node in thelist
	private var lastNode:Node = null;
	// Whether or not to apply the depths to the movieclips after each change
	private var _autoApply:Boolean = false;
	// Gets and sets the auto apply property
	public function get autoApply ():Boolean
	{
		return _autoApply;
	}
	public function set autoApply (apply:Boolean)
	{
		_autoApply = (apply == null) ? false : apply;
	}
	/**
	     * Constructor
	     */
	public function DepthManager (autoApplyDepths:Boolean)
	{
		if (autoApplyDepths != undefined || autoApplyDepths != null)
		{
			_autoApply = autoApplyDepths;
		}
	}
	/**
	     * Returns whether this manager is empty or not
	     */
	public function isEmpty ():Boolean
	{
		return (firstNode == null);
	}
	/**
	     * Find and occupy the depth before the specified clip, if the clip you specified does not exist within the depth manager
	     * the clip will be placed at the beginning.
	     */
	public function occupyBefore (beforeClip:MovieClip, clip:MovieClip)
	{
		if (isEmpty ())
		{
			occupyFirst (clip);
			return;
		}
		if (beforeClip == undefined)
		{
			occupyFirst (clip);
			return;
		}
		var newNode:Node = new Node (clip, null);
		for (var node:Node = firstNode; node != null; node = node.next)
		{
			if (node.next.data == beforeClip)
			{
				newNode.next = node.next;
				node.next = newNode;
				if (_autoApply) applyDepths ();
				return;
			}
		}
	}
	/**
	     * Find and occupy the depth after the specified clip, if the clip you specified does not exist within the depth manager
	     * the clip will be placed at the end.
	     */
	public function occupyAfter (afterClip:MovieClip, clip:MovieClip)
	{
		if (isEmpty ())
		{
			occupyFirst (clip);
			return;
		}
		if (afterClip == undefined)
		{
			occupyLast (clip);
			return;
		}
		var newNode:Node = new Node (clip, null);
		for (var node:Node = firstNode; node != null; node = node.next)
		{
			if (node.data == afterClip)
			{
				newNode.next = node.next;
				node.next = newNode;
				if (_autoApply) applyDepths();
				return;
			}
		}
	}
	/**
	     * Occupy the first depth
	     */
	public function occupyFirst (clip:MovieClip)
	{
		var newNode:Node = new Node (clip, null);
		if (isEmpty ())
		{
			firstNode = newNode;
			lastNode = newNode;
			if (_autoApply) applyDepths();
			return;
		}
		newNode.next = firstNode;
		firstNode = newNode;
		//_autoApply ? applyDepths() : return;
	}
	/**
	     * Find and occupy the highest depth
	     */
	public function occupyLast (clip:MovieClip)
	{
		var newNode:Node = new Node (clip, null);
		if (isEmpty ())
		{
			firstNode = newNode;
			lastNode = newNode;
			if (_autoApply)
			{
				applyDepths ();
			}
			else
			{
				return;
			}
			return;
		}
		lastNode.next = newNode;
		lastNode = newNode;
		if (_autoApply) applyDepths();
	}
	/**
	     * Removes the clip from the manager
	     */
	public function remove (clip:MovieClip)
	{
		for (var node:Node = firstNode; node != null; node = node.next)
		{
			if (node.next.data == clip)
			{
				removeNode (node);
				if (_autoApply) applyDepths();
				return;
			}
		}
	}
	/**
	     * Applys the model of depths, for each clip, to the actual depths of the clips
	     */
	public function applyDepths ()
	{
		var count:Number = 0;
		for (var node:Node = firstNode; node != null; node = node.next)
		{
			node.data.swapDepths (DepthManager.startDepth + count);
			count++;
		}
	}
	/**
	     * Pass it a node and it will remove the node after
	     */
	private function removeNode (node:Node):Void
	{
		node.next = node.next.next;
	}
	/** DIAGNOSTICS **/
	public function showAll ()
	{
		for (var node:Node = firstNode; node != null; node = node.next)
		{
			trace (node.data);
		}
	}
}

I will be back in about 30 minutes with some optimizations.

Here’s much more optimized version…


/**
 * A class used for managing the depths for movieclips.
 * This class relies heavy on linked structures for efficiency and speed.
 *
 * @author Michael Avila
 * @version 1.0.0
 */
class DepthManager
{
	// The depth that this manager will begin allocating at
	private static var startDepth:Number = 100;
	
   // The first node in the list
	private var firstNode:Node = null;
	// The last node in thelist
	private var lastNode:Node = null;
   // Last Modified Node ( also the position )
   private var lastModified:Node = null

   // the offset for the depth... we need this offset because we are no longer applying depths from the firstNode
   private var depthOffset:Number = 0;
   
	// Whether or not to apply the depths to the movieclips after each change
	private var _autoApply:Boolean = false;
   // Whether or not the depths have been applied yet
   private var beenApplied:Boolean = false;
   // Whether or not the depths need to be applied
   private var needsApply:Boolean = false;

	// Gets and sets the auto apply property
	public function get autoApply ():Boolean { return _autoApply;	}
	public function set autoApply (apply:Boolean) { _autoApply = (apply == null) ? false : apply; }
	
   /**
	 * Constructor
	 */
	public function DepthManager (autoApplyDepths:Boolean)
	{
		if (autoApplyDepths != undefined || autoApplyDepths != null)
		{
			_autoApply = autoApplyDepths;
		}
	}
	
   /**
	 * Returns whether this manager is empty or not
	 */
	public function isEmpty ():Boolean
	{
		return (firstNode == null);
	}
	
   /**
	 * Find and occupy the depth before the specified clip, if the clip you specified does not exist within the depth manager
	 * the clip will be placed at the beginning.
	 */
	public function occupyBefore (beforeClip:MovieClip, clip:MovieClip)
	{
      // for efficiency we will test whether or not this inserted node
      // is before the lastModified node, if it is then we will set this to the new lastModified
      // node, if not we leave it
      var isLastModified:Boolean = true;
      // used to count the index of the last modified.  Is then used
      // as an offset to calculate the correct depth.
      var index:Number = 0;

		if (isEmpty ())
		{
			occupyFirst (clip);
			return;
		}
		if (beforeClip == undefined)
		{
			occupyFirst (clip);
			return;
		}
		var newNode:Node = new Node (clip, null);

      
      
		for (var node:Node = firstNode; node != null; node = node.next)
		{
         if (node == lastModified)
         {
            isLastModified = false;
         }
			if (node.next.data == beforeClip)
			{
            if (isLastModified)
            {
               lastModified = newNode;
               depthOffset = index;
            }
				newNode.next = node.next;
				node.next = newNode;
				if (_autoApply)
            {
               applyDepths ();
            }
            else
            {
               needsApply = true;
            }
				return;
			}
         index++;
      }
	}
	
   /**
	 * Find and occupy the depth after the specified clip, if the clip you specified does not exist within the depth manager
	 * the clip will be placed at the end.
	 */
	public function occupyAfter (afterClip:MovieClip, clip:MovieClip)
	{
      // for efficiency we will test whether or not this inserted node
      // is before the lastModified node, if it is then we will set this to the new lastModified
      // node, if not we leave it
      var isLastModified:Boolean = true;
      // used to count the index of the last modified.  Is then used
      // as an offset to calculate the correct depth.
      var index:Number = 0;
      
		if (isEmpty ())
		{
			occupyFirst (clip);
			return;
		}
		if (afterClip == undefined)
		{
			occupyLast (clip);
			return;
		}
		var newNode:Node = new Node (clip, null);
		for (var node:Node = firstNode; node != null; node = node.next)
		{
         if (node == lastModified)
         {
            isLastModified = false;
         }
			if (node.data == afterClip)
			{
            if (isLastModified)
            {
               lastModified = newNode;
               depthOffset = index;
            }
				newNode.next = node.next;
				node.next = newNode;
				if (_autoApply)
            {
               applyDepths();
            }
            else
            {
               needsApply = true;
            }
				return;
			}
         index++;
		}
	}
	
   /**
	 * Occupy the first depth
	 */
	public function occupyFirst (clip:MovieClip)
	{
		var newNode:Node = new Node (clip, null);

      lastModified = newNode;
      
		if (isEmpty ())
		{
			firstNode = newNode;
			lastNode = newNode;
			if (_autoApply) applyDepths();
			return;
		}
		newNode.next = firstNode;
		firstNode = newNode;
		if (_autoApply)
      {
         applyDepths();
      }
      else
      {
         needsApply = true;
      }
	}
	
   /**
	 * Find and occupy the highest depth
	 */
	public function occupyLast (clip:MovieClip)
	{
		var newNode:Node = new Node (clip, null);

      lastModified = (lastModified == null) ? newNode : null;
      
		if (isEmpty ())
		{
			firstNode = newNode;
			lastNode = newNode;
			if (_autoApply) applyDepths ();
			return;
		}
		lastNode.next = newNode;
		lastNode = newNode;
		if (_autoApply)
      {
         applyDepths();
      }
      else
      {
         needsApply = true;
      }
	}
	
   /**
	 * Removes the clip from the manager
	 */
	public function remove (clip:MovieClip)
	{
		for (var node:Node = firstNode; node != null; node = node.next)
		{
			if (node.next.data == clip)
			{
			   node.next = node.next.next;
				return;
			}
		}
	}
	
   /**
	 * Applys the model of depths, for each clip, to the actual depths of the clips
	 */
	public function applyDepths ()
	{
      // if we don't need to do this.. then don't
      if ( !needsApply ) return;

      var startNode:Node = beenApplied ? lastModified : firstNode;
		var count:Number = 0;
		for (var node:Node = startNode; node != null; node = node.next)
		{
         trace( count );
			node.data.swapDepths (DepthManager.startDepth + (count + depthOffset ));
			count++;
		}

      needsApply = false;
      lastModified = lastNode;

      if (!beenApplied) beenApplied = true;
	}
	
   /** DIAGNOSTICS **/
	public function showAll ()
	{
		for (var node:Node = firstNode; node != null; node = node.next)
		{
			trace (node.data);
		}
	}
}


Here the optimization all takes place when applying depths. What I went ahead and did was kept track of the last node that was modified that was closes to the beginning of the list. That way when we apply depths we just apply from that node on (if the depths have already been applied before). This prevents us from applying the depths to every clip every time we apply. Instead we will only apply from the changed position on.

This is rather efficient believe it or not.

Take Care.

_Michael