Sign in to follow this  

Exposing multiple objects to Lua from C++

This topic is 1185 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I've been scouring the web all day and I can't figure this out.

 

1) does the below code make sense?

2) how would I go about extending it to support multiple exposed classes?

//less relevant functions have been omitted below

static int loadEntity(IN lua_State *L)
{
        //QUESTION: I'm assuming Lua automatically manages memory and it's safe to call
        //lua_newuserdata without worrying about reallocation/freeing. This seems weird,
        //but I haven't found a way around it
	IEntity** ptr = (IEntity**)lua_newuserdata(L, sizeof(IEntity*));
	*ptr = entityptr;
	luaL_getmetatable(L, "IEntity");
	lua_setmetatable(L, -2);

	return 1;
}


static int loadGame(IN lua_State *L)
{
	IGame** ptr = (IGame**)lua_newuserdata(L, sizeof(IGame*));
	*ptr = gameptr;
	luaL_getmetatable(L, "IGame");
	lua_setmetatable(L, -2);

	return 1;
}


static int AddModifier(IN lua_State * L)
{
	IGame* game = NULL;

/////////////////////////////////////////////////////////////////
/////////////////////PROBLEM COMES UP HERE///////////////////////
/////////////////////////////////////////////////////////////////

	game = checkGame(L, 2); //<- doesn't work, runs fine if I comment this out; debugging says IGame isn't even present on the stack
	IEntity* entity = checkEntity(L, 1); //works as expected
	
        ...

	return 1;
}



void luaExposeInterface(IN lua_State * L, IN const luaL_Reg listInterfaceFunctions[], IN char * sName)
{
	luaL_newmetatable(L, sName);
        //expose all the functions in the interface
	luaL_setfuncs(L, listInterfaceFunctions, 0);
	lua_pushvalue(L, -1);
	lua_setfield(L, -1, "__index");
	lua_setglobal(L, sName);
}


int main()
{
	static const luaL_Reg listGameUIFuncs[] = {
			{ "load", loadGame },
                           ...
			{ NULL, NULL }
		};

	static const luaL_Reg listEntityFuncs[] = {
			{ "load", loadEntity },
                            ...
			{ "AddModifier", AddModifier },
			{ NULL, NULL }
		};

//this should expose both IGame and IEntity
	luaExposeInterface(L, listGameUIFuncs, "IGame");
	luaExposeInterface(L, listEntityFuncs, "IEntity");

     ...

//run the script
	lua_getglobal(L, "EntryPoint");
	LUAErr(L, lua_pcall(L, 0, 0, 0));
}

Here's a relevant excerpt from the Lua code:

function EntryPoint()
        //successfully calls loadGame
	local gamestate = IGame:load()
        //sucessfully loads loadEntity
	local entity = IEntity:load()
        
        //fails if I try to access IGame
	entity:AddModifier("name", 1.0, 100)
end

To recap the code - the problem is exposing both IGame and IEntity as separate interfaces within Lua. I'll be wanting to expose more than one entity instance as well.

 

To be frank, I'm probably missing the entire point here and doing it wrong, but I can't really find concrete info on how to achieve what I really want. Which, on the Lua side, would look something like this:

function EntryPoint(entity1, entity2, gamestate)
        ...
end

Since I'm not completely sure how, or in fact what it is that I'm trying to achieve, some direction or references would be nice. I know there are several binding libraries out there, but I'm still very green when it comes to Lua and I wanted to familiarize myself with the library first. Additionally, there are some requirements that I'm not ready to look over: for instance, I've excluded luabind from consideration as I'm not using Boost and do not want to add it to my codebase.

Share this post


Link to post
Share on other sites

I'm sorry to ask, but heaving read your post I still don't understand: what do you want exactly? What do you mean by 'support multiple exposed classes'? Do you mean many instances of the same class or many different classes?

 

