obs-studio/deps/obs-scripting/obs-scripting-lua-source.c

748 lines
18 KiB
C

/******************************************************************************
Copyright (C) 2017 by Hugh Bailey <jim@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "obs-scripting-lua.h"
#include "cstrcache.h"
#include <obs-module.h>
/* ========================================================================= */
static inline const char *get_table_string_(lua_State *script, int idx,
const char *name, const char *func)
{
const char *str = "";
lua_pushstring(script, name);
lua_gettable(script, idx - 1);
if (!lua_isstring(script, -1))
warn("%s: no item '%s' of type %s", func, name, "string");
else
str = cstrcache_get(lua_tostring(script, -1));
lua_pop(script, 1);
return str;
}
static inline int get_table_int_(lua_State *script, int idx, const char *name,
const char *func)
{
int val = 0;
lua_pushstring(script, name);
lua_gettable(script, idx - 1);
val = (int)lua_tointeger(script, -1);
lua_pop(script, 1);
UNUSED_PARAMETER(func);
return val;
}
static inline void get_callback_from_table_(lua_State *script, int idx,
const char *name, int *p_reg_idx,
const char *func)
{
*p_reg_idx = LUA_REFNIL;
lua_pushstring(script, name);
lua_gettable(script, idx - 1);
if (!lua_isfunction(script, -1)) {
if (!lua_isnil(script, -1)) {
warn("%s: item '%s' is not a function", func, name);
}
lua_pop(script, 1);
} else {
*p_reg_idx = luaL_ref(script, LUA_REGISTRYINDEX);
}
}
#define get_table_string(script, idx, name) \
get_table_string_(script, idx, name, __FUNCTION__)
#define get_table_int(script, idx, name) \
get_table_int_(script, idx, name, __FUNCTION__)
#define get_callback_from_table(script, idx, name, p_reg_idx) \
get_callback_from_table_(script, idx, name, p_reg_idx, __FUNCTION__)
bool ls_get_libobs_obj_(lua_State *script, const char *type, int lua_idx,
void *libobs_out, const char *id, const char *func,
int line)
{
swig_type_info *info = SWIG_TypeQuery(script, type);
if (info == NULL) {
warn("%s:%d: SWIG could not find type: %s%s%s", func, line,
id ? id : "", id ? "::" : "", type);
return false;
}
int ret = SWIG_ConvertPtr(script, lua_idx, libobs_out, info, 0);
if (!SWIG_IsOK(ret)) {
warn("%s:%d: SWIG failed to convert lua object to obs "
"object: %s%s%s",
func, line, id ? id : "", id ? "::" : "", type);
return false;
}
return true;
}
#define ls_get_libobs_obj(type, lua_index, obs_obj) \
ls_get_libobs_obj_(ls->script, #type " *", lua_index, obs_obj, ls->id, \
__FUNCTION__, __LINE__)
bool ls_push_libobs_obj_(lua_State *script, const char *type, void *libobs_in,
bool ownership, const char *id, const char *func,
int line)
{
swig_type_info *info = SWIG_TypeQuery(script, type);
if (info == NULL) {
warn("%s:%d: SWIG could not find type: %s%s%s", func, line,
id ? id : "", id ? "::" : "", type);
return false;
}
SWIG_NewPointerObj(script, libobs_in, info, (int)ownership);
return true;
}
#define ls_push_libobs_obj(type, obs_obj, ownership) \
ls_push_libobs_obj_(ls->script, #type " *", obs_obj, ownership, \
ls->id, __FUNCTION__, __LINE__)
/* ========================================================================= */
struct obs_lua_data;
struct obs_lua_source {
struct obs_lua_script *data;
lua_State *script;
const char *id;
const char *display_name;
int func_create;
int func_destroy;
int func_get_width;
int func_get_height;
int func_get_defaults;
int func_get_properties;
int func_update;
int func_activate;
int func_deactivate;
int func_show;
int func_hide;
int func_video_tick;
int func_video_render;
int func_save;
int func_load;
pthread_mutex_t definition_mutex;
struct obs_lua_data *first_source;
struct obs_lua_source *next;
struct obs_lua_source **p_prev_next;
};
extern pthread_mutex_t lua_source_def_mutex;
struct obs_lua_source *first_source_def = NULL;
struct obs_lua_data {
obs_source_t *source;
struct obs_lua_source *ls;
int lua_data_ref;
struct obs_lua_data *next;
struct obs_lua_data **p_prev_next;
};
#define call_func(name, args, rets) \
call_func_(ls->script, ls->func_##name, args, rets, #name, \
ls->display_name)
#define have_func(name) (ls->func_##name != LUA_REFNIL)
#define ls_push_data() \
lua_rawgeti(ls->script, LUA_REGISTRYINDEX, ld->lua_data_ref)
#define ls_pop(count) lua_pop(ls->script, count)
#define lock_script() \
struct obs_lua_script *__data = ls->data; \
struct obs_lua_script *__prev_script = current_lua_script; \
current_lua_script = __data; \
pthread_mutex_lock(&__data->mutex);
#define unlock_script() \
pthread_mutex_unlock(&__data->mutex); \
current_lua_script = __prev_script;
static const char *obs_lua_source_get_name(void *type_data)
{
struct obs_lua_source *ls = type_data;
return ls->display_name;
}
static void *obs_lua_source_create(obs_data_t *settings, obs_source_t *source)
{
struct obs_lua_source *ls = obs_source_get_type_data(source);
struct obs_lua_data *data = NULL;
pthread_mutex_lock(&ls->definition_mutex);
if (!ls->script)
goto fail;
if (!have_func(create))
goto fail;
lock_script();
ls_push_libobs_obj(obs_data_t, settings, false);
ls_push_libobs_obj(obs_source_t, source, false);
call_func(create, 2, 1);
int lua_data_ref = luaL_ref(ls->script, LUA_REGISTRYINDEX);
if (lua_data_ref != LUA_REFNIL) {
data = bmalloc(sizeof(*data));
data->source = source;
data->ls = ls;
data->lua_data_ref = lua_data_ref;
}
unlock_script();
if (data) {
struct obs_lua_data *next = ls->first_source;
data->next = next;
data->p_prev_next = &ls->first_source;
if (next)
next->p_prev_next = &data->next;
ls->first_source = data;
}
fail:
pthread_mutex_unlock(&ls->definition_mutex);
return data;
}
static void call_destroy(struct obs_lua_data *ld)
{
struct obs_lua_source *ls = ld->ls;
ls_push_data();
call_func(destroy, 1, 0);
luaL_unref(ls->script, LUA_REGISTRYINDEX, ld->lua_data_ref);
ld->lua_data_ref = LUA_REFNIL;
}
static void obs_lua_source_destroy(void *data)
{
struct obs_lua_data *ld = data;
struct obs_lua_source *ls = ld->ls;
struct obs_lua_data *next;
pthread_mutex_lock(&ls->definition_mutex);
if (!ls->script)
goto fail;
if (!have_func(destroy))
goto fail;
lock_script();
call_destroy(ld);
unlock_script();
fail:
next = ld->next;
*ld->p_prev_next = next;
if (next)
next->p_prev_next = ld->p_prev_next;
bfree(data);
pthread_mutex_unlock(&ls->definition_mutex);
}
static uint32_t obs_lua_source_get_width(void *data)
{
struct obs_lua_data *ld = data;
struct obs_lua_source *ls = ld->ls;
uint32_t width = 0;
pthread_mutex_lock(&ls->definition_mutex);
if (!ls->script)
goto fail;
if (!have_func(get_width))
goto fail;
lock_script();
ls_push_data();
if (call_func(get_width, 1, 1)) {
width = (uint32_t)lua_tointeger(ls->script, -1);
ls_pop(1);
}
unlock_script();
fail:
pthread_mutex_unlock(&ls->definition_mutex);
return width;
}
static uint32_t obs_lua_source_get_height(void *data)
{
struct obs_lua_data *ld = data;
struct obs_lua_source *ls = ld->ls;
uint32_t height = 0;
pthread_mutex_lock(&ls->definition_mutex);
if (!ls->script)
goto fail;
if (!have_func(get_height))
goto fail;
lock_script();
ls_push_data();
if (call_func(get_height, 1, 1)) {
height = (uint32_t)lua_tointeger(ls->script, -1);
ls_pop(1);
}
unlock_script();
fail:
pthread_mutex_unlock(&ls->definition_mutex);
return height;
}
static void obs_lua_source_get_defaults(void *type_data, obs_data_t *settings)
{
struct obs_lua_source *ls = type_data;
pthread_mutex_lock(&ls->definition_mutex);
if (!ls->script)
goto fail;
if (!have_func(get_defaults))
goto fail;
lock_script();
ls_push_libobs_obj(obs_data_t, settings, false);
call_func(get_defaults, 1, 0);
unlock_script();
fail:
pthread_mutex_unlock(&ls->definition_mutex);
}
static obs_properties_t *obs_lua_source_get_properties(void *data)
{
struct obs_lua_data *ld = data;
struct obs_lua_source *ls = ld->ls;
obs_properties_t *props = NULL;
pthread_mutex_lock(&ls->definition_mutex);
if (!ls->script)
goto fail;
if (!have_func(get_properties))
goto fail;
lock_script();
ls_push_data();
if (call_func(get_properties, 1, 1)) {
ls_get_libobs_obj(obs_properties_t, -1, &props);
ls_pop(1);
}
unlock_script();
fail:
pthread_mutex_unlock(&ls->definition_mutex);
return props;
}
static void obs_lua_source_update(void *data, obs_data_t *settings)
{
struct obs_lua_data *ld = data;
struct obs_lua_source *ls = ld->ls;
pthread_mutex_lock(&ls->definition_mutex);
if (!ls->script)
goto fail;
if (!have_func(update))
goto fail;
lock_script();
ls_push_data();
ls_push_libobs_obj(obs_data_t, settings, false);
call_func(update, 2, 0);
unlock_script();
fail:
pthread_mutex_unlock(&ls->definition_mutex);
}
#define DEFINE_VOID_DATA_CALLBACK(name) \
static void obs_lua_source_##name(void *data) \
{ \
struct obs_lua_data *ld = data; \
struct obs_lua_source *ls = ld->ls; \
if (!have_func(name)) \
return; \
lock_script(); \
ls_push_data(); \
call_func(name, 1, 0); \
unlock_script(); \
}
DEFINE_VOID_DATA_CALLBACK(activate)
DEFINE_VOID_DATA_CALLBACK(deactivate)
DEFINE_VOID_DATA_CALLBACK(show)
DEFINE_VOID_DATA_CALLBACK(hide)
#undef DEFINE_VOID_DATA_CALLBACK
static void obs_lua_source_video_tick(void *data, float seconds)
{
struct obs_lua_data *ld = data;
struct obs_lua_source *ls = ld->ls;
pthread_mutex_lock(&ls->definition_mutex);
if (!ls->script)
goto fail;
if (!have_func(video_tick))
goto fail;
lock_script();
ls_push_data();
lua_pushnumber(ls->script, (double)seconds);
call_func(video_tick, 2, 0);
unlock_script();
fail:
pthread_mutex_unlock(&ls->definition_mutex);
}
static void obs_lua_source_video_render(void *data, gs_effect_t *effect)
{
struct obs_lua_data *ld = data;
struct obs_lua_source *ls = ld->ls;
pthread_mutex_lock(&ls->definition_mutex);
if (!ls->script)
goto fail;
if (!have_func(video_render))
goto fail;
lock_script();
ls_push_data();
ls_push_libobs_obj(gs_effect_t, effect, false);
call_func(video_render, 2, 0);
unlock_script();
fail:
pthread_mutex_unlock(&ls->definition_mutex);
}
static void obs_lua_source_save(void *data, obs_data_t *settings)
{
struct obs_lua_data *ld = data;
struct obs_lua_source *ls = ld->ls;
pthread_mutex_lock(&ls->definition_mutex);
if (!ls->script)
goto fail;
if (!have_func(save))
goto fail;
lock_script();
ls_push_data();
ls_push_libobs_obj(obs_data_t, settings, false);
call_func(save, 2, 0);
unlock_script();
fail:
pthread_mutex_unlock(&ls->definition_mutex);
}
static void obs_lua_source_load(void *data, obs_data_t *settings)
{
struct obs_lua_data *ld = data;
struct obs_lua_source *ls = ld->ls;
pthread_mutex_lock(&ls->definition_mutex);
if (!ls->script)
goto fail;
if (!have_func(load))
goto fail;
lock_script();
ls_push_data();
ls_push_libobs_obj(obs_data_t, settings, false);
call_func(load, 2, 0);
unlock_script();
fail:
pthread_mutex_unlock(&ls->definition_mutex);
}
static void source_type_unload(struct obs_lua_source *ls)
{
#define unref(name) \
luaL_unref(ls->script, LUA_REGISTRYINDEX, name); \
name = LUA_REFNIL
unref(ls->func_create);
unref(ls->func_destroy);
unref(ls->func_get_width);
unref(ls->func_get_height);
unref(ls->func_get_defaults);
unref(ls->func_get_properties);
unref(ls->func_update);
unref(ls->func_activate);
unref(ls->func_deactivate);
unref(ls->func_show);
unref(ls->func_hide);
unref(ls->func_video_tick);
unref(ls->func_video_render);
unref(ls->func_save);
unref(ls->func_load);
#undef unref
}
static void obs_lua_source_free_type_data(void *type_data)
{
struct obs_lua_source *ls = type_data;
pthread_mutex_lock(&ls->definition_mutex);
if (ls->script) {
lock_script();
source_type_unload(ls);
unlock_script();
ls->script = NULL;
}
pthread_mutex_unlock(&ls->definition_mutex);
pthread_mutex_destroy(&ls->definition_mutex);
bfree(ls);
}
EXPORT void obs_enable_source_type(const char *name, bool enable);
static inline struct obs_lua_source *find_existing(const char *id)
{
struct obs_lua_source *existing = NULL;
pthread_mutex_lock(&lua_source_def_mutex);
struct obs_lua_source *ls = first_source_def;
while (ls) {
/* can compare pointers here due to string table */
if (ls->id == id) {
existing = ls;
break;
}
ls = ls->next;
}
pthread_mutex_unlock(&lua_source_def_mutex);
return existing;
}
static int obs_lua_register_source(lua_State *script)
{
struct obs_lua_source ls = {0};
struct obs_lua_source *existing = NULL;
struct obs_lua_source *v = NULL;
struct obs_source_info info = {0};
const char *id;
if (!verify_args1(script, is_table))
goto fail;
id = get_table_string(script, -1, "id");
if (!id || !*id)
goto fail;
/* redefinition */
existing = find_existing(id);
if (existing) {
if (existing->script) {
existing = NULL;
goto fail;
}
pthread_mutex_lock(&existing->definition_mutex);
}
v = existing ? existing : &ls;
v->script = script;
v->id = id;
info.id = v->id;
info.type = (enum obs_source_type)get_table_int(script, -1, "type");
info.output_flags = get_table_int(script, -1, "output_flags");
lua_pushstring(script, "get_name");
lua_gettable(script, -2);
if (lua_pcall(script, 0, 1, 0) == 0) {
v->display_name = cstrcache_get(lua_tostring(script, -1));
lua_pop(script, 1);
}
if (!v->display_name || !*v->display_name || !*info.id ||
!info.output_flags)
goto fail;
#define get_callback(val) \
do { \
get_callback_from_table(script, -1, #val, &v->func_##val); \
info.val = obs_lua_source_##val; \
} while (false)
get_callback(create);
get_callback(destroy);
get_callback(get_width);
get_callback(get_height);
get_callback(get_properties);
get_callback(update);
get_callback(activate);
get_callback(deactivate);
get_callback(show);
get_callback(hide);
get_callback(video_tick);
get_callback(video_render);
get_callback(save);
get_callback(load);
#undef get_callback
get_callback_from_table(script, -1, "get_defaults",
&v->func_get_defaults);
if (!existing) {
ls.data = current_lua_script;
pthread_mutexattr_t mutexattr;
pthread_mutexattr_init(&mutexattr);
pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&ls.definition_mutex, &mutexattr);
pthread_mutexattr_destroy(&mutexattr);
info.type_data = bmemdup(&ls, sizeof(ls));
info.free_type_data = obs_lua_source_free_type_data;
info.get_name = obs_lua_source_get_name;
info.get_defaults2 = obs_lua_source_get_defaults;
obs_register_source(&info);
pthread_mutex_lock(&lua_source_def_mutex);
v = info.type_data;
struct obs_lua_source *next = first_source_def;
v->next = next;
if (next)
next->p_prev_next = &v->next;
v->p_prev_next = &first_source_def;
first_source_def = v;
pthread_mutex_unlock(&lua_source_def_mutex);
} else {
existing->script = script;
existing->data = current_lua_script;
obs_enable_source_type(id, true);
struct obs_lua_data *ld = v->first_source;
while (ld) {
struct obs_lua_source *ls = v;
if (have_func(create)) {
obs_source_t *source = ld->source;
obs_data_t *settings =
obs_source_get_settings(source);
ls_push_libobs_obj(obs_data_t, settings, false);
ls_push_libobs_obj(obs_source_t, source, false);
call_func(create, 2, 1);
ld->lua_data_ref =
luaL_ref(ls->script, LUA_REGISTRYINDEX);
obs_data_release(settings);
}
ld = ld->next;
}
}
fail:
if (existing) {
pthread_mutex_unlock(&existing->definition_mutex);
}
return 0;
}
/* ========================================================================= */
void add_lua_source_functions(lua_State *script)
{
lua_getglobal(script, "obslua");
lua_pushstring(script, "obs_register_source");
lua_pushcfunction(script, obs_lua_register_source);
lua_rawset(script, -3);
lua_pop(script, 1);
}
static inline void undef_source_type(struct obs_lua_script *data,
struct obs_lua_source *ls)
{
pthread_mutex_lock(&ls->definition_mutex);
pthread_mutex_lock(&data->mutex);
obs_enable_source_type(ls->id, false);
struct obs_lua_data *ld = ls->first_source;
while (ld) {
call_destroy(ld);
ld = ld->next;
}
source_type_unload(ls);
ls->script = NULL;
pthread_mutex_unlock(&data->mutex);
pthread_mutex_unlock(&ls->definition_mutex);
}
void undef_lua_script_sources(struct obs_lua_script *data)
{
pthread_mutex_lock(&lua_source_def_mutex);
struct obs_lua_source *def = first_source_def;
while (def) {
if (def->script == data->script)
undef_source_type(data, def);
def = def->next;
}
pthread_mutex_unlock(&lua_source_def_mutex);
}