Introduction to Loading and Saving

Home --> Programming Projects --> Fractal Block Engine --> Introduction to Loading and Saving

When does Loading and Saving Occur?

So for example, if the player creates a stone block in a chunk, that will be stored in the CC object associated with the chunk. If the player moves far away from the chunk, the chunk will be destroyed (so that it is no longer in the "Active Chunk Tree"). However the CC Object will remain until the player saves the game. If the player exists without saving, all CC Objects are discarded and there is no saving.

Chunks, Chunk Changes Objects, and Chunk Files


Here is the process for adding a chunk into the active chunk tree: Whenever a change is made to a chunk, the CC Object is updated immediately.

Here is the process for removing a chunk from the active chunk tree:

Why use Chunk Changes Objects (CC Objects)?

The reason we are using Chunk Changes Objects is so we only need to store and track CHANGES that have been made to a chunk, not the entire state of a chunk. This saves both time and space. However, it is significantly more complicated and harder to debug than a system where there are no CC Objects.

What would it look like to not have CC Objects? Well, when the player first visits a chunk it is is procedurally generated, but then once the player makes any change to the chunk the ENTIRE state of the chunk should be saved to a chunk file. Then, when the player comes back to this chunk, the saved chunk file should be loaded without the need to re-procedurally generate the chunk. Also, saving the entire state of a chunk is simpler than recording one change at a time and buffering them.

Although Fractal Block World uses CC Objects, this was a major headache for development. Someone making their own game should seriously consider if this is worth the added complexity.

Dirty vs Clean CC Objects

Suppose when adding a certain chunk to the active chunk tree, we see that there is an associated chunk file. We then create the CC Object from the chunk file then use that to generate the chunk. Suppose that no further change is made to the chunk. That is, the CC Object is not modified after it is created from the chunk file. Then when the player saves the game, there is no need to flush the CC Object to the chunk file (because the chunk file is up to date). That is, we are saying that every CC Object can have a dirty bit, and the CC Object only needs to be saved when this dirty bit is set to true.

Fractal Block World has gone back and forth on the use of these CC Object dirty bits. On the one hand it is not that difficult to program these dirty bits and it has a significant reward. On the other hand if not done correctly, there can be annoying bugs.

CC Object Revert Time

Every change (stored in a CC Object) that is made has a "revert time": the time at which the change will be reverted back to its original state. For example, creating a wood block might last for one hour, whereas creating a stone block might last for 10 hours. Many changes have a revert time that is at most one hour in the future. For example, damaging a monster to change its health will normally revert in an hour.

The revert time of a CC Object is the maximum revert time all all changes stored by the object. Chunk files simlarly have these revert times.

The loaded game time is the time of the game when it was last loaded. When the game is being played, we are able to remove chunk files whose revert time is less than the loaded game time.

We also could periodically open chunk files and remove from them changes that have reverted (even if the entire chunk file has not reverted). However, Fractal Block World does not do this.

A Puzzle

Suppose it is time to save the game, and there is a CC Object that is empty (it does not store any changes). Do we still need to save it?

Answer: Maybe! The issue is when there is a chunk file associated to the chunk. Then it is not good enough to simply not save the CC Object. Instead we need to REMOVE the chunk file.

This is something worth thinking about, and something to be careful about when designing the system.

Dormant Chunks

Suppose the player plays the game for minutes to hours without saving the game. There will be many thousands of CC Objects created. The most common of these CC Objects are the dormant ones.

A CC Object is dormant iff there is no chunk or chunk file associated to it. These are probably the CC Objects for chunks that the player has visited for the first time and has made changes to them. Many of these changes are short term.

There are two issues. One issue is that when it is time to save, processing all these dormant chunks will take time. The other issue is that storing all these dormant chunks in the mean time will take up a lot of memory.

Thus it makes sense to delete dormant CC Objects once they become irrelevant. Fractal Block World sorts all dormant CC Objects based on their revert time. Once a dormant CC Object's revert time is less than the current game time (not the loaded game time), we can delete the CC Object.

