Initial dummy template with hot reload logic
This commit is contained in:
commit
c2860399fa
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
source
|
||||||
|
*.pdx
|
11
.vscode/launch.json
vendored
Normal file
11
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "playdate",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Playdate: Debug",
|
||||||
|
"preLaunchTask": "${defaultBuildTask}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
32
.vscode/tasks.json
vendored
Normal file
32
.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"type": "shell",
|
||||||
|
"label": "Fennel: Compile",
|
||||||
|
"command": "make source",
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "pdc",
|
||||||
|
"problemMatcher": ["$pdc-lua", "$pdc-external"],
|
||||||
|
"dependsOn": ["Fennel: Compile"],
|
||||||
|
"label": "Playdate: Build"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "playdate-simulator",
|
||||||
|
"problemMatcher": ["$pdc-external"],
|
||||||
|
"label": "Playdate: Run"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Playdate: Build and Run",
|
||||||
|
"dependsOn": ["Playdate: Build", "Playdate: Run"],
|
||||||
|
"dependsOrder": "sequence",
|
||||||
|
"problemMatcher": [],
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
32
Makefile
Normal file
32
Makefile
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
# adapted from https://git.sr.ht/~nytpu/fennel-playdate-template/tree/master/item/Makefile
|
||||||
|
|
||||||
|
rwildcard=$(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) $(filter $(subst *,%,$2),$d))
|
||||||
|
|
||||||
|
PDXNAME = Explosionface
|
||||||
|
EXCLUDESOURCES = src/macros.fnl
|
||||||
|
SOURCEFILES = $(filter-out $(EXCLUDESOURCES),$(call rwildcard, src, *.fnl))
|
||||||
|
OUTFILES = $(subst src,source,$(SOURCEFILES:.fnl=.lua) $(filter-out $(SOURCEFILES),$(call rwildcard, src, *)))
|
||||||
|
|
||||||
|
all: $(PDXNAME).pdx
|
||||||
|
|
||||||
|
source: $(OUTFILES)
|
||||||
|
|
||||||
|
source/%.lua: src/%.fnl
|
||||||
|
mkdir -p $(shell dirname $@)
|
||||||
|
fennel -c $< > $@
|
||||||
|
|
||||||
|
source/%: src/%
|
||||||
|
mkdir -p $(shell dirname $@)
|
||||||
|
cp $< $@
|
||||||
|
|
||||||
|
$(PDXNAME).pdx: source
|
||||||
|
pdc source $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -r $(PDXNAME).pdx
|
||||||
|
rm -r source
|
||||||
|
|
||||||
|
run: $(PDXNAME).pdx
|
||||||
|
PlaydateSimulator $<
|
||||||
|
|
||||||
|
.PHONY: all source clean
|
21
src/main.fnl
Normal file
21
src/main.fnl
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
(import :CoreLibs/object)
|
||||||
|
(import :CoreLibs/graphics)
|
||||||
|
(import :CoreLibs/sprites)
|
||||||
|
(import :CoreLibs/timer)
|
||||||
|
(import :package)
|
||||||
|
|
||||||
|
(local reload (require :meta.reload))
|
||||||
|
|
||||||
|
(local test (require :test))
|
||||||
|
(local gfx playdate.graphics)
|
||||||
|
|
||||||
|
(let [font (gfx.getSystemFont gfx.font.kVariantNormal)]
|
||||||
|
(fn _G.drawtext [text x y] (font:drawText text x y)))
|
||||||
|
|
||||||
|
(fn playdate.update []
|
||||||
|
(drawtext test.x 5 5)
|
||||||
|
(drawtext "Hello from Fennel!" 5 25)
|
||||||
|
(gfx.sprite.update)
|
||||||
|
(playdate.timer.updateTimers)
|
||||||
|
(reload.check)
|
||||||
|
)
|
32
src/meta/iter.fnl
Normal file
32
src/meta/iter.fnl
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
(fn coro-iter [f ?start-arg]
|
||||||
|
(fn coro-next [co prev]
|
||||||
|
(if (= (coroutine.status co) :dead) nil
|
||||||
|
|
||||||
|
((fn [success ...] (if success ... (error ...)))
|
||||||
|
(coroutine.resume co prev))))
|
||||||
|
(values coro-next (coroutine.create (fn [...] (f ...) nil)) ?start-arg))
|
||||||
|
|
||||||
|
(fn countiter [minmax ?max ?step]
|
||||||
|
(let [min (if ?max minmax 1)
|
||||||
|
max (or ?max minmax)
|
||||||
|
step (or ?step 1)]
|
||||||
|
(fn [_ iprev]
|
||||||
|
(let [i (if iprev (+ iprev step) min)]
|
||||||
|
(when (if (> step 0) (<= i max) (>= i max)) i)))))
|
||||||
|
|
||||||
|
(fn reverse-ipairs [table]
|
||||||
|
(fn prev [tbl i-next]
|
||||||
|
(let [i (- i-next 1)]
|
||||||
|
(when (> i 0)
|
||||||
|
(values i (. tbl i)))))
|
||||||
|
(values prev table (+ (length table) 1)))
|
||||||
|
|
||||||
|
(fn nextpacked [packed i-prev]
|
||||||
|
(let [i (+ i-prev 1)]
|
||||||
|
(when (<= i (or packed.n (length packed)))
|
||||||
|
(values i (. packed i)))))
|
||||||
|
|
||||||
|
(fn ivalues [...]
|
||||||
|
(values nextpacked (table.pack ...) 0))
|
||||||
|
|
||||||
|
{: coro-iter : countiter : reverse-ipairs : nextpacked : ivalues}
|
54
src/meta/proxy.fnl
Normal file
54
src/meta/proxy.fnl
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
; Proxy: An object with no backing data of its own, that intercepts property accesses
|
||||||
|
; from / to its backing table, possibly transforming them in the process.
|
||||||
|
; It should look, as much as possible, like a regular table, though they are subject
|
||||||
|
; to certain limitations:
|
||||||
|
; * table.* functions like `table.insert` do not work reliably or at all before Lua 5.3
|
||||||
|
; * the `next` function always returns nil, because in order for the __index and __newindex
|
||||||
|
; metamethods to fire on every access, the raw table must not have any data in it
|
||||||
|
; * On Lua 5.1 proxies are implemented as userdata so that the __len metamethod functions.
|
||||||
|
; `type` is rewritten to return :table in this case, so `match` will continue to work.
|
||||||
|
; Proxies also implement the atom protocol by default.
|
||||||
|
|
||||||
|
(local {: extend} (require :meta.table))
|
||||||
|
|
||||||
|
(local Proxy {})
|
||||||
|
|
||||||
|
(fn Proxy.proxy? [proxy]
|
||||||
|
"Returns true if an object is a proxy."
|
||||||
|
(match (getmetatable proxy)
|
||||||
|
{: target} true
|
||||||
|
_ false))
|
||||||
|
|
||||||
|
(fn Proxy.target [proxy]
|
||||||
|
"Returns the target object that the proxy is intercepting."
|
||||||
|
(. (getmetatable proxy) :target))
|
||||||
|
|
||||||
|
(fn Proxy.new-blank [metatable]
|
||||||
|
"Creates a new 'blank' table with the given `metatable`."
|
||||||
|
(setmetatable {} metatable))
|
||||||
|
|
||||||
|
(fn Proxy.target-next [t ?k]
|
||||||
|
(let [target (Proxy.target t)
|
||||||
|
k (next target ?k)]
|
||||||
|
(when k (values k (. t k)))))
|
||||||
|
|
||||||
|
(fn Proxy.metatable [target ?override]
|
||||||
|
"Returns a 'default' metatable, overriding table metamethods to implement 'no-ops' so as to
|
||||||
|
appear as though it is the target object. The `?override` table, if provided, is merged into
|
||||||
|
the metatable, overriding any metamethods it may contain."
|
||||||
|
(extend {: target
|
||||||
|
:__index #(. (Proxy.target $1) $2)
|
||||||
|
:__newindex #(tset (Proxy.target $1) $2 $3)
|
||||||
|
:__pairs #(values Proxy.target-next $1 nil)
|
||||||
|
:__len #(length (Proxy.target $1))
|
||||||
|
:__type :table
|
||||||
|
:get Proxy.target
|
||||||
|
:set #(tset (getmetatable $1) :target $2)}
|
||||||
|
(or ?override {})))
|
||||||
|
|
||||||
|
(fn Proxy.new [target ?mt-override]
|
||||||
|
"Creates a new proxy object targetting `target`, with behaviour customized in its metatable by
|
||||||
|
`?mt-override`."
|
||||||
|
(Proxy.new-blank (Proxy.metatable target ?mt-override)))
|
||||||
|
|
||||||
|
Proxy
|
80
src/meta/reload.fnl
Normal file
80
src/meta/reload.fnl
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
(local Proxy (require :meta.proxy))
|
||||||
|
(local reload {})
|
||||||
|
|
||||||
|
(if playdate.isSimulator
|
||||||
|
(do
|
||||||
|
(fn reload.redirect-table [t target]
|
||||||
|
(each [k _ (pairs t)] (tset t k nil))
|
||||||
|
(fn __reload [t new-val]
|
||||||
|
(let [mt (getmetatable t)]
|
||||||
|
(set mt.target new-val)
|
||||||
|
t))
|
||||||
|
(setmetatable t (Proxy.metatable target {: __reload})))
|
||||||
|
|
||||||
|
(fn reload.copy-table-contents [src dst ?preprocess]
|
||||||
|
(each [k v (pairs src)]
|
||||||
|
(tset dst k (if ?preprocess (?preprocess v (. dst k)) v))))
|
||||||
|
|
||||||
|
(fn reload.replace-table [old new]
|
||||||
|
(reload.copy-table-contents new old #(reload.reload-value $2 $1))
|
||||||
|
(reload.redirect-table new old)
|
||||||
|
old)
|
||||||
|
|
||||||
|
(fn reload.reload-table [old new]
|
||||||
|
(let [mt (getmetatable old)]
|
||||||
|
;; TODO: figure out how `new`'s metatable should influence `old`?
|
||||||
|
(if (and mt mt.__reload) (mt.__reload old new)
|
||||||
|
(= (type new) :table) (reload.replace-table old new)
|
||||||
|
new)))
|
||||||
|
|
||||||
|
(fn reload.reload-value [old new]
|
||||||
|
(if (= (type old) :table) (reload.reload-table old new)
|
||||||
|
new))
|
||||||
|
|
||||||
|
(fn reload.reload [modname]
|
||||||
|
(let [old (. package.loaded modname)
|
||||||
|
_ (tset package.loaded modname nil)
|
||||||
|
new (reload.reload-value old (require modname))]
|
||||||
|
new))
|
||||||
|
|
||||||
|
(set package.watches {})
|
||||||
|
|
||||||
|
(fn reload.modtime [modname]
|
||||||
|
(playdate.file.modtime (package.pdzfilename modname)))
|
||||||
|
|
||||||
|
(fn reload.modtime= [time1 time2]
|
||||||
|
(and (= time1.year time2.year)
|
||||||
|
(= time1.month time2.month)
|
||||||
|
(= time1.day time2.day)
|
||||||
|
(= time1.hour time2.hour)
|
||||||
|
(= time1.minute time2.minute)
|
||||||
|
(= time1.second time2.second)))
|
||||||
|
|
||||||
|
(fn reload.watch [modname]
|
||||||
|
(when (and (= nil (. package.watches modname))
|
||||||
|
(playdate.file.exists (package.pdzfilename modname)))
|
||||||
|
(tset package.watches modname {:modtime (reload.modtime modname)})))
|
||||||
|
|
||||||
|
(fn reload.check []
|
||||||
|
(each [modname watch (pairs package.watches)]
|
||||||
|
(let [newmodtime (reload.modtime modname)]
|
||||||
|
(when (not (reload.modtime= watch.modtime newmodtime))
|
||||||
|
(print (.. "Plugin reload: " modname))
|
||||||
|
(set watch.modtime newmodtime)
|
||||||
|
(reload.reload modname)))))
|
||||||
|
|
||||||
|
(reload.watch :meta.proxy)
|
||||||
|
(reload.watch :meta.iter)
|
||||||
|
(reload.watch :meta.table)
|
||||||
|
|
||||||
|
(let [oldrequire _G.require]
|
||||||
|
(fn _G.require [modname]
|
||||||
|
(when (= nil (. package.loaded modname))
|
||||||
|
(reload.watch modname))
|
||||||
|
(oldrequire modname))))
|
||||||
|
|
||||||
|
(do
|
||||||
|
(fn reload.watch [modname])
|
||||||
|
(fn reload.check [])))
|
||||||
|
|
||||||
|
reload
|
57
src/meta/table.fnl
Normal file
57
src/meta/table.fnl
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
(local {: ivalues} (require :meta.iter))
|
||||||
|
(local tbl {})
|
||||||
|
|
||||||
|
; Naming convention: Verb means mutation, noun / adjective means no mutation
|
||||||
|
|
||||||
|
(fn tbl.ensure [t key ?default-value]
|
||||||
|
(match (. t key)
|
||||||
|
something something
|
||||||
|
nil (do (tset t key (or ?default-value {}))
|
||||||
|
(. t key))))
|
||||||
|
|
||||||
|
(fn tbl.extend [t ...]
|
||||||
|
(each [_ t-override (ivalues ...)]
|
||||||
|
(collect [k v (pairs t-override) &into t] k v))
|
||||||
|
t)
|
||||||
|
|
||||||
|
(fn tbl.defaults [t ...]
|
||||||
|
(each [_ t-default (ivalues ...)]
|
||||||
|
(collect [k v (pairs t-default) &into t]
|
||||||
|
(when (= (. t k) nil) (values k v))))
|
||||||
|
t)
|
||||||
|
|
||||||
|
(fn tbl.unset [t ...]
|
||||||
|
(each [_ k (ivalues ...)]
|
||||||
|
(tset t k nil))
|
||||||
|
t)
|
||||||
|
|
||||||
|
(fn tbl.clear [t ...]
|
||||||
|
(let [keys-to-keep (collect [_ k (ivalues ...)] k true)]
|
||||||
|
(each [k (pairs t)]
|
||||||
|
(when (not (. keys-to-keep k))
|
||||||
|
(tset t k nil))))
|
||||||
|
t)
|
||||||
|
|
||||||
|
(fn tbl.deepclone [v]
|
||||||
|
(if (= (type v) :table)
|
||||||
|
(let [mt (getmetatable v)]
|
||||||
|
(if (and mt mt.__deepclone)
|
||||||
|
(mt.__deepclone v)
|
||||||
|
(collect [k child (pairs v)] k (tbl.deepclone child))))
|
||||||
|
v))
|
||||||
|
|
||||||
|
(fn tbl.concat [t ...]
|
||||||
|
(each [_ t-concat (ivalues ...)]
|
||||||
|
(icollect [_ v (ipairs t-concat) &into t] v))
|
||||||
|
t)
|
||||||
|
|
||||||
|
(fn tbl.extended [...]
|
||||||
|
(tbl.extend {} ...))
|
||||||
|
|
||||||
|
(fn tbl.concatenated [...]
|
||||||
|
(tbl.concat [] ...))
|
||||||
|
|
||||||
|
(fn tbl.unpacked [packed ?i ?j]
|
||||||
|
(table.unpack packed ?i (or ?j packed.n)))
|
||||||
|
|
||||||
|
tbl
|
37
src/package.lua
Normal file
37
src/package.lua
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
_G.package = { loaded = {}, searchers = {}, preload = {} }
|
||||||
|
|
||||||
|
function _G.require(modname)
|
||||||
|
local module = package.loaded[modname]
|
||||||
|
if module ~= nil then
|
||||||
|
return module
|
||||||
|
end
|
||||||
|
local searcherrors = {}
|
||||||
|
for _, searcher in ipairs(package.searchers) do
|
||||||
|
local loader, data = searcher(modname)
|
||||||
|
if type(loader) == "function" then
|
||||||
|
module = loader(data)
|
||||||
|
package.loaded[modname] = module
|
||||||
|
return module
|
||||||
|
elseif type(loader) == "string" then
|
||||||
|
table.insert(searcherrors, loader)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
error("Unable to load module: " .. modname .. "\n" .. table.concat(searcherrors, "\n"))
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(package.searchers, function (modname)
|
||||||
|
return package.preload[modname]
|
||||||
|
end)
|
||||||
|
|
||||||
|
function package.pdzfilename(modname)
|
||||||
|
return modname:gsub("%.", "/") .. ".pdz"
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(package.searchers, function (modname)
|
||||||
|
local filename = package.pdzfilename(modname)
|
||||||
|
if playdate.file.exists(filename) then
|
||||||
|
return playdate.file.run, filename
|
||||||
|
else
|
||||||
|
return filename .. " does not exist"
|
||||||
|
end
|
||||||
|
end)
|
5
src/pdxinfo
Normal file
5
src/pdxinfo
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
name=Explosionface
|
||||||
|
author=Jeremy Penner
|
||||||
|
description=Keep the world from exploding!
|
||||||
|
bundleID=com.glorioustrainwrecks.explosionface
|
||||||
|
buildNumber=1
|
1
src/test.fnl
Normal file
1
src/test.fnl
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{:x :suppery :z 3}
|
Loading…
Reference in a new issue