Convert a value string in metatable to table for lookup - lua

I have an enormous table of functions named engine_api with inline documentation. Because it's becoming so large I'd like to make it more modular.
The api is set up like this:
-- Engine API module
local engine_api = {
engine = {
-- engine functions...
version = ...
},
image = {
-- image functions...
jpeg = {
-- jpeg specific bits
},
},
project = {
-- project functions
}
}
return engine_api
As you can see, it's more than 2 levels deep.
The whole thing is returned as a single table so other parts of the system can call into this api like this:
local api = require "engine_api"
print("Engine version:", engine_api.engine.version());
I still need it to work this way. But what I'd like to do is separate various parts of the API into different files. I thought I might be able to do this using metamethods. But when the metamethod is looked up, the value is actually a string so this naive approach will not work.
local engine = {
-- engine functions...
version = function()
print("engine.version")
end
}
local image = {
-- image functions
get = function()
print("image.get")
end
}
local project = {
-- project functions
load = function()
print("project.load()")
end
}
-- Engine API module
local engine_api = {
}
local engine_api_mt = {
__index = function(tbl, k)
print("k=", k)
return k
end
}
setmetatable(engine_api, engine_api_mt)
Instead I seem to have to do bunch of if then else statements to compare the table name with the string and then return the table and not the string. Is there a way of performing the conversion automatically?

You can make engine_api a single file and require files inside that table, like so:
engine_api.lua:
local engine_api = {
engine = require("engine"),
image = require("image"),
project = require("project"),
}
return engine_api
engine.lua:
local engine = {
version = function()
print("my version")
end
}
return engine
And so on, that way you can call engine_api.engine.version() with no problems at all. I think the metatable is just over the top, unless you are doing something more specific?

Related

Can't find a way to save a table of data

I have been working on a survival game base on "Booga Booga" but i cant seem to find out how to load player data on the game. The data im trying to load is saved in the for loop that follows:
module.SaveData = function (player, DT)
local data_saved = {}
local setData = player.inventory.Inv:GetChildren()
for i, v in pairs(setData) do
table.insert(data_saved, {[v] = {
value = v.Value,
name = v.Name
}})
end
Data_Store:SetAsync(player.UserId, data_saved)
end
I've done multiple things to attempt to solve this problem
I've tried load with http service
I've already attempted loading the raw table
and I've tried to use a global data store instead
here is my code that loads the data as of now:
game.Players.PlayerAdded:Connect(function(plr)
CF.PlayerInventorySetup(plr) -- not relavent
p = plr -- not relavent
local PD =require(game.ServerScriptService.DataHandler)
local plrdata =PD.FetchData(plr)
for i, v in pairs(plrdata) do -- this is what im having trouble with
if not plr.inventory.Inv:FindFirstChild(v) then
local newint = Instance.new("NumberValue")
newint.Name = v.name -- the ouput says: string expected, got nil
newint.Value = v.value --
newint.Parent = plr.inventory.Inv
end
end
end)
I actually don't know wtf to do.
Before we talk about a solution, let's talk about what's happening when you save the data. Let's say for example, your inventory is a list of NumberValues like this :
Fish (value of 3)
Iron (value of 10)
Grass (value of 8)
Your FetchData function expects that the saved player data is formatted like this :
{
{ value = 3, name = "Fish" },
{ value = 10, name = "Iron" },
{ value = 8, name = "Grass" },
}
However, the result of the loop in the SaveData function would be this :
{
{ Fish(NumberValue) = { value=3, name="Fish" }},
{ Iron(NumberValue) = { value=10, name="Iron" }},
{ Grass(NumberValue) = { value=8, name="Grass" }},
}
On every step of the loop, you are pushing a new dictionary into the data_saved table.
The FetchData function expects that dictionary to have keys for "name" and "value". But, you've pushed that data one layer deeper, so those keys don't exist where the FetchData code expects them to and it throws errors.
So to fix your issue in the SetData function, remove the outer layer of that table, and just use the raw data.
table.insert(data_saved, {
value = v.Value,
name = v.Name
})

Lua, local variables in a table

