colla/docs/arena.md
2024-11-29 16:10:48 +01:00

131 lines
No EOL
4.1 KiB
Markdown

---
title = Arena
---
# Arena
-----------
An arena is a bump allocator, under the hood it can use one of 3 strategies to allocate its data:
* `Virtual`: allocates with virtual memory, meaning that it reserves the data upfront, but only allocates one page at a time (usually 64 Kb). This is the preferred way to use the arena as it can freely allocate gigabytes of memory for free
* `Malloc`: allocates the memory upfront using malloc
* `Static`: uses a buffer passed by the user instead of allocating
To help with allocating big chunks of data, `arena.h` defines the macros `KB`, `MB`, and `GB`. If you don't need them you can define `ARENA_NO_SIZE_HELPERS`
To create an arena use the macro `arenaMake`:
```c
arena_t varena = arenaMake(ARENA_VIRTUAL, GB(1));
uint8 buffer[1024];
arena_t sarena = arenaMake(ARENA_STATIC, sizeof(buffer), buffer);
```
To allocate use the `alloc` macro. The parameters to allocate are:
* A pointer to the arena
* The type to allocate
* (optional) the number of items to allocate
* (optional) flags:
* `ALLOC_NOZERO`: by default the arena sets the memory to zero before returning, use this if you want to skip it
* `ALLOC_SOFT_FAIL`: by default the arena panics when an allocation fails, meaning that it never returns `NULL`.
* (automatic) the align of the type
* (automatic) the size of the type
Example usage:
```c
// allocate 15 strings
str_t *string_list = alloc(arena, str_t, 15);
// allocate a 1024 bytes buffer
uint8 *buffer = alloc(arena, uint8, 1024);
// allocate a structure
game_t *game = alloc(arena, game_t);
```
The strength of the arena is that it makes it much easier to reason about lifetimes, for instance:
```c
// pass a pointer to the arena for the data that we need to return
WCHAR *win32_get_full_path(arena_t *arena, const char *rel_path) {
// we need a small scratch arena for allocations that
// will be thrown away at the end of this function
uint8 scratch_buf[1024];
arena_t scratch = arenaMake(ARENA_STATIC, sizeof(scratch_buf, scratch_buf));
WCHAR *win32_rel_path = str_to_wchar(&scratch, rel_path);
DWORD pathlen = GetFullPathName(win32_rel_path, 0, NULL, NULL);
WCHAR *fullpath = alloc(arena, WCHAR, pathlen + 1);
GetFullPath(win32_rel_path, pathlen + 1, fullpath, NULL);
// notice how we don't need to free anything at the end
return fullpath;
}
```
There are a few helper functions:
* `arenaScratch`: sub allocate an arena from another arena
* `arenaTell`: returns the number of bytes allocated
* `arenaRemaining`: returns the number of bytes that can still be allocated
* `arenaRewind`: rewinds the arena to N bytes from the start (effectively "freeing" that memory)
* `arenaPop`: pops N bytes from the arena (effectively "freeing" that memory)
Here is an example usage of a full program:
```c
typedef struct {
char *buf;
usize len;
} str_t;
str_t read_file(arena_t *arena, const char *filename) {
str_t out = {0};
FILE *fp = fopen(filename, "rb");
if (!fp) goto error;
fseek(fp, 0, SEEK_END);
out.len = ftell(fp);
fseek(fp, 0, SEEK_SET);
out.buf = alloc(arena, char, out.len + 1);
fread(out.buf, 1, out.len, fp);
error:
return out;
}
void write_file(const char *filename, str_t data) {
FILE *fp = fopen(filename, "wb");
if (!fp) return;
fwrite(data.buf, 1, data.len, fp);
}
int main() {
arena_t arena = arenaMake(ARENA_VIRTUAL, GB(1));
str_t title = {0};
{
uint8 tmpbuf[KB(5)];
arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf);
str_t file = read_file(&scratch, "file.json");
json_t json = json_parse(&scratch, file);
title = str_dup(arena, json_get(json, "title"));
}
{
// copying an arena by value effectively makes it a scratch arena,
// as long as you don't use the original inside the same scope!
arena_t scratch = arena;
str_t to_write = str_fmt(
&scratch,
"{ \"title\": \"%s\" }", title.buf
);
write_file("out.json", to_write);
}
// cleanup all allocations at once
arenaCleanup(&arena);
}
```