初始化构造函数

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. D - Denouncing Mafia DFS

    这道题其实很简单,求k个到根的链,使得链上的节点的个数尽可能多,如果节点被计算过了,就不能再被计算了,其实我们发现,只要k>=叶子节点,那么肯定是全部,所以我们考虑所有的叶子节点,DFS到根节点 ...

  2. oracle函数 NLS_UPPER(x[,y])

    [功能]返回字符串并将字符串的转换为大写; [参数]x字符型表达式 [参数]Nls_param可选,指定排序的方式(nls_sort=) . SCHINESE_RADICAL_M(部首.笔画) SCH ...

  3. iOS开发那些事-响应内存警告

    好的应用应该在系统内存警告情况下释放一些可以重新创建的资源.在iOS中我们可以在应用程序委托对象.视图控制器以及其它类中获得系统内存警告消息. 1.应用程序委托对象 在应用程序委托对象中接收内存警告消 ...

  4. linux扫盲之CPU模式

    相信研究linux的大大都知道linux有实模式.保护模式.虚拟模式三种. 不多说,拷贝黏贴!  80386开始,cpu有三种工作方式:实模式,保护模式和虚拟8086模式.只有在刚刚启动的时候是rea ...

  5. win10 uwp httpClient 登陆CSDN

    本文告诉大家如何模拟登陆csdn,这个方法可以用于模拟登陆其他网站. HttpClient 使用 Cookie 我们可以使用下面代码让 HttpClient 使用 Cookie ,有了这个才可以保存登 ...

  6. sublime text 3创建新文件插件-AdvanceNewFile

    这里要记录sublime text 3 在创建新文件时安装的插件–AdvanceNewFile ST本来自带的创建新文件的快捷键是ctrl+n.但是用户需要保存时才可修改名称以及文件路径.但是安装完A ...

  7. AUTOSSH设置ssh隧道,实现反向代理访问内网主机

    内网主机上配置: autossh -M -CNR :localhost: ubuntu@123.207.121.121 可以实现将访问主机123.207.121.121的1234端口的数据,通过隧道转 ...

  8. 达观数据CTO纪达麒:小标注数据量下自然语言处理实战经验

    自然语言处理在文本信息抽取.自动审校.智能问答.情感分析等场景下都有非常多的实际应用需求,在人工智能领域里有极为广泛的应用场景.然而在实际工程应用中,最经常面临的挑战是我们往往很难有大量高质量的标注语 ...

  9. H3C PPP MP实现方式

  10. P1089 过独木桥

    题目描述 今年的 CSP-J/S 比赛马上就要开始了,代码决定的 N 位女学生排队去参加比赛. 期间他们遇到了代码决定的 M 位男生组成的男生队伍. 他们堵在了一座独木桥前.但是独木桥每次只能过一个人 ...