Binary data blocks

Prerequisite knowledege: Buffers. See here for the official GameMaker explanation:https://help.yoyogames.com/hc/en-us/articles/216754708-Buffers

Buffers


To quickly give you a little bit of intuition: buffers are like how your favorite text editor, like Word, works: when you write something (press a key), text is added after your cursor (that blinking bar), you can move your cursor to anywhere in the document, but you can only write where your cursor is. A buffer is essentially a block of memory with a cursor somewhere. By writing (or reading) things, you move the cursor around. Instead of writing words, you write numbers and strings to buffers.

Binary Data Blocks (BDBs) are basically online buffers. One of the advantages over INIs is that BDBs are only downloaded when you need them. This means that you can store much more data in BDBs than in INIs, since INIs need to be fully downloaded when you connect to the server, while BDBs don't do anything until you open a BDB.

Data types


To use buffers, you'll need to be aware of the data types that you can use. Although BDBs can be bigger than INIs, they still have a size limit, so it's important that you keep whatever you write to the BDBs as small as possible. The following data types are supported by BDBs:


  • bdb_u8: stores numbers 0-255

  • bdb_u16: stores numbers 0-65535

  • bdb_u32: stores numbers 0-4.2 billion

  • bdb_s8: stores numbers -128-127

  • bdb_s16: stores numbers

  • bdb_s32: stores numbers -2 billion to 2 billion

  • bdb_f32: stores floating point numbers, for example something like 5.4 or 0.3

  • bdb_f64: stores floating point numbers, but twice as precise. You'll almost never need this.

  • bdb_string: stores strings. Uses u16 to store the string length, then one u8 for each character.



Note how each name except bdb_string, start with a letter (u, s, f) and is followed by a number. The number indicates how much space it will use, in bits. Each 8 bits equals one byte. By default, BDBs allow for up to 16KiB of storage, or 16 * 1024 bytes.

Some examples:


  • One bdb_u32 uses 4 bytes, because 32/8 = 4

  • Five bdb_s16s use 10 bytes, because (16 * 5) / 8 = 10



