初始化构造函数

ActionManager::ActionManager()
: _targets(nullptr), //目标节点的表头
_currentTarget(nullptr), //当前正在运行的节点
_currentTargetSalvaged(false) //释放标记
{}
typedef struct _hashElement
{
struct _ccArray *actions; //和taget绑定的所有action组成的数组
Node *target; //执行action的节点
int actionIndex; //actions的索引
Action *currentAction; //当前正在执行的action,(异步线程才会用到
bool currentActionSalvaged; //释放标记(异步线程使用
bool paused; //是否暂停
UT_hash_handle hh; //哈希表的句柄
} tHashElement; 在调用了node->runAction后,ActionManager会将node和action信息加入到hash表中。在ActionManger的update函数中遍历_targets的所有元素,并执行和每个target绑定的actions,然后对actions遍历执行step。
在Direction的初始化时调用_scheduler->scheduleUpdate(_actionManager, Scheduler::PRIORITY_SYSTEM, false);
保证了每帧都会调用ActionManager的update方法

析构函数

ActionManager::~ActionManager()
{
CCLOGINFO("deallocing ActionManager: %p", this); removeAllActions(); //移除所有的动作
}

删除哈希元素

void ActionManager::deleteHashElement(tHashElement *element)
{
ccArrayFree(element->actions); //释放element的所有动作
HASH_DEL(_targets, element); //从targets中删除element
element->target->release(); //target引用计数-1
free(element); //释放element的内存空间
}

分配存放动作对象的空间

void ActionManager::actionAllocWithHashElement(tHashElement *element)
{
// 4 actions per Node by default
if (element->actions == nullptr) //如果当前动作数组为空则分配四个空间
{
element->actions = ccArrayNew(4);
}else
if (element->actions->num == element->actions->max) //如果将要超过最大容量,就将内存加倍
{
ccArrayDoubleCapacity(element->actions);
} }

通过索引移除动作

除了移除全部动作的方法以外,都是调用这个方法来移除action

void ActionManager::removeActionAtIndex(ssize_t index, tHashElement *element)
{
//获取要移除的action对象
Action *action = static_cast<Action*>(element->actions->arr[index]);
//判断是否是正在运行的action,一般不会进入这个判断,
//除非是正在执行update的step的时候,异步线程调用了remove来移除正在执行的这个action,
//才会进到这个判断中,currentActionSalvaged就是一个安全锁,确保了动作执行不会出错
if (action == element->currentAction && (! element->currentActionSalvaged))
{
element->currentAction->retain(); //引用计数+1
element->currentActionSalvaged = true; //标记将要删除
} //从actions中移除索引index的action
ccArrayRemoveObjectAtIndex(element->actions, index, true); // update actionIndex in case we are in tick. looping over the actions
if (element->actionIndex >= index)
{
//索引总数-1
element->actionIndex--;
} //如果节点没有动作,将节点删除(延迟删除和立即删除)
if (element->actions->num == 0)
{
if (_currentTarget == element)
{
_currentTargetSalvaged = true;
}
else
{
deleteHashElement(element);
}
}
}

暂停动作

void ActionManager::pauseTarget(Node *target)
{
tHashElement *element = nullptr;
HASH_FIND_PTR(_targets, &target, element);//找到目标节点返回element
if (element)//标记暂停
{
element->paused = true;
}
}

恢复动作

void ActionManager::resumeTarget(Node *target)
{
tHashElement *element = nullptr;
HASH_FIND_PTR(_targets, &target, element);//找到目标节点返回element
if (element) //标记不暂停
{
element->paused = false;
}
}

暂停所有的动作

Vector<Node*> ActionManager::pauseAllRunningActions()
{
Vector<Node*> idsWithActions; for (tHashElement *element=_targets; element != nullptr; element = (tHashElement *)element->hh.next) //遍历target链表,paused是true就修改为false
{
if (! element->paused)
{
element->paused = true;
idsWithActions.pushBack(element->target);
}
} return idsWithActions;
}

恢复所有的动作

void ActionManager::resumeTargets(const Vector<Node*>& targetsToResume)
{
for(const auto &node : targetsToResume)//遍历所有的target,将其中的所有pause设为false
{
this->resumeTarget(node);
}
}

