async testing

This commit is contained in:
jacob 2025-12-12 14:10:10 -06:00
parent 476d154beb
commit 5fd73b7911
10 changed files with 138 additions and 144 deletions

View File

@ -30,24 +30,29 @@ void OnAsyncTick(AsyncTickCallbackFunc *func)
void AsyncWorkerEntryPoint(WaveLaneCtx *lane)
{
AsyncTickCtx *tick = &Base_tl.async.tick_ctx;
tick->arena = AcquireArena(Gibi(64));
/* Tick forever */
for (;;)
{
TempArena scratch = BeginScratchNoConflict();
AsyncWorkerState *w = &Base.async.worker;
AsyncWorkerCtx *w = &Base.async.worker_ctx;
/* FIXME: Remove this */
SleepSeconds(0.100);
//////////////////////////////
//- Grab async functions
//- Begin tick
WaveSync(lane);
ResetArena(tick->arena);
if (lane->idx == 0)
{
Lock lock = LockE(&Base.async.mutex);
{
w->callbacks_count = Base.async.callback_nodes_count;
w->callbacks = PushStructsNoZero(scratch.arena, AsyncTickCallback, w->callbacks_count);
w->callbacks = PushStructsNoZero(tick->arena, AsyncTickCallback, w->callbacks_count);
u64 callback_idx = 0;
for (AsyncTickCallbackNode *n = Base.async.first_callback_node; n; n = n->next)
{
@ -66,13 +71,7 @@ void AsyncWorkerEntryPoint(WaveLaneCtx *lane)
for (u64 callback_idx = 0; callback_idx < w->callbacks_count; ++callback_idx)
{
AsyncTickCallback *callback = &w->callbacks[callback_idx];
callback->func(lane);
callback->func(lane, tick);
}
//////////////////////////////
//- End tick
WaveSync(lane);
EndScratch(scratch);
}
}

View File

@ -1,7 +1,8 @@
////////////////////////////////////////////////////////////
//~ Async types
//~ Async callback
typedef void AsyncTickCallbackFunc(WaveLaneCtx *lane);
Struct(AsyncTickCtx);
typedef void AsyncTickCallbackFunc(WaveLaneCtx *lane, AsyncTickCtx *tick);
Struct(AsyncTickCallback)
{
@ -14,8 +15,17 @@ Struct(AsyncTickCallbackNode)
AsyncTickCallback callback;
};
Struct(AsyncWorkerState)
////////////////////////////////////////////////////////////
//~ Async worker types
Struct(AsyncTickCtx)
{
Arena *arena;
};
Struct(AsyncWorkerCtx)
{
AsyncTickCtx tick;
u64 callbacks_count;
AsyncTickCallback *callbacks;
};

View File

@ -1,2 +1,2 @@
BaseState Base = ZI;
ThreadLocal BaseThreadLocalState Base_tl = ZI;
BaseCtx Base = ZI;
ThreadLocal BaseThreadLocalCtx Base_tl = ZI;

View File

@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////
//~ Global state
//~ Global context
Struct(BaseState)
Struct(BaseCtx)
{
/* Command line */
struct
@ -38,22 +38,27 @@ Struct(BaseState)
u64 callback_nodes_count;
AsyncTickCallbackNode *first_callback_node;
AsyncTickCallbackNode *last_callback_node;
AsyncWorkerState worker;
AsyncWorkerCtx worker_ctx;
} async;
};
extern BaseState Base;
extern BaseCtx Base;
////////////////////////////////////////////////////////////
//~ Thread-local state
//~ Thread-local context
Struct(BaseThreadLocalState)
Struct(BaseThreadLocalCtx)
{
struct
{
Arena *perm;
Arena *scratch[2];
} arenas;
struct
{
AsyncTickCtx tick_ctx;
} async;
};
extern ThreadLocal BaseThreadLocalState Base_tl;
extern ThreadLocal BaseThreadLocalCtx Base_tl;

View File

@ -1,4 +1,4 @@
W32_State W32 = ZI;
W32_Ctx W32 = ZI;
////////////////////////////////////////////////////////////
//~ Win32 embedded data

View File

@ -54,9 +54,9 @@ Struct(W32_FindEmbeddedDataCtx)
};
////////////////////////////////////////////////////////////
//~ State types
//~ Context types
Struct(W32_State)
Struct(W32_Ctx)
{
SYSTEM_INFO info;
u32 main_thread_id;
@ -95,7 +95,7 @@ Struct(W32_State)
Atomic64 readable_logs_count;
};
extern W32_State W32;
extern W32_Ctx W32;
////////////////////////////////////////////////////////////
//~ Embedded data initialization

View File

@ -1,11 +1,11 @@
GC_State GC = ZI;
GC_Ctx GC = ZI;
////////////////////////////////////////////////////////////
//~ Bootstrap
void GC_Bootstrap(void)
{
OnAsyncTick(GC_AsyncTick);
OnAsyncTick(GC_TickAsync);
}
////////////////////////////////////////////////////////////
@ -130,42 +130,33 @@ GC_Run GC_RunFromString(Arena *arena, String str, GC_FontKey font, f32 font_size
}
////////////////////////////////////////////////////////////
//~ Async tick
//~ Async
void GC_AsyncTick(WaveLaneCtx *lane)
void GC_TickAsync(WaveLaneCtx *lane, AsyncTickCtx *tick)
{
TempArena scratch = BeginScratchNoConflict();
GC_WorkerState *w = &GC.worker_state;
GC_AsyncCtx *async = &GC.async_ctx;
/* Flatten cmds */
// if (lane->idx == 0)
// {
// Lock lock = LockE();
// {
//////////////////////////////
//- Begin tick
// }
// Unlock(&lock);
// }
if (lane->idx == 0)
{
Lock lock = LockE(&GC.submitted_cmds_mutex);
{
async->cmds.count = GC.submitted_cmds_count;
async->cmds.v = PushStructsNoZero(tick->arena, GC_Cmd, GC.submitted_cmds_count);
}
Unlock(&lock);
}
WaveSync(lane);
EndScratch(scratch);
}
// ////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////
// //~ Font load job
// JobImpl(F_Load, sig, _)

View File

@ -103,15 +103,18 @@ Struct(GC_CmdChunk)
};
////////////////////////////////////////////////////////////
//~ State
//~ Context types
Struct(GC_WorkerState)
Struct(GC_AsyncCtx)
{
u64 cmds_count;
GC_Cmd *cmds;
struct
{
u64 count;
GC_Cmd *v;
} cmds;
};
Struct(GC_State)
Struct(GC_Ctx)
{
Mutex glyphs_mutex;
GC_GlyphBin glyph_bins[16384];
@ -121,7 +124,7 @@ Struct(GC_State)
GC_CmdChunk *first_submitted_cmd_chunk;
GC_CmdChunk *last_submitted_cmd_chunk;
GC_WorkerState worker_state;
GC_AsyncCtx async_ctx;
} extern GC;
////////////////////////////////////////////////////////////
@ -141,23 +144,9 @@ u64 GC_HashFromGlyphDesc(GC_GlyphDesc desc);
GC_Run GC_RunFromString(Arena *arena, String str, GC_FontKey font, f32 font_size);
////////////////////////////////////////////////////////////
//~ Async tick
void GC_AsyncTick(WaveLaneCtx *lane);
//~ Async
void GC_TickAsync(WaveLaneCtx *lane, AsyncTickCtx *ctx);

