You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
529 lines
14 KiB
C
529 lines
14 KiB
C
/*
|
|
* Example code for userdata handling in the moon toolkit.
|
|
*
|
|
* The moon toolkits provides the following functions for handling
|
|
* userdata in an easy and safe way:
|
|
* - moon_defobject
|
|
* - moon_newobject
|
|
* - moon_newpointer
|
|
* - moon_newfield
|
|
* - moon_killobject
|
|
* - moon_checkobject
|
|
* - moon_testobject
|
|
* - moon_defcast
|
|
*
|
|
* Using those functions enables you to
|
|
* - Create and register a new metatable for a C type in a single
|
|
* function call, including methods and metamethods with upvalues.
|
|
* - Have properties and methods for an object at the same time.
|
|
* - Use C values and pointers to those values in a uniform way.
|
|
* - Support multiple different ways to create/destroy a C type.
|
|
* - Expose fields embedded in another object in a type-safe way.
|
|
* - Bind tagged unions in a type-safe way.
|
|
* - Define functions that release resources in a safe way before
|
|
* the object becomes unreachable.
|
|
* - Use userdata polymorphically (use one method implementation for
|
|
* multiple similar types).
|
|
*/
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <limits.h>
|
|
#include <lua.h>
|
|
#include <lauxlib.h>
|
|
#include "moon.h"
|
|
|
|
|
|
/* Types to be exposed to Lua: */
|
|
typedef struct {
|
|
int x;
|
|
int y;
|
|
} D;
|
|
|
|
typedef struct {
|
|
D d;
|
|
} C;
|
|
|
|
typedef struct {
|
|
double f;
|
|
} B;
|
|
|
|
#define TYPE_B 1
|
|
#define TYPE_C 2
|
|
|
|
typedef struct {
|
|
int tag;
|
|
union {
|
|
B b;
|
|
C c;
|
|
} u;
|
|
} A;
|
|
|
|
|
|
/* Check functions to make sure that embedded userdata are still
|
|
* valid. A change in the tagged union (A_switch) or running a cleanup
|
|
* function (C_close) can make embedded userdata invalid. */
|
|
static int type_b_check( void* p ) {
|
|
int* tagp = p;
|
|
int res = *tagp == TYPE_B;
|
|
return res;
|
|
}
|
|
|
|
static int type_c_check( void* p ) {
|
|
int* tagp = p;
|
|
int res = *tagp == TYPE_C;
|
|
return res;
|
|
}
|
|
|
|
static int object_valid_check( void* p ) {
|
|
unsigned char* flagp = p;
|
|
int res = *flagp & MOON_OBJECT_IS_VALID;
|
|
return res;
|
|
}
|
|
|
|
|
|
/* Types that are similar can share one method implementation, but a
|
|
* pointer to one type has to be transformed to a pointer to the other
|
|
* type. This is probably much more useful when binding C++ libraries
|
|
* with proper type hierarchies! */
|
|
static void* C_to_D( void* p ) {
|
|
C* c = p;
|
|
printf( "casting C to D\n" );
|
|
return &(c->d);
|
|
}
|
|
|
|
|
|
|
|
/* The (meta-)methods are pretty straightforward. Just use
|
|
* `moon_checkobject` instead of `luaL_checkudata`. */
|
|
static int A_index( lua_State* L ) {
|
|
A* a = moon_checkobject( L, 1, "A" );
|
|
char const* key = luaL_checkstring( L, 2 );
|
|
if( 0 == strcmp( key, "tag" ) ) {
|
|
lua_pushstring( L, a->tag == TYPE_B ? "b" : "c" );
|
|
} else if( 0 == strcmp( key, "b" ) && a->tag == TYPE_B ) {
|
|
/* To avoid creating the sub-userdata on every __index access, the
|
|
* userdata values are cached in the uservalue field of the parent
|
|
* using the same field name as in the struct. */
|
|
if( moon_getuvfield( L, 1, "b" ) == LUA_TNIL ) {
|
|
/* Create a new userdata that represents a field in another
|
|
* object already exposed to Lua. A gc function is unnecessary
|
|
* since the parent userdata already takes care of that.
|
|
* However, the parent userdata must be kept alive long enough
|
|
* (by putting the value at index 1 into the uservalue table of
|
|
* the new userdata), and the new userdata may only be used as
|
|
* long as the `a->tag` field satisfies the `type_b_check`
|
|
* function! */
|
|
void** p = moon_newfield( L, "B", 1, type_b_check, &(a->tag) );
|
|
/* The userdata stores a pointer to the `a->b` field. */
|
|
*p = &(a->u.b);
|
|
lua_pushvalue( L, -1 );
|
|
moon_setuvfield( L, 1, "b" );
|
|
}
|
|
} else if( 0 == strcmp( key, "c" ) && a->tag == TYPE_C ) {
|
|
if( moon_getuvfield( L, 1, "c" ) == LUA_TNIL ) {
|
|
void** p = moon_newfield( L, "C", 1, type_c_check, &(a->tag) );
|
|
*p = &(a->u.c);
|
|
lua_pushvalue( L, -1 );
|
|
moon_setuvfield( L, 1, "c" );
|
|
}
|
|
} else
|
|
lua_pushnil( L );
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int A_switch( lua_State* L ) {
|
|
A* a = moon_checkobject( L, 1, "A" );
|
|
lua_settop( L, 1 );
|
|
if( a->tag == TYPE_B ) {
|
|
a->tag = TYPE_C;
|
|
a->u.c.d.x = 0;
|
|
a->u.c.d.y = 0;
|
|
} else {
|
|
a->tag = TYPE_B;
|
|
a->u.b.f = 0.0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int A_printme( lua_State* L ) {
|
|
A* a = moon_checkobject( L, 1, "A" );
|
|
if( a->tag == TYPE_B )
|
|
printf( "A { u.b.f = %g }\n", a->u.b.f );
|
|
else
|
|
printf( "A { u.c.d = { x = %d, y = %d } }\n",
|
|
a->u.c.d.x, a->u.c.d.y );
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int B_property_f( lua_State* L ) {
|
|
B* b = moon_checkobject( L, 1, "B" );
|
|
printf( "property{f} B (uv1: %d, uv2: %d)\n",
|
|
(int)lua_tointeger( L, lua_upvalueindex( 1 ) ),
|
|
(int)lua_tointeger( L, lua_upvalueindex( 2 ) ) );
|
|
if( lua_gettop( L ) < 3 ) { /* __index */
|
|
lua_pushnumber( L, b->f );
|
|
return 1;
|
|
} else { /* __newindex */
|
|
b->f = luaL_checknumber( L, 3 );
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
static int B_index( lua_State* L ) {
|
|
B* b = moon_checkobject( L, 1, "B" );
|
|
char const* key = luaL_checkstring( L, 2 );
|
|
printf( "__index B (uv1: %d, uv2: %d)\n",
|
|
(int)lua_tointeger( L, lua_upvalueindex( 1 ) ),
|
|
(int)lua_tointeger( L, lua_upvalueindex( 2 ) ) );
|
|
if( 0 == strcmp( key, "f" ) ) {
|
|
lua_pushnumber( L, b->f );
|
|
} else
|
|
lua_pushnil( L );
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int B_newindex( lua_State* L ) {
|
|
B* b = moon_checkobject( L, 1, "B" );
|
|
char const* key = luaL_checkstring( L, 2 );
|
|
printf( "__newindex B (uv1: %d, uv2: %d)\n",
|
|
(int)lua_tointeger( L, lua_upvalueindex( 1 ) ),
|
|
(int)lua_tointeger( L, lua_upvalueindex( 2 ) ) );
|
|
if( 0 == strcmp( key, "f" ) )
|
|
b->f = luaL_checknumber( L, 3 );
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
|
|
static int B_printme( lua_State* L ) {
|
|
B* b = moon_checkobject( L, 1, "B" );
|
|
printf( "B { f = %g }\n", b->f );
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int C_index( lua_State* L ) {
|
|
C* c = moon_checkobject( L, 1, "C" );
|
|
/* You can get a pointer to the `moon_object_header` structure
|
|
* common to all objects created via the moon toolkit by using the
|
|
* normal `lua_touserdata` function: */
|
|
moon_object_header* h = lua_touserdata( L, 1 );
|
|
char const* key = luaL_checkstring( L, 2 );
|
|
printf( "__index C (uv1: %d, uv2: %d)\n",
|
|
(int)lua_tointeger( L, lua_upvalueindex( 1 ) ),
|
|
(int)lua_tointeger( L, lua_upvalueindex( 2 ) ) );
|
|
if( 0 == strcmp( key, "d" ) ) {
|
|
if( moon_getuvfield( L, 1, "d" ) == LUA_TNIL ) {
|
|
/* The `object_valid_check` makes sure that the parent object
|
|
* has the `MOON_OBJECT_IS_VALID` flag set. This flag is reset
|
|
* by the `moon_killobject` function. */
|
|
void** p = moon_newfield( L, "D", 1, object_valid_check, &h->flags );
|
|
*p = &(c->d);
|
|
lua_pushvalue( L, -1 );
|
|
moon_setuvfield( L, 1, "d" );
|
|
}
|
|
} else
|
|
lua_pushnil( L );
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int C_newindex( lua_State* L ) {
|
|
C* c = moon_checkobject( L, 1, "C" );
|
|
char const* key = luaL_checkstring( L, 2 );
|
|
printf( "__newindex C (uv1: %d, uv2: %d)\n",
|
|
(int)lua_tointeger( L, lua_upvalueindex( 1 ) ),
|
|
(int)lua_tointeger( L, lua_upvalueindex( 2 ) ) );
|
|
if( 0 == strcmp( key, "d" ) ) {
|
|
D* d = moon_checkobject( L, 3, "D" );
|
|
c->d = *d;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int C_close( lua_State* L ) {
|
|
/* Use `moon_testobject` here to test it. `moon_checkobject` would
|
|
* make more sense in practice! */
|
|
if( !moon_testobject( L, 1, "C" ) )
|
|
luaL_error( L, "need a 'C' object" );
|
|
/* Run cleanup function (if any) to release resources and mark the
|
|
* C object as invalid. `moon_checkobject` will raise an error for
|
|
* invalid objects. */
|
|
moon_killobject( L, 1 );
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int C_printme( lua_State* L ) {
|
|
C* c = moon_checkobject( L, 1, "C" );
|
|
printf( "C { d = { x = %d, y = %d } } (uv1: %d, uv2: %d)\n",
|
|
c->d.x, c->d.y,
|
|
(int)lua_tointeger( L, lua_upvalueindex( 1 ) ),
|
|
(int)lua_tointeger( L, lua_upvalueindex( 2 ) ) );
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int D_index( lua_State* L ) {
|
|
D* d = moon_checkobject( L, 1, "D" );
|
|
char const* key = luaL_checkstring( L, 2 );
|
|
if( 0 == strcmp( key, "x" ) )
|
|
lua_pushinteger( L, d->x );
|
|
else if( 0 == strcmp( key, "y" ) )
|
|
lua_pushinteger( L, d->y );
|
|
else {
|
|
#if LUA_VERSION_NUM < 502
|
|
lua_getfenv( L, 1 );
|
|
#else
|
|
lua_getuservalue( L, 1 );
|
|
#endif
|
|
lua_pushvalue( L, 2 );
|
|
lua_rawget( L, -2 );
|
|
lua_replace( L, -2 );
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int D_newindex( lua_State* L ) {
|
|
D* d = moon_checkobject( L, 1, "D" );
|
|
char const* key = luaL_checkstring( L, 2 );
|
|
if( 0 == strcmp( key, "x" ) )
|
|
d->x = (int)moon_checkint( L, 3, INT_MIN, INT_MAX );
|
|
else if( 0 == strcmp( key, "y" ) )
|
|
d->y = (int)moon_checkint( L, 3, INT_MIN, INT_MAX );
|
|
else {
|
|
#if LUA_VERSION_NUM < 502
|
|
lua_getfenv( L, 1 );
|
|
#else
|
|
lua_getuservalue( L, 1 );
|
|
#endif
|
|
lua_pushvalue( L, 2 );
|
|
lua_pushvalue( L, 3 );
|
|
lua_rawset( L, -3 );
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int D_printme( lua_State* L ) {
|
|
D* d = moon_checkobject( L, 1, "D" );
|
|
printf( "D { x = %d, y = %d }\n", d->x, d->y );
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int D_vcall( lua_State* L ) {
|
|
moon_checkobject( L, 1, "D" );
|
|
lua_getfield( L, 1, "func" );
|
|
if( lua_isfunction( L, -1 ) ) {
|
|
lua_insert( L, 1 );
|
|
lua_call( L, lua_gettop( L )-1, LUA_MULTRET );
|
|
return lua_gettop( L );
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int objex_getAmethods( lua_State* L ) {
|
|
if( moon_getmethods( L, "A" ) == LUA_TNIL )
|
|
lua_pushnil( L );
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int objex_newA( lua_State* L ) {
|
|
/* Create a new A object. The memory is allocated inside the
|
|
* userdata when using `moon_newobject`. Here no cleanup function
|
|
* is used (0 pointer). */
|
|
A* ud = moon_newobject( L, "A", 0 );
|
|
ud->tag = TYPE_B;
|
|
ud->u.b.f = 0.0;
|
|
/* `moon_newobject`, and `moon_newpointer` don't allocate a
|
|
* uservalue table by default. `moon_newfield` only does if the
|
|
* given index is non-zero. If you need a uservalue table (e.g. to
|
|
* cache embedded userdatas), you have to add one yourself: */
|
|
lua_newtable( L );
|
|
#if LUA_VERSION_NUM < 502
|
|
lua_setfenv( L, -2 );
|
|
#else
|
|
lua_setuservalue( L, -2 );
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int objex_newB( lua_State* L ) {
|
|
B* b = moon_newobject( L, "B", 0 );
|
|
b->f = 0.0;
|
|
return 1;
|
|
}
|
|
|
|
|
|
static void C_destructor( void* p ) {
|
|
printf( "destroying C: %p\n", p );
|
|
}
|
|
|
|
static int objex_newC( lua_State* L ) {
|
|
/* Create a C object and register a dummy cleanup function (just
|
|
* for tests): */
|
|
C* c = moon_newobject( L, "C", C_destructor );
|
|
c->d.x = 0;
|
|
c->d.y = 0;
|
|
printf( "creating C: %p\n", (void*)c );
|
|
lua_newtable( L );
|
|
#if LUA_VERSION_NUM < 502
|
|
lua_setfenv( L, -2 );
|
|
#else
|
|
lua_setuservalue( L, -2 );
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int objex_newD( lua_State* L ) {
|
|
D* d = moon_newobject( L, "D", 0 );
|
|
d->x = 0;
|
|
d->y = 0;
|
|
lua_newtable( L );
|
|
#if LUA_VERSION_NUM < 502
|
|
lua_setfenv( L, -2 );
|
|
#else
|
|
lua_setuservalue( L, -2 );
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int objex_getD( lua_State* L ) {
|
|
static D d = { 1, 2 };
|
|
/* `moon_newpointer` without a cleanup function can be used to
|
|
* expose a global variable to Lua: */
|
|
void** ud = moon_newpointer( L, "D", 0 );
|
|
*ud = &d;
|
|
lua_newtable( L );
|
|
#if LUA_VERSION_NUM < 502
|
|
lua_setfenv( L, -2 );
|
|
#else
|
|
lua_setuservalue( L, -2 );
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
|
|
static D* newD( int x, int y ) {
|
|
D* d = malloc( sizeof( *d ) );
|
|
if( d ) {
|
|
printf( "allocating D: %p\n", (void*)d );
|
|
d->x = x;
|
|
d->y = y;
|
|
}
|
|
return d;
|
|
}
|
|
|
|
static void freeD( void* d ) {
|
|
printf( "deallocating D: %p\n", d );
|
|
free( d );
|
|
}
|
|
|
|
static int objex_makeD( lua_State* L ) {
|
|
int x = (int)moon_checkint( L, 1, INT_MIN, INT_MAX );
|
|
int y = (int)moon_checkint( L, 2, INT_MIN, INT_MAX );
|
|
/* Usually, `moon_newpointer` is used when your API handles memory
|
|
* allocation and deallocation for its types: */
|
|
void** p = moon_newpointer( L, "D", freeD );
|
|
/* The cleanup function will only run if the pointer is set to a
|
|
* non-NULL value. `moon_checkobject` also will raise an error when
|
|
* passed a NULL object! */
|
|
*p = newD( x, y );
|
|
if( !*p )
|
|
luaL_error( L, "memory allocation error" );
|
|
lua_newtable( L );
|
|
#if LUA_VERSION_NUM < 502
|
|
lua_setfenv( L, -2 );
|
|
#else
|
|
lua_setuservalue( L, -2 );
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
|
|
int luaopen_objex( lua_State* L ) {
|
|
luaL_Reg const objex_funcs[] = {
|
|
{ "getAmethods", objex_getAmethods },
|
|
{ "newA", objex_newA },
|
|
{ "newB", objex_newB },
|
|
{ "newC", objex_newC },
|
|
{ "newD", objex_newD },
|
|
{ "getD", objex_getD },
|
|
{ "makeD", objex_makeD },
|
|
{ "derive", moon_derive },
|
|
{ "downcast", moon_downcast },
|
|
{ NULL, NULL }
|
|
};
|
|
/* You put metamethods and normal methods in the same luaL_Reg
|
|
* array. `moon_defobject` puts the functions in the right place
|
|
* automatically. */
|
|
luaL_Reg const A_methods[] = {
|
|
{ "__index", A_index },
|
|
{ "switch", A_switch },
|
|
{ "printme", A_printme },
|
|
{ NULL, NULL }
|
|
};
|
|
luaL_Reg const B_methods[] = {
|
|
{ ".f", B_property_f },
|
|
#if 0
|
|
{ "__index", B_index },
|
|
{ "__newindex", B_newindex },
|
|
{ "printme", B_printme },
|
|
#endif
|
|
{ NULL, NULL }
|
|
};
|
|
luaL_Reg const C_methods[] = {
|
|
{ "__index", C_index },
|
|
{ "__newindex", C_newindex },
|
|
{ "printme", C_printme },
|
|
/* A C object can be cast to a D object (see below), so C objects
|
|
* can use the methods of the D type! */
|
|
{ "printmeD", D_printme },
|
|
{ "close", C_close },
|
|
{ NULL, NULL }
|
|
};
|
|
luaL_Reg const D_methods[] = {
|
|
{ "__index", D_index },
|
|
{ "__newindex", D_newindex },
|
|
{ "printme", D_printme },
|
|
{ "vcall", D_vcall },
|
|
{ NULL, NULL }
|
|
};
|
|
/* All object types must be defined once (this creates the
|
|
* metatables): */
|
|
moon_defobject( L, "A", sizeof( A ), A_methods, 0 );
|
|
(void)B_printme; /* avoid warning */
|
|
lua_pushinteger( L, 1 );
|
|
lua_pushinteger( L, 2 );
|
|
moon_defobject( L, "B", sizeof( B ), B_methods, 2 );
|
|
lua_pushinteger( L, 1 );
|
|
lua_pushinteger( L, 2 );
|
|
moon_defobject( L, "C", sizeof( C ), C_methods, 2 );
|
|
moon_defobject( L, "D", sizeof( D ), D_methods, 0 );
|
|
/* Add a type cast from a C object to the embedded D object. The
|
|
* cast is executed automatically during moon_checkobject. */
|
|
moon_defcast( L, "C", "D", C_to_D );
|
|
#if LUA_VERSION_NUM < 502
|
|
luaL_register( L, "objex", objex_funcs );
|
|
#else
|
|
luaL_newlib( L, objex_funcs );
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
|