Code Sample – Action List

public final class ActionList extends Action
{
  private var size_:int = 0;
  
  //(key, value) = (id, lane)
  private var laneDictionary_:Dictionary          = null;
  private var laneOrderDirty_:Boolean             = null;
  private var lanes_         :Vector.<ActionLane> = null;
  private var lanePool_      :ObjectPool          = null;
  
  public function size():int { return size_; }
  
  private var autoComplete_:Boolean;
  public function ActionList
  (
    autoComplete :Boolean = true, 
    instantUpdate:Boolean = false
  )
  {
    super(instantUpdate);
    
    laneDictionary_ = new Dictionary();
    laneOrderDirty_ = true;
    lanes_          = new Vector.<ActionLane>();
    lanePool_       = new ObjectPool();
    
    autoComplete_ = autoComplete;
    onCancelled.addOnce(cancelSubactions);
  }
  
  public function pushBack
  (
    action:Action, 
    laneID:int = 0
  ):ActionList
  {
    getLane(laneID).actions.pushBack(action);
    action.laneID_ = laneID;
    ++size_;
    return this;
  }
  
  public function pushFront
  (
    action:Action, 
    laneID:int = 0
  ):ActionList
  {
    getLane(laneID).actions.pushFront(action);
    action.laneID_ = laneID;
    ++size_;
    return this;
  }
  
  private function injectDependency(action:Action):void
  {
    action.parent_ = this;
    action.setInjector(getInjector());
    getInjector().injectInto(action);
  }
  
  override public function update(dt:Number):void
  {
    if (isPaused() || isFinished()) return;
    
    //lane order dirty, sort
    if (laneOrderDirty_) lanes_.sort(laneSorter);
    
    //iterate through all lanes
    var i:int, len:int;
    var lane:ActionLane;
    var laneActions:InList;
    for (i = 0, len = lanes_.length; i < len; ++i)
    {
      lane = lanes_[i];
      laneActions = lane.actions;
      
      //iterate through actions in the current lane
      var action:Action;
      var iter:InListIterator = laneActions.getIterator();
      while (action = iter.data())
      {
        //action completed before update, remove and continue
        if (action.isFinished())
        {
          iter.remove();
          --size_;
          continue;
        }
        
        //action not paused, update
        if (!action.isPaused())
        {
          //first update, call onStarted()
          if (!action.isStarted_) 
          {
            //set injector & lane ID (key)
            injectDependency(action);
            
            action.onStarted.dispatch();
            action.isStarted_ = true;
            
            //action cancelled or completed after init, 
            //remove and continue
            if (action.isFinished())
            {
              iter.remove();
              --size_;
              continue;
            }
            
            //action does not update instantly after init if 
            //it's not completed, continue
            if (!action.instantUpdate_)
            {
              //action is blocking, break
              if (action.isBlocking()) break;
              
              iter.next();
              continue;
            }
          }
          
          //update action
          action.update(dt);
          
          //action cancelled or completed after update, 
          //remove and continue
          if (action.isFinished())
          {
            iter.remove();
            --size_;
            continue;
          }
        }
        
        //action is blocking, break
        if (action.isBlocking()) break;
        
        iter.next();
      }
    }
    
    //loop backwards, recycling empty lanes
    for (i = lanes_.length - 1; i >= 0; --i)
    {
      lane = lanes_[i];
      
      //empty lane, remove from dictionary and put into pool
      if (lane.actions.size() == 0)
      {
        delete laneDictionary_[lane.id];
        lanes_.splice(i, 1);
        lanePool_.put(lane);
      }
    }
    
    if (size_ == 0 && autoComplete_)
    {
      complete();
    }
  }
  
  
  
  //lane commands
  //----------------------------------------------------------
  
  public function cancelLane(laneID:int = 0):ActionList
  {
    var iter:InListIterator = 
      getLane(laneID).actions.getIterator();
    var action:Action;
    while (action = iter.data())
    {
      action.cancel();
      iter.next();
    }
    return this;
  }
  
  public function pauseLane(laneID:int = 0):ActionList
  {
    var iter:InListIterator = 
      getLane(laneID).actions.getIterator();
    var action:Action;
    while (action = iter.data())
    {
      action.pause();
      iter.next();
    }
    return this;
  }
  
  public function resumeLane(laneID:int = 0):ActionList
  {
    var iter:InListIterator = 
      getLane(laneID).actions.getIterator();
    var action:Action;
    while (action = iter.data())
    {
      action.resume();
      iter.next();
    }
    return this;
  }
  
  public function blockLane(laneID:int = 0):ActionList
  {
    var iter:InListIterator = 
      getLane(laneID).actions.getIterator();
    var action:Action;
    while (action = iter.data())
    {
      action.block();
      iter.next();
    }
    return this;
  }
  
  public function unblockLane(laneID:int = 0):ActionList
  {
    var iter:InListIterator = 
      getLane(laneID).actions.getIterator();
    var action:Action;
    while (action = iter.data())
    {
      action.unblock();
      iter.next();
    }
    return this;
  }
  
  //----------------------------------------------------------
  //end of lane commands
  
  
  //utility functions
  //----------------------------------------------------------
  
  public static function serial(...actions):ActionList
  {
    var actionList:ActionList = new ActionList(true);
    for
    (
      var i:int = 0, len:int = actions.length; 
      i < len; ++i
    )
    {
      var action:Action = actions[i];
      if (!action) continue;
      
      action.block();
      actionList.pushBack(action);
    }
    return actionList;
  }
  
  public static function parallel(...actions):ActionList
  {
    var actionList:ActionList = new ActionList(true);
    for
    (
      var i:int = 0, len:int = actions.length; 
      i < len; ++i
    )
    {
      var action:Action = actions[i];
      if (!action) continue;
      
      action.unblock();
      actionList.pushBack(action);
    }
    return actionList;
  }
  
  //----------------------------------------------------------
  //end of utility functions
  
  
  private function cancelSubactions():void 
  {
    //cancel all underlying actions
    for (var key:* in laneDictionary_)
    {
      var lane:ActionLane = laneDictionary_[key];
      var action:Action;
      var iter:InListIterator = lane.actions.getIterator();
      
      while (action = iter.data())
      {
        if (!action.isFinished()) action.cancel();
        iter.next();
      }
      
      delete laneDictionary_[key];
    }
  }
  
  private function getLane(laneID:int):ActionLane
  {
    var lane:ActionLane;
    
    //create lane if non-existent
    if (!(lane = laneDictionary_[laneID]))
    {
      //get lane from pool if pool is not empty
      lane = (lanePool_.isEmpty())
             ? (new ActionLane())
             : (lanePool_.get());
      lane.id = laneID;
      
      laneDictionary_[laneID] = lane;
      lanes_.push(lane);
      laneOrderDirty_ = true;
    }
    
    return lane;
  }
}

Leave a Reply