As for the question in the code - yes, lua_newuserdata will allocate a bunch of memory for you and it's garbage collected so you don't have to worry about it, if you free all C references (which is the only point of failure and possible leak of memory/object) and all Lua strong references are gone then the memory from lua_newuserdata will be garbage collected and __gc metamethod will be ran, if present. This is explained in great detail in Lua documentation.

 

As for second problem in the code - why do you think IGame would/should be on the stack?

Share this post


Link to post
Share on other sites

I originally tried to write my own implementation for binding C++ to lua to learn the lua C api.  I got it mostly working for simple classes, but quickly gave up and switched to using a library because it would just take too much time to be able to support exporting any class generically with inheritance, constructors, different parameters, return types, etc.  

 

Not to discourage you from continuing to learn the api, but if you decide to give up like me and use a library, I'd suggest LuaBridge: https://github.com/vinniefalco/LuaBridge. It's very lightweight and easy to use. The syntax is similar to Luabind, but it doesn't need boost and it can use your class member functions directly without needing to create a bunch of static wrappers:

getGlobalNamespace(L)
 .beginClass<ClassA>("ClassA")
   .addFunction("Func1", &ClassA::Func1)
 .endClass()

To your actual problem, when you call entity:AddModifier() from lua, lua pushes entity on to the stack (from the entity: part), which is why you could pop it from the stack in C++ with checkEntity(), however as FRex mentioned, the IGame object was never pushed on the stack within the call to entity:addModifier().  You would need to pass it in as a parameter.

Share this post


Link to post
Share on other sites


I'm sorry to ask, but heaving read your post I still don't understand: what do you want exactly? What do you mean by 'support multiple exposed classes'? Do you mean many instances of the same class or many different classes?

 

Both. For starters I want to expose both IGame as kind of a "namespace" to enable access to the game's internal state and IEntity as two or more separate instances to expose the internal state of several different discrete objects.

 

I realize the former can be done with globals, but that wouldn't really solve the problem.

 


As for the question in the code - yes, lua_newuserdata will allocate a bunch of memory for you and it's garbage collected so you don't have to worry about it, if you free all C references (which is the only point of failure and possible leak of memory/object) and all Lua strong references are gone then the memory from lua_newuserdata will be garbage collected and __gc metamethod will be ran, if present. This is explained in great detail in Lua documentation.

 

Yes, I figured as much. I did read the docs as well. What is kind of confusing to me is that I can't reuse the allocated memory. I'm probably running into a simple entry level learning curve here, but it would be more logical to me if I could allocated the memory once (since I'm not storing a full object, but rather a pointer) and then change that pointer as needed while having a sense of transparency in terms of what memory and how much of it is allocated. Right now I'm having trouble seeing when exactly a leak will occur and when it will be collected.

 

Then again, I'm still struggling with the basics and the fact that I can't even explain what I want in detail means that I'm probably not really getting how things should work in the first place.

 


As for second problem in the code - why do you think IGame would/should be on the stack?

 

Ah - my assumption is that after

 

1) exposing IGame's metatable

2) and loading it in response to IGame:load() (which class loadGame())

 

it would be pushed onto the stack much like IEntity is right now. Apparently my assumption is wrong :)

 


I originally tried to write my own implementation for binding C++ to lua to learn the lua C api.  I got it mostly working for simple classes, but quickly gave up and switched to using a library because it would just take too much time to be able to support exporting any class generically with inheritance, constructors, different parameters, return types, etc.  

 

Not to discourage you from continuing to learn the api, but if you decide to give up like me and use a library, I'd suggest LuaBridge: https://github.com/vinniefalco/LuaBridge.

 

Indeed - that's the impression I've gotten as well. I'm probably going to migrate to a library soon enough - I guess my first problem isn't really what the code should look like, though, but rather how it should work in the first place. Thanks for the suggestion!

 


To your actual problem, when you call entity:AddModifier() from lua, lua pushes entity on to the stack (from the entity: part), which is why you could pop it from the stack in C++ with checkEntity(), however as FRex mentioned, the IGame object was never pushed on the stack within the call to entity:addModifier().  You would need to pass it in as a parameter.

 

Hm - how so?

 

