Acetate: a visual debugging tool for Playdate

I recently discovered the debugDraw capabilities provided by the SDK…and then got a bit carried away. Long story short, I've created Acetate, which extends the base capabilities with a slew of new features and conveniences specifically for debugging sprites. Here's a hit list of key features, but the README in the repo will give you a better sense of what's possible.

  1. Debug drawing from directly within your sprite classes
  2. Out-of-the-box visualizations: bounding boxes, center points, rotation, etc.
  3. Controls for cycling through your sprites one by one
  4. Rich debug strings (displayed in a custom monospaced font!)
  5. Option to pause the game while performing visual debugging
  6. Keyboard shortcuts for toggling debug mode and visualization options
  7. Settings that let you decide how it looks and behaves


I've release this essentially as a beta at the moment. I think it's fairly complete, and definitely useful, but it doesn't have tests and I've only used it in one project so far. I'd love for others to try it out and provide feedback. I hope it's useful! What other interesting debugging tools have folks built?

15 Likes

I spoke too soon :man_facepalming: it IS a toybox :slight_smile:

I'll went ahead and added it to the toystore as acetate so it can be referred to directly:

toybox add acetate

If you want you can also move the font into a folder named assets in the repo and it will be automatically installed in the user's source folder. The destination location can be tweaked via the assets_sub_folder in the toy box's Boxfile. You can find an example of an asset toybox here.

3 Likes

Ah, that's neat. If I understand correctly, the tweaked path would still live inside toybox_assets . Is that right? I'm pondering how to deal with the fact that I've got a debugFontPath setting, which is just a string, which I'd like to work regardless of whether the repo is installed with toybox.

I suppose I could use some canonical string format in order to determine when the string represents a default path, e.g. "$DEFAULT/Acetate-Mono-Bold-Condensed". Then to process the path I'd have to detect that it's in the default format, check somehow whether the installation was done with toybox, and then substitute either "fonts" or "toybox_assets/github.com/ebeneliason/Acetate-Mono-Bold-Condensed".

I'd love a way to configure it to drop directly into source/fonts/, or even have this happen automatically for canonically named subdirectories of the assets folder.

I know what you mean. Problem here would be potential naming collisions between toyboxes.

You 'could' probably put your assets in toybox_assets/github.com/ebeneliason/Acetate-Mono-Bold-Condensed in your own repo and then create a Boxfile to add an asset path to that (with toybox set assets_sub_folder <value>). That way the path would be the same. It's not very elegant though.

I'm still trying to figure out a good solution here but I'm butting against the way pdc tries to simplify assets and asset paths without any way to configure it or get around it.

Yeah, I understand the need to account for possible naming collisions. However, given that they seem like an edge case, and that one must run toybox update manually from the CLI anyway, it might be worth supporting default asset installs to e.g. source/fonts and just throwing an error if a conflicting directory is already present. You could drop an invisible sentinel file in the directories you install via toybox so that you know not to throw an error when overwriting them on subsequent updates.

To make this concrete, you'd take a font file stored at myToybox/assets/fonts/myFont and install it at source/fonts/myToybox/myFont. The myToybox subdirectory within fonts would namespace all the font assets provided by the toybox, and contain e.g. a .toybox-asset file, which could optionally contain info about the toybox that installed the asset and its version.

Upon reflection, I realized that I don't need any fancy path substitution logic. I've created a function to load my fonts which looks in the canonical fonts/ directory first (using the provided debugFontPath setting verbatim), then in the toybox_assets/… path (appending the configured debugFontPath setting value to it), and finally loads the system font as a fallback. At each step I just check to see whether the font I've attempted to load is nil before continuing.

I haven’t pushed that yet. I want to do some local testing with toybox.py first.

it might be worth supporting default asset installs to e.g. source/fonts and just throwing an error if a conflicting directory is already present.

It was tempting to do that when I first added the feature but it's not very elegant either. If a toybox clash with your own stuff that's maybe ok but if one toybox developer creates a name collision with other toyboxes, users of those have no immediate solution to fix it.

you'd take a font file stored at myToybox/assets/fonts/myFont and install it at source/fonts/myToybox/myFont

That is pretty what is happening right now, with the server name (i.e. GitHub) being thrown in the path too, unless I'm not understanding the difference in what you're describing.

That's true. I suppose you could guarantee unique names for any toyboxes registered in the toystore, but maybe that's not enough. Or, perhaps that’s a requirement to get that behavior, and unregistered toyboxes still work as they do now?