添加动作

void ActionManager::addAction(Action *action, Node *target, bool paused)
{
CCASSERT(action != nullptr, "action can't be nullptr!");
CCASSERT(target != nullptr, "target can't be nullptr!");
if(action == nullptr || target == nullptr)
return; tHashElement *element = nullptr;
// we should convert it to Ref*, because we save it as Ref*
Ref *tmp = target;
//在target列表中查找需要的target,返回element
HASH_FIND_PTR(_targets, &tmp, element);
//如果没查到,就新建一个element 并且初始化
if (! element)
{
element = (tHashElement*)calloc(sizeof(*element), 1);
element->paused = paused;
target->retain();
element->target = target;
HASH_ADD_PTR(_targets, target, element); //创建新的表头
} actionAllocWithHashElement(element); //target创建存储动作的列表,如果放不下了,让空间加倍 CCASSERT(! ccArrayContainsObject(element->actions, action), "action already be added!");//判断是否已经加入列表中
ccArrayAppendObject(element->actions, action);//将action加入actions数组中 action->startWithTarget(target); //(重新)设定action的target
}

移除所有的动作

void ActionManager::removeAllActions()
{
for (tHashElement *element = _targets; element != nullptr; )//清空所有的target的动作
{
auto target = element->target;
element = (tHashElement*)element->hh.next;
removeAllActionsFromTarget(target);
}
}

移除target中的所有动作

void ActionManager::removeAllActionsFromTarget(Node *target)
{
// explicit null handling
if (target == nullptr)
{
return;
}
//在target列表中查找目标target
tHashElement *element = nullptr;
HASH_FIND_PTR(_targets, &target, element);
//找到目标
if (element)
{
//并且当前的action在action列表中,并且没有被标记为Salvaged
//salvaged标记的意义是,当前动作需要被释放,但它还没有完成,需要完成之后在释放,所以先做标记,之后动作完成之后释放,类似于延迟release
if (ccArrayContainsObject(element->actions, element->currentAction) && (! element->currentActionSalvaged))
{
element->currentAction->retain();
element->currentActionSalvaged = true;
} ccArrayRemoveAllObjects(element->actions);//移除目标节点的所有动作
/*
while (arr->num > 0)
{
(arr->arr[--arr->num])->release();
}
*/
//当前节点的动作都被移除了,节点没有作用了,之后也会被删除
if (_currentTarget == element)
{
_currentTargetSalvaged = true; //当前正在运行,之后移除
}
else
{
deleteHashElement(element); //不在运行,立即release
}
}
}

移除动作

void ActionManager::removeAction(Action *action)
{
// explicit null handling
if (action == nullptr)
{
return;
} tHashElement *element = nullptr;
Ref *target = action->getOriginalTarget();//获取和动作绑定的节点
HASH_FIND_PTR(_targets, &target, element);
//在actions数组中查找需要移除的action,找到就移除
if (element)
{
auto i = ccArrayGetIndexOfObject(element->actions, action);
if (i != CC_INVALID_INDEX)
{
removeActionAtIndex(i, element);
}
}
}

通过Tag移除Action

void ActionManager::removeActionByTag(int tag, Node *target)
{
CCASSERT(tag != Action::INVALID_TAG, "Invalid tag value!");
CCASSERT(target != nullptr, "target can't be nullptr!");
if (target == nullptr)
{
return;
} tHashElement *element = nullptr;
//在target列表中查找需要的target,返回element+
HASH_FIND_PTR(_targets, &target, element);
if (element)
{
auto limit = element->actions->num; //获取action的个数
for (int i = 0; i < limit; ++i) //遍历action,找到对应的tag即删除action
{
Action *action = static_cast<Action*>(element->actions->arr[i]); if (action->getTag() == (int)tag && action->getOriginalTarget() == target)
{
removeActionAtIndex(i, element);
break;
}
}
}
}

通过Tag移除所有的action

