Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Lua and OO bindings

This thread is locked; no one can reply to it. rss feed Print
Lua and OO bindings
Thomas Fjellstrom
Member #476
June 2000
avatar

This isn't actually a question. I finally managed to figure out the lua5.2 lib properly.

I thought I'd share what I came up with:

http://pastebin.com/C3Py3Msu

#SelectExpand
1#include <cstdio> 2#include <lua.hpp> 3#include <cerrno> 4#include <cstring> 5#include <stdlib.h> 6 7#include <new> 8 9void lua_stack_dump(lua_State *L); 10 11#define _strify(str) #str 12#define _concat(str1, str2) str1 ## str2 13 14#define register_class(state, class_name, meta_table, class_table) do { \ 15 luaL_newmetatable(state, #class_name); \ 16 lua_pushvalue(state, -1); \ 17 lua_setfield(state, -2, "__index"); \ 18 luaL_setfuncs(state, class_table, 0); \ 19 luaL_newlib(state, meta_table); \ 20 /* lua_pushvalue(state, -1);*/ \ 21 lua_setglobal(state, #class_name); \ 22 lua_stack_dump(state); \ 23} while(0) 24 25#define create_lua_object(state, class_name, obj) do { \ 26 void *udata__ = lua_newuserdata(state, sizeof(_concat(Lua,class_name))); \ 27 _concat(Lua,class_name) *obj__ = ::new (udata__) _concat(Lua,class_name)(state, obj); \ 28 \ 29 luaL_getmetatable(state, #class_name); \ 30 lua_setmetatable(state, -2); \ 31 lua_stack_dump(state); \ 32} while(0) 33 34#define get_lua_object(state, class_name) (_concat(Lua,class_name) *)luaL_checkudata(state, 1, #class_name) 35 36#define destroy_lua_object(state, class_name) do { \ 37 _concat(Lua, class_name) *udata__ = (_concat(Lua, class_name) *)luaL_checkudata(state, 1, #class_name); \ 38 udata__->~_concat(Lua, class_name)(); \ 39} while(0) 40 41class Test 42{ 43 public: 44 Test() { printf("new Test!\n"); } 45 ~Test() { printf("delete Test!\n"); } 46 47 void it() { printf("TEST IT!\n"); } 48}; 49 50class LuaTest 51{ 52 public: 53 LuaTest(lua_State *, Test *test) : test(test) { printf("new LuaTest!\n"); } 54 ~LuaTest() { printf("delete LuaTest!\n"); } 55 void it() { test->it(); } 56 private: 57 Test *test; 58}; 59 60static int Test_destroy(lua_State *state) 61{ 62 printf("Test_destroy\n"); 63 destroy_lua_object(state, Test); 64 return 0; 65} 66 67static int Test_it(lua_State *state) 68{ 69 printf("Test_it\n"); 70 LuaTest *test = get_lua_object(state, Test); 71 test->it(); 72 return 0; 73} 74 75static int Test_new(lua_State *state) 76{ 77 printf("Test_new begin\n"); 78 Test *newTest = new Test(); 79 create_lua_object(state, Test, newTest); 80 printf("Test_new end\n"); 81 return 1; 82} 83 84static const luaL_Reg test_meta_methods[] = { 85 { "new", &Test_new }, 86 { "__gc", &Test_destroy }, 87 { 0, 0 } 88}; 89 90static const luaL_Reg test_methods[] = { 91 { "it", &Test_it }, 92 { 0, 0 } 93}; 94 95int main(int argc, char **argv) 96{ 97 printf("start\n"); 98 lua_State *state = luaL_newstate(); 99 luaL_openlibs(state); 100 101 printf("call register_class\n"); 102 register_class(state, Test, test_meta_methods, test_methods); 103 104 printf("open lua file\n"); 105 FILE *fh = fopen("luaobjtest.lua", "r"); 106 if(!fh) 107 { 108 printf("failed to open luaobjtest.lua: %s\n", strerror(errno)); 109 return 0; 110 } 111 112 fseek(fh, 0, SEEK_END); 113 long len = ftell(fh); 114 fseek(fh, 0, SEEK_SET); 115 116 char *script = (char *)malloc(len+1); 117 if(!script) 118 { 119 printf("failed to allocate enough memory to load luaobjtest.lua\n"); 120 return 0; 121 } 122 123 script[len] = 0; 124 size_t ret = fread(script, 1, len, fh); 125 if(ret != (size_t)len) 126 { 127 printf("failed to read entire luaobjtest.lua file, read %li bytes, expected %li bytes.\n", ret, len); 128 return 0; 129 } 130 131 fclose(fh); 132 133 printf("call luaL_loadstring\n"); 134 if(luaL_loadstring(state, script) != LUA_OK) 135 { 136 const char *errstr = luaL_checklstring(state, 1, 0); 137 printf("failed to load luaobjtest.lua: %s\n", errstr); 138 free(script); 139 return 0; 140 } 141 142 printf("call lua_pcall\n"); 143 if(lua_pcall(state, 0, LUA_MULTRET, 0) != LUA_OK) 144 { 145 const char *errstr = luaL_checklstring(state, 1, 0); 146 printf("failed to run luaobjtest.lua: %s\n", errstr); 147 free(script); 148 return 0; 149 } 150 151 printf("done!\n"); 152 153 return 0; 154} 155 156void lua_stack_dump(lua_State *L) 157{ 158 int i; 159 int top = lua_gettop(L); 160 for (i = 1; i <= top; i++) { /* repeat for each level */ 161 int t = lua_type(L, i); 162 switch (t) { 163 164 case LUA_TSTRING: /* strings */ 165 printf("`%s'", lua_tostring(L, i)); 166 break; 167 168 case LUA_TBOOLEAN: /* booleans */ 169 printf(lua_toboolean(L, i) ? "true" : "false"); 170 break; 171 172 case LUA_TNUMBER: /* numbers */ 173 printf("%g", lua_tonumber(L, i)); 174 break; 175 176 default: /* other values */ 177 printf("%s", lua_typename(L, t)); 178 break; 179 180 } 181 printf(" "); /* put a separator */ 182 } 183 printf("\n"); /* end the listing */ 184}

