This is a short post related to Opaque object representations from Our Machinery. I have to begin that my understanding of how Our Machineries tech works is somewhat limit so please bear with me. While I try to explain my thought process. As far as I understood how modules work is similar to how Quake 2 did their modules. A module has both a struct for export and one for import containing a number of function pointer. At load time the module gets a filled out import struct and fills out its own export struct with its own functions.
As for the allocators. I would have guessed that all engine specific allocators are defined internally in the engine inside the foundation library. On load time each module would request a number of alloactors (for example for different use cases). This could include just a functions pointer and void pointer or a handle/interface:
struct module_import im;
im.temporary_allocator = &temp_allocator;
im.persistent_alloactor = &pers_allocator;
// ...
struct module_export *ex = load_module(&im);
Know the problem is that we want the user of the engine to also be able to define allocators. One way is to include callbacks in each allocator or have a vtable. My idea to have one function inside the library mapping from allocator type to alloc or free:
enum tm_allocator_type
{
TM_ALLOCATOR_TYPE_ENGINE = 0x100000
TM_ALLACTOR_TYPE_ARENA,
TM_ALLACTOR_TYPE_BLOCK,
//....
};
struct tm_allocator_handle
{
int type;
}
void* tm_alloc(tm_allocator_handle* handle, size_t size)
{
switch(handle->type) {
case TM_ALLACTOR_TYPE_ARENA: {
struct tm_arena_allocator = (struct tm_arena_allocator)handle;
return tm_arena_alloc(size);
}
break;
//....
}
}
This allocator handle and alloc functions can be passed to any module.
struct module_import im;
im.temporary_allocator = &temp_allocator;
im.persistent_alloactor = &pers_allocator;
im.alloc = tm_alloc;
im.free = tm_free;
// ...
struct module_export *ex = load_module(&im);
However so far it is limited to only those alloactor types that are defined inside the engine. However it is also possible for anyone else to define their own versions (all internal allocator type begin on an offet while user alloactor begin at 0):
enum my_allocator_type {
MY_ALLOCATOR_STACK,
MY_ALLCATOR_SYSTEM.
MY_ALLCATOR_TRACE
};
void my_alloc(struct tm_allocator_handle* handle, size_t size)
{
switch(handle->type) {
default: return tm_alloc(handle, size);
case MY_ALLOCATOR_STACK: {
struct my_stack_allocator = (struct my_stack_allocator)handle;
return my_stack_alloc(size);
} break;
//....
}
}
Now just like with our engine allocator tm_alloc
and tm_free
functions we can also pass our own my_alloc
and my_free
to
any module that needs an allocator while being able to use either the engine or my own allocators
struct module_import im;
im.temporary_allocator = &temp_allocator;
im.persistent_alloactor = &pers_allocator;
im.alloc = my_alloc;
im.free = my_free;
// ...
struct module_export *ex = load_module(&im);
This doesn't seem that different from what we are doing. You are passing
allocator_handle *
andalloc()
to submodules inmodule_import
, which is exactly what we are doing, except we have them wrapped up in a struct:Then in your
my_alloc
you have a switch that looks at data in theallocator_handle
and calls out to different actual allocator functions based on the type in the allocator handle. We could do that too in ourrealloc()
implementation of course, but I don't see what you get from that extra level of indirection. If you want to use the trace allocator (for instance), why not just do:The only drawback I see here is that if you want to pass multiple allocators (
temp_allocator
,pers_allocator
) you now have to pass separatealloc
functions too, for the different allocators, so there's more typing:But don't you kind of have to do that anyway? Because what if your
temp_allocator
wants to use the systemalloc
function and thepers_allocator
wants to use your user implementedmy_alloc()
.And the drawback can be fixed by wrapping the handle and the function(s) up in a struct so you can pass them as a single argument. You could call this struct something like
tm_allocator_i
;)