lovr/src/api/api.c

596 lines
16 KiB
C

#include "api.h"
#include "util.h"
#include "lib/lua/lutf8lib.h"
#include <lua.h>
#include <lauxlib.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#ifndef LOVR_DISABLE_GRAPHICS
#include "data/modelData.h"
#include "graphics/graphics.h"
#endif
typedef void voidFn(void);
typedef void destructorFn(void*);
#ifdef _WIN32
#define LOVR_EXPORT __declspec(dllexport)
#else
#define LOVR_EXPORT __attribute__((visibility("default")))
#endif
LOVR_EXPORT int luaopen_lovr(lua_State* L);
LOVR_EXPORT int luaopen_lovr_audio(lua_State* L);
LOVR_EXPORT int luaopen_lovr_data(lua_State* L);
LOVR_EXPORT int luaopen_lovr_event(lua_State* L);
LOVR_EXPORT int luaopen_lovr_filesystem(lua_State* L);
LOVR_EXPORT int luaopen_lovr_graphics(lua_State* L);
LOVR_EXPORT int luaopen_lovr_headset(lua_State* L);
LOVR_EXPORT int luaopen_lovr_math(lua_State* L);
LOVR_EXPORT int luaopen_lovr_physics(lua_State* L);
LOVR_EXPORT int luaopen_lovr_system(lua_State* L);
LOVR_EXPORT int luaopen_lovr_thread(lua_State* L);
LOVR_EXPORT int luaopen_lovr_timer(lua_State* L);
// Object names are lightuserdata because Variants need a non-Lua string due to threads.
static int luax_meta__tostring(lua_State* L) {
lua_getfield(L, -1, "__info");
TypeInfo* info = lua_touserdata(L, -1);
lua_pushstring(L, info->name);
return 1;
}
// Currently the same as tostring
static int luax_type(lua_State* L) {
lua_getfield(L, 1, "__info");
TypeInfo* info = lua_touserdata(L, -1);
lua_pushstring(L, info->name);
return 1;
}
static int luax_meta__gc(lua_State* L) {
Proxy* p = lua_touserdata(L, 1);
if (p) {
// Remove from userdata cache
lua_getfield(L, LUA_REGISTRYINDEX, "_lovrobjects");
if (lua_istable(L, -1)) {
lua_pushlightuserdata(L, p->object);
lua_pushnil(L);
lua_rawset(L, -3);
}
lua_pop(L, 1);
// Release
lua_getmetatable(L, 1);
lua_getfield(L, -1, "__info");
TypeInfo* info = lua_touserdata(L, -1);
if (info->destructor) {
lovrRelease(p->object, info->destructor);
p->object = NULL;
}
}
return 0;
}
static int luax_runfinalizers(lua_State* L) {
lua_getfield(L, LUA_REGISTRYINDEX, "_lovrfinalizers");
for (int i = luax_len(L, 2); i >= 1; i--) {
lua_rawgeti(L, 2, i);
voidFn* finalizer = (voidFn*) lua_tocfunction(L, -1);
finalizer();
lua_pop(L, 1);
}
return 0;
}
void luax_preload(lua_State* L) {
static const luaL_Reg lovrModules[] = {
{ "lovr", luaopen_lovr },
#ifndef LOVR_DISABLE_AUDIO
{ "lovr.audio", luaopen_lovr_audio },
#endif
#ifndef LOVR_DISABLE_DATA
{ "lovr.data", luaopen_lovr_data },
#endif
#ifndef LOVR_DISABLE_EVENT
{ "lovr.event", luaopen_lovr_event },
#endif
#ifndef LOVR_DISABLE_FILESYSTEM
{ "lovr.filesystem", luaopen_lovr_filesystem },
#endif
#ifndef LOVR_DISABLE_GRAPHICS
{ "lovr.graphics", luaopen_lovr_graphics },
#endif
#ifndef LOVR_DISABLE_HEADSET
{ "lovr.headset", luaopen_lovr_headset },
#endif
#ifndef LOVR_DISABLE_MATH
{ "lovr.math", luaopen_lovr_math },
#endif
#ifndef LOVR_DISABLE_PHYSICS
{ "lovr.physics", luaopen_lovr_physics },
#endif
#ifndef LOVR_DISABLE_SYSTEM
{ "lovr.system", luaopen_lovr_system },
#endif
#ifndef LOVR_DISABLE_THREAD
{ "lovr.thread", luaopen_lovr_thread },
#endif
#ifndef LOVR_DISABLE_TIMER
{ "lovr.timer", luaopen_lovr_timer },
#endif
#ifndef LOVR_DISABLE_UTF8
{ "utf8", luaopen_utf8 },
#endif
{ NULL, NULL }
};
lua_getglobal(L, "package");
lua_getfield(L, -1, "preload");
luax_register(L, lovrModules);
lua_pop(L, 2);
}
void _luax_registertype(lua_State* L, const char* name, const luaL_Reg* functions, destructorFn* destructor) {
// Push metatable
luaL_newmetatable(L, name);
lua_getmetatable(L, -1);
// m.__index = m
lua_pushvalue(L, -1);
lua_setfield(L, -1, "__index");
// m.__info = info
TypeInfo* info = lua_newuserdata(L, sizeof(TypeInfo));
info->name = name;
info->destructor = destructor;
lua_setfield(L, -2, "__info");
// m.__gc = gc
lua_pushcfunction(L, luax_meta__gc);
lua_setfield(L, -2, "__gc");
// m.__close = gc
lua_pushcfunction(L, luax_meta__gc);
lua_setfield(L, -2, "__close");
// m.__tostring
lua_pushcfunction(L, luax_meta__tostring);
lua_setfield(L, -2, "__tostring");
// Register class methods
if (functions) {
luax_register(L, functions);
}
// :release method
lua_pushcfunction(L, luax_meta__gc);
lua_setfield(L, -2, "release");
// :type method
lua_pushcfunction(L, luax_type);
lua_setfield(L, -2, "type");
// Pop metatable
lua_pop(L, 1);
}
void* _luax_totype(lua_State* L, int index, uint64_t hash) {
Proxy* p = lua_touserdata(L, index);
if (p && lua_type(L, index) != LUA_TLIGHTUSERDATA && p->hash == hash) {
return p->object;
}
return NULL;
}
void* _luax_checktype(lua_State* L, int index, uint64_t hash, const char* debug) {
void* object = _luax_totype(L, index, hash);
if (!object) {
luax_typeerror(L, index, debug);
}
return object;
}
int luax_typeerror(lua_State* L, int index, const char* expected) {
const char* name;
if (luaL_getmetafield(L, index, "__name") == LUA_TSTRING) {
name = lua_tostring(L, -1);
} else if (lua_type(L, index) == LUA_TLIGHTUSERDATA) {
name = "light userdata";
} else {
name = luaL_typename(L, index);
}
const char* message = lua_pushfstring(L, "%s expected, got %s", expected, name);
return luaL_argerror(L, index, message);
}
// Registers the userdata on the top of the stack in the registry.
void _luax_pushtype(lua_State* L, const char* type, uint64_t hash, void* object) {
if (!object) {
lua_pushnil(L);
return;
}
lua_getfield(L, LUA_REGISTRYINDEX, "_lovrobjects");
// Create the registry if it doesn't exist yet
if (lua_isnil(L, -1)) {
lua_newtable(L);
lua_replace(L, -2);
// Create the metatable
lua_newtable(L);
// __mode = v
lua_pushliteral(L, "v");
lua_setfield(L, -2, "__mode");
// Set the metatable
lua_setmetatable(L, -2);
// Write the table to the registry
lua_pushvalue(L, -1);
lua_setfield(L, LUA_REGISTRYINDEX, "_lovrobjects");
}
lua_pushlightuserdata(L, object);
lua_gettable(L, -2);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
} else {
lua_remove(L, -2);
return;
}
// Allocate userdata
Proxy* p = (Proxy*) lua_newuserdata(L, sizeof(Proxy));
luaL_newmetatable(L, type);
lua_setmetatable(L, -2);
lovrRetain(object);
p->object = object;
p->hash = hash;
// Write to registry and remove registry, leaving userdata on stack
lua_pushlightuserdata(L, object);
lua_pushvalue(L, -2);
lua_settable(L, -4);
lua_remove(L, -2);
}
int _luax_checkenum(lua_State* L, int index, const StringEntry* map, const char* fallback, const char* label) {
size_t length;
const char* string = fallback ? luaL_optlstring(L, index, fallback, &length) : luaL_checklstring(L, index, &length);
for (int i = 0; map[i].length; i++) {
if (map[i].length == length && !memcmp(map[i].string, string, length)) {
return i;
}
}
if (index > 0) {
return luaL_argerror(L, index, lua_pushfstring(L, "invalid %s '%s'", label, string));
} else {
return luaL_error(L, "invalid %s '%s'", label, string);
}
}
void luax_registerloader(lua_State* L, lua_CFunction loader, int index) {
lua_getglobal(L, "table");
lua_getfield(L, -1, "insert");
lua_getglobal(L, "package");
#if LUA_VERSION_NUM == 501
lua_getfield(L, -1, "loaders");
#else
lua_getfield(L, -1, "searchers");
#endif
lua_remove(L, -2);
if (lua_istable(L, -1)) {
lua_pushinteger(L, index);
lua_pushcfunction(L, loader);
lua_call(L, 3, 0);
} else {
lua_pop(L, 2);
}
lua_pop(L, 1);
}
int luax_resume(lua_State* T, int n) {
#if LUA_VERSION_NUM >= 504
int results;
return lua_resume(T, NULL, n, &results);
#elif LUA_VERSION_NUM >= 502
return lua_resume(T, NULL, n);
#else
return lua_resume(T, n);
#endif
}
int luax_loadbufferx(lua_State* L, const char* buffer, size_t size, const char* name, const char* mode) {
#if LUA_VERSION_NUM >= 502
return luaL_loadbufferx(L, buffer, size, name, mode);
#else
bool binary = buffer[0] == LUA_SIGNATURE[0];
if (mode && !strchr(mode, binary ? 'b' : 't')) {
lua_pushliteral(L, "attempt to load chunk with wrong mode");
return LUA_ERRSYNTAX;
}
return luaL_loadbuffer(L, buffer, size, name);
#endif
}
void luax_vthrow(void* context, const char* format, va_list args) {
lua_State* L = (lua_State*) context;
lua_pushvfstring(L, format, args);
lua_error(L);
}
void luax_vlog(void* context, int level, const char* tag, const char* format, va_list args) {
static const char* levels[] = {
[LOG_DEBUG] = "debug",
[LOG_INFO] = "info",
[LOG_WARN] = "warn",
[LOG_ERROR] = "error"
};
lua_State* L = (lua_State*) context;
lua_getglobal(L, "lovr");
lua_getfield(L, -1, "log");
if (lua_type(L, -1) == LUA_TFUNCTION) {
lua_pushvfstring(L, format, args);
lua_pushstring(L, levels[level]);
lua_pushstring(L, tag);
lua_call(L, 3, 0);
}
lua_pop(L, 1);
}
// An implementation of luaL_traceback for Lua 5.1
void luax_traceback(lua_State* L, lua_State* T, const char* message, int level) {
if (!lua_checkstack(L, 5)) {
return;
}
lua_getglobal(L, "debug");
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
return;
}
lua_getfield(L, -1, "traceback");
if (!lua_isfunction(L, -1)) {
lua_pop(L, 2);
return;
}
lua_remove(L, -2);
lua_pushthread(T);
lua_pushstring(L, message);
lua_pushinteger(L, level);
lua_call(L, 3, 1);
}
int luax_getstack(lua_State *L) {
luax_traceback(L, L, lua_tostring(L, 1), 2);
return 1;
}
void luax_pushconf(lua_State* L) {
lua_getfield(L, LUA_REGISTRYINDEX, "_lovrconf");
}
int luax_setconf(lua_State* L) {
luax_pushconf(L);
lovrCheck(lua_isnil(L, -1), "Unable to set lovr.conf multiple times");
lua_pop(L, 1);
lua_setfield(L, LUA_REGISTRYINDEX, "_lovrconf");
return 0;
}
void luax_setmainthread(lua_State *L) {
#if LUA_VERSION_NUM < 502
lua_pushthread(L);
lua_rawseti(L, LUA_REGISTRYINDEX, LUA_RIDX_MAINTHREAD);
#endif
}
void luax_atexit(lua_State* L, voidFn* finalizer) {
lua_getfield(L, LUA_REGISTRYINDEX, "_lovrfinalizers");
if (lua_isnil(L, -1)) {
lua_newtable(L);
lua_replace(L, -2);
// Userdata sentinel since tables don't have __gc (yet)
lua_newuserdata(L, sizeof(void*));
lua_createtable(L, 0, 1);
lua_pushcfunction(L, luax_runfinalizers);
lua_setfield(L, -2, "__gc");
lua_setmetatable(L, -2);
lua_setfield(L, -2, "");
// Write to the registry
lua_pushvalue(L, -1);
lua_setfield(L, LUA_REGISTRYINDEX, "_lovrfinalizers");
}
int length = luax_len(L, -1);
lua_pushcfunction(L, (lua_CFunction) finalizer);
lua_rawseti(L, -2, length + 1);
lua_pop(L, 1);
}
uint32_t _luax_checku32(lua_State* L, int index) {
double x = lua_tonumber(L, index);
if (x == 0. && !lua_isnumber(L, index)) {
luax_typeerror(L, index, "number");
}
if (x < 0. || x > UINT32_MAX) {
const char* message = lua_pushfstring(L, "expected a number between 0 and %u, got %g", UINT32_MAX, x);
luaL_argerror(L, index, message);
}
return (uint32_t) x;
}
uint32_t _luax_optu32(lua_State* L, int index, uint32_t fallback) {
return luaL_opt(L, luax_checku32, index, fallback);
}
void luax_readcolor(lua_State* L, int index, float color[4]) {
color[0] = color[1] = color[2] = color[3] = 1.f;
if (lua_istable(L, index)) {
for (int i = 1; i <= 4; i++) {
lua_rawgeti(L, index, i);
}
color[0] = luax_checkfloat(L, -4);
color[1] = luax_checkfloat(L, -3);
color[2] = luax_checkfloat(L, -2);
color[3] = luax_optfloat(L, -1, 1.);
lua_pop(L, 4);
} else if (lua_gettop(L) >= index + 2) {
color[0] = luax_checkfloat(L, index);
color[1] = luax_checkfloat(L, index + 1);
color[2] = luax_checkfloat(L, index + 2);
color[3] = luax_optfloat(L, index + 3, 1.);
} else if (lua_gettop(L) <= index + 1) {
uint32_t x = luaL_checkinteger(L, index);
color[0] = ((x >> 16) & 0xff) / 255.f;
color[1] = ((x >> 8) & 0xff) / 255.f;
color[2] = ((x >> 0) & 0xff) / 255.f;
color[3] = luax_optfloat(L, index + 1, 1.);
}
}
// Like readcolor, but only consumes 1 argument (nil, hex, table, vec3, vec4), useful for table keys
void luax_optcolor(lua_State* L, int index, float color[4]) {
switch (lua_type(L, index)) {
case LUA_TNONE:
case LUA_TNIL:
color[0] = color[1] = color[2] = color[3] = 1.f;
break;
case LUA_TNUMBER: {
uint32_t x = lua_tonumber(L, index);
color[0] = ((x >> 16) & 0xff) / 255.f;
color[1] = ((x >> 8) & 0xff) / 255.f;
color[2] = ((x >> 0) & 0xff) / 255.f;
color[3] = 1.f;
break;
}
case LUA_TTABLE:
index = index > 0 ? index : (index + lua_gettop(L) + 1);
for (int i = 1; i <= 4; i++) {
lua_rawgeti(L, index, i);
}
color[0] = luax_checkfloat(L, -4);
color[1] = luax_checkfloat(L, -3);
color[2] = luax_checkfloat(L, -2);
color[3] = luax_optfloat(L, -1, 1.);
lua_pop(L, 4);
break;
case LUA_TUSERDATA:
case LUA_TLIGHTUSERDATA: {
VectorType type;
float* v = luax_tovector(L, index, &type);
if (type == V_VEC3) {
memcpy(color, v, 3 * sizeof(float));
color[3] = 1.f;
break;
} else if (type == V_VEC4) {
memcpy(color, v, 4 * sizeof(float));
break;
}
} /* fallthrough */
default: lovrThrow("Expected nil, number, table, vec3, or vec4 for color value");
}
}
int luax_readmesh(lua_State* L, int index, float** vertices, uint32_t* vertexCount, uint32_t** indices, uint32_t* indexCount, bool* shouldFree) {
if (lua_istable(L, index)) {
lua_rawgeti(L, index, 1);
bool nested = lua_type(L, -1) == LUA_TTABLE;
lua_pop(L, 1);
*vertexCount = luax_len(L, index) / (nested ? 1 : 3);
lovrCheck(*vertexCount > 0, "Invalid mesh data: vertex count is zero");
*vertices = lovrMalloc(sizeof(float) * *vertexCount * 3);
*shouldFree = true;
if (nested) {
for (uint32_t i = 0; i < *vertexCount; i++) {
lua_rawgeti(L, index, i + 1);
lua_rawgeti(L, -1, 1);
lua_rawgeti(L, -2, 2);
lua_rawgeti(L, -3, 3);
(*vertices)[3 * i + 0] = luax_checkfloat(L, -3);
(*vertices)[3 * i + 1] = luax_checkfloat(L, -2);
(*vertices)[3 * i + 2] = luax_checkfloat(L, -1);
lua_pop(L, 4);
}
} else {
for (uint32_t i = 0; i < *vertexCount * 3; i++) {
lua_rawgeti(L, index, i + 1);
(*vertices)[i] = luax_checkfloat(L, -1);
lua_pop(L, 1);
}
}
if (indices) {
luaL_checktype(L, index + 1, LUA_TTABLE);
*indexCount = luax_len(L, index + 1);
lovrCheck(*indexCount > 0, "Invalid mesh data: index count is zero");
lovrCheck(*indexCount % 3 == 0, "Index count must be a multiple of 3");
*indices = lovrMalloc(sizeof(uint32_t) * *indexCount);
for (uint32_t i = 0; i < *indexCount; i++) {
lua_rawgeti(L, index + 1, i + 1);
uint32_t index = luaL_checkinteger(L, -1) - 1;
lovrCheck(index < *vertexCount, "Invalid vertex index %d (expected [%d, %d])", index + 1, 1, *vertexCount);
(*indices)[i] = index;
lua_pop(L, 1);
}
}
return index + 2;
}
ModelData* modelData = luax_totype(L, index, ModelData);
if (modelData) {
lovrModelDataGetTriangles(modelData, vertices, indices, vertexCount, indexCount);
*shouldFree = false;
return index + 1;
}
#ifndef LOVR_DISABLE_GRAPHICS
Model* model = luax_totype(L, index, Model);
if (model) {
ModelData* modelData = lovrModelGetInfo(model)->data;
lovrModelDataGetTriangles(modelData, vertices, indices, vertexCount, indexCount);
*shouldFree = false;
return index + 1;
}
Mesh* mesh = luax_totype(L, index, Mesh);
if (mesh) {
lovrMeshGetTriangles(mesh, vertices, indices, vertexCount, indexCount);
*shouldFree = true;
return index + 1;
}
return luaL_argerror(L, index, "table, ModelData, Model, or Mesh expected");
#else
return luaL_argerror(L, index, "table or ModelData expected");
#endif
}