|
| 1 | +--- |
| 2 | +title: Database |
| 3 | +description: Docs for the database system in FerrumC. |
| 4 | +--- |
| 5 | + |
| 6 | +FerrumC uses the LMDB database for storing data in a key-value format, allowing for fast and efficient data storage and |
| 7 | +retrieval. Currently, this is used exclusively for storing chunks, but in the future this will be expanded to store other |
| 8 | +data such as player data and entities. |
| 9 | + |
| 10 | +There are several layers of abstraction in place to make working with the database easier. <br/> |
| 11 | +Firstly there are some functions in `/src/lib/storage/src/lmdb.rs` that vastly simplify the process of interacting with |
| 12 | +the database. These function provide a simple interface for reading and writing data to the database through the use of |
| 13 | +functions such as `get()`, `insert()`, `delete()`, and `update()`, etc. Most of these functions work with a 128-bit key, |
| 14 | +a table name as a string and an array of bytes as the value. <br/> |
| 15 | +Inside most of these functions, tokio is used to spawn a blocking task, relegating the non-async database operations to |
| 16 | +a separate task to prevent blocking the main thread. This is done by using the `tokio::task::spawn_blocking()` function. |
| 17 | +This does require some ownership shenanigans, hence the Env being wrapped in an Arc and cloned before entering the |
| 18 | +blocking task. <br/> |
| 19 | +These functions can be used by anything, but does offer a fairly primitive interface. Code needing to interact with the |
| 20 | +database should have their own wrappers for handling things like caching or serializing/deserializing data. |
| 21 | + |
| 22 | +There is a second layer of abstract for chunks, found in `/src/lib/world/src/db_functions.rs`. These functions are |
| 23 | +specifically for handling chunk data, and provide a more high-level interface for working with chunks. |
| 24 | +These functions handle caching, serializing/deserializing, and converting coordinates and the dimension name into a key. |
| 25 | + |
| 26 | +The specifics of caching are covered in a separate section, but the general idea is that chunks are stored in a cache |
| 27 | +any time they are read or written, and are removed from the cache when a time or size cap is hit. <br/> |
| 28 | +Serializing is currently done using the [bitcode](https://crates.io/crates/bitcode/) crate, which is a simple and |
| 29 | +performant way to serialize and deserialize data. This is used to serialize the chunk data into a byte array, which is |
| 30 | +then stored in the database, using the aforementioned primitive functions. There is also a compression step used to |
| 31 | +decrease the amount of disk space a world takes up<br/> |
| 32 | +Generating the key is done in the `create_key()` function, which takes a dimension name, an x coordinate and a z |
| 33 | +coordinate and produces a 128-bit key. This key is then used to interact with the database. This key is created by first |
| 34 | +hashing the dimension name with [wyhash](https://crates.io/crates/wyhash) and shifting the 64 bit digest into a 128-bit |
| 35 | +key as the first 32 bits. The x and z coordinates are then widened to 48-bit integers and shifted into the remaining |
| 36 | +96 bits. This ensures that the dimension name has sufficient a keyspace to not risk hash collisions with many dimensions |
| 37 | +and the x and z coordinates can be sufficiently large to not limit the world size.<br/> |
0 commit comments