void ActionManager::removeAllActionsByTag(int tag, Node *target)
{
CCASSERT(tag != Action::INVALID_TAG, "Invalid tag value!");
CCASSERT(target != nullptr, "target can't be nullptr!");
if (target == nullptr)
{
return;
} tHashElement *element = nullptr;
HASH_FIND_PTR(_targets, &target, element); if (element)
{
auto limit = element->actions->num;
for (int i = 0; i < limit;) //遍历动作列表,删除所有与需要删除的tag匹配的action
{
Action *action = static_cast<Action*>(element->actions->arr[i]); if (action->getTag() == (int)tag && action->getOriginalTarget() == target)
{
removeActionAtIndex(i, element);
--limit;
}
else
{
++i;
}
}
}
}

获取指定target下的action个数

ssize_t ActionManager::getNumberOfRunningActionsInTarget(const Node *target) const
{
tHashElement *element = nullptr;
HASH_FIND_PTR(_targets, &target, element);
if (element)
{
return element->actions ? element->actions->num : 0;
} return 0;
}

获取指定target下的指定tag的action的个数

size_t ActionManager::getNumberOfRunningActionsInTargetByTag(const Node *target,
int tag)
{
CCASSERT(tag != Action::INVALID_TAG, "Invalid tag value!"); tHashElement *element = nullptr;
HASH_FIND_PTR(_targets, &target, element); if(!element || !element->actions)
return 0; int count = 0;
auto limit = element->actions->num;
for(int i = 0; i < limit; ++i) //循环action列表,tag符合则计数器+1
{
auto action = static_cast<Action*>(element->actions->arr[i]);
if(action->getTag() == tag)
++count;
} return count;
}

获取所有在actions列表中的action个数

ssize_t ActionManager::getNumberOfRunningActions() const
{
ssize_t count = 0;
struct _hashElement* element = nullptr;
struct _hashElement* tmp = nullptr;
HASH_ITER(hh, _targets, element, tmp)//循环target列表
{
count += (element->actions ? element->actions->num : 0);
}
return count;
}

!ActionManager的刷新函数

void ActionManager::update(float dt)
{
//遍历targets
for (tHashElement *elt = _targets; elt != nullptr; )
{
_currentTarget = elt; //将当前遍历到的target保存为currentTarget
_currentTargetSalvaged = false;//将延迟删除标记设为false if (! _currentTarget->paused) //如果不是暂停状态
{
// The 'actions' MutableArray may change while inside this loop.
//遍历当前节点的所有action
for (_currentTarget->actionIndex = 0; _currentTarget->actionIndex < _currentTarget->actions->num;
_currentTarget->actionIndex++)
{
//获取当前action
_currentTarget->currentAction = static_cast<Action*>(_currentTarget->actions->arr[_currentTarget->actionIndex]);
//如果当前节点的当前动作是空,则遍历下一个action
if (_currentTarget->currentAction == nullptr)
{
continue;
}
//先设置释放标记为false
_currentTarget->currentActionSalvaged = false;
//刷新当前动作的进程(根据经过时间和设置的时间判断动作需要执行多少)
_currentTarget->currentAction->step(dt);
//如果被标记为需要释放则释放(正常不会)
if (_currentTarget->currentActionSalvaged)
{
// The currentAction told the node to remove it. To prevent the action from
// accidentally deallocating itself before finishing its step, we retained
// it. Now that step is done, it's safe to release it.
//对应上面延时删除时的retain
_currentTarget->currentAction->release();
} else
//否则判断是否已经完成 ,完成则停止动作
if (_currentTarget->currentAction->isDone())
{
_currentTarget->currentAction->stop();
//获取当前action对象
Action *action = _currentTarget->currentAction;
// Make currentAction nil to prevent removeAction from salvaging it.
//将当前动作变量设置为空
_currentTarget->currentAction = nullptr;
removeAction(action);//释放临时变量中已经完成的action
}
//初始化currentAction变量
_currentTarget->currentAction = nullptr;
}
} // elt, at this moment, is still valid
// so it is safe to ask this here (issue #490)
//取得下一个target
elt = (tHashElement*)(elt->hh.next); // only delete currentTarget if no actions were scheduled during the cycle (issue #481)
//如果当前target被标记为需要释放,并且当前节点的动作列表为空,则立即释放当前节点
if (_currentTargetSalvaged && _currentTarget->actions->num == 0)
{
deleteHashElement(_currentTarget);
}
//if some node reference 'target', it's reference count >= 2 (issues #14050)
//否则判断其引用标记是否为1,是则释放当前节点
else if (_currentTarget->target->getReferenceCount() == 1)
{
deleteHashElement(_currentTarget);
}
} // issue #635
//遍历结束,哈希表中的当前节点指针设置为空
_currentTarget = nullptr;
}

