lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Hi,

I wrote something that may be useful. It compiles but it isn't tested
yet.
It's a function lua_xcopy that copies n lua values from one lua Stack
to another, like lua_xmove does. But contrary to lua_xmove, I try to
make a full copy, that is without making referances between the two
lua_Stacks.
It's not yet tested but if it works, it should copy without problems
numbers, strings, light userdatas, booleans, nils, tables (with
metatables) and functions (both C and lua, with upvalues and
environment) but not lua threads.
It will also copy userdatas with their metatables. The problem there is
that if a userdata is collected in one lua_Stack, lua will call the
__gc metamethod, and on the C side, some memory may be freed at this
time. So the same userdata on the other lua Stack may cause bugs
(trying to access freed memory). To prevent that, a metamethod __xcopy
is called on both metatables. Then the C side will know that it needs
two calls to __gc to free the data :)

I looked at pluto to help me with that.
I coded that because I also want multithreading in Lua :) A solution
that copies lua datas like that can make independant lua stacks
communicate without the need to lock the whole state by lua_lock and
lua_unlock.

Any thoughts about that ?

Mildred
-- 
Mildred       <xmpp:mildred@jabber.fr> <http://mildred632.free.fr/>
Clef GPG :    <hkp://pgp.mit.edu> ou <http://mildred632.free.fr/gpg_key>
Fingerprint : 197C A7E6 645B 4299 6D37 684B 6F9D A8D6 [9A7D 2E2B]


-- 
Mildred       <xmpp:mildred@jabber.fr> <http://mildred632.free.fr/>
Clef GPG :    <hkp://pgp.mit.edu> ou <http://mildred632.free.fr/gpg_key>
Fingerprint : 197C A7E6 645B 4299 6D37 684B 6F9D A8D6 [9A7D 2E2B]
/******************************************************************************
**
** Copyright (C) 2006 Mildred.
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to
** deal in the Software without restriction, including without limitation the
** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
** sell copies of the Software, and to permit persons to whom the Software is
** furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in
** all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
** IN THE SOFTWARE.
**
******************************************************************************/

#include <lua.h>
#include <lauxlib.h>
#include "lua_src/lapi.c"

#define lua_userdatalength(L,i) (uvalue(index2adr((L),(i)))->len)
#define lua_iscfunction(L,i) (clvalue(index2adr((L),(i)))->c.isC)
#define lua_getcfunction(L,i) (clvalue(index2adr((L),(i)))->c.f)

typedef struct {
	int        top;
	lua_State* L;
} xStack;

static void xcopy_value(lua_State* S, xStack* F, int idx, xStack* T);

/*
Notation :
	xStack* F	From stack
	xStack* T	To stack
*/

/* any nil, boolean, lightuserdata, number, string is equal to any other object with the same value. So there is no need to store those values in the stack S which is used to store referances to objects that can be encoutred multiples times */
static void xcopy_nil(xStack* T) {
	lua_pushnil(T->L);
	T->top++;
}
static void xcopy_boolean(xStack* F, int idx, xStack* T) {
	lua_pushboolean(T->L, lua_toboolean(F->L, idx));
	T->top++;
}
static void xcopy_lightuserdata(xStack* F, int idx, xStack* T) {
	lua_pushlightuserdata(T->L, lua_touserdata(F->L, idx));
	T->top++;
}
static void xcopy_number(xStack* F, int idx, xStack* T) {
	lua_pushnumber(T->L, lua_tonumber(F->L, idx));
	T->top++;
}
static void xcopy_string(xStack* F, int idx, xStack* T) {
	size_t sz;
	const char* s = lua_tolstring(F->L, idx, &sz);
	lua_pushlstring(T->L, s, sz);
	T->top++;
}

/* Tables and userdata have individual metatables (although multiple tables and userdata can share a same table as their metatable); values of all other types share one single metatable per type. So, there is one single metatable for all numbers, and for all strings, etc. */

