What I’m doing is just exploiting Unity’s assembly and Burst-generated shared library to get access to native functionality which bound to internal headers. We have two options to make everything blittable: one is spartan which requires to edit the compiled managed assembly, and another is more elegant since we can exploit the way how Unity is handling interoperability, but we will need to write/generate a lot more code for reflection instead of adding a simple overload.
When it’s done, Burst compiles every single function natively as a job and then exporting entry points for dynamic linking. At startup, a structure with all required pointers is composed and passed as a pointer to the native plugin which de-references it and keeps a copy statically during a session. I’m using there dlopen() / LoadLibrary() and dlsym() / GetProcAddress() to load any required shared library. Then, native API invokes the appropriate pointers as functions so essentially UnityEngine.Time.frameCount becomes int (SYMBIOTIC_FUNCTION *frameCount)(void); for example. The prototype of C function looks like this:
int symbiotic_time_framecount(void) {
return symbiotic.time.frameCount();
}
Events remains on the main thread as jobs executed with Run() within a system:
[BurstCompile]
private struct CreateJob : IJob {
public Symbiotic symbiotic;
public void Execute() {
if (Native.LoadMain(ref symbiotic))
Native.Awake();
Native.Start();
}
}
[BurstCompile]
private struct UpdateJob : IJob {
public void Execute() {
Native.Update(UnityEngine.Time.deltaTime);
}
}
[BurstCompile]
private struct DestroyJob : IJob {
public void Execute() {
Native.Destroy();
Native.UnloadMain();
}
}
C prototypes in the native plugin looks like this:
void symbiotic_awake(void) {
if (events.awake != NULL)
events.awake();
}
void symbiotic_start(void) {
if (events.start != NULL)
events.start();
}
void symbiotic_update(float deltaTime) {
if (events.update != NULL)
events.update(deltaTime);
}
void symbiotic_destroy(void) {
if (events.destroy != NULL)
events.destroy();
}
C implementation of custom logic which compiled as a separate shared library:
#define SYMBIOTIC_MAIN
#include "symbiotic.h"
void symbiotic_awake(void) {
symbiotic_debug_log_info("Awake");
// Assert test
#ifdef SYMBIOTIC_DEBUG
symbiotic_debug_log_info("Testing assert:");
#endif
SYMBIOTIC_ASSERT(1 < 0);
SYMBIOTIC_ASSERT_MESSAGE(1 < 0, "Assert message");
// Time test
symbiotic_debug_log_info("Testing time:");
symbiotic_debug_log_info(symbiotic_string_format("Frame count: %i", symbiotic_time_framecount()));
}
void symbiotic_start(void) {
symbiotic_debug_log_info("Start");
}
void symbiotic_update(float deltaTime) {
}
void symbiotic_destroy(void) {
symbiotic_debug_log_info("Destroy");
}
Compiled with GCC and executed right in the Unity:

It works well and fast. The only problem is the strange exception which, I hope, will be fixed.