diff options
author | Piotr Dobrowolski <admin@tastycode.pl> | 2017-01-07 19:35:38 +0100 |
---|---|---|
committer | Piotr Dobrowolski <admin@tastycode.pl> | 2017-01-07 19:35:38 +0100 |
commit | 590af22cd8a4c696924cf33536ae0611be04728c (patch) | |
tree | 673cbc61d512cd4279537bd87d85cdb2dcb4e3f5 | |
download | love2d-signage-590af22cd8a4c696924cf33536ae0611be04728c.tar.gz love2d-signage-590af22cd8a4c696924cf33536ae0611be04728c.tar.bz2 love2d-signage-590af22cd8a4c696924cf33536ae0611be04728c.tar.xz love2d-signage-590af22cd8a4c696924cf33536ae0611be04728c.zip |
Initial commit
-rw-r--r-- | .editorconfig | 16 | ||||
-rw-r--r-- | main.lua | 71 | ||||
-rw-r--r-- | screens/screen1.lua | 18 | ||||
-rw-r--r-- | screens/screen2.lua | 19 | ||||
-rw-r--r-- | vendor/debugGraph.lua | 132 | ||||
-rw-r--r-- | vendor/inspect.lua | 341 | ||||
-rw-r--r-- | vendor/lume.lua | 772 | ||||
-rw-r--r-- | vendor/lurker.lua | 249 |
8 files changed, 1618 insertions, 0 deletions
diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0d44863 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 + +[*.{c,h,cpp}] +indent_style = spaces +indent_size = 4 + +[*.lua] +indent_style = spaces +indent_size = 2 diff --git a/main.lua b/main.lua new file mode 100644 index 0000000..d2987ee --- /dev/null +++ b/main.lua @@ -0,0 +1,71 @@ +debugGraph = require 'vendor.debugGraph' +inspect = require 'vendor.inspect' + +screens = { + require 'screens.screen1', + require 'screens.screen2', +} + +state = { + currentScreen = 1, + state = 'running', + cycleTime = 3, + transitionTime = 1, + transitioning = false, + stateCounter = 0, +} + +function love.load() + love.window.setMode(1366, 768, {borderless=true}) + + secondaryCanvas = love.graphics.newCanvas(love.graphics.getWidth(), love.graphics.getHeight()) + fpsGraph = debugGraph:new('fps', 0, 0) + memGraph = debugGraph:new('mem', 0, 30) +end + +function love.draw() + -- love.graphics.reset() + screens[state.currentScreen].render() + if state.transitioning then + secondaryCanvas:renderTo(screens[state.currentScreen % #screens + 1].render) + love.graphics.setColor(255, 255, 255, 255 * (state.stateCounter / state.transitionTime)) -- red, green, blue, opacity (this would be white with 20% opacity) + love.graphics.draw(secondaryCanvas, 0, 0) + end + + -- Draw graphs + love.graphics.setColor(255, 255, 255, 128) + --love.graphics.setNewFont(10) + --love.graphics.print(inspect(state), 0, 60, 0) + + fpsGraph:draw() + memGraph:draw() +end + +function love.update(dt) + screens[state.currentScreen].update(dt) + + if state.transitioning then + screens[state.currentScreen % #screens + 1].update(dt) + end + + state.stateCounter = state.stateCounter + dt + + if state.transitioning then + if state.stateCounter >= state.transitionTime then + state.stateCounter = 0 + state.transitioning = false + state.currentScreen = (state.currentScreen % #screens) + 1 + end + else + if state.stateCounter >= state.cycleTime then + state.stateCounter = 0 + state.transitioning = true + end + end + + -- Update the graphs + fpsGraph:update(dt) + memGraph:update(dt) + + require("vendor.lurker").update() +end diff --git a/screens/screen1.lua b/screens/screen1.lua new file mode 100644 index 0000000..71f8fd4 --- /dev/null +++ b/screens/screen1.lua @@ -0,0 +1,18 @@ +local node = {} + +function node.render() +love.graphics.setColor( 0, 80, 0 ) +love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight()) +love.graphics.setColor(255, 255, 255, 255) +love.graphics.setNewFont(60) + +love.graphics.printf("Welcome to Warsaw Hackerspace\nextreme digital signage technology!", 0, love.graphics.getHeight()/2 - 30, love.graphics.getWidth(), 'center') + +love.graphics.setColor(255, 0, 0) +love.graphics.circle("fill", 20, 300, 50) +end + +function node.update(dt) +end + +return node diff --git a/screens/screen2.lua b/screens/screen2.lua new file mode 100644 index 0000000..21510f2 --- /dev/null +++ b/screens/screen2.lua @@ -0,0 +1,19 @@ +local node = {} + +function node.render() +love.graphics.setColor( 80, 0, 0 ) +love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight()) + +love.graphics.setColor(255, 255, 255, 255) +love.graphics.setNewFont(90) + +love.graphics.printf("Screen 2!", 0, love.graphics.getHeight()/2 - 45, love.graphics.getWidth(), 'center') + +love.graphics.setColor(255, 50, 0) +love.graphics.circle("fill", 60, 300, 50) +end + +function node.update(dt) +end + +return node diff --git a/vendor/debugGraph.lua b/vendor/debugGraph.lua new file mode 100644 index 0000000..4eac7b9 --- /dev/null +++ b/vendor/debugGraph.lua @@ -0,0 +1,132 @@ +--[[ + UNLICENSE + + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or distribute this + software, either in source code form or as a compiled binary, for any purpose, + commercial or non-commercial, and by any means. + + In jurisdictions that recognize copyright laws, the author or authors of this + software dedicate any and all copyright interest in the software to the public + domain. We make this dedication for the benefit of the public at large and to + the detriment of our heirs and successors. We intend this dedication to be an + overt act of relinquishment in perpetuity of all present and future rights to + this software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTBILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT, IN NO EVENT SHALL THE AUTHORS BE + LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to <http://unlicense.org/> +]] + +-- Code based on https://github.com/icrawler/FPSGraph + +local debugGraph = {} + +function debugGraph:new(type, x, y, width, height, delay, label, font) + if ({mem=0, fps=0, custom=0})[type] == nil then + error('Acceptable types: mem, fps, custom') + end + + local instance = { + x = x or 0, -- X position + y = y or 0, -- Y position + width = width or 50, -- Graph width + height = height or 30, -- Graph height + delay = delay or 0.5, -- Update delay + label = label or type, -- Graph label + font = font or love.graphics.newFont(8), + data = {}, + _max = 0, + _time = 0, + _type = type, + } + + -- Build base data + for i = 0, math.floor(instance.width / 2) do + table.insert(instance.data, 0) + end + + -- Updating the graph + function instance:update(dt, val) + local lastTime = self._time + self._time = (self._time + dt) % self.delay + + -- Check if the minimum amount of time has past + if dt > self.delay or lastTime > self._time then + -- Fetch data if needed + if val == nil then + if self._type == 'fps' then + -- Collect fps info and update the label + val = 0.75 * 1 / dt + 0.25 * love.timer.getFPS() + self.label = "FPS: " .. math.floor(val * 10) / 10 + elseif self._type == 'mem' then + -- Collect memory info and update the label + val = collectgarbage('count') + self.label = "Memory (KB): " .. math.floor(val * 10) / 10 + else + -- If the val is nil then we'll just skip this time + return + end + end + + + -- pop the old data and push new data + table.remove(self.data, 1) + table.insert(self.data, val) + + -- Find the highest value + local max = 0 + for i=1, #self.data do + local v = self.data[i] + if v > max then + max = v + end + end + + self._max = max + end + end + + function instance:draw() + -- Store the currently set font and change the font to our own + local fontCache = love.graphics.getFont() + love.graphics.setFont(self.font) + + local max = math.ceil(self._max/10) * 10 + 20 + local len = #self.data + local steps = self.width / len + + -- Build the line data + local lineData = {} + for i=1, len do + -- Build the X and Y of the point + local x = steps * (i - 1) + self.x + local y = self.height * (-self.data[i] / max + 1) + self.y + + -- Append it to the line + table.insert(lineData, x) + table.insert(lineData, y) + end + + -- Draw the line + love.graphics.line(unpack(lineData)) + + -- Print the label + if self.label ~= '' then + love.graphics.print(self.label, self.x, self.y + self.height - self.font:getHeight()) + end + + -- Reset the font + love.graphics.setFont(fontCache) + end + + return instance +end + +return debugGraph diff --git a/vendor/inspect.lua b/vendor/inspect.lua new file mode 100644 index 0000000..ae5b430 --- /dev/null +++ b/vendor/inspect.lua @@ -0,0 +1,341 @@ +local inspect ={ + _VERSION = 'inspect.lua 3.1.0', + _URL = 'http://github.com/kikito/inspect.lua', + _DESCRIPTION = 'human-readable representations of tables', + _LICENSE = [[ + MIT LICENSE + + Copyright (c) 2013 Enrique GarcĂa Cota + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ]] +} + +local tostring = tostring + +inspect.KEY = setmetatable({}, {__tostring = function() return 'inspect.KEY' end}) +inspect.METATABLE = setmetatable({}, {__tostring = function() return 'inspect.METATABLE' end}) + +-- Apostrophizes the string if it has quotes, but not aphostrophes +-- Otherwise, it returns a regular quoted string +local function smartQuote(str) + if str:match('"') and not str:match("'") then + return "'" .. str .. "'" + end + return '"' .. str:gsub('"', '\\"') .. '"' +end + +-- \a => '\\a', \0 => '\\0', 31 => '\31' +local shortControlCharEscapes = { + ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", + ["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v" +} +local longControlCharEscapes = {} -- \a => nil, \0 => \000, 31 => \031 +for i=0, 31 do + local ch = string.char(i) + if not shortControlCharEscapes[ch] then + shortControlCharEscapes[ch] = "\\"..i + longControlCharEscapes[ch] = string.format("\\%03d", i) + end +end + +local function escape(str) + return (str:gsub("\\", "\\\\") + :gsub("(%c)%f[0-9]", longControlCharEscapes) + :gsub("%c", shortControlCharEscapes)) +end + +local function isIdentifier(str) + return type(str) == 'string' and str:match( "^[_%a][_%a%d]*$" ) +end + +local function isSequenceKey(k, sequenceLength) + return type(k) == 'number' + and 1 <= k + and k <= sequenceLength + and math.floor(k) == k +end + +local defaultTypeOrders = { + ['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4, + ['function'] = 5, ['userdata'] = 6, ['thread'] = 7 +} + +local function sortKeys(a, b) + local ta, tb = type(a), type(b) + + -- strings and numbers are sorted numerically/alphabetically + if ta == tb and (ta == 'string' or ta == 'number') then return a < b end + + local dta, dtb = defaultTypeOrders[ta], defaultTypeOrders[tb] + -- Two default types are compared according to the defaultTypeOrders table + if dta and dtb then return defaultTypeOrders[ta] < defaultTypeOrders[tb] + elseif dta then return true -- default types before custom ones + elseif dtb then return false -- custom types after default ones + end + + -- custom types are sorted out alphabetically + return ta < tb +end + +-- For implementation reasons, the behavior of rawlen & # is "undefined" when +-- tables aren't pure sequences. So we implement our own # operator. +local function getSequenceLength(t) + local len = 1 + local v = rawget(t,len) + while v ~= nil do + len = len + 1 + v = rawget(t,len) + end + return len - 1 +end + +local function getNonSequentialKeys(t) + local keys = {} + local sequenceLength = getSequenceLength(t) + for k,_ in pairs(t) do + if not isSequenceKey(k, sequenceLength) then table.insert(keys, k) end + end + table.sort(keys, sortKeys) + return keys, sequenceLength +end + +local function getToStringResultSafely(t, mt) + local __tostring = type(mt) == 'table' and rawget(mt, '__tostring') + local str, ok + if type(__tostring) == 'function' then + ok, str = pcall(__tostring, t) + str = ok and str or 'error: ' .. tostring(str) + end + if type(str) == 'string' and #str > 0 then return str end +end + +local function countTableAppearances(t, tableAppearances) + tableAppearances = tableAppearances or {} + + if type(t) == 'table' then + if not tableAppearances[t] then + tableAppearances[t] = 1 + for k,v in pairs(t) do + countTableAppearances(k, tableAppearances) + countTableAppearances(v, tableAppearances) + end + countTableAppearances(getmetatable(t), tableAppearances) + else + tableAppearances[t] = tableAppearances[t] + 1 + end + end + + return tableAppearances +end + +local copySequence = function(s) + local copy, len = {}, #s + for i=1, len do copy[i] = s[i] end + return copy, len +end + +local function makePath(path, ...) + local keys = {...} + local newPath, len = copySequence(path) + for i=1, #keys do + newPath[len + i] = keys[i] + end + return newPath +end + +local function processRecursive(process, item, path, visited) + + if item == nil then return nil end + if visited[item] then return visited[item] end + + local processed = process(item, path) + if type(processed) == 'table' then + local processedCopy = {} + visited[item] = processedCopy + local processedKey + + for k,v in pairs(processed) do + processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited) + if processedKey ~= nil then + processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited) + end + end + + local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited) + setmetatable(processedCopy, mt) + processed = processedCopy + end + return processed +end + + + +------------------------------------------------------------------- + +local Inspector = {} +local Inspector_mt = {__index = Inspector} + +function Inspector:puts(...) + local args = {...} + local buffer = self.buffer + local len = #buffer + for i=1, #args do + len = len + 1 + buffer[len] = args[i] + end +end + +function Inspector:down(f) + self.level = self.level + 1 + f() + self.level = self.level - 1 +end + +function Inspector:tabify() + self:puts(self.newline, string.rep(self.indent, self.level)) +end + +function Inspector:alreadyVisited(v) + return self.ids[v] ~= nil +end + +function Inspector:getId(v) + local id = self.ids[v] + if not id then + local tv = type(v) + id = (self.maxIds[tv] or 0) + 1 + self.maxIds[tv] = id + self.ids[v] = id + end + return tostring(id) +end + +function Inspector:putKey(k) + if isIdentifier(k) then return self:puts(k) end + self:puts("[") + self:putValue(k) + self:puts("]") +end + +function Inspector:putTable(t) + if t == inspect.KEY or t == inspect.METATABLE then + self:puts(tostring(t)) + elseif self:alreadyVisited(t) then + self:puts('<table ', self:getId(t), '>') + elseif self.level >= self.depth then + self:puts('{...}') + else + if self.tableAppearances[t] > 1 then self:puts('<', self:getId(t), '>') end + + local nonSequentialKeys, sequenceLength = getNonSequentialKeys(t) + local mt = getmetatable(t) + local toStringResult = getToStringResultSafely(t, mt) + + self:puts('{') + self:down(function() + if toStringResult then + self:puts(' -- ', escape(toStringResult)) + if sequenceLength >= 1 then self:tabify() end + end + + local count = 0 + for i=1, sequenceLength do + if count > 0 then self:puts(',') end + self:puts(' ') + self:putValue(t[i]) + count = count + 1 + end + + for _,k in ipairs(nonSequentialKeys) do + if count > 0 then self:puts(',') end + self:tabify() + self:putKey(k) + self:puts(' = ') + self:putValue(t[k]) + count = count + 1 + end + + if mt then + if count > 0 then self:puts(',') end + self:tabify() + self:puts('<metatable> = ') + self:putValue(mt) + end + end) + + if #nonSequentialKeys > 0 or mt then -- result is multi-lined. Justify closing } + self:tabify() + elseif sequenceLength > 0 then -- array tables have one extra space before closing } + self:puts(' ') + end + + self:puts('}') + end +end + +function Inspector:putValue(v) + local tv = type(v) + + if tv == 'string' then + self:puts(smartQuote(escape(v))) + elseif tv == 'number' or tv == 'boolean' or tv == 'nil' then + self:puts(tostring(v)) + elseif tv == 'table' then + self:putTable(v) + else + self:puts('<',tv,' ',self:getId(v),'>') + end +end + +------------------------------------------------------------------- + +function inspect.inspect(root, options) + options = options or {} + + local depth = options.depth or math.huge + local newline = options.newline or '\n' + local indent = options.indent or ' ' + local process = options.process + + if process then + root = processRecursive(process, root, {}, {}) + end + + local inspector = setmetatable({ + depth = depth, + level = 0, + buffer = {}, + ids = {}, + maxIds = {}, + newline = newline, + indent = indent, + tableAppearances = countTableAppearances(root) + }, Inspector_mt) + + inspector:putValue(root) + + return table.concat(inspector.buffer) +end + +setmetatable(inspect, { __call = function(_, ...) return inspect.inspect(...) end }) + +return inspect + diff --git a/vendor/lume.lua b/vendor/lume.lua new file mode 100644 index 0000000..3004507 --- /dev/null +++ b/vendor/lume.lua @@ -0,0 +1,772 @@ +-- +-- lume +-- +-- Copyright (c) 2016 rxi +-- +-- This library is free software; you can redistribute it and/or modify it +-- under the terms of the MIT license. See LICENSE for details. +-- + +local lume = { _version = "2.2.3" } + +local pairs, ipairs = pairs, ipairs +local type, assert, unpack = type, assert, unpack or table.unpack +local tostring, tonumber = tostring, tonumber +local math_floor = math.floor +local math_ceil = math.ceil +local math_atan2 = math.atan2 or math.atan +local math_sqrt = math.sqrt +local math_abs = math.abs + +local noop = function() +end + +local identity = function(x) + return x +end + +local patternescape = function(str) + return str:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%1") +end + +local absindex = function(len, i) + return i < 0 and (len + i + 1) or i +end + +local iscallable = function(x) + if type(x) == "function" then return true end + local mt = getmetatable(x) + return mt and mt.__call ~= nil +end + +local getiter = function(x) + if lume.isarray(x) then + return ipairs + elseif type(x) == "table" then + return pairs + end + error("expected table", 3) +end + +local iteratee = function(x) + if x == nil then return identity end + if iscallable(x) then return x end + if type(x) == "table" then + return function(z) + for k, v in pairs(x) do + if z[k] ~= v then return false end + end + return true + end + end + return function(z) return z[x] end +end + + + +function lume.clamp(x, min, max) + return x < min and min or (x > max and max or x) +end + + +function lume.round(x, increment) + if increment then return lume.round(x / increment) * increment end + return x >= 0 and math_floor(x + .5) or math_ceil(x - .5) +end + + +function lume.sign(x) + return x < 0 and -1 or 1 +end + + +function lume.lerp(a, b, amount) + return a + (b - a) * lume.clamp(amount, 0, 1) +end + + +function lume.smooth(a, b, amount) + local t = lume.clamp(amount, 0, 1) + local m = t * t * (3 - 2 * t) + return a + (b - a) * m +end + + +function lume.pingpong(x) + return 1 - math_abs(1 - x % 2) +end + + +function lume.distance(x1, y1, x2, y2, squared) + local dx = x1 - x2 + local dy = y1 - y2 + local s = dx * dx + dy * dy + return squared and s or math_sqrt(s) +end + + +function lume.angle(x1, y1, x2, y2) + return math_atan2(y2 - y1, x2 - x1) +end + + +function lume.vector(angle, magnitude) + return math.cos(angle) * magnitude, math.sin(angle) * magnitude +end + + +function lume.random(a, b) + if not a then a, b = 0, 1 end + if not b then b = 0 end + return a + math.random() * (b - a) +end + + +function lume.randomchoice(t) + return t[math.random(#t)] +end + + +function lume.weightedchoice(t) + local sum = 0 + for _, v in pairs(t) do + assert(v >= 0, "weight value less than zero") + sum = sum + v + end + assert(sum ~= 0, "all weights are zero") + local rnd = lume.random(sum) + for k, v in pairs(t) do + if rnd < v then return k end + rnd = rnd - v + end +end + + +function lume.isarray(x) + return (type(x) == "table" and x[1] ~= nil) and true or false +end + + +function lume.push(t, ...) + local n = select("#", ...) + for i = 1, n do + t[#t + 1] = select(i, ...) + end + return ... +end + + +function lume.remove(t, x) + local iter = getiter(t) + for i, v in iter(t) do + if v == x then + if lume.isarray(t) then + table.remove(t, i) + break + else + t[i] = nil + break + end + end + end + return x +end + + +function lume.clear(t) + local iter = getiter(t) + for k in iter(t) do + t[k] = nil + end + return t +end + + +function lume.extend(t, ...) + for i = 1, select("#", ...) do + local x = select(i, ...) + if x then + for k, v in pairs(x) do + t[k] = v + end + end + end + return t +end + + +function lume.shuffle(t) + local rtn = {} + for i = 1, #t do + local r = math.random(i) + if r ~= i then + rtn[i] = rtn[r] + end + rtn[r] = t[i] + end + return rtn +end + + +function lume.sort(t, comp) + local rtn = lume.clone(t) + if comp then + if type(comp) == "string" then + table.sort(rtn, function(a, b) return a[comp] < b[comp] end) + else + table.sort(rtn, comp) + end + else + table.sort(rtn) + end + return rtn +end + + +function lume.array(...) + local t = {} + for x in ... do t[#t + 1] = x end + return t +end + + +function lume.each(t, fn, ...) + local iter = getiter(t) + if type(fn) == "string" then + for _, v in iter(t) do v[fn](v, ...) end + else + for _, v in iter(t) do fn(v, ...) end + end + return t +end + + +function lume.map(t, fn) + fn = iteratee(fn) + local iter = getiter(t) + local rtn = {} + for k, v in iter(t) do rtn[k] = fn(v) end + return rtn +end + + +function lume.all(t, fn) + fn = iteratee(fn) + local iter = getiter(t) + for _, v in iter(t) do + if not fn(v) then return false end + end + return true +end + + +function lume.any(t, fn) + fn = iteratee(fn) + local iter = getiter(t) + for _, v in iter(t) do + if fn(v) then return true end + end + return false +end + + +function lume.reduce(t, fn, first) + local acc = first + local started = first and true or false + local iter = getiter(t) + for _, v in iter(t) do + if started then + acc = fn(acc, v) + else + acc = v + started = true + end + end + assert(started, "reduce of an empty table with no first value") + return acc +end + + +function lume.set(t) + local rtn = {} + for k in pairs(lume.invert(t)) do + rtn[#rtn + 1] = k + end + return rtn +end + + +function lume.filter(t, fn, retainkeys) + fn = iteratee(fn) + local iter = getiter(t) + local rtn = {} + if retainkeys then + for k, v in iter(t) do + if fn(v) then rtn[k] = v end + end + else + for _, v in iter(t) do + if fn(v) then rtn[#rtn + 1] = v end + end + end + return rtn +end + + +function lume.reject(t, fn, retainkeys) + fn = iteratee(fn) + local iter = getiter(t) + local rtn = {} + if retainkeys then + for k, v in iter(t) do + if not fn(v) then rtn[k] = v end + end + else + for _, v in iter(t) do + if not fn(v) then rtn[#rtn + 1] = v end + end + end + return rtn +end + + +function lume.merge(...) + local rtn = {} + for i = 1, select("#", ...) do + local t = select(i, ...) + local iter = getiter(t) + for k, v in iter(t) do + rtn[k] = v + end + end + return rtn +end + + +function lume.concat(...) + local rtn = {} + for i = 1, select("#", ...) do + local t = select(i, ...) + if t ~= nil then + local iter = getiter(t) + for _, v in iter(t) do + rtn[#rtn + 1] = v + end + end + end + return rtn +end + + +function lume.find(t, value) + local iter = getiter(t) + for k, v in iter(t) do + if v == value then return k end + end + return nil +end + + +function lume.match(t, fn) + fn = iteratee(fn) + local iter = getiter(t) + for k, v in iter(t) do + if fn(v) then return v, k end + end + return nil +end + + +function lume.count(t, fn) + local count = 0 + local iter = getiter(t) + if fn then + fn = iteratee(fn) + for _, v in iter(t) do + if fn(v) then count = count + 1 end + end + else + if lume.isarray(t) then + return #t + end + for _ in iter(t) do count = count + 1 end + end + return count +end + + +function lume.slice(t, i, j) + i = i and absindex(#t, i) or 1 + j = j and absindex(#t, j) or #t + local rtn = {} + for x = i < 1 and 1 or i, j > #t and #t or j do + rtn[#rtn + 1] = t[x] + end + return rtn +end + + +function lume.first(t, n) + if not n then return t[1] end + return lume.slice(t, 1, n) +end + + +function lume.last(t, n) + if not n then return t[#t] end + return lume.slice(t, -n, -1) +end + + +function lume.invert(t) + local rtn = {} + for k, v in pairs(t) do rtn[v] = k end + return rtn +end + + +function lume.pick(t, ...) + local rtn = {} + for i = 1, select("#", ...) do + local k = select(i, ...) + rtn[k] = t[k] + end + return rtn +end + + +function lume.keys(t) + local rtn = {} + local iter = getiter(t) + for k in iter(t) do rtn[#rtn + 1] = k end + return rtn +end + + +function lume.clone(t) + local rtn = {} + for k, v in pairs(t) do rtn[k] = v end + return rtn +end + + +function lume.fn(fn, ...) + assert(iscallable(fn), "expected a function as the first argument") + local args = { ... } + return function(...) + local a = lume.concat(args, { ... }) + return fn(unpack(a)) + end +end + + +function lume.once(fn, ...) + local f = lume.fn(fn, ...) + local done = false + return function(...) + if done then return end + done = true + return f(...) + end +end + + +local memoize_fnkey = {} +local memoize_nil = {} + +function lume.memoize(fn) + local cache = {} + return function(...) + local c = cache + for i = 1, select("#", ...) do + local a = select(i, ...) or memoize_nil + c[a] = c[a] or {} + c = c[a] + end + c[memoize_fnkey] = c[memoize_fnkey] or {fn(...)} + return unpack(c[memoize_fnkey]) + end +end + + +function lume.combine(...) + local n = select('#', ...) + if n == 0 then return noop end + if n == 1 then + local fn = select(1, ...) + if not fn then return noop end + assert(iscallable(fn), "expected a function or nil") + return fn + end + local funcs = {} + for i = 1, n do + local fn = select(i, ...) + if fn ~= nil then + assert(iscallable(fn), "expected a function or nil") + funcs[#funcs + 1] = fn + end + end + return function(...) + for _, f in ipairs(funcs) do f(...) end + end +end + + +function lume.call(fn, ...) + if fn then + return fn(...) + end +end + + +function lume.time(fn, ...) + local start = os.clock() + local rtn = {fn(...)} + return (os.clock() - start), unpack(rtn) +end + + +local lambda_cache = {} + +function lume.lambda(str) + if not lambda_cache[str] then + local args, body = str:match([[^([%w,_ ]-)%->(.-)$]]) + assert(args and body, "bad string lambda") + local s = "return function(" .. args .. ")\nreturn " .. body .. "\nend" + lambda_cache[str] = lume.dostring(s) + end + return lambda_cache[str] +end + + +local serialize + +local serialize_map = { + [ "boolean" ] = tostring, + [ "nil" ] = tostring, + [ "string" ] = function(v) return string.format("%q", v) end, + [ "number" ] = function(v) + if v ~= v then return "0/0" -- nan + elseif v == 1 / 0 then return "1/0" -- inf + elseif v == -1 / 0 then return "-1/0" end -- -inf + return tostring(v) + end, + [ "table" ] = function(t, stk) + stk = stk or {} + if stk[t] then error("circular reference") end + local rtn = {} + stk[t] = true + for k, v in pairs(t) do + rtn[#rtn + 1] = "[" .. serialize(k, stk) .. "]=" .. serialize(v, stk) + end + stk[t] = nil + return "{" .. table.concat(rtn, ",") .. "}" + end +} + +setmetatable(serialize_map, { + __index = function(_, k) error("unsupported serialize type: " .. k) end +}) + +serialize = function(x, stk) + return serialize_map[type(x)](x, stk) +end + +function lume.serialize(x) + return serialize(x) +end + + +function lume.deserialize(str) + return lume.dostring("return " .. str) +end + + +function lume.split(str, sep) + if not sep then + return lume.array(str:gmatch("([%S]+)")) + else + assert(sep ~= "", "empty separator") + local psep = patternescape(sep) + return lume.array((str..sep):gmatch("(.-)("..psep..")")) + end +end + + +function lume.trim(str, chars) + if not chars then return str:match("^[%s]*(.-)[%s]*$") end + chars = patternescape(chars) + return str:match("^[" .. chars .. "]*(.-)[" .. chars .. "]*$") +end + + +function lume.wordwrap(str, limit) + limit = limit or 72 + local check + if type(limit) == "number" then + check = function(s) return #s >= limit end + else + check = limit + end + local rtn = {} + local line = "" + for word, spaces in str:gmatch("(%S+)(%s*)") do + local s = line .. word + if check(s) then + table.insert(rtn, line .. "\n") + line = word + else + line = s + end + for c in spaces:gmatch(".") do + if c == "\n" then + table.insert(rtn, line .. "\n") + line = "" + else + line = line .. c + end + end + end + table.insert(rtn, line) + return table.concat(rtn) +end + + +function lume.format(str, vars) + if not vars then return str end + local f = function(x) + return tostring(vars[x] or vars[tonumber(x)] or "{" .. x .. "}") + end + return (str:gsub("{(.-)}", f)) +end + + +function lume.trace(...) + local info = debug.getinfo(2, "Sl") + local t = { info.short_src .. ":" .. info.currentline .. ":" } + for i = 1, select("#", ...) do + local x = select(i, ...) + if type(x) == "number" then + x = string.format("%g", lume.round(x, .01)) + end + t[#t + 1] = tostring(x) + end + print(table.concat(t, " ")) +end + + +function lume.dostring(str) + return assert((loadstring or load)(str))() +end + + +function lume.uuid() + local fn = function(x) + local r = math.random(16) - 1 + r = (x == "x") and (r + 1) or (r % 4) + 9 + return ("0123456789abcdef"):sub(r, r) + end + return (("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"):gsub("[xy]", fn)) +end + + +function lume.hotswap(modname) + local oldglobal = lume.clone(_G) + local updated = {} + local function update(old, new) + if updated[old] then return end + updated[old] = true + local oldmt, newmt = getmetatable(old), getmetatable(new) + if oldmt and newmt then update(oldmt, newmt) end + for k, v in pairs(new) do + if type(v) == "table" then update(old[k], v) else old[k] = v end + end + end + local err = nil + local function onerror(e) + for k in pairs(_G) do _G[k] = oldglobal[k] end + err = lume.trim(e) + end + local ok, oldmod = pcall(require, modname) + oldmod = ok and oldmod or nil + xpcall(function() + package.loaded[modname] = nil + local newmod = require(modname) + if type(oldmod) == "table" then update(oldmod, newmod) end + for k, v in pairs(oldglobal) do + if v ~= _G[k] and type(v) == "table" then + update(v, _G[k]) + _G[k] = v + end + end + end, onerror) + package.loaded[modname] = oldmod + if err then return nil, err end + return oldmod +end + + +local ripairs_iter = function(t, i) + i = i - 1 + local v = t[i] + if v then return i, v end +end + +function lume.ripairs(t) + return ripairs_iter, t, (#t + 1) +end + + +function lume.color(str, mul) + mul = mul or 1 + local r, g, b, a + r, g, b = str:match("#(%x%x)(%x%x)(%x%x)") + if r then + r = tonumber(r, 16) / 0xff + g = tonumber(g, 16) / 0xff + b = tonumber(b, 16) / 0xff + a = 1 + elseif str:match("rgba?%s*%([%d%s%.,]+%)") then + local f = str:gmatch("[%d.]+") + r = (f() or 0) / 0xff + g = (f() or 0) / 0xff + b = (f() or 0) / 0xff + a = f() or 1 + else + error(("bad color string '%s'"):format(str)) + end + return r * mul, g * mul, b * mul, a * mul +end + + +function lume.rgba(color) + local a = math_floor((color / 16777216) % 256) + local r = math_floor((color / 65536) % 256) + local g = math_floor((color / 256) % 256) + local b = math_floor((color) % 256) + return r, g, b, a +end + + +local chain_mt = {} +chain_mt.__index = lume.map(lume.filter(lume, iscallable, true), + function(fn) + return function(self, ...) + self._value = fn(self._value, ...) + return self + end + end) +chain_mt.__index.result = function(x) return x._value end + +function lume.chain(value) + return setmetatable({ _value = value }, chain_mt) +end + +setmetatable(lume, { + __call = function(_, ...) + return lume.chain(...) + end +}) + + +return lume diff --git a/vendor/lurker.lua b/vendor/lurker.lua new file mode 100644 index 0000000..6bb32a1 --- /dev/null +++ b/vendor/lurker.lua @@ -0,0 +1,249 @@ +-- +-- lurker +-- +-- Copyright (c) 2015 rxi +-- +-- This library is free software; you can redistribute it and/or modify it +-- under the terms of the MIT license. See LICENSE for details. +-- + +-- Assumes lume is in the same directory as this file +local lume = require((...):gsub("[^/.\\]+$", "lume")) + +local lurker = { _version = "1.0.1" } + + +local dir = love.filesystem.enumerate or love.filesystem.getDirectoryItems +local isdir = love.filesystem.isDirectory +local time = love.timer.getTime or os.time +local lastmodified = love.filesystem.getLastModified + +local lovecallbacknames = { + "update", + "load", + "draw", + "mousepressed", + "mousereleased", + "keypressed", + "keyreleased", + "focus", + "quit", +} + + +function lurker.init() + lurker.print("Initing lurker") + lurker.path = "." + lurker.preswap = function() end + lurker.postswap = function() end + lurker.interval = .5 + lurker.protected = true + lurker.quiet = false + lurker.lastscan = 0 + lurker.lasterrorfile = nil + lurker.files = {} + lurker.funcwrappers = {} + lurker.lovefuncs = {} + lurker.state = "init" + lume.each(lurker.getchanged(), lurker.resetfile) + return lurker +end + + +function lurker.print(...) + print("[lurker] " .. lume.format(...)) +end + + +function lurker.listdir(path, recursive, skipdotfiles) + path = (path == ".") and "" or path + local function fullpath(x) return path .. "/" .. x end + local t = {} + for _, f in pairs(lume.map(dir(path), fullpath)) do + if not skipdotfiles or not f:match("/%.[^/]*$") then + if recursive and isdir(f) then + t = lume.concat(t, lurker.listdir(f, true, true)) + else + table.insert(t, lume.trim(f, "/")) + end + end + end + return t +end + + +function lurker.initwrappers() + for _, v in pairs(lovecallbacknames) do + lurker.funcwrappers[v] = function(...) + local args = {...} + xpcall(function() + return lurker.lovefuncs[v] and lurker.lovefuncs[v](unpack(args)) + end, lurker.onerror) + end + lurker.lovefuncs[v] = love[v] + end + lurker.updatewrappers() +end + + +function lurker.updatewrappers() + for _, v in pairs(lovecallbacknames) do + if love[v] ~= lurker.funcwrappers[v] then + lurker.lovefuncs[v] = love[v] + love[v] = lurker.funcwrappers[v] + end + end +end + + +function lurker.onerror(e, nostacktrace) + lurker.print("An error occurred; switching to error state") + lurker.state = "error" + + -- Release mouse + local setgrab = love.mouse.setGrab or love.mouse.setGrabbed + setgrab(false) + + -- Set up callbacks + for _, v in pairs(lovecallbacknames) do + love[v] = function() end + end + + love.update = lurker.update + + love.keypressed = function(k) + if k == "escape" then + lurker.print("Exiting...") + love.event.quit() + end + end + + local stacktrace = nostacktrace and "" or + lume.trim((debug.traceback("", 2):gsub("\t", ""))) + local msg = lume.format("{1}\n\n{2}", {e, stacktrace}) + local colors = { 0xFF1E1E2C, 0xFFF0A3A3, 0xFF92B5B0, 0xFF66666A, 0xFFCDCDCD } + love.graphics.reset() + love.graphics.setFont(love.graphics.newFont(12)) + + love.draw = function() + local pad = 25 + local width = love.graphics.getWidth() + local function drawhr(pos, color1, color2) + local animpos = lume.smooth(pad, width - pad - 8, lume.pingpong(time())) + if color1 then love.graphics.setColor(lume.rgba(color1)) end + love.graphics.rectangle("fill", pad, pos, width - pad*2, 1) + if color2 then love.graphics.setColor(lume.rgba(color2)) end + love.graphics.rectangle("fill", animpos, pos, 8, 1) + end + local function drawtext(str, x, y, color, limit) + love.graphics.setColor(lume.rgba(color)) + love.graphics[limit and "printf" or "print"](str, x, y, limit) + end + love.graphics.setBackgroundColor(lume.rgba(colors[1])) + love.graphics.clear() + drawtext("An error has occurred", pad, pad, colors[2]) + drawtext("lurker", width - love.graphics.getFont():getWidth("lurker") - + pad, pad, colors[4]) + drawhr(pad + 32, colors[4], colors[5]) + drawtext("If you fix the problem and update the file the program will " .. + "resume", pad, pad + 46, colors[3]) + drawhr(pad + 72, colors[4], colors[5]) + drawtext(msg, pad, pad + 90, colors[5], width - pad * 2) + love.graphics.reset() + end +end + + +function lurker.exitinitstate() + lurker.state = "normal" + if lurker.protected then + lurker.initwrappers() + end +end + + +function lurker.exiterrorstate() + lurker.state = "normal" + for _, v in pairs(lovecallbacknames) do + love[v] = lurker.funcwrappers[v] + end +end + + +function lurker.update() + if lurker.state == "init" then + lurker.exitinitstate() + end + local diff = time() - lurker.lastscan + if diff > lurker.interval then + lurker.lastscan = lurker.lastscan + diff + local changed = lurker.scan() + if #changed > 0 and lurker.lasterrorfile then + local f = lurker.lasterrorfile + lurker.lasterrorfile = nil + lurker.hotswapfile(f) + end + end +end + + +function lurker.getchanged() + local function fn(f) + return f:match("%.lua$") and lurker.files[f] ~= lastmodified(f) + end + return lume.filter(lurker.listdir(lurker.path, true, true), fn) +end + + +function lurker.modname(f) + return (f:gsub("%.lua$", ""):gsub("[/\\]", ".")) +end + + +function lurker.resetfile(f) + lurker.files[f] = lastmodified(f) +end + + +function lurker.hotswapfile(f) + lurker.print("Hotswapping '{1}'...", {f}) + if lurker.state == "error" then + lurker.exiterrorstate() + end + if lurker.preswap(f) then + lurker.print("Hotswap of '{1}' aborted by preswap", {f}) + lurker.resetfile(f) + return + end + local modname = lurker.modname(f) + local t, ok, err = lume.time(lume.hotswap, modname) + if ok then + lurker.print("Swapped '{1}' in {2} secs", {f, t}) + else + lurker.print("Failed to swap '{1}' : {2}", {f, err}) + if not lurker.quiet and lurker.protected then + lurker.lasterrorfile = f + lurker.onerror(err, true) + lurker.resetfile(f) + return + end + end + lurker.resetfile(f) + lurker.postswap(f) + if lurker.protected then + lurker.updatewrappers() + end +end + + +function lurker.scan() + if lurker.state == "init" then + lurker.exitinitstate() + end + local changed = lurker.getchanged() + lume.each(changed, lurker.hotswapfile) + return changed +end + + +return lurker.init() |