Сбой приложения с объектами, созданными в цикле с использованием Luabind в C++

Я пытаюсь использовать Lua с моим прототипом игрового движка, но я застрял со странной ошибкой.

Моя цель - создавать объекты X в цикле с Lua и отображать их.

sprite = Sprite("icon.jpg", 300, 300, 0)
sprite2 = Sprite("icon.jpg", 100, 100, 0)

b1 = BoxObject(sprite)
b2 = BoxObject(sprite2)

sprite3 = Sprite("circle.png", 200, 100, 0)
sprite4 = Sprite("circle.png", 300, 100, 0)

b3 = CircleObject(sprite3)
b4 = CircleObject(sprite4)

n = Node()
n:AddChild(b1)
n:AddChild(b2)
n:AddChild(b3)
n:AddChild(b4)

for i = 0, 10, 1 do 
    x = math.random(700)
    y = math.random(500)
    n:AddChild(BoxObject(Sprite("icon.jpg", x, y, 0)))
end

for i = 0, 10, 1 do 
    x = math.random(700)
    y = math.random(500)
    local s = Sprite("circle.png", x, y, 0)
    local o = CircleObject(s)
    n:AddChild(BoxObject)
end

Если я сделаю так, код работает без ошибок, но игра вылетает в случайное время от мгновения до нескольких секунд. Если я использую этот код только с объектами, созданными в циклах, а не с созданными вручную, игра мгновенно вылетает.

Однако, если я пишу код Lua, эквивалентный C++, он работает без проблем.

for(int i = 0; i < 20; i++){
    float x = rand() % 700;
    float y = rand() % 500;

    n->AddChild(new BoxObject(new Sprite("icon.jpg", x, y)));
}

for(int i = 0; i < 20; i++){
    float x = rand() % 700;
    float y = rand() % 500;

    n->AddChild(new CircleObject(new Sprite("circle.png", x, y)));
}

Это моя привязка Lua

static void Lua(lua_State *lua){
    luabind::module(lua)
    [
     luabind::class_<Node>("Node")
     .def(luabind::constructor<>())
     .def(luabind::constructor<float, float, float>())
     .def("Render", &Node::Render)
     .def("Move", &Node::Move)
     .def("Rotate", &Node::Rotate)
     .def("AddChild", &Node::AddChild)
     .def("RotateAroundPoint", &Node::RotateAroundPoint)
     ];
}

Каждый метод принимает и возвращает void, кроме AddChild

virtual void AddChild(Node *child);

Объекты Sprite и Box и Circle наследуются от класса Node.

Кто-нибудь знает, что может вызвать эту странную ошибку? Буду рад любой помощи.


person Adam Tomeček    schedule 04.08.2012    source источник
comment
Похоже, что объекты, созданные в циклах, действуют как локальные переменные. Если я меняю, например, спрайт3 на локальный спрайт3, через некоторое время он так же вылетает. Так что, возможно, это просто мое непонимание Lua.   -  person Adam Tomeček    schedule 04.08.2012
comment
n:AddChild(BoxObject), не является ли BoxObject функцией?   -  person Q2Ftb3k    schedule 05.08.2012
comment
Нет, BoxObject — это класс, унаследованный от Node (так же, как Sprite) для обработки объектов Box2D, и Sprite передается ему через конструктор как его графическое представление.   -  person Adam Tomeček    schedule 05.08.2012


Ответы (1)


У вас серьезные проблемы с собственностью.

В C/C++ фрагмент кода «владеет» указателем или другим ресурсом, если этот код или объект берет на себя ответственность за его уничтожение. Он сохраняет его живым, пока он нужен, и гарантирует, что он будет уничтожен, когда он перестанет его использовать.

Поддержание понимания того, кому принадлежит то, что жизненно важно для любой даже слегка сложной программы на C или C++.

Когда Luabind создает объект C++ из Lua, сценарий Lua владеет этим объектом. Это означает, что сборщик мусора Lua сам решит, когда его удалить.

Итак, это:

local s = Sprite("circle.png", x, y, 0)

Создаст объект Sprite, время жизни которого определяется состоянием Lua. Когда в состоянии Lua больше нет активных ссылок на него, состояние Lua сможет удалить его.

Я не знаю, как работает ваш код, но эта строка:

local o = CircleObject(s)

Предполагается, что конструктор CircleObject должен претендовать на владение переданным ему объектом (таким образом, CircleObject решает, когда его удалить). Если это правда, то вам нужно указать это, когда вы связываете этот конструктор с Luabind.

Если конструктор CircleObject не должен брать на себя ответственность, то... ну, откровенно говоря, ваш дизайн вызывает подозрения; долгосрочное хранение указателя, которым вы не владеете, просто умоляет что-то испортить. Но если действительно предполагается хранить указатель, принадлежащий кому-то другому, то вам нужно использовать механизмы Lua, чтобы гарантировать, что Sprite останется живым, пока CircleObject существует.

Точно так же функция Node::AddChild выглядит так, будто заявляет права собственности на любой узел, который ей дан. Это будет сделано следующим образом:

.def("AddChild", &Node::AddChild, adopt(_1))

_1 означает первый параметр функции. Это означает, что Node претендует на владение первым параметром; если он принадлежит Lua, эта цепочка владения теперь разорвана, и C++ теперь владеет объектом.

Лично я бы сказал, что этот API... не подходит для Lua. Кажется, вы хотите, чтобы все второстепенные права собственности на объекты выполнялись в коде C++. Таким образом, Lua должен вызывать функции C++, которые устанавливают все эти второстепенные объекты без вмешательства Lua. Lua должен вызвать функцию-член NodeManager, чтобы получить/создать Node, затем он должен вызвать функцию, которая создаст Sprite внутри этого Node.

Lua не должен иметь дело с мелочами BoxObject и тому подобного. Ему не нужно напрямую создавать объект Sprite и помещать его в Node.

Кроме того, опять же лично, этот API не годится и для C++. Если Sprite нужно хранить в каком-то контейнере, а этот контейнер нужно хранить в каком-то Node, то не должно быть возможности создать Sprite без сохранения его в контейнере. Вот для чего нужны фабричные функции. Я даже не уверен, для чего нужны BoxObject и CircleObject, так как Sprite, кажется, делает всю работу.

person Nicol Bolas    schedule 04.08.2012
comment
Я столкнулся с похожей проблемой — еще один способ решить проблемы владения — использовать интеллектуальные указатели. Рассмотрим shared_ptr boost или эквивалент С++ 11. Использование общих ptr не позволит сборщику мусора lua освободить объект в неподходящее время. Документы luabind охватывают изменения в вашем коде, необходимые для того, чтобы это работало. - person yurbles; 05.08.2012
comment
Спасибо за отличный ответ. Я думал, что это должна быть проблема владения между Lua и C++. Эти локальные переменные в сценарии Lua были просто моими тестами, которые я забыл удалить. Я только что попытался добавить accept(_1) к своим привязкам Lua, но компилятор кричит, что не знает принятия. И для моего C++ API. Sprite не нужно хранить в дочернем списке Node. С ним можно работать отдельно (он наследуется от Node, так что у него тоже могут быть свои дочерние элементы), но я использую один основной Node для упрощения рендеринга. Лучше вызывать Node::Render(), чем вызывать его для каждого спрайта. А BoxObject и CircleObject — это классы для объектов Box2D. - person Adam Tomeček; 05.08.2012
comment
Хорошо, я пропустил некоторые заголовки Luabind, но теперь он генерирует ошибки с Luabind accept_policy. Но я подумаю об использовании менеджеров для создания объектов. - person Adam Tomeček; 05.08.2012