Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
782 lines (707 sloc) 22.4 KB
-- See Copyright Notice in LICENSE.txt
util = {}
function util.shader_loader(filename)
return resource.create_shader(resource.load_file(filename))
end
function util.videoplayer(name, opt)
local stream, start, fps, frame, width, height
local function open_stream()
stream = resource.load_video(name)
start = sys.now()
fps = stream:fps()
frame = 0
width, height = stream:size()
end
open_stream()
opt = opt or {}
local speed = opt.speed or 1
fps = fps * speed
local loop = true
if opt.loop ~= nil then loop = opt.loop end
local done = false
return {
draw = function(self, x1, y1, x2, y2, alpha)
if done then return end
local now = sys.now()
local target_frame = (now - start) * fps
if target_frame > frame + 10 then
print(string.format(
"slow player for '%s'. missed %d frames since last call",
name,
target_frame - frame
))
-- too slow to decode. rebase time
start = now - frame * 1/fps
else
while frame < target_frame do
if not stream:next() then
if loop then
print("player: looping")
open_stream()
stream:next()
break
else
-- stream completed
done = true
return false
end
end
frame = frame + 1
end
end
stream:draw(x1, y1, x2, y2, alpha)
return true
end;
texid = function(self)
return stream:texid()
end;
next = function(self)
return not done
end;
state = function(self)
return stream:state()
end;
size = function(self)
return stream:size()
end;
dispose = function(self)
return stream:dispose()
end;
}
end
util.loaders = {
png = resource.load_image;
jpg = resource.load_image;
jpeg = resource.load_image;
gif = resource.load_image;
bmp = resource.load_image;
ttf = resource.load_font;
otf = resource.load_font;
avi = util.videoplayer;
mpg = util.videoplayer;
ogg = util.videoplayer;
flv = util.videoplayer;
mkv = util.videoplayer;
mp4 = util.videoplayer;
mov = util.videoplayer;
frag = util.shader_loader;
}
function util.auto_loader(container, filter)
container = container or {}
filter = filter or function() return true end
local loaded_version = {}
local function auto_load(name)
if filter and not filter(name) then
return
end
if loaded_version[name] == CONTENTS[name] then
-- print("auto_loader: already loaded " .. name)
return
end
local target, suffix = name:match("(.*)[.]([^.]+)$")
if not target then
print("loader: invalid resource name " .. name .. ". ignoring " .. name)
return
end
local loader = util.loaders[suffix]
if not loader then
print("loader: no resource loader for suffix " .. suffix .. ". ignoring " .. name)
return
end
local success, res = pcall(loader, name)
if not success then
print("loader: cannot load " .. name .. ": " .. res)
else
print("loader: updated " .. target .. " (triggered by " .. name .. ")")
container[target] = res
loaded_version[name] = CONTENTS[name]
end
end
print("loader: loading known resources")
for name, added in pairs(CONTENTS) do
auto_load(name)
end
node.event("content_update", auto_load)
node.event("content_remove", function(name)
local target, suffix = name:match("(.*)[.]([^.]+)$")
if target and util.loaders[suffix] and container[target] then
print("loader: unloaded " .. target .. " (triggered by " .. name .. ")")
container[target] = nil
loaded_version[name] = nil
end
end)
return container
end
function util.resource_loader(resources, container)
container = container or _G
local whitelist = {}
for _, name in ipairs(resources) do
whitelist[name] = true
end
return util.auto_loader(container, function(name)
return whitelist[name]
end)
end
function util.file_watch(filename, handler)
local loaded_version = nil
local function updated(name)
if name ~= filename then
return
end
if loaded_version == CONTENTS[filename] then
return
end
loaded_version = CONTENTS[filename]
handler(resource.load_file(filename))
end
node.event("content_update", updated)
updated(filename)
end
local function handle_suffix_match(suffix, pattern, callback, ...)
local data = {...}
return (function(s, e, ...)
if s == nil then
return false
end
local args = {...}
for n = 1, #data do
args[#args+1] = data[n]
end
callback(unpack(args))
return true
end)(suffix:find(pattern))
end
function util.osc_mapper(routes)
node.event("osc", function(suffix, ...)
for pattern, callback in pairs(routes) do
if handle_suffix_match(suffix, pattern, callback, ...) then
return
end
end
end)
end
function util.data_mapper(routes)
node.event("data", function(data, suffix)
for pattern, callback in pairs(routes) do
if handle_suffix_match(suffix, pattern, callback, data) then
return
end
end
end)
end
function util.generator(refiller)
local items = {}
return {
next = function(self)
local next_item = next(items)
if not next_item then
for _, value in ipairs(refiller()) do
items[value] = 1
end
next_item = next(items)
if not next_item then
error("no items available")
end
end
items[next_item] = nil
return next_item
end;
add = function(self, value)
items[value] = 1
end;
remove = function(self, value)
items[value] = nil
end;
}
end
function util.set_interval(interval, callback)
local next_call = sys.now() + interval
node.event("render", function()
local now = sys.now()
if now > next_call then
next_call = now + interval
callback()
end
end)
callback()
end
function util.post_effect(shader, shader_opt)
local surface = resource.create_snapshot()
gl.ortho()
gl.clear(0,0,0,1)
shader:use(shader_opt)
surface:draw(0, 0, WIDTH, HEIGHT)
shader:deactivate()
end
function util.running_text(opt)
local current_idx = 1
local current_left = 0
local last = sys.now()
local generator = opt.generator
local font = opt.font
local size = opt.size or 10
local speed = opt.speed or 10
local color = opt.color or {1,1,1,1}
local texts = {}
return {
draw = function(self, y)
local now = sys.now()
local xoff = current_left
local idx = 1
while xoff < WIDTH do
if #texts < idx then
table.insert(texts, generator.next())
end
local width = font:write(xoff, y, texts[idx] .. " - ", size, unpack(color))
xoff = xoff + width
if xoff < 0 then
current_left = xoff
table.remove(texts, idx)
else
idx = idx + 1
end
end
local delta = now - last
last = now
current_left = current_left - delta * speed
end;
add = function(self, text)
generator:add(text)
end;
}
end
function util.scale_into(target_width, target_height, source_width, source_height)
local prop_height = source_height * target_width / source_width
local prop_width = source_width * target_height / source_height
local x1, y1, x2, y2
if prop_height > target_height then
local x_center = target_width / 2
local half_width = prop_width / 2
x1 = x_center - half_width
y1 = 0
x2 = x_center + half_width
y2 = target_height
else
local y_center = target_height / 2
local half_height = prop_height / 2
x1 = 0
y1 = y_center - half_height
x2 = target_width
y2 = y_center + half_height
end
return x1, y1, x2, y2
end
function util.draw_correct(obj, x1, y1, x2, y2, ...)
local ox1, oy1, ox2, oy2 = util.scale_into(
x2 - x1, y2 - y1, obj:size()
)
obj:draw(x1 + ox1, y1 + oy1, x1 + ox2, y1 + oy2, ...)
end
function table.filter(t, predicate)
local j = 1
for i, v in ipairs(t) do
if predicate(v) then
t[j] = v
j = j + 1
end
end
while t[j] ~= nil do
t[j] = nil
j = j + 1
end
return t
end
-- Based on http://lua-users.org/wiki/TableSerialization
-- Modified to *not* use debug.getinfo
--[[
Author: Julio Manuel Fernandez-Diaz
Date: January 12, 2007
(For Lua 5.1)
Modified slightly by RiciLake to avoid the unnecessary table traversal in tablecount()
Formats tables with cycles recursively to any depth.
The output is returned as a string.
References to other tables are shown as values.
Self references are indicated.
The string returned is "Lua code", which can be procesed
(in the case in which indent is composed by spaces or "--").
Userdata and function keys and values are shown as strings,
which logically are exactly not equivalent to the original code.
This routine can serve for pretty formating tables with
proper indentations, apart from printing them:
print(table.show(t, "t")) -- a typical use
Heavily based on "Saving tables with cycles", PIL2, p. 113.
Arguments:
t is the table.
name is the name of the table (optional)
indent is a first indentation (optional).
--]]
function table.show(t, name, indent)
local cart -- a container
local autoref -- for self references
-- (RiciLake) returns true if the table is empty
local function isemptytable(t) return next(t) == nil end
local function basicSerialize (o)
local so = tostring(o)
if type(o) == "function" or type(o) == "number" or type(o) == "boolean" then
return so
else
return string.format("%q", so)
end
end
local function addtocart (value, name, indent, saved, field)
indent = indent or ""
saved = saved or {}
field = field or name
cart = cart .. indent .. field
if type(value) ~= "table" then
cart = cart .. " = " .. basicSerialize(value) .. ";\n"
else
if saved[value] then
cart = cart .. " = {...}; -- " .. saved[value]
.. " (self reference)\n"
autoref = autoref .. name .. " = " .. saved[value] .. ";\n"
else
saved[value] = name
--if tablecount(value) == 0 then
if isemptytable(value) then
cart = cart .. " = {};\n"
else
cart = cart .. " = {\n"
for k, v in pairs(value) do
k = basicSerialize(k)
local fname = string.format("%s[%s]", name, k)
field = string.format("[%s]", k)
-- three spaces between levels
addtocart(v, fname, indent .. " ", saved, field)
end
cart = cart .. indent .. "};\n"
end
end
end
end
name = name or "__unnamed__"
if type(t) ~= "table" then
return name .. " = " .. basicSerialize(t)
end
cart, autoref = "", ""
addtocart(t, name, indent)
return cart .. autoref
end
function table.keys(t)
local ret = {}
for k, v in pairs(t) do
ret[#ret+1] = k
end
return ret
end
function pp(t)
print(table.show(t))
end
-- Sandboxed package loader
package = {
loadlib = function(libname, funcname)
error("no native linking")
end;
seeall = function(module)
return setmetatable(module, {
__index = _G
})
end;
loaded = {
table = table;
string = string;
math = math;
table = table;
coroutine = coroutine;
debug = debug;
struct = struct;
util = util;
sys = sys;
gl = gl;
resource = resource;
};
loaders = {
function(modname)
local filename = modname .. ".lua"
local status, content = pcall(resource.load_file, filename)
if not status then
return "no file " .. filename .. ": " .. content
else
return function(loader_modname)
assert(loader_modname == modname)
local filename = PATH .. "/" .. modname .. ".lua"
return assert(loadstring(content, "=" .. filename))(modname)
end, filename
end
end;
-- bundled moduls loader
function(modname)
local filename = modname .. ".lua"
local content = _BUNDLED_MODULES[filename]
if not content then
return "no file " .. filename
else
return function(loader_modname)
print("loading bundled module '" .. loader_modname .. "'")
assert(loader_modname == modname)
return assert(loadstring(content, "=" .. filename))(modname)
end, filename
end
end
};
}
package.loaded['package'] = package
function require(modname)
local loaded = package.loaded[modname]
if loaded then
return loaded
end
-- find loader
local loader
local errors = {"module '" .. modname .. "' not found:"}
for _, searcher in ipairs(package.loaders) do
local searcher_val = searcher(modname)
if type(searcher_val) == "function" then
loader = searcher_val
break
elseif type(searcher_val) == "string" then
errors[#errors + 1] = "\t" .. searcher_val
end
end
if not loader then
error(table.concat(errors, "\n"))
end
-- load module
local value = loader(modname)
if value then
package.loaded[modname] = value
elseif not package.loaded[modname] then
package.loaded[modname] = true
end
return package.loaded[modname]
end
function util.init_hosted()
local json = require "json"
local hosted = nil
local config_json = nil
local node_json = nil
local reload_config = function()
print "[hosted] reloading config"
-- pp(hosted)
-- pp(node_json)
-- pp(config_json)
if hosted and node_json and config_json then
local parsed = hosted.parse_config(node_json.options, config_json)
_G['CONFIG'] = parsed
node.dispatch("config_update", parsed)
end
end
util.file_watch("hosted.lua", function(content)
print "[hosted] loading hosted.lua"
local filename = PATH .. "/hosted.lua"
hosted = assert(loadstring(content, "=" .. filename))()
reload_config()
end)
util.file_watch("node.json", function(content)
print("[hosted] loading node.json")
node_json = json.decode(content)
_G['NODE'] = node_json
reload_config()
node.dispatch("node_update", node_json)
end)
util.file_watch("config.json", function(content)
print("[hosted] loading config.json")
config_json = json.decode(content)
reload_config()
end)
util.file_watch("package.json", function(content)
print("[hosted] loading package.json")
local package_json = json.decode(content)
_G['PACKAGE'] = package_json
node.dispatch("package_update", package_json)
end)
end
-- compatibility with older versions
hosted_init = util.init_hosted
do
local function red(str) return "" .. str .. "" end
local function green(str) return "" .. str .. "" end
local function yellow(str) return "" .. str .. "" end
local handlers = {
["boolean"] = function(cmd, info, target)
local function setup()
target[cmd] = info.value
end
local function info()
return string.format("(%s)", tostring(target[cmd]))
end
local function call(arg)
local value = ({
["true"] = true;
["1"] = true;
["y"] = true;
["false"] = false;
["0"] = false;
["n"] = false;
})[arg]
if value == nil then
print(red("invalid value: true/false expected"))
else
target[cmd] = value
print(green("value updated"))
end
end
return {
setup = setup;
param = "<true|false>";
info = info;
call = call;
}
end;
["string"] = function(cmd, info, target)
local function setup()
target[cmd] = info.value
end
local function info()
return string.format("(\"%s\")", target[cmd])
end
local function call(arg)
target[cmd] = arg
print(green("value updated"))
end
return {
setup = setup;
param = "<\"new value\">";
info = info;
call = call;
}
end;
["number"] = function(cmd, info, target)
local function setup()
target[cmd] = info.value
end
local function info()
return string.format("(%f)", target[cmd])
end
local function call(arg)
local value = tonumber(arg)
if value == nil then
print(red("invalid value: number expected"))
else
target[cmd] = value
print(green("value updated"))
end
end
return {
setup = setup;
param = "<number>";
info = info;
call = call;
}
end;
["function"] = function(cmd, info, target)
local function call(arg)
return info.value(target, readln, arg)
end
return {
setup = function() end;
param = "";
info = function() return "" end;
call = call;
}
end;
}
local function create_menu_interface(name, target, options, readln)
if not target then
target = _G
end
for cmd, info in pairs(options) do
local type = info.type or type(info.value)
info.handler = handlers[type](cmd, info, target)
info.handler.setup()
end
local function print_help()
local max_size = 0
local cmds = {}
for cmd, info in pairs(options) do
max_size = math.max(max_size, #cmd + 1 + #info.handler.param)
cmds[#cmds+1] = cmd
end
table.sort(cmds)
print()
print(green("Available commands/values:"))
for idx, cmd in ipairs(cmds) do
local info = options[cmd]
print(string.format("%-" .. tostring(max_size) .. "s - %s %s", cmd .. " " .. info.handler.param, info.help, info.handler.info()))
end
print()
end
return function()
while true do
print()
print(yellow(name .. " - your command"))
local line = readln()
if line == "?" or line == "help" then
print_help()
elseif line == "" or line == "exit" then
break
else
local cmd, arg = string.match(line, "^([^%s]+) (.*)$")
if not cmd then
cmd = line
end
local option = options[cmd]
if option then
option.handler.call(arg)
else
print(red("invalid command line \"" .. line .. "\". type '?' for help"))
end
end
end
end
end
local function create_submenu(name, options)
return {
value = function(target, readln)
return create_menu_interface(name, target, options, readln)()
end;
help = name;
}
end
local function create_variable(value, help)
return {
value = value;
help = help;
}
end
local function tcp_export(options, target)
local main_menu = create_menu_interface("main menu", target, options, function()
return coroutine.yield()
end)
if not N.clients then
N.clients = {}
end
node.event("connect", function(client)
local handler = coroutine.wrap(function()
print(green("configuration interface for " .. PATH))
print(green("-----------------------------------------"))
while true do
main_menu()
end
end)
N.clients[client] = handler
handler()
end)
node.event("input", function(line, client)
N.clients[client](line)
end)
node.event("disconnect", function(client)
N.clients[client] = nil
end)
end
util.menu = {
tcp = tcp_export;
sub = create_submenu;
var = create_variable;
}
end