Maybe it'll help someone here trying to embed lua in their own programs, who also want to use objects from C++.

append:

Forgot the actual lua script:

local test = Test.new();
test:it();

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Dizzy Egg
Member #10,824
March 2009
avatar

Fjellstrom! This is...you are....nice one man! :D

----------------------------------------------------
Please check out my songs:
https://soundcloud.com/dont-rob-the-machina

Thomas Fjellstrom
Member #476
June 2000
avatar

Dizzy Egg said:

Fjellstrom! This is...you are....nice one man!

I take it you've been fighting with lua 5.2 as well? Most of my frustration was due to a typo/thinko in luaL_checkudata, passed 0 instead of 1 as the second argument. tried changing everything but that, thinking it was ok ::)

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Dizzy Egg
Member #10,824
March 2009
avatar

Ahh...err...well I've been applying the 'copy & paste' approach (because I'm a lazy bastard), and have had not much luck so far with 5.2, I've hacked? together a solution, but compared to what you've just shared it looks like...well, a mess.

----------------------------------------------------
Please check out my songs:
https://soundcloud.com/dont-rob-the-machina

Thomas Fjellstrom
Member #476
June 2000
avatar

I.. Yeah, I tried the copy & paste method hoping it would get me at least part way, but almost none of the available code is for 5.2.

I may change up the api later to use templates and other magic to make things a bit easier and cleaner. Maybe..

Right now thats just ripped out of a project I started trying to integrate lua into while I was at my moms. I spent days if not a week (or two) trying to get it to work properly.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Dizzy Egg
Member #10,824
March 2009
avatar

It's much appreciated! And 'hi' to your mum! (and Jasper!)

