new live app
[?]
Nov 14, 2022, 1:12 AM
TNRO6KLZXIZUFWKCXSWAJHN2CMHS56ATGGULOKMJC2YNCFRJZKLACDependencies
Change contents
- add root[1.0][0.1]
- file addition: main.lua[0.2]
-- A general architecture for free-wheeling, live programs:-- on startup:-- scan both the app directory and the save directory for files with numeric prefixes-- from the largest numeric prefix found, obtain a manifest-- load all files (which must start with a numeric prefix) from the manifest)---- then start drawing frames on screen and reacting to events---- events from keyboard and mouse are handled as the app desires---- on incoming messages to a specific file, however, the app must:-- save the message's value to a specific, unused numeric prefix-- execute the value---- if a game encounters an error:-- find the previous version of the definition in the 'head' numeric prefix-- decrement 'head'json = require 'json'-- a namespace of "frameworky" things in addition to love.*-- these can't be modifiedapp = {}-- a namespace of frameworky callbacks-- these can be modified liveon = {}-- ========= on startup, load the version at headfunction love.load(args, commandline)-- version controlapp.Head = 0app.Next_version = 1app.History = {} -- array of filename roots corresponding to each numeric prefixapp.Manifest = {} -- mapping from roots to numeric prefixes as of version app.Headapp.load_files_so_far()-- hysteresis in a few placesapp.Current_time = 0app.Previous_read = 0app.Last_focus_time = 0app.Last_resize_time = 0love.window.setTitle('app')if on.load then on.load() endendfunction app.load_files_so_far()print('new edits will go to ' .. love.filesystem.getSaveDirectory())local files = {}app.append_files_with_numeric_prefix('', files)table.sort(files)app.check_integrity(files)app.append_files_with_numeric_prefix(love.filesystem.getSaveDirectory(), files)table.sort(files)app.check_integrity(files)app.History = app.load_history(files)app.Next_version = #app.History + 1local head_string = love.filesystem.read('head')app.Head = tonumber(head_string)if app.Head > 0 thenapp.Manifest = json.decode(love.filesystem.read(app.versioned_manifest(app.Head)))endapp.load_everything_in_manifest()endfunction app.append_files_with_numeric_prefix(dir, files)for _,file in ipairs(love.filesystem.getDirectoryItems(dir)) dotable.insert(files, file)endendfunction app.check_integrity(files)local manifest_found, file_found = false, falselocal expected_index = 1for _,file in ipairs(files) dofor numeric_prefix, root in file:gmatch('(%d+)-(.+)') do-- only runs oncelocal index = tonumber(numeric_prefix)-- skip files without numeric prefixesif index ~= nil thenif index < expected_index thenprint(index, expected_index)endassert(index >= expected_index)if index > expected_index thenassert(index == expected_index+1)assert(manifest_found and file_found)expected_index = indexmanifest_found, file_found = false, falseendassert(index == expected_index)if root == 'manifest' thenassert(not manifest_found)manifest_found = trueelseassert(not file_found)file_found = trueendendendendendfunction app.load_history(files)local result = {}for _,file in ipairs(files) dofor numeric_prefix, root in file:gmatch('(%d+)-(.+)') do-- only runs oncelocal index = tonumber(numeric_prefix)-- skipif index ~= nil thenif root ~= 'manifest' thenassert(index == #result+1)table.insert(result, root)endendendendreturn resultendfunction app.load_everything_in_manifest()for k,v in pairs(app.Manifest) doif k ~= 'parent' thenlocal root, index = k, vlocal filename = app.versioned_filename(index, root)local buf = love.filesystem.read(filename)assert(buf and buf ~= '')app.eval(buf)endendendfunction app.versioned_filename(index, root)return ('%04d-%s'):format(index, root)endfunction app.versioned_manifest(index)return ('%04d-manifest'):format(index)end-- ========= on each frame, check for messages and alter the app as neededfunction love.update(dt)app.Current_time = app.Current_time + dtif app.Current_time < app.Last_resize_time + 0.1 thenreturnendif app.Current_time - app.Previous_read > 0.1 thenlocal buf = app.receive()if buf thenapp.run(buf)endapp.Previous_read = app.Current_timeendif on.update then on.update(dt) endend-- look for a message from outside, and return nil if there's nothingfunction app.receive()local f = io.open(love.filesystem.getUserDirectory()..'/_love_akkartik_driver_app')if f == nil then return nil endlocal result = f:read('*a')f:close()if result == '' then return nil end -- empty file == no messageprint('<='..app.color(--[[bold]]1, --[[blue]]4))print(result)print(app.reset_terminal())-- we can't unlink files, so just clear themlocal clear = io.open(love.filesystem.getUserDirectory()..'/_love_akkartik_driver_app', 'w')clear:close()return resultendfunction app.send(msg)local f = io.open(love.filesystem.getUserDirectory()..'/_love_akkartik_app_driver', 'w')if f == nil then return endf:write(msg)f:close()print('=>'..app.color(0, --[[green]]2))print(msg)print(app.reset_terminal())end-- args:-- format: 0 for normal, 1 for bold-- color: 0-15function app.color(format, color)return ('\027[%d;%dm'):format(format, 30+color)endfunction app.reset_terminal()return '\027[m'end-- define or undefine top-level bindingsfunction app.run(buf)local cmd = buf:match('^%S+')assert(cmd)print('command is '..cmd)if cmd == 'QUIT' thenlove.event.quit(1)elseif cmd == 'MANIFEST' thenapp.send(json.encode(app.Manifest))elseif cmd == 'DELETE' thenlocal binding = buf:match('^%S+%s+(%S+)')app.Manifest[binding] = nilapp.eval(binding..' = nil')local next_filename = app.versioned_filename(app.Next_version, binding)love.filesystem.write(next_filename, '')table.insert(app.History, binding)app.Manifest.parent = app.Headlocal manifest_filename = app.versioned_manifest(app.Next_version)love.filesystem.write(manifest_filename, json.encode(app.Manifest))app.Head = app.Next_versionlove.filesystem.write('head', tostring(app.Head))app.Next_version = app.Next_version + 1elseif cmd == 'GET' thenlocal binding = buf:match('^%S+%s+(%S+)')app.send(app.get_binding(binding))-- other commands go hereelselocal binding = cmdlocal next_filename = app.versioned_filename(app.Next_version, binding)love.filesystem.write(next_filename, buf)table.insert(app.History, binding)app.Manifest[binding] = app.Next_versionapp.Manifest.parent = app.Headlocal manifest_filename = app.versioned_manifest(app.Next_version)love.filesystem.write(manifest_filename, json.encode(app.Manifest))app.Head = app.Next_versionlove.filesystem.write('head', tostring(app.Head))app.Next_version = app.Next_version + 1local status, err = app.eval(buf)if not status then-- roll backapp.Head = app.Manifest.parentlocal previous_manifest_filename = app.versioned_manifest(app.Head)app.Manifest = json.decode(love.filesystem.read(previous_manifest_filename))-- throw an errorapp.send('ERROR '..tostring(err))endendendfunction app.get_binding(name)if app.Manifest[name] thenreturn love.filesystem.read(app.versioned_filename(app.Manifest[name], name))endend-- Wrapper for Lua's weird evaluation model.-- Lua is persnickety about expressions vs statements, so we need to do some-- extra work to get the result of an evaluation.-- return values:-- all well -> true, ...-- load failed -> nil, error message-- run failed -> false, error messagefunction app.eval(buf)-- We assume a program is either correct with 'return' prefixed xor not.-- Is this correct? Who knows! But the Lua REPL does this as well.local f = load('return '..buf, 'REPL')if f thenreturn pcall(f)endlocal f, err = load(buf, 'REPL')if f thenreturn pcall(f)elsereturn nil, errendend-- all other love callbacks can just be delegated to the on.* namespace-- just easier to remember-- how to make these discoverable?function love.draw()if on.draw then on.draw() endendfunction love.quit()if on.quit then on.quit() endendfunction love.resize(w,h)screen = {w=w, h=h}if on.resize then on.resize(w,h) endapp.Last_resize_time = app.Current_timeendfunction love.filedropped()if on.filedropped then on.filedropped() endendfunction love.focus(in_focus)if in_focus thenapp.Last_focus_time = app.Current_timeendif on.focus then on.focus(in_focus) endendfunction love.keypressed(key, scancode, isrepeat)-- ignore events for some time after window in focus (mostly alt-tab)if app.Current_time < app.Last_focus_time + 0.01 thenreturnendif on.keypressed then on.keypressed(key, scancode, isrepeat) endendfunction love.textinput(t)-- ignore events for some time after window in focus (mostly alt-tab)if app.Current_time < app.Last_focus_time + 0.01 thenreturnendif on.textinput then on.textinput(t) endendfunction love.keyreleased(key, scancode, isrepeat)if on.keyreleased then on.keyreleased(key, scancode, isrepeat) endendfunction love.mousepressed(x,y, mouse_button)if on.mousepressed then on.mousepressed(x,y, mouse_button) endendfunction love.mousereleased(x,y, mouse_button)if on.mousereleased then on.mousereleased(x,y, mouse_button) endend-- ========= on error, pause the app and wait for messagesfunction love.run()love.load(love.arg.parseGameArguments(arg), arg)love.timer.step()local dt = 0return function()local status, result = xpcall(app.try_run, app.handle_error)return resultendend-- one iteration of the event loop-- return nil to continue the event loop, non-nil to quit-- from https://love2d.org/wiki/love.runfunction app.try_run()if love.event thenlove.event.pump()for name, a,b,c,d,e,f in love.event.poll() doif name == 'quit' thenif not love.quit() thenreturn a or 0endendlove.handlers[name](a,b,c,d,e,f)endend-- updatedt = love.timer.step()love.update(dt)-- draw before update to give it a chance to mutate statelove.graphics.origin()love.graphics.clear(love.graphics.getBackgroundColor())love.draw()love.graphics.present()love.timer.sleep(0.001)-- returning nil continues the loopend-- return nil to continue the event loop, non-nil to quitfunction app.handle_error(err)local msg = tostring(err)-- draw a pause indicator on screenlove.graphics.setColor(1,0,0)love.graphics.rectangle('fill', 10,10, 3,10)love.graphics.rectangle('fill', 16,10, 3,10)love.graphics.present()-- print stack trace here just in case we ran the app through a terminallocal stack_trace = debug.traceback('Error: ' .. tostring(msg), --[[stack frame]]2):gsub('\n[^\n]+$', '')print(stack_trace)print('Look in the driver for options to investigate further.')print("(You probably can't close the app window at this point. If you don't have the driver set up, you might need to force-quit.)")-- send stack trace to driver and wait for a responseapp.send('ERROR '..stack_trace)local bufrepeatbuf = app.receive()until bufif buf == 'QUIT' thenreturn trueendapp.run(buf)end-- some abbreviationsgraphics = love.graphicscolor = graphics.setColorline = graphics.linerect = graphics.rectanglecirc = graphics.circleellipse = graphics.ellipse-- also screen is set in resize above (which seems to also be called during initialization) - file addition: json.lua[0.2]
---- https://github.com/rxi/json.lua---- Copyright (c) 2020 rxi---- 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 json = { _version = "0.1.2" }--------------------------------------------------------------------------------- Encode-------------------------------------------------------------------------------local encodelocal escape_char_map = {[ "\\" ] = "\\",[ "\"" ] = "\"",[ "\b" ] = "b",[ "\f" ] = "f",[ "\n" ] = "n",[ "\r" ] = "r",[ "\t" ] = "t",}local escape_char_map_inv = { [ "/" ] = "/" }for k, v in pairs(escape_char_map) doescape_char_map_inv[v] = kendlocal function escape_char(c)return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))endlocal function encode_nil(val)return "null"endlocal function encode_table(val, stack)local res = {}stack = stack or {}-- Circular reference?if stack[val] then error("circular reference") endstack[val] = trueif rawget(val, 1) ~= nil or next(val) == nil then-- Treat as array -- check keys are valid and it is not sparselocal n = 0for k in pairs(val) doif type(k) ~= "number" thenerror("invalid table: mixed or invalid key types")endn = n + 1endif n ~= #val thenerror("invalid table: sparse array")end-- Encodefor i, v in ipairs(val) dotable.insert(res, encode(v, stack))endstack[val] = nilreturn "[" .. table.concat(res, ",") .. "]"else-- Treat as an objectfor k, v in pairs(val) doif type(k) ~= "string" thenerror("invalid table: mixed or invalid key types")endtable.insert(res, encode(k, stack) .. ":" .. encode(v, stack))endstack[val] = nilreturn "{" .. table.concat(res, ",") .. "}"endendlocal function encode_string(val)return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'endlocal function encode_number(val)-- Check for NaN, -inf and infif val ~= val or val <= -math.huge or val >= math.huge thenerror("unexpected number value '" .. tostring(val) .. "'")endreturn string.format("%.14g", val)endlocal type_func_map = {[ "nil" ] = encode_nil,[ "table" ] = encode_table,[ "string" ] = encode_string,[ "number" ] = encode_number,[ "boolean" ] = tostring,}encode = function(val, stack)local t = type(val)local f = type_func_map[t]if f thenreturn f(val, stack)enderror("unexpected type '" .. t .. "'")endfunction json.encode(val)return ( encode(val) )end--------------------------------------------------------------------------------- Decode-------------------------------------------------------------------------------local parselocal function create_set(...)local res = {}for i = 1, select("#", ...) dores[ select(i, ...) ] = trueendreturn resendlocal space_chars = create_set(" ", "\t", "\r", "\n")local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")local literals = create_set("true", "false", "null")local literal_map = {[ "true" ] = true,[ "false" ] = false,[ "null" ] = nil,}local function next_char(str, idx, set, negate)for i = idx, #str doif set[str:sub(i, i)] ~= negate thenreturn iendendreturn #str + 1endlocal function decode_error(str, idx, msg)local line_count = 1local col_count = 1for i = 1, idx - 1 docol_count = col_count + 1if str:sub(i, i) == "\n" thenline_count = line_count + 1col_count = 1endenderror( string.format("%s at line %d col %d", msg, line_count, col_count) )endlocal function codepoint_to_utf8(n)-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixalocal f = math.floorif n <= 0x7f thenreturn string.char(n)elseif n <= 0x7ff thenreturn string.char(f(n / 64) + 192, n % 64 + 128)elseif n <= 0xffff thenreturn string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)elseif n <= 0x10ffff thenreturn string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,f(n % 4096 / 64) + 128, n % 64 + 128)enderror( string.format("invalid unicode codepoint '%x'", n) )endlocal function parse_unicode_escape(s)local n1 = tonumber( s:sub(1, 4), 16 )local n2 = tonumber( s:sub(7, 10), 16 )-- Surrogate pair?if n2 thenreturn codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)elsereturn codepoint_to_utf8(n1)endendlocal function parse_string(str, i)local res = ""local j = i + 1local k = jwhile j <= #str dolocal x = str:byte(j)if x < 32 thendecode_error(str, j, "control character in string")elseif x == 92 then -- `\`: Escaperes = res .. str:sub(k, j - 1)j = j + 1local c = str:sub(j, j)if c == "u" thenlocal hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)or str:match("^%x%x%x%x", j + 1)or decode_error(str, j - 1, "invalid unicode escape in string")res = res .. parse_unicode_escape(hex)j = j + #hexelseif not escape_chars[c] thendecode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")endres = res .. escape_char_map_inv[c]endk = j + 1elseif x == 34 then -- `"`: End of stringres = res .. str:sub(k, j - 1)return res, j + 1endj = j + 1enddecode_error(str, i, "expected closing quote for string")endlocal function parse_number(str, i)local x = next_char(str, i, delim_chars)local s = str:sub(i, x - 1)local n = tonumber(s)if not n thendecode_error(str, i, "invalid number '" .. s .. "'")endreturn n, xendlocal function parse_literal(str, i)local x = next_char(str, i, delim_chars)local word = str:sub(i, x - 1)if not literals[word] thendecode_error(str, i, "invalid literal '" .. word .. "'")endreturn literal_map[word], xendlocal function parse_array(str, i)local res = {}local n = 1i = i + 1while 1 dolocal xi = next_char(str, i, space_chars, true)-- Empty / end of array?if str:sub(i, i) == "]" theni = i + 1breakend-- Read tokenx, i = parse(str, i)res[n] = xn = n + 1-- Next tokeni = next_char(str, i, space_chars, true)local chr = str:sub(i, i)i = i + 1if chr == "]" then break endif chr ~= "," then decode_error(str, i, "expected ']' or ','") endendreturn res, iendlocal function parse_object(str, i)local res = {}i = i + 1while 1 dolocal key, vali = next_char(str, i, space_chars, true)-- Empty / end of object?if str:sub(i, i) == "}" theni = i + 1breakend-- Read keyif str:sub(i, i) ~= '"' thendecode_error(str, i, "expected string for key")endkey, i = parse(str, i)-- Read ':' delimiteri = next_char(str, i, space_chars, true)if str:sub(i, i) ~= ":" thendecode_error(str, i, "expected ':' after key")endi = next_char(str, i + 1, space_chars, true)-- Read valueval, i = parse(str, i)-- Setres[key] = val-- Next tokeni = next_char(str, i, space_chars, true)local chr = str:sub(i, i)i = i + 1if chr == "}" then break endif chr ~= "," then decode_error(str, i, "expected '}' or ','") endendreturn res, iendlocal char_func_map = {[ '"' ] = parse_string,[ "0" ] = parse_number,[ "1" ] = parse_number,[ "2" ] = parse_number,[ "3" ] = parse_number,[ "4" ] = parse_number,[ "5" ] = parse_number,[ "6" ] = parse_number,[ "7" ] = parse_number,[ "8" ] = parse_number,[ "9" ] = parse_number,[ "-" ] = parse_number,[ "t" ] = parse_literal,[ "f" ] = parse_literal,[ "n" ] = parse_literal,[ "[" ] = parse_array,[ "{" ] = parse_object,}parse = function(str, idx)local chr = str:sub(idx, idx)local f = char_func_map[chr]if f thenreturn f(str, idx)enddecode_error(str, idx, "unexpected character '" .. chr .. "'")endfunction json.decode(str)if type(str) ~= "string" thenerror("expected argument of type string, got " .. type(str))endlocal res, idx = parse(str, next_char(str, 1, space_chars, true))idx = next_char(str, idx, space_chars, true)if idx <= #str thendecode_error(str, idx, "trailing garbage")endreturn resendreturn json - file addition: head[0.2]
0 - file addition: conf.lua[0.2]
function love.conf(t)t.window.resizable = trueend