How to get code using 'require' to run on the Playdate without changes

I'd started building some logic for a game I wanted to make prior to getting my Playdate, figuring I could get that working and tackle UI once I actually had the little yellow guy in my hand. But I wasn't aware of the "import" vs "require" situation on the Playdate, so in a fit of modularization I used "require" quite heavily in my code to keep things nice & tidy. So was I bit alarmed when I finally started reading the Playdate SDK docs. It would probably have been annoying but not a big deal to convert things from "import" to require" in my code, but I had been hoping to keep the logic code platform independent ... standard Lua.

I took to heart what it says in the book "Programming in Lua": "Require is just a standard function" -- so I can make my own. I created a "loader" file that just defines a simple version of "require" in the global namespace, and then imports and populates a table with all the package names that are going to be required. The most important bit of the trick is since some of my modules are "requiring" others, I have to make sure that the simple ones are imported first, so that when the later complex ones are imported, the "require" function will already be set up to return the necessary dependencies.

--- file logic.lua ---

local packages = {}

-- makes a replacement for require that simply doles out modules from a table
require = function(name) return packages[name] end

-- populate the packages table
-- a module that uses 'require' must be imported _after_ its dependency so that
-- the packages table will all ready contain a reference when it is invoked inside
-- the module needing the dependency
packages.ns_map = import "../logic/ns_map.lua"
packages.ns_equipment = import "../logic/ns_equipment.lua"
packages.ns_creatures = import "../logic/ns_creatures.lua"
packages["ns_game.messages"] = import "../logic/ns_game/messages.lua"
packages["ns_game.place"] = import "../logic/ns_game/place.lua"
packages["ns_game.travel"] = import "../logic/ns_game/travel.lua"
packages["ns_game.market"] = import "../logic/ns_game/market.lua"
packages["ns_game"] = import "../logic/ns_game/init.lua"

and then in my main.lua ...

import "CoreLibs/object"
import "logic"

local ns_map = require "ns_map"
local ns_game = require "ns_game"
printTable(ns_game.status())
printTable(ns_game.choices())

...shows that everything is hooked up and working as expected. This approach is a little naive, and wouldn't work with the style of module that add themselves directly to the package.loaded global (which is not defined on the Playdate) but with a little more work you could make a more full featured version of require that would support that.. I'm fairly new to Lua, but I'm finding it quite cool how the higher level features of the language are build on top of the primitive ones, in way that makes it quite easy customize when the standard behavior doesn't do what you want (or is missing, as in this case)

3 Likes

For the benefit of anyone else looking for good solutions and finding this post as I did earlier today:

As of SDK 1.12.2, it looks like I can get away with something as simple as this:

if not import then import = require end

And then just use import everywhere I would have normally used require. It's decidedly unidiomatic Lua, but Good Enough for my purposes (which is being able to run unit tests against parts of my project outside of the simulator).

I could have sworn I tried this earlier, maybe 1.9.0, and discovered weird ways in which it broke, but with 1.12.2 it seems to be working fine.

4 Likes