lua-users home
lua-l archive

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


Thank you for the responses. I went with Jerome's suggestions and now
I have a few follow-up questions.


> It is possible. In my_matrix[2][1] there are two metamethod calls. One
> on the matrix itself, and one on an intermediate object. To have this
> work, you need to make sure the __index of your matrix class returns
> an intermediate userdata, a vector. Then in that vector type you have
> to implement the __index and __newindex metamethods. To have this
> modify the original matrix, the vector object has to be a pointer to
> the original matrix rather than a copy of the row/column you indexed.

As you suggested, I introduced a new MyVector userdata type which gets
created and returned on the __index access of my Matrix. The userdata
for MyVector defines the __index and __newindex metamethods.

To make sure the MyVector data is shared with MyMatrix data, I naively
defined my MyVector as:
typedef struct MyVector
{
	MyMatrix2* myMatrix2; // pointer to myMatrix to share the data
	int theRow;
} MyVector;

This actually all seems to work and I'm quite surprised I got it
working on the first try.
But I realized there is one corner case I don't know how to handle.

In Lua, it is possible to now save a vector from the matrix, e.g.
my_vector1 = my_matrix[1]
my_vector2 = my_matrix[2]

I can modify the vector later and its changes will be reflected in the
matrix which is good. But I realized that the matrix can get released
by the garbage collector even though there might still be vectors that
have a pointer to it. So if I do this:
my_matrix = nil
collectgarbage()
print(my_vector1) -- I expect garbage or crashes

I think my vector is no longer good and I may be in trouble.


So what is the correct way to deal with this shared relationship?



> If performance is not an issue, remember that you can pass any value
> when indexing, including tables. So if you make your matrix
> metamethods accept tables, you can get the following syntax:
>
> my_matrix[{1, 2}] = 42
> assert( my_matrix[{1, 2}] == 42 )

I thought this was a very clever idea and I also tried implementing
it. It seemed to work very well. Implementation-wise, it was much
easier than the above situation. I also realized that I can use
userdata here too that have metatables that also respond to the same
indices. (I happened to have some other userdata types that kind of
fit-in.)

I haven't decided which way I'll go yet. One consideration is that I'm
trying to pass the API for the scripting team to use and not just for
myself. I'm familiar with the [x][y] notation. Is [{x, y}] seen in
other languages?

> By the way, now that I think about it, it could be nice to have a
> syntactic sugar that would transform [x, y] into [{x, y}].

I'm curious. Does [x, y] currently have any meaning in Lua?


New code for both Vector and [{x,y}] pasted below.
Thanks,
Eric


#include <lua/lua.h>
#include <lua/lualib.h>
#include <lua/lauxlib.h>
#include <string.h>
#include <stdbool.h>
#include "lua_matrix.h"


typedef struct MyMatrix2
{
	double m11, m12;
	double m21, m22;
} MyMatrix2;

typedef struct MyVector
{
	MyMatrix2* myMatrix2;
	int theRow;
} MyVector;

const char* MYMATRIX2_METATABLEID = "MyMatrix2";
const char* MYVECTOR_METATABLEID = "MyVector";

static MyMatrix2* LuaCheckMyMatrix2(lua_State* lua_state, int ud)
{
	return luaL_checkudata(lua_state, ud, MYMATRIX2_METATABLEID);
}

static MyVector* LuaCheckMyVector(lua_State* lua_state, int ud)
{
	return luaL_checkudata(lua_state, ud, MYVECTOR_METATABLEID);
}


static int MyMatrix2ToLua(lua_State* lua_state)
{
	MyMatrix2* the_struct = LuaCheckMyMatrix2(lua_state, -1);
    lua_newtable(lua_state);
	
		lua_pushnumber(lua_state, the_struct->m11);
		lua_setfield(lua_state, -2, "m11");

		lua_pushnumber(lua_state, the_struct->m12);
		lua_setfield(lua_state, -2, "m12");

		lua_pushnumber(lua_state, the_struct->m21);
		lua_setfield(lua_state, -2, "m21");

		lua_pushnumber(lua_state, the_struct->m22);
		lua_setfield(lua_state, -2, "m22");
		
    return 1;
}


