A little knowledge is a dangerous thing, but my understanding of garbage collection and meshes is now as follows (thanks largely to the source code of the Codea Runtime Library):
Lua 5.1’s garbage collection makes use of the field totalbytes
of Lua’s global_State
(source file lgc.c
). When a new userdata value is created of size s
, behind the scenes in the Lua API, luaM_malloc
is used to allocate memory of s
plus the size of the Udata
type (lapi.c
and lstring.c
):
LUA_API void *lua_newuserdata (lua_State *L, size_t size) {
Udata *u;
...
u = luaS_newudata(L, size, getcurrenv(L));
...
}
Udata *luaS_newudata (lua_State *L, size_t s, Table *e) {
Udata *u;
...
u = cast(Udata *, luaM_malloc(L, s + sizeof(Udata)));
...
}
```
`luaM_malloc` is actually a variety of `luaM_realloc` (`lmem.h`):
#define luaM_malloc(L,t) luaM_realloc_(L, NULL, 0, (t))
```
and `luaM_realloc` increases `totalbytes` by the size of the memory requested (`osize` being `0`) (`lmem.c`):
void *luaM_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) {
...
g->totalbytes = (g->totalbytes - osize) + nsize;
...
}
```
In the case of a new 'mesh' userdata value, behind the scenes, a new userdata value is created by the Codea API of size `MESH_SIZE`, which is the size of the `mesh_type` type (`mesh.m`):
#define MESH_SIZE sizeof(mesh_type)
```
`mesh_type` is a relatively small `struct` (`mesh.h`), relative to the size of the data associated with a mesh value. The bulky data of the mesh is reserved using the `initBuffer` function, which makes use of C's speedy `malloc` (not a function that causes Lua's `totalbytes` tracking to increase) (`mesh.m`):
static mesh_type *Pnew(lua_State *L)
{
mesh_type *meshData = lua_newuserdata(L, MESH_SIZE);
initBuffer(&meshData->vertices, 3);
initBuffer(&meshData->colors, 4);
initBuffer(&meshData->texCoords, 2);
...
}
static void initBuffer(float_buffer* buffer, size_t elementSize)
{
buffer->capacity = 3000;
...
buffer->buffer = malloc(buffer->capacity * buffer->elementSize * sizeof(GLfloat));
...
}
```
Similarly, re-sizing a mesh's buffers as the mesh grows larger than its initial capacity makes use of C's `realloc`:
static void resizeBuffer(float_buffer* buffer, int newLength)
{
...
buffer->buffer = realloc(buffer->buffer,
buffer->capacity * buffer->elementSize * sizeof(GLfloat));
...
}
```
The overall result is that `local myMesh = mesh()` can make use of much more memory than the memory that Lua's garbage collection keeps track of using `totalbytes`. As a consequence, it can be necessary to keep track of, or be aware of, the memory use outside of the automatic collection and code accordingly.
My first example above did not take that into account, and, after creating mesh values with 30,000 vertices at a rate of about 20 times a second, soon falls over.