As above, doesn't calling IGame:load() (which translates to loadGame() in C++) push it onto the stack. How would I make this work?

 

 

Basically I'm trying to implement a module-based structure whereby the available modules in a Lua script would be along the lines of:

 

Discrete objects, eg

IEvent and IEntity - any number of discrete instances depending on the event

 

Interfaced internal classes, eg

IGame - expose current game state

IUI - expose UI functionality

 

I would love to pass in discrete objects (things like events and entities) as function arguments while exposing interfaced internal classes as global namespaces.

 

Does this make sense or is my logic going down the wrong path?

Share this post


Link to post
Share on other sites

If you want single IGame object, that you call methods on like game.something() then use a global table, with functions each of which has an upvalue with light userdata of your game instance in them.

If you want to be able to extend it easily with pure Lua methods and call both the same way(game:something(), no matter C or Lua method), do it with global full userdata and metatables.

As for the many instances of same class. Just push each as a separate userdata and give each the same metatable named IEntity (one metatable can apply to as many types, full userdata values or tables as you want).

You also CAN reset your pointers (or any values you stored) in full userdata, you probably shouldn't because there is no point really and it'd be weird for user values in Lua to suddenly be different instances.
 
The only way to leak memory (leak in the Lua VM, not leak as in NEVER free before exiting, VM frees all it allocated when it's closed) is to have a C reference that you throw away but forget to release.

The only things that are on Lua stack when your C code gets called are the things you passed to it, plus optional first 'self' argument from the use of : instead of . for function call.
thing:something(10,20,30) is the same as thing.something(thing,10,20,30)
except for implementation details and being evaluated once in the first case.

If you do thing:something(10,20,30) you pass to your something function 4 values: thing, 10, 20 and 30.

There is no way to access locals (debug library doesn't count for doing that, it's not meant for real code) in C function code, if you want to use something, it has to be passed in (and be on the stack), upvalue (and be on the stack under pseudo indices) or globally accessible in Lua or C (and you just lua_getglobal or access them directly as you would any other global).

Edited by FRex

Share this post


Link to post
Share on other sites
As above, doesn't calling IGame:load() (which translates to loadGame() in C++) push it onto the stack. How would I make this work?

 

 

When lua calls a C/C++ function, it pushes all arguments on the stack, but this is not a global stack.  It is a local stack to that C/C++ function, where the first item on the stack is the first parameter. When you use the syntax entity:AddModifier("name", 1.0, 100), it's equivalent to AddModifier( entity, "name", 1.0, 100).  So entity became the first argument.  On the C/C++ side, AddModifier only has visibility to the local stack, so only the 4 parameter values.  You can verify this by writing a simple C++ function to print the stack and then calling it from your C++ function before popping any values from the stack.

 

See http://www.lua.org/pil/26.html for information about the stack

and http://www.lua.org/pil/24.2.3.html for printing the contents of the stack from C/C++

 

FRex beat me to the rest. For the C/C++ AddModifier function to have access to IGame from lua, it needs to be passed as a parameter from lua, an upvalue (similar to static variables), or a lua global.  It seems like you want to use globals, so you could declare your IGame as a global in lua and then access it anywhere you want in C++ with lua_getglobal.  Alternatively, if you only have one instance of IGame in C++ which you have also exposed to lua, then C++ already has access to the instance, so you don't actually need to get it from lua anywhere.

Share this post


Link to post
Share on other sites

When lua calls a C/C++ function, it pushes all arguments on the stack, but this is not a global stack.  It is a local stack to that C/C++ function, where the first item on the stack is the first parameter. When you use the syntax entity:AddModifier("name", 1.0, 100), it's equivalent to AddModifier( entity, "name", 1.0, 100).  So entity became the first argument.  On the C/C++ side, AddModifier only has visibility to the local stack, so only the 4 parameter values.  You can verify this by writing a simple C++ function to print the stack and then calling it from your C++ function before popping any values from the stack.

 

Okay, that is much more clear.

 

The simplest solution would be to simply store sourceentity in this entity (in C++) and expose it via its own set of functions in Lua. Eg, to extend the above example, I would use entity:GetSourceModifier().

 

Or encapsulate it further by exposing it like:

 

event = IEvent:load()

source = IEntity:GetSource(event.GetID()) //uses lookup from a global event list to get the event and pushes the source entity onto the stack

target = IEntity:GetTarget(event.GetID())

 

And also call my regular functions through IEvent, which has access to all of the involved parties:

 

event:AddTargetModifier(...)

val = event:GetSourceModifier(...)

 

Or alternatively:

 

target:AddModifier(...)

source:GetModifier(...)

 

 


You also CAN reset your pointers (or any values you stored) in full userdata, you probably shouldn't because there is no point really and it'd be weird for user values in Lua to suddenly be different instances.

 

I think I'm misunderstanding this. The way I see it a pointer exists inside the userdata value for a script during the script's lifetime (not its execution time) or until it's collected. So if I run a script in response to an action that an entity performs, I want the user data to point to that particular object. I also want to use a single instance of a script for all instances of a particular entity type and I don't want to do any allocations/frees while the game is running. The most reasonable way to accomplish this would be to do it like this:

void IEntity::HandleEvent(IEvent * event)
{
   IScript* script = GetScript(event->action);
   script->Set(event); //set userdata for this particular event instance, which contains everything from the target to the source and the current game state
   script->Run();
}

In short, I can't figure out what IScript::Set() should look like. Right now I'm calling lua_newuserdata() in response to each execution of the script to set the current instance of the entity. Is this the way I should go about it?

Edited by irreversible

Share this post


Link to post
Share on other sites

The way Lua internally works it will always allocate space for something when running.

If you really are worried about allocation taking time there is a way to plug your own memory function to lua_newstate (luaL_newstate has the default malloc/free allocator).

You probably shouldn't, the standard allocators in desktop implementations are fast enough for majority of cases.

How long your Lua value lives (by lives I mean not eligible for GC collection) depends on what scope it's in and a lot of other things.

 

1. Using lua_newuserdata and then letting it get collected is probably good enough for you, unless it shows as being significant at all compared to other code that does real work in your game.

2. Another way to do it is have single entity userdata with metatable already set and just look it up via C ref and change the pointer each time you want to call your script on another entity, it's just one extremely fast (via rawgeti) look up compared to allocating new data, looking up metatable by name and setting it every time.

3. Even better would be to have each of your entities have it's own C ref that points to their userdata with set pointer, metatable and all, possibly lazily initialized. Again it'll make you just call rawgeti once, and do none of the usual: newuserdata, getmetatable, setmetatable stuff so it'll be faster.

 

Both times (both 1. vs 2. and 1. vs 3.) it's: rawgeti (raw access to array element + safety etc. checks) vs. allocation (probably doesn't matter thanks to how efficient lea, ms and other malloc/free libc allocators are..) + metatable look up by name (pretty bad, has to get length of string, hash it, find the duplicate in Lua, then using that hash look up in hash table the right table) + set it (just house keeping, bunch of pointer work, irrelevant).

 

