Helpers for manipulating orca structs living in wasm memory:
- convert between wasm memory and native pointers, asserting on out-of-bounds - oc_wasm_list helpers - oc_wasm_str8 helpers - pushing things to arenas in wasm memory
This commit is contained in:
parent
0205d90941
commit
fbb03d27a3
|
@ -19,7 +19,7 @@ f32 minf(f32 a, f32 b);
|
||||||
|
|
||||||
ORCA_EXPORT void oc_on_init(void)
|
ORCA_EXPORT void oc_on_init(void)
|
||||||
{
|
{
|
||||||
oc_runtime_window_set_title(OC_STR8("clock"));
|
oc_window_set_title(OC_STR8("clock"));
|
||||||
oc_runtime_window_set_size((oc_vec2){ .x = 400, .y = 400 });
|
oc_runtime_window_set_size((oc_vec2){ .x = 400, .y = 400 });
|
||||||
|
|
||||||
surface = oc_surface_canvas();
|
surface = oc_surface_canvas();
|
||||||
|
|
|
@ -615,7 +615,7 @@ ORCA_EXPORT void oc_on_init()
|
||||||
{
|
{
|
||||||
oc_log_info("Hello, world (from C)");
|
oc_log_info("Hello, world (from C)");
|
||||||
|
|
||||||
oc_runtime_window_set_title(OC_STR8("fluid"));
|
oc_window_set_title(OC_STR8("fluid"));
|
||||||
|
|
||||||
surface = oc_surface_gles();
|
surface = oc_surface_gles();
|
||||||
oc_surface_select(surface);
|
oc_surface_select(surface);
|
||||||
|
|
|
@ -37,7 +37,7 @@ void compile_shader(GLuint shader, const char* source)
|
||||||
|
|
||||||
ORCA_EXPORT void oc_on_init(void)
|
ORCA_EXPORT void oc_on_init(void)
|
||||||
{
|
{
|
||||||
oc_runtime_window_set_title(OC_STR8("triangle"));
|
oc_window_set_title(OC_STR8("triangle"));
|
||||||
|
|
||||||
surface = oc_surface_gles();
|
surface = oc_surface_gles();
|
||||||
oc_surface_select(surface);
|
oc_surface_select(surface);
|
||||||
|
|
|
@ -69,7 +69,7 @@ oc_str8 loadFile(oc_arena* arena, oc_str8 filename)
|
||||||
|
|
||||||
ORCA_EXPORT void oc_on_init(void)
|
ORCA_EXPORT void oc_on_init(void)
|
||||||
{
|
{
|
||||||
oc_runtime_window_set_title(OC_STR8("pong"));
|
oc_window_set_title(OC_STR8("pong"));
|
||||||
|
|
||||||
surface = oc_surface_canvas();
|
surface = oc_surface_canvas();
|
||||||
canvas = oc_canvas_create();
|
canvas = oc_canvas_create();
|
||||||
|
|
|
@ -10,7 +10,7 @@ oc_arena textArena = { 0 };
|
||||||
|
|
||||||
ORCA_EXPORT void oc_on_init(void)
|
ORCA_EXPORT void oc_on_init(void)
|
||||||
{
|
{
|
||||||
oc_runtime_window_set_title(OC_STR8("ui"));
|
oc_window_set_title(OC_STR8("ui"));
|
||||||
|
|
||||||
surface = oc_surface_canvas();
|
surface = oc_surface_canvas();
|
||||||
canvas = oc_canvas_create();
|
canvas = oc_canvas_create();
|
||||||
|
|
|
@ -457,9 +457,10 @@ ORCA_API int oc_directory_create(oc_str8 path);
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
|
void oc_window_set_title(oc_str8 title);
|
||||||
|
void oc_window_set_size(oc_vec2 size);
|
||||||
|
|
||||||
void ORCA_IMPORT(oc_request_quit)(void);
|
void ORCA_IMPORT(oc_request_quit)(void);
|
||||||
void ORCA_IMPORT(oc_runtime_window_set_title)(oc_str8 title);
|
|
||||||
void ORCA_IMPORT(oc_runtime_window_set_size)(oc_vec2 size);
|
|
||||||
|
|
||||||
#endif // !defined(OC_PLATFORM_ORCA) || !(OC_PLATFORM_ORCA)
|
#endif // !defined(OC_PLATFORM_ORCA) || !(OC_PLATFORM_ORCA)
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ typedef struct oc_log_output
|
||||||
static oc_log_output oc_logDefaultOutput = { .kind = ORCA_LOG_OUTPUT_CONSOLE };
|
static oc_log_output oc_logDefaultOutput = { .kind = ORCA_LOG_OUTPUT_CONSOLE };
|
||||||
oc_log_output* OC_LOG_DEFAULT_OUTPUT = &oc_logDefaultOutput;
|
oc_log_output* OC_LOG_DEFAULT_OUTPUT = &oc_logDefaultOutput;
|
||||||
|
|
||||||
void ORCA_IMPORT(oc_runtime_log)(oc_log_level level,
|
void ORCA_IMPORT(oc_bridge_log)(oc_log_level level,
|
||||||
int fileLen,
|
int fileLen,
|
||||||
const char* file,
|
const char* file,
|
||||||
int functionLen,
|
int functionLen,
|
||||||
|
@ -77,7 +77,7 @@ void platform_log_push(oc_log_output* output,
|
||||||
|
|
||||||
oc_str8 string = oc_str8_list_join(scratch, ctx.list);
|
oc_str8 string = oc_str8_list_join(scratch, ctx.list);
|
||||||
|
|
||||||
oc_runtime_log(level, strlen(file), file, strlen(function), function, line, oc_str8_ip(string));
|
oc_bridge_log(level, strlen(file), file, strlen(function), function, line, oc_str8_ip(string));
|
||||||
|
|
||||||
oc_arena_scope_end(tmp);
|
oc_arena_scope_end(tmp);
|
||||||
}
|
}
|
||||||
|
@ -86,8 +86,8 @@ void platform_log_push(oc_log_output* output,
|
||||||
// Assert/Abort
|
// Assert/Abort
|
||||||
//----------------------------------------------------------------
|
//----------------------------------------------------------------
|
||||||
|
|
||||||
_Noreturn void ORCA_IMPORT(oc_runtime_abort_ext)(const char* file, const char* function, int line, const char* msg);
|
_Noreturn void ORCA_IMPORT(oc_bridge_abort_ext)(const char* file, const char* function, int line, const char* msg);
|
||||||
_Noreturn void ORCA_IMPORT(oc_runtime_assert_fail)(const char* file, const char* function, int line, const char* src, const char* msg);
|
_Noreturn void ORCA_IMPORT(oc_bridge_assert_fail)(const char* file, const char* function, int line, const char* src, const char* msg);
|
||||||
|
|
||||||
_Noreturn void oc_abort_ext(const char* file, const char* function, int line, const char* fmt, ...)
|
_Noreturn void oc_abort_ext(const char* file, const char* function, int line, const char* fmt, ...)
|
||||||
{
|
{
|
||||||
|
@ -108,7 +108,7 @@ _Noreturn void oc_abort_ext(const char* file, const char* function, int line, co
|
||||||
|
|
||||||
oc_str8 msg = oc_str8_list_join(scratch.arena, ctx.list);
|
oc_str8 msg = oc_str8_list_join(scratch.arena, ctx.list);
|
||||||
|
|
||||||
oc_runtime_abort_ext(file, function, line, msg.ptr);
|
oc_bridge_abort_ext(file, function, line, msg.ptr);
|
||||||
|
|
||||||
oc_scratch_end(scratch);
|
oc_scratch_end(scratch);
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ _Noreturn void oc_assert_fail(const char* file, const char* function, int line,
|
||||||
|
|
||||||
oc_str8 msg = oc_str8_list_join(scratch.arena, ctx.list);
|
oc_str8 msg = oc_str8_list_join(scratch.arena, ctx.list);
|
||||||
|
|
||||||
oc_runtime_assert_fail(file, function, line, src, msg.ptr);
|
oc_bridge_assert_fail(file, function, line, src, msg.ptr);
|
||||||
|
|
||||||
oc_scratch_end(scratch);
|
oc_scratch_end(scratch);
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,11 +56,20 @@ oc_runtime* oc_runtime_get()
|
||||||
return (&__orcaApp);
|
return (&__orcaApp);
|
||||||
}
|
}
|
||||||
|
|
||||||
oc_runtime_env* oc_runtime_env_get()
|
oc_wasm_env* oc_runtime_get_env()
|
||||||
{
|
{
|
||||||
return (&__orcaApp.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);
|
||||||
|
}
|
||||||
|
|
||||||
void orca_wasm3_abort(IM3Runtime runtime, M3Result res, const char* file, const char* function, int line, const char* msg)
|
void orca_wasm3_abort(IM3Runtime runtime, M3Result res, const char* file, const char* function, int line, const char* msg)
|
||||||
{
|
{
|
||||||
M3ErrorInfo errInfo = { 0 };
|
M3ErrorInfo errInfo = { 0 };
|
||||||
|
@ -75,67 +84,21 @@ void orca_wasm3_abort(IM3Runtime runtime, M3Result res, const char* file, const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define ORCA_WASM3_ABORT(runtime, err, msg) orca_wasm3_abort(runtime, err, __FILE__, __FUNCTION__, __LINE__, msg)
|
void oc_bridge_window_set_title(oc_wasm_str8 title)
|
||||||
|
|
||||||
void* oc_runtime_env_ptr_to_native(oc_runtime_env* env, void* wasmPtr, u32 length)
|
|
||||||
{
|
{
|
||||||
// We can't use the runtime's memory pointer directly because wasm3 embeds a
|
oc_str8 nativeTitle = oc_wasm_str8_to_native(title);
|
||||||
// header at the beginning of the block we give it.
|
if(nativeTitle.ptr)
|
||||||
u64 bufferIndex = (u64)wasmPtr & 0xffffffff;
|
|
||||||
u32 memSize = 0;
|
|
||||||
char* memory = (char*)m3_GetMemory(env->m3Runtime, &memSize, 0);
|
|
||||||
|
|
||||||
if(bufferIndex + length < memSize)
|
|
||||||
{
|
{
|
||||||
char* nativePtr = memory + bufferIndex;
|
oc_window_set_title(__orcaApp.window, nativeTitle);
|
||||||
return nativePtr;
|
|
||||||
}
|
|
||||||
//TODO directly abort here?
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* oc_runtime_ptr_to_native(oc_runtime* orca, void* wasmPtr, u32 length)
|
|
||||||
{
|
|
||||||
return (oc_runtime_env_ptr_to_native(&orca->env, wasmPtr, length));
|
|
||||||
}
|
|
||||||
|
|
||||||
void* oc_wasm_arena_push(oc_runtime_env* env, i32 arenaIndex, u64 size)
|
|
||||||
{
|
|
||||||
void* retValues[1] = { 0 };
|
|
||||||
const void* retPointers[1] = { &retValues[0] };
|
|
||||||
const void* args[2] = { &arenaIndex, &size };
|
|
||||||
|
|
||||||
M3Result res = m3_Call(env->exports[OC_EXPORT_ARENA_PUSH], 2, args);
|
|
||||||
if(res)
|
|
||||||
{
|
|
||||||
ORCA_WASM3_ABORT(env->m3Runtime, res, "Runtime error");
|
|
||||||
}
|
|
||||||
|
|
||||||
res = m3_GetResults(env->exports[OC_EXPORT_ARENA_PUSH], 1, retPointers);
|
|
||||||
if(res)
|
|
||||||
{
|
|
||||||
ORCA_WASM3_ABORT(env->m3Runtime, res, "Runtime error");
|
|
||||||
}
|
|
||||||
void* ptr = oc_runtime_env_ptr_to_native(env, retValues[0], size);
|
|
||||||
return (ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void oc_runtime_window_set_title(oc_str8 title)
|
|
||||||
{
|
|
||||||
title.ptr = oc_runtime_ptr_to_native(oc_runtime_get(), title.ptr, title.len);
|
|
||||||
if(title.ptr)
|
|
||||||
{
|
|
||||||
oc_window_set_title(__orcaApp.window, title);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void oc_runtime_window_set_size(oc_vec2 size)
|
void oc_bridge_window_set_size(oc_vec2 size)
|
||||||
{
|
{
|
||||||
oc_window_set_content_size(__orcaApp.window, size);
|
oc_window_set_content_size(__orcaApp.window, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void oc_runtime_log(oc_log_level level,
|
void oc_bridge_log(oc_log_level level,
|
||||||
int fileLen,
|
int fileLen,
|
||||||
char* file,
|
char* file,
|
||||||
int functionLen,
|
int functionLen,
|
||||||
|
@ -372,9 +335,9 @@ char m3_type_to_tag(M3ValueType type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void oc_runtime_env_init(oc_runtime_env* runtime)
|
void oc_wasm_env_init(oc_wasm_env* runtime)
|
||||||
{
|
{
|
||||||
memset(runtime, 0, sizeof(oc_runtime_env));
|
memset(runtime, 0, sizeof(oc_wasm_env));
|
||||||
oc_base_allocator* allocator = oc_base_allocator_default();
|
oc_base_allocator* allocator = oc_base_allocator_default();
|
||||||
runtime->wasmMemory.committed = 0;
|
runtime->wasmMemory.committed = 0;
|
||||||
runtime->wasmMemory.reserved = 4ULL << 30;
|
runtime->wasmMemory.reserved = 4ULL << 30;
|
||||||
|
@ -392,7 +355,7 @@ i32 orca_runloop(void* user)
|
||||||
{
|
{
|
||||||
oc_runtime* app = &__orcaApp;
|
oc_runtime* app = &__orcaApp;
|
||||||
|
|
||||||
oc_runtime_env_init(&app->env);
|
oc_wasm_env_init(&app->env);
|
||||||
|
|
||||||
//NOTE: loads wasm module
|
//NOTE: loads wasm module
|
||||||
const char* bundleNameCString = "module";
|
const char* bundleNameCString = "module";
|
||||||
|
@ -418,7 +381,7 @@ i32 orca_runloop(void* user)
|
||||||
|
|
||||||
app->env.m3Runtime = m3_NewRuntime(app->env.m3Env, stackSize, NULL);
|
app->env.m3Runtime = m3_NewRuntime(app->env.m3Env, stackSize, NULL);
|
||||||
//NOTE: host memory will be freed when runtime is freed.
|
//NOTE: host memory will be freed when runtime is freed.
|
||||||
m3_RuntimeSetMemoryCallbacks(app->env.m3Runtime, wasm_memory_resize_callback, wasm_memory_free_callback, &app->env.wasmMemory);
|
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);
|
M3Result res = m3_ParseModule(app->env.m3Env, &app->env.m3Module, (u8*)app->env.wasmBytecode.ptr, app->env.wasmBytecode.len);
|
||||||
if(res)
|
if(res)
|
||||||
|
@ -569,7 +532,7 @@ i32 orca_runloop(void* user)
|
||||||
if(exports[OC_EXPORT_RAW_EVENT])
|
if(exports[OC_EXPORT_RAW_EVENT])
|
||||||
{
|
{
|
||||||
#ifndef M3_BIG_ENDIAN
|
#ifndef M3_BIG_ENDIAN
|
||||||
oc_event* eventPtr = (oc_event*)wasm_memory_offset_to_ptr(&app->env.wasmMemory, app->env.rawEventOffset);
|
oc_event* eventPtr = (oc_event*)oc_wasm_address_to_ptr(app->env.rawEventOffset, sizeof(oc_event));
|
||||||
memcpy(eventPtr, event, sizeof(*event));
|
memcpy(eventPtr, event, sizeof(*event));
|
||||||
|
|
||||||
const void* args[1] = { &app->env.rawEventOffset };
|
const void* args[1] = { &app->env.rawEventOffset };
|
||||||
|
|
|
@ -54,18 +54,18 @@ const oc_export_desc OC_EXPORT_DESC[] = {
|
||||||
#undef OC_STR8_LIT
|
#undef OC_STR8_LIT
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct wasm_memory
|
typedef struct oc_wasm_memory
|
||||||
{
|
{
|
||||||
char* ptr;
|
char* ptr;
|
||||||
u64 reserved;
|
u64 reserved;
|
||||||
u64 committed;
|
u64 committed;
|
||||||
|
|
||||||
} wasm_memory;
|
} oc_wasm_memory;
|
||||||
|
|
||||||
typedef struct oc_runtime_env
|
typedef struct oc_wasm_env
|
||||||
{
|
{
|
||||||
oc_str8 wasmBytecode;
|
oc_str8 wasmBytecode;
|
||||||
wasm_memory wasmMemory;
|
oc_wasm_memory wasmMemory;
|
||||||
|
|
||||||
// wasm3 data
|
// wasm3 data
|
||||||
IM3Environment m3Env;
|
IM3Environment m3Env;
|
||||||
|
@ -74,7 +74,7 @@ typedef struct oc_runtime_env
|
||||||
IM3Function exports[OC_EXPORT_COUNT];
|
IM3Function exports[OC_EXPORT_COUNT];
|
||||||
u32 rawEventOffset;
|
u32 rawEventOffset;
|
||||||
|
|
||||||
} oc_runtime_env;
|
} oc_wasm_env;
|
||||||
|
|
||||||
typedef struct log_entry
|
typedef struct log_entry
|
||||||
{
|
{
|
||||||
|
@ -114,20 +114,20 @@ typedef struct oc_runtime
|
||||||
{
|
{
|
||||||
bool quit;
|
bool quit;
|
||||||
oc_window window;
|
oc_window window;
|
||||||
|
oc_debug_overlay debugOverlay;
|
||||||
|
|
||||||
oc_file_table fileTable;
|
oc_file_table fileTable;
|
||||||
oc_file rootDir;
|
oc_file rootDir;
|
||||||
|
|
||||||
oc_runtime_env env;
|
oc_wasm_env env;
|
||||||
|
|
||||||
oc_debug_overlay debugOverlay;
|
|
||||||
|
|
||||||
} oc_runtime;
|
} oc_runtime;
|
||||||
|
|
||||||
oc_runtime* oc_runtime_get();
|
oc_runtime* oc_runtime_get(void);
|
||||||
oc_runtime_env* oc_runtime_env_get();
|
oc_wasm_env* oc_runtime_get_env(void);
|
||||||
|
oc_str8 oc_runtime_get_wasm_memory(void);
|
||||||
|
|
||||||
void* oc_runtime_ptr_to_native(oc_runtime* runtime, void* wasmPtr, u32 length);
|
void orca_wasm3_abort(IM3Runtime runtime, M3Result res, const char* file, const char* function, int line, const char* msg);
|
||||||
void* oc_wasm_arena_push(oc_runtime_env* env, i32 arenaIndex, u64 size);
|
#define ORCA_WASM3_ABORT(runtime, err, msg) orca_wasm3_abort(runtime, err, __FILE__, __FUNCTION__, __LINE__, msg)
|
||||||
|
|
||||||
#endif //__RUNTIME_H_
|
#endif //__RUNTIME_H_
|
||||||
|
|
|
@ -7,15 +7,17 @@
|
||||||
*****************************************************************/
|
*****************************************************************/
|
||||||
#include "platform/platform_io_internal.h"
|
#include "platform/platform_io_internal.h"
|
||||||
#include "runtime.h"
|
#include "runtime.h"
|
||||||
|
#include "runtime_memory.h"
|
||||||
|
|
||||||
oc_io_cmp oc_runtime_io_wait_single_req(oc_io_req* wasmReq)
|
oc_io_cmp oc_bridge_io_single_rect(oc_io_req* wasmReq)
|
||||||
{
|
{
|
||||||
oc_runtime* orca = oc_runtime_get();
|
oc_runtime* orca = oc_runtime_get();
|
||||||
|
|
||||||
oc_io_cmp cmp = { 0 };
|
oc_io_cmp cmp = { 0 };
|
||||||
oc_io_req req = *wasmReq;
|
oc_io_req req = *wasmReq;
|
||||||
|
|
||||||
void* buffer = oc_runtime_ptr_to_native(orca, req.buffer, req.size);
|
//TODO have a separate oc_wasm_io_req struct
|
||||||
|
void* buffer = oc_wasm_address_to_ptr((oc_wasm_addr)(uintptr_t)req.buffer, req.size);
|
||||||
|
|
||||||
if(buffer)
|
if(buffer)
|
||||||
{
|
{
|
||||||
|
@ -40,51 +42,20 @@ oc_io_cmp oc_runtime_io_wait_single_req(oc_io_req* wasmReq)
|
||||||
return (cmp);
|
return (cmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
oc_file oc_file_open_with_request_bridge(oc_str8 path, oc_file_access rights, oc_file_open_flags flags)
|
oc_file oc_file_open_with_request_bridge(oc_wasm_str8 path, oc_file_access rights, oc_file_open_flags flags)
|
||||||
{
|
{
|
||||||
oc_file file = oc_file_nil();
|
oc_file file = oc_file_nil();
|
||||||
oc_runtime* orca = oc_runtime_get();
|
oc_runtime* orca = oc_runtime_get();
|
||||||
|
|
||||||
path.ptr = oc_runtime_ptr_to_native(orca, path.ptr, path.len);
|
oc_str8 nativePath = oc_wasm_str8_to_native(path);
|
||||||
if(path.ptr)
|
|
||||||
|
if(nativePath.ptr)
|
||||||
{
|
{
|
||||||
file = oc_file_open_with_request_for_table(path, rights, flags, &orca->fileTable);
|
file = oc_file_open_with_request_for_table(nativePath, rights, flags, &orca->fileTable);
|
||||||
}
|
}
|
||||||
return (file);
|
return (file);
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct oc_wasm_list
|
|
||||||
{
|
|
||||||
u32 first;
|
|
||||||
u32 last;
|
|
||||||
} oc_wasm_list;
|
|
||||||
|
|
||||||
typedef struct oc_wasm_list_elt
|
|
||||||
{
|
|
||||||
u32 prev;
|
|
||||||
u32 next;
|
|
||||||
} oc_wasm_list_elt;
|
|
||||||
|
|
||||||
typedef struct oc_wasm_str8
|
|
||||||
{
|
|
||||||
u64 len;
|
|
||||||
u32 ptr;
|
|
||||||
} oc_wasm_str8;
|
|
||||||
|
|
||||||
typedef struct oc_wasm_str8_elt
|
|
||||||
{
|
|
||||||
oc_wasm_list_elt listElt;
|
|
||||||
oc_wasm_str8 string;
|
|
||||||
|
|
||||||
} oc_wasm_str8_elt;
|
|
||||||
|
|
||||||
typedef struct oc_wasm_str8_list
|
|
||||||
{
|
|
||||||
oc_wasm_list list;
|
|
||||||
u64 eltCount;
|
|
||||||
u64 len;
|
|
||||||
} oc_wasm_str8_list;
|
|
||||||
|
|
||||||
typedef struct oc_wasm_file_dialog_desc
|
typedef struct oc_wasm_file_dialog_desc
|
||||||
{
|
{
|
||||||
oc_file_dialog_kind kind;
|
oc_file_dialog_kind kind;
|
||||||
|
@ -111,7 +82,7 @@ typedef struct oc_wasm_file_open_with_dialog_result
|
||||||
|
|
||||||
} oc_wasm_file_open_with_dialog_result;
|
} oc_wasm_file_open_with_dialog_result;
|
||||||
|
|
||||||
oc_wasm_file_open_with_dialog_result oc_file_open_with_dialog_bridge(i32 wasmArenaIndex,
|
oc_wasm_file_open_with_dialog_result oc_file_open_with_dialog_bridge(oc_wasm_addr wasmArena,
|
||||||
oc_file_access rights,
|
oc_file_access rights,
|
||||||
oc_file_open_flags flags,
|
oc_file_open_flags flags,
|
||||||
oc_wasm_file_dialog_desc* desc)
|
oc_wasm_file_dialog_desc* desc)
|
||||||
|
@ -124,10 +95,10 @@ oc_wasm_file_open_with_dialog_result oc_file_open_with_dialog_bridge(i32 wasmAre
|
||||||
.flags = desc->flags
|
.flags = desc->flags
|
||||||
};
|
};
|
||||||
|
|
||||||
nativeDesc.title.ptr = oc_runtime_ptr_to_native(orca, (char*)(uintptr_t)desc->title.ptr, desc->title.len);
|
nativeDesc.title.ptr = oc_wasm_address_to_ptr(desc->title.ptr, desc->title.len);
|
||||||
nativeDesc.title.len = desc->title.len;
|
nativeDesc.title.len = desc->title.len;
|
||||||
|
|
||||||
nativeDesc.okLabel.ptr = oc_runtime_ptr_to_native(orca, (char*)(uintptr_t)desc->okLabel.ptr, desc->okLabel.len);
|
nativeDesc.okLabel.ptr = oc_wasm_address_to_ptr(desc->okLabel.ptr, desc->okLabel.len);
|
||||||
nativeDesc.okLabel.len = desc->okLabel.len;
|
nativeDesc.okLabel.len = desc->okLabel.len;
|
||||||
|
|
||||||
if(oc_file_is_nil(desc->startAt) && desc->startPath.len)
|
if(oc_file_is_nil(desc->startAt) && desc->startPath.len)
|
||||||
|
@ -138,17 +109,14 @@ oc_wasm_file_open_with_dialog_result oc_file_open_with_dialog_bridge(i32 wasmAre
|
||||||
{
|
{
|
||||||
nativeDesc.startAt = desc->startAt;
|
nativeDesc.startAt = desc->startAt;
|
||||||
}
|
}
|
||||||
nativeDesc.startPath.ptr = oc_runtime_ptr_to_native(orca, (char*)(uintptr_t)desc->startPath.ptr, desc->startPath.len);
|
nativeDesc.startPath.ptr = oc_wasm_address_to_ptr(desc->startPath.ptr, desc->startPath.len);
|
||||||
nativeDesc.startPath.len = desc->startPath.len;
|
nativeDesc.startPath.len = desc->startPath.len;
|
||||||
|
|
||||||
u32 eltIndex = desc->filters.list.first;
|
u32 eltIndex = desc->filters.list.first;
|
||||||
while(eltIndex)
|
while(eltIndex)
|
||||||
{
|
{
|
||||||
oc_wasm_str8_elt* elt = oc_runtime_ptr_to_native(orca, (char*)(uintptr_t)eltIndex, sizeof(oc_wasm_str8_elt));
|
oc_wasm_str8_elt* elt = oc_wasm_address_to_ptr(eltIndex, sizeof(oc_wasm_str8_elt));
|
||||||
|
oc_str8 filter = oc_wasm_str8_to_native(elt->string);
|
||||||
oc_str8 filter = { 0 };
|
|
||||||
filter.ptr = oc_runtime_ptr_to_native(orca, (char*)(uintptr_t)elt->string.ptr, elt->string.len);
|
|
||||||
filter.len = elt->string.len;
|
|
||||||
|
|
||||||
oc_str8_list_push(scratch.arena, &nativeDesc.filters, filter);
|
oc_str8_list_push(scratch.arena, &nativeDesc.filters, filter);
|
||||||
|
|
||||||
|
@ -164,31 +132,12 @@ oc_wasm_file_open_with_dialog_result oc_file_open_with_dialog_bridge(i32 wasmAre
|
||||||
.file = nativeResult.file
|
.file = nativeResult.file
|
||||||
};
|
};
|
||||||
|
|
||||||
u32 memSize;
|
|
||||||
char* wasmMemory = (char*)m3_GetMemory(orca->env.m3Runtime, &memSize, 0);
|
|
||||||
oc_wasm_file_open_with_dialog_elt* lastElt = 0;
|
|
||||||
|
|
||||||
oc_list_for(&nativeResult.selection, elt, oc_file_open_with_dialog_elt, listElt)
|
oc_list_for(&nativeResult.selection, elt, oc_file_open_with_dialog_elt, listElt)
|
||||||
{
|
{
|
||||||
oc_wasm_file_open_with_dialog_elt* wasmElt = oc_wasm_arena_push(&orca->env, wasmArenaIndex, sizeof(oc_wasm_file_open_with_dialog_elt));
|
oc_wasm_file_open_with_dialog_elt* wasmElt = oc_wasm_arena_push(wasmArena, sizeof(oc_wasm_file_open_with_dialog_elt));
|
||||||
wasmElt->file = elt->file;
|
wasmElt->file = elt->file;
|
||||||
|
|
||||||
if(result.selection.last == 0)
|
oc_wasm_list_push_back(&result.selection, &wasmElt->listElt);
|
||||||
{
|
|
||||||
result.selection.first = ((char*)wasmElt - wasmMemory);
|
|
||||||
result.selection.last = result.selection.first;
|
|
||||||
wasmElt->listElt.prev = 0;
|
|
||||||
wasmElt->listElt.next = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
wasmElt->listElt.prev = result.selection.last;
|
|
||||||
wasmElt->listElt.next = 0;
|
|
||||||
lastElt->listElt.next = ((char*)wasmElt - wasmMemory);
|
|
||||||
|
|
||||||
result.selection.last = ((char*)wasmElt - wasmMemory);
|
|
||||||
}
|
|
||||||
lastElt = wasmElt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
oc_scratch_end(scratch);
|
oc_scratch_end(scratch);
|
||||||
|
|
|
@ -7,10 +7,11 @@
|
||||||
*****************************************************************/
|
*****************************************************************/
|
||||||
|
|
||||||
#include "runtime.h"
|
#include "runtime.h"
|
||||||
|
#include "runtime_memory.h"
|
||||||
|
|
||||||
void* wasm_memory_resize_callback(void* p, unsigned long size, void* userData)
|
void* oc_wasm_memory_resize_callback(void* p, unsigned long size, void* userData)
|
||||||
{
|
{
|
||||||
wasm_memory* memory = (wasm_memory*)userData;
|
oc_wasm_memory* memory = (oc_wasm_memory*)userData;
|
||||||
|
|
||||||
if(memory->committed >= size)
|
if(memory->committed >= size)
|
||||||
{
|
{
|
||||||
|
@ -32,35 +33,119 @@ void* wasm_memory_resize_callback(void* p, unsigned long size, void* userData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void wasm_memory_free_callback(void* p, void* userData)
|
void oc_wasm_memory_free_callback(void* p, void* userData)
|
||||||
{
|
{
|
||||||
wasm_memory* memory = (wasm_memory*)userData;
|
oc_wasm_memory* memory = (oc_wasm_memory*)userData;
|
||||||
|
|
||||||
oc_base_allocator* allocator = oc_base_allocator_default();
|
oc_base_allocator* allocator = oc_base_allocator_default();
|
||||||
oc_base_release(allocator, memory->ptr, memory->reserved);
|
oc_base_release(allocator, memory->ptr, memory->reserved);
|
||||||
memset(memory, 0, sizeof(wasm_memory));
|
memset(memory, 0, sizeof(oc_wasm_memory));
|
||||||
}
|
}
|
||||||
|
|
||||||
extern u32 oc_mem_grow(u64 size)
|
extern u32 oc_mem_grow(u64 size)
|
||||||
{
|
{
|
||||||
oc_runtime_env* runtime = oc_runtime_env_get();
|
oc_wasm_env* env = oc_runtime_get_env();
|
||||||
wasm_memory* memory = &runtime->wasmMemory;
|
oc_wasm_memory* memory = &env->wasmMemory;
|
||||||
|
|
||||||
size = oc_align_up_pow2(size, d_m3MemPageSize);
|
size = oc_align_up_pow2(size, d_m3MemPageSize);
|
||||||
u64 totalSize = size + m3_GetMemorySize(runtime->m3Runtime);
|
u64 totalSize = size + m3_GetMemorySize(env->m3Runtime);
|
||||||
|
|
||||||
u32 addr = memory->committed;
|
u32 addr = memory->committed;
|
||||||
|
|
||||||
//NOTE: call resize memory, which will call our custom resize callback... this is a bit involved because
|
//NOTE: call resize memory, which will call our custom resize callback... this is a bit involved because
|
||||||
// wasm3 doesn't allow resizing the memory directly
|
// wasm3 doesn't allow resizing the memory directly
|
||||||
M3Result res = ResizeMemory(runtime->m3Runtime, totalSize / d_m3MemPageSize);
|
M3Result res = ResizeMemory(env->m3Runtime, totalSize / d_m3MemPageSize);
|
||||||
|
|
||||||
return (addr);
|
return (addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void* wasm_memory_offset_to_ptr(wasm_memory* memory, u32 offset)
|
void* oc_wasm_address_to_ptr(oc_wasm_addr addr, oc_wasm_size size)
|
||||||
{
|
{
|
||||||
M3MemoryHeader* header = (M3MemoryHeader*)(memory->ptr);
|
oc_str8 mem = oc_runtime_get_wasm_memory();
|
||||||
OC_DEBUG_ASSERT(offset < header->length, "Wasm offset exceeds memory length");
|
OC_ASSERT(addr + size < mem.len, "Object overflows wasm memory");
|
||||||
return memory->ptr + sizeof(M3MemoryHeader) + offset;
|
|
||||||
|
void* ptr = (addr == 0) ? 0 : mem.ptr + addr;
|
||||||
|
return (ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
oc_wasm_addr oc_wasm_address_from_ptr(void* ptr, oc_wasm_size size)
|
||||||
|
{
|
||||||
|
oc_wasm_addr addr = 0;
|
||||||
|
if(ptr != 0)
|
||||||
|
{
|
||||||
|
oc_str8 mem = oc_runtime_get_wasm_memory();
|
||||||
|
OC_ASSERT((char*)ptr > mem.ptr && (((char*)ptr - mem.ptr) + size < mem.len), "Object overflows wasm memory");
|
||||||
|
|
||||||
|
addr = (char*)ptr - mem.ptr;
|
||||||
|
}
|
||||||
|
return (addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------------
|
||||||
|
// oc_wasm_list helpers
|
||||||
|
//------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void oc_wasm_list_push(oc_wasm_list* list, oc_wasm_list_elt* elt)
|
||||||
|
{
|
||||||
|
elt->next = list->first;
|
||||||
|
elt->prev = 0;
|
||||||
|
|
||||||
|
oc_wasm_addr eltAddr = oc_wasm_address_from_ptr(elt, sizeof(oc_wasm_list_elt));
|
||||||
|
|
||||||
|
if(list->first)
|
||||||
|
{
|
||||||
|
oc_wasm_list_elt* first = oc_wasm_address_to_ptr(list->first, sizeof(oc_wasm_list_elt));
|
||||||
|
first->prev = eltAddr;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
list->last = eltAddr;
|
||||||
|
}
|
||||||
|
list->first = eltAddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void oc_wasm_list_push_back(oc_wasm_list* list, oc_wasm_list_elt* elt)
|
||||||
|
{
|
||||||
|
elt->prev = list->last;
|
||||||
|
elt->next = 0;
|
||||||
|
|
||||||
|
oc_wasm_addr eltAddr = oc_wasm_address_from_ptr(elt, sizeof(oc_wasm_list_elt));
|
||||||
|
|
||||||
|
if(list->last)
|
||||||
|
{
|
||||||
|
oc_wasm_list_elt* last = oc_wasm_address_to_ptr(list->last, sizeof(oc_wasm_list_elt));
|
||||||
|
last->next = eltAddr;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
list->first = eltAddr;
|
||||||
|
}
|
||||||
|
list->last = eltAddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------------
|
||||||
|
// Wasm arenas helpers
|
||||||
|
//------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void* oc_wasm_arena_push(oc_wasm_addr arena, u64 size)
|
||||||
|
{
|
||||||
|
oc_wasm_env* env = oc_runtime_get_env();
|
||||||
|
|
||||||
|
oc_wasm_addr retValues[1] = { 0 };
|
||||||
|
const void* retPointers[1] = { (void*)&retValues[0] };
|
||||||
|
const void* args[2] = { &arena, &size };
|
||||||
|
|
||||||
|
M3Result res = m3_Call(env->exports[OC_EXPORT_ARENA_PUSH], 2, args);
|
||||||
|
if(res)
|
||||||
|
{
|
||||||
|
ORCA_WASM3_ABORT(env->m3Runtime, res, "Runtime error");
|
||||||
|
}
|
||||||
|
|
||||||
|
res = m3_GetResults(env->exports[OC_EXPORT_ARENA_PUSH], 1, retPointers);
|
||||||
|
if(res)
|
||||||
|
{
|
||||||
|
ORCA_WASM3_ABORT(env->m3Runtime, res, "Runtime error");
|
||||||
|
}
|
||||||
|
void* ptr = oc_wasm_address_to_ptr(retValues[0], size);
|
||||||
|
return (ptr);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
/************************************************************/ /**
|
||||||
|
*
|
||||||
|
* @file: runtime_memory.h
|
||||||
|
* @author: Martin Fouilleul
|
||||||
|
* @date: 02/09/2023
|
||||||
|
*
|
||||||
|
*****************************************************************/
|
||||||
|
#ifndef __RUNTIME_MEMORY_H_
|
||||||
|
#define __RUNTIME_MEMORY_H_
|
||||||
|
|
||||||
|
#include "runtime.h"
|
||||||
|
|
||||||
|
typedef u32 oc_wasm_addr;
|
||||||
|
typedef u32 oc_wasm_size;
|
||||||
|
|
||||||
|
void* oc_wasm_address_to_ptr(oc_wasm_addr addr, oc_wasm_size size);
|
||||||
|
oc_wasm_addr oc_wasm_address_from_ptr(void* ptr, oc_wasm_size size);
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------------
|
||||||
|
// oc_wasm_list helpers
|
||||||
|
//------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
typedef struct oc_wasm_list
|
||||||
|
{
|
||||||
|
u32 first;
|
||||||
|
u32 last;
|
||||||
|
} oc_wasm_list;
|
||||||
|
|
||||||
|
typedef struct oc_wasm_list_elt
|
||||||
|
{
|
||||||
|
u32 prev;
|
||||||
|
u32 next;
|
||||||
|
} oc_wasm_list_elt;
|
||||||
|
|
||||||
|
bool oc_wasm_list_empty(oc_wasm_list* list);
|
||||||
|
|
||||||
|
#define oc_wasm_list_begin(list) oc_wasm_address_to_ptr(list->first, sizeof(oc_wasm_list_elt))
|
||||||
|
#define oc_wasm_list_end(list) oc_wasm_address_to_ptr(list->last, sizeof(oc_wasm_list_elt))
|
||||||
|
|
||||||
|
#define oc_wasm_list_next(elt) oc_wasm_address_to_ptr((elt)->next, sizeof(oc_wasm_list_elt))
|
||||||
|
#define oc_wasm_list_prev(elt) oc_wasm_address_to_ptr((elt)->prev, sizeof(oc_wasm_list_elt))
|
||||||
|
|
||||||
|
#define oc_wasm_list_entry(ptr, type, member) \
|
||||||
|
oc_container_of(ptr, type, member)
|
||||||
|
|
||||||
|
#define oc_wasm_list_next_entry(list, elt, type, member) \
|
||||||
|
(((elt)->member.next != 0) ? oc_wasm_list_entry(oc_wasm_list_next((elt)->member), type, member) : 0)
|
||||||
|
|
||||||
|
#define oc_wasm_list_prev_entry(list, elt, type, member) \
|
||||||
|
(((elt)->member.prev != 0) ? oc_wasm_list_entry(oc_wasm_list_prev((elt)->member), type, member) : 0)
|
||||||
|
|
||||||
|
#define oc_wasm_list_checked_entry(elt, type, member) \
|
||||||
|
(((elt) != 0) ? oc_wasm_list_entry(elt, type, member) : 0)
|
||||||
|
|
||||||
|
#define oc_wasm_list_first_entry(list, type, member) \
|
||||||
|
(oc_wasm_list_checked_entry(oc_wasm_list_begin(list), type, member))
|
||||||
|
|
||||||
|
#define oc_wasm_list_last_entry(list, type, member) \
|
||||||
|
(oc_wasm_list_checked_entry(oc_wasm_list_last(list), type, member))
|
||||||
|
|
||||||
|
#define oc_wasm_list_for(list, elt, type, member) \
|
||||||
|
for(type* elt = oc_wasm_list_checked_entry(oc_wasm_list_begin(list), type, member); \
|
||||||
|
elt != 0; \
|
||||||
|
elt = oc_wasm_list_checked_entry(oc_wasm_list_next((elt)->member), type, member))
|
||||||
|
|
||||||
|
void oc_wasm_list_push(oc_wasm_list* list, oc_wasm_list_elt* elt);
|
||||||
|
void oc_wasm_list_push_back(oc_wasm_list* list, oc_wasm_list_elt* elt);
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------------
|
||||||
|
// oc_wasm_str8 helpers
|
||||||
|
//------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
typedef struct oc_wasm_str8
|
||||||
|
{
|
||||||
|
u64 len;
|
||||||
|
u32 ptr;
|
||||||
|
} oc_wasm_str8;
|
||||||
|
|
||||||
|
typedef struct oc_wasm_str8_elt
|
||||||
|
{
|
||||||
|
oc_wasm_list_elt listElt;
|
||||||
|
oc_wasm_str8 string;
|
||||||
|
|
||||||
|
} oc_wasm_str8_elt;
|
||||||
|
|
||||||
|
typedef struct oc_wasm_str8_list
|
||||||
|
{
|
||||||
|
oc_wasm_list list;
|
||||||
|
u64 eltCount;
|
||||||
|
u64 len;
|
||||||
|
} oc_wasm_str8_list;
|
||||||
|
|
||||||
|
#define oc_wasm_str8_to_native(wasmString) ((oc_str8){ .ptr = oc_wasm_address_to_ptr(wasmString.ptr, wasmString.len), .len = wasmString.len })
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------------
|
||||||
|
// Wasm arenas helpers
|
||||||
|
//------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void* oc_wasm_arena_push(oc_wasm_addr arena, u64 size);
|
||||||
|
|
||||||
|
#endif //__RUNTIME_MEMORY_H_
|
|
@ -1,7 +1,7 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"name": "oc_runtime_log",
|
"name": "oc_bridge_log",
|
||||||
"cname": "oc_runtime_log",
|
"cname": "oc_bridge_log",
|
||||||
"ret": {"name": "void", "tag": "v"},
|
"ret": {"name": "void", "tag": "v"},
|
||||||
"args": [ {"name": "level",
|
"args": [ {"name": "level",
|
||||||
"type": {"name": "oc_log_level", "tag": "i"}},
|
"type": {"name": "oc_log_level", "tag": "i"}},
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
"type": {"name": "u64", "tag": "I"}}]
|
"type": {"name": "u64", "tag": "I"}}]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "oc_runtime_assert_fail",
|
"name": "oc_bridge_assert_fail",
|
||||||
"cname": "oc_assert_fail",
|
"cname": "oc_assert_fail",
|
||||||
"ret": {"name": "void", "tag": "v"},
|
"ret": {"name": "void", "tag": "v"},
|
||||||
"args": [ {"name": "file",
|
"args": [ {"name": "file",
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "oc_runtime_abort_ext",
|
"name": "oc_bridge_abort_ext",
|
||||||
"cname": "oc_abort_ext",
|
"cname": "oc_abort_ext",
|
||||||
"ret": {"name": "void", "tag": "v"},
|
"ret": {"name": "void", "tag": "v"},
|
||||||
"args": [ {"name": "file",
|
"args": [ {"name": "file",
|
||||||
|
@ -71,17 +71,17 @@
|
||||||
"args": []
|
"args": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "oc_runtime_window_set_title",
|
"name": "oc_window_set_title",
|
||||||
"cname": "oc_runtime_window_set_title",
|
"cname": "oc_bridge_window_set_title",
|
||||||
"ret": {"name": "void", "tag": "v"},
|
"ret": {"name": "void", "tag": "v"},
|
||||||
"args": [
|
"args": [
|
||||||
{ "name": "title",
|
{ "name": "title",
|
||||||
"type": {"name": "oc_str8", "tag": "S"}}
|
"type": {"name": "oc_str8", "cname": "oc_wasm_str8", "tag": "S"}}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "oc_runtime_window_set_size",
|
"name": "oc_window_set_size",
|
||||||
"cname": "oc_runtime_window_set_size",
|
"cname": "oc_bridge_window_set_size",
|
||||||
"ret": {"name": "void", "tag": "v"},
|
"ret": {"name": "void", "tag": "v"},
|
||||||
"args": [
|
"args": [
|
||||||
{ "name": "size",
|
{ "name": "size",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"name": "oc_io_wait_single_req",
|
"name": "oc_io_wait_single_req",
|
||||||
"cname": "oc_runtime_io_wait_single_req",
|
"cname": "oc_bridge_io_single_rect",
|
||||||
"ret": {"name": "oc_io_cmp", "tag": "S"},
|
"ret": {"name": "oc_io_cmp", "tag": "S"},
|
||||||
"args": [ {"name": "req",
|
"args": [ {"name": "req",
|
||||||
"type": {"name": "oc_io_req*", "tag": "p"}}]
|
"type": {"name": "oc_io_req*", "tag": "p"}}]
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
"ret": {"name": "oc_file", "tag": "S"},
|
"ret": {"name": "oc_file", "tag": "S"},
|
||||||
"args": [
|
"args": [
|
||||||
{"name": "path",
|
{"name": "path",
|
||||||
"type": {"name": "oc_str8", "tag": "S"}},
|
"type": {"name": "oc_str8", "cname": "oc_wasm_str8", "tag": "S"}},
|
||||||
{"name": "rights",
|
{"name": "rights",
|
||||||
"type": {"name": "oc_file_access", "tag": "i"}},
|
"type": {"name": "oc_file_access", "tag": "i"}},
|
||||||
{"name": "flags",
|
{"name": "flags",
|
||||||
|
|
Loading…
Reference in New Issue