Update:
Some further reading (local variable cannot be seen in a closure across the file?) gave me an "aha!" moment as to exactly why my code didn't work.
In Lua, local x is visible to anything within the same scope - things in the same function, if/then structures, for loops, other functions called from that function, etc, but not other modules, even if they're called from within the local's scope. I haven't found a better explanation than "because reasons", but simply knowing that modules are an exception to the "normal" scope behavior at least puts my mind at ease.
Original post:
(Terribly sorry if there's an answer for this out there; I've been Googling for a couple of hours and come up short.)
I've written a GUI library for my preferred audio software (Reaper). There's a strong potential for scripts to be running during recording/playback, so performance is a big issue, and I'm trying to keep everything Local where possible. Easy enough, in general, but I'm having a bit of trouble when it comes to using the GUI library + element classes in a script.
Main GUI module:
-- Core.lua --
local function GUI_table()
local GUI = {}
-- Template for GUI elements
GUI.Element = {}
function GUI.Element:new(name)
local elm = {}
setmetatable(elm, self)
self.__index = self
return elm
end
...add a bunch of GUI.do_this = function()....
return GUI
end
GUI = GUI_table()
All of the GUI elements are separate files of the same form:
-- Class - Button.lua --
if not GUI then throw_a_missing_library_error_and_quit end
GUI.Button = GUI.Element:new()
function GUI.Button:draw()
...etc...
I'm currently loading them from the parent script via loadfile("Core.lua")(). This works well enough, but it places GUI in the global table with the associated overhead for lookups. Trying to rewrite things so that GUI can be local has, thus far, not gone well. I've tried:
local GUI
loadfile("Core.lua")()
loadfile("Class - Button.lua")()
...
Fails because the main script's GUI calls all go to the local _GUI, but loaded files can't see or add to it because of scoping.
loadfile("Core.lua")()
loadfile("Class - Button.lua")()
local GUI = GUI
Runs fine, but doesn't make a difference performancewise: Errors in module code still trace back to the module (i.e. "line 23 in Class - Button.lua"), which leads me to assume the modules are still held within their own scope and my local GUI isn't actually being touched.
I've also tried having Core.lua return the GUI table directly, so the main script can have local GUI = loadfile("Core.lua")(), but I ran into trouble giving the element modules access to it, as above. I know, scoping.
So, given all of the above, is there a "correct" way to write/structure the modules so that everything ends up in a local GUI? I get the impression that the defunct module(..., package.seeall) functionality would have solved this... maybe not though.
Cheers.
This is a problem with circular dependencies. I notice that Element and Button go inside GUI, but they don't depend on GUI for their construction. Putting Element in its own file will allow you to make everything local.
-- Core.lua --
local GUI = {}
-- Template for GUI elements
GUI.Element = require 'Element'
GUI.Button = require 'Button'
return GUI
-- Element.lua --
return {
new = function(self, name)
local elm = {}
setmetatable(elm, self)
self.__index = self
return elm
end,
}
-- Class - Button.lua --
Button = require('Element'):new()
function Button:draw()
end
return Button
Related
I have a lua script which is started by an external process - the two then interact through StandardInput and StandardOutput, through a simple loop:
local running = true;
while running do
print('ready'); io.flush(); --the parent process now knows it can interact with the lua script
local input = io.read(); --the parent process, whenever ready, provides the lua script with a given input
if input=="exit" then running=false --this ends the script...
else print(DoStuffWithInput(input)); --...or the lua script responds here
end
I want the lua script to end if it detects that the parent process no longer exists, given that currently CPU usage jumps to 100% when the parent is closed (presumably because io.read() is no longer needing to wait for any input, although I'm not sure what it's reading instead).
What would be an elegant and safe way of detecting if the parent process has closed (presumably changing the stdin and stdout of the lua script)?
I've tried a few things, but io.stdin and io.stdout always seem to be a file, and I'm not sure where to go from here.
To simplify things, let's assume, that there are two Lua scripts, one program and one module, e.g:
sample.lua
local sample = {}
sample.fun1 = function()
end;
return sample;
I'm using it in program, like this:
program.lua
local sample = require("sample");
sample.fun1();
And now i'm trying to use the same module in C (first running lua program, what is important here), something like this:
luaL_dofile(luaState, "program.lua");
...
lua_getglobal(luaState, "sample");
lua_getfield(luaState,-1, "fun1");
or
luaL_dofile(luaState, "program.lua");
...
lua_getglobal(luaState, "sample.fun1");
But both versions causing crash. My question here: Is it possible to use somehow module, which was previously used in program? For this example it is of course nonsense, but my system is far more complicated and i need this functionality. Maybe some other approach to achieve this?
I'm building, or trying to build, a mod called More Drops (DS). I ported it to Don't Starve from "Don't Starve Together", so my lua knowledge is very limited, but I've been working on this thing for days and can't figure it out, so I need to ask for help.
I figured out how to expand on the original mod authors work by adding new trees and resources, but when it comes to "hackable" resources, I'm stumped.
So this is what I was trying, and tons of variations on this. This doesn't work and I don't understand why, and I'll show you what does work in the next two.
--bamboo
if inst.name == "Bamboo Patch" and utils.LootRandom(bambooChance) then
utils.DoTimes(GetModConfigData("bambooAmount", KnownModIndex:GetModActualName("(JEM) More Drops DS")), inst.components.lootdropper.SpawnLootPrefab, inst.components.lootdropper, "bamboo")
end
This does work
--Spawn extra logs for tall trees
if inst.components.growable.stage == 3 and utils.LootRandom(logChance) then
utils.DoTimes(GetModConfigData("extralogstall", KnownModIndex:GetModActualName("(JEM) More Drops DS")), inst.components.lootdropper.SpawnLootPrefab, inst.components.lootdropper, "log")
end
And this works as well
if inst.name == "Evergreen" and inst.components.growable.stage ~= 1 then
if utils.DropLootRandom(inst, "pinecone", seed_chance) then print("Dropped seed") end
end
Now I'm not sure how much of the files I should paste here, but here's the start of the trees.lua just to add a bit more context
local utils = require("utils")
local logChance = GetModConfigData("logChance", KnownModIndex:GetModActualName("(JEM) More Drops DS"))
local function ImproveTree(inst)
--Do these when the tree is fully chopped
local seed_chance = GetModConfigData("treeseedchance", KnownModIndex:GetModActualName("(JEM) More Drops DS"))
local egg_chance = GetModConfigData("eggChance", KnownModIndex:GetModActualName("(JEM) More Drops DS"))
local coconut_seed_chance = GetModConfigData("coconutChance", KnownModIndex:GetModActualName("(JEM) More Drops DS"))
local bambooChance = GetModConfigData("bambooChance", KnownModIndex:GetModActualName("(JEM) More Drops DS"))
local oldonfinish = inst.components.workable.onfinish
inst.components.workable:SetOnFinishCallback(function(inst, chopper)
All of the info is called in modmain.lua correctly as far as I can tell, I made sure to add things in the same way as the working code.
I expected this to drop some extra bamboo! I've tried so many different ways, even made my own choppable.lua file and added it the same way the original author added pickable.lua, but I figured it's gotta be a bit more like trees than regular resources, since you use a tool and it drops the item on the ground rather than adding to invetory.
If you want to see the whole mod, I've uploaded it here: https://www.dropbox.com/s/ahss2d0s861j2an/%28JEM%29%20More%20Drops%20DS.7z?dl=0
Before I start let me clarify that I've looked through the existing questions regarding sandboxing, but none of the solutions I found really seemed to work for me.
What I am trying to implement is not necessarily a completely safe sandbox to run unsafe code, but provide an environment that emulates the functions of a game (Star Wars Empire at War to be exact) I'm modding that can be extended using Lua scripts. The game provides a bunch of global functions that I want to implement in this environment to be able to test my game extensions without launching the game itself, since its debugging capabilities are quite poor. The game also has some quirks. The relevant issue in this case is that using upvalues will produce corrupt save games, so this is why our Lua modules have to be globals. Since my game scripts are not contained in a single file and can require other files, call and declare their own functions using setfenv() to set an environment does not really work for me, since that applies only to the function you specify and not to any other functions it might call.
Now for my code itself. So far I've tried to implement a simple sandbox by simply deep cloning _G and restoring it to its former state afterwards (this is still WIP, I realize that you can still do table and environment modifications or potentially break out of the sandbox that aren't covered by this approach; as I said, this is not intended to run unsafe code).
This is in sandbox.lua:
local function run(func)
-- tested deep_clone with luassert's assert.are_same(), so this should be fine
local g_clone = deep_clone(_G)
-- coroutine is needed because one of the game functions needs
-- to be able to interrupt the script
coroutine.wrap(function()
func()
end)()
for k, v in pairs(_G) do
if not g_clone[k] then
_G[k] = nil
else
_G[k] = g_clone[k]
end
end
end
And this is my test script:
sandbox.run(function()
require "src/declare_global" -- this declares A_GLOBAL = "TEST"
print(A_GLOBAL) -- prints "TEST", everything is fine
end)
print(A_GLOBAL) -- prints nil, as intended
-- as I've restored _G to its former state I should be able to require
-- the script again!
require "src/declare_global"
print("package.loaded: "..tostring(package.loaded["src/declare_global"])) -- prints nil
print(A_GLOBAL) -- prints nil
As you can see my problem is that requiring the same file that simply declares a global after I did it in my sandbox doesn't work anymore. So there's probably an issue with restoring the state of _G, but I don't have any clue what it could be. The require call itself seems to work, though. If the script I'm requiring returns a function, I can still store it in a variable and execute it.
I'd really appreciate any suggestions on how to fix this.
I found a solution to my problem. However, I feel this is an inefficient way to achieve what I want, so if somebody has a better suggestion I would love to hear it.
#EgorSkriptunoff's comment on my original question regarding the "shallow" the restoration of _G pointed me in the right direction, so I ended up writing a "deep restore" function, which seems to work fine with what I have tested so far.
local known_tables = {}
local function deep_restore(tab, backup)
known_tables[tab] = true
for k, v in pairs(tab) do
if backup[k] == nil then
tab[k] = nil
elseif type(v) == "table" and not known_tables[v] then
deep_restore(v, backup[k])
elseif not type(v) == "table" then
tab[k] = backup[k]
end
end
end
local function run(func)
local g_clone = deep_clone(_G)
coroutine.wrap(function()
func()
end)()
deep_restore(_G, g_clone)
known_tables = {}
end
Results:
sandbox.run(function()
require "src/declare_global"
print("Printing inside sandbox: "..A_GLOBAL) -- prints TEST
end)
print("This should be nil: "..tostring(A_GLOBAL)) -- nil
print("package.loaded should be nil: "..tostring(package.loaded["src/declare_global"])) -- nil
print("\nRequiring again...")
require "src/declare_global"
print("package.loaded: "..tostring(package.loaded["src/declare_global"])) -- true
print(A_GLOBAL) -- TEST
I use VLC media player 1.1.9 on Ubuntu 11.04. I'm trying to experiment with lua extensions for VLC; so I've added the file test.lua in ~/.local/share/vlc/lua/extensions/, which has only these two lines:
fps="25.000"
frame_duration=1/tonumber(fps)
When I run vlc with verbose output for debugging, I get (edited to split on multiple lines:):
$ vlc --verbose 2
...
[0xa213874] lua generic warning: Error loading script
~/.local/share/vlc/lua/extensions/test.lua:
.../.local/share/vlc/lua/extensions/test.lua:2:
attempt to call global 'tonumber' (a nil value)
...
Now, as far as I know, tonumber as function is part of Lua5.1 proper (Lua 5.1 Reference Manual: tonumber) - and on my system:
$ locate --regex 'lua.*so.*' | head -4
/usr/lib/libipelua.so.7.0.10
/usr/lib/liblua5.1.so
/usr/lib/liblua5.1.so.0
/usr/lib/liblua5.1.so.0.0.0
... apparently I do have Lua 5.1 installed.
So, why do I get an error on using tonumber here - and how can I use this (and other) standard functions in a VLC lua extension properly?
Documentation is sparse for VLC Lua extensions to say the least but I did find an example in the github vlc repository here: https://github.com/videolan/vlc/blob/master/share/lua/extensions/VLSub.lua
Judging from that example it appears you need to supply some basic event functions for your addon for VLC to call into when certain events happen. Some of the obvious callback handlers I've noticed:
descriptor, this should return a table that contains fields describing your addon.
activate, this seems to get called when you activate it from view menubar.
deactivate, called when you deactivate the addon from view menubar.
plus a couple of other functions like close and input_change which you can guess what they're for.
From my brief testing done on VLC 2.0.8 under Win7 it appears VLC loads the lua extension using an empty sandbox environment. This is likely the reason you're getting nil for tonumber and I'm betting none of the other standard lua functions are accessible either when you try to perform computation at this global scope.
However, if I move that code into one of the event handling functions then all those standard functions are accessible again. For example:
function descriptor()
return
{
title = "Test Ext";
version = "0.1";
author = "";
shortdesc = "Testing Lua Extension";
capabilities = {};
description = "VLC Hello Test Addon";
}
end
function activate()
print "test activating"
local fps = tonumber "25.000"
local frame_duration = 1 / fps
print(frame_duration)
return true
end
-- ...
That prints out what you would expect in the console debug log. Now the documentation (what little there is) doesn't mention any of this but what's probably happening here is VLC is injecting the standard lua functions and vlc api table into the sandboxed environment when any of these event handlers get called. But during the extension loading phase, it is done in an empty sandbox environment which explains why all those lua function calls end up being nil when you try to use it at the outter most scope.
I recommend cloning the VLC source tree from github and then performing a grep on the C source that's embedding lua to see what VLC is really doing behind the scenes. Most of the relevant code will likely be here: https://github.com/videolan/vlc/tree/master/modules/lua
Probably some extension script installed in your system overwrites the function and the Lua interpreter instance is shared between all extension scripts, so you end up not being able to call the function if that script is called before yours.
As a quick workaround, Lua being dynamically typed, you can still do things like:
1 / "25.000"
and the string will be coerced to a number.
Alternatively, you can define a tonumber equivalent like:
string_to_num = function(s) return s + 0 end
This again relies on dynamic typing.