/* First some utility functions */
static int already_exists(lua_State* S, xStack* F, int idx){
	/* idx : any valid index */
	lua_pushlightuserdata(S, (void*) lua_topointer(F->L, idx));
	lua_rawget(S, 1);
	if(lua_isnil(S, -1)) {
		lua_pop(S, 1);
		return 0;
	}
	return 1;
}
static void register_value(lua_State* S, xStack* T, int idx){
	/* idx : any valid index */
	lua_pushlightuserdata(S, (void*) lua_topointer(T->L, idx));
	lua_pushvalue(T->L, idx);
	lua_xmove(T->L, S, 1);
	lua_rawset(S, 1);
}
static void do_metatable(lua_State* S, xStack* F, int idx, xStack* T){
	/* idx : any valid index */
	lua_getmetatable(F->L, idx);
	F->top++;
	xcopy_value(S, F, F->top, T);
	lua_pop(F->L, 1);
	F->top--;
	lua_setmetatable(T->L, -2);
}

/* Now the xcopy_* functions */

static void xcopy_table(lua_State* S, xStack* F, int idx, xStack* T) {
	/* idx : a positive index ! */
	if(already_exists(S, F, idx)) return;
	lua_newtable(T->L);
	T->top++;
	lua_pushnil(F->L); /* first index */
	F->top++;
	while (lua_next(F->L, idx) != 0) {
		F->top++;
		xcopy_value(S, F, F->top-1, T); /* the key */
		xcopy_value(S, F, F->top,   T); /* the value */
		lua_rawset(T->L, -3);
		T->top--;
		T->top--;
		lua_pop(F->L, 1); /* remove value, keep the key */
		F->top--;
	}
	F->top--;
	do_metatable(S, F, idx, T);
	register_value(S, T, -1);
}

static void xcopy_userdata(lua_State* S, xStack* F, int idx, xStack* T) {
	/* idx : a positive index ! */
	if(already_exists(S, F, idx)) return;
	size_t sz = lua_userdatalength(F->L, idx);
	register char* u1 = (char*)lua_touserdata(F->L, idx);
	register char* u2 = lua_newuserdata(T->L, sz);
	size_t i;
	for(i=0; i<sz; i++) u2[i]=u1[i];
	T->top++;
	do_metatable(S, F, idx, T);
	/* There is a need to count the number of times the userdata is copied and garbage collected. If there is allocated memory, it mustn't be freed on the first call to __gc metamethod because the copy needs it. So, with that, it is possible to know exactly when the userdata is copied, and then count the times it is copied. */
	luaL_callmeta(F->L, idx, "__xcopy");
	lua_pop(F->L, 1);
	luaL_callmeta(T->L,  -1, "__xcopy");
	lua_pop(T->L, 1);
	register_value(S, T, -1);
}

static int writer (lua_State *L, const void* b, size_t size, void* B) {
	(void)L;
	luaL_addlstring((luaL_Buffer*) B, (const char *)b, size);
	return 0;
}

