-- love.run: main entrypoint function for LÖVE -- -- Most apps can just use the default shown in https://love2d.org/wiki/love.run, -- but we need to override it to: -- * recover from errors (by switching to the source editor) -- * run all tests (functions starting with 'test_') on startup, and -- * save some state that makes it possible to switch between the main app -- and a source editor, while giving each the illusion of complete -- control. function love.run() Version, Major_version = App.love_version() App.snapshot_love() -- Tests always run at the start. App.run_tests_and_initialize() --? print('==') love.timer.step() local dt = 0 return function() if love.event then love.event.pump() for name, a,b,c,d,e,f in love.event.poll() do if name == "quit" then if not love.quit or not love.quit() then return a or 0 end end xpcall(function() love.handlers[name](a,b,c,d,e,f) end, handle_error) end end dt = love.timer.step() xpcall(function() App.update(dt) end, handle_error) love.graphics.origin() love.graphics.clear(love.graphics.getBackgroundColor()) xpcall(App.draw, handle_error) love.graphics.present() love.timer.sleep(0.001) end end function handle_error(err) local callstack = debug.traceback('', --[[stack frame]]2) Error_message = 'Error: ' .. tostring(err)..'\n'..cleaned_up_callstack(callstack) print(Error_message) if Current_app == 'run' then Settings.current_app = 'source' love.filesystem.write('config', json.encode(Settings)) load_file_from_source_or_save_directory('main.lua') App.undo_initialize() App.run_tests_and_initialize() else -- abort without running love.quit handler Disable_all_quit_handlers = true love.event.quit() end end -- I tend to read code from files myself (say using love.filesystem calls) -- rather than offload that to load(). -- Functions compiled in this manner have ugly filenames of the form [string "filename"] -- This function cleans out this cruft from error callstacks. function cleaned_up_callstack(callstack) local frames = {} for frame in string.gmatch(callstack, '[^\n]+\n*') do local line = frame:gsub('^%s*(.-)\n?$', '%1') local filename, rest = line:match('([^:]*):(.*)') local core_filename = filename:match('^%[string "(.*)"%]$') -- pass through frames that don't match this format -- this includes the initial line "stack traceback:" local new_frame = (core_filename or filename)..':'..rest table.insert(frames, new_frame) end -- the initial "stack traceback:" line was unindented and remains so return table.concat(frames, '\n\t') end -- The rest of this file wraps around various LÖVE primitives to support -- automated tests. Often tests will run with a fake version of a primitive -- that redirects to the real love.* version once we're done with tests. -- -- Not everything is so wrapped yet. Sometimes you still have to use love.* -- primitives directly. App = {} function App.love_version() local major_version, minor_version = love.getVersion() local version
Copyright (c) 2020, Andinus <andinus@nand.sh>

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
check the results of -- file operations inside the App.filesystem table. function App.open_for_reading(filename) if App.filesystem[filename] then return { lines = function(self) return App.filesystem[filename]:gmatch('[^\n]+') end, read = function(self) return App.filesystem[filename] end, close = function(self) end, } end end function App.read_file(filename) return App.filesystem[filename] end function App.open_for_writing(filename) App.filesystem[filename] = '' return { write = function(self, s) App.filesystem[filename] = App.filesystem[filename]..s end, close = function(self) end, } end function App.write_file(filename, contents) App.filesystem[filename] = contents return --[[status]] true end function App.mkdir(dirname) -- nothing in test mode end function App.remove(filename) App.filesystem[filename] = nil end -- Some helpers to trigger an event and then refresh the screen. Akin to one -- iteration of the event loop. -- all textinput events are also keypresses -- TODO: handle chords of multiple keys function App.run_after_textinput(t) App.keypressed(t) App.textinput(t) App.keyreleased(t) App.screen.contents = {} App.draw() end -- not all keys are textinput -- TODO: handle chords of multiple keys function App.run_after_keychord(chord, key) App.keychord_press(chord, key) App.keyreleased(key) App.screen.contents = {} App.draw() end function App.run_after_mouse_click(x,y, mouse_button) App.fake_mouse_press(x,y, mouse_button) App.mousepressed(x,y, mouse_button) App.fake_mouse_release(x,y, mouse_button) App.mousereleased(x,y, mouse_button) App.screen.contents = {} App.draw() end function App.run_after_mouse_press(x,y, mouse_button) App.fake_mouse_press(x,y, mouse_button) App.mousepressed(x,y, mouse_button) App.screen.contents = {} App.draw() end function App.run_after_mouse_release(x,y, mouse_button) App.fake_mouse_release(x,y, mouse_button) App.mousereleased(x,y, mouse_button) App.screen.contents = {} App.draw() end -- miscellaneous internal helpers function App.color(color) love.graphics.setColor(color.r, color.g, color.b, color.a) end -- prepend file/line/test function prepend_debug_info_to_test_failure(test_name, err) local err_without_line_number = err:gsub('^[^:]*:[^:]*: ', '') local stack_trace = debug.traceback('', --[[stack frame]]5) local file_and_line_number = stack_trace:gsub('stack traceback:\n', ''):gsub(': .*', '') local full_error = file_and_line_number..':'..test_name..' -- '..err_without_line_number --? local full_error = file_and_line_number..':'..test_name..' -- '..err_without_line_number..'\t\t'..stack_trace:gsub('\n', '\n\t\t') table.insert(Test_errors, full_error) end nativefs = require 'nativefs' local Keys_down = {} -- call this once all tests are run -- can't run any tests after this function App.disable_tests() -- have LÖVE delegate all handlers to App if they exist -- make sure to late-bind handlers like LÖVE's defaults do for name in pairs(love.handlers) do if App[name] then -- love.keyboard.isDown doesn't work on Android, so emulate it using -- keypressed and keyreleased events if name == 'keypressed' then love.handlers[name] = function(key, scancode, isrepeat) Keys_down[key] = true return App.keypressed(key, scancode, isrepeat) end elseif name == 'keyreleased' then love.handlers[name] = function(key, scancode) Keys_down[key] = nil return App.keyreleased(key, scancode) end else love.handlers[name] = function(...) App[name](...) end end end end -- test methods are disallowed outside tests App.run_tests = nil App.disable_tests = nil App.screen.init = nil App.filesystem = nil App.time = nil App.run_after_textinput = nil App.run_after_keychord = nil App.keypress = nil App.keyrelease = nil App.run_after_mouse_click = nil App.run_after_mouse_press = nil App.run_after_mouse_release = nil App.fake_keys_pressed = nil App.fake_key_press = nil App.fake_key_release = nil App.fake_mouse_state = nil App.fake_mouse_press = nil App.fake_mouse_release = nil -- other methods dispatch to real hardware App.screen.resize = love.window.setMode App.screen.size = love.window.getMode App.screen.move = love.window.setPosition App.screen.position = love.window.getPosition App.screen.print = love.graphics.print App.open_for_reading = function(filename) local result = nativefs.newFile(filename) local ok, err = result:open('r') if ok then return result else return ok, err end end App.read_file = function(path) if not is_absolute_path(path) then return --[[status]] false, 'Please use an unambiguous absolute path.' end local f, err = App.open_for_reading(path) if err then return --[[status]] false, err end local contents = f:read() f:close() return contents end App.open_for_writing = function(filename) local result = nativefs.newFile(filename) local ok, err = result:open('w') if ok then return result else return ok, err end end App.write_file = function(path, contents) if not is_absolute_path(path) then return --[[status]] false, 'Please use an unambiguous absolute path.' end local f, err = App.open_for_writing(path) if err then return --[[status]] false, err end f:write(contents) f:close() return --[[status]] true end App.files = nativefs.getDirectoryItems App.file_info = nativefs.getInfo App.mkdir = nativefs.createDirectory App.remove = nativefs.remove App.source_dir = love.filesystem.getSource()..'/' -- '/' should work even on Windows App.current_dir = nativefs.getWorkingDirectory()..'/' App.save_dir = love.filesystem.getSaveDirectory()..'/' App.get_time = love.timer.getTime App.get_clipboard = love.system.getClipboardText App.set_clipboard = love.system.setClipboardText App.key_down = function(key) return Keys_down[key] end App.mouse_move = love.mouse.setPosition App.mouse_down = love.mouse.isDown App.mouse_x = love.mouse.getX App.mouse_y = love.mouse.getY end