Gamebuino Save format

Creations

Sorunome

1 year ago

This will explain you how to use the gamebuino save format, the gb.save interface. First, this will explain the simple usage and after that the advanced, more customizable usage with config-gamebuino.h

Save Structure

The savefile is structured in blocks. A block can hold a number or a buffer. By default the savefile has 64 blocks.

Simple number usage

Handling numbers is very easily done:

gb.save.set(0, 42); // saves 42 in block 0
gb.save.set(1, -1337); // saves -1337 in block 1
int i = gb.save.get(0); // fetches block 0, which is 42
int j = gb.save.get(1); // fetches block 0, which is -1337
gb.save.del(0); // deletes block 0

Numbers default to zero (configurable, see advanced usage)

Blob usage

Now, instead of storing a number in a block, you can also store a blob. By default the size of a blob is 32 bytes.

Well, what is a blob? A blob can be literetally anything! You can store strings, buffers, objects or just about anything you'd want to store.

gb.save.set(2, "Foxes are awesome!"); // stores the string "Foxes are awesome!" in block 2

CustomObject obj;
gb.save.set(3, obj); // stores obj

void* buffer = malloc(10);
gb.save.set(4, buffer, 10); // stores a buffer of length 10

if these functions return true setting was possible, if they return false then something went wrong.

Now, how do you fetch those blobs?

char string[32];
gb.save.get(2, string);
gb.display.print(string); // prints block 2 as string, so "Foxes are awesome!"

CustomObject obj;
gb.save.get(3, obj); // fetches the custom object

void* buf = malloc(10);
gb.save.get(4, buf, 10); // fetches up to 10 bytes from block 4

if these blocks don't exist yet the buffer is filled with zeros (configurable, see advanced usage)

Important notice

Please note that after setting it is enforced wether the block is an int or a blob. For example:

// this will error
gb.save.set(0, 42);
gb.save.set(0, "blah"); // error, block is an int!!

// this will also error
gb.save.set(0, 42);
char name[10];
gb.save.get(0, name); // error, block is an int!

// this will work, though
gb.save.set(0, 42);
gb.save.del(0); // we delete the block
gb.save.set(0, "Foxies!");

Floats etc.

You can easily store floats / signed ints etc. as ints by casting them:

gb.save.set(0, (int32_t)3.14159);
float pi = (float)gb.save.get(0);

gb.save.set(1, (int32_t)true);
bool stuff = (bool)gb.save.get(1);


Advanced Usage

The basic idea of advanced usage is to pre-define which block is an int and which one is a blob and to allow custom defaults. For this we need to create an array of the type SaveDefault, passing that then on to gb.save.config:

const SaveDefault savefileDefaults[] = {
  { /* data */ },
  { /* data */ },
};

void setup() {
  gb.begin();

  // set the save defaults
  gb.save.config(savefileDefaults);
}

Now, there are multiple different SaveDefault data things possible. The first argument is always the block number to configure, the second one which type that block must be (SAVETYPE_INT or SAVETYPE_BLOB). After that, it depends on if you use int or blob:

SAVETYPE_INT

As third argument you have to set the default value. Fourth one is always zero. So these all are valid SaveDefault data things:

{ 0, SAVETYPE_INT, 9999, 0 }
{ 1, SAVETYPE_INT, -42, 0 }

SAVETYPE_BLOB

Blobs must have a third and optionally a fourth argument. With only three arguments the third one is the max length of the blob (fourth one must be zero again), with four arguments the third one is the default and the third one the max length. For the default data, you see below how that needs to be set.

The length of a blob can only be up to ~2^32 bytes*!

So these are valid blob constructors:

{ 2, SAVETYPE_BLOB, 20, 0 }
{ 3, SAVETYPE_BLOB, {.ptr="Fox"}, 20 }

Example

Ok, that was a lot to take in. So here is an example taking full use of all the features:

const SaveDefault savefileDefaults[] = {
  { SAVE_PLAYERNAME, SAVETYPE_BLOB, {.ptr="Link"}, 20 },
  { SAVE_HEARTS, SAVETYPE_INT, 3, 0 },
  { SAVE_SOMEBUFFER, SAVETYPE_BLOB, 25, 0 },
};

void setup() {
  gb.begin();

  gb.save.config(savefileDefaults);
}

// somewhere later in the code
char name[20];
gb.save.get(SAVE_PLAYERNAME, name);
gb.display.print(name); // prints "Link"

int hearts = gb.save.get(SAVE_HEARTS); // returns 3


I sure hope this helped you to understand how to use the save API to its full extend!


*: While a blob can be up to ~2^32 bytes large, the total payload may not exceed 2^32-1 bytes. Payload is all blob sizes added together. A blob is always four bytes larger than you set it, because it is prefixed with its size.

