- Lua 99.9%
- Makefile 0.1%
livecoding library for LÖVE
This is a small live coding library for LÖVE.
Overview
LICK allows developers to reload in their changed code without restarting the application. It monitors file changes and reloads the necessary files automatically.
Key Features
- Automatic Reloading: Watches for changes in your source files and reloads them as needed.
- Error Handling: Redirects errors to the command line or displays them on the screen, making debugging easier.
- Customizable: Offers several optional parameters to customize the behavior of the live coding environment.
- Ignore Patterns: Support for a
.lickignorefile to exclude files from watching, similar to.gitignore. - Smart Package Management: Protects certain packages from being unloaded. Can customize with
lick.ignorePackages. - Reload Callbacks: Execute custom code when files are reloaded with the
lick.onReloadcallback. - Compatibility: Tested and works seamlessly with LÖVE 11.5.
How It Works
The library overrides the default love.run function to include file watching capabilities. When a file change is detected, it reloads the file and optionally calls love.load to reset the game state. Errors encountered during the reload process are captured and displayed either in the console or on the screen, depending on the configuration.
Getting Started
To use the livecoding library, simply copy lick.lua into your project and require it in your own main.lua file and set the desired parameters. The library will handle the rest, ensuring that your changes are reflected in real-time.
Optional Parameters
lick.debug = true-- displays errors in love windowlick.reset = true-- calls love.load every time you save the file, if set to false it will only be called when starting LÖVElick.clearFlag = false-- if true, clears the screen only when a file is modified; otherwise, clears every frame.lick.sleepTime = 0.001-- sleep time in seconds, default is 0.001lick.showReloadMessage = true-- show message when a file is reloadedlick.chunkLoadMessage = "CHUNK LOADED"-- message to show when a chunk is loadedlick.updateAllFiles = false-- include all .lua files in the directory and subdirectories in the watchlist for changeslick.clearPackages = false-- clear all packages in package.loaded on file changelick.debugTextXOffset = 50-- X offset for debug text from the center (positive moves right)lick.debugTextWidth = 400-- Maximum width for debug textlick.debugTextAlpha = 0.8-- Opacity of the debug text (0.0 to 1.0)lick.debugTextAlignment = "right"-- Alignment of the debug text ("left", "right", "center", "justify")lick.fileExtensions = {}-- file extensions to watch (e.g., {".lua", ".txt"}). If empty, watches all file typeslick.ignoreFile = ".lickignore"-- name of the ignore file (default: ".lickignore")lick.mergePatterns = false-- if true, merges .lickignore patterns with built-in ignore patternslick.ignorePackages = {}-- table of package names to protect from being cleared (e.g., {mylib = true, socket = true})lick.onReload = function(files) end-- callback function called after reload with list of modified files
File Ignore Patterns
LICK supports a .lickignore file to exclude files and directories from being watched. The synyax is similar to .gitignore. An example .lickignore file is included in this repository.
Creating a .lickignore File
Create a .lickignore file in your project root with patterns for files to ignore:
# Comments start with #
lick.lua
config.lua
#Ignore entire directories
assets/
temp/
# Wildcard patterns
*.txt
test_*.lua
# Recursive wildcards
**/debug/
Default Ignore Patterns
If no .lickignore file is found, LICK uses these default patterns:
# Lick Itself
lick.lua
.lickignore
# Common Lua/LOVE directories
.git/
.vscode/
.idea/
vendor/
lib/
libraries/
modules/
Set lick.mergePatterns = true to combine your .lickignore with the defaults, otherwise yours will override the default.
File Extensions
The lick.fileExtensions parameter controls which file types are watched:
-- Watch only Lua files (default behavior when updateAllFiles is true)
lick.fileExtensions = {".lua"}
-- Watch multiple file types
lick.fileExtensions = {".lua", ".txt", ".json"}
-- Watch ALL file types (leave empty)
lick.fileExtensions = {}
Protected Built-in Libraries
When lick.clearPackages = true, LICK clears the package cache on reload but protects built-in Lua and LOVE libraries from being cleared:
Default Protected Libraries:
- string, table, math, io, os, debug, coroutine, package
- utf8, bit, jit, ffi (if available)
- love (all LÖVE modules)
Protecting Additional Packages
Use lick.ignorePackages to protect additional third-party libraries from being cleared:
lick.clearPackages = true
lick.ignorePackages = {
socket = true, -- protect luasocket
cjson = true, -- protect cjson
["my.custom.lib"] = true -- protect your custom library
}
This ensures stability while still allowing your custom modules to reload. Package names should match the keys used in package.loaded.
Reload Callback
The onReload callback allows you to execute custom code after files are reloaded:
lick.onReload = function(reloaded_files)
print("Files reloaded:")
for _, file in ipairs(reloaded_files) do
print(" - " .. file)
end
-- Reset your custom modules, clear caches, etc.
if myModule then
myModule.reset()
end
end
The callback receives a table containing the paths of all files that were modified.
Custom love.run Implementation
By default, LICK overrides love.run to handle file watching and error display. If you need to implement your own love.run (for fixed timestep, custom event handling, etc.), LICK exposes three API functions you can call manually:
| Function | Purpose |
|---|---|
lick.init() |
Initialize file watching. Call once at startup. |
lick.check() |
Check for file changes and reload. Call once per frame. |
lick.drawDebug() |
Draw error overlay (requires lick.debug = true). |
Example Custom love.run
function love.run()
-- Initialize LICK before the game loop starts
lick.init()
if love.load then love.load() end
if love.timer then love.timer.step() end
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
love.handlers[name](a, b, c, d, e, f)
end
end
if love.timer then dt = love.timer.step() end
-- Check for file changes before update/draw
-- so modifications take effect immediately
lick.check()
if love.update then love.update(dt) end
if love.graphics and love.graphics.isActive() then
love.graphics.origin()
love.graphics.clear(love.graphics.getBackgroundColor())
if love.draw then love.draw() end
-- Draw error overlay on top of everything
lick.drawDebug()
love.graphics.present()
end
if love.timer then love.timer.sleep(0.001) end
end
end
Your custom love.run will override LICK's default implementation, giving you full control over the game loop while retaining hot-reload functionality.
Example main.lua
lick = require "lick"
lick.reset = true -- reload love.load every time you save
lick.updateAllFiles = true -- watch all files
lick.fileExtensions = {} -- watch all file types
lick.clearPackages = true -- clear package cache on reload
-- Protect specific third-party libraries from being cleared
lick.ignorePackages = {
socket = true,
json = true
}
-- Optional: callback when files reload
lick.onReload = function(files)
print("Reloaded " .. #files .. " file(s)")
end
function love.load()
circle = {}
circle.x = 1
end
function love.update(dt)
circle.x = circle.x + dt*5
end
function love.draw(dt)
love.graphics.circle("fill", 400+100*math.sin(circle.x), 300, 16,16)
end