Browse Source

Lua data model improvements: Add arrays, structures and event callbacks (#150)

* Polish the lua data model.

* Compatible with Lua 5.2

* Some fixes.

* Some fixes
actboy168 5 years ago
parent
commit
7dbf49c8be
2 changed files with 254 additions and 64 deletions
  1. 9 4
      Samples/luainvaders/data/options.rml
  2. 245 60
      Source/Lua/LuaDataModel.cpp

+ 9 - 4
Samples/luainvaders/data/options.rml

@@ -31,6 +31,10 @@ if Options.datamodel == nil then
 	Options.datamodel = rmlui.contexts["main"]:OpenDataModel("options", {
 		graphics = 'ok',
 		options_changed = false,
+		audios = {
+			{id="reverb", label="Reverb", checked=true},
+			{id="3d", label="3D Spatialisation"},
+		}
 	})
 end
 
@@ -72,8 +76,8 @@ function Options.LoadOptions(document)
     
     AsInput(document:GetElementById(options['graphics'])).checked = true
     --because everything is loaded as a string, we have to fool around with the boolean variables
-    AsInput(document:GetElementById('reverb')).checked = (options['reverb'] == 'true')
-    AsInput(document:GetElementById('3d')).checked = (options['3d'] == 'true')
+	Options.datamodel.audios[1].checked = (options['reverb'] == 'true')
+	Options.datamodel.audios[2].checked = (options['3d'] == 'true')
 	Options.datamodel.options_changed = false
 end
 
@@ -119,8 +123,9 @@ end
 				<p data-if="graphics == 'bad'">Are you sure about this? Bad graphics are just plain <em>bad.</em></p>
 				<p>
 					Audio:<br />
-					<input id="reverb" type="checkbox" name="reverb" checked /> Reverb<br />
-					<input id="3d" type="checkbox" name="3d" /> 3D Spatialisation
+					<span data-for="audio : audios">
+						<input data-attr-id="audio.id" type="checkbox" data-attr-name="audio.id" data-attrif-checked="audio.checked" /> {{audio.label}}<br />
+					</span>
 				</p>
 			</div>
 			<input type="submit" name="button" value="accept" data-attrif-disabled="!options_changed">Accept</input>

+ 245 - 60
Source/Lua/LuaDataModel.cpp

@@ -37,40 +37,232 @@
 namespace Rml {
 namespace Lua {
 
+namespace luabind {
+#if LUA_VERSION_NUM < 503
+	static void lua_reverse(lua_State* L, int a, int b) {
+		for (; a < b; ++a, --b) {
+			lua_pushvalue(L, a);
+			lua_pushvalue(L, b);
+			lua_replace(L, a);
+			lua_replace(L, b);
+		}
+	}
+	void lua_rotate(lua_State* L, int idx, int n) {
+		int n_elems = 0;
+		idx = lua_absindex(L, idx);
+		n_elems = lua_gettop(L) - idx + 1;
+		if (n < 0) {
+			n += n_elems;
+		}
+		if (n > 0 && n < n_elems) {
+			luaL_checkstack(L, 2, "not enough stack slots available");
+			n = n_elems - n;
+			lua_reverse(L, idx, idx + n - 1);
+			lua_reverse(L, idx + n, idx + n_elems - 1);
+			lua_reverse(L, idx, idx + n_elems - 1);
+		}
+	}
+#endif
+	using call_t = Rml::Function<void(void)>;
+	inline int errhandler(lua_State* L) {
+		const char* msg = lua_tostring(L, 1);
+		if (msg == NULL) {
+			if (luaL_callmeta(L, 1, "__tostring") && lua_type(L, -1) == LUA_TSTRING)
+				return 1;
+			else
+				msg = lua_pushfstring(L, "(error object is a %s value)", luaL_typename(L, 1));
+		}
+		luaL_traceback(L, L, msg, 1);
+		return 1;
+	}
+	inline void errfunc(const char* msg) {
+		Log::Message(Log::LT_WARNING, "%s", msg);
+	}
+	inline int function_call(lua_State* L) {
+		call_t& f = *(call_t*)lua_touserdata(L, 1);
+		f();
+		return 0;
+	}
+	inline bool invoke(lua_State* L, call_t f, int argn = 0) {
+		if (!lua_checkstack(L, 3)) {
+			errfunc("stack overflow");
+			lua_pop(L, argn);
+			return false;
+		}
+		lua_pushcfunction(L, errhandler);
+		lua_pushcfunction(L, function_call);
+		lua_pushlightuserdata(L, &f);
+		lua_rotate(L, -argn - 3, 3);
+		if (lua_pcall(L, 1 + argn, 0, lua_gettop(L) - argn - 2) != LUA_OK) {
+			errfunc(lua_tostring(L, -1));
+			lua_pop(L, 2);
+			return false;
+		}
+		lua_pop(L, 1);
+		return true;
+	}
+}
+
 class LuaScalarDef;
+class LuaTableDef;
 
 struct LuaDataModel {
+	DataModelConstructor constructor;
 	DataModelHandle handle;
 	lua_State *dataL;
 	LuaScalarDef *scalarDef;
+	LuaTableDef *tableDef;
+	int top;
 };
 
+class LuaTableDef : public VariableDefinition {
+public:
+	LuaTableDef(const struct LuaDataModel* model);
+	bool Get(void* ptr, Variant& variant) override;
+	bool Set(void* ptr, const Variant& variant) override;
+	int Size(void* ptr) override;
+	DataVariable Child(void* ptr, const DataAddressEntry& address) override;
+protected:
+	const struct LuaDataModel* model;
+};
 
-class LuaScalarDef final : public VariableDefinition {
+class LuaScalarDef final : public LuaTableDef {
 public:
-	LuaScalarDef (const struct LuaDataModel *model) :
-		VariableDefinition(DataVariableType::Scalar), model(model) {}
-private:
-	bool Get(void* ptr, Variant& variant) override {
-		lua_State *L = model->dataL;
-		if (!L)
-			return false;
-		int id = int((intptr_t)ptr);
-		GetVariant(L, id, &variant);
-		return true;
+	LuaScalarDef(const struct LuaDataModel* model);
+	DataVariable Child(void* ptr, const DataAddressEntry& address) override;
+};
+
+LuaTableDef::LuaTableDef(const struct LuaDataModel *model)
+	: VariableDefinition(DataVariableType::Scalar)
+	, model(model)
+{}
+
+bool LuaTableDef::Get(void* ptr, Variant& variant) {
+	lua_State *L = model->dataL;
+	if (!L)
+		return false;
+	int id = (int)(intptr_t)ptr;
+	GetVariant(L, id, &variant);
+	return true;
+}
+
+bool LuaTableDef::Set(void* ptr, const Variant& variant) {
+	int id = (int)(intptr_t)ptr;
+	lua_State *L = model->dataL;
+	if (!L)
+		return false;
+	PushVariant(L, &variant);
+	lua_replace(L, id);
+	return true;
+}
+
+
+static int
+lLuaTableDefSize(lua_State* L) {
+	lua_pushinteger(L, luaL_len(L, 1));
+	return 1;
+}
+
+static int
+lLuaTableDefChild(lua_State* L) {
+	lua_gettable(L, 1);
+	return 1;
+}
+
+int LuaTableDef::Size(void* ptr) {
+	lua_State* L = model->dataL;
+	if (!L)
+		return 0;
+	int id = (int)(intptr_t)ptr;
+	if (lua_type(L, id) != LUA_TTABLE) {
+		return 0;
 	}
-	bool Set(void* ptr, const Rml::Variant& variant) override {
-		int id = int((intptr_t)ptr);
-		lua_State *L = model->dataL;
-		if (!L)
-			return false;
-		PushVariant(L, &variant);
-		lua_replace(L, id);
-		return true;
+	if (!lua_checkstack(L, 4)) {
+		return 0;
+	}
+	lua_pushcfunction(L, lLuaTableDefSize);
+	lua_pushvalue(L, id);
+	if (LUA_OK != lua_pcall(L, 1, 1, 0)) {
+		lua_pop(L, 1);
+		return 0;
 	}
+	int size = (int)lua_tointeger(L, -1);
+	lua_pop(L, 1);
+	return size;
+}
 
-	const struct LuaDataModel *model;
-};
+DataVariable LuaTableDef::Child(void* ptr, const DataAddressEntry& address) {
+	lua_State* L = model->dataL;
+	if (!L)
+		return DataVariable{};
+	int id = (int)(intptr_t)ptr;
+	if (lua_type(L, id) != LUA_TTABLE) {
+		return DataVariable{};
+	}
+	if (!lua_checkstack(L, 4)) {
+		return DataVariable{};
+	}
+	lua_pushcfunction(L, lLuaTableDefChild);
+	lua_pushvalue(L, id);
+	if (address.index == -1) {
+		lua_pushlstring(L, address.name.data(), address.name.size());
+	}
+	else {
+		lua_pushinteger(L, (lua_Integer)address.index + 1);
+	}
+	if (LUA_OK != lua_pcall(L, 2, 1, 0)) {
+		lua_pop(L, 1);
+		return DataVariable{};
+	}
+	return DataVariable(model->tableDef, (void*)(intptr_t)lua_gettop(L));
+}
+
+LuaScalarDef::LuaScalarDef(const struct LuaDataModel* model)
+	: LuaTableDef(model)
+{}
+
+DataVariable LuaScalarDef::Child(void* ptr, const DataAddressEntry& address) {
+	lua_State* L = model->dataL;
+	if (!L)
+		return DataVariable{};
+	lua_settop(L, model->top);
+	return LuaTableDef::Child(ptr, address);
+}
+
+static void
+BindVariable(struct LuaDataModel* D, lua_State* L) {
+	lua_State* dataL = D->dataL;
+	if (!lua_checkstack(dataL, 4)) {
+		luaL_error(L, "Memory Error");
+	}
+	int id = lua_gettop(dataL) + 1;
+	D->top = id;
+	// L top : key value
+	lua_xmove(L, dataL, 1);	// move value to dataL with index(id)
+	lua_pushvalue(L, -1);	// dup key
+	lua_xmove(L, dataL, 1);
+	lua_pushinteger(dataL, id);
+	lua_rawset(dataL, 1);
+	const char* key = lua_tostring(L, -1);
+	if (lua_type(dataL, D->top) == LUA_TFUNCTION) {
+		D->constructor.BindEventCallback(key, [=](DataModelHandle, Event& event, const VariantList& varlist) {
+			lua_pushvalue(dataL, id);
+			lua_xmove(dataL, L, 1);
+			luabind::invoke(L, [&](){
+				LuaType<Event>::push(L,&event,false);
+				for (auto const& variant : varlist) {
+					PushVariant(L, &variant);
+				}
+				lua_call(L, (int)varlist.size() + 1, 0);
+			}, 1);
+		});
+	}
+	else {
+		D->constructor.BindCustomDataVariable(key,
+			DataVariable(D->scalarDef, (void*)(intptr_t)id)
+		);
+	}
+}
 
 static int
 getId(lua_State *L, lua_State *dataL) {
@@ -102,51 +294,32 @@ static int
 lDataModelSet(lua_State *L) {
 	struct LuaDataModel *D = (struct LuaDataModel *)lua_touserdata(L, 1);
 	lua_State *dataL = D->dataL;
-	if (dataL == nullptr)
-		luaL_error(L, "DataModel closed");
-	int id = getId(L, dataL);
-	lua_xmove(L, dataL, 1);
-	lua_replace(dataL, id);
-	D->handle.DirtyVariable(lua_tostring(L, 2));
-	return 0;
-}
+	if (dataL == NULL)
+		luaL_error(L, "DataModel released");
+	lua_settop(dataL, D->top);
 
-// Construct a lua sub thread for LuaDataModel
-// stack 1 : { name(string) -> id(integer) }
-// stack 2- : values
-// For example : build from { str = "Hello", x = 0 }
-//	1: { str = 2 , x = 3 }
-//	2: "Hello"
-//	3: 0
-static lua_State *
-InitDataModelFromTable(lua_State *L, int index, Rml::DataModelConstructor &ctor, class LuaScalarDef *def) {
-	lua_State *dataL = lua_newthread(L);
-	lua_newtable(dataL);
-	intptr_t id = 2;
-	lua_pushnil(L);
-	while (lua_next(L, index) != 0) {
-		if (!lua_checkstack(dataL, 4)) {
-			luaL_error(L, "Memory Error");
-		}
-		// L top : key value
-		lua_xmove(L, dataL, 1);	// move value to dataL with index(id)
-		lua_pushvalue(L, -1);	// dup key
+	lua_pushvalue(L, 2);
+	lua_xmove(L, dataL, 1);
+	lua_rawget(dataL, 1);
+	if (lua_type(dataL, -1) == LUA_TNUMBER) {
+		int id = (int)lua_tointeger(dataL, -1);
+		lua_pop(dataL, 1);
 		lua_xmove(L, dataL, 1);
-		lua_pushinteger(dataL, id);
-		lua_rawset(dataL, 1);
-		const char *key = lua_tostring(L, -1);
-		ctor.BindCustomDataVariable(key, Rml::DataVariable(def, (void *)id));
-		++id;
+		lua_replace(dataL, id);
+		D->handle.DirtyVariable(lua_tostring(L, 2));
+		return 0;
 	}
-	return dataL;
+	lua_pop(dataL, 1);
+	BindVariable(D, L);
+	return 0;
 }
 
 bool
-OpenLuaDataModel(lua_State *L, Rml::Context *context, int name_index, int table_index) {
-	Rml::String name = luaL_checkstring(L, name_index);
+OpenLuaDataModel(lua_State *L, Context *context, int name_index, int table_index) {
+	String name = luaL_checkstring(L, name_index);
 	luaL_checktype(L, table_index, LUA_TTABLE);
 
-	Rml::DataModelConstructor constructor = context->CreateDataModel(name);
+	DataModelConstructor constructor = context->CreateDataModel(name);
 	if (!constructor) {
 		constructor = context->GetDataModel(name);
 		if (!constructor) {
@@ -157,11 +330,20 @@ OpenLuaDataModel(lua_State *L, Rml::Context *context, int name_index, int table_
 	struct LuaDataModel *D = (struct LuaDataModel *)lua_newuserdata(L, sizeof(*D));
 	D->dataL = nullptr;
 	D->scalarDef = nullptr;
+	D->tableDef = nullptr;
+	D->constructor = constructor;
 	D->handle = constructor.GetModelHandle();
 
 	D->scalarDef = new LuaScalarDef(D);
-	D->dataL = InitDataModelFromTable(L, table_index, constructor, D->scalarDef);
-	lua_setuservalue(L, -2);	// set D->dataL into uservalue of D
+	D->tableDef = new LuaTableDef(D);
+	D->dataL = lua_newthread(L);
+	D->top = 1;
+	lua_newtable(D->dataL);
+	lua_pushnil(L);
+	while (lua_next(L, table_index) != 0) {
+		BindVariable(D, L);
+	}
+	lua_setuservalue(L, -2);
 
 	if (luaL_newmetatable(L, RMLDATAMODEL)) {
 		luaL_Reg l[] = {
@@ -185,8 +367,11 @@ CloseLuaDataModel(lua_State *L) {
 	luaL_checkudata(L, -1, RMLDATAMODEL);
 	struct LuaDataModel *D = (struct LuaDataModel *)lua_touserdata(L, -1);
 	D->dataL = nullptr;
+	D->top = 0;
 	delete D->scalarDef;
 	D->scalarDef = nullptr;
+	delete D->tableDef;
+	D->tableDef = nullptr;
 	lua_pushnil(L);
 	lua_setuservalue(L, -2);
 }