I've created a cart minification & linting tool in python - Shrinko8:
https://github.com/thisismypassport/shrinko8
If you don't want to download anything, or want to use a UI - you can use the webapp here:
https://thisismypassport.github.io/shrinko8/
Otherwise, see the github link for details on how to download & use the tool.
Features
-
It can do aggressive minification to reduce the token count, the character count, and the compressed size of a cart, giving meaningfully better results than other known tools like p8tool or GEM.
-
(E.g. in one example, a 81,416 char cart went to 31,213 chars with shrinko8, 35,563 chars with p8tool (though p8tool didn't run on it without having to do some hacks), and 40,813 chars with GEM.)
-
It can do linting, aka reporting of issues that may indicate bugs in your code, such as undefined, unused and duplicate local variables.
-
It supports all pico8 syntax and globals, including modern ones (as of the time of writing, but I do plan to update it when needed).
-
It also supports creating a p8.png file directly, and can actually compress carts better than Pico-8 would.
- It also now supports declaring constants and replacing constant expressions with their value during minification - including even removing 'if false' blocks - see readme for more info.
Usage
Using the web-app is hopefully self-explanatory.
Example of using the command line: (first creates p8, second creates p8.png, third minifies less but works without changes for any cart)
python shrinko8.py path-to-input.p8 path-to-output.p8 --lint --count --minify python shrinko8.py path-to-input.p8 path-to-output.p8.png --lint --count --minify python shrinko8.py path-to-input.p8 path-to-output.p8.png --lint --count --minify-safe-only |
More information is in the readme
I've decided to share this tool since people were having trouble using p8tool on their modern carts.
Comments, issues, and contribution are welcome.
Thanks to pancelor for many bug reports, suggestions, and the tool's name.
Hi @thissismypassword:
I could sure use something like this for some of the more complex programs like the dialog window I made recently. Yep I manually compressed that myself. Took some time.
From the link you listed is there a way to download everything listed there you have - perhaps as a ZIP file - without downloading each file manually ?
@dw817 - yes, there's a Download Zip button under "Code" in all github projects, but I've added a link to it in the readme now so it's more visible.
OK, first off, big props on the fine work you did here, @thisismypassword, now that I could download it, thanks ! Definite gold star work here. Interesting idea to use lowercase letters as variable names. I'll have to remember that.
Yet - I am a little disappointed.
I thought it would take large commands such as circfill and optionally turn them into _ci or something, as I did with my code. Is there a way to do this automatically in your PY process ?
_ci=circfill cls() _ci(64,64,32) |
this tool is fantastic! it's already helping me out a lot with my current project, which is very low on compressed space
@dw817 - thanks, doing this fully automatically is too much for now (especially as it also increases the token count, so it's a trade-off), but I did add a --no-preserve option which, together with --[[preserve]], allows you to do this semi-automatically.
(You have to decide on the globals you wish to rename and add the "renames" yourself, but you can then keep using the globals by their real names and the minifier will determine the best name for them as usual).
See the new "Renaming Pico-8 Built-in functions" section in the readme for exact instructions.
Update: I've added the ability to output p8.png files (previously I kept this ability disabled since my compression code used to be worse than Pico-8's, but now that I improved it to be a bit better than Pico-8's - I've enabled this)
Glad to have a newer tool to point people to! I haven't formally given up on p8tool but I have partial PXA support that isn't working and I got kind of demoralized to fix it, so it stalled and has fallen behind. Maybe I can crib off of yours and unblock it. 😅 Nicely done!
One more update - I've added the ability to specify a custom python script to run before/after the processing, which can be useful to merge in custom data to the cart, etc.
More details (and example of use) in the README in github.
EDIT: And one more - I've added automatic removal of most unneeded parentheses, which decreases token count as part of the minification as well. (This is useful because memorizing the entire operator precedence table is hard and removing all unneeded parentheses results in hard-to-understand code)
I've finally got RP-8 fully migrated over to shrinko8, and the results have been amazing. p8tool, when I was using it, took off about 2k bytes, which was fantastic and unblocked my forward progress at the time. shrinko8 manages to take off another 2k or so bytes on top of that, dropping RP-8's compressed size from ~16.6k to ~12.4k.
Of the additional 2k I got from shrinko8, about half was just from better minification, the other half was from taking advantage of shrinko8's extension options to minify global names and table keys across both Lua code and parsed string data / embedded scripts. (This allowed me to exempt many fewer names from minification.)
If you need to squeeze out every byte you can, and especially if you've already been aggressive about moving structured data into strings to reduce token count, you should definitely check this project out.
edit: The linter is also great. It even found a missing local
that was eating about 0.5% of the CPU budget!
It's been 4-months since I commented on this marvelous tool. If you are having difficulty in downloading and installing it, I've put some information HERE to help:
[11-14-22]
OK, suggestion for future Shrinko-8. Convert this:
if orange==25 then cats=17 dogs=34 end |
to THIS:
if(a==25)b=17c=34 |
Currently I am getting this:
if e==25then n=17i=34end |
The easiest way to determine this is to ensure there are no additional IF
statements found within the block of code that is to be Shrinko-ed and definitely no incomplete IF
that requires ELSE, ELSEIF, or END on an additional line outside.
I won't be creating a new post each time. If I have further suggestions for Shrinko8, I will post them in this message.
Thanks for your attention to this !
@thisismypassword Thanks for this great tool. Do you have an Itch or Patreon or some other way the community can support your hard work?
I've now created a webapp for Shrinko8, so if you previously wanted to try it but didn't want to download things or to use the command line, now you don't have to:
@dw817 - Thanks for the suggestion. A bit late, but added:
Rewriting if statements into shorthand form is now done under the "--focus-chars" command line option.
(And sometimes even without it - depending on heuristics)
Plus, another feature just added to v1.1:
Shrinko8 now merges consecutive local statements, allowing you to write the more readable:
local stuff = {} local unrelated = 3 |
And receive:
local a,b={},3 |
Which saves tokens and characters.
(Plus, it does the same for assignments under the "--focus-tokens" command line option)
suggestion:
been creating a game using this so i can fit it into a url, and ive come up with a way to get rid of some characters a bit more, but this is what ive done:
from this:
function _init() text="hello world!" number=1 end function _update() number+=1 end function _draw() cls() print(text..number) end |
to this (commented and indented to explain whats happening and to view it better.):
function _draw() --init runs once at the start of the program, achieved by running this --on the first frame only if(not a)a=true b="hello world!"c=1 --write update function stuff write here, update runs before draw, so ----that's why it goes first c+=1 --lastly, the draw code cls() print(b..c) end |
version without comments and a bit less characters:
function _draw() if(not a)a=true b="hello world!"c=1 c+=1 cls() print(b..c) end |
the reason _draw is used is because draw has less characters, but _update60 may be used for 60 fps with basically the same result.
this could be in the aggressive minification option, though its not needed because it is not really tedious to do by hand, but it would be nice if it was automated.
I don't know if this is a bad idea to do this, if this is not efficient or not organized any way, but it doesn't give any errors, so i guess its fine.
I'm having an error with the program. It tried to compile commented lines, so I get an error like "unterminated brackets" when the brackets are just part of a comment.
@StrongSand94191 - can you post a reproducible example?
Are you sure you're using the latest version of both Pico-8 and Shrinko8?
There was recently(-ish) a change in how (whether) Pico-8 deals with nested block comments, and Shrinko8 now follows the latest version's behavior (aka - no nested comments).
@thisismypassword If I paste in my celeste mod into the Shrinko8 webapp, I get "input.p8:1810:3 Unterminated long brackets." The line this is referring to is gfx. The cartridge runs fine, and if I delete the stuff like gfx and sfx, shrinko8 works.
@StrongSand94191 - Oh, it looks like you have a "--[[" at the end of the cart there. I'm curious - why?
It looks like pico-8 treats unclosed block comments like that as a regular line comment, which is weird.
I've changed shrinko8 to do the same so your case should be fixed now.
Though I'd recommend you to get rid of that unclosed "--[[" anyway, in case pico-8 decides to error out here in the future (like plain lua does).
@JoVaMi - There should be subtle differences in behaviour when you put everything in just one of _draw/_update (as the mainloop will call them at subtly different times and sometimes even different amounts), so I wouldn't want to do that automatically. [*]
That said, the idea of allowing specifying an "--[[inline]]" hint to request shrinko8 to inline stuff (without having to "dirty" your code) was tossed around - though I'm not yet sure if/when I'll add it.
[*] Well, there is a good question of "you're already risking breaking carts under aggressive minification, why not also risk this behaviour change" - I guess the answer to that is that I try to limit such breakage risks to those that give a lot of benefit, and whose impact is obvious and well-understood.
I've now added supports for constants.
E.g. you can now write:
--[[const]] k_spr_hero = 5 --[[const]] k_box_x, k_box_y = 50, 60 --[[const]] DEBUG = false function _draw() spr(k_spr_hero, x, y) -- k_spr_hero gets replaced with 5 rectfill(k_box_x, k_box_y, k_box_x + 7, k_box_y + 11) -- becomes rectfill(50,60,57,71) end if DEBUG then -- this block gets removed unless DEBUG is true various_debug_stuff() end |
And have Shrinko8 automatically replace constant expressions with their value.
More information is in the readme section
[Please log in to post a comment]