View File

@ -1,18 +1,18 @@
#include "meta.h"
BuildData build = ZI;
BuildCtx Build = ZI;
////////////////////////////////////////////////////////////
//~ Helpers
i32 GetBuildStatus(void)
{
return Atomic32Fetch(&build.status);
return Atomic32Fetch(&Build.status);
}
void SetBuildStatus(i32 code)
{
Atomic32FetchTestSet(&build.status, 0, code);
Atomic32FetchTestSet(&Build.status, 0, code);
}
void EchoLine(String msg)
@ -462,9 +462,9 @@ void BuildEntryPoint(WaveLaneCtx *lane)
/* Flatten */
StringList starting_layer_names = ZI;
PushStringToList(perm, &starting_layer_names, cmdline.leaf_layer_name);
build.layers_parse = M_FlattenEntries(perm, parsed, starting_layer_names);
Build.layers_parse = M_FlattenEntries(perm, parsed, starting_layer_names);
SetBuildStatus(build.layers_parse.errors.count > 0);
SetBuildStatus(Build.layers_parse.errors.count > 0);
}
//- Generate C file
@ -474,7 +474,7 @@ void BuildEntryPoint(WaveLaneCtx *lane)
StringList c_include_lines = ZI;
StringList c_bootstrap_lines = ZI;
{
for (M_Entry *entry = build.layers_parse.first; entry->valid; entry = entry->next)
for (M_Entry *entry = Build.layers_parse.first; entry->valid; entry = entry->next)
{
M_EntryKind kind = entry->kind;
M_Token *entry_tok = entry->name_token;
@ -501,12 +501,12 @@ void BuildEntryPoint(WaveLaneCtx *lane)
else
{
String err = StringF(perm, "Directory '%F' not found", FmtString(full));
M_PushError(perm, &build.c_parse.errors, arg1_tok, err);
M_PushError(perm, &Build.c_parse.errors, arg1_tok, err);
}
}
else
{
M_PushError(perm, &build.c_parse.errors, entry_tok, Lit("Expected resource store & directory name"));
M_PushError(perm, &Build.c_parse.errors, entry_tok, Lit("Expected resource store & directory name"));
}
} break;
case M_EntryKind_VertexShader:
@ -526,7 +526,7 @@ void BuildEntryPoint(WaveLaneCtx *lane)
}
else
{
M_PushError(perm, &build.c_parse.errors, entry_tok, Lit("Expected shader name"));
M_PushError(perm, &Build.c_parse.errors, entry_tok, Lit("Expected shader name"));
}
} break;
case M_EntryKind_IncludeC:
@ -545,12 +545,12 @@ void BuildEntryPoint(WaveLaneCtx *lane)
else
{
String err = StringF(perm, "File '%F' not found", FmtString(full));
M_PushError(perm, &build.c_parse.errors, arg0_tok, err);
M_PushError(perm, &Build.c_parse.errors, arg0_tok, err);
}
}
else
{
M_PushError(perm, &build.c_parse.errors, entry_tok, Lit("Expected file name"));
M_PushError(perm, &Build.c_parse.errors, entry_tok, Lit("Expected file name"));
}
} break;
case M_EntryKind_Bootstrap:
@ -563,13 +563,13 @@ void BuildEntryPoint(WaveLaneCtx *lane)
}
else
{
M_PushError(perm, &build.c_parse.errors, entry_tok, Lit("Expected bootstrap function name"));
M_PushError(perm, &Build.c_parse.errors, entry_tok, Lit("Expected bootstrap function name"));
}
} break;
}
}
}
if (build.c_parse.errors.count == 0)
if (Build.c_parse.errors.count == 0)
{
StringList c_out_lines = ZI;
PushStringToList(perm, &c_out_lines, Lit("// Auto generated file"));
@ -628,7 +628,7 @@ void BuildEntryPoint(WaveLaneCtx *lane)
F_ClearWrite(c_out_file, c_out);
}
SetBuildStatus(build.c_parse.errors.count > 0);
SetBuildStatus(Build.c_parse.errors.count > 0);
}
//- Generate HLSL file
@ -660,7 +660,7 @@ void BuildEntryPoint(WaveLaneCtx *lane)
{
StringList gpu_include_lines = ZI;
{
for (M_Entry *entry = build.layers_parse.first; entry->valid; entry = entry->next)
for (M_Entry *entry = Build.layers_parse.first; entry->valid; entry = entry->next)
{
M_EntryKind kind = entry->kind;
M_Token *entry_tok = entry->name_token;
@ -685,12 +685,12 @@ void BuildEntryPoint(WaveLaneCtx *lane)
else
{
String err = StringF(perm, "File '%F' not found", FmtString(full));
M_PushError(perm, &build.gpu_parse.errors, arg0_tok, err);
M_PushError(perm, &Build.gpu_parse.errors, arg0_tok, err);
}
}
else
{
M_PushError(perm, &build.gpu_parse.errors, entry_tok, Lit("Expected file name"));
M_PushError(perm, &Build.gpu_parse.errors, entry_tok, Lit("Expected file name"));
}
} break;
case M_EntryKind_VertexShader:
@ -707,18 +707,18 @@ void BuildEntryPoint(WaveLaneCtx *lane)
ShaderEntry *e = PushStruct(perm, ShaderEntry);
e->kind = shader_kind;
e->name = shader_name;
SllQueuePush(build.gpu_parse.first_shader_entry, build.gpu_parse.last_shader_entry, e);
++build.gpu_parse.shader_entries_count;
SllQueuePush(Build.gpu_parse.first_shader_entry, Build.gpu_parse.last_shader_entry, e);
++Build.gpu_parse.shader_entries_count;
}
else
{
M_PushError(perm, &build.gpu_parse.errors, entry_tok, Lit("Expected shader name"));
M_PushError(perm, &Build.gpu_parse.errors, entry_tok, Lit("Expected shader name"));
}
} break;
}
}
}
if (build.gpu_parse.errors.count == 0)
if (Build.gpu_parse.errors.count == 0)
{
StringList gpu_out_lines = ZI;
PushStringToList(perm, &gpu_out_lines, Lit("// Auto generated file"));
@ -746,13 +746,13 @@ void BuildEntryPoint(WaveLaneCtx *lane)
}
}
SetBuildStatus(build.gpu_parse.errors.count > 0);
SetBuildStatus(Build.gpu_parse.errors.count > 0);
}
//- Generate archive info
{
/* Push embedded archive dirs */
for (M_Entry *entry = build.layers_parse.first; entry->valid; entry = entry->next)
for (M_Entry *entry = Build.layers_parse.first; entry->valid; entry = entry->next)
{
M_EntryKind kind = entry->kind;
M_Token *entry_tok = entry->name_token;
@ -775,38 +775,38 @@ void BuildEntryPoint(WaveLaneCtx *lane)
ResDir *rd = PushStruct(perm, ResDir);
rd->store_name = store_name;
rd->dir_path = full;
SllQueuePush(build.res_dir_parse.first_res_dir, build.res_dir_parse.last_res_dir, rd);
++build.res_dir_parse.res_dirs_count;
SllQueuePush(Build.res_dir_parse.first_res_dir, Build.res_dir_parse.last_res_dir, rd);
++Build.res_dir_parse.res_dirs_count;
}
else
{
String err = StringF(perm, "Directory '%F' not found", FmtString(full));
M_PushError(perm, &build.res_dir_parse.errors, arg1_tok, err);
M_PushError(perm, &Build.res_dir_parse.errors, arg1_tok, err);
}
}
else
{
M_PushError(perm, &build.res_dir_parse.errors, entry_tok, Lit("Expected resource store & directory name"));
M_PushError(perm, &Build.res_dir_parse.errors, entry_tok, Lit("Expected resource store & directory name"));
}
} break;
}
}
SetBuildStatus(build.res_dir_parse.errors.count > 0);
SetBuildStatus(Build.res_dir_parse.errors.count > 0);
}
//- Prep obj arrays
{
/* Gpu objs */
build.gpu_objs.count = build.gpu_parse.shader_entries_count;
build.gpu_objs.array = PushStructs(perm, GpuObj, build.gpu_objs.count);
Build.gpu_objs.count = Build.gpu_parse.shader_entries_count;
Build.gpu_objs.array = PushStructs(perm, GpuObj, Build.gpu_objs.count);
/* Embed objs */
build.embed_objs.count += build.res_dir_parse.res_dirs_count;
if (build.gpu_parse.shader_entries_count > 0)
Build.embed_objs.count += Build.res_dir_parse.res_dirs_count;
if (Build.gpu_parse.shader_entries_count > 0)
{
build.embed_objs.count += 1;
Build.embed_objs.count += 1;
}
build.embed_objs.array = PushStructs(perm, EmbedObj, build.embed_objs.count);
Build.embed_objs.array = PushStructs(perm, EmbedObj, Build.embed_objs.count);
}
}
@ -823,39 +823,39 @@ void BuildEntryPoint(WaveLaneCtx *lane)
{
if (lane->idx == WaveLaneIdxFromTaskIdx(lane, task_idx++) && GetBuildStatus() == 0)
{
build.c_obj.obj_file = StringF(perm, "%F_gen_c.obj", FmtString(cmdline.leaf_layer_name));
Build.c_obj.obj_file = StringF(perm, "%F_gen_c.obj", FmtString(cmdline.leaf_layer_name));
String cmd = StringF(perm,
"cl.exe /c %F -Fo:%F %F %F %F %F",
FmtString(c_out_file),
FmtString(build.c_obj.obj_file),
FmtString(Build.c_obj.obj_file),
FmtString(StringFromList(perm, cp.flags_msvc, Lit(" "))),
FmtString(StringFromList(perm, cp.compiler_only_flags_msvc, Lit(" "))),
FmtString(StringFromList(perm, cp.warnings_msvc, Lit(" "))),
FmtString(StringFromList(perm, cp.defs, Lit(" "))));
OS_CommandResult cmd_result = OS_RunCommand(perm, cmd);
String cmd_output = TrimWhitespace(cmd_result.output);
build.c_obj.output = cmd_output;
build.c_obj.return_code = cmd_result.code;
Build.c_obj.output = cmd_output;
Build.c_obj.return_code = cmd_result.code;
/* Ignore MSVC file-name echo */
if (MatchString(TrimWhitespace(build.c_obj.output), F_GetFileName(c_out_file)))
if (MatchString(TrimWhitespace(Build.c_obj.output), F_GetFileName(c_out_file)))
{
build.c_obj.output = Zstr;
Build.c_obj.output = Zstr;
}
SetBuildStatus(build.c_obj.return_code);
SetBuildStatus(Build.c_obj.return_code);
}
}
//- Compile shaders
{
u32 gpu_obj_idx = 0;
for (ShaderEntry *e = build.gpu_parse.first_shader_entry; e; e = e->next)
for (ShaderEntry *e = Build.gpu_parse.first_shader_entry; e; e = e->next)
{
if (lane->idx == WaveLaneIdxFromTaskIdx(lane, task_idx++) && GetBuildStatus() == 0)
{
String shader_name = e->name;
GpuObj *gpu_obj = &build.gpu_objs.array[gpu_obj_idx];
GpuObj *gpu_obj = &Build.gpu_objs.array[gpu_obj_idx];
String out_file = StringF(perm, "%F/%F", FmtString(shader_store_name), FmtString(e->name));
String target = e->kind == ShaderEntryKind_VS ? Lit("vs_6_6")
: e->kind == ShaderEntryKind_PS ? Lit("ps_6_6")
@ -877,16 +877,16 @@ void BuildEntryPoint(WaveLaneCtx *lane)
SetBuildStatus(gpu_obj->return_code);
/* Final shader compilation lane embeds shader archive */
u32 finished_count = Atomic32FetchAdd(&build.gpu_objs.finished_count, 1) + 1;
if (finished_count == build.gpu_objs.count && GetBuildStatus() == 0)
u32 finished_count = Atomic32FetchAdd(&Build.gpu_objs.finished_count, 1) + 1;
if (finished_count == Build.gpu_objs.count && GetBuildStatus() == 0)
{
EmbedObj *embed = &build.embed_objs.array[embed_idx];
EmbedObj *embed = &Build.embed_objs.array[embed_idx];
*embed = Embed(shader_store_name, shader_store_name);
}
}
++gpu_obj_idx;
}
if (build.gpu_parse.shader_entries_count > 0)
if (Build.gpu_parse.shader_entries_count > 0)
{
++embed_idx;
}
@ -894,13 +894,13 @@ void BuildEntryPoint(WaveLaneCtx *lane)
//- Embed resource dirs
{
for (ResDir *rd = build.res_dir_parse.first_res_dir; rd; rd = rd->next)
for (ResDir *rd = Build.res_dir_parse.first_res_dir; rd; rd = rd->next)
{
if (lane->idx == WaveLaneIdxFromTaskIdx(lane, task_idx++) && GetBuildStatus() == 0)
{
String dir_path = rd->dir_path;
String store_name = rd->store_name;
EmbedObj *embed = &build.embed_objs.array[embed_idx];
EmbedObj *embed = &Build.embed_objs.array[embed_idx];
*embed = Embed(store_name, dir_path);
}
++embed_idx;
@ -928,10 +928,10 @@ void BuildEntryPoint(WaveLaneCtx *lane)
String obj_files_str = ZI;
{
StringList obj_files = ZI;
PushStringToList(perm, &obj_files, build.c_obj.obj_file);
for (u64 embed_obj_idx = 0; embed_obj_idx < build.embed_objs.count; ++embed_obj_idx)
PushStringToList(perm, &obj_files, Build.c_obj.obj_file);
for (u64 embed_obj_idx = 0; embed_obj_idx < Build.embed_objs.count; ++embed_obj_idx)
{
EmbedObj *embed_obj = &build.embed_objs.array[embed_obj_idx];
EmbedObj *embed_obj = &Build.embed_objs.array[embed_obj_idx];
PushStringToList(perm, &obj_files, embed_obj->obj_file);
}
obj_files_str = StringFromList(perm, obj_files, Lit(" "));
@ -944,12 +944,12 @@ void BuildEntryPoint(WaveLaneCtx *lane)
FmtString(StringFromList(perm, cp.flags_msvc, Lit(" "))),
FmtString(StringFromList(perm, cp.linker_only_flags_msvc, Lit(" "))));
OS_CommandResult result = OS_RunCommand(perm, cmd);
build.link.output = TrimWhitespace(result.output);
build.link.return_code = result.code;
Build.link.output = TrimWhitespace(result.output);
Build.link.return_code = result.code;
i64 link_elapsed_ns = TimeNs() - start_ns;
// EchoLine(StringF(perm, ">>>>> Linked in %Fs", FmtFloat(SecondsFromNs(link_elapsed_ns))));
SetBuildStatus(build.link.return_code);
SetBuildStatus(Build.link.return_code);
}
//////////////////////////////
@ -960,9 +960,9 @@ void BuildEntryPoint(WaveLaneCtx *lane)
String gpu_obj_output = ZI;
{
GpuObj *disp_obj = 0;
for (u32 gpu_obj_idx = 0; gpu_obj_idx < build.gpu_objs.count; ++gpu_obj_idx)
for (u32 gpu_obj_idx = 0; gpu_obj_idx < Build.gpu_objs.count; ++gpu_obj_idx)
{
GpuObj *gpu_obj = &build.gpu_objs.array[gpu_obj_idx];
GpuObj *gpu_obj = &Build.gpu_objs.array[gpu_obj_idx];
if (!disp_obj || TrimWhitespace(disp_obj->output).len == 0 || gpu_obj->return_code != 0)
{
disp_obj = gpu_obj;
@ -976,22 +976,22 @@ void BuildEntryPoint(WaveLaneCtx *lane)
String embed_obj_output = ZI;
{
StringList embed_obj_outputs = ZI;
for (u32 embed_obj_idx = 0; embed_obj_idx < build.embed_objs.count; ++embed_obj_idx)
for (u32 embed_obj_idx = 0; embed_obj_idx < Build.embed_objs.count; ++embed_obj_idx)
{
EmbedObj *embed_obj = &build.embed_objs.array[embed_obj_idx];
EmbedObj *embed_obj = &Build.embed_objs.array[embed_obj_idx];
PushStringToList(perm, &embed_obj_outputs, embed_obj->output);
}
embed_obj_output = StringFromList(perm, embed_obj_outputs, Lit("\n"));
}
EchoLineOrNothing(StringFromMetaErrors(perm, build.layers_parse.errors));
EchoLineOrNothing(StringFromMetaErrors(perm, build.c_parse.errors));
EchoLineOrNothing(StringFromMetaErrors(perm, build.gpu_parse.errors));
EchoLineOrNothing(build.c_obj.output);
EchoLineOrNothing(StringFromMetaErrors(perm, Build.layers_parse.errors));
EchoLineOrNothing(StringFromMetaErrors(perm, Build.c_parse.errors));
EchoLineOrNothing(StringFromMetaErrors(perm, Build.gpu_parse.errors));
EchoLineOrNothing(Build.c_obj.output);
EchoLineOrNothing(gpu_obj_output);
EchoLineOrNothing(StringFromMetaErrors(perm, build.res_dir_parse.errors));
EchoLineOrNothing(StringFromMetaErrors(perm, Build.res_dir_parse.errors));
EchoLineOrNothing(embed_obj_output);
EchoLineOrNothing(build.link.output);
EchoLineOrNothing(Build.link.output);
}
//////////////////////////////

View File

@ -65,7 +65,7 @@ Struct(LineCol)
};
////////////////////////////////////////////////////////////
//~ Build state types
//~ Build ctx types
Struct(CompilerParams)
{
@ -120,7 +120,7 @@ Struct(EmbedObj)
i32 return_code;
};
Struct(BuildData)
Struct(BuildCtx)
{
Atomic32 status;
@ -174,7 +174,7 @@ Struct(BuildData)
} link;
};
extern BuildData build;
extern BuildCtx Build;
////////////////////////////////////////////////////////////
//~ Helpers