The option 1. is what you have now, 2. is pretty weird but better than 1. if you go for micro optimizations, 3. wastes a couple bytes for one lua userdata value, pointer and int per each entity but it's pretty intuitive for each entity to hold reference to itself on Lua side (and free it when it itself dies on C++ side).

It also allows for having some data on C++ side and some (any) on Lua side (in tables, each entity would get its' own table) on per entity basis via metamethods, but it's much more advanced usage at this point.

Edited by FRex

Share this post


Link to post
Share on other sites

Actually the allocations themselves don't really worry me - the reason I'm bringing it up is that I'm using my own memory manager and it'd be great to have all memory ops confined to one framework. But that's not really crucial. In any case, I've done a little bit more poking around and have come across another problem:

 

 

I'm passing a table from C++ to Lua where each row has the format {string | float} and it seems to be working quite nicely. Except that it crashes the game after repeated use (I'm assuming it's due to the fact that I'm creating the table from scratch each time the function is called):

//In Lua:

activeItem = player:GetActiveItem()

//In C++

static int GetActiveItem(IN lua_State * L)
{
	IEntity* entity = checkEntity(L, 1);

	if(entity->activeItem)
		{
		lua_newtable(L);
		int top = lua_gettop(L);

		for(int i = 0; i < (int)entity->activeItem->modifiers.size(); i++)
			{
			IEntityModifier* m = &entity->activeItem->modifiers[i];

			lua_pushlstring(L, m->sName, STRLEN(m->sName));
			lua_pushnumber(L, m->fValue);
			lua_settable(L, top);
			}
		}

	return 1;
}