Precomputing Chunk Files

Once a chunk is removed from the active chunk tree, we still need to hold on to the associated CC Object. What we can do at the time when the chunk is removed is we can precompute the chunk file that would need to be saved later. This makes it faster when the user saves the game. This is what Fractal Block World does.

There is a draw back to this method of precomputing the chunk file. The issue is that the later were generate a chunk file, the more changes will have reverted and hence will not need to be saved.

Naming Chunk Files

We refer to a CC Object by its chunk path. This seems like the only sensible way to do it. Here is an example of a chunk path:

73a_8f5_421_c49.

Here the "73a" refers to the position (7,3,10), the 8f5 refers to the position (8,15,5), etc (we are using hexadecimal). Every position is of the form (x,y,z) where each of x,y,z is between 0 and 15 inclusive. Hence we can refer to each position as an integer between 0 and 4095 inclusive. Fractal Block Words stores every block path as a vector of such integers.

What should be the name of the chunk file that sores the data in the CC Object with the block path 73a_8f5_421_c49? We could simply call the file

"73a_8f5_421_c49.txt"

However the problem is that the names of these files can be extremely long. So instead we have each chunk file be named a "hashcode" of the chunk path, and inside each chunk file is the full path. The hahscode is a 4 byte unsigned integer. Here is how the hashcode of a block path is computed in Fractal Block World:
//The first 81 primes after one million.
//We actually only use the first 80.
int prime_table[] = {
    1000003, 1000033, 1000037, 1000039, 1000081, 1000099, 1000117, 1000121, 1000133,
    1000151, 1000159, 1000171, 1000183, 1000187, 1000193, 1000199, 1000211, 1000213,
    1000231, 1000249, 1000253, 1000273, 1000289, 1000291, 1000303, 1000313, 1000333,
    1000357, 1000367, 1000381, 1000393, 1000397, 1000403, 1000409, 1000423, 1000427,
    1000429, 1000453, 1000457, 1000507, 1000537, 1000541, 1000547, 1000577, 1000579,
    1000589, 1000609, 1000619, 1000621, 1000639, 1000651, 1000667, 1000669, 1000679,
    1000691, 1000697, 1000721, 1000723, 1000763, 1000777, 1000793, 1000829, 1000847,
    1000849, 1000859, 1000861, 1000889, 1000907, 1000919, 1000921, 1000931, 1000969,
    1000973, 1000981, 1000999, 1001003, 1001017, 1001023, 1001027, 1001041, 1001069};

//Gets the n-th prime after one million, looping around.
int GetPrime(int n) {
    if( n < 0 ) n = 0; //Should not happen.
    n = n % 80;
    return prime_table[n];
}

//The hashcode of a block path.
unsigned int GetHashcodeOfBlockPath(
    const vector<int> & path)
{
    int n = 0;
    long long total = 0;
    total += GetPrime(n++) * path.size();
    for(int i = 0; i < path.size(); ++i) {
        total += GetPrime(n++) * path[i];
    }
    return (total % 0xffffffff);
}

So then writing the hashcode in hexadecimal, a chunk file is named something like

"3f21ada6_1.txt".

What is the purpose of the "_1" at the end? This is in case there is a hash collision. If there are two chunk files with block paths with identical hashcodes, then "_1" and "_2" are used to distinguish the files. A hash collition is extremely rare.

Suppose we want to determine if there is a chunk file with a certain block path P. We first compute the hashcode of P and see if there is a chunk file with that name followed by "_X". If there is such a file, we must open it and start to read it to see if that path of the chunk file is P.

Not Literally Storing Each Chunk File in its own File

In an older version of Fractal Block World each chunk file literally was its own file on the filesystem. A problem with this is that playing the game for several hours, there are tens of thousands of files.

Instead, we can bundle chunk files together into one big file. The big file is broken into "pages" (of size 256 bytes). A chunk file is then stored in some number of pages inside the big file.

The Manifest

MORE!!! MORE!!! MORE!!! MORE!!!