static int MyMatrix2FromLua(lua_State* lua_state)
{
	int number_of_args = lua_gettop(lua_state);
	MyMatrix2 the_struct;
	if(0 == number_of_args)
	{
		// Allow empty to build the identity matrix
		the_struct.m11 = 1.0; the_struct.m12 = 0.0;
		the_struct.m21 = 0.0; the_struct.m22 = 1.0;
	}
	else if(1 == number_of_args)
	{
		if (!lua_istable(lua_state, -1))
		{
			luaL_error(lua_state, "Invalid parameters to create MyMatrix2");
		}
		
		lua_getfield(lua_state, -1, "m11");
		the_struct.m11 = luaL_checknumber(lua_state, -1);
		lua_pop(lua_state, 1);

		lua_getfield(lua_state, -1, "m12");
		the_struct.m12 = luaL_checknumber(lua_state, -1);
		lua_pop(lua_state, 1);

		lua_getfield(lua_state, -1, "m21");
		the_struct.m21 = luaL_checknumber(lua_state, -1);
		lua_pop(lua_state, 1);

		lua_getfield(lua_state, -1, "m22");
		the_struct.m22 = luaL_checknumber(lua_state, -1);
		lua_pop(lua_state, 1);
	}
	else if(4 == number_of_args)
	{
		the_struct.m11 = luaL_checknumber(lua_state, -4);
		the_struct.m12 = luaL_checknumber(lua_state, -3);
		the_struct.m21 = luaL_checknumber(lua_state, -2);
		the_struct.m22 = luaL_checknumber(lua_state, -1);
	}
	else
	{
		luaL_error(lua_state, "Invalid parameters to create MyMatrix2");
	}
	void* the_result = lua_newuserdata(lua_state, sizeof(MyMatrix2));
	memcpy(the_result, &(the_struct), sizeof(MyMatrix2));

	luaL_getmetatable(lua_state, MYMATRIX2_METATABLEID);
	lua_setmetatable(lua_state, -2);
	
    return 1;
}

static int ConvertMyMatrix2ToString(lua_State* lua_state)
{
	MyMatrix2* the_struct = LuaCheckMyMatrix2(lua_state, -1);
	lua_pushfstring(lua_state, "\n%f  %f\n%f  %f\n",
		the_struct->m11, the_struct->m12,
		the_struct->m21, the_struct->m22);
	return 1;
}
static int ConvertMyVectorToString(lua_State* lua_state)
{
	MyVector* the_vector = LuaCheckMyVector(lua_state, -1);
	MyMatrix2* the_struct = the_vector->myMatrix2;
	
	if(the_vector->theRow == 1)
	{
		lua_pushfstring(lua_state, "Vector Row 1:  %f  %f\n",
			the_struct->m11, the_struct->m12);
	}
	else
	{
		lua_pushfstring(lua_state, "Vector Row 2:  %f  %f\n",
			the_struct->m21, the_struct->m22);
	}

	return 1;
}




