mesecons icon indicating copy to clipboard operation
mesecons copied to clipboard

Add library support to Luacontrollers

Open cheapie opened this issue 3 years ago • 10 comments

This allows mods to provide their own libraries that can be accessed from within a Luacontroller, for example to make working with advanced digilines peripherals somewhat easier. Libraries can be added to the mesecon.luacontroller_libraries table, and then the code running in the Luacontroller can use require() to request one. require() will return nil if the library is not present.

As some examples of where this could be useful:

  • One of my mods (digistuff) provides a "touchscreen" that can display various GUIs, but controlling it is rather difficult. I have written a library to do it (TSLib) but at the moment the only way to use the library is to copy/paste it into the beginning of the LuaC program. This change would allow digistuff to supply the library itself, and LuaC programs could simply require("TSLib") to obtain a copy.
  • On servers that use lightweight interrupts, a mod could provide various helper functions to implement IIDs in another way, or possibly provide an alternative to the whole interrupt() mechanism altogether.
  • A server owner may desire to make certain game-related functions (such as minetest.get_craft_result(), minetest.get_server_status(), or possibly even the entire "minetest" table in singleplayer) available within Luacontrollers. This would allow that to be done without editing the mesecons_luacontroller mod itself.

cheapie avatar Mar 21 '21 06:03 cheapie

for adding something like this, and for using require. But I don't see the need to change the envs of the funcs.

The envs need to be changed, otherwise the library functions are unable to access normal Luacontroller functionality (digiline_send(), interrupt(), event, pin, port, etc.)

cheapie avatar Mar 26 '21 14:03 cheapie

Yes I know. But if you use functions in mesecon.luacontroller_libraries, as suggested, you can generate the libraries depending on the env (and so access the env stuff via setfenv of your function or using the env as upvalue in your library functions).

Desour avatar Mar 26 '21 15:03 Desour

Hmm... how about if I was to allow libraries to be registered either as a table or a function - if they're a table then it would do what ti does now, or if it's a function then it would be called as you suggested. This way you could have all sorts of advanced stuff like you suggested, whereas simple libraries like the ones I was more after don't need to specially handle the environment themselves.

cheapie avatar Mar 26 '21 18:03 cheapie

¯\_(ツ)_/¯ The current way of changing the env modifies your given functions every time a luacontroller executes its code, and imo is just weird. Consider these examples:

local function f1(c)
	digiline_send(c, "bla")
end
local function f2(c)
	f1(c.."2")
end
local function f3(p)
	return minetest.get_node(p)
end
local get_node = minetest.get_node
local function f4(p)
	return get_node(p)
end
local incl_both = false
mesecon.luacontroller_libraries["foo"] = {
	f2 = f2,
	f1 = incl_both and f1 or nil,
	f3 = f3,
	f4 = f4,
}

In the luacontroller f2 won't work because f1 can't access digiline_send, but if you set incl_both to true it will work. Also, f3 doesn't work, but f4 does. I consider such things too confusing to be the default.

With the more general approach, it's more explicit, but not really more complex imo:

mesecon.luacontroller_libraries["foo"] = function(env, pos)
	local function f1(c)
		env.digiline_send(c, "bla")
	end
	local function f2(c)
		f1(c.."2")
	end
	local function f3(p)
		return minetest.get_node(p)
	end
	local get_node = minetest.get_node
	local function f4(p)
		return get_node(p)
	end
	local incl_both = false
	return {
		f2 = f2,
		f1 = incl_both and f1 or nil,
		f3 = f3,
		f4 = f4,
	}
end

You could allow both ways, but I wouldn't encourage using the one that sets the envs.

Edit: Also, the standard luacontroller env functions use the normal env, so I could also reason with consistency.

Edit2: The libraries you think of probably do something that could be done with the normal luacontroller functions (=> require instead of copy). Such things could be done by local lib = loadfile(path); mesecon.luacontroller_libraries["foo"] = function(env, pos) return setfenv(lib)() end;, then you even have your loacontroller code in a separate file where you can make a normal module and do all the stuff you can do in the luacontroller. (But beware of require-cycles that are not handled by the current require implementation.)

Btw. this PR fixes #428.

Desour avatar Mar 26 '21 19:03 Desour

I will admit that the current way is a little strange in regards to how the environment behaves, but it's the cleanest way I could come up with for libraries that just aim to do things that the LuaC could already do, just without copy/pasting a bunch - like the one for the touchscreen that just gives a bunch of functions that eventually call digiline_send(), or in #428 where the goal is just to supply (copies of) a table without any functions at all.

Both of these are doable in the way you suggest, but with additional requirements (the mod registering it would need to supply a getter function).

Give me a few minutes here and I'll see about coming up with a (hopefully) relatively clean way to just handle both.

cheapie avatar Mar 26 '21 19:03 cheapie

Is this still relevant/active?

I think this feature will open up a whole new ecosystem of pluggable libraries and i'm looking forward to that :+1:

Can i help somehow here? Does this need further testing or documentation?

BuckarooBanzay avatar Jun 08 '21 06:06 BuckarooBanzay

Is this still relevant/active?

I don't know - I pushed that last set of changes after the previous message and never heard anything about it after that.

cheapie avatar Jun 09 '21 01:06 cheapie

The string metatable sandbox should probably be mentioned in the documentation. Furthermore, require should exit the string metatable sandbox while loading libraries. It might also be good to store loaded libraries and return them if they are required again, as this would match the behavior of the "real" require.

TurkeyMcMac avatar Jan 25 '22 02:01 TurkeyMcMac

fyi: this has been forked and implemented here: https://github.com/mt-mods/mesecons not sure if this is still relevant/feasible to merge here

BuckarooBanzay avatar Jan 25 '22 06:01 BuckarooBanzay

To elaborate on the string metatable point: Say the library function is called at https://github.com/minetest-mods/mesecons/blob/91e3b13d5acf40af5531994eb7eb847de5f6afcc/mesecons_luacontroller/init.lua#L473 and it in turn calls another function. This function may not expect to be run inside the string metatable sandbox. If it tries to call s:find with a pattern as an argument, for example, it will crash unexpectedly.

TurkeyMcMac avatar Jan 25 '22 13:01 TurkeyMcMac