Files
Bolt/src/library/plugin/plugin.c
2024-08-17 01:52:15 +01:00

1450 lines
58 KiB
C

#include "plugin.h"
#include "plugin_api.h"
#include "../ipc.h"
#include "../../../modules/hashmap/hashmap.h"
#include "../../../modules/spng/spng/spng.h"
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#if defined(_WIN32)
#include <windows.h>
LARGE_INTEGER performance_frequency;
#endif
#define API_VERSION_MAJOR 1
#define API_VERSION_MINOR 0
#define PUSHSTRING(STATE, STR) lua_pushlstring(STATE, STR, sizeof(STR) - sizeof(*(STR)))
#define SNPUSHSTRING(STATE, BUF, STR, ...) {int n = snprintf(BUF, sizeof(BUF), STR, __VA_ARGS__);lua_pushlstring(STATE, BUF, n <= 0 ? 0 : (n >= sizeof(BUF) ? sizeof(BUF) - 1 : n));}
#define API_ADD(FUNC) PUSHSTRING(state, #FUNC);lua_pushcfunction(state, api_##FUNC);lua_settable(state, -3);
#define API_ADD_SUB(STATE, FUNC, SUB) PUSHSTRING(STATE, #FUNC);lua_pushcfunction(STATE, api_##SUB##_##FUNC);lua_settable(STATE, -3);
#define API_ADD_SUB_ALIAS(STATE, FUNC, ALIAS, SUB) PUSHSTRING(STATE, #ALIAS);lua_pushcfunction(STATE, api_##SUB##_##FUNC);lua_settable(STATE, -3);
#define PLUGIN_REGISTRYNAME "plugin"
#define WINDOWS_REGISTRYNAME "windows"
#define BATCH2D_META_REGISTRYNAME "batch2dmeta"
#define RENDER3D_META_REGISTRYNAME "render3dmeta"
#define MINIMAP_META_REGISTRYNAME "minimapmeta"
#define SWAPBUFFERS_META_REGISTRYNAME "swapbuffersmeta"
#define SURFACE_META_REGISTRYNAME "surfacemeta"
#define RESIZE_META_REGISTRYNAME "resizemeta"
#define MOUSEMOTION_META_REGISTRYNAME "mousemotionmeta"
#define MOUSEBUTTON_META_REGISTRYNAME "mousebuttonmeta"
#define MOUSEBUTTONUP_META_REGISTRYNAME MOUSEBUTTON_META_REGISTRYNAME
#define SCROLL_META_REGISTRYNAME "scrollmeta"
#define WINDOW_META_REGISTRYNAME "windowmeta"
#define SWAPBUFFERS_CB_REGISTRYNAME "swapbufferscb"
#define BATCH2D_CB_REGISTRYNAME "batch2dcb"
#define RENDER3D_CB_REGISTRYNAME "render3dcb"
#define MINIMAP_CB_REGISTRYNAME "minimapcb"
#define MOUSEMOTION_CB_REGISTRYNAME "mousemotioncb"
#define MOUSEBUTTON_CB_REGISTRYNAME "mousebuttoncb"
#define MOUSEBUTTONUP_CB_REGISTRYNAME "mousebuttonupcb"
#define SCROLL_CB_REGISTRYNAME "scrollcb"
enum {
WINDOW_ONRESIZE,
WINDOW_ONMOUSEMOTION,
WINDOW_ONMOUSEBUTTON,
WINDOW_ONMOUSEBUTTONUP,
WINDOW_ONSCROLL,
WINDOW_EVENT_ENUM_SIZE, // last member of enum
};
struct ResizeEvent {
uint16_t width;
uint16_t height;
};
struct MouseMotionEvent {
struct MouseEvent* details;
};
struct MouseButtonEvent {
struct MouseEvent* details;
uint8_t button; // 1 left, 2 right, 3 middle
};
struct MouseScrollEvent {
struct MouseEvent* details;
uint8_t direction; // 0 down, 1 up
};
static struct PluginManagedFunctions managed_functions;
static uint64_t next_window_id;
static struct WindowInfo windows;
static bool inited = false;
static int _bolt_api_init(lua_State* state);
static BoltSocketType fd = 0;
// a currently-running plugin.
// note strings are not null terminated, and "path" must always be converted to use '/' as path-separators
// and must always end with a trailing separator.
struct Plugin {
lua_State* state;
char* id;
char* path;
uint32_t id_length;
uint32_t path_length;
};
static void _bolt_plugin_window_onresize(struct EmbeddedWindow*, struct ResizeEvent*);
static void _bolt_plugin_window_onmousemotion(struct EmbeddedWindow*, struct MouseMotionEvent*);
static void _bolt_plugin_window_onmousebutton(struct EmbeddedWindow*, struct MouseButtonEvent*);
static void _bolt_plugin_window_onmousebuttonup(struct EmbeddedWindow*, struct MouseButtonEvent*);
static void _bolt_plugin_window_onscroll(struct EmbeddedWindow*, struct MouseScrollEvent*);
static void _bolt_plugin_handle_mousemotion(struct MouseMotionEvent*);
static void _bolt_plugin_handle_mousebutton(struct MouseButtonEvent*);
static void _bolt_plugin_handle_mousebuttonup(struct MouseButtonEvent*);
static void _bolt_plugin_handle_scroll(struct MouseScrollEvent*);
void _bolt_plugin_free(struct Plugin* const* plugin) {
lua_close((*plugin)->state);
free((*plugin)->id);
free(*plugin);
}
static int _bolt_window_map_compare(const void* a, const void* b, void* udata) {
return **(uint64_t**)a != **(uint64_t**)b;
}
static uint64_t _bolt_window_map_hash(const void* item, uint64_t seed0, uint64_t seed1) {
return hashmap_sip(*(uint64_t**)item, sizeof(uint64_t), seed0, seed1);
}
static int _bolt_plugin_map_compare(const void* a, const void* b, void* udata) {
const struct Plugin* p1 = *(const struct Plugin* const*)a;
const struct Plugin* p2 = *(const struct Plugin* const*)b;
if (p1->id_length != p2->id_length) return p1->id_length;
return strncmp(p1->id, p2->id, p1->id_length);
}
static uint64_t _bolt_plugin_map_hash(const void* item, uint64_t seed0, uint64_t seed1) {
const struct Plugin* p = *(const struct Plugin* const*)item;
return hashmap_sip(p->id, p->id_length, seed0, seed1);
}
static struct hashmap* plugins;
// macro for defining callback functions "_bolt_plugin_handle_*" and "api_setcallback*"
// e.g. DEFINE_CALLBACK(swapbuffers, SWAPBUFFERS, SwapBuffersEvent)
#define DEFINE_CALLBACK(APINAME, REGNAME, STRUCTNAME) \
void _bolt_plugin_handle_##APINAME(struct STRUCTNAME* e) { \
size_t iter = 0; \
void* item; \
while (hashmap_iter(plugins, &iter, &item)) { \
struct Plugin* plugin = *(struct Plugin* const*)item; \
void* newud = lua_newuserdata(plugin->state, sizeof(struct STRUCTNAME)); /*stack: userdata*/ \
memcpy(newud, e, sizeof(struct STRUCTNAME)); \
lua_getfield(plugin->state, LUA_REGISTRYINDEX, REGNAME##_META_REGISTRYNAME); /*stack: userdata, metatable*/ \
lua_setmetatable(plugin->state, -2); /*stack: userdata*/ \
PUSHSTRING(plugin->state, REGNAME##_CB_REGISTRYNAME); /*stack: userdata, enumname*/ \
lua_gettable(plugin->state, LUA_REGISTRYINDEX); /*stack: userdata, callback*/ \
if (!lua_isfunction(plugin->state, -1)) { \
lua_pop(plugin->state, 2); \
continue; \
} \
lua_pushvalue(plugin->state, -2); /*stack: userdata, callback, userdata*/ \
if (lua_pcall(plugin->state, 1, 0, 0)) { /*stack: userdata, ?error*/ \
const char* e = lua_tolstring(plugin->state, -1, 0); \
printf("plugin callback " #APINAME " error: %s\n", e); \
lua_pop(plugin->state, 2); /*stack: (empty)*/ \
_bolt_plugin_stop(plugin->id, plugin->id_length); \
break; \
} else { \
lua_pop(plugin->state, 1); /*stack: (empty)*/ \
} \
} \
} \
static int api_setcallback##APINAME(lua_State* state) { \
_bolt_check_argc(state, 1, "setcallback" #APINAME); \
PUSHSTRING(state, REGNAME##_CB_REGISTRYNAME); \
if (lua_isfunction(state, 1)) { \
lua_pushvalue(state, 1); \
} else { \
lua_pushnil(state); \
} \
lua_settable(state, LUA_REGISTRYINDEX); \
return 0; \
}
// macro for defining function "api_window_on*" and "_bolt_plugin_window_on*"
// e.g. DEFINE_WINDOWEVENT(resize, RESIZE, ResizeEvent)
#define DEFINE_WINDOWEVENT(APINAME, REGNAME, EVNAME) \
static int api_window_on##APINAME(lua_State* state) { \
_bolt_check_argc(state, 2, "window_on"#APINAME); \
const struct EmbeddedWindow* window = lua_touserdata(state, 1); \
lua_getfield(state, LUA_REGISTRYINDEX, WINDOWS_REGISTRYNAME); /*stack: window table*/ \
lua_pushinteger(state, window->id); /*stack: window table, window id*/ \
lua_gettable(state, -2); /*stack: window table, event table*/ \
lua_pushinteger(state, WINDOW_ON##REGNAME); /*stack: window table, event table, event id*/ \
if (lua_isfunction(state, 2)) { \
lua_pushvalue(state, 2); \
} else { \
lua_pushnil(state); \
} /*stack: window table, event table, event id, value*/ \
lua_settable(state, -3); /*stack: window table, event table*/ \
lua_pop(state, 2); /*stack: (empty)*/ \
return 0; \
} \
void _bolt_plugin_window_on##APINAME(struct EmbeddedWindow* window, struct EVNAME* event) { \
lua_State* state = window->plugin; \
lua_getfield(state, LUA_REGISTRYINDEX, WINDOWS_REGISTRYNAME); /*stack: window table*/ \
lua_pushinteger(state, window->id); /*stack: window table, window id*/ \
lua_gettable(state, -2); /*stack: window table, event table*/ \
lua_pushinteger(state, WINDOW_ON##REGNAME); /*stack: window table, event table, event id*/ \
lua_gettable(state, -2); /*stack: window table, event table, function or nil*/ \
if (lua_isfunction(state, -1)) { \
void* newud = lua_newuserdata(state, sizeof(struct EVNAME)); /*stack: window table, event table, function, event*/ \
memcpy(newud, event, sizeof(struct EVNAME)); \
lua_getfield(state, LUA_REGISTRYINDEX, REGNAME##_META_REGISTRYNAME); /*stack: window table, event table, function, event, event metatable*/ \
lua_setmetatable(state, -2); /*stack: window table, event table, function, event*/ \
if (lua_pcall(state, 1, 0, 0)) { /*stack: window table, event table, ?error*/ \
const char* e = lua_tolstring(state, -1, 0); \
printf("plugin window on" #APINAME " error: %s\n", e); \
lua_getfield(state, LUA_REGISTRYINDEX, PLUGIN_REGISTRYNAME); /*stack: window table, event table, error, plugin*/ \
const struct Plugin* plugin = lua_touserdata(state, -1); \
lua_pop(state, 4); /*stack: (empty)*/ \
_bolt_plugin_stop(plugin->id, plugin->id_length); \
} else { \
lua_pop(state, 2); /*stack: (empty)*/ \
} \
} else { \
lua_pop(state, 3); \
} \
}
static int surface_gc(lua_State* state) {
const struct SurfaceFunctions* functions = lua_touserdata(state, 1);
managed_functions.surface_destroy(functions->userdata);
return 0;
}
static int window_gc(lua_State* state) {
struct EmbeddedWindow* window = lua_touserdata(state, 1);
// destroy the public hashmap entry and clean up the struct itself
_bolt_rwlock_lock_write(&windows.lock);
_bolt_rwlock_destroy(&window->lock);
hashmap_delete(windows.map, &window);
_bolt_rwlock_unlock_write(&windows.lock);
// destroy the plugin registry entry
lua_getfield(state, LUA_REGISTRYINDEX, WINDOWS_REGISTRYNAME);
lua_pushinteger(state, window->id);
lua_pushnil(state);
lua_settable(state, -3);
lua_pop(state, 1);
managed_functions.surface_destroy(window->surface_functions.userdata);
return 0;
}
void _bolt_plugin_on_startup() {
windows.map = hashmap_new(sizeof(struct EmbeddedWindow*), 8, 0, 0, _bolt_window_map_hash, _bolt_window_map_compare, NULL, NULL);
_bolt_rwlock_init(&windows.lock);
memset(&windows.input, 0, sizeof(windows.input));
#if defined(_WIN32)
QueryPerformanceFrequency(&performance_frequency);
#endif
}
void _bolt_plugin_init(const struct PluginManagedFunctions* functions) {
_bolt_plugin_ipc_init(&fd);
const char* display_name = getenv("JX_DISPLAY_NAME");
if (display_name && *display_name) {
size_t name_len = strlen(display_name);
struct BoltIPCMessageToHost message = {.message_type = IPC_MSG_IDENTIFY, .items = name_len};
_bolt_ipc_send(fd, &message, sizeof(message));
_bolt_ipc_send(fd, display_name, name_len);
}
managed_functions = *functions;
_bolt_rwlock_lock_write(&windows.lock);
next_window_id = 1;
plugins = hashmap_new(sizeof(struct Plugin*), 8, 0, 0, _bolt_plugin_map_hash, _bolt_plugin_map_compare, NULL, NULL);
inited = 1;
_bolt_rwlock_unlock_write(&windows.lock);
}
static int _bolt_api_init(lua_State* state) {
lua_createtable(state, 0, 13);
API_ADD(apiversion)
API_ADD(checkversion)
API_ADD(time)
API_ADD(datetime)
API_ADD(weekday)
API_ADD(setcallback2d)
API_ADD(setcallback3d)
API_ADD(setcallbackminimap)
API_ADD(setcallbackswapbuffers)
API_ADD(setcallbackmousemotion)
API_ADD(setcallbackmousebutton)
API_ADD(setcallbackmousebuttonup)
API_ADD(setcallbackscroll)
API_ADD(createsurface)
API_ADD(createsurfacefromrgba)
API_ADD(createsurfacefrompng)
API_ADD(createwindow)
return 1;
}
uint8_t _bolt_plugin_is_inited() {
return inited;
}
void _bolt_plugin_process_windows(uint32_t window_width, uint32_t window_height) {
struct SwapBuffersEvent event;
_bolt_plugin_handle_swapbuffers(&event);
_bolt_plugin_handle_messages();
struct WindowInfo* windows = _bolt_plugin_windowinfo();
_bolt_rwlock_lock_write(&windows->input_lock);
struct WindowPendingInput inputs = windows->input;
memset(&windows->input, 0, sizeof(windows->input));
_bolt_rwlock_unlock_write(&windows->input_lock);
if (inputs.mouse_motion) {
struct MouseMotionEvent event = {.details = &inputs.mouse_motion_event};
_bolt_plugin_handle_mousemotion(&event);
}
if (inputs.mouse_left) {
struct MouseButtonEvent event = {.details = &inputs.mouse_left_event, .button = MBLeft};
_bolt_plugin_handle_mousebutton(&event);
}
if (inputs.mouse_right) {
struct MouseButtonEvent event = {.details = &inputs.mouse_right_event, .button = MBRight};
_bolt_plugin_handle_mousebutton(&event);
}
if (inputs.mouse_middle) {
struct MouseButtonEvent event = {.details = &inputs.mouse_middle_event, .button = MBMiddle};
_bolt_plugin_handle_mousebutton(&event);
}
if (inputs.mouse_left_up) {
struct MouseButtonEvent event = {.details = &inputs.mouse_left_up_event, .button = MBLeft};
_bolt_plugin_handle_mousebuttonup(&event);
}
if (inputs.mouse_right_up) {
struct MouseButtonEvent event = {.details = &inputs.mouse_right_up_event, .button = MBRight};
_bolt_plugin_handle_mousebuttonup(&event);
}
if (inputs.mouse_middle_up) {
struct MouseButtonEvent event = {.details = &inputs.mouse_middle_up_event, .button = MBMiddle};
_bolt_plugin_handle_mousebuttonup(&event);
}
if (inputs.mouse_scroll_up) {
struct MouseScrollEvent event = {.details = &inputs.mouse_scroll_up_event, .direction = 1};
_bolt_plugin_handle_scroll(&event);
}
if (inputs.mouse_scroll_down) {
struct MouseScrollEvent event = {.details = &inputs.mouse_scroll_down_event, .direction = 0};
_bolt_plugin_handle_scroll(&event);
}
_bolt_rwlock_lock_read(&windows->lock);
size_t iter = 0;
void* item;
while (hashmap_iter(windows->map, &iter, &item)) {
struct EmbeddedWindow* window = *(struct EmbeddedWindow**)item;
_bolt_rwlock_lock_write(&window->lock);
bool did_resize = false;
if (window->metadata.width > window_width) {
window->metadata.width = window_width;
did_resize = true;
}
if (window->metadata.height > window_height) {
window->metadata.height = window_height;
did_resize = true;
}
if (window->metadata.x < 0) {
window->metadata.x = 0;
}
if (window->metadata.y < 0) {
window->metadata.y = 0;
}
if (window->metadata.x + window->metadata.width > window_width) {
window->metadata.x = window_width - window->metadata.width;
}
if (window->metadata.y + window->metadata.height > window_height) {
window->metadata.y = window_height - window->metadata.height;
}
struct EmbeddedWindowMetadata metadata = window->metadata;
_bolt_rwlock_unlock_write(&window->lock);
_bolt_rwlock_lock_write(&window->input_lock);
struct WindowPendingInput inputs = window->input;
memset(&window->input, 0, sizeof(window->input));
_bolt_rwlock_unlock_write(&window->input_lock);
if (did_resize) {
struct PluginSurfaceUserdata* ud = window->surface_functions.userdata;
managed_functions.surface_resize_and_clear(ud, metadata.width, metadata.height);
struct ResizeEvent event = {.width = metadata.width, .height = metadata.height};
_bolt_plugin_window_onresize(window, &event);
}
if (inputs.mouse_motion) {
struct MouseMotionEvent event = {.details = &inputs.mouse_motion_event};
_bolt_plugin_window_onmousemotion(window, &event);
}
if (inputs.mouse_left) {
struct MouseButtonEvent event = {.details = &inputs.mouse_left_event, .button = MBLeft};
_bolt_plugin_window_onmousebutton(window, &event);
}
if (inputs.mouse_right) {
struct MouseButtonEvent event = {.details = &inputs.mouse_right_event, .button = MBRight};
_bolt_plugin_window_onmousebutton(window, &event);
}
if (inputs.mouse_middle) {
struct MouseButtonEvent event = {.details = &inputs.mouse_middle_event, .button = MBMiddle};
_bolt_plugin_window_onmousebutton(window, &event);
}
if (inputs.mouse_left_up) {
struct MouseButtonEvent event = {.details = &inputs.mouse_left_up_event, .button = MBLeft};
_bolt_plugin_window_onmousebuttonup(window, &event);
}
if (inputs.mouse_right_up) {
struct MouseButtonEvent event = {.details = &inputs.mouse_right_up_event, .button = MBRight};
_bolt_plugin_window_onmousebuttonup(window, &event);
}
if (inputs.mouse_middle_up) {
struct MouseButtonEvent event = {.details = &inputs.mouse_middle_up_event, .button = MBMiddle};
_bolt_plugin_window_onmousebuttonup(window, &event);
}
if (inputs.mouse_scroll_up) {
struct MouseScrollEvent event = {.details = &inputs.mouse_scroll_up_event, .direction = 1};
_bolt_plugin_window_onscroll(window, &event);
}
if (inputs.mouse_scroll_down) {
struct MouseScrollEvent event = {.details = &inputs.mouse_scroll_down_event, .direction = 0};
_bolt_plugin_window_onscroll(window, &event);
}
window->surface_functions.draw_to_screen(window->surface_functions.userdata, 0, 0, metadata.width, metadata.height, metadata.x, metadata.y, metadata.width, metadata.height);
}
_bolt_rwlock_unlock_read(&windows->lock);
}
void _bolt_plugin_close() {
_bolt_plugin_ipc_close(fd);
size_t iter = 0;
void* item;
while (hashmap_iter(plugins, &iter, &item)) {
struct Plugin** plugin = item;
_bolt_plugin_free(plugin);
}
_bolt_rwlock_lock_write(&windows.lock);
hashmap_free(plugins);
inited = 0;
_bolt_rwlock_unlock_write(&windows.lock);
}
struct WindowInfo* _bolt_plugin_windowinfo() {
return &windows;
}
void _bolt_plugin_handle_messages() {
struct BoltIPCMessageToHost message;
while (_bolt_ipc_poll(fd)) {
if (_bolt_ipc_receive(fd, &message, sizeof(message)) != 0) break;
switch (message.message_type) {
case IPC_MSG_STARTPLUGINS: {
// note: incoming messages are sanitised by the UI, by replacing `\` with `/` and
// making sure to leave a trailing slash, when initiating this type of message
// (see PluginMenu.svelte)
for (size_t i = 0; i < message.items; i += 1) {
struct Plugin* plugin = malloc(sizeof(struct Plugin));
plugin->state = luaL_newstate();
uint32_t main_length;
_bolt_ipc_receive(fd, &plugin->id_length, sizeof(uint32_t));
_bolt_ipc_receive(fd, &plugin->path_length, sizeof(uint32_t));
_bolt_ipc_receive(fd, &main_length, sizeof(uint32_t));
plugin->id = malloc(plugin->id_length);
plugin->path = malloc(plugin->path_length);
_bolt_ipc_receive(fd, plugin->id, plugin->id_length);
_bolt_ipc_receive(fd, plugin->path, plugin->path_length);
char* full_path = lua_newuserdata(plugin->state, plugin->path_length + main_length + 1);
memcpy(full_path, plugin->path, plugin->path_length);
_bolt_ipc_receive(fd, full_path + plugin->path_length, main_length);
full_path[plugin->path_length + main_length] = '\0';
if (_bolt_plugin_add(full_path, plugin)) {
lua_pop(plugin->state, 1);
} else {
_bolt_plugin_free(&plugin);
}
}
break;
}
default:
printf("unknown message type %u\n", message.message_type);
break;
}
}
}
uint8_t _bolt_plugin_add(const char* path, struct Plugin* plugin) {
// load the user-provided string as a lua function, putting that function on the stack
if (luaL_loadfile(plugin->state, path)) {
const char* e = lua_tolstring(plugin->state, -1, 0);
printf("plugin load error: %s\n", e);
lua_pop(plugin->state, 1);
return 0;
}
// put this into our list of plugins (important to do this before lua_pcall)
struct Plugin* const* old_plugin = hashmap_set(plugins, &plugin);
if (hashmap_oom(plugins)) {
printf("plugin load error: out of memory\n");
_bolt_plugin_free(&plugin);
return 0;
}
if (old_plugin) {
// a plugin with this id was already running and we just overwrote it, so make sure not to leak the memory
_bolt_plugin_free(old_plugin);
}
// add the struct pointer to the registry
PUSHSTRING(plugin->state, PLUGIN_REGISTRYNAME);
lua_pushlightuserdata(plugin->state, plugin);
lua_settable(plugin->state, LUA_REGISTRYINDEX);
// Open just the specific libraries plugins are allowed to have
lua_pushcfunction(plugin->state, luaopen_base);
lua_call(plugin->state, 0, 0);
lua_pushcfunction(plugin->state, luaopen_package);
lua_call(plugin->state, 0, 0);
lua_pushcfunction(plugin->state, luaopen_string);
lua_call(plugin->state, 0, 0);
lua_pushcfunction(plugin->state, luaopen_table);
lua_call(plugin->state, 0, 0);
lua_pushcfunction(plugin->state, luaopen_math);
lua_call(plugin->state, 0, 0);
// load Bolt API into package.preload, so that `require("bolt")` will find it
lua_getfield(plugin->state, LUA_GLOBALSINDEX, "package");
lua_getfield(plugin->state, -1, "preload");
PUSHSTRING(plugin->state, "bolt");
lua_pushcfunction(plugin->state, _bolt_api_init);
lua_settable(plugin->state, -3);
// now set package.path to the plugin's root path
char* search_path = lua_newuserdata(plugin->state, plugin->path_length + 5);
memcpy(search_path, plugin->path, plugin->path_length);
memcpy(&search_path[plugin->path_length], "?.lua", 5);
PUSHSTRING(plugin->state, "path");
lua_pushlstring(plugin->state, search_path, plugin->path_length + 5);
lua_settable(plugin->state, -5);
lua_pop(plugin->state, 2);
// finally, restrict package.loaders by removing the module searcher and all-in-one searcher,
// because these can load .dll and .so files which are a huge security concern, and also
// because stupid people will make windows-only plugins with it and I'm not dealing with that
lua_getfield(plugin->state, -1, "loaders");
lua_pushnil(plugin->state);
lua_pushnil(plugin->state);
lua_rawseti(plugin->state, -3, 3);
lua_rawseti(plugin->state, -2, 4);
lua_pop(plugin->state, 2);
// create window table (empty)
PUSHSTRING(plugin->state, WINDOWS_REGISTRYNAME);
lua_newtable(plugin->state);
lua_settable(plugin->state, LUA_REGISTRYINDEX);
// create the metatable for all RenderBatch2D objects
PUSHSTRING(plugin->state, BATCH2D_META_REGISTRYNAME);
lua_newtable(plugin->state);
PUSHSTRING(plugin->state, "__index");
lua_createtable(plugin->state, 0, 14);
API_ADD_SUB(plugin->state, vertexcount, batch2d)
API_ADD_SUB(plugin->state, verticesperimage, batch2d)
API_ADD_SUB(plugin->state, isminimap, batch2d)
API_ADD_SUB(plugin->state, targetsize, batch2d)
API_ADD_SUB(plugin->state, vertexxy, batch2d)
API_ADD_SUB(plugin->state, vertexatlasxy, batch2d)
API_ADD_SUB(plugin->state, vertexatlaswh, batch2d)
API_ADD_SUB(plugin->state, vertexuv, batch2d)
API_ADD_SUB(plugin->state, vertexcolour, batch2d)
API_ADD_SUB(plugin->state, textureid, batch2d)
API_ADD_SUB(plugin->state, texturesize, batch2d)
API_ADD_SUB(plugin->state, texturecompare, batch2d)
API_ADD_SUB(plugin->state, texturedata, batch2d)
API_ADD_SUB_ALIAS(plugin->state, vertexcolour, vertexcolor, batch2d)
lua_settable(plugin->state, -3);
lua_settable(plugin->state, LUA_REGISTRYINDEX);
// create the metatable for all Render3D objects
PUSHSTRING(plugin->state, RENDER3D_META_REGISTRYNAME);
lua_newtable(plugin->state);
PUSHSTRING(plugin->state, "__index");
lua_createtable(plugin->state, 0, 13);
API_ADD_SUB(plugin->state, vertexcount, render3d)
API_ADD_SUB(plugin->state, vertexxyz, render3d)
API_ADD_SUB(plugin->state, vertexmeta, render3d)
API_ADD_SUB(plugin->state, atlasxywh, render3d)
API_ADD_SUB(plugin->state, vertexuv, render3d)
API_ADD_SUB(plugin->state, vertexcolour, render3d)
API_ADD_SUB(plugin->state, textureid, render3d)
API_ADD_SUB(plugin->state, texturesize, render3d)
API_ADD_SUB(plugin->state, texturecompare, render3d)
API_ADD_SUB(plugin->state, texturedata, render3d)
API_ADD_SUB(plugin->state, toworldspace, render3d)
API_ADD_SUB(plugin->state, toscreenspace, render3d)
API_ADD_SUB(plugin->state, worldposition, render3d)
API_ADD_SUB_ALIAS(plugin->state, vertexcolour, vertexcolor, render3d)
lua_settable(plugin->state, -3);
lua_settable(plugin->state, LUA_REGISTRYINDEX);
// create the metatable for all RenderMinimap objects
PUSHSTRING(plugin->state, MINIMAP_META_REGISTRYNAME);
lua_newtable(plugin->state);
PUSHSTRING(plugin->state, "__index");
lua_createtable(plugin->state, 0, 3);
API_ADD_SUB(plugin->state, angle, minimap)
API_ADD_SUB(plugin->state, scale, minimap)
API_ADD_SUB(plugin->state, position, minimap)
lua_settable(plugin->state, -3);
lua_settable(plugin->state, LUA_REGISTRYINDEX);
// create the metatable for all SwapBuffers objects
PUSHSTRING(plugin->state, SWAPBUFFERS_META_REGISTRYNAME);
lua_newtable(plugin->state);
PUSHSTRING(plugin->state, "__index");
lua_createtable(plugin->state, 0, 0);
lua_settable(plugin->state, -3);
lua_settable(plugin->state, LUA_REGISTRYINDEX);
// create the metatable for all Surface objects
PUSHSTRING(plugin->state, SURFACE_META_REGISTRYNAME);
lua_newtable(plugin->state);
PUSHSTRING(plugin->state, "__index");
lua_createtable(plugin->state, 0, 4);
API_ADD_SUB(plugin->state, clear, surface)
API_ADD_SUB(plugin->state, drawtoscreen, surface)
API_ADD_SUB(plugin->state, drawtosurface, surface)
API_ADD_SUB(plugin->state, drawtowindow, surface)
lua_settable(plugin->state, -3);
PUSHSTRING(plugin->state, "__gc");
lua_pushcfunction(plugin->state, surface_gc);
lua_settable(plugin->state, -3);
lua_settable(plugin->state, LUA_REGISTRYINDEX);
// create both of the metatables for Window objects
PUSHSTRING(plugin->state, WINDOW_META_REGISTRYNAME);
lua_newtable(plugin->state);
PUSHSTRING(plugin->state, "__index");
lua_createtable(plugin->state, 0, 7);
API_ADD_SUB(plugin->state, id, window)
API_ADD_SUB(plugin->state, size, window)
API_ADD_SUB(plugin->state, clear, window)
API_ADD_SUB(plugin->state, onresize, window)
API_ADD_SUB(plugin->state, onmousemotion, window)
API_ADD_SUB(plugin->state, onmousebutton, window)
API_ADD_SUB(plugin->state, onmousebuttonup, window)
API_ADD_SUB(plugin->state, onscroll, window)
lua_settable(plugin->state, -3);
PUSHSTRING(plugin->state, "__gc");
lua_pushcfunction(plugin->state, window_gc);
lua_settable(plugin->state, -3);
lua_settable(plugin->state, LUA_REGISTRYINDEX);
// create the metatable for all ResizeEvent objects
PUSHSTRING(plugin->state, RESIZE_META_REGISTRYNAME);
lua_newtable(plugin->state);
PUSHSTRING(plugin->state, "__index");
lua_createtable(plugin->state, 0, 1);
API_ADD_SUB(plugin->state, size, resizeevent)
lua_settable(plugin->state, -3);
lua_settable(plugin->state, LUA_REGISTRYINDEX);
// create the metatable for all MouseMotionEvent objects
PUSHSTRING(plugin->state, MOUSEMOTION_META_REGISTRYNAME);
lua_newtable(plugin->state);
PUSHSTRING(plugin->state, "__index");
lua_createtable(plugin->state, 0, 7);
API_ADD_SUB(plugin->state, xy, mouseevent)
API_ADD_SUB(plugin->state, ctrl, mouseevent);
API_ADD_SUB(plugin->state, shift, mouseevent);
API_ADD_SUB(plugin->state, meta, mouseevent);
API_ADD_SUB(plugin->state, alt, mouseevent);
API_ADD_SUB(plugin->state, capslock, mouseevent);
API_ADD_SUB(plugin->state, numlock, mouseevent);
API_ADD_SUB(plugin->state, mousebuttons, mouseevent);
lua_settable(plugin->state, -3);
lua_settable(plugin->state, LUA_REGISTRYINDEX);
// create the metatable for all MouseButtonEvent objects
PUSHSTRING(plugin->state, MOUSEBUTTON_META_REGISTRYNAME);
lua_newtable(plugin->state);
PUSHSTRING(plugin->state, "__index");
lua_createtable(plugin->state, 0, 8);
API_ADD_SUB(plugin->state, xy, mouseevent)
API_ADD_SUB(plugin->state, ctrl, mouseevent);
API_ADD_SUB(plugin->state, shift, mouseevent);
API_ADD_SUB(plugin->state, meta, mouseevent);
API_ADD_SUB(plugin->state, alt, mouseevent);
API_ADD_SUB(plugin->state, capslock, mouseevent);
API_ADD_SUB(plugin->state, numlock, mouseevent);
API_ADD_SUB(plugin->state, mousebuttons, mouseevent);
API_ADD_SUB(plugin->state, button, mousebutton);
lua_settable(plugin->state, -3);
lua_settable(plugin->state, LUA_REGISTRYINDEX);
// create the metatable for all MouseScrollEvent objects
PUSHSTRING(plugin->state, SCROLL_META_REGISTRYNAME);
lua_newtable(plugin->state);
PUSHSTRING(plugin->state, "__index");
lua_createtable(plugin->state, 0, 8);
API_ADD_SUB(plugin->state, xy, mouseevent)
API_ADD_SUB(plugin->state, ctrl, mouseevent);
API_ADD_SUB(plugin->state, shift, mouseevent);
API_ADD_SUB(plugin->state, meta, mouseevent);
API_ADD_SUB(plugin->state, alt, mouseevent);
API_ADD_SUB(plugin->state, capslock, mouseevent);
API_ADD_SUB(plugin->state, numlock, mouseevent);
API_ADD_SUB(plugin->state, mousebuttons, mouseevent);
API_ADD_SUB(plugin->state, direction, scroll);
lua_settable(plugin->state, -3);
lua_settable(plugin->state, LUA_REGISTRYINDEX);
// attempt to run the function
if (lua_pcall(plugin->state, 0, 0, 0)) {
const char* e = lua_tolstring(plugin->state, -1, 0);
printf("plugin startup error: %s\n", e);
lua_pop(plugin->state, 1);
hashmap_delete(plugins, &plugin);
return 0;
} else {
return 1;
}
}
void _bolt_plugin_stop(char* id, uint32_t id_length) {
struct Plugin p = {.id = id, .id_length = id_length};
struct Plugin* pp = &p;
struct Plugin* const* plugin = hashmap_delete(plugins, &pp);
_bolt_plugin_free(plugin);
}
// Calls `error()` if arg count is incorrect
static void _bolt_check_argc(lua_State* state, int expected_argc, const char* function_name) {
char error_buffer[256];
const int argc = lua_gettop(state);
if (argc != expected_argc) {
SNPUSHSTRING(state, "incorrect argument count to '%s': expected %i, got %i", function_name, expected_argc, argc);
lua_error(state);
}
}
DEFINE_CALLBACK(swapbuffers, SWAPBUFFERS, SwapBuffersEvent)
DEFINE_CALLBACK(2d, BATCH2D, RenderBatch2D)
DEFINE_CALLBACK(3d, RENDER3D, Render3D)
DEFINE_CALLBACK(minimap, MINIMAP, RenderMinimapEvent)
DEFINE_CALLBACK(mousemotion, MOUSEMOTION, MouseMotionEvent)
DEFINE_CALLBACK(mousebutton, MOUSEBUTTON, MouseButtonEvent)
DEFINE_CALLBACK(mousebuttonup, MOUSEBUTTONUP, MouseButtonEvent)
DEFINE_CALLBACK(scroll, SCROLL, MouseScrollEvent)
DEFINE_WINDOWEVENT(resize, RESIZE, ResizeEvent)
DEFINE_WINDOWEVENT(mousemotion, MOUSEMOTION, MouseMotionEvent)
DEFINE_WINDOWEVENT(mousebutton, MOUSEBUTTON, MouseButtonEvent)
DEFINE_WINDOWEVENT(mousebuttonup, MOUSEBUTTONUP, MouseButtonEvent)
DEFINE_WINDOWEVENT(scroll, SCROLL, MouseScrollEvent)
static int api_apiversion(lua_State* state) {
_bolt_check_argc(state, 0, "apiversion");
lua_pushnumber(state, API_VERSION_MAJOR);
lua_pushnumber(state, API_VERSION_MINOR);
return 2;
}
static int api_checkversion(lua_State* state) {
_bolt_check_argc(state, 2, "checkversion");
char error_buffer[256];
lua_Integer expected_major = lua_tonumber(state, 1);
lua_Integer expected_minor = lua_tonumber(state, 2);
if (expected_major != API_VERSION_MAJOR) {
SNPUSHSTRING(state, error_buffer, "checkversion major version mismatch: major version is %u, plugin expects %u", API_VERSION_MAJOR, (unsigned int)expected_major);
lua_error(state);
}
if (expected_minor > API_VERSION_MINOR) {
SNPUSHSTRING(state, error_buffer, "checkversion minor version mismatch: minor version is %u, plugin expects at least %u", API_VERSION_MINOR, (unsigned int)expected_minor);
lua_error(state);
}
return 2;
}
static int api_time(lua_State* state) {
_bolt_check_argc(state, 0, "time");
#if defined(_WIN32)
LARGE_INTEGER ticks;
if (QueryPerformanceCounter(&ticks)) {
const uint64_t microseconds = (ticks.QuadPart * 1000000) / performance_frequency.QuadPart;
lua_pushinteger(state, microseconds);
} else {
lua_pushnil(state);
}
#else
struct timespec s;
clock_gettime(CLOCK_MONOTONIC_RAW, &s);
const uint64_t microseconds = (s.tv_sec * 1000000) + (s.tv_nsec / 1000);
lua_pushinteger(state, microseconds);
#endif
return 1;
}
static int api_datetime(lua_State* state) {
_bolt_check_argc(state, 0, "datetime");
const time_t t = time(NULL);
const struct tm* time = gmtime(&t);
lua_pushinteger(state, time->tm_year + 1900);
lua_pushinteger(state, time->tm_mon + 1);
lua_pushinteger(state, time->tm_mday);
lua_pushinteger(state, time->tm_hour);
lua_pushinteger(state, time->tm_min);
lua_pushinteger(state, time->tm_sec);
return 6;
}
static int api_weekday(lua_State* state) {
_bolt_check_argc(state, 0, "weekday");
const time_t t = time(NULL);
const struct tm* time = gmtime(&t);
lua_pushinteger(state, time->tm_wday + 1);
return 1;
}
static int api_createsurface(lua_State* state) {
_bolt_check_argc(state, 2, "createsurface");
const lua_Integer w = lua_tointeger(state, 1);
const lua_Integer h = lua_tointeger(state, 2);
struct SurfaceFunctions* functions = lua_newuserdata(state, sizeof(struct SurfaceFunctions));
managed_functions.surface_init(functions, w, h, NULL);
lua_getfield(state, LUA_REGISTRYINDEX, SURFACE_META_REGISTRYNAME);
lua_setmetatable(state, -2);
return 1;
}
static int api_createsurfacefromrgba(lua_State* state) {
_bolt_check_argc(state, 3, "createsurfacefromrgba");
const lua_Integer w = lua_tointeger(state, 1);
const lua_Integer h = lua_tointeger(state, 2);
const size_t req_length = w * h * 4;
size_t length;
const void* rgba = lua_tolstring(state, 3, &length);
struct SurfaceFunctions* functions = lua_newuserdata(state, sizeof(struct SurfaceFunctions));
if (length >= req_length) {
managed_functions.surface_init(functions, w, h, rgba);
} else {
uint8_t* ud = lua_newuserdata(state, req_length);
memcpy(ud, rgba, length);
memset(ud + length, 0, req_length - length);
managed_functions.surface_init(functions, w, h, ud);
lua_pop(state, 1);
}
lua_getfield(state, LUA_REGISTRYINDEX, SURFACE_META_REGISTRYNAME);
lua_setmetatable(state, -2);
return 1;
}
static int api_createsurfacefrompng(lua_State* state) {
_bolt_check_argc(state, 1, "createsurfacefrompng");
const char extension[] = ".png";
size_t rgba_size, path_length;
const char* path = lua_tolstring(state, 1, &path_length);
lua_getfield(state, LUA_REGISTRYINDEX, PLUGIN_REGISTRYNAME);
const struct Plugin* plugin = lua_touserdata(state, -1);
const size_t full_path_length = plugin->path_length + path_length + sizeof(extension);
char* full_path = lua_newuserdata(state, full_path_length);
memcpy(full_path, plugin->path, plugin->path_length);
memcpy(full_path + plugin->path_length, path, path_length + 1);
for (char* c = full_path + plugin->path_length; *c; c += 1) {
if (*c == '.') *c = '/';
}
memcpy(full_path + plugin->path_length + path_length, extension, sizeof(extension));
FILE* f = fopen(full_path, "rb");
if (!f) {
char error_buffer[65536];
SNPUSHSTRING(state, error_buffer, "createsurfacefrompng: error opening file '%s'", (char*)full_path);
lua_error(state);
}
fseek(f, 0, SEEK_END);
const long png_size = ftell(f);
fseek(f, 0, SEEK_SET);
void* png = lua_newuserdata(state, png_size);
if (fread(png, 1, png_size, f) < png_size) {
char error_buffer[65536];
fclose(f);
SNPUSHSTRING(state, error_buffer, "createsurfacefrompng: error reading file '%s'", (char*)full_path);
lua_error(state);
}
fclose(f);
#define CALL_SPNG(FUNC, ...) err = FUNC(__VA_ARGS__); if(err){char b[65536];free(rgba);SNPUSHSTRING(state,b,"createsurfacefrompng: error decoding file '%s': " #FUNC " returned %i",(char*)full_path,err);lua_error(state);}
void* rgba = NULL;
int err;
spng_ctx* spng = spng_ctx_new(0);
CALL_SPNG(spng_set_png_buffer, spng, png, png_size);
struct spng_ihdr ihdr;
CALL_SPNG(spng_get_ihdr, spng, &ihdr)
CALL_SPNG(spng_decoded_image_size, spng, SPNG_FMT_RGBA8, &rgba_size)
rgba = malloc(rgba_size);
CALL_SPNG(spng_decode_image, spng, rgba, rgba_size, SPNG_FMT_RGBA8, 0)
spng_ctx_free(spng);
lua_pop(state, 3);
#undef CALL_SPNG
lua_pushinteger(state, ihdr.width);
lua_pushinteger(state, ihdr.height);
struct SurfaceFunctions* functions = lua_newuserdata(state, sizeof(struct SurfaceFunctions));
managed_functions.surface_init(functions, ihdr.width, ihdr.height, rgba);
lua_getfield(state, LUA_REGISTRYINDEX, SURFACE_META_REGISTRYNAME);
lua_setmetatable(state, -2);
free(rgba);
return 3;
}
static int api_createwindow(lua_State* state) {
_bolt_check_argc(state, 4, "createwindow");
// push a window onto the stack as the return value, then initialise it
struct EmbeddedWindow* window = lua_newuserdata(state, sizeof(struct EmbeddedWindow));
window->id = next_window_id;
window->plugin = state;
_bolt_rwlock_init(&window->lock);
_bolt_rwlock_init(&window->input_lock);
window->metadata.x = lua_tointeger(state, 1);
window->metadata.y = lua_tointeger(state, 2);
window->metadata.width = lua_tointeger(state, 3);
window->metadata.height = lua_tointeger(state, 4);
memset(&window->input, 0, sizeof(window->input));
managed_functions.surface_init(&window->surface_functions, window->metadata.width, window->metadata.height, NULL);
lua_getfield(state, LUA_REGISTRYINDEX, WINDOW_META_REGISTRYNAME);
lua_setmetatable(state, -2);
next_window_id += 1;
// create an empty event table in the registry for this window
lua_getfield(state, LUA_REGISTRYINDEX, WINDOWS_REGISTRYNAME);
lua_pushinteger(state, window->id);
lua_createtable(state, WINDOW_EVENT_ENUM_SIZE, 0);
lua_settable(state, -3);
lua_pop(state, 1);
// set this window in the hashmap, which is accessible by backends
_bolt_rwlock_lock_write(&windows.lock);
hashmap_set(windows.map, &window);
_bolt_rwlock_unlock_write(&windows.lock);
return 1;
}
static int api_batch2d_vertexcount(lua_State* state) {
_bolt_check_argc(state, 1, "batch2d_vertexcount");
struct RenderBatch2D* batch = lua_touserdata(state, 1);
lua_pushinteger(state, batch->index_count);
return 1;
}
static int api_batch2d_verticesperimage(lua_State* state) {
_bolt_check_argc(state, 1, "batch2d_verticesperimage");
struct RenderBatch2D* batch = lua_touserdata(state, 1);
lua_pushinteger(state, batch->vertices_per_icon);
return 1;
}
static int api_batch2d_isminimap(lua_State* state) {
_bolt_check_argc(state, 1, "batch2d_isminimap");
struct RenderBatch2D* batch = lua_touserdata(state, 1);
lua_pushboolean(state, batch->is_minimap);
return 1;
}
static int api_batch2d_targetsize(lua_State* state) {
_bolt_check_argc(state, 1, "batch2d_targetsize");
struct RenderBatch2D* batch = lua_touserdata(state, 1);
lua_pushinteger(state, batch->screen_width);
lua_pushinteger(state, batch->screen_height);
return 2;
}
static int api_batch2d_vertexxy(lua_State* state) {
_bolt_check_argc(state, 2, "batch2d_vertexxy");
struct RenderBatch2D* batch = lua_touserdata(state, 1);
const lua_Integer index = lua_tointeger(state, 2);
int32_t xy[2];
batch->vertex_functions.xy(index - 1, batch->vertex_functions.userdata, xy);
lua_pushinteger(state, xy[0]);
lua_pushinteger(state, xy[1]);
return 2;
}
static int api_batch2d_vertexatlasxy(lua_State* state) {
_bolt_check_argc(state, 2, "batch2d_vertexatlasxy");
struct RenderBatch2D* batch = lua_touserdata(state, 1);
const lua_Integer index = lua_tointeger(state, 2);
int32_t xy[2];
batch->vertex_functions.atlas_xy(index - 1, batch->vertex_functions.userdata, xy);
lua_pushinteger(state, xy[0]);
lua_pushinteger(state, xy[1]);
return 2;
}
static int api_batch2d_vertexatlaswh(lua_State* state) {
_bolt_check_argc(state, 2, "batch2d_vertexatlaswh");
struct RenderBatch2D* batch = lua_touserdata(state, 1);
const lua_Integer index = lua_tointeger(state, 2);
int32_t wh[2];
batch->vertex_functions.atlas_wh(index - 1, batch->vertex_functions.userdata, wh);
lua_pushinteger(state, wh[0]);
lua_pushinteger(state, wh[1]);
return 2;
}
static int api_batch2d_vertexuv(lua_State* state) {
_bolt_check_argc(state, 2, "batch2d_vertexuv");
struct RenderBatch2D* batch = lua_touserdata(state, 1);
const lua_Integer index = lua_tointeger(state, 2);
double uv[2];
batch->vertex_functions.uv(index - 1, batch->vertex_functions.userdata, uv);
lua_pushnumber(state, uv[0]);
lua_pushnumber(state, uv[1]);
return 2;
}
static int api_batch2d_vertexcolour(lua_State* state) {
_bolt_check_argc(state, 4, "batch2d_vertexcolour");
struct RenderBatch2D* batch = lua_touserdata(state, 1);
const lua_Integer index = lua_tointeger(state, 2);
double colour[4];
batch->vertex_functions.colour(index - 1, batch->vertex_functions.userdata, colour);
lua_pushnumber(state, colour[0]);
lua_pushnumber(state, colour[1]);
lua_pushnumber(state, colour[2]);
lua_pushnumber(state, colour[3]);
return 4;
}
static int api_batch2d_textureid(lua_State* state) {
_bolt_check_argc(state, 1, "batch2d_textureid");
struct RenderBatch2D* render = lua_touserdata(state, 1);
const size_t id = render->texture_functions.id(render->texture_functions.userdata);
lua_pushinteger(state, id);
return 1;
}
static int api_batch2d_texturesize(lua_State* state) {
_bolt_check_argc(state, 1, "batch2d_texturesize");
struct RenderBatch2D* render = lua_touserdata(state, 1);
size_t size[2];
render->texture_functions.size(render->texture_functions.userdata, size);
lua_pushinteger(state, size[0]);
lua_pushinteger(state, size[1]);
return 2;
}
static int api_batch2d_texturecompare(lua_State* state) {
_bolt_check_argc(state, 4, "batch2d_texturecompare");
struct RenderBatch2D* render = lua_touserdata(state, 1);
const size_t x = lua_tointeger(state, 2);
const size_t y = lua_tointeger(state, 3);
size_t data_len;
const unsigned char* data = (const unsigned char*)lua_tolstring(state, 4, &data_len);
const uint8_t match = render->texture_functions.compare(render->texture_functions.userdata, x, y, data_len, data);
lua_pushboolean(state, match);
return 1;
}
static int api_batch2d_texturedata(lua_State* state) {
_bolt_check_argc(state, 4, "batch2d_texturedata");
struct RenderBatch2D* render = lua_touserdata(state, 1);
const size_t x = lua_tointeger(state, 2);
const size_t y = lua_tointeger(state, 3);
const size_t len = lua_tointeger(state, 4);
const uint8_t* ret = render->texture_functions.data(render->texture_functions.userdata, x, y);
lua_pushlstring(state, (const char*)ret, len);
return 1;
}
static int api_minimap_angle(lua_State* state) {
_bolt_check_argc(state, 1, "minimap_angle");
struct RenderMinimapEvent* render = lua_touserdata(state, 1);
lua_pushnumber(state, render->angle);
return 1;
}
static int api_minimap_scale(lua_State* state) {
_bolt_check_argc(state, 1, "minimap_scale");
struct RenderMinimapEvent* render = lua_touserdata(state, 1);
lua_pushnumber(state, render->scale);
return 1;
}
static int api_minimap_position(lua_State* state) {
_bolt_check_argc(state, 1, "minimap_position");
struct RenderMinimapEvent* render = lua_touserdata(state, 1);
lua_pushnumber(state, render->x);
lua_pushnumber(state, render->y);
return 2;
}
static int api_surface_clear(lua_State* state) {
char error_buffer[256];
const int argc = lua_gettop(state);
switch (argc) {
case 1: {
const struct SurfaceFunctions* functions = lua_touserdata(state, 1);
functions->clear(functions->userdata, 0.0, 0.0, 0.0, 0.0);
break;
}
case 4: {
const struct SurfaceFunctions* functions = lua_touserdata(state, 1);
const double r = lua_tonumber(state, 2);
const double g = lua_tonumber(state, 3);
const double b = lua_tonumber(state, 4);
functions->clear(functions->userdata, r, g, b, 1.0);
break;
}
case 5: {
const struct SurfaceFunctions* functions = lua_touserdata(state, 1);
const double r = lua_tonumber(state, 2);
const double g = lua_tonumber(state, 3);
const double b = lua_tonumber(state, 4);
const double a = lua_tonumber(state, 5);
functions->clear(functions->userdata, r, g, b, a);
break;
}
default: {
SNPUSHSTRING(state, error_buffer, "incorrect argument count to 'surface_clear': expected 1, 4, or 5, but got %i", argc);
lua_error(state);
}
}
return 0;
}
static int api_surface_drawtoscreen(lua_State* state) {
_bolt_check_argc(state, 9, "surface_drawtoscreen");
const struct SurfaceFunctions* functions = lua_touserdata(state, 1);
const int sx = lua_tointeger(state, 2);
const int sy = lua_tointeger(state, 3);
const int sw = lua_tointeger(state, 4);
const int sh = lua_tointeger(state, 5);
const int dx = lua_tointeger(state, 6);
const int dy = lua_tointeger(state, 7);
const int dw = lua_tointeger(state, 8);
const int dh = lua_tointeger(state, 9);
functions->draw_to_screen(functions->userdata, sx, sy, sw, sh, dx, dy, dw, dh);
return 0;
}
static int api_surface_drawtosurface(lua_State* state) {
_bolt_check_argc(state, 10, "surface_drawtosurface");
const struct SurfaceFunctions* functions = lua_touserdata(state, 1);
const struct SurfaceFunctions* target = lua_touserdata(state, 2);
const int sx = lua_tointeger(state, 3);
const int sy = lua_tointeger(state, 4);
const int sw = lua_tointeger(state, 5);
const int sh = lua_tointeger(state, 6);
const int dx = lua_tointeger(state, 7);
const int dy = lua_tointeger(state, 8);
const int dw = lua_tointeger(state, 9);
const int dh = lua_tointeger(state, 10);
functions->draw_to_surface(functions->userdata, target->userdata, sx, sy, sw, sh, dx, dy, dw, dh);
return 0;
}
static int api_surface_drawtowindow(lua_State* state) {
_bolt_check_argc(state, 10, "surface_drawtowindow");
const struct SurfaceFunctions* functions = lua_touserdata(state, 1);
const struct EmbeddedWindow* target = lua_touserdata(state, 2);
const int sx = lua_tointeger(state, 3);
const int sy = lua_tointeger(state, 4);
const int sw = lua_tointeger(state, 5);
const int sh = lua_tointeger(state, 6);
const int dx = lua_tointeger(state, 7);
const int dy = lua_tointeger(state, 8);
const int dw = lua_tointeger(state, 9);
const int dh = lua_tointeger(state, 10);
functions->draw_to_surface(functions->userdata, target->surface_functions.userdata, sx, sy, sw, sh, dx, dy, dw, dh);
return 0;
}
static int api_window_clear(lua_State* state) {
char error_buffer[256];
const int argc = lua_gettop(state);
switch (argc) {
case 1: {
const struct EmbeddedWindow* window = lua_touserdata(state, 1);
window->surface_functions.clear(window->surface_functions.userdata, 0.0, 0.0, 0.0, 0.0);
break;
}
case 4: {
const struct EmbeddedWindow* window = lua_touserdata(state, 1);
const double r = lua_tonumber(state, 2);
const double g = lua_tonumber(state, 3);
const double b = lua_tonumber(state, 4);
window->surface_functions.clear(window->surface_functions.userdata, r, g, b, 1.0);
break;
}
case 5: {
const struct EmbeddedWindow* window = lua_touserdata(state, 1);
const double r = lua_tonumber(state, 2);
const double g = lua_tonumber(state, 3);
const double b = lua_tonumber(state, 4);
const double a = lua_tonumber(state, 5);
window->surface_functions.clear(window->surface_functions.userdata, r, g, b, a);
break;
}
default: {
SNPUSHSTRING(state, error_buffer, "incorrect argument count to 'window_clear': expected 1, 4, or 5, but got %i", argc);
lua_error(state);
}
}
return 0;
}
static int api_window_id(lua_State* state) {
_bolt_check_argc(state, 1, "window_id");
const struct EmbeddedWindow* window = lua_touserdata(state, 1);
lua_pushinteger(state, window->id);
return 1;
}
static int api_window_size(lua_State* state) {
_bolt_check_argc(state, 1, "window_size");
struct EmbeddedWindow* window = lua_touserdata(state, 1);
_bolt_rwlock_lock_read(&window->lock);
lua_pushinteger(state, window->metadata.width);
lua_pushinteger(state, window->metadata.height);
_bolt_rwlock_unlock_read(&window->lock);
return 2;
}
static int api_render3d_vertexcount(lua_State* state) {
_bolt_check_argc(state, 1, "render3d_vertexcount");
const struct Render3D* render = lua_touserdata(state, 1);
lua_pushinteger(state, render->vertex_count);
return 1;
}
static int api_render3d_vertexxyz(lua_State* state) {
_bolt_check_argc(state, 2, "render3d_vertexxyz");
const struct Render3D* render = lua_touserdata(state, 1);
const lua_Integer index = lua_tointeger(state, 2);
int32_t xyz[3];
render->vertex_functions.xyz(index - 1, render->vertex_functions.userdata, xyz);
lua_pushinteger(state, xyz[0]);
lua_pushinteger(state, xyz[1]);
lua_pushinteger(state, xyz[2]);
return 3;
}
static int api_render3d_vertexmeta(lua_State* state) {
_bolt_check_argc(state, 2, "render3d_vertexmeta");
const struct Render3D* render = lua_touserdata(state, 1);
const lua_Integer index = lua_tointeger(state, 2);
size_t meta = render->vertex_functions.atlas_meta(index - 1, render->vertex_functions.userdata);
lua_pushinteger(state, meta);
return 1;
}
static int api_render3d_atlasxywh(lua_State* state) {
_bolt_check_argc(state, 2, "render3d_atlasxywh");
const struct Render3D* render = lua_touserdata(state, 1);
const lua_Integer meta = lua_tointeger(state, 2);
int32_t xywh[4];
render->vertex_functions.atlas_xywh(meta, render->vertex_functions.userdata, xywh);
lua_pushinteger(state, xywh[0]);
lua_pushinteger(state, xywh[1]);
lua_pushinteger(state, xywh[2]);
lua_pushinteger(state, xywh[3]);
return 4;
}
static int api_render3d_vertexuv(lua_State* state) {
_bolt_check_argc(state, 2, "render3d_vertexuv");
const struct Render3D* render = lua_touserdata(state, 1);
const lua_Integer index = lua_tointeger(state, 2);
double uv[4];
render->vertex_functions.uv(index, render->vertex_functions.userdata, uv);
lua_pushnumber(state, uv[0]);
lua_pushnumber(state, uv[1]);
return 2;
}
static int api_render3d_vertexcolour(lua_State* state) {
_bolt_check_argc(state, 2, "render3d_vertexcolour");
const struct Render3D* render = lua_touserdata(state, 1);
const lua_Integer index = lua_tointeger(state, 2);
double col[4];
render->vertex_functions.colour(index, render->vertex_functions.userdata, col);
lua_pushnumber(state, col[0]);
lua_pushnumber(state, col[1]);
lua_pushnumber(state, col[2]);
lua_pushnumber(state, col[3]);
return 2;
}
static int api_render3d_textureid(lua_State* state) {
_bolt_check_argc(state, 1, "render3d_textureid");
struct Render3D* render = lua_touserdata(state, 1);
const size_t id = render->texture_functions.id(render->texture_functions.userdata);
lua_pushinteger(state, id);
return 1;
}
static int api_render3d_texturesize(lua_State* state) {
_bolt_check_argc(state, 1, "render3d_texturesize");
struct Render3D* render = lua_touserdata(state, 1);
size_t size[2];
render->texture_functions.size(render->texture_functions.userdata, size);
lua_pushinteger(state, size[0]);
lua_pushinteger(state, size[1]);
return 2;
}
static int api_render3d_texturecompare(lua_State* state) {
_bolt_check_argc(state, 4, "render3d_texturecompare");
struct Render3D* render = lua_touserdata(state, 1);
const size_t x = lua_tointeger(state, 2);
const size_t y = lua_tointeger(state, 3);
size_t data_len;
const unsigned char* data = (const unsigned char*)lua_tolstring(state, 4, &data_len);
const uint8_t match = render->texture_functions.compare(render->texture_functions.userdata, x, y, data_len, data);
lua_pushboolean(state, match);
return 1;
}
static int api_render3d_texturedata(lua_State* state) {
_bolt_check_argc(state, 4, "render3d_texturedata");
struct Render3D* render = lua_touserdata(state, 1);
const size_t x = lua_tointeger(state, 2);
const size_t y = lua_tointeger(state, 3);
const size_t len = lua_tointeger(state, 4);
const uint8_t* ret = render->texture_functions.data(render->texture_functions.userdata, x, y);
lua_pushlstring(state, (const char*)ret, len);
return 1;
}
static int api_render3d_toworldspace(lua_State* state) {
_bolt_check_argc(state, 4, "render3d_toworldspace");
struct Render3D* render = lua_touserdata(state, 1);
const int x = lua_tointeger(state, 2);
const int y = lua_tointeger(state, 3);
const int z = lua_tointeger(state, 4);
double out[3];
render->matrix_functions.to_world_space(x, y, z, render->matrix_functions.userdata, out);
lua_pushnumber(state, out[0]);
lua_pushnumber(state, out[1]);
lua_pushnumber(state, out[2]);
return 3;
}
static int api_render3d_toscreenspace(lua_State* state) {
_bolt_check_argc(state, 4, "render3d_toscreenspace");
struct Render3D* render = lua_touserdata(state, 1);
const int x = lua_tointeger(state, 2);
const int y = lua_tointeger(state, 3);
const int z = lua_tointeger(state, 4);
double out[2];
render->matrix_functions.to_screen_space(x, y, z, render->matrix_functions.userdata, out);
lua_pushnumber(state, out[0]);
lua_pushnumber(state, out[1]);
return 2;
}
static int api_render3d_worldposition(lua_State* state) {
_bolt_check_argc(state, 1, "render3d_worldposition");
struct Render3D* render = lua_touserdata(state, 1);
double out[3];
render->matrix_functions.world_pos(render->matrix_functions.userdata, out);
lua_pushnumber(state, out[0]);
lua_pushnumber(state, out[1]);
lua_pushnumber(state, out[2]);
return 3;
}
static int api_resizeevent_size(lua_State* state) {
_bolt_check_argc(state, 1, "resizeevent_size");
struct ResizeEvent* event = lua_touserdata(state, 1);
lua_pushinteger(state, event->width);
lua_pushinteger(state, event->height);
return 2;
}
static int api_mouseevent_xy(lua_State* state) {
_bolt_check_argc(state, 1, "mouseevent_xy");
struct MouseEvent** event = lua_touserdata(state, 1);
lua_pushinteger(state, (*event)->x);
lua_pushinteger(state, (*event)->y);
return 2;
}
static int api_mouseevent_ctrl(lua_State* state) {
_bolt_check_argc(state, 1, "mouseevent_ctrl");
struct MouseEvent** event = lua_touserdata(state, 1);
lua_pushinteger(state, (*event)->ctrl);
return 1;
}
static int api_mouseevent_shift(lua_State* state) {
_bolt_check_argc(state, 1, "mouseevent_shift");
struct MouseEvent** event = lua_touserdata(state, 1);
lua_pushinteger(state, (*event)->shift);
return 1;
}
static int api_mouseevent_meta(lua_State* state) {
_bolt_check_argc(state, 1, "mouseevent_meta");
struct MouseEvent** event = lua_touserdata(state, 1);
lua_pushinteger(state, (*event)->meta);
return 1;
}
static int api_mouseevent_alt(lua_State* state) {
_bolt_check_argc(state, 1, "mouseevent_alt");
struct MouseEvent** event = lua_touserdata(state, 1);
lua_pushinteger(state, (*event)->alt);
return 1;
}
static int api_mouseevent_capslock(lua_State* state) {
_bolt_check_argc(state, 1, "mouseevent_capslock");
struct MouseEvent** event = lua_touserdata(state, 1);
lua_pushinteger(state, (*event)->capslock);
return 1;
}
static int api_mouseevent_numlock(lua_State* state) {
_bolt_check_argc(state, 1, "mouseevent_numlock");
struct MouseEvent** event = lua_touserdata(state, 1);
lua_pushinteger(state, (*event)->numlock);
return 1;
}
static int api_mouseevent_mousebuttons(lua_State* state) {
_bolt_check_argc(state, 1, "mouseevent_mousebuttons");
struct MouseEvent** event = lua_touserdata(state, 1);
lua_pushinteger(state, (*event)->mb_left);
lua_pushinteger(state, (*event)->mb_right);
lua_pushinteger(state, (*event)->mb_middle);
return 3;
}
static int api_mousebutton_button(lua_State* state) {
_bolt_check_argc(state, 1, "mousebutton_button");
struct MouseButtonEvent* event = lua_touserdata(state, 1);
lua_pushinteger(state, event->button);
return 1;
}
static int api_scroll_direction(lua_State* state) {
_bolt_check_argc(state, 1, "scroll_direction");
struct MouseScrollEvent* event = lua_touserdata(state, 1);
lua_pushinteger(state, event->direction);
return 1;
}