More Lua
Well, if you’ve been following this blog, you already know that I use Lua to script everything in the game… it’s a great language, but sometimes it has its quirks…
I spend the last couple of days trying to solve an issue that arose when I lifeshaped exactly 10 imps to life… the 10th one would lead to a crash in the luajit kernel, which is kind of inaccessible to me… as I believed my code was to blame instead of luajit (it’s in use for so many years), and if I changed the order of some operations, the crash would be in a completely different place, so I started hunting for the problem…
Turns out the problem was creating a new coroutine from within a coroutine… On SurgePlayer, I ask the system to create a coroutine (so, at the C++ level). After the coroutine is created, the first iteration of it is called, and this coroutine was creating a new one as well… So far, so good, I’ve done similar things a million times, but it seems that this has a very small chance of having problems (and debugging was a nightmare since one of the consequences was the lost of the stack frame)… So, while I haven’t figured out exactly what the problem was, since the symptoms were very similar to a multithreading issue, I applied the same reasoning to the solution, and prevented the creation of coroutines from inside coroutines… Now, when I create a coroutine, it gets added to a list of “coroutines to be created and run”, and only at the end of the animation pump I actually create them…
This apparently made the problem go away (after extensive testing, nothing guarantees the problem won’t appear again), so my guess was that the previous system was changing data that wasn’t supposed to be changed while running…
Anyway, Lua is still number one, but since I dislike writing binds, I built a tool that analyzes my source code for some tokens and builds the binds itself… This works great, and really reduces my time spend writing binds (and makes them more reliable)…
So, I have pieces of code in the C++ like this:
LUA_OBJ_CALL inline Vec3f get_pos() { return _object3d.get()->pos; } LUA_OBJ_CALL virtual void set_pos(const Vec3f& p) { _object3d.get()->pos=p; } LUA_OBJ_CALL Vec3f get_proj_pos(const Vec3f& offset); LUA_OBJ_CALL inline Vec3f get_dir() { Vec3f ret; _object3d.get()->ori.rotate_point(ret,Vec3f::UNIT_Z); return ret; } LUA_OBJ_CALL inline Vec3f get_up() { Vec3f ret; _object3d.get()->ori.rotate_point(ret,Vec3f::UNIT_Y); return ret; } LUA_OBJ_CALL inline Vec3f get_right() { Vec3f ret; _object3d.get()->ori.rotate_point(ret,Vec3f::UNIT_X); return ret; } LUA_OBJ_CALL void set_dir(const Vec3f& d); LUA_OBJ_CALL inline Quaternionf get_ori() { return _object3d.get()->ori; }
The tool I built (buildffi) detects the “LUA_OBJ_CALL” and writes the corresponding FFI code that bridges the C++/Lua world. It has some other nice tricks built in (for singleton access and debug outputs), but it’s basically this… It does all the translation between the Lua types and my own types (even templated ones, like Vec3f), and in the future it will do the doc generation (through an ancillary file)… I’m also thinking of adding some development time checks to the functions (a kind of developer build of the SurgePlayer), in which the correctness of the parameters will be verified (instead of a straightforward crash if something happens that I ‘m not expecting).
Out of curiosity, the above piece of code generates the following C binds:
LuaVec3 __declspec(dllexport) get_pos(ISurgeObject* SELF_OBJECT) { if (!SELF_OBJECT) { sclog(SCLOG_WARNING,"NULL pointer passed to function get_pos (C:get_pos)!"); return Vec3f_to_Lua(Vec3f::ZERO); } auto obj0=obj_conv<SurgeObject2d>(SELF_OBJECT); if (obj0) return Vec3f_to_Lua(obj0->get_pos()); auto obj1=obj_conv<SurgeObject3d>(SELF_OBJECT); if (obj1) return Vec3f_to_Lua(obj1->get_pos()); if (SELF_OBJECT->get_implementation()) sclog(SCLOG_WARNING,"get_pos (C:get_pos) can't be called on an object of type %s!",SELF_OBJECT->get_entity_type_string()); return Vec3f_to_Lua(Vec3f::ZERO); } void __declspec(dllexport) set_pos(ISurgeObject* SELF_OBJECT,LuaVec3 p) { if (!SELF_OBJECT) { sclog(SCLOG_WARNING,"NULL pointer passed to function set_pos (C:set_pos)!"); return; } auto obj0=obj_conv<SurgeObject2d>(SELF_OBJECT); if (obj0) { obj0->set_pos(Lua_to_Vec3f(p)); return; } auto obj1=obj_conv<SurgeObject3d>(SELF_OBJECT); if (obj1) { obj1->set_pos(Lua_to_Vec3f(p)); return; } if (SELF_OBJECT->get_implementation()) sclog(SCLOG_WARNING,"set_pos (C:set_pos) can't be called on an object of type %s!",SELF_OBJECT->get_entity_type_string()); } LuaVec3 __declspec(dllexport) get_proj_pos(ISurgeObject* SELF_OBJECT,LuaVec3 offset) { if (!SELF_OBJECT) { sclog(SCLOG_WARNING,"NULL pointer passed to function get_proj_pos (C:get_proj_pos)!"); return Vec3f_to_Lua(Vec3f::ZERO); } auto obj0=obj_conv<SurgeObject3d>(SELF_OBJECT); if (obj0) return Vec3f_to_Lua(obj0->get_proj_pos(Lua_to_Vec3f(offset))); if (SELF_OBJECT->get_implementation()) sclog(SCLOG_WARNING,"get_proj_pos (C:get_proj_pos) can't be called on an object of type %s!",SELF_OBJECT->get_entity_type_string()); return Vec3f_to_Lua(Vec3f::ZERO); } LuaVec3 __declspec(dllexport) get_dir(ISurgeObject* SELF_OBJECT) { if (!SELF_OBJECT) { sclog(SCLOG_WARNING,"NULL pointer passed to function get_dir (C:get_dir)!"); return Vec3f_to_Lua(Vec3f::ZERO); } auto obj0=obj_conv<SurgeObject2d>(SELF_OBJECT); if (obj0) return Vec3f_to_Lua(obj0->get_dir()); auto obj1=obj_conv<SurgeObject3d>(SELF_OBJECT); if (obj1) return Vec3f_to_Lua(obj1->get_dir()); if (SELF_OBJECT->get_implementation()) sclog(SCLOG_WARNING,"get_dir (C:get_dir) can't be called on an object of type %s!",SELF_OBJECT->get_entity_type_string()); return Vec3f_to_Lua(Vec3f::ZERO); } LuaVec3 __declspec(dllexport) get_up(ISurgeObject* SELF_OBJECT) { if (!SELF_OBJECT) { sclog(SCLOG_WARNING,"NULL pointer passed to function get_up (C:get_up)!"); return Vec3f_to_Lua(Vec3f::ZERO); } auto obj0=obj_conv<SurgeObject2d>(SELF_OBJECT); if (obj0) return Vec3f_to_Lua(obj0->get_up()); auto obj1=obj_conv<SurgeObject3d>(SELF_OBJECT); if (obj1) return Vec3f_to_Lua(obj1->get_up()); if (SELF_OBJECT->get_implementation()) sclog(SCLOG_WARNING,"get_up (C:get_up) can't be called on an object of type %s!",SELF_OBJECT->get_entity_type_string()); return Vec3f_to_Lua(Vec3f::ZERO); } LuaVec3 __declspec(dllexport) get_right(ISurgeObject* SELF_OBJECT) { if (!SELF_OBJECT) { sclog(SCLOG_WARNING,"NULL pointer passed to function get_right (C:get_right)!"); return Vec3f_to_Lua(Vec3f::ZERO); } auto obj0=obj_conv<SurgeObject2d>(SELF_OBJECT); if (obj0) return Vec3f_to_Lua(obj0->get_right()); auto obj1=obj_conv<SurgeObject3d>(SELF_OBJECT); if (obj1) return Vec3f_to_Lua(obj1->get_right()); if (SELF_OBJECT->get_implementation()) sclog(SCLOG_WARNING,"get_right (C:get_right) can't be called on an object of type %s!",SELF_OBJECT->get_entity_type_string()); return Vec3f_to_Lua(Vec3f::ZERO); } oid __declspec(dllexport) set_dir(ISurgeObject* SELF_OBJECT,LuaVec3 d) { if (!SELF_OBJECT) { sclog(SCLOG_WARNING,"NULL pointer passed to function set_dir (C:set_dir)!"); return; } auto obj0=obj_conv<SurgeObject2d>(SELF_OBJECT); if (obj0) { obj0->set_dir(Lua_to_Vec3f(d)); return; } auto obj1=obj_conv<SurgeObject3d>(SELF_OBJECT); if (obj1) { obj1->set_dir(Lua_to_Vec3f(d)); return; } if (SELF_OBJECT->get_implementation()) sclog(SCLOG_WARNING,"set_dir (C:set_dir) can't be called on an object of type %s!",SELF_OBJECT->get_entity_type_string()); } LuaQuaternion __declspec(dllexport) get_ori(ISurgeObject* SELF_OBJECT) { if (!SELF_OBJECT) { sclog(SCLOG_WARNING,"NULL pointer passed to function get_ori (C:get_ori)!"); return Quaternion_to_Lua(Quaternionf::IDENTITY); } auto obj0=obj_conv<SurgeObject2d>(SELF_OBJECT); if (obj0) return Quaternion_to_Lua(obj0->get_ori()); auto obj1=obj_conv<SurgeObject3d>(SELF_OBJECT); if (obj1) return Quaternion_to_Lua(obj1->get_ori()); if (SELF_OBJECT->get_implementation()) sclog(SCLOG_WARNING,"get_ori (C:get_ori) can't be called on an object of type %s!",SELF_OBJECT->get_entity_type_string()); return Quaternion_to_Lua(Quaternionf::IDENTITY); }
It also generates the FFI binds on the Lua side, which is great…
So, instead of having to write: the function itself (C++), the bind (C), and the Lua FFI declaration (Lua), and in the future, the documentation (Doxygen or something like that), I just do the C++ part and let the tool build the code…
Currently, the system generates about 4000 lines of code… and if I want to go to old-style Lua (for platforms that don’t support LuaJIT, for example), I can just change the generator and get the old-school Lua binds (wouldn’t be that easy because standard Lua doesn’t support the data types necessary, but it’s doable).
Anyway, work has been going great… I wish I had vacations more often!
Comment
You must be logged in to post a comment.