Log In  
Follow
huulong
Follow

Sometimes your cartridge fits in the token count, but not the characters count / compressed size, so you can't export it until you reduce the number of characters in the cartridge. You'd also like to keep comments, meaningful variable names and even debug code where you can, in case you're gonna continue working on the code.

One way to do this is to use a build pipeline:

  • copy your source file(s) to an intermediate directory
  • process file(s) to reduce code size
  • output final cartridge

If you use picotool or work with compiled languages, you should be familiar with that process. It may sound a bit overkill for PICO-8, but is very useful if you're stuck in the case mentioned above.

This is what I do when working with my custom framework pico-boots, but while I don't think many devs would be interested in using a complete framework for PICO-8 written by somebody else, they may be interested in the individual processing steps described below. You can always refer to pico-boots' repository for implementation details.

Note that I use picotool to build my cartridge from multiple sources, but the techniques I use apply to single files too. For those also using picotool, I'll explain for which Step the processing should be applied: on individual sources (pre-processing) or on the output cartridge (post-processing).

Content

  • Comment stripping
  • Minification
  • Debug code stripping
    • Multi-line
    • Single-line
  • Going further

Comment stripping

Probably the most obvious, you'll want to remove single line ("--") and block ("--[[ ]]") comments.

I used to do it manually; now the minification step does it for me, so I won't dwell on it. However, if you already use very short variable names but have lots of comments, it's worth trying comment stripping alone.

Single line comment stripping is easy to implement in a file processing script, block comment may be a bit trickier.

Step: pre-processing or post-processing

Minification

Code minification mainly consists in variable/key name shortening, space trimming and comment stripping. You can whatever does the job. However, note that minifiers are written for pure Lua and may not like PICO-8 shortcuts such as single-line

if(cond) effect

and

a += 5

So you may want to expand those into pure Lua statements, manually or inside your pipeline (don't worry though, minification will often more than make up for the loss of space).

If you use picotool, note that it generates a single-line "if(cond) effect" during the build. So you'll need to expand that in your pipeline post-processing, just before minification.

Personally, I use a custom branch in my fork of luamin. It's basically Luamin plus a few features/fixes:

  • option "--minify-level" for aggressive member minification: useful if you want to minify even attribute and method names (requires extra care!)
  • option "--newline-separator" for partial newline preservation for easier debugging: otherwise the code is a veeery long line and error messages just tell you "assert on line 1" (note that there is only a newline where luamin would put a ';' without the option, so clauses ending with brackets, like function definitions, will still chain into longer lines)
  • works outside the terminal (e.g. in Sublime Text)
    (more info on minification and npm in pico-boots README)

I know that picotool has a --lua-minify option, but it was too aggressive for me (it minifies even "__call" which breaks metatable logic).

Step: pre-processing or post-processing (but if using aggressive minification, post-processing only so member names are minified the same way across the cartridge)

Example

function demo_app.instantiate_gamestates() -- override
  return {main_menu(), debug_demo(), input_demo(), render_demo()}
end

function demo_app.on_pre_start() -- override
end

function demo_app.on_post_start() -- override
  -- enable mouse devkit
  input:toggle_mouse(true)
  ui:set_cursor_sprite_data(visual_data.sprites.cursor)
end

function demo_app.on_reset() -- override
  ui:set_cursor_sprite_data(nil)
end

becomes

function n.o()return{i(),j(),k(),l()}end
function n.p()end
function n.q()g:r(true)h:s(m.t.u)end
function n.v()h:s(nil)end

Implementation example

There is not much to do since it's already in the Luamin package, which you can get using "npm install/update" (see package.json).

However, if you're working on an actual .p8 cartridge rather than pure Lua code, you may want to extract the lua section, minify it and reinject it back. minify.py does precisely that (and also expands single-line "if(cond) effect").

Possible improvement on luamin for PICO-8

Only use lower characters to generate minified identifiers (see IDENTIFIER_PARTS in luamin.js). Otherwise, since PICO-8 auto-lowers characters when opening code in the integrated editor, it may cause variable conflicts ("Ab" becomes "ab" which may be another variable in scope) or even prevent cartridge from running ("Do" becomes "do" which is a keyword).

Debug code stripping

Multi-line

You can use whatever you want to flag debug code and remove it for the release build. However, you need some way to tell your pipeline that you are making a debug vs a release build.

On my projects, I use generic symbol-based preprocessing similar to the one in C# (or C++), but very much simplified. You define a set of symbols for your current build config (e.g. debug config defines ["assert", "log"], release config defines nothing). Then you surround parts you don't want in some build configs with special markers:

-- will be stripped in release
#if log
printh("Player hit!")
printh("Remaining HP: "..player.hp)
#endif

During build, any code surrounded by undefined symbols is stripped.

Of course, if you don't need one symbol per debug feature like me, you can just define "debug" for the debug config and surround all your debug code with "#if debug" and "#endif".

Step: pre-processing or post-processing, but recommend pre-processing to make core build faster, as there would be less code to assemble

Example

#if assert
assert(damage > 0)
#endif

player.hp = player.hp - damage

#if log
printh("Player hit!")
printh("Remaining HP: "..player.hp)
#endif

becomes in release config:

player.hp = player.hp - damage

Single-line

For single-line, you can write a simple parser that strips line calling certain functions.

For instance, I strip all single-line "assert(...)" calls if the "assert" symbol is not defined. It means that in the example above, I don't even need the "#if assert" anymore. It's very convenient when you have many logs and assertions. But multi-line stripping is still useful for more complex behaviors.

Step: same as multi-line

Example

assert(damage > 0)

player.hp = player.hp - damage

log("Player hit!")
log("Remaining HP: "..player.hp)

becomes in release config:

player.hp = player.hp - damage

Implementation example

See both multi-line and single-line stripping in preprocess.py

It also contains a reversed stripping tag "--[[#pico8" and "--#pico8]]" that comments out the code until it is built into a cartridge; but that's only useful if you run unit tests in pure Lua.

Going further

If your game has a lot of text, string compression is your next step. You can make your own, or check out a tool like p8advent (also see post which mentions alternative).

Also, if you're interested in the full build pipeline I use, check out the generic build script and the demo project script using the former.

P#72970 2020-02-10 20:48 ( Edited 2020-02-10 21:01)

When printing with no coordinate arguments, text is printed on the next line after the last prompt position. In addition, when the bottom of the screen is reached, all previous graphics are moved upward.

It is also possible to print multiple lines at once, and when printed at the bottom, it will also move all previous graphics from multiple lines.

However, the lines after the 1st one won't be printed correctly. The 2nd line's bottom half will be hidden, and the lines after that will be invisible.

Reproduction:

  1. Start PICO-8
  2. Enter print("1\n2\n3")
  3. Repeat until prompt reaches the bottom of the screen
  4. Repeat. From then on, "2" will be cut and "3" will be invisible.

Note:

This also applies to Lua multiline strings using [[ ]], [==[ ]==], etc. instead of "\n".

P#60412 2018-12-28 14:47

Follow Lexaloffle:        
Generated 2020-07-04 10:20 | 0.082s | 4194k | Q:13