1
\$\begingroup\$

I am writing my own small game engine (thin layer based on SDL2) in C++. My base class is Node (contains information on an entity's position, scale, rotation, etc.), And I've got two special subclasses, Layer and Scene.

Node
|_SpriteNode
|_RectNode
|_...
|_Layer
  |_Scene

A layer can have Node children. Each time the loop runs, I call the update(int timeInterval) method on the scene being rendered, and the method loops through the vector<Node*> field containing my children. Layers propagate that call to their children, etc.

This works well until I start adding/removing children during the update. My first implementation of Layer had those two methods:

void Layer::addChild(Node* child)
{
    if(!child) return;
    _children.push_back(child);
}

void Layer::removeChild(Node* child)
{
    _children.erase(child)
}

This is, of course, stupid, since removing a Node from the vector would cause a null pointer error when the update loop ( for (auto child: _chidren) ). Using an iterator instead could fix this, but when a Node calls _parent.removeChild(this), it does not know about the iterator being used for that frame.

The solution I use at the moment is having an addition and a deletion buffer in the Layer, to which new and deleted nodes are added during the frame update. Once the frame has been rendered, the Layer performs the actual insertions and deletions from its children array. It works, and I've made a Game Jam game with this, but is seems more than sketchy to me.

I have looked through Cocos2D-X source code, and they use a simple push_back() or erase() call, which makes me think I'm missing a bigger point, or some trick that prevents the iterator issue. Is there something obvious (or not) that I missed?

Sorry if the question is dumb or missing a point. I hope I documented everything enough :)

\$\endgroup\$
1
\$\begingroup\$

The simplest thing to do is make a copy of the Node * container before iterating over it.

void Layer::update() {
    auto children = _children;
    for(auto child : children) {
        child->update();
    }
}

If you want to avoid this copy, you can defer the additions and removals until after the update loop finishes.

std::vector<Node *> _additions;
std::vector<Node *> _removals;

void Layer::addChild(Node *child) {
    _additions.push_back(child);
}

void Layer::removeChild(Node *child) {
    _removals.push_back(child);
}

void Layer::update() {
    for(auto child : _children) {
        child->update();
    }

    for(auto child : _additions) {
        _children.push_back(child);
    }
    _additions.clear();

    for(auto child : _removals) {
        _children.erase(child);
    }
    _removals.clear();
}

EDIT: It seems I didn't read your question thoroughly enough to realize that you've already tried the deferral method. Keep doing that.

\$\endgroup\$
  • \$\begingroup\$ Will keep doing that then. Thank you very much for the answer! \$\endgroup\$ – amyinorbit Apr 3 '15 at 10:51
0
\$\begingroup\$

As long you are not using threads/multi-core you have nothing to worry about - your code won't be executed "in parallel".

If you do use it, one solution is to implement child's queues for insertion and deletion, and to process them in the same thread - before or after your looping through children.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.