The main difference is that the files wouldn't wind up at a subpath of toybox_assets — they'd wind up in the canonical fonts, images, sounds etc. directories of the project. And yes, the domain and userid segments would also be stripped out, leaving a path that would read normally even in the absence of toybox.py, with just the project name directory to group them.

But with all that said, the workarounds for this aren’t so bad, as noted above. It may not be worth the trouble!

I got this working and pushed it out as 0.1.1! Thanks for the suggestion.

1 Like

This library looks awesome by the way... :heart_eyes:

1 Like

Anyone tried this out yet? I've got a few fixes and minor enhancements waiting in the wings, and I'm thinking I'll probably just tag the next release as 1.0. I'd love to hear any feedback!

2 Likes

I spent some time adding luacheck and unit tests this week. I found a fair few bugs in the process, which I intend to push out fixes for soon. I also have a few related questions about the architectural approach I’m taking, which I’d like to get right before marking it 1.0.

  1. What’s the right encapsulation approach?
  2. What’s the right initialization approach?
  3. What’s the right configuration approach?

Regarding (1), this utility operates as a singleton. As such, I opted not to bother making it a class to be instantiated. Instead, it’s a bunch of functions and fields all grouped together under the Acetate namespace. Is that the best approach, and if so, should “Acetate” be upper or lowercase (like e.g. playdate.foo etc.)?

Regarding (2), currently it is auto-initialized when it gets imported. Initialization basically includes: loading the font and assigning playdate.debugDraw and playdate.keyPressed functions. These things only happen in the simulator, so initialization doesn’t seem risky, but nonetheless I wonder if it’s best practice to require the user to call init explicitly. (Notably, when using toybox.py the user doesn’t have control over when to import, since all toybox imports are bundled.)

Regarding (3), I currently have a settings.lua file which defines a bunch of fields directly in the Acetate namespace, which the user could change as desired. When using toybox.py, any changes to this file would be overwritten on updates, so I’m pondering a better approach. Should I require copying that to a canonically named file to create a custom config? Take the name of a config file as an argument to init? Or even just accept all the settings as arguments to init using table calling syntax?

Guidance on any of the above would be much appreciated! I want to make sure I’ve got a solid approach in place so I don’t need to make breaking changes after 1.0. Thanks!

I went ahead and made a best guess for each of the above. Here's where I landed:

  1. What’s the right encapsulation approach?

I decided to adhere to the module/package naming convention outlined in the LUA style guide and switch to lowercase.

  1. What’s the right initialization approach?

I decided to require calling init(). It seems like a sensible bit of control to leave to the developer — explicit is always better, right? — and it provides a good hook for specifying custom settings…

  1. What’s the right configuration approach?

I decided to accept a table of settings overrides as a sole argument to init(). This affords reasonable flexibility, as one can either provide just a few overridden values inline, or create a copy of the settings.lua file, provide a custom name for the table defined therein, import it, and then pass the table in as an argument by name.

Hopefully these are good decisions, but I went ahead and pushed them out as a 0.2.0 beta release (which also includes some other features and fixes) just in case. If no one suggests better alternatives or reports any bugs, I'll do a ceremonial tagging of a 1.0 release in a week or so. In the meantime, I'd still love to hear from you if you've tried it!

1 Like

Regarding (3), I currently have a settings.lua file which defines a bunch of fields directly in the Acetate namespace, which the user could change as desired. When using toybox.py , any changes to this file would be overwritten on updates, so I’m pondering a better approach.

There's a writeup about this in the toybox doc here. Check out how a toybox like pdbase does it. If you open the projet individually Luacheck will use the .luacheckrc file which then loads luacheck/Luacheck.lua.

How a toybox user can integrate this is described here. If the toybox is added to a project then toybox.py includes luacheck/Luacheck.lua into its top level luacheck.lua which can then be included into the users' .luacheck.rc like this:

require "toyboxes/luacheck" (stds, files)

std = "lua54+playdate+toyboxes"

operators = {"+=", "-=", "*=", "/="}

It's all transparent once it's setup and any toybox that provide luacheck globals will work automatically.

Let me know if you have any issues with this.

I understand the confusion based on how I phrased my sentence, but settings.lua is a configuration file for the utility, and isn’t related to luacheck. I did add luacheck support, and I believe I’ve set it up properly with respect to the linked instructions, but the question was about the best way to provide configuration of the utility itself. See this section of the README for a description of the approach I’m using now.