----------------------------------------------------
Please check out my songs:
https://soundcloud.com/dont-rob-the-machina

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Thomas Fjellstrom
Member #476
June 2000
avatar

lua's api really could have been done better.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Raidho36
Member #14,628
October 2012
avatar

LUA's api isn't designed to be used in object-oriented fashion, provided LUA don't support objects. So it's as if you were complaining that OO-coding with plain assembly was hard and it could've done it better.

Vanneto
Member #8,643
May 2007

Nobody is complaining. The only one (again!) that is bitching and moaning is... You. :P

EDIT: And while the API for Lua is in C, the language itself supports OOP.

In capitalist America bank robs you.

Raidho36
Member #14,628
October 2012
avatar

Hey, I'm not moaning. Also, that's not really an OOP, I mean you could do just the same with C, but that doesn't make C an object-oriented language.

Polybios
Member #12,293
October 2010

I did something quite similar, very much inspired by this wrapper solution here, which also provides some nice templates and ugly macros ( ;D ) for passing arguments : Embedding Lua for Fun and Profit

Had to adapt it to 5.2, too, though. As far as I remember, it wasn't more than replacing luaL_openlib with luaL_setfuncs. (edit: There was another thing, in Lua 5.2, tables can have a __gc metamethod while in 5.1 they can't, so I had to allow its execution for the userdata only, as this wrapper uses an userdata object wrapped inside a table for its c++-objects.)
Also, this wrapper doesn't require separate wrapper classes, as yours.
But I like the separation of class-methods and meta-methods, which you do, very much.

The one thing I found quite complicated to do about binding lua and c++ was to track userdata objects which contain other user-data objects, when the container is managed on the C side. You have to keep around a lua reference in your container object on the C-side in order to prevent the contained objects from being garbage-collected...
Have you thought about how do deal with that already? I'd be interested in how you do it.