I can't yet tell what the best way to modify a table is, but I'll need a way to update the floating point values. A more logical solution would thus be:

static int GetActiveItem(IN lua_State * L)
{
	static bool bTableCreated = false;

	IEntity* entity = checkEntity(L, 1);

	if(entity->activeItem)
		{
		if(!bTableCreated)
			{
			lua_newtable(L);

			int top = lua_gettop(L);

			for(int i = 0; i < (int)entity->activeItem->entity->modifiers.size(); i++)
				{
				IEntityModifier* m = &entity->activeItem->entity->modifiers[i];

				lua_pushlstring(L, m->sName, STRLEN(m->sName) + 1);
				lua_pushnumber(L, m->fValue);
				lua_settable(L, -3/*top*/);
				}
			lua_setglobal(L, "activeItem");
			bTableCreated = true;
			}
		else
			{
			lua_getglobal(L, "activeItem");
//TODO: update fields that have changed
			}
		}

	return 1;
}

Which doesn't seem to work. I get an error that I'm trying to perform arithmetic on a field with a nil value when I try to access a valid entry in the table.

 

Once again, Google is somewhat light on concrete examples on how to go about this...

Share this post


Link to post
Share on other sites

You can (extremely easily) specify a function for memory allocation in lua_newstate.

Why does your first example crash and why is there +1 in your second example? Lua allows strings to have embedded \0s. That's probably your problem, your keys are not "key", they are "key\0" because of your +1.

Also, since it's really hard to guess from small snips of code with no context or way to run them: http://sscce.org/

Share this post


Link to post
Share on other sites

The crash is becoming increasingly more frustrating and is showing all signs of memory corruption. I can't reproduce it in any other way save for with the above code, though.

 

I'll try to reduce the problem to a self-contained sample to tomorrow. Right now, as it is, I have no idea why it crashes, NULL byte included or not... All I can tell is that it seems to be dependent on the number of times the function is run and it only crashes if the a table row contains more than one field.

Share this post


Link to post
Share on other sites

Alright then - my first reaction was that I was dealing with a sneaky buffer overflow somewhere in my code so I wrote a stack verifier for my memory manager and made sure I wasn't trashing any unwanted memory by accident. As it is, I'm not. So I started weeding out as much code as possible to come up with the simplest test case and finally managed to reproduce the problem on a small scale. I'd like to note first:

 

1) the crash is spontaneous - no exceptions are thrown. Depending on something I can't quite understand yet, though, the nature of the crash may either be instantaneous or may pop up a "xxx.exe has stopped working - Windows is checking for solutions" notification. Debugging halts in an unloaded frame. Adding output tracers and a counter to the code produces repeatable results and seems to indicate that the crash happens in lua_pushlstring

2) the number of iterations the script can run depends on what I'm writing into the table - I can't tell if it's the length of the string or not, but my initial suspicion is that Lua causes a segfault internally. I don't know of any way to debug it.

3) when creating the table from scratch each time I call GetActiveItem() from the script, the number of iterations I get out of it before it crashes in my current production code is 63. By changing what I put into the table I can get more iterations out of it, but the fact remains: I'm doing something wrong

4) I can create a whole lot of tables in advance (the below code creates 40000), but even then lua_getglobal() crashes, albeit after more times I run the script

 

