orca/src/runtime.c

972 lines
34 KiB
C

/*************************************************************************
*
* Orca
* Copyright 2023 Martin Fouilleul and the Orca project contributors
* See LICENSE.txt for licensing information
*
**************************************************************************/
#include <errno.h>
#include <math.h>
#include <stdio.h>
#define OC_INCLUDE_GL_API
#include "graphics/graphics_common.h"
#include "orca.h"
#include "runtime.h"
#include "runtime_clipboard.c"
#include "runtime_io.c"
#include "runtime_memory.c"
oc_font orca_font_create(const char* resourcePath)
{
//NOTE(martin): create default fonts
oc_arena_scope scratch = oc_scratch_begin();
oc_str8 fontPath = oc_path_executable_relative(scratch.arena, OC_STR8(resourcePath));
oc_font font = oc_font_nil();
FILE* fontFile = fopen(fontPath.ptr, "r");
if(!fontFile)
{
oc_log_error("Could not load font file '%s': %s\n", fontPath.ptr, strerror(errno));
}
else
{
char* fontData = 0;
fseek(fontFile, 0, SEEK_END);
u32 fontDataSize = ftell(fontFile);
rewind(fontFile);
fontData = malloc(fontDataSize);
fread(fontData, 1, fontDataSize, fontFile);
fclose(fontFile);
oc_unicode_range ranges[5] = { OC_UNICODE_BASIC_LATIN,
OC_UNICODE_C1_CONTROLS_AND_LATIN_1_SUPPLEMENT,
OC_UNICODE_LATIN_EXTENDED_A,
OC_UNICODE_LATIN_EXTENDED_B,
OC_UNICODE_SPECIALS };
font = oc_font_create_from_memory(oc_str8_from_buffer(fontDataSize, fontData), 5, ranges);
free(fontData);
}
oc_scratch_end(scratch);
return (font);
}
oc_runtime __orcaApp = { 0 };
oc_runtime* oc_runtime_get()
{
return (&__orcaApp);
}
oc_wasm_env* oc_runtime_get_env()
{
return (&__orcaApp.env);
}
oc_str8 oc_runtime_get_wasm_memory()
{
oc_str8 mem = { 0 };
u32 size = 0;
mem.ptr = (char*)m3_GetMemory(__orcaApp.env.m3Runtime, &size, 0);
mem.len = size;
return (mem);
}
u64 orca_check_cstring(IM3Runtime runtime, const char* ptr)
{
uint32_t memorySize = 0;
char* memory = (char*)m3_GetMemory(runtime, &memorySize, 0);
//NOTE: Here we are guaranteed that ptr is in [ memory ; memory + memorySize [
// hence (memory + memorySize) - ptr is representable by size_t and <= memorySize
size_t maxLen = (memory + memorySize) - ptr;
u64 len = strnlen(ptr, maxLen);
if(len == maxLen)
{
//NOTE: string overflows wasm memory, return a length that will trigger the bounds check
len = maxLen + 1;
}
return (len + 1); //include null-terminator
}
void orca_wasm3_abort(IM3Runtime runtime, M3Result res, const char* file, const char* function, int line, const char* msg)
{
M3ErrorInfo errInfo = { 0 };
m3_GetErrorInfo(runtime, &errInfo);
if(errInfo.message && res == errInfo.result)
{
oc_abort_ext(file, function, line, "%s: %s (%s)", msg, res, errInfo.message);
}
else
{
oc_abort_ext(file, function, line, "%s: %s", msg, res);
}
}
void oc_bridge_window_set_title(oc_wasm_str8 title)
{
oc_str8 nativeTitle = oc_wasm_str8_to_native(title);
if(nativeTitle.ptr)
{
oc_window_set_title(__orcaApp.window, nativeTitle);
}
}
void oc_bridge_window_set_size(oc_vec2 size)
{
oc_window_set_content_size(__orcaApp.window, size);
}
oc_wasm_str8 oc_bridge_clipboard_get_string(oc_wasm_addr wasmArena)
{
return oc_runtime_clipboard_get_string(&__orcaApp.clipboard, wasmArena);
}
void oc_bridge_clipboard_set_string(oc_wasm_str8 value)
{
oc_runtime_clipboard_set_string(&__orcaApp.clipboard, value);
}
void oc_bridge_log(oc_log_level level,
int functionLen,
char* function,
int fileLen,
char* file,
int line,
int msgLen,
char* msg)
{
oc_debug_overlay* debug = &__orcaApp.debugOverlay;
//NOTE: recycle first entry if we exceeded the max entry count
debug->entryCount++;
if(debug->entryCount > debug->maxEntries)
{
log_entry* e = oc_list_pop_entry(&debug->logEntries, log_entry, listElt);
if(e)
{
oc_list_push(&debug->logFreeList, &e->listElt);
debug->entryCount--;
}
}
u64 cap = sizeof(log_entry) + fileLen + functionLen + msgLen;
//NOTE: allocate a new entry
//TODO: should probably use a buddy allocator over the arena or something
log_entry* entry = 0;
oc_list_for(debug->logFreeList, elt, log_entry, listElt)
{
if(elt->cap >= cap)
{
oc_list_remove(&debug->logFreeList, &elt->listElt);
entry = elt;
break;
}
}
if(!entry)
{
char* mem = oc_arena_push(&debug->logArena, cap);
entry = (log_entry*)mem;
entry->cap = cap;
}
char* payload = (char*)entry + sizeof(log_entry);
entry->function.len = functionLen;
entry->function.ptr = payload;
payload += entry->function.len;
entry->file.len = fileLen;
entry->file.ptr = payload;
payload += entry->file.len;
entry->msg.len = msgLen;
entry->msg.ptr = payload;
payload += entry->msg.len;
memcpy(entry->file.ptr, file, fileLen);
memcpy(entry->function.ptr, function, functionLen);
memcpy(entry->msg.ptr, msg, msgLen);
entry->level = level;
entry->line = line;
entry->recordIndex = debug->logEntryTotalCount;
debug->logEntryTotalCount++;
oc_list_push_back(&debug->logEntries, &entry->listElt);
oc_log_ext(level,
function,
file,
line,
"%.*s\n",
msgLen,
msg);
}
void oc_bridge_request_quit(void)
{
__orcaApp.quit = true;
}
typedef struct orca_surface_create_data
{
oc_window window;
oc_surface_api api;
oc_surface surface;
} orca_surface_create_data;
i32 orca_surface_callback(void* user)
{
orca_surface_create_data* data = (orca_surface_create_data*)user;
data->surface = oc_surface_create_for_window(data->window, data->api);
#if OC_PLATFORM_WINDOWS
//NOTE(martin): on windows we set all surfaces to non-synced, and do a single "manual" wait here.
// on macOS each surface is individually synced to the monitor refresh rate but don't block each other
oc_surface_swap_interval(data->surface, 0);
#endif
//NOTE: this will be called on main thread, so we need to deselect the surface here,
// and reselect it on the orca thread
oc_surface_deselect();
return (0);
}
oc_surface orca_surface_canvas(void)
{
orca_surface_create_data data = {
.surface = oc_surface_nil(),
.window = __orcaApp.window,
.api = OC_CANVAS
};
oc_dispatch_on_main_thread_sync(__orcaApp.window, orca_surface_callback, (void*)&data);
oc_surface_select(data.surface);
return (data.surface);
}
oc_surface orca_surface_gles(void)
{
orca_surface_create_data data = {
.surface = oc_surface_nil(),
.window = __orcaApp.window,
.api = OC_GLES
};
oc_dispatch_on_main_thread_sync(__orcaApp.window, orca_surface_callback, (void*)&data);
oc_surface_select(data.surface);
return (data.surface);
}
void orca_surface_render_commands(oc_surface surface,
oc_color clearColor,
u32 primitiveCount,
oc_primitive* primitives,
u32 eltCount,
oc_path_elt* elements)
{
oc_runtime* app = &__orcaApp;
char* memBase = app->env.wasmMemory.ptr;
u32 memSize = app->env.wasmMemory.committed;
if(((char*)primitives > memBase)
&& ((char*)primitives + primitiveCount * sizeof(oc_primitive) - memBase <= memSize)
&& ((char*)elements > memBase)
&& ((char*)elements + eltCount * sizeof(oc_path_elt) - memBase <= memSize)
&& oc_window_is_minimized(app->window) == false)
{
oc_surface_render_commands(surface,
clearColor,
primitiveCount,
primitives,
eltCount,
elements);
}
}
void debug_overlay_toggle(oc_debug_overlay* overlay)
{
overlay->show = !overlay->show;
if(overlay->show)
{
overlay->logScrollToLast = true;
}
}
void log_entry_ui(oc_debug_overlay* overlay, log_entry* entry)
{
oc_arena_scope scratch = oc_scratch_begin();
static const char* levelNames[] = { "Error: ", "Warning: ", "Info: " };
static const oc_color levelColors[] = { { 0.8, 0, 0, 1 },
{ 1, 0.5, 0, 1 },
{ 0, 0.8, 0, 1 } };
static const oc_color bgColors[3][2] = { //errors
{ { 0.6, 0, 0, 0.5 }, { 0.8, 0, 0, 0.5 } },
//warning
{ { 0.4, 0.4, 0.4, 0.5 }, { 0.5, 0.5, 0.5, 0.5 } },
//info
{ { 0.4, 0.4, 0.4, 0.5 }, { 0.5, 0.5, 0.5, 0.5 } }
};
oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 },
.size.height = { OC_UI_SIZE_CHILDREN },
.layout.axis = OC_UI_AXIS_Y,
.layout.margin.x = 10,
.layout.margin.y = 5,
.bgColor = bgColors[entry->level][entry->recordIndex & 1] },
OC_UI_STYLE_SIZE
| OC_UI_STYLE_LAYOUT_AXIS
| OC_UI_STYLE_LAYOUT_MARGINS
| OC_UI_STYLE_BG_COLOR);
oc_str8 key = oc_str8_pushf(scratch.arena, "%ull", entry->recordIndex);
oc_ui_container_str8(key, OC_UI_FLAG_DRAW_BACKGROUND)
{
oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 },
.size.height = { OC_UI_SIZE_CHILDREN },
.layout.axis = OC_UI_AXIS_X },
OC_UI_STYLE_SIZE
| OC_UI_STYLE_LAYOUT_AXIS);
oc_ui_container("header", 0)
{
oc_ui_style_next(&(oc_ui_style){ .color = levelColors[entry->level],
.font = overlay->fontBold },
OC_UI_STYLE_COLOR
| OC_UI_STYLE_FONT);
oc_ui_label(levelNames[entry->level]);
oc_str8 loc = oc_str8_pushf(scratch.arena,
"%.*s() in %.*s:%i:",
oc_str8_ip(entry->file),
oc_str8_ip(entry->function),
entry->line);
oc_ui_label_str8(loc);
}
oc_ui_label_str8(entry->msg);
}
oc_scratch_end(scratch);
}
char m3_type_to_tag(M3ValueType type)
{
switch(type)
{
case c_m3Type_none:
return ('v');
case c_m3Type_i32:
return ('i');
case c_m3Type_i64:
return ('I');
case c_m3Type_f32:
return ('f');
case c_m3Type_f64:
return ('d');
case c_m3Type_unknown:
default:
return ('!');
}
}
void oc_wasm_env_init(oc_wasm_env* runtime)
{
memset(runtime, 0, sizeof(oc_wasm_env));
oc_base_allocator* allocator = oc_base_allocator_default();
runtime->wasmMemory.committed = 0;
runtime->wasmMemory.reserved = 4ULL << 30;
runtime->wasmMemory.ptr = oc_base_reserve(allocator, runtime->wasmMemory.reserved);
}
#include "wasmbind/clock_api_bind_gen.c"
#include "wasmbind/core_api_bind_gen.c"
#include "wasmbind/gles_api_bind_manual.c"
#include "wasmbind/gles_api_bind_gen.c"
#include "wasmbind/io_api_bind_gen.c"
#include "wasmbind/surface_api_bind_manual.c"
#include "wasmbind/surface_api_bind_gen.c"
i32 orca_runloop(void* user)
{
oc_runtime* app = &__orcaApp;
oc_wasm_env_init(&app->env);
//NOTE: loads wasm module
oc_arena_scope scratch = oc_scratch_begin();
const char* bundleNameCString = "module";
oc_str8 modulePath = oc_path_executable_relative(scratch.arena, OC_STR8("../app/wasm/module.wasm"));
FILE* file = fopen(modulePath.ptr, "rb");
if(!file)
{
OC_ABORT("The application couldn't load: web assembly module not found");
}
fseek(file, 0, SEEK_END);
u64 wasmSize = ftell(file);
rewind(file);
app->env.wasmBytecode.len = wasmSize;
app->env.wasmBytecode.ptr = oc_malloc_array(char, wasmSize);
fread(app->env.wasmBytecode.ptr, 1, app->env.wasmBytecode.len, file);
fclose(file);
u32 stackSize = 65536;
app->env.m3Env = m3_NewEnvironment();
app->env.m3Runtime = m3_NewRuntime(app->env.m3Env, stackSize, NULL);
//NOTE: host memory will be freed when runtime is freed.
m3_RuntimeSetMemoryCallbacks(app->env.m3Runtime, oc_wasm_memory_resize_callback, oc_wasm_memory_free_callback, &app->env.wasmMemory);
M3Result res = m3_ParseModule(app->env.m3Env, &app->env.m3Module, (u8*)app->env.wasmBytecode.ptr, app->env.wasmBytecode.len);
if(res)
{
ORCA_WASM3_ABORT(app->env.m3Runtime, res, "The application couldn't parse its web assembly module");
}
res = m3_LoadModule(app->env.m3Runtime, app->env.m3Module);
if(res)
{
ORCA_WASM3_ABORT(app->env.m3Runtime, res, "The application couldn't load its web assembly module into the runtime");
}
m3_SetModuleName(app->env.m3Module, bundleNameCString);
oc_scratch_end(scratch);
//NOTE: bind orca APIs
{
int err = 0;
err |= bindgen_link_core_api(app->env.m3Module);
err |= bindgen_link_surface_api(app->env.m3Module);
err |= bindgen_link_clock_api(app->env.m3Module);
err |= bindgen_link_io_api(app->env.m3Module);
err |= bindgen_link_gles_api(app->env.m3Module);
err |= manual_link_gles_api(app->env.m3Module);
if(err)
{
OC_ABORT("The application couldn't link one or more functions to its web assembly module (see console log for more information)");
}
}
//NOTE: compile
res = m3_CompileModule(app->env.m3Module);
if(res)
{
ORCA_WASM3_ABORT(app->env.m3Runtime, res, "The application couldn't compile its web assembly module");
}
//NOTE: Find and type check event handlers.
for(int i = 0; i < OC_EXPORT_COUNT; i++)
{
const oc_export_desc* desc = &OC_EXPORT_DESC[i];
IM3Function handler = 0;
m3_FindFunction(&handler, app->env.m3Runtime, desc->name.ptr);
if(handler)
{
bool checked = false;
//NOTE: check function signature
int retCount = m3_GetRetCount(handler);
int argCount = m3_GetArgCount(handler);
if(retCount == desc->retTags.len && argCount == desc->argTags.len)
{
checked = true;
for(int retIndex = 0; retIndex < retCount; retIndex++)
{
M3ValueType m3Type = m3_GetRetType(handler, retIndex);
char tag = m3_type_to_tag(m3Type);
if(tag != desc->retTags.ptr[retIndex])
{
checked = false;
break;
}
}
if(checked)
{
for(int argIndex = 0; argIndex < argCount; argIndex++)
{
M3ValueType m3Type = m3_GetArgType(handler, argIndex);
char tag = m3_type_to_tag(m3Type);
if(tag != desc->argTags.ptr[argIndex])
{
checked = false;
break;
}
}
}
}
if(checked)
{
app->env.exports[i] = handler;
}
else
{
oc_log_error("type mismatch for event handler %.*s\n", (int)desc->name.len, desc->name.ptr);
}
}
}
//NOTE: get location of the raw event slot
IM3Global rawEventGlobal = m3_FindGlobal(app->env.m3Module, "oc_rawEvent");
app->env.rawEventOffset = (u32)rawEventGlobal->intValue;
//NOTE: preopen the app local root dir
{
scratch = oc_scratch_begin();
oc_str8 localRootPath = oc_path_executable_relative(scratch.arena, OC_STR8("../app/data"));
oc_io_req req = { .op = OC_IO_OPEN_AT,
.open.rights = OC_FILE_ACCESS_READ | OC_FILE_ACCESS_WRITE,
.size = localRootPath.len,
.buffer = localRootPath.ptr };
oc_io_cmp cmp = oc_io_wait_single_req_for_table(&req, &app->fileTable);
app->rootDir = cmp.handle;
oc_scratch_end(scratch);
}
IM3Function* exports = app->env.exports;
//NOTE: call init handler
if(exports[OC_EXPORT_ON_INIT])
{
M3Result res = m3_Call(exports[OC_EXPORT_ON_INIT], 0, 0);
if(res)
{
ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
}
}
if(exports[OC_EXPORT_FRAME_RESIZE])
{
oc_rect content = oc_window_get_content_rect(app->window);
u32 width = (u32)content.w;
u32 height = (u32)content.h;
const void* args[2] = { &width, &height };
M3Result res = m3_Call(exports[OC_EXPORT_FRAME_RESIZE], 2, args);
if(res)
{
ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
}
}
oc_ui_set_context(&app->debugOverlay.ui);
while(!app->quit)
{
scratch = oc_scratch_begin();
oc_event* event = 0;
while((event = oc_next_event(scratch.arena)) != 0)
{
if(app->debugOverlay.show)
{
oc_ui_process_event(event);
}
if(exports[OC_EXPORT_RAW_EVENT])
{
oc_event* clipboardEvent = oc_runtime_clipboard_process_event_begin(scratch.arena, &__orcaApp.clipboard, event);
oc_event* events[2];
u64 eventsCount;
if(clipboardEvent != 0)
{
events[0] = clipboardEvent;
events[1] = event;
eventsCount = 2;
}
else
{
events[0] = event;
eventsCount = 1;
}
for(int i = 0; i < eventsCount; i++)
{
#ifndef M3_BIG_ENDIAN
oc_event* eventPtr = (oc_event*)oc_wasm_address_to_ptr(app->env.rawEventOffset, sizeof(oc_event));
memcpy(eventPtr, events[i], sizeof(*events[i]));
const void* args[1] = { &app->env.rawEventOffset };
M3Result res = m3_Call(exports[OC_EXPORT_RAW_EVENT], 1, args);
if(res)
{
ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
}
#else
oc_log_error("oc_on_raw_event() is not supported on big endian platforms");
#endif
}
oc_runtime_clipboard_process_event_end(&__orcaApp.clipboard);
}
switch(event->type)
{
case OC_EVENT_WINDOW_CLOSE:
case OC_EVENT_QUIT:
{
app->quit = true;
}
break;
case OC_EVENT_WINDOW_RESIZE:
{
oc_rect frame = { 0, 0, event->move.frame.w, event->move.frame.h };
if(exports[OC_EXPORT_FRAME_RESIZE])
{
u32 width = (u32)event->move.content.w;
u32 height = (u32)event->move.content.h;
const void* args[2] = { &width, &height };
M3Result res = m3_Call(exports[OC_EXPORT_FRAME_RESIZE], 2, args);
if(res)
{
ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
}
}
}
break;
case OC_EVENT_MOUSE_BUTTON:
{
if(event->key.action == OC_KEY_PRESS)
{
if(exports[OC_EXPORT_MOUSE_DOWN])
{
oc_mouse_button button = event->key.button;
const void* args[1] = { &button };
M3Result res = m3_Call(exports[OC_EXPORT_MOUSE_DOWN], 1, args);
if(res)
{
ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
}
}
}
else
{
if(exports[OC_EXPORT_MOUSE_UP])
{
oc_mouse_button button = event->key.button;
const void* args[1] = { &button };
M3Result res = m3_Call(exports[OC_EXPORT_MOUSE_UP], 1, args);
if(res)
{
ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
}
}
}
}
break;
case OC_EVENT_MOUSE_MOVE:
{
if(exports[OC_EXPORT_MOUSE_MOVE])
{
const void* args[4] = { &event->mouse.x, &event->mouse.y, &event->mouse.deltaX, &event->mouse.deltaY };
M3Result res = m3_Call(exports[OC_EXPORT_MOUSE_MOVE], 4, args);
if(res)
{
ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
}
}
}
break;
case OC_EVENT_KEYBOARD_KEY:
{
if(event->key.action == OC_KEY_PRESS)
{
if(event->key.keyCode == OC_KEY_D
&& (event->key.mods & OC_KEYMOD_SHIFT)
&& (event->key.mods & OC_KEYMOD_MAIN_MODIFIER))
{
debug_overlay_toggle(&app->debugOverlay);
}
if(exports[OC_EXPORT_KEY_DOWN])
{
const void* args[2] = { &event->key.scanCode, &event->key.keyCode };
M3Result res = m3_Call(exports[OC_EXPORT_KEY_DOWN], 2, args);
if(res)
{
ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
}
}
}
else if(event->key.action == OC_KEY_RELEASE)
{
if(exports[OC_EXPORT_KEY_UP])
{
const void* args[2] = { &event->key.scanCode, &event->key.keyCode };
M3Result res = m3_Call(exports[OC_EXPORT_KEY_UP], 2, args);
if(res)
{
ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
}
}
}
}
break;
default:
break;
}
}
oc_surface_deselect();
if(exports[OC_EXPORT_FRAME_REFRESH])
{
M3Result res = m3_Call(exports[OC_EXPORT_FRAME_REFRESH], 0, 0);
if(res)
{
ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
}
}
oc_surface_select(app->debugOverlay.surface);
oc_canvas_select(app->debugOverlay.canvas);
if(app->debugOverlay.show)
{
oc_surface_bring_to_front(app->debugOverlay.surface);
oc_ui_style debugUIDefaultStyle = { .bgColor = { 0 },
.color = { 1, 1, 1, 1 },
.font = app->debugOverlay.fontReg,
.fontSize = 16,
.borderColor = { 1, 0, 0, 1 },
.borderSize = 2 };
oc_ui_style_mask debugUIDefaultMask = OC_UI_STYLE_BG_COLOR
| OC_UI_STYLE_COLOR
| OC_UI_STYLE_BORDER_COLOR
| OC_UI_STYLE_BORDER_SIZE
| OC_UI_STYLE_FONT
| OC_UI_STYLE_FONT_SIZE;
oc_vec2 frameSize = oc_surface_get_size(app->debugOverlay.surface);
oc_ui_frame(frameSize, &debugUIDefaultStyle, debugUIDefaultMask)
{
oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 },
.size.height = { OC_UI_SIZE_PARENT, 1, 1 } },
OC_UI_STYLE_SIZE);
oc_ui_container("overlay area", 0)
{
//...
}
oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 },
.size.height = { OC_UI_SIZE_PARENT, 0.4 },
.layout.axis = OC_UI_AXIS_Y,
.bgColor = { 0, 0, 0, 0.5 } },
OC_UI_STYLE_SIZE
| OC_UI_STYLE_LAYOUT_AXIS
| OC_UI_STYLE_BG_COLOR);
oc_ui_container("log console", OC_UI_FLAG_DRAW_BACKGROUND)
{
oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 },
.size.height = { OC_UI_SIZE_CHILDREN },
.layout.axis = OC_UI_AXIS_X,
.layout.spacing = 10,
.layout.margin.x = 10,
.layout.margin.y = 10 },
OC_UI_STYLE_SIZE
| OC_UI_STYLE_LAYOUT);
oc_ui_container("log toolbar", 0)
{
oc_ui_style buttonStyle = { .layout.margin.x = 4,
.layout.margin.y = 4,
.roundness = 2,
.bgColor = { 0, 0, 0, 0.5 },
.color = { 1, 1, 1, 1 } };
oc_ui_style_mask buttonStyleMask = OC_UI_STYLE_LAYOUT_MARGINS
| OC_UI_STYLE_ROUNDNESS
| OC_UI_STYLE_BG_COLOR
| OC_UI_STYLE_COLOR;
oc_ui_style_match_after(oc_ui_pattern_all(), &buttonStyle, buttonStyleMask);
if(oc_ui_button("Clear").clicked)
{
oc_list_for_safe(app->debugOverlay.logEntries, entry, log_entry, listElt)
{
oc_list_remove(&app->debugOverlay.logEntries, &entry->listElt);
oc_list_push(&app->debugOverlay.logFreeList, &entry->listElt);
app->debugOverlay.entryCount--;
}
}
}
oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 },
.size.height = { OC_UI_SIZE_PARENT, 1, 1 } },
OC_UI_STYLE_SIZE);
//TODO: this is annoying to have to do that. Basically there's another 'contents' box inside oc_ui_panel,
// and we need to change that to size according to its parent (whereas the default is sizing according
// to its children)
oc_ui_pattern pattern = { 0 };
oc_ui_pattern_push(scratch.arena, &pattern, (oc_ui_selector){ .kind = OC_UI_SEL_OWNER });
oc_ui_pattern_push(scratch.arena, &pattern, (oc_ui_selector){ .kind = OC_UI_SEL_TEXT, .text = OC_STR8("contents") });
oc_ui_style_match_after(pattern, &(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 } }, OC_UI_STYLE_SIZE_WIDTH);
oc_ui_box* panel = oc_ui_box_lookup("log view");
f32 scrollY = 0;
if(panel)
{
scrollY = panel->scroll.y;
}
oc_ui_panel("log view", OC_UI_FLAG_SCROLL_WHEEL_Y)
{
panel = oc_ui_box_top()->parent;
oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 },
.size.height = { OC_UI_SIZE_CHILDREN },
.layout.axis = OC_UI_AXIS_Y,
.layout.margin.y = 5 },
OC_UI_STYLE_SIZE
| OC_UI_STYLE_LAYOUT_AXIS);
oc_ui_container("contents", 0)
{
oc_list_for(app->debugOverlay.logEntries, entry, log_entry, listElt)
{
log_entry_ui(&app->debugOverlay, entry);
}
}
}
if(app->debugOverlay.logScrollToLast)
{
if(panel->scroll.y >= scrollY)
{
panel->scroll.y = oc_clamp_low(panel->childrenSum[1] - panel->rect.h, 0);
}
else
{
app->debugOverlay.logScrollToLast = false;
}
}
else if(panel->scroll.y >= (panel->childrenSum[1] - panel->rect.h) - 1)
{
app->debugOverlay.logScrollToLast = true;
}
}
}
oc_ui_draw();
}
else
{
oc_set_color_rgba(0, 0, 0, 0);
oc_clear();
}
oc_render(app->debugOverlay.canvas);
oc_surface_present(app->debugOverlay.surface);
oc_scratch_end(scratch);
#if OC_PLATFORM_WINDOWS
//NOTE(martin): on windows we set all surfaces to non-synced, and do a single "manual" wait here.
// on macOS each surface is individually synced to the monitor refresh rate but don't block each other
oc_vsync_wait(app->window);
#endif
}
if(exports[OC_EXPORT_TERMINATE])
{
M3Result res = m3_Call(exports[OC_EXPORT_TERMINATE], 0, 0);
if(res)
{
ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
}
}
oc_request_quit();
return (0);
}
int main(int argc, char** argv)
{
oc_log_set_level(OC_LOG_LEVEL_INFO);
oc_init();
oc_clock_init();
oc_runtime* app = &__orcaApp;
//NOTE: create window and surfaces
oc_rect windowRect = { .x = 100, .y = 100, .w = 810, .h = 610 };
app->window = oc_window_create(windowRect, OC_STR8("orca"), 0);
app->debugOverlay.show = false;
app->debugOverlay.surface = oc_surface_create_for_window(app->window, OC_CANVAS);
app->debugOverlay.canvas = oc_canvas_create();
app->debugOverlay.fontReg = orca_font_create("../resources/Menlo.ttf");
app->debugOverlay.fontBold = orca_font_create("../resources/Menlo Bold.ttf");
app->debugOverlay.maxEntries = 200;
oc_arena_init(&app->debugOverlay.logArena);
#if OC_PLATFORM_WINDOWS
//NOTE(martin): on windows we set all surfaces to non-synced, and do a single "manual" wait here.
// on macOS each surface is individually synced to the monitor refresh rate but don't block each other
oc_surface_swap_interval(app->debugOverlay.surface, 0);
#else
oc_surface_swap_interval(app->debugOverlay.surface, 1);
#endif
oc_surface_deselect();
oc_ui_init(&app->debugOverlay.ui);
//NOTE: show window and start runloop
oc_window_bring_to_front(app->window);
oc_window_focus(app->window);
oc_window_center(app->window);
oc_thread* runloopThread = oc_thread_create(orca_runloop, 0);
while(!oc_should_quit())
{
oc_pump_events(-1);
//TODO: what to do with mem scratch here?
}
oc_thread_join(runloopThread, NULL);
oc_canvas_destroy(app->debugOverlay.canvas);
oc_surface_destroy(app->debugOverlay.surface);
oc_window_destroy(app->window);
oc_terminate();
return (0);
}