Skip to main content
GameDev.net gamedev.net
🔒 Locked

Luabind: ownership and destruction!

Started by golgoth13 Feb 23, 2009 at 1:39 PM 14 replies 8.4k views
Original Post
golgoth13
golgoth13
Hi everyone, I ve been pulling my air for awhile on this issue, I want a remove ownership destruction from Luabind, and manage my own garbage via C++; The doc says: Adopt: Used to transfer ownership across language boundaries. index The index which should transfer ownership, _N or result Here is what I do:

.cpp

class_<Object>("Object")
.def(constructor<>(), adopt(result))

.lua

myObject = Object();

luabind/src/object_rep.hpp

but when lua_close() is called, it still want to delete its pointer:

	template<class T>
	struct delete_s
	{
		static void apply(void* ptr)
		{
			delete static_cast<T*>(ptr); <<<<<< goes here!
		}
	};
What did I missed? [Edited by - golgoth13 on February 25, 2009 5:04:26 PM]
dw9
dw9
adopt(), and return value policies in general, doesn't work with constructors. Even if it did, adopt(result) in this case would actually mean "transfer ownership from C++ to Lua". A possible workaround would be add a function that releases the ownership:

void release_ownership(Object*){}...class_<Object>("Object")  .def(luabind::constructor<>()),  def("release_ownership", &release_ownership, adopt(_1))


and then wrap Object.__init() in Lua:

local oldinit = Object.__initfunction Object.__init(self)    oldinit(self)    release_ownership(self)end


I'm curious though; how does this work? Who manages the Object instances?
Kambiz
Kambiz
Have you tried using smart pointers? (for example boost::shared_ptr or std::tr1::shared_ptr)
class_<Object,shared_ptr<Object> >("Object")...

As long as you hold a reference in c++ lua shouldn't be able to delete the object.
golgoth13
golgoth13
Hi again,

I ve posted on the Luabind mailing list, but my message is on old for some reason…