static int GetIndexOnMyMatrix2(lua_State* lua_state)
{
//	fprintf(stderr, "GetIndexOnMyMatrix2\n");
	MyMatrix2* the_struct = LuaCheckMyMatrix2(lua_state, -2);
	
	int array_index = 0;
	bool use_2d = false;
	// allows my_matrix[1] through my_matrix[4]
	if(lua_isnumber(lua_state, -1))
	{
		array_index = lua_tointeger(lua_state, -1);
		use_2d = true;
	}
	// allows my_matrix.m11 my_matrix.m12, my_matrix.m21, my_matrix.m22
	else if(lua_isstring(lua_state, -1))
	{
		const char* index_string = lua_tostring(lua_state, -1);

		if(!strcmp("m11", index_string))
		{
			array_index = 1;
		}
		else if(!strcmp("m12", index_string))
		{
			array_index = 2;
		}
		else if(!strcmp("m21", index_string))
		{
			array_index = 3;
		}
		else if(!strcmp("m22", index_string))
		{
			array_index = 4;
		}
		else
		{
			luaL_error(lua_state, "invalid member access '%s' of MyMatrix2
__index", index_string);
		}
	}
	else if(lua_istable(lua_state, -1) || lua_isuserdata(lua_state, -1))
	{
//		fprintf(stderr, "GetIndexOnMyMatrix2: userdata\n");
		int first_index;
		int second_index;
		lua_pushnumber(lua_state, 1); // first element
		lua_gettable(lua_state, -2);
		first_index = luaL_checkint(lua_state, -1);
		lua_pop(lua_state, 1);
		lua_pushnumber(lua_state, 2); // second element
		lua_gettable(lua_state, -2);
		second_index = luaL_checkint(lua_state, -1);
		lua_pop(lua_state, 1);

		array_index = 2*(first_index-1) + second_index;
	}
	else
	{
		luaL_error(lua_state, "invalid member access of MyMatrix2 __index");
	}
	if(use_2d)
	{
		luaL_argcheck(lua_state, 1 <= array_index && array_index <= 2, -1,
"index out of range");
//		fprintf(stderr, "Creating Vector for row: %d\n", array_index);
		MyVector* return_vector = (MyVector*)lua_newuserdata(lua_state,
sizeof(MyVector));
		return_vector->myMatrix2 = the_struct;
		return_vector->theRow = array_index;
		luaL_getmetatable(lua_state, MYVECTOR_METATABLEID);
		lua_setmetatable(lua_state, -2);
	}
	else
	{
		luaL_argcheck(lua_state, 1 <= array_index && array_index <= 4, -1,
"index out of range");
		if(array_index == 1)
		{
			lua_pushnumber(lua_state, the_struct->m11);
		}
		else if(array_index == 2)
		{
			lua_pushnumber(lua_state, the_struct->m12);
		}	
		else if(array_index == 3)
		{
			lua_pushnumber(lua_state, the_struct->m21);
		}
		else if(array_index == 4)
		{
			lua_pushnumber(lua_state, the_struct->m22);
		}
	}

	return 1;
}


static int SetIndexOnMyMatrix2(lua_State* lua_state)
{
	MyMatrix2* the_struct = LuaCheckMyMatrix2(lua_state, -3);
	int element_index = 0;
	
//	fprintf(stderr, "SetIndexOnMyMatrix2\n");
	if(lua_isnumber(lua_state, -2))
	{
		printf("is_number\n");
		element_index = lua_tointeger(lua_state, -2);
	}
	else if(lua_isstring(lua_state, -2))
	{
//		printf("is_string\n");
		const char* index_string = lua_tostring(lua_state, -2);

		if(!strcmp("m11", index_string))
		{
			element_index = 1;
		}
		else if(!strcmp("m12", index_string))
		{
			element_index = 2;
		}
		else if(!strcmp("m21", index_string))
		{
			element_index = 3;
		}
		else if(!strcmp("m22", index_string))
		{
			element_index = 4;
		}
		else
		{
			luaL_error(lua_state, "invalid member access '%s' of MyMatrix2
__newindex", index_string);
		}
	}
	// This supports array access to fake 2-dimensional calls,
	// e.g foo[{2, 1}] is equal to foo[3]
	// This attempts to support tables and userdata, presuming
	// the userdata has a metamethod to respond to an index.
	else if(lua_istable(lua_state, -2) || lua_isuserdata(lua_state, -2))
	{
//		fprintf(stderr, "SetIndexOnMyMatrix2: userdata\n");
		int first_index;
		int second_index;
		lua_pushvalue(lua_state, -2); // copy table on top of stack: [table]
		lua_pushnumber(lua_state, 1); // first index: [table number]
		lua_gettable(lua_state, -2); // stack: [table return_number]

		first_index = luaL_checkint(lua_state, -1);
		lua_pop(lua_state, 1); // stack: [table]

		lua_pushnumber(lua_state, 2); // second index: [table number]
		lua_gettable(lua_state, -2); // stack: [table return_number]

		second_index = luaL_checkint(lua_state, -1);
		lua_pop(lua_state, 1); // stack: [table]

		lua_pop(lua_state, 1); // clear table copy

		element_index = 2*(first_index-1) + second_index;
		printf("array_index=%d\n", element_index);
	}
	else
	{
		printf("what is this: %d\n", lua_type(lua_state, -2));

	}

	luaL_argcheck(lua_state, 1 <= element_index && element_index <= 4,
-2, "index out of range");
	int new_value = luaL_checknumber(lua_state, -1);
	
	if(element_index == 1)
	{
		the_struct->m11 = new_value;
	}
	else if(element_index == 2)
	{
		the_struct->m12 = new_value;
	}	
	else if(element_index == 3)
	{
		the_struct->m21 = new_value;
	}
	else if(element_index == 4)
	{
		the_struct->m22 = new_value;
	}	
	return 0;
}

static int GetIndexOnMyVector(lua_State* lua_state)
{
//	fprintf(stderr, "GetIndexOnMyVector\n");
	MyVector* my_vector = LuaCheckMyVector(lua_state, -2);
	MyMatrix2* the_struct = my_vector->myMatrix2;
	
	int array_index = 0;
	bool use_2d = false;
	if(lua_isnumber(lua_state, -1))
	{
		array_index = lua_tointeger(lua_state, -1);
		use_2d = true;
	}
	else
	{
		luaL_error(lua_state, "invalid member access of MyVector __index");
	}
	luaL_argcheck(lua_state, 1 <= array_index && array_index <= 2, -1,
"index out of range");
	if(array_index == 1)
	{
		if(my_vector->theRow == 1)
		{
			lua_pushnumber(lua_state, the_struct->m11);
		}
		else
		{
			lua_pushnumber(lua_state, the_struct->m21);
		}
	}
	else if(array_index == 2)
	{
		if(my_vector->theRow == 1)
		{
			lua_pushnumber(lua_state, the_struct->m12);
		}
		else
		{
			lua_pushnumber(lua_state, the_struct->m22);
		}
	}	
	return 1;
}

static int SetIndexOnMyVector(lua_State* lua_state)
{
//	fprintf(stderr, "SetIndexOnMyVector\n");
	MyVector* my_vector = LuaCheckMyVector(lua_state, -3);
	MyMatrix2* the_struct = my_vector->myMatrix2;
	int array_index = 0;
	
	if(lua_isnumber(lua_state, -2))
	{
		array_index = lua_tointeger(lua_state, -2);
	}
	else
	{
		fprintf(stderr, "what is this: %d\n", lua_type(lua_state, -2));
		
		luaL_error(lua_state, "invalid member access of MyVector __newindex");
	}
	luaL_argcheck(lua_state, 1 <= array_index && array_index <= 2, -2,
"index out of range");
	int new_value = luaL_checknumber(lua_state, -1);
	
	if(array_index == 1)
	{
		if(my_vector->theRow == 1)
		{
			the_struct->m11 = new_value;
		}
		else
		{
			the_struct->m21 = new_value;
		}
	}
	else if(array_index == 2)
	{
		if(my_vector->theRow == 1)
		{
			the_struct->m12 = new_value;
		}
		else
		{
			the_struct->m22 = new_value;
		}
	}	
	return 0;
}


static int MyMatrix2GC(lua_State* lua_state)
{
	fprintf(stderr, "MyMatrix2GC\n");
	MyMatrix2* the_struct = LuaCheckMyMatrix2(lua_state, -1);
	the_struct->m11 = -1.0;
	the_struct->m12 = -1.0;
	the_struct->m21 = -1.0;
	the_struct->m22 = -1.0;
	return 0;
}



static const struct luaL_reg methods_for_mymatrix2[] =
{
	{"__tostring", ConvertMyMatrix2ToString},
	{"__index", GetIndexOnMyMatrix2},
	{"__newindex", SetIndexOnMyMatrix2},
	{"__gc", MyMatrix2GC},
	{NULL,NULL},
};

static const struct luaL_reg methods_for_myvector[] =
{
	{"__tostring", ConvertMyVectorToString},
	{"__index", GetIndexOnMyVector},
	{"__newindex", SetIndexOnMyVector},
	{NULL,NULL},
};

static const luaL_reg lua_mymatrix2_functions[] =
{
	{"MyMatrix2FromLua", MyMatrix2FromLua},
	{"MyMatrix2ToLua", MyMatrix2ToLua},
	{NULL,NULL},

};

int luaopen_matrix2(lua_State* lua_state)
{
	luaL_newmetatable(lua_state, MYMATRIX2_METATABLEID);
//	lua_pushvalue(lua_state, -1);
//	lua_setfield(lua_state, -2, "__index");
	luaL_register(lua_state, NULL, methods_for_mymatrix2);

	luaL_newmetatable(lua_state, MYVECTOR_METATABLEID);
//	lua_pushvalue(lua_state, -1);
//	lua_setfield(lua_state, -2, "__index");
	luaL_register(lua_state, NULL, methods_for_myvector);

    luaL_register(lua_state, "matrix2", lua_mymatrix2_functions);
	return 1;
}