The below code won't run when you copy-paste it. I didn't create a new project so I ended up copy-pasting it together - hence it might be missing a basic include file or whatnot.

 

To run it, you'll also have to replace the "lout << ??? << endl;" lines with printf() or whatever output functionality you have available to you and then simply call fnTestLuaTable() from something like main() or WinMain() to run the test. The create all-tables-at-once vs create-table-as-needed approach can be toggled by uncommenting the two lines in GetActiveItem2(). The test is pretty QnD, but it should compile and it behaves on par with what I'm encountering in my main code.

 

Note that I'm using the latest version of LuaJIT.

#include <windows.h>

extern "C" {
#include "../lua/LuaJIT-2.0.3/src/lua.h"
#include "../lua/LuaJIT-2.0.3/src/lauxlib.h"
#include "../lua/LuaJIT-2.0.3/src/lualib.h"
}

struct IEntity;

IEntity* curent;

static int loadEntity(IN lua_State *L)
{
IEntity** udata = (IEntity**)lua_newuserdata(L, sizeof(IEntity*));
*udata = curent;
luaL_getmetatable(L, "IEntity");
lua_setmetatable(L, -2);

return 1;
}

void LUAErr(lua_State* L, int status)
{
	if(status != 0)
		{
		lout << "LUA" << " (" << status << "): " << lua_tostring(L, -1) << std::endl;
		lua_pop(L, 1); // remove error message
		}
}

void luaExposeInterface(IN lua_State * L, IN const luaL_Reg listInterfaceFunctions[], IN char * sName)
{
	luaL_newmetatable(L, sName);
	luaL_setfuncs(L, listInterfaceFunctions, 0);
	lua_pushvalue(L, -1);
	lua_setfield(L, -1, "__index");
	lua_pushvalue(L, -2);
	lua_setfield(L, -2, "__metatable");
	lua_setglobal(L, sName);
}




static int GetActiveItem2(IN lua_State * L)
{
	static bool bTableCreated = false;

	static int ir = 0;

	//output the iteration number
	lout<< ir++ <<endl;

 	if(!bTableCreated)
 		{
//uncomment the next two lines to create a bunch of tables in advance
		//for(int t = 0; t < (int)40000; t++)
			{
			lua_newtable(L);

			for(int i = 0; i < (int)20; i++)
				{
				const char* ptr = "Force";
				lua_pushlstring(L, ptr, STRLEN(ptr));
				lua_pushnumber(L, 8);
				lua_settable(L, -3);
				}
			char sname[1024];
			sprintf_s(sname, "activeItem%d", 1);
			lua_setglobal(L, sname);
			}

		lua_getglobal(L, "activeItem1");
		lout << "create table" << endl;

		//bTableCreated = true;
		}
	else
		{
		lout << "load table" << endl;
		lua_getglobal(L, "activeItem1");
		}

	return 1;
}


static int AddVelocity2(IN lua_State * L)
{
	float fVelocity = (float)luaL_checknumber(L, 2);

	lout << fVelocity << endl;

	return 0;
}



void fnTestLuaTable()
{
	lua_State* L;

	L = lua_open();
	luaopen_base(L);
	luaopen_table(L);
	luaopen_io(L);
	luaopen_string(L);
	luaopen_math(L);
	luaopen_debug(L);
	luaopen_ffi(L);
	luaL_openlibs(L);

	IEntity* entity = NULL;
	curent = entity;

	static const luaL_Reg listEntityFuncs[] = {
 			{ "load", loadEntity },
			{ "GetActiveItem", GetActiveItem2 },
			{ "AddVelocity", AddVelocity2 },
			{ "__gc", NULL},
			{ NULL, NULL }
		};

	luaExposeInterface(L, listEntityFuncs, "IEntity");

	LUAErr(L, luaL_dofile(L, "test2.lua"));

//run it a bunch of times
	for(int i = 0; i < (int)2000; i++)
		{
		lua_getglobal(L, "OnEntityAction");

		lua_pushnumber(L, 0);
		LUAErr(L, lua_pcall(L, 1, 0, 0));
		}
}