Sorry in advance if this is an incorrect question. I'm fairly new to Lua and I'm not sure how to go about this. I want to access a variable stored in a table from a function variable.
As far as I know there is no self-referencing tables before constructed.
An example would be this:
local bigTable = {
a = {
foo = 0,
bar = function(y)
print(foo) --Incorrect
end
}
}
What would be the best approach for this situation?
What you want to do is to create a table first, and append the keys to it:
local a = {}
a.foo = 0
a.bar = function()
print(a.foo)
end
local bigTable = {
a = a
}
bigTable.a.bar() -- prints 0
local bigTable = {
a = {
foo = 0,
bar = function(self, ...)
print(self.foo)
end,
}
}
-- Somewhere else in the code...
bigTable.a.bar(bigTable.a) --> 0
-- or the shorter but (almost) equivalent form:
bigTable.a:bar() --> prints 0
I anticipate your next question will be "What does the : do?", and for that there's lots of answers on SO already :)
Note that there's potential for a performance improvement here: if the above code gets called a lot, the bar method will be created again and again, so it could make sense to cache it; but that's pointless unless the surrounding code is already fast enough that this one allocation would have a noticeable impact on its runtime.

How do I load data from another lua file?

I have a main lua file app.lua , and in that app I have a button to "load data" in.
NOTE: LUA 5.1 not 5.2
The data file is a lua file as well with tables in it.
data1 = {
{a,b,c},
{1,2,3}
}
data2 = {
{d,e,f}
}
The goal was to make those tables available to the app anytime I choose to load the file.
I tried the example from the lua site
function dofile (filename)
local f = assert(loadfile(filename))
return f()
end
but f() is just printing a massive string. I can't seem to access f.data1[1] for example.
The file you're loading is not a data table. That's a piece of code, anonymous function that is executable. You run that code in return f() statement.
But see what that code does - it doesn't return anything. Instead it assigns two global variables, data1 and data2. You can access those as data1[1] for example.
You could return the data in the file being loaded, that way it wouldn't pollute the global environment, and probably will look like you imagined it to be:
return {
data1 = { {a,b,c}, {1,2,3} },
data2 = { d,e,f}
}
And in other file:
local f = assert(loadfile(filename))
my_data = f()
print(my_data.data1[1][1])

How to split a Torch class into several files in a Lua rock