I also hacked the Lua core so I had to declare globals. I don't like the concept of treating everything that isn't anything else as globals implicitly (I mean, I don't think global by default is bad, but you should be required to declare them in order to prevent typos from crashing your program, just as an extra-check).

Raidho36 said:

I mean you could do just the same with C

Last time I checked, C didn't provide anything like meta-methods, which allow you to do constructors, destructors, operator overloading, ...

Raidho36
Member #14,628
October 2012
avatar

I was comparing it to LUA's "OO". Seriously, that ain't OO. It's just LUA's loose internal structure allows a lot of things to work, which makes it possible to run sort of OO there. But LUA by itself has no support for it so you'd have to kludge up some kludgingly kludgy kludges to make it work right, just like you'd have to with C.

Polybios
Member #12,293
October 2010

That's sort of right, but I'd still argue that it works better with Lua than with C because you have metatables and metamethods. You simply can't do this implicit- function-calling-OOP-magic with C!
If you need native-true-OOP support out of the box, you should use a different embeddable scripting language, not Lua.
I like the concept of Lua's general purpose tables which is why I use it.

Thomas Fjellstrom
Member #476
June 2000
avatar

It's about as good as C++'s OO support. It has some convenient syntax sugar to allow some OO concepts to work, just like lua does \o/

But yes, please keep this as on topic as possible, winging about random crap just because you can is looked down upon.

Polybios said:

Also, this wrapper doesn't require separate wrapper classes, as yours.

I started off without the wrapper classes. I added them thinking not using them was causing issues somehow? I tried a lot of things to get my code to work. But it turns out I want to keep the wrappers anyhow, so I can let lua properly dispose of stuff.

The wrapper classes in some instances will be aloud to dispose of their wrapped objects, and you can't really do that by storing a single pointer in the udata.

It actually wouldn't take much to rip out the need for the wrapper class in those macros..

Something like:

#SelectExpand
1#include <cstdio> 2#include <lua.hpp> 3#include <cerrno> 4#include <cstring> 5#include <stdlib.h> 6 7#include <new> 8 9void lua_stack_dump(lua_State *L); 10 11#define _strify(str) #str 12#define _concat(str1, str2) str1 ## str2 13 14#define register_class(state, class_name, meta_table, class_table) do { \ 15 luaL_newmetatable(state, #class_name); \ 16 lua_pushvalue(state, -1); \ 17 lua_setfield(state, -2, "__index"); \ 18 luaL_setfuncs(state, class_table, 0); \ 19 luaL_newlib(state, meta_table); \ 20 lua_setglobal(state, #class_name); \ 21} while(0) 22 23#define create_lua_object(state, class_name, obj) do { \ 24 void **udata__ = (void **)lua_newuserdata(state, sizeof(class_name *)); \ 25 *udata__ = obj; \ 26 luaL_getmetatable(state, #class_name); \ 27 lua_setmetatable(state, -2); \ 28} while(0) 29 30#define get_lua_object(state, class_name) *(class_name **)luaL_checkudata(state, 1, #class_name) 31 32#define destroy_lua_object(state, class_name) do { \ 33 class_name **udata__ = (class_name **)luaL_checkudata(state, 1, #class_name); \ 34 (*udata__)->~class_name(); \ 35} while(0) 36 37class Test 38{ 39 public: 40 Test() { printf("new Test!\n"); } 41 ~Test() { printf("delete Test!\n"); } 42 43 void it() { printf("TEST IT!\n"); } 44}; 45 46static int Test_destroy(lua_State *state) 47{ 48 printf("Test_destroy\n"); 49 destroy_lua_object(state, Test); 50 return 0; 51} 52 53static int Test_it(lua_State *state) 54{ 55 printf("Test_it\n"); 56 Test *test = get_lua_object(state, Test); 57 test->it(); 58 return 0; 59} 60 61static int Test_new(lua_State *state) 62{ 63 printf("Test_new begin\n"); 64 Test *newTest = new Test(); 65 create_lua_object(state, Test, newTest); 66 printf("Test_new end\n"); 67 return 1; 68} 69 70static const luaL_Reg test_meta_methods[] = { 71 { "new", &Test_new }, 72 { "__gc", &Test_destroy }, 73 { 0, 0 } 74}; 75 76static const luaL_Reg test_methods[] = { 77 { "it", &Test_it }, 78 { 0, 0 } 79}; 80 81int main(int argc, char **argv) 82{ 83 printf("start\n"); 84 lua_State *state = luaL_newstate(); 85 luaL_openlibs(state); 86 87 printf("call register_class\n"); 88 register_class(state, Test, test_meta_methods, test_methods); 89 90 printf("open lua file\n"); 91 FILE *fh = fopen("luaobjtest.lua", "r"); 92 if(!fh) 93 { 94 printf("failed to open luaobjtest.lua: %s\n", strerror(errno)); 95 return 0; 96 } 97 98 fseek(fh, 0, SEEK_END); 99 long len = ftell(fh); 100 fseek(fh, 0, SEEK_SET); 101 102 char *script = (char *)malloc(len+1); 103 if(!script) 104 { 105 printf("failed to allocate enough memory to load luaobjtest.lua\n"); 106 return 0; 107 } 108 109 script[len] = 0; 110 size_t ret = fread(script, 1, len, fh); 111 if(ret != (size_t)len) 112 { 113 printf("failed to read entire luaobjtest.lua file, read %li bytes, expected %li bytes.\n", ret, len); 114 return 0; 115 } 116 117 fclose(fh); 118 119 printf("call luaL_loadstring\n"); 120 if(luaL_loadstring(state, script) != LUA_OK) 121 { 122 const char *errstr = luaL_checklstring(state, 1, 0); 123 printf("failed to load luaobjtest.lua: %s\n", errstr); 124 free(script); 125 return 0; 126 } 127 128 printf("call lua_pcall\n"); 129 if(lua_pcall(state, 0, LUA_MULTRET, 0) != LUA_OK) 130 { 131 const char *errstr = luaL_checklstring(state, 1, 0); 132 printf("failed to run luaobjtest.lua: %s\n", errstr); 133 free(script); 134 return 0; 135 } 136 137 printf("done!\n"); 138 139 return 0; 140} 141 142void lua_stack_dump(lua_State *L) 143{ 144 int i; 145 int top = lua_gettop(L); 146 for (i = 1; i <= top; i++) { /* repeat for each level */ 147 int t = lua_type(L, i); 148 switch (t) { 149 150 case LUA_TSTRING: /* strings */ 151 printf("`%s'", lua_tostring(L, i)); 152 break; 153 154 case LUA_TBOOLEAN: /* booleans */ 155 printf(lua_toboolean(L, i) ? "true" : "false"); 156 break; 157 158 case LUA_TNUMBER: /* numbers */ 159 printf("%g", lua_tonumber(L, i)); 160 break; 161 162 default: /* other values */ 163 printf("%s", lua_typename(L, t)); 164 break; 165 166 } 167 printf(" "); /* put a separator */ 168 } 169 printf("\n"); /* end the listing */ 170}

The only issue with that, is if you really want lua calling your destructor? Probably not, so that should probably be removed.

Quote:

Have you thought about how do deal with that already? I'd be interested in how you do it.

I haven't considered that. Even slightly. I'm sure you can do it simply enough, possibly with a nice little "smart_lua_ptr" class to wrap those lua objects?

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Polybios
Member #12,293
October 2010

Interesting.

You could use placement new directly on your C++-object/the userdata with (sizeof(YourClass) and call the destructor explicitly in the __gc method, can't you? Is there a difference between Lua - TestDestroy - ~LuaTest (explicitly) - ~Test (implicitly) and Lua - TestDestroy - ~Test (explicitly)? Isn't it just shorter?
At least that's how I do it. I found it to become really tedious to write the glue code, so I'd like to minimize that and avoid extra wrapper classes when possible. (Of course you can emit the __gc glue-function (e. g. TestDestroy) in one line using a macro).

But: In what situation would you want the Lua wrapper class to let go of the wrapped C++ object? Wouldn't that be insecure? ???

While a smart pointer is a good idea (of which I haven't thought ::) ), I think it doesn't solve the problem with the references on its own. You'd still need a place to store your lua_smart_ptrs; chances are that you don't want your C++-container-object to store them, because then, you couldn't use it with plain C++ objects anymore (?). Besides, I'd prefer to not have my C++ objects "contaminated" with Lua glue code. You definitively need those references though, because it will inevitably crash when you don't have them (speaking from experience here 8-) ). [As soon as Lua runs a GC-cycle, it will collect your CppObjects, because it is unable to "see" that they are "referenced" on the C++ side in your CppContainer.]

While I think about it, this container-object-reference-thing is actually the only case for which you definitively need a wrapper class, no?

Well, whatever, I just wanted to draw your attention to this problem, because it was the part which I found the most difficult. :)
I've also spent quite some time in order to get it to work; I guess I spent even more time searching for premade solutions - which either didn't work, were made for Lua 5.1 or were overly complicated and took ages to compile because of all the templates involved.

(By the way, is there a reason you don't use lua_dofile in the example? )

Thomas Fjellstrom
Member #476
June 2000
avatar

The main reason I use pointers (or the wrapper object) rather than explicit objects is, at least in the project I wrote the original code for, most objects will be created in the C++ code, and exposed to lua. And I don't want lua destroying my objects on me.

With the wrapper class, I have an extra step where I can put some smarter code to handle the lua destruction event. It could potentially do smarter things. Maybe.

Polybios said:

But: In what situation would you want the Lua wrapper class to let go of the wrapped C++ object? Wouldn't that be insecure?

Yeah, it could be. But its nice to allow it, say the lua code creates an object, and doesn't attach it to anything, and then exits? I'd like the orphans to go away if at all possible.

Quote:

(By the way, is there a reason you don't use lua_dofile in the example? )

Nope. I yanked the loadstring call from another example that loads from an array of strings. I just forgot about dofile and friends.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Polybios
Member #12,293
October 2010


most objects will be created in the C++ code, and exposed to lua. And I don't want lua destroying my objects on me.

Ah, okay, understand now. :)

Thomas Fjellstrom
Member #476
June 2000
avatar

In case you're interested, this is the other lua test I made:

#SelectExpand
1#include <lua.hpp> 2#include <cstring> 3#include <cstdlib> 4 5struct Script { 6 const char *name; 7 const char *script; 8 bool loaded; 9} scripts[] = { 10 { "luatest1", "bar = 123; print(\"luatest1 run!\"); function foo() \n print(\"Hi from luatest1.foo!\");\nend", false }, 11 { "luatest2", "bar = 123; print(\"luatest2 run!\"); function foo() \n print(\"Hi from luatest2.foo!\");\nend", false }, 12 { "luatest3", "bar = 123; print(\"luatest3 run!\"); function foo() \n print(\"Hi from luatest3.foo!\");\nend", false }, 13 { "luatest4", "bar = 123; print(\"luatest4 run!\"); function foo() \n print(\"Hi from luatest4.foo!\");\nend", false } 14}; 15 16bool load_script(lua_State *state, const char *name, const char *str) 17{ 18 printf("load script %s\n", name); 19 20 if(luaL_loadstring(state, str) != LUA_OK) // 1 21 { 22 const char *errstr = luaL_checklstring(state, 1, 0); 23 printf("failed to load luatest: %s\n", errstr); 24 return false; 25 } 26 27 lua_newtable(state); // script env // 21 28 lua_setglobal(state, name); // set global name for new table // 1 29 30 lua_getglobal(state, name); // 21 31 lua_newtable(state); // metatable // 321 32 lua_getglobal(state, "_G"); // 4321 33 lua_setfield(state, -2, "__index"); // set __index in metatable to _G // 321 34 lua_setmetatable(state, -2); // set metatable for script env // 21 35 36 lua_setupvalue(state, 1, 1); // set env for state // 21 37 38 printf("run script %s\n", name); 39 if(lua_pcall(state, 0, LUA_MULTRET, 0) != LUA_OK) // run script // 1 40 { 41 const char *errstr = luaL_checklstring(state, 1, 0); 42 printf("failed to run script %s: %s\n", name, errstr); 43 return false; 44 } 45 46 return true; 47} 48 49bool run_script(lua_State *state, const char *name) 50{ 51 lua_getglobal(state, name); 52 lua_getfield(state, -1, "foo"); 53 if(lua_pcall(state, 0, LUA_MULTRET, 0) != LUA_OK) 54 { 55 const char *errstr = luaL_checklstring(state, 1, 0); 56 printf("failed to call %s.foo: %s\n", name, errstr); 57 return false; 58 } 59 60 return true; 61} 62 63int main(int argc, char **argv) 64{ 65 lua_State *state = luaL_newstate(); 66 luaL_openlibs(state); 67 68 printf("begin\n"); 69 70 for(int i = 0; i < sizeof(scripts) / sizeof(Script); i++) 71 { 72 if(!load_script(state, scripts[i].name, scripts[i].script)) 73 { 74 printf("failed to load %s :(\n", scripts[i].name); 75 continue; 76 } 77 78 scripts[i].loaded = true; 79 } 80 81 for(int i = 0; i < sizeof(scripts) / sizeof(Script); i++) 82 { 83 if(scripts[i].loaded) 84 { 85 if(!run_script(state, scripts[i].name)) 86 { 87 printf("failed to run %s :(\n", scripts[i].name); 88 continue; 89 } 90 } 91 } 92 93 printf("done!\n"); 94 return 0; 95}

It loads each script into its own table/namespace in the same lua_State. I intend(ed) to use it to load a bunch of scripts that do certain things, and I can then just run them all in sequence without the overhead of separate states and the like. Oh, and they could potentially share some data if needed. They are partially sandboxed, so a bit safer than just loading a bunch of scripts into the same state, but not quite locked down, which you can do, but I didn't bother with the added complexity.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Go to: