removeChild on node when action is still running

removeChild on node when action is still running
0.0 0

#1

Hi there,

I have following issue. I have (in general) two nodes: 1 and 2. Every node has running actions. When node 1 actions are finished I remove node 2 (node 2 actions are still running). Node 2 is removed, but destructor is not called (refCount > 0). However, when node 2 actions are finished and after that node 1 calls to remove node 2, destructor of the node 2 is called.

Any idea? I tried stopAllActions, removeAllActionsFromTarget before removing node 2, but without success.


#2

When runAction is called ref count of the node is incremented. When node 2 with running actions is removed, those actions are not stopped/removed. It looks like actions are in some “suspended” mode. They are not finishing so ref count can’t be decremented and node is not removed from the memory. Am I right? Is there any solution for that?


#3

removeFromParentAndCleanup()


#4

Thanks. removeChild(node) does the same and the result is also the same. The workaround is to speed up node 2 actions so they end before node 1 actions end.


#5

I will try to create a simpler testcase (to make sure that my own code does not break something).


#6

It’s not technically the same. :slight_smile:


#7

Hmm… :wink: It leads to the same result. “parent” node is the same in both cases, so removeChild is called by the same node.


#8

removeChild() doesn’t cleanup :slight_smile:


#9

Are you sure? :wink:

@param cleanup True if all running actions and callbacks on the child node will be cleanup, false otherwise.
virtual void removeChild(Node* child, bool cleanup = true);


#10

But, I made some tests and it looks like it is issue with my code (yep, I am not so surprised :P) - somewhere my node gets additional ref count (too many things at the same time are happening… diging it ;).


#11

I will surely look! I haven’t in a while. Perhaps the API has changed. I should look at my own docs.


#12

Ok, I think found the root cause and the solution :wink:

I am working on some general solution. Shortly, I keep actions in vector. Sometime later, I run all the stored actions (with Spawn) on some node. Everything is fine and works well when actions are finished - removeChild cleans all and destructors are called. However, when actions were not finished they weren’t really removed. The problem was that actions were still referenced in the “vector”. When I clear the vector actions are removed properly (their ref count decreased) and then node may be removed properly because nothing refers to it. Uff… I even have a test case for that :stuck_out_tongue:

ps. Sometimes I prefer pure new/delete :stuck_out_tongue:


#13

Interesting, post your code and I am happy to test too.


#14
#ifndef __TEST_ROOT_H__
#define __TEST_ROOT_H__

#include "cocos2d.h"

#include "TestNodeListener.h"

class TestRoot : public cocos2d::Node, public TestNodeListener
{
public:

    virtual bool init();  

    CREATE_FUNC(TestRoot);

    virtual void onAnimationStart(TestNode*) override;
    virtual void onAnimationEnd(TestNode*) override;

    void addNode1(TestNode* node);
    void addNode2(TestNode* node);

protected:

    TestNode* _node1;
    TestNode* _node2;
};

#endif // __TEST_ROOT_H__

#include "TestRoot.h"

#include "TestNode.h"

USING_NS_CC;

bool TestRoot::init()
{
if (!Node::init())
{
    return false;
}

_node1 = nullptr;
_node2 = nullptr;

return true;
}

void TestRoot::onAnimationStart(TestNode* node)
{
if (node == _node1)
    CCLOG(">>>>>>>>>>>>>>>>>>>>>> Animation started (node 1).");

if (node == _node2)
    CCLOG(">>>>>>>>>>>>>>>>>>>>>> Animation started (node 2).");
}

void TestRoot::onAnimationEnd(TestNode* node)
{
if (node == _node1)
{
    CCLOG(">>>>>>>>>>>>>>>>>>>>>> Animation finished (node 1).");

    // _actions MUST be cleared. Otherwise action is sill referenced and it
    // keep the reference to the _node.
    _node2->clearActions();

    removeChild(_node2);
    _node2 = nullptr;

    //removeChild(_node1);
    //_node1 = nullptr;
}

if (node == _node2)
{
    CCLOG(">>>>>>>>>>>>>>>>>>>>>> Animation finished (node 2).");
    removeChild(_node1);
    _node1 = nullptr;
}
}

void TestRoot::addNode1(TestNode* node)
{
_node1 = node;
addChild(_node1);
}

void TestRoot::addNode2(TestNode* node)
{
_node2 = node;
addChild(_node2);
}


#ifndef __TEST_NODE_LISTENER_H__
#define __TEST_NODE_LISTENER_H__

#include "cocos2d.h"

class TestNode;

class TestNodeListener
{
public:

/** Called just after animation has been started. */
virtual void onAnimationStart(TestNode*) = 0;

/** Called just after animation has been ended. */
virtual void onAnimationEnd(TestNode*) = 0;
};

#endif // __TEST_NODE_LISTENER_H__

#ifndef __TEST_NODE_H__
#define __TEST_NODE_H__

#include "cocos2d.h"

class TestNodeListener;

class TestNode : public cocos2d::Node
{
public:

virtual ~TestNode();

virtual bool init();  

CREATE_FUNC(TestNode);

void setListener(TestNodeListener* listener) { _listener = listener; }
void startAnimation(float time);

void clearActions() { _actions.clear(); }

protected:

void animationCallback();

TestNodeListener* _listener;

cocos2d::Vector<cocos2d::FiniteTimeAction*> _actions;

};

#endif // __TEST_NODE_H__


#include "TestNode.h"

#include "TestNodeListener.h"

USING_NS_CC;

bool TestNode::init()
{
if (!Node::init())
{
    return false;
}

_listener = nullptr;

return true;
}

TestNode::~TestNode()
{
CCLOG("Test node destructor!");
}

void TestNode::startAnimation(float time)
{
if (_listener != nullptr)
    _listener->onAnimationStart(this);

auto delay = DelayTime::create(time);
auto targetAction = TargetedAction::create(
    this,
    delay);

/*
auto delay = DelayTime::create(time);
auto callback = CallFunc::create(CC_CALLBACK_0(TestNode::animationCallback, this));
auto actionReady = Sequence::create(delay, callback, nullptr);
runAction(actionReady);
*/

_actions.pushBack(targetAction);

Spawn* simultaneousActions = Spawn::create(_actions);
auto callback = CallFunc::create(CC_CALLBACK_0(TestNode::animationCallback, this));
auto actionDelay = DelayTime::create(0.0f);
Sequence* actionsPlusCallback = Sequence::create(actionDelay, simultaneousActions, callback, nullptr);
runAction(actionsPlusCallback);
}

void TestNode::animationCallback()
{
if (_listener != nullptr)
    _listener->onAnimationEnd(this);
}

#15

TestNode.h/cpp, TestRoot.h/cpp, TestNodeListener.h

And here is how you call it:

TestRoot* root = TestRoot::create();
_controller->getScene()->addChild(root);

TestNode* node1 = TestNode::create();
TestNode* node2 = TestNode::create();

root->addNode1(node1);
root->addNode2(node2);

node1->setListener(root);
node2->setListener(root);

node1->startAnimation(0.3f);
node2->startAnimation(0.5f);

#16

If you don’t call “node->clearActions();”, which clears _actions node destructor won’t be called.

So it works similar to java gc.