To make your game load and save BDBs as fast as possible, you should always choose the data type that is the smallest. For example, if you're storing health which is always 0-100, use bdb_u8 or bdb_s8. If you're storing a value between 0 and 1 (for example 0.05), multiply it by 100 and store it in a bdb_u8, so 0.05 would be stored as 5. This saves you 3 bytes over using a bdb_f32! (I know, it may not sound like much, but if you're saving 10,000 values it makes the difference between fitting inside one BDB and being way too big.)

BDB naming


All BDBs are numbered. For example, if your game has 16 BDBs, they'll be named 0, 1, 2, ..., 15. If you want to show better names to your players, you could use GameINIs to store extra BDB information.

There is also one additional BDB available for each player. These BDBs have negative numbers. For example, for a player with player_id 541, his BDB is available as number -541. These player-only BDBs can only be written to by the player that owns it, but they can be read by anyone. This makes BDBs perfect for sharing user-made content like levels, or for accessing information when the player is offline. For example, you could store the inventory in the player's BDB, so that everyone can see each other's inventory even when they're offline.

BDB usage


As mentioned before, BDBs are not downloaded until you need them. This also means that they won't be available immediately. To download a BDB, use gms_bdb_open. This function requires both a block ID (see BDB naming), and a callback script. Since it takes some time for the BDB to be downloaded, the extension will execute your script when the download is finished, so that the game can continue to run and you can (for example) show a loading dialog.

The general stucture looks something like this:
gms_bdb_open(-1, true, on_bdb_downloaded) // Load size43's BDB

// script: on_bdb_downloaded

var fid = argument0
// Use functions like gms_bdb_read(fid, ...) or gms_bdb_write(fid, ...)

gms_bdb_close(fid)


Note: Do not forget to close the BDB after using it. The changes will not be uploaded until you close the BDB. If you forget to close, all data will be lost!

Exclusive access


BDBs support one more, very useful, feature: exclusive access. In a GameINI, everyone can write to it at any time. Which is great, as long as no two people write to the same key at the same time: then it's a race to see whose change arrives at the server the last, and the other one will be lost.

Exclusive access solves this problem by only allowing one player to read or write the BDB at any time. All other players that call gms_bdb_open will be placed in a wait queue, and will be allowed in one-by-one after the first player closes the BDB. This means that no changes will ever be lost, because no two people can write to the same BDB at the same time.

Unless you have a specific reason to disable exclusive access, you should always use it. Disabling exclusive access can be done by changing the second argument to gms_bdb_open to false, like this:
gms_bdb_open(-1, false, on_bdb_downloaded) // Load size43's BDB


Switching from INIs to BDBs


Switching from INIs to BDBs is relatively simple if you're already using a script to save all data at once. One major difference is that BDBs use ordering to determine what's what, while INIs use sections and keys. This means that if you save things in a different order than how you load things, your save will corrupt!

For example, consider the following INI save script:
ini_open('save.ini')
ini_write_real('Player', 'health', health)
ini_write_real('Player', 'score', score)
ini_close()


This can be translated to:

gms_bdb_open(-gms_self_playerid(), true, on_save_game) // Load player's BDB

// script: on_save_game

var fid = argument0
gms_bdb_write(fid, bdb_u8, health)
gms_bdb_write(fid, bdb_u32, score)
gms_bdb_close(fid)


We use u8 for health, since it will always be 0-100, and u32 for score, since we know that it's always greater or equal to 0, and will probably never be more than 4.2 billion.

Note: Saving can now take multiple steps. The game is not saved until gms_bdb_close() is called!

To load the save data:

gms_bdb_open(-gms_self_playerid(), true, on_load_game) // Load player's BDB

// script: on_load_game

var fid = argument0
if gms_bdb_size(fid) >= 5
{
    health = gms_bdb_read(fid, bdb_u8)
    score = gms_bdb_read(fid, bdb_u32)
}
gms_bdb_close(fid)
// hide the loading dialog, goto rm_play or something similar


Note how we're using different scripts for the saving/loading callback. Also note the check for the size using gms_bdb_size. We need this to check and make sure there's enough data to read. If the player has never played before, his BDB will be completely empty!

Migrating BDBs to newer versions


If you add more to the save, like the number of lives, you'll run into a problem. If your player has just updated the game, they will still have their save data stored in the old format! You'll need to store a version in your BDBs, so you can properly detect what's in the BDB! For example, the load script might look like this:

gms_bdb_open(-gms_self_playerid(), true, on_load_game) // Load player's BDB

// script: on_load_game

var fid = argument0
if gms_bdb_size(fid) >= 6
{
    var version = gms_bdb_read(fid, bdb_u8)
    health = gms_bdb_read(fid, bdb_u8)
    score = gms_bdb_read(fid, bdb_u32)
    if version >= 2
    {
        // Version 2 includes lives

        lives = gms_bdb_read(fid, bdb_u8)
    } else {
        // We're loading an old version which does not save lives. Reset lives to 1.

        lives = 1
    }
}
gms_bdb_close(fid)
// hide the loading dialog, goto rm_play or something similar


Obviously on_save_game also needs to be changed so that it includes a version number too.

BDB limits


Any new game will have 8 BDBs of 16 KiB available by default. If you go over the limit, your BDB will be truncated to 16KiB without any warning or error. You can check how much data you're using by calling gms_bdb_tell right before gms_bdb_close.

Please contact gamemakerserver@outlook.com if your game requires bigger BDBs, or if you need more BDBs so we can discuss options. If you mail me, please include the following:


  • What limit you are hitting, and what limits you'd need

  • How many players you expect your game have in total

  • How many players you expect your game to have online concurrently

  • Your saving code (I'll look through this and give suggestions to make the save smaller)

Replies (0)

Last message on 18 Apr 2024