├── .gitignore ├── README.md └── live ├── app.lua ├── conf.lua ├── livemodule.lua └── main.lua /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![LOVE](https://img.shields.io/badge/L%C3%96VE-11.1-EA316E.svg)](http://love2d.org/) 2 | 3 | # LÖVELive 4 | Live coding framework for [LÖVE](https://love2d.org/) 5 | 6 | [![lovelive](https://img.youtube.com/vi/ZUnI2amTmzQ/0.jpg)](https://www.youtube.com/watch?v=ZUnI2amTmzQ) 7 | 8 | ```sh 9 | $ ls 10 | live README.md 11 | $ 12 | $ love live & 13 | [1] 1234 14 | $ $EDITOR live/app.lua 15 | ``` 16 | 17 | 18 | # License 19 | - zlib License 20 | -------------------------------------------------------------------------------- /live/app.lua: -------------------------------------------------------------------------------- 1 | -- -*- Mode: lua; tab-width: 2; lua-indent-level: 2; indent-tabs-mode: nil; -*- 2 | 3 | local livemodule = require('livemodule') 4 | local app = {} 5 | 6 | -- config 7 | function app.liveconf(t) 8 | t.live = true 9 | t.use_pcall = true 10 | t.autoreload.enable = true 11 | t.autoreload.interval = 1.0 12 | t.reloadkey = "f5" 13 | t.gc_before_reload = false 14 | t.error_file = nil -- "error.txt" 15 | end 16 | 17 | 18 | -- callbacks 19 | 20 | function app.load(arg) 21 | -- initial load (== love.load()) 22 | app.reload() 23 | end 24 | 25 | function app.reload() 26 | -- reload by lovelive 27 | -- you can reload another module by `foo = livemodule.reload('foo')` 28 | end 29 | 30 | function app.update(dt) 31 | 32 | end 33 | 34 | function app.draw() 35 | love.timer.sleep(0.02) 36 | end 37 | 38 | return app 39 | 40 | -- vim: set ts=2 sw=2 tw=72 expandtab: 41 | -------------------------------------------------------------------------------- /live/conf.lua: -------------------------------------------------------------------------------- 1 | -- -*- Mode: lua; tab-width: 2; lua-indent-level: 2; indent-tabs-mode: nil; -*- 2 | 3 | -- https://love2d.org/wiki/Config_Files 4 | 5 | function love.conf(t) 6 | t.identity = nil -- The name of the save directory (string) 7 | t.appendidentity = false -- Search files in source directory before save directory (boolean) 8 | t.version = "11.0" -- The LOVE version this game was made for (string) 9 | t.console = false -- Attach a console (boolean, Windows only) 10 | t.accelerometerjoystick = true -- Enable the accelerometer on iOS and Android by exposing it as a Joystick (boolean) 11 | t.externalstorage = false -- True to save files (and read from the save directory) in external storage on Android (boolean) 12 | t.gammacorrect = false -- Enable gamma-correct rendering, when supported by the system (boolean) 13 | 14 | t.audio.mixwithsystem = true -- Keep background music playing when opening LOVE (boolean, iOS and Android only) 15 | 16 | t.window.title = "Untitled" -- The window title (string) 17 | t.window.icon = nil -- Filepath to an image to use as the window's icon (string) 18 | t.window.width = 800 -- The window width (number) 19 | t.window.height = 600 -- The window height (number) 20 | t.window.borderless = false -- Remove all border visuals from the window (boolean) 21 | t.window.resizable = false -- Let the window be user-resizable (boolean) 22 | t.window.minwidth = 1 -- Minimum window width if the window is resizable (number) 23 | t.window.minheight = 1 -- Minimum window height if the window is resizable (number) 24 | t.window.fullscreen = false -- Enable fullscreen (boolean) 25 | t.window.fullscreentype = "desktop" -- Choose between "desktop" fullscreen or "exclusive" fullscreen mode (string) 26 | t.window.vsync = 1 -- Vertical sync mode (number) 27 | t.window.msaa = 0 -- The number of samples to use with multi-sampled antialiasing (number) 28 | t.window.display = 1 -- Index of the monitor to show the window in (number) 29 | t.window.highdpi = false -- Enable high-dpi mode for the window on a Retina display (boolean) 30 | t.window.x = nil -- The x-coordinate of the window's position in the specified display (number) 31 | t.window.y = nil -- The y-coordinate of the window's position in the specified display (number) 32 | 33 | t.modules.audio = true -- Enable the audio module (boolean) 34 | t.modules.data = true -- Enable the data module (boolean) 35 | t.modules.event = true -- Enable the event module (boolean) 36 | t.modules.font = true -- Enable the font module (boolean) 37 | t.modules.graphics = true -- Enable the graphics module (boolean) 38 | t.modules.image = true -- Enable the image module (boolean) 39 | t.modules.joystick = true -- Enable the joystick module (boolean) 40 | t.modules.keyboard = true -- Enable the keyboard module (boolean) 41 | t.modules.math = true -- Enable the math module (boolean) 42 | t.modules.mouse = true -- Enable the mouse module (boolean) 43 | t.modules.physics = true -- Enable the physics module (boolean) 44 | t.modules.sound = true -- Enable the sound module (boolean) 45 | t.modules.system = true -- Enable the system module (boolean) 46 | t.modules.thread = true -- Enable the thread module (boolean) 47 | t.modules.timer = true -- Enable the timer module (boolean), Disabling it will result 0 delta time in love.update 48 | t.modules.touch = true -- Enable the touch module (boolean) 49 | t.modules.video = true -- Enable the video module (boolean) 50 | t.modules.window = true -- Enable the window module (boolean) 51 | end 52 | 53 | -- vim: set ts=2 sw=2 tw=72 expandtab: 54 | -------------------------------------------------------------------------------- /live/livemodule.lua: -------------------------------------------------------------------------------- 1 | -- -*- Mode: lua; tab-width: 2; lua-indent-level: 2; indent-tabs-mode: nil; -*- 2 | 3 | local reload = function(mod) 4 | local package = require("package") 5 | local oldmod = package.loaded[mod] 6 | package.loaded[mod] = nil 7 | local ok, obj = pcall(require, mod, true) 8 | if ok then 9 | return obj 10 | else 11 | print('error while livemodule.reload(): ' .. tostring(obj)) 12 | package.loaded[mod] = oldmod 13 | return oldmod 14 | end 15 | end 16 | 17 | return { reload = reload } 18 | -------------------------------------------------------------------------------- /live/main.lua: -------------------------------------------------------------------------------- 1 | -- -*- Mode: lua; tab-width: 2; lua-indent-level: 2; indent-tabs-mode: nil; -*- 2 | 3 | -- 4 | -- if you want use lovelive then 5 | -- use app.lua instead of main.lua 6 | -- end 7 | -- 8 | 9 | 10 | local arg, app, conf, t0 11 | local errormsg 12 | 13 | local load_module = function(mod, reload) 14 | local m = require(mod) 15 | if reload then 16 | if m.reload then 17 | m.reload() 18 | end 19 | else 20 | if m.load then 21 | m.load(arg) 22 | end 23 | end 24 | return m 25 | end 26 | 27 | local reload_module = function(mod, gc) 28 | -- this return (module, errormsg) pair 29 | local package = require("package") 30 | if package.loaded[mod] == nil then 31 | -- initial load 32 | return load_module(mod, false), nil 33 | else 34 | -- reload 35 | local oldmod = package.loaded[mod] 36 | package.loaded[mod] = nil 37 | if gc then 38 | collectgarbage("collect") 39 | end 40 | local ok, obj = pcall(load_module, mod, true) 41 | if ok then 42 | return obj, nil 43 | else 44 | package.loaded[mod] = oldmod 45 | return oldmod, obj 46 | end 47 | end 48 | end 49 | 50 | local loadliveconf = function() 51 | local c = { 52 | -- default config 53 | live = true, 54 | use_pcall = true, 55 | autoreload = { 56 | enable = true, 57 | interval = 1.0 58 | }, 59 | reloadkey = "f5", 60 | gc_before_reload = false, 61 | error_file = nil 62 | } 63 | if app.liveconf then 64 | app.liveconf(c) 65 | end 66 | if not c.live then 67 | c.autoreload.enable = false 68 | c.use_pcall = false 69 | c.reloadkey = nil 70 | end 71 | return c 72 | end 73 | 74 | local draw_msg = function(msg) 75 | love.graphics.clear() 76 | love.graphics.setColor(255, 255, 255, 255) 77 | love.graphics.print(msg, 0, 0) 78 | end 79 | 80 | local set_error = function(msg) 81 | if msg then 82 | if msg ~= errormsg then 83 | errormsg = msg 84 | print(errormsg) 85 | end 86 | if conf.error_file then 87 | local f = io.open(conf.error_file, "a+") 88 | f:write(errormsg .. "\n\n") 89 | f:flush() 90 | f:close() 91 | end 92 | else 93 | errormsg = nil 94 | end 95 | end 96 | 97 | local reload = function() 98 | local msg 99 | app, msg = reload_module("app", conf.gc_before_reload) 100 | if (not msg) and errormsg then 101 | print("live: recover from error state.") 102 | end 103 | set_error(msg) 104 | end 105 | 106 | 107 | local call = function(name, ...) 108 | if not app[name] then 109 | return 110 | end 111 | if conf.use_pcall then 112 | local ok, err = pcall(app[name], ...) 113 | if not ok then 114 | print('error from '.. name .. '(): ' .. tostring(err)) 115 | set_error(err) 116 | return err 117 | end 118 | else 119 | app[name](...) 120 | end 121 | end 122 | 123 | 124 | -- love callbacks 125 | 126 | 127 | love.load = function(arg) 128 | arg = arg 129 | app = load_module("app") 130 | conf = loadliveconf() 131 | t0 = love.timer.getTime() 132 | end 133 | 134 | love.update = function(dt) 135 | if conf.autoreload.enable then 136 | local t1 = love.timer.getTime() 137 | local elapsed = t1 - t0 138 | if elapsed > conf.autoreload.interval then 139 | t0 = t0 + elapsed 140 | reload() 141 | end 142 | end 143 | call("update", dt) 144 | end 145 | 146 | love.keypressed = function(key, scancode, isrepeat) 147 | if conf.reloadkey and conf.reloadkey == key then 148 | reload() 149 | end 150 | call("keypressed", key, isrepeat) 151 | end 152 | 153 | love.draw = function(...) 154 | if errormsg then 155 | draw_msg(errormsg) 156 | love.timer.sleep(0.1) 157 | else 158 | local err = call("draw", ...) 159 | if err then 160 | draw_msg(err) 161 | love.timer.sleep(0.1) 162 | end 163 | end 164 | end 165 | 166 | local lovecallbacks = [[ 167 | keyreleased 168 | mousefocus 169 | mousemoved 170 | directorydropped 171 | filedropped 172 | focus 173 | keyreleased 174 | lowmemory 175 | mousefocus 176 | mousemoved 177 | mousepressed 178 | mousereleased 179 | quit 180 | resize 181 | textedited 182 | textinput 183 | threaderror 184 | touchmoved 185 | touchpressed 186 | touchreleased 187 | visible 188 | 189 | gamepadaxis 190 | gamepadpressed 191 | gamepadreleased 192 | joystickadded 193 | joystickaxis 194 | joystickhat 195 | joystickpressed 196 | joystickreleased 197 | joystickremoved 198 | ]] 199 | -- errorhandler 200 | 201 | local override = function(name) 202 | local s = "love." .. name .. " = function(...) call('" .. name .. "', ...) end" 203 | local func, err = load(s, nil, 't', {call=call, love=love}) 204 | assert(func, err) 205 | local ok, _ = pcall(func) 206 | assert(ok) 207 | end 208 | 209 | for name in string.gmatch(lovecallbacks, "%S+") do 210 | override(name) 211 | end 212 | 213 | 214 | -- vim: set ts=2 sw=2 tw=72 expandtab: 215 | --------------------------------------------------------------------------------