static void xcopy_function(lua_State* S, xStack* F, int idx, xStack* T) {
	/* idx : a positive index ! */
	if(already_exists(S, F, idx)) return;
	/* get the number of upvalues */
	lua_Debug ar;
	lua_pushvalue(F->L, idx);
	lua_getinfo(F->L, ">u", &ar);
	lua_pop(F->L, 1);
	/* depending on the function type */
	if(lua_iscfunction(F->L, idx)) {
		/* push those upvalues */
		int i;
		/* ipvalues indexes start at 0,
		see lua_pushcclosure source */
		for(i=0; i<ar.nups; i++){
			lua_getupvalue(F->L, idx, i);
			F->top++;
			xcopy_value(S, F, F->top, T);
			lua_pop(F->L, 1);
			F->top--;
		}
		lua_pushcclosure(T->L, lua_getcfunction(F->L, idx), ar.nups);
		T->top = T->top - ar.nups; /* remove upvalues */
		T->top++;
	}else{
		/* dump lua function and reload it */
		luaL_Buffer b;
		luaL_buffinit(T->L, &b);
		T->top++;
		if(lua_dump(F->L, writer, &b) != 0){
			/* error in dump, push nil function */
			lua_pushnil(T->L);
			return;
		}
		/* load the function */
		size_t sz;
		const char* s = lua_tolstring(T->L, -1, &sz);
		luaL_loadbuffer(T->L, s, sz, NULL);
		lua_remove(T->L, -2);
		/* set upvalues */
		int i;
		for(i=0; i<ar.nups; i++){
			lua_getupvalue(F->L, idx, i);
			F->top++;
			xcopy_value(S, F, F->top, T);
			lua_setupvalue(T->L, -2, i);
			T->top--;
			lua_pop(F->L, 1);
			F->top--;
		}
	}
	lua_getfenv(F->L, idx);
	F->top++;
	xcopy_value(S, F, F->top, T);
	lua_setfenv(T->L, -1);
	T->top--; /* setfenv pops an element */
	lua_pop(F->L, 1);
	F->top--;
	do_metatable(S, F, idx, T);
	register_value(S, T, -1);
}

static void xcopy_thread(lua_State* S, xStack* F, int idx, xStack* T) {
	/* no idea yet */
	xcopy_nil(T);
}

/* push on the stack L the value at index idx from stack O */
static void xcopy_value(lua_State* S, xStack* F, int idx, xStack* T) {
	/* idx : a positive index ! */
	int t = lua_type(F->L, idx);
	switch(t) {
	default:
	case LUA_TNONE:    {printf("unknown type %i\n", t);  } break;
	case LUA_TPROTO:   {printf("can't handle proto\n");  } break;
	case LUA_TUPVAL:   {printf("can't handle upval\n");  } break;
	case LUA_TDEADKEY: {printf("can't handle deadkey\n");} break;
	case LUA_TNIL:     {xcopy_nil          (T);          } break;
	case LUA_TBOOLEAN: {xcopy_boolean      (F, idx, T);  } break;
	case LUA_TLIGHTUSERDATA:
	                   {xcopy_lightuserdata(F, idx, T);  } break;
	case LUA_TNUMBER:  {xcopy_number       (F, idx, T);  } break;
	case LUA_TSTRING:  {xcopy_string       (F, idx, T);  } break;
	case LUA_TTABLE:   {xcopy_table     (S, F, idx, T);  } break;
	case LUA_TFUNCTION:{xcopy_function  (S, F, idx, T);  } break;
	case LUA_TUSERDATA:{xcopy_userdata  (S, F, idx, T);  } break;
	case LUA_TTHREAD:  {xcopy_thread    (S, F, idx, T);  } break;
	}
}


int lua_xcopy(lua_State* L1, lua_State* L2, int num) {
	/* if nothing to copy */
	if(num<=0) return 1;
	if(L1 == L2){
		lua_State* L = lua_newthread(L1);
		int top = lua_gettop(L1);
		lua_insert(L1, top-num);
		int res = lua_xcopy(L1, L, num);
		lua_xmove(L, L1, num);
		lua_remove(L1, top-num);
		return res;
	}
	xStack S1;
		S1.L = L1;
		S1.top = lua_gettop(L1);
	/* if wants to move more objects that the stack contains */
	if(S1.top < num) return 0;
	xStack S2;
		S2.L = L2;
		S2.top = lua_gettop(L2);
	/* save the top values */
	int top1 = S1.top;
	int top2 = S2.top;
	/* create a stack to cache values */
	lua_State* S = lua_newthread(S2.L);
	/* idx = first index (positive notation) */
	int idx = S1.top - num + 1;
	for(; idx <= top1; idx++){
		/* for each value to copy */
		xcopy_value(S, &S1, idx, &S2);
	}
	/* remove the tempoary stack S
	done at the end to prevent garbage collection */
	lua_remove(S2.L, top2 + 1);
	return 1;
}