一步步进行 LUA的OOD封装 (二)

概要介绍了如何将lua面向过程的封装方式向面向对象的方向靠拢,这里开始逐步介绍其做法。还是以lua_tinker为主
首先我们知道lua中最接近c中类概念的就是它的表,在上一页里已经介绍过他们的相似之处了,这里也就不再继续扯这个了。
总体的目标是把一个C中类表述到lua中,让脚本能够识别传入的userdata是属于这个类的并且允许lua去使用这个类特定的类成员函数及其变量
第一步就是给这个即将在lua中出现的类命名,其实这个类名可能99%的时间是没机会在lua脚本中见到的,唯一1%的能用到该名字时候是在lua_tinker中不认识这种userdata时(比如你接口传递的时候用到了void*传userdata而非模板能识别的类参)需要强行用debug.semetatbale把这个数据和你用户在lua里拿到的数据给关联上
lua_tinker::class_add<T>(lua_State*L ,const char* name)是专门负责干这事的,其作用有2个

其一通过class_name<T> 这个产生一个global name ,好让以后在lua_tinker这层融合里面把模板能识别的类转到lua能接受的全局字串上
实现很简单,认真看看很好懂
template<typename T>
struct class_name
{
	static const char* name(const char* name = NULL)
	{
		static char temp[256] = "";
		if(name) strcpy(temp,name);
		return temp;
	}
}

其二是把这个name在lua里面建好以后要用到的metatable表,并且把修改这表的__index,__newindex,__gc 好用来接管脚本中 [] : 和gc释放时候的操作

这几点的使用例子和实现方式接下来会详细讲到。__name到没多少用,不是lua本身的meta规则的东西,tinker自己也没怎么用到

lua_pushstring(L, name);
		lua_newtable(L);


		lua_pushstring(L, "__name");
		lua_pushstring(L, name);
		lua_rawset(L, -3);


		lua_pushstring(L, "__index");
		lua_pushcclosure(L, meta_get, 0);
		lua_rawset(L, -3);


		lua_pushstring(L, "__newindex");
		lua_pushcclosure(L, meta_set, 0);
		lua_rawset(L, -3);


		lua_pushstring(L, "__gc");
		lua_pushcclosure(L, destroyer<T>, 0);
		lua_rawset(L, -3);


		lua_settable(L, LUA_GLOBALSINDEX);

顺带提一下 meta_get 和 meta_set这2个函数的实现方式决定了 metatable 也能之间形成类似继承的体系,(当然还得依赖class_inh这个函数),是个很巧妙的设计

int lua_tinker::meta_get(lua_State *L)
{
	lua_getmetatable(L,1);
	lua_pushvalue(L,2);
	lua_rawget(L,-2);


	if(lua_isuserdata(L,-1))
	{
		user2type<var_base*>::invoke(L,-1)->get(L);
		lua_remove(L, -2);
	}
	else if(lua_isnil(L,-1))
	{
		lua_remove(L,-1);
		invoke_parent(L);
		if(lua_isnil(L,-1))
		{
			lua_pushfstring(L, "can't find '%s' class variable. (forgot registering class variable ?)", lua_tostring(L, 2));
			lua_error(L);
		}
	} 


	lua_remove(L,-2);


	return 1;
}
user2type<var_base*>::invoke(L,-1) 是个从栈上按索引提取userdata数据到c这边来用的一个接口,本身用了很多特征模板的方式实现一些校检,比较难看懂也比较难讲明白,所以这里就偷个懒先跳过实现的部分了
上面的代码最要就干了大致这么一件事情:当 脚本中出现 c.a或c[a] 这个词法时,脚本切换到的宿主函数meta_get,先把 c从栈上拿出它的meta_table来看 有没有对a这个元操作有没有定义过,如果没有则用invoke_parent去找c的父类的meta_table 里找对a操作的定义,直到找到var_base*这个用来描述成员变量地址偏移,通过var_base::get(L)回传正确的成员变量到lua中


基本流程差不太多,在c[a] = b 或 c.a = b赋值操作时切换到的宿主函数meta_set 不过他在找不到的a时候会另外一种处理方式,类似stl::map[a]的赋值操作,产生一个对象来接受b的赋值,并且这个只会在本层发生不会再
劳驾invoke_parent去操作,同是通过var_base::set(L)让成员变量自己去栈上取到新值给自己赋值上
int lua_tinker::meta_set(lua_State *L)
{
	lua_getmetatable(L,1);
	lua_pushvalue(L,2);
	lua_rawget(L,-2);


	if(lua_isuserdata(L,-1))
	{
		user2type<var_base*>::invoke(L,-1)->set(L);
	}
	else if(lua_isnil(L, -1))
	{
		lua_pushvalue(L,2);
		lua_pushvalue(L,3);
		lua_rawset(L, -4);
	}
	lua_settop(L, 3);
	return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值