1、actionAllocationWithHashElement方法用来给element的动作容器第一次创建空间,并且会判断当前的actions数组能不能放得下了,放不下了会扩容,容量加倍

2、removeActionAtIndex中会通过index移除对应的action,如果移除的这个action是当前正在使用的,会retain住,并且设Salvaged为true,但还是会从actions数组中移除这个action。如果element中的actions容器也空了,并且这个element是正在使用的,会给它的salvaged设为true,在update的时候再删除(延时删除)。

3、startWithTarget主要是设置执行action的target,但会根据不同的action再执行更多的操作,比如rotateBy的startWithTarget调用了actionInterval的startWithTarget

(actionInterval的startWithTarget又执行了①FiniteTimeAction的startWithTarget,而FiniteTimeAction没有重写这个方法,实际调用的是Action的这个方法,用来设置执行aciton的target,当调用stop的时候会将target设为空。②设置了执行的时间elapsed为0。③设置了第一次执行的标记firstTick。④设置了执行完成标记done为false),

然后又设置了旋转x和y的角度。

4、removeAllAction中遍历了ActionManager中的targets来对每个元素都执行removeAllActionsFromTarget方法,从这里我们可以看到所有绑定了action的target都被放在了targets中,大概可以看出来ActionManager的部分结构。再接着往下看removeAllActionsFromTarget,在这个方法中先查找targets中是否存在要移除action的那个target;然后又判断了要移除的actions中有没有正在执行的action,如果有就ratain住不让它被删除,然后标记为待删除,延时删除,这里猜测在update中可能会有对标记为待删除的对象统一处理的代码,后面再看;然后对action容器中所有对象都release,除了上面retain的没有被删除外,都被删除了;最后判断了target是不是也正在使用,如果是,就标记为延时删除,否则release删除掉,因为actions如果已经空了,那target在ActionManager中存在就没有意义了,如果有正在执行的action,这里肯定是正在执行动作的target,延时删除它。

5、遍历targets中所有的元素(target) ,初始化延时标记为false,如果没有被暂停,遍历当前target的actions容器;

先获取当前正在执行的动作,判断是否存在,判断是不是空的,如果不是空,会将actionSalvaged置为false,然后执行action的step,刷新动作的进程;

下面判断了动作的salvaged,一般是false,只有在多线程的时候才可能会在这句代码之前执行了remove的操作,可能会让actionSalvaged变成true;

然后判断了动作是否执行完成了,因为有些动作是持续执行一段时间的,如果执行完了会在step中将done置为true;动作执行完了就执行stop方法,将action的_target设为空,并且移除了这个action;

将currentAction置为空;

往后递增;

下面判断了几个数组是不是空的,是就移除;

最后将currentTarget置为空;

ActionManager刷新方法中需要注意的是actionSalvaged标记,这个是安全锁,在update中我们可以看到它先被置为false,然后才判断是不是true,正常来说是不会执行这一步if的,但是如果在step中,也就是action正在执行的时候,异步线程执行了remove操作,就会在step的下面对执行完的action进行remove,保证的线程的安全。正常来说,如果要移除action,在removeActionAtIndex或者removeAllActions中是可以直接移除的。

如果这样写

断点之后会发现,在执行remove的时候currentTarget和currenAction都是空的,因为remove是在两次update中间执行的。

最后我们会发现ActionManager的结构是这样的:

