Getting Started with Datastore and Files

As someone who is self-teaching everything to do with LUA and coding, I am admittedly stumped by some of the more basic elements.

After stumbling around with Datastore / Files and performing actions like saving, loading, and deleting, I have learned a handful of things I would like to pull together as a sort of mini-primer for others who might find it useful.

Saving Data:

  • playdate.datastore.write in layman's terms

    • This will take the table you give it and store the data as a JSON file in your projects folders. This is useful because they are tiny to store and easy to turn back into table later as needed. So what data would you need later? Put that into a table and pipe it into this.

    • Don't overlook the note about the ".json" file extension should be omitted in your file name. Save yourself an hour of trial and error because my dyslexia made me skip words.

  • Sprites:

    • You might want to try to save a sprite in your game data so you can load it later. Store the components that make up the sprite. Then when you load your save data, you will have what you need to re-create the sprite.
      • Image Path, Width, Height, X location, Y location, any special attributes

Loading Data:

  • playdate.datastore.read in layman's terms

    • This takes your file name and turn it back into a table. While the save action can be used more or less standalone in a line. The read action needs to be attached to a table for the data to be loaded into.
      • local loadedData = ds.read(filename) <- In this example, loadedData becomes your table that the data can now be freely accessed like any other table. I can't express just how much of a facepalm moment I had when I realized this.
  • If you were saving sprite data in the save file, you will need to build a process to loop through however many sprites there were and rebuild them with the data you saved.

Bundle ID

  • In Sim vs. On Device & working with both

    • Documentation (presently ver. 2.6.2) doesn't mention that the bundle ID changes for sideloaded games. There is a thread HERE that explains it, but the TLDR is that panic needed a way to globally identify apps that get sideloaded, so additional values are added to what you provide in the bundle ID in the format of "user.###.[bundleID]"

      • So do you need to do anything? Generally speaking you probably won't need to, but that depends on your project. If you are hardcoding values that include your bundleID, like a listFiles search, then you will have to pay attention that your function might not work the same when sideloaded as the string will have those additional values.

      • In my own experience, I coded in a simple filtering for one of my functions. I needed a way to gather up all the save files that have been created for an on-screen listing. Running just a simple pd.files.listFiles() will give you all the files AND directories. So I just filtered those results to just the .json files as those would be my saves.

General Advice

  • What is in your table?:

    • It's easy to potentially lose track of what exact data is being stored in the variables you are putting in your save data. Open the JSON and inspect it. Check to make sure you have the data you were expecting and in the structure you intended.

    • If your project is one where a lot of save files might exist, it could behoove you to make sure there isn't a lot of extra data in the save data. Keep your saves lean and you will help keep your projects data space requirements low.

      • In my own project I found that I was duplicating a lot of data that was being copied from some of my reference files into tables and variables in the games memory. Seeing what all was in my save data lead me to a large refactoring to further improve the structure of my project and not rely on duplicating data, but make it more dynamic and rely heavier on my reference files and accessing them as needed. I went from my save files being 5-7 KB (with just placeholder data) down to just 1 KB.
  • Testing on Device

    • This might seem very obvious to most, but I took it for granted that everything on the simulator would play out exactly the same way on device, but maybe with some performance slowdowns with certain actions. The simulator can handle some situations that the device cannot.
      • In my case, I have a gridview list that will list out all the saved files that are being listed in a table called RecordsList. Well to have the row count be dynamic, I just used gridview:setNumberOfRows(#RecordsList). The problem with that was if RecordsList was empty because there were no saved files, then on device load it would crash, but it worked perfectly fine in simulator.
      • The simulator can handle the count of the RecordList being a nil value, but the device cannot and I had to build a check and workaround to handle my RecordList potentially being empty.

Now onto the audience participation portion!

What can you add? What did I get fully wrong? What extra advice do you want to offer up to others on these topics? What questions do you have that someone might be able to answer?

4 Likes

For anyone who was watching for an update, it's done! I updated the original post with all my findings from my recent foray into the mysterious areas of the datastore.

1 Like