View full creation

NeoTechni

NEW 1 year ago

Please note that after setting it is enforced weather the block is an int or a blob.

*whether

dreamer3

NEW 1 year ago

Out of curiosity since games typically won't be sharing save data what is the advantage of using this format vs just defining and using your own custom save format?

Sorunome

1 year ago

Well, with your own save format you'd have to think yourself about how to save things etc. This API provides abstraction to the point that you can just

gb.save.set(0, someObject);

and all the hard lifting is done by the library

Sorunome

NEW 1 year ago

dreamer3 dreamer3

Well, with your own save format you'd have to think yourself about how to save things etc. This API provides abstraction to the point that you can just

gb.save.set(0, someObject);

and all the hard lifting is done by the library

Codnpix

NEW 4 weeks ago

Hello, thank you for these explanations.

I'm totally new using c++ and I have a little question :

I don't understand this syntax : {.ptr="Link"}

Thank you again.


EDIT : 

Another couple of questions while I'm trying to make this work on my stuff :) :

I want to save the last unlocked level, so I set the default value like this : 

#define SAVE_LAST_UNLOCKED_LEVEL 0
const SaveDefault savefileDefaults[] = {
  {SAVE_LAST_UNLOCKED_LEVEL, SAVETYPE_INT, 1, 0}
};

The default value is 1 so if no level has been unlocked yet, only the number 1 is available.

This works well so far, but I have a problem when I try to overwrite the value I stored there.

My setup function looks like this :

void setup() {
  gb.begin();
  gb.save.config(savefileDefaults);
  currentLevel = gb.save.get(SAVE_LAST_UNLOCKED_LEVEL);
  lastUnlockedLevel = gb.save.get(SAVE_LAST_UNLOCKED_LEVEL);
  chosenLevel = currentLevel;
  displayMenu();
}


And later in the program, when a level is cleared, I call this function :

void updateSavedLevel() {

  if (currentLevel > lastUnlockedLevel) {
    gb.save.set(SAVE_LAST_UNLOCKED_LEVEL, currentLevel);
  }

  lastUnlockedLevel = gb.save.get(SAVE_LAST_UNLOCKED_LEVEL);
}

But that SAVE_LAST_UNLOCKED_LEVEL remains always 1, so the levels don't unlocked themselves in the menu...

Did I miss something ? Is it possible to write over that default value ?

If not, what should I do ?

Thanks.

Sorunome

4 weeks ago

I don't understand this syntax : {.ptr="Link"} 

If you are rather new to C++, you shouldn't worry too much about the syntax, just that you can use it this way. If, however, you want to look more into it, then look into struct and unioin initilization.

Another couple of questions while I'm trying to make this work on my stuff :) : [...]

are you using lastUnlockedLevel to base the menu output off of? did you try to gb.display.print(lastUnlockedLevel) to verify that variables contents?

Sorunome

NEW 4 weeks ago

Codnpix Codnpix

I don't understand this syntax : {.ptr="Link"} 

If you are rather new to C++, you shouldn't worry too much about the syntax, just that you can use it this way. If, however, you want to look more into it, then look into struct and unioin initilization.

Another couple of questions while I'm trying to make this work on my stuff :) : [...]

are you using lastUnlockedLevel to base the menu output off of? did you try to gb.display.print(lastUnlockedLevel) to verify that variables contents?

Codnpix

4 weeks ago

If you are rather new to C++, you shouldn't worry too much about the syntax, just that you can use it this way. If, however, you want to look more into it, then look into struct and unioin initilization.

Thank you for the link !

are you using lastUnlockedLevel to base the menu output off of? did you try to gb.display.print(lastUnlockedLevel) to verify that variables contents?

Yes this is the variable I use to update the menu. I did try a print of my variable, and even directly of the gb.save.get(), but nothing moved...

But I finally succeeded by just using the basic operations save.get() and save.set(), and no default values array... It was not really necessary in that precise case but it was the occasion to understand how to use it.

Codnpix

NEW 4 weeks ago

Sorunome Sorunome

If you are rather new to C++, you shouldn't worry too much about the syntax, just that you can use it this way. If, however, you want to look more into it, then look into struct and unioin initilization.

Thank you for the link !

are you using lastUnlockedLevel to base the menu output off of? did you try to gb.display.print(lastUnlockedLevel) to verify that variables contents?

Yes this is the variable I use to update the menu. I did try a print of my variable, and even directly of the gb.save.get(), but nothing moved...

But I finally succeeded by just using the basic operations save.get() and save.set(), and no default values array... It was not really necessary in that precise case but it was the occasion to understand how to use it.

You must be logged in in order to post a message on the forum

Log in