In my recently aided in the development of a Dataframe package for Torch. As the code base has quickly doubled there is a need to split the class into several sections for better organization and follow-up (issue #8).
A simple test-class would be a test.lua file in the root folder of the test-package:
test = torch.class('test')
function test:__init()
self.data = {}
end
function test:a()
print("a")
end
function test:b()
print("b")
end
Now the rockspec for this would simply be:
package = "torch-test"
version = "0.1-1"
source = {
url = "..."
}
description = {
summary = "A test class",
detailed = [[
Just an example
]],
license = "MIT/X11",
maintainer = "Jon Doe"
}
dependencies = {
"lua ~> 5.1",
"torch >= 7.0",
}
build = {
type = 'builtin',
modules = {
["test"] = 'test.lua',
}
}
In order to get multiple files to work for a single class it is necessary to return the class object initially created and pass it to the subsections. The above example can be put into the file structure:
\init.lua
\main.lua
\test-0.1-1.rockspec
\Extensions\a.lua
\Extensions\b.lua
The luarocks install/make copies the files according to 'require' syntax where each . signifies a directory and the .lua is left out, i.e. we need to change the rockspec to:
package = "torch-test"
version = "0.1-1"
source = {
url = "..."
}
description = {
summary = "A test class",
detailed = [[
Just an example
]],
license = "MIT/X11",
maintainer = "Jon Doe"
}
dependencies = {
"lua ~> 5.1",
"torch >= 7.0",
}
build = {
type = 'builtin',
modules = {
["test.init"] = 'init.lua',
["test.main"] = 'main.lua',
["test.Extensions.a"] = 'a.lua',
["test.Extensions.b"] = 'b.lua'
}
}
The above will thus create a test-folder where the packages reside together with the files and subdirectories. The class initialization now resides in the init.lua that returns the class object:
test = torch.class('test')
function test:__init()
self.data = {}
end
return test
The subclass-files now need to pickup the class object that is passed using loadfile() (see init.lua file below). The a.lua should now look like this:
local params = {...}
local test = params[1]
function test:a()
print("a")
end
and similar addition for the b.lua:
local params = {...}
local test = params[1]
function test:b()
print("b")
end
In order to glue everything together we have the init.lua file. The following is probably a little over-complicated but it takes care of:
Finding all extensions available and loading them (Note: requires lua filesystem that you should add to the rockspec and you still need to add each file into the rockspec or it won't be in the Extensions folder)
Identifies the paths folder
Loads the main.lua
Works in a pure testing environment without the package installed
The code for init.lua:
require 'lfs'
local file_exists = function(name)
local f=io.open(name,"r")
if f~=nil then io.close(f) return true else return false end
end
-- If we're in development mode the default path should be the current
local test_path = "./?.lua"
local search_4_file = "Extensions/load_batch"
if (not file_exists(string.gsub(test_path, "?", search_4_file))) then
-- split all paths according to ;
for path in string.gmatch(package.path, "[^;]+;") do
-- remove trailing ;
path = string.sub(path, 1, string.len(path) - 1)
if (file_exists(string.gsub(path, "?", "test/" .. search_4_file))) then
test_path = string.gsub(path, "?", "test/?")
break;
end
end
if (test_path == nil) then
error("Can't find package files in search path: " .. tostring(package.path))
end
end
local main_file = string.gsub(test_path,"?", "main")
local test = assert(loadfile(main_file))()
-- Load all extensions, i.e. .lua files in Extensions directory
ext_path = string.gsub(test_path, "[^/]+$", "") .. "Extensions/"
for extension_file,_ in lfs.dir (ext_path) do
if (string.match(extension_file, "[.]lua$")) then
local file = ext_path .. extension_file
assert(loadfile(file))(test)
end
end
return test
I hope this helps if you run into the same problem and find the documentation a little too sparse. If you happen to know a better solution, please share.

Using __call to create a DSL with lua, how to implement subsections?

I am new to lua and I am trying to create a configuration DSL which allows to have sections that already have defaults.
So, the java table is predefined with lot of values
java = {
source = 1.6,
target = 1.6,
directories = {
sources = "src/main/java",
output = "build/clases",
},
}
I have a Config prototype that implements __call so that when called as a function with a table constructor, it only overwrites the defaults. Something (like) this:
Config.__call = function(t, props)
for k,v in pairs(props) do
t[k] = v
end
end
The idea is that you can call the dsl only to specify what you want to override:
java {
source = 1.5,
directories {
sources = "customsrcdir",
}
}
There is a Config.new method that allows to apply the prototype recursively to the tables so that all have a metatable with the __call method set.
My problem is with the "directories" subsection. It is evaluated in the global context, so the only way this works is:
java {
source = 1.5,
java.directories {
sources = "customsrcdir",
}
}
Which is pointless, as this is the same as doing:
java {
source = 1.5
}
java.directories {
sources = "customsrcdir",
}
I tried different approaches to have the desired DSL to work. One was setting a custom global environment with _ENV, but then I realized the table is evaluated before __call.
I wonder if someone with more lua experience has implemented a DSL like this using more advanced table/metatable/_ENV magic.
It's possible to do it your way with calls, but the solution's so convoluted that it's not worth the omission of the =. If you still want the table merge/replacement functionality, then that's not too difficult.
local function merge(t1, t2)
for k, v in pairs(t2) do
-- Merge tables with tables, unless the replacing table is an array,
-- in which case, the array table overwrites the destination.
if type(t1[k]) == 'table' and type(v) == 'table' and #v == 0 then
merge(t1[k], v)
else
t1[k] = v
end
end
end
local data = {
java = {
source = 1.6,
target = 1.6,
directories = {
sources = "src/main/java",
output = "build/classes",
},
}
}
local dsl = {}
load( [[
java = {
source = 1.5,
directories = {
sources = "customsrcdir",
},
}
]], 'dsl-config', 't', dsl)()
merge(data, dsl)
Dumping data will result in:
java = {
directories = {
output = "build/classes",
sources = "customsrcdir"
}
source = 1.5,
target = 1.6
}
Check out how premake does it... Might be a more elegant solution than what you have going right now. http://en.wikipedia.org/wiki/Premake#Sample_Script

Resources