int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR lpCmdLine,
                     int nCmdShow)
{
//my own error piping class is initialized here
//errlogAttachStream(&out);
//errlogEnableConsole(true);

fnTestLuaTable();

return 0;
}

And the corresponding Lua file:

function OnEntityAction(iAction)
	player = IEntity:load()
	activeItem2 = player:GetActiveItem()
	
	player:AddVelocity(activeItem2["Force"])
end

I'm hoping the problem is something painfully simple - I'm really having difficulty stripping it down to anything more bare bones than this...

 

The fact that it runs okay for a number of times and then crashes with no warning or even a silent opt-out is worrisome to say the least, though.

Edited by irreversible

Share this post


Link to post
Share on other sites

Bah, indeed - I think I copied it from somewhere:

static void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup)
{
	luaL_checkstack(L, nup + 1, "too many upvalues");
	for(; l->name != NULL; l++)
		{  /* fill the table with given functions */
		int i;
		lua_pushstring(L, l->name);
		for(i = 0; i < nup; i++)  /* copy upvalues to the top */
			lua_pushvalue(L, -(nup + 1));
		lua_pushcclosure(L, l->func, nup);  /* closure with those upvalues */
		lua_settable(L, -(nup + 3));
		}
	lua_pop(L, nup);  /* remove upvalues */
}

Share this post


Link to post
Share on other sites

It crashes deep in the GC collection cycle because of this line:

{ "__gc", NULL},

which makes it try call a NULL pointer as a function. If you want to omit a metamethod then just leave it out of the luaL_Reg array. It also works at first because GC is (by definition) lazy and ran only once in a while so it takes a while for your NULL __gc metamethod to be called.

Share this post


Link to post
Share on other sites

Wow - thanks! Did you get that from the disassembly?

 

Two questions:

 

- if I omit it, will Lua's internal garbage collection be disabled? I'm not so much worried about leaked memory since I intend to pre-load everything anyway and the memory footprint shouldn't really be anything to worry about on PC. Hence, the question is rather - should I keep this in mind as something that needs addressing later?

- truth be told, I haven't had the chance to read up on Lua's garbage collection, but what does the line do anyway? Does it allow me to handle server-side object destruction in response to dynamic allocation? If so, I won't need it anyway as I don't really have the intention of doing any allocations that aren't directly controlled by the engine.

 

As an aside, it seems a bit daft that Lua crashes if it's passed a NULL pointer. More importantly, I'm not sure where I got this, but I'll be more careful to trust code found on the Internet :)

Share this post


Link to post
Share on other sites

I got it from this backtrace from gdb which I got in a couple of gdb commands (literally two commands: run + backtrace)

#0  0x00000000 in ?? ()
#1  0x45bc973f in lj_BC_FUNCC () from /lib/libluajit-5.1.so.2
#2  0x45bd68e9 in gc_call_finalizer () from /lib/libluajit-5.1.so.2
#3  0x45bf1047 in gc_finalize () from /lib/libluajit-5.1.so.2
#4  0x45bf1171 in gc_onestep () from /lib/libluajit-5.1.so.2
#5  0x45bf1633 in lj_gc_step () from /lib/libluajit-5.1.so.2
#6  0x45c0923a in lua_pushlstring () from /lib/libluajit-5.1.so.2
#7  0x0804918a in GetActiveItem2 (L=0xb7fdc1c0) at main.cpp:86
#8  0x45bc973f in lj_BC_FUNCC () from /lib/libluajit-5.1.so.2
#9  0x45c0df05 in lua_pcall () from /lib/libluajit-5.1.so.2
#10 0x08049408 in fnTestLuaTable () at main.cpp:154
#11 0x08049439 in main () at main.cpp:164
 

1. No, __gc is just a finalizer for the object and has nothing to do with when GC is ran, for controlling GC there is lua_gc in C and collectgarbage in Lua.

2. It's just a finalizer, whether or not you need to use it depends on what you are doing exactly.

Share this post


Link to post
Share on other sites

This topic is 1185 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this