chris wrote:What would it take to enable loadlib? This really seems like the ideal way to add sound support to Celestia.
Enabling loadlib is a Lua build option. Currently Celestia is linking pre-built Lua libraries which do not have loadlib enabled, so we would need to rebuild the Lua libraries to enable loadlib in them.
However, it's not difficult to include the code for loadlib in celx.cpp (which I've done for testing). Here's the code I've been using for loadlib (on MacOS X):
Code: Select all
// ==================== loadlib =====================================================
/*
* This is an implementation of loadlib based on the dlfcn interface.
* The dlfcn interface is available in Linux, SunOS, Solaris, IRIX, FreeBSD,
* NetBSD, AIX 4.2, HPUX 11, and probably most other Unix flavors, at least
* as an emulation layer on top of native functions.
*/
extern "C" {
#include <lualib.h>
/* #include <lauxlib.h> -- don't have it */
#include <dlfcn.h>
}
static int loadlib(lua_State *L)
{
/* temp -- don't have lauxlib
const char *path=luaL_checkstring(L,1);
const char *init=luaL_checkstring(L,2);
*/
const char *path=lua_tostring(L,1);
const char *init=lua_tostring(L,2);
void *lib=dlopen(path,RTLD_NOW);
if (lib!=NULL)
{
lua_CFunction f=(lua_CFunction) dlsym(lib,init);
if (f!=NULL)
{
lua_pushlightuserdata(L,lib);
lua_pushcclosure(L,f,1);
return 1;
}
}
/* else return appropriate error messages */
lua_pushnil(L);
lua_pushstring(L,dlerror());
lua_pushstring(L,(lib!=NULL) ? "init" : "open");
if (lib!=NULL) dlclose(lib);
return 3;
}
// ==================== loadlib =====================================================
In addition to the code above, you'd need to add:
Code: Select all
lua_pushstring(state, "loadlib");
lua_pushcfunction(state, loadlib);
lua_settable(state, LUA_GLOBALSINDEX);
in order make the loadlib C function accessible to Lua scripts. I've put this code in LuaState::init for testing purposes, but it probably should go in LuaState::requestIO and LuaState::charEntered (or a common function called from them) where luaopen_io (lua_iolibopen) is called. It would be very helpful if a call to luaopen_debug was also included there.
Once, this is done, a CELX script can call loadlib to load a dynamic library. But loadlib requires a full (platform-dependent) pathname, so it shouldn't be called directly. Instead, a Lua module should be used to do the loading. Then the script can use the "require" function to load the library for access from Lua. For example, the following code uses the SPICE library:
Code: Select all
require "spicelib";
spicelib.ldpool ( "/.../naif0007.tls" );
spicelib.spklef ( "/.../vg2_jup.bsp" );
et = spicelib.str2et("1979 AUG 5 02:03:48.482");
x,y,z = spicelib.spkezr( "voyager 2", et,"j2000","NONE","jupiter");
In this example, the require function loads "spicelib.lua", which locates and loads the dynamic library. The Lua loader module looks like this:
Code: Select all
-- spicelib.lua
local opendylib = function(libmain)
local pkgname = _REQUIREDNAME
local sourceinfo = debug.getinfo(2,"S");
local sourcefile = sourceinfo.source;
local sourcefile = string.sub(sourcefile,2);
local sourcedir = string.sub(sourcefile,string.find(sourcefile,".*%/"));
--[[ TO DO: handle cross-platform libraries ]]
local libsuffix = ".dylib"
local libfile = sourcedir..pkgname..libsuffix;
local open_lib = loadlib(libfile,libmain);
open_lib();
end
opendylib("luaopen_spicelib");
The loader module requires that the dynamic library have the same name and be located in the same directory. (Note that this version works only on Mac OS X because it assumes the dynamic library has a ".dylib" suffix.)
The real work, of course, is in building the dynamic library with the C code to provide a Lua binding for the library functions. Mainly this involves writing cover functions that get the arguments from the Lua stack, call the C function, and then push the results onto the Lua stack. Here's an example:
Code: Select all
static int spice_spkezr (lua_State *l)
{
SpiceDouble state[6];
SpiceDouble et,lt;
checkArgs(l, 5, 5, "Four arguments expected to function spicelib.spkezr");
const char* body = safeGetString(l, 1, AllErrors, "First argument to spicelib.spkezr must be a string");
et = safeGetNumber(l, 2, AllErrors, "Second argument to spicelib.spkezr must be a string", 0.0);
const char* frame = safeGetString(l, 3, AllErrors, "Third argument to spicelib.spkezr must be a string");
const char* arg4 = safeGetString(l, 4, AllErrors, "Fourth argument to spicelib.spkezr must be a string");
const char* refbody = safeGetString(l, 5, AllErrors, "Fifth argument to spicelib.spkezr must be a string");
spkezr_c ( body, et, frame, arg4, refbody, state, <);
lua_pushnumber(l,state[0]);
lua_pushnumber(l,state[1]);
lua_pushnumber(l,state[2]);
lua_pushnumber(l,state[3]);
lua_pushnumber(l,state[4]);
lua_pushnumber(l,state[5]);
lua_pushnumber(l,lt);
return 7;
}
I've used examples here from my experiments with using SPICE, but the same approach should work with OpenAL, etc. The important point is that, except for enabling loadlib, everything can be done externally to Celestia by a Lua add-on contributor.
- Hank