从零开始のcocos2dx生活(四)ActionManager的更多相关文章

  1. 从零开始のcocos2dx生活(七)ParticleSystem

    CCParticleSystem是用来设置粒子效果的类 1.粒子分为两种模式:重力模式 和 半径模式 重力模式独占属性: gravity 重力方向,Vec2类型,可以分别指定不同方向的重力大小 spe ...

  2. 从零开始のcocos2dx生活(二)Node

    节点 Node 文章目录 节点 Node 前言 变量初始化 创建一个节点对象 获取节点依赖的计数器 获取节点的描述(获取节点的Tag) 节点的局部层顺序值(LocalZOrder) 设置节点的Loca ...

  3. 从零开始のcocos2dx生活(十一)TableView

    目录 简述 主要变量 主要方法 setVerticalFillOrder reloadData cellAtIndex updateCellAtIndex insertCellAtIndex remo ...

  4. 从零开始のcocos2dx生活(十)ScrollView

    目录 简介 基础变量 ScrollViewDelegate Direction _dragging _container _touchMoved _bounceable _touchLength 方法 ...

  5. 从零开始のcocos2dx生活(九)CCBReader

    NodeLoaderLibrary是用来存储节点加载器类型的类,通过registerDefaultNodeLoaders()可以注册所有默认类型的加载器 在CocosBuilder的使用手册中: 1. ...

  6. 从零开始のcocos2dx生活(八)ParticleSystemQuad

    https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/#_1 写的真的非常好-最近没时间拜读,只看 ...

  7. 从零开始のcocos2dx生活(六)EventDispatcher

    EventDispatcher可能是所有的里面比较不容易理解也不容易看的 我说自己的理解可能会误导到你们-[索了你们看不下去>< 我写了几乎所有的代码的注释,有的是废话跳过就好 主要的代码 ...

  8. 从零开始のcocos2dx生活(一)内存管理

    cocos中所有的对象都是继承自Ref基类,Ref的职责就是对对象进行引用计数管理 内存管理中最重要的是三个方法retain().release().autorelease() 在cocos中创建对象 ...

  9. 从零开始のcocos2dx生活(五)ActionEase

    文章目录 sineEaseIn sineEaseOut sineEaseInOut expoEaseIn expoEaseOut expoEaseInOut easeIn easeOut easeIn ...

随机推荐

  1. Python数据集变量及相关含义

  2. NOIP2007 树网的核 && [BZOJ2282][Sdoi2011]消防

    NOIP2007 树网的核 树的直径的最长性是一个很有用的概念,可能对一些题都帮助. 树的直径给定一棵树,树中每条边都有一个权值,树中两点之间的距离定义为连接两点的路径边权之和.树中最远的两个节点之间 ...

  3. 如何减少idea的内存消耗

    如何减少idea的内存消耗 标签: idea 内存 内存泄露 异常 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/u014028392/articl ...

  4. 【hiho一下 第147周】小Hi的烦恼

    [题目链接]:http://hihocoder.com/contest/hiho147/problem/1 [题意] [题解] bitset的应用; 设better[j][i]表示第j个科目排名为1. ...

  5. H3C IP 地址格式和表示方法

  6. pytorch入坑一 | Tensor及其基本操作

    由于之前的草稿都没了,现在只有重写…. 我好痛苦 本章只是对pytorch的常规操作进行一个总结,大家看过有脑子里有印象就好,知道有这么个东西,需要的时候可以再去详细的看,另外也还是需要在实战中多运用 ...

  7. 2018-8-10-win10-uwp-自定义控件初始化

    title author date CreateTime categories win10 uwp 自定义控件初始化 lindexi 2018-08-10 19:16:50 +0800 2018-2- ...

  8. JavaScript中Number数字数值浮点运算有误差

    JavaScript浮点运算的一个bug. 比如:7*0.8 JavaScript算出来就是:5.6000000000000005 //调用:numberExcept(arg1,arg2) //返回值 ...

  9. MySQL高级配置

    参考文章:http://www.jb51.net/article/47419.htm https://blog.csdn.net/waneto2008/article/details/52502208 ...

  10. [转]Redis和Memcache区别,优缺点对比

    1. Redis和Memcache都是将数据存放在内存中,都是内存数据库.不过memcache还可用于缓存其他东西,例如图片.视频等等. 2.Redis不仅仅支持简单的k/v类型的数据,同时还提供li ...