Here is a simplify version of my work:
.cpp            module(in_lua)            [                  /// Base class for all classes.                  class_<Object, boost::shared_ptr<Object>>("Object)                  .def(constructor<>())                  ,                  class_<Actor, Object, boost::shared_ptr<Object>>("Actor)                  .def(constructor<>())                  .def("SetGeometry", &Actor::SetGeometry)                  ,                  class_<Cube, Object, boost::shared_ptr<Cube>>("Cube")                  .def(constructor<Float, Float, Float>())            ];.luamyGeo = Cube(1,1,1);myActor = Actor();myActor:SetGeometry(myGeo);App onExit():1 - delete all C++ objects.3 - lua_close().

I m using the boost::shared_ptr because it does not call the C++ object destructors when lua_close() is called.

It all works fine, but my problem occurs when myActor:SetGeometry(myGeo); is present, lua_close() try to delete myGeo when it has already been deleted. And BAM!

I ve also tried

.def("SetGeometry", &Actor::SetGeometry, adopt(_2))

In this case myGeo is not recognized on the C++ side… I m in the dark now… I ve tried a zillion possibilities so far…

Complementary questions:

Is it _>("Cube")
Or
_>("Cube")
?

Do we need to put the space like so?

_ >("Cube")

I ll try the dw9 method... thx again!

[Edited by - golgoth13 on February 24, 2009 10:51:15 AM]
Kambiz
Kambiz
What do you mean by "1 - delete all C++ objects." ? Are you deleting objects wrapped by shared_ptr manually?
golgoth13
golgoth13
yes, every objects is handle by a manager, knows who owns who and is in charge of deleting any orphans on cue or everything when the app exit. so I don’t need Lua to sneak in my garbage. In fact, if we could omit auto garbage collection, I would. I m stunt we have to macro manage this, quite a turn off so far.
Kambiz
Kambiz
shared_ptr will delete the wrapped object automatically, if you have your own memory manager you may also need to write a wrapper class to be used in lua.
EDIT: boost::intrusive_ptr may be useful.

[Edited by - Kambiz on February 24, 2009 11:23:20 AM]
ddn3
ddn3
Your mixing 2 different memory management paradigms, managed and unmanaged memory models . Your going to have to accommodate one or the other, or not share any data between them at all.

Even then, if Luabind is anything like Mluabind, it should allow you to use smart pointers in place of raw pointers to your objects in Lua, as Kambiz suggested, this should solve your memory sharing issues, since counted pointers don't deleted until all instances are destroyed, which you can make sure as long as you hold an instance in C++ Lua can't destroy your object, with the added benefit if you ever want Lua to manage the memory you can do that also.

Good Luck!

-ddn
golgoth13
golgoth13
a wrapper? interesting... but to wrap what? going to all C++ classes and derived them in Lua? I don’t think so... Gaining speed is awesome but almost a bonus in my case… I m trying to removed any glue code and make scripting easier to maintain with Luabind.

I thought Luabind was an interface to communicate directly with c++, no more, no less…

In this case:

myActor:SetGeometry(myGeo);

why would Luabind keep a reference of an object passed as a parameter? it doesn’t make any sense….

why is it intervening in managing memory in the first place? I don’t mind inviting Luabind in my living room, but if I have to change the colors of my bedroom walls to make it happy, I m going to get cranky!

I m investigating intrusive_ptr…

thx for your inputs gentlemen.

Ddn3: Unfortunately MLuabind is not 64 bit friendly so I had to go back to Luabind.
dw9
dw9
I should have mentioned that using a smart pointer that doesn't delete is a simple solution. Maybe something like this:

struct object_ptr{    object_ptr(Object* p)      : p(p)    {}};Object* get_pointer(object_ptr const& x){    return x.p;}object_ptr* get_const_holder(object_ptr*){    return 0;}...class_<Object, object_ptr>("Object")


Of course, in:

myActor:SetGeometry(myGeo);


luabind doesn't create any references to the parameter. I don't know what made you think that. For obvious reasons it does keep a reference to instances created in Lua.
golgoth13
golgoth13
Quote:
Your going to have to accommodate one or the other, or not share any data between them at all.


My choice is pretty clear, but it seems Luabind won’t let go that easy... Where is the magic LUABIND_LEAVE_MY_GARBAGE_ALONE define? Just kidding, I ve been on this Luabind thing for over a week and a half and it still doesn’t work without crashing… this is madness... 

regarding to the intrusive_ptr example... you have to build a pseudo object manager... I don’t want Luabind to have anything to do with this... or worst, spreading Boost features in my C++ Classes... I m working on it, but I m still missing an elegant solution.

So far I think my best guess is to find out why myGeo is not passed correctly on the C++ side:
.cpp            module(in_lua)            [                  /// Base class for all classes.                  class_<Object, boost::shared_ptr<Object> >("Object)                  .def(constructor<>())                  ,                  class_<Actor, Object, boost::shared_ptr<Object> >("Actor)                  .def(constructor<>())                  .def("SetGeometry", &Actor::SetGeometry, adopt(_2))                  ,                  class_<Cube, Object, boost::shared_ptr< Object> >("Cube")                  .def(constructor<Float, Float, Float>())            ];.lua

myGeo = Cube(1,1,1);
myActor = Actor();
myActor:SetGeometry(myGeo);

anything missing here?
golgoth13
golgoth13
Quote:
luabind doesn't create any references to the parameter. I don't know what made you think that.


Mainly because, when I do NOT register object created in lua with my manager, it all works fine, but when myActor:SetGeometry(myGeo); is present, lua_close() crash on delete x:

template inline void checked_delete(T * x)
{
typedef char type_must_be_complete[ sizeof(T)? 1: -1 ];
(void) sizeof(type_must_be_complete);
delete x;
}

it might be deeper somewhere else in my class inheritance or something… but it does crash for an hocus pocus reason.

Thx for your suggestion dw9... I ll dig it…

EDIT: could this crash being cause by a compiler option? Im using VS 2008.
dw9
dw9
Most likely the double delete is caused by you making a mistake. First of all; you can't both use shared_ptr<> and manual memory management. If you hold your instances by shared_ptr<>, it's impossible for luabind to release ownership. If you need to transfer ownership you need to use raw pointers.

It's difficult to help you, because you leave so much of the code out. I still don't have any idea on how your ownership management works. First you indicated that your ownership just happens magically somehow, but now it seems like you manually add your objects to a manager of some sort. Which is it?
golgoth13
golgoth13
my apologies, my engine is becoming fairly complex and it’s hard to summarize sometimes, but I ll try to be more precise,

in the lua file I do this:

myGeo = Cube(1,5,3);
myGeo:Register();

myActor = Actor();
myActor:Register();

myActor:SetGeometry(myGeo);

1 - Register() add a pointer to an std:vector hold by the Manager. The Manager keeps track on pretty much “everything” that is being created… depending on the RTTI of the Object it will also fire functions like Reset(), Update(), Simulate(), Render() for everyone, if needed.
2 - When SetGeometry(myGeo) is called, myActor becomes the owner of myGeo, thus, a myGeo::ownerCount++ is been increamented.

When deleting myGeo, the Manager looks if myGeo::ownerCount > 0, if no, myGeo is being deleted.

When deleting myActor, myActor will Release myGeo like so: myGeo::ownerCount—- and call the Manager to do the dirty job. If it is not own by anyone else, the manager will destroy myGeo.

EDIT: Manager is the only one having the delete call.

All of this is being done on C++ side of course.

Quote:
If you need to transfer ownership you need to use raw pointers


Is this the smart pointer concept you ve mentioned before? I have no real academic formation in programming, sometimes, basic concept/terms are missing to my knowledge.

Hope this help you help me, thx again for your inputs.
golgoth13
golgoth13
I did found this on nables.com:


LUABIND_DECL_NON_DELETABLE(c)
before the registration function we call to instantiate the class_
note that thats our own macro in our fork of luabind. its not in luabind, so you will get an error if you use it.
for reference here is our implementation
#define LuaClassNonDeletable(in_class)namespace luabind{	namespace detail	{		template<>		struct delete_s<in_class>		{			static void apply(void* ptr)			{			}		};		template<>		struct destruct_only_s<in_class>		{			static void apply(void* ptr)			{			}		};	}}


note: Ive removed the backslashes and the asserts.

The asserts detect cases where luabind thinks it owns the pointer. This shouldnt really happen for non deletable types.
For example if luabind copies an object it will take ownership of the new copy. If it ever did this with a non deletable type we would likely have a memory leak.

The asserts detect cases where luabind thinks it owns the pointer. This shouldnt really happen for non deletable types.

For example if luabind copies an object it will take ownership of the new copy. If it ever did this with a non deletable type we would likely have a memory leak.


and cut! that’s all he posted...

Mulder: I knew it, I m not taking crazy pills...
Scully: we still don’t know if this can work.

This piece of evidence is coming from the object_ref.hpp … they are the same but the destruction calls are missing. Clever!

Anyone can make sense of this and figure how the pieces fit together?

[Edited by - golgoth13 on February 25, 2009 11:53:30 AM]
golgoth13
golgoth13
Alrigth I got it, for all the believers, here it is:

.cpp
#define LuaClassNonDeletable(in_class)namespace luabind{namespace detail{template<> struct delete_s<in_class> {static void apply(void* ptr) {}};template<> struct destruct_only_s<in_class> {static void apply(void* ptr){}};}}LuaClassNonDeletable(Object)LuaClassNonDeletable(Cube)LuaClassNonDeletable(Actor)Int Expose_Objects(lua_State* in_lua){            module(in_lua)            [                  /// Base class for all classes.                  class_<Object>("Object")                  .def(constructor<>())                  ,                  class_<Actor, Object>("Actor")                  .def(constructor<>())                  .def("SetGeometry", &Actor::SetGeometry)                  ,                  class_<Cube, Object>("Cube")                  .def(constructor<Float, Float, Float>())            ];		return 0;}

And voila, thx everyone this case is solved!

the truth is out there...

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.