3016 lines
92 KiB
C++
Vendored
3016 lines
92 KiB
C++
Vendored
// https://github.com/kunitoki/LuaBridge3
|
|
// Copyright 2020, kunitoki
|
|
// Copyright 2019, Dmitry Tarakanov
|
|
// Copyright 2012, Vinnie Falco <vinnie.falco@gmail.com>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#pragma once
|
|
|
|
#include "Config.h"
|
|
#include "ClassInfo.h"
|
|
#include "Errors.h"
|
|
#include "FuncTraits.h"
|
|
#include "LuaHelpers.h"
|
|
#include "Options.h"
|
|
#include "Stack.h"
|
|
#include "TypeTraits.h"
|
|
#include "Userdata.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <memory>
|
|
#include <new>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace luabridge {
|
|
|
|
class LuaRef;
|
|
|
|
namespace detail {
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief Extract exactly what Stack<T>::get() produces.
|
|
*
|
|
* Allows that implicit conversions (e.g. std::reference_wrapper<T> → T&) happen correctly at the call site.
|
|
*/
|
|
template <class T>
|
|
using stack_value_t = remove_cvref_t<
|
|
decltype(*std::declval<decltype(Stack<T>::get(std::declval<lua_State*>(), 0))>())>;
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief Trivially-destructible storage for one decoded function argument.
|
|
*
|
|
* Longjmp-safe: the raw byte array and the construction flag are trivially
|
|
* destructible, so raise_lua_error (longjmp) while ArgStorage objects sit on
|
|
* the C++ stack does not skip any live C++ destructor. The contained T must
|
|
* be managed explicitly via construct/destroy.
|
|
*/
|
|
template <class T>
|
|
struct ArgStorage
|
|
{
|
|
using StoredType = stack_value_t<T>;
|
|
|
|
alignas(StoredType) std::byte data[sizeof(StoredType)];
|
|
bool constructed = false;
|
|
|
|
StoredType* ptr() noexcept { return std::launder(reinterpret_cast<StoredType*>(data)); }
|
|
|
|
void destroy() noexcept
|
|
{
|
|
if (constructed)
|
|
{
|
|
std::destroy_at(ptr());
|
|
constructed = false;
|
|
}
|
|
}
|
|
};
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief Decode one argument from the Lua stack into storage element I.
|
|
*
|
|
* Sets error_arg/error_msg on the first failure; subsequent calls are no-ops.
|
|
*/
|
|
template <std::size_t I, class ArgsPack, std::size_t Start, class StorageTuple>
|
|
void decode_arg(lua_State* L, StorageTuple& storage, int& error_arg, const char*& error_msg)
|
|
{
|
|
using T = std::tuple_element_t<I, ArgsPack>;
|
|
using StoredType = typename std::tuple_element_t<I, StorageTuple>::StoredType;
|
|
|
|
if (error_arg)
|
|
return;
|
|
|
|
auto result = Stack<T>::get(L, static_cast<int>(I + Start));
|
|
if (! result)
|
|
{
|
|
error_arg = static_cast<int>(I + 1);
|
|
error_msg = result.error_cstr();
|
|
return;
|
|
}
|
|
|
|
::new (std::get<I>(storage).data) StoredType(std::move(*result));
|
|
std::get<I>(storage).constructed = true;
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief Make argument lists extracting them from the lua state, starting at a stack index.
|
|
*
|
|
* Arguments are decoded sequentially left to right. On failure every already-
|
|
* constructed argument is explicitly destroyed before raise_lua_error is called,
|
|
* so longjmp never skips a live C++ destructor.
|
|
*
|
|
* @tparam ArgsPack Arguments pack to extract from the lua stack.
|
|
* @tparam Start Start index where stack variables are located in the lua stack.
|
|
*/
|
|
template <class ArgsPack, std::size_t Start, std::size_t... Indices>
|
|
auto make_arguments_list_impl([[maybe_unused]] lua_State* L, std::index_sequence<Indices...>)
|
|
{
|
|
std::tuple<ArgStorage<std::tuple_element_t<Indices, ArgsPack>>...> storage;
|
|
|
|
int error_arg = 0;
|
|
const char* error_msg = nullptr;
|
|
|
|
(decode_arg<Indices, ArgsPack, Start>(L, storage, error_arg, error_msg), ...);
|
|
|
|
if (error_arg)
|
|
{
|
|
(std::get<Indices>(storage).destroy(), ...);
|
|
raise_lua_error(L, "Error decoding argument #%d: %s", error_arg, error_msg);
|
|
}
|
|
|
|
auto result = tupleize(std::move(*std::get<Indices>(storage).ptr())...);
|
|
(std::get<Indices>(storage).destroy(), ...);
|
|
return result;
|
|
}
|
|
|
|
template <class ArgsPack, std::size_t Start>
|
|
auto make_arguments_list(lua_State* L)
|
|
{
|
|
return make_arguments_list_impl<ArgsPack, Start>(L, std::make_index_sequence<std::tuple_size_v<ArgsPack>>());
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief Helpers for iterating through tuple arguments, pushing each argument to the lua stack.
|
|
*/
|
|
template <std::size_t Index = 0, class... Types>
|
|
auto push_arguments(lua_State*, std::tuple<Types...>)
|
|
-> std::enable_if_t<Index == sizeof...(Types), std::tuple<Result, std::size_t>>
|
|
{
|
|
return std::make_tuple(Result(), Index + 1);
|
|
}
|
|
|
|
template <std::size_t Index = 0, class... Types>
|
|
auto push_arguments(lua_State* L, std::tuple<Types...> t)
|
|
-> std::enable_if_t<Index < sizeof...(Types), std::tuple<Result, std::size_t>>
|
|
{
|
|
using T = std::tuple_element_t<Index, std::tuple<Types...>>;
|
|
|
|
auto result = Stack<T>::push(L, std::get<Index>(t));
|
|
if (! result)
|
|
return std::make_tuple(result, Index + 1);
|
|
|
|
return push_arguments<Index + 1, Types...>(L, std::move(t));
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief Helpers for iterating through tuple arguments, popping each argument from the lua stack.
|
|
*/
|
|
template <std::ptrdiff_t Start, std::ptrdiff_t Index = 0, class... Types>
|
|
auto pop_arguments(lua_State*, std::tuple<Types...>&)
|
|
-> std::enable_if_t<Index == sizeof...(Types), std::size_t>
|
|
{
|
|
return sizeof...(Types);
|
|
}
|
|
|
|
template <std::ptrdiff_t Start, std::ptrdiff_t Index = 0, class... Types>
|
|
auto pop_arguments(lua_State* L, std::tuple<Types...>& t)
|
|
-> std::enable_if_t<Index < sizeof...(Types), std::size_t>
|
|
{
|
|
using T = std::tuple_element_t<Index, std::tuple<Types...>>;
|
|
|
|
std::get<Index>(t) = Stack<T>::get(L, Start - Index);
|
|
|
|
return pop_arguments<Start, Index + 1, Types...>(L, t);
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief Push a tuple on the stack.
|
|
*/
|
|
template <class Tuple, std::size_t... Is>
|
|
Result push_tuple_impl(lua_State* L, const Tuple& value, std::index_sequence<Is...>)
|
|
{
|
|
Result result;
|
|
(void)((result = Stack<std::decay_t<std::tuple_element_t<Is, Tuple>>>::push(L, std::get<Is>(value)), bool(result)) && ...);
|
|
return result;
|
|
}
|
|
|
|
template <class... Ts>
|
|
Result push_tuple(lua_State* L, const std::tuple<Ts...>& value)
|
|
{
|
|
return push_tuple_impl(L, value, std::index_sequence_for<Ts...>{});
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief Check if a method name is a metamethod.
|
|
*/
|
|
template <class T = void, class... Args>
|
|
constexpr auto make_array(Args&&... args)
|
|
{
|
|
if constexpr (std::is_same_v<void, T>)
|
|
return std::array<std::common_type_t<std::decay_t<Args>...>, sizeof...(Args)>{{ std::forward<Args>(args)... }};
|
|
else
|
|
return std::array<T, sizeof...(Args)>{{ std::forward<Args>(args)... }};
|
|
}
|
|
|
|
inline bool is_metamethod(std::string_view method_name)
|
|
{
|
|
static constexpr auto metamethods = make_array<std::string_view>(
|
|
"__add",
|
|
"__band",
|
|
"__bnot",
|
|
"__bor",
|
|
"__bxor",
|
|
"__call",
|
|
"__close",
|
|
"__concat",
|
|
"__div",
|
|
"__eq",
|
|
"__gc",
|
|
"__idiv",
|
|
"__index",
|
|
"__ipairs",
|
|
"__le",
|
|
"__len",
|
|
"__lt",
|
|
"__metatable",
|
|
"__mod",
|
|
"__mode",
|
|
"__mul",
|
|
"__name",
|
|
"__newindex",
|
|
"__pairs",
|
|
"__pow",
|
|
"__shl",
|
|
"__shr",
|
|
"__sub",
|
|
"__tostring",
|
|
"__unm"
|
|
);
|
|
|
|
if (method_name.size() <= 2 || method_name[0] != '_' || method_name[1] != '_')
|
|
return false;
|
|
|
|
auto result = std::lower_bound(metamethods.begin(), metamethods.end(), method_name);
|
|
return result != metamethods.end() && *result == method_name;
|
|
}
|
|
|
|
inline void rawset_super_method(lua_State* L, int tableIndex, const char* key)
|
|
{
|
|
LUABRIDGE_ASSERT(key != nullptr);
|
|
|
|
tableIndex = lua_absindex(L, tableIndex);
|
|
|
|
// Stack before: ..., value
|
|
if (key[0] == '_')
|
|
lua_pushliteral(L, "super"); // Stack: ..., value, "super"
|
|
else
|
|
lua_pushliteral(L, "super_"); // Stack: ..., value, "super_"
|
|
|
|
lua_pushstring(L, key); // Stack: ..., value, prefix, key
|
|
lua_concat(L, 2); // Stack: ..., value, super_key
|
|
lua_insert(L, -2); // Stack: ..., super_key, value
|
|
lua_rawset(L, tableIndex); // Pops key/value. Stack: ...
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief Make super method name.
|
|
*/
|
|
inline Options get_class_options(lua_State* L, int index)
|
|
{
|
|
LUABRIDGE_ASSERT(lua_istable(L, index)); // Stack: mt
|
|
|
|
Options options = defaultOptions;
|
|
|
|
lua_rawgetp_x(L, index, getClassOptionsKey()); // Stack: mt, ifb (may be nil)
|
|
if (lua_isnumber(L, -1))
|
|
options = Options::fromUnderlying(lua_tointeger(L, -1));
|
|
|
|
lua_pop(L, 1);
|
|
|
|
return options;
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief Push class or const table onto stack.
|
|
*/
|
|
inline void push_class_or_const_table(lua_State* L, int index)
|
|
{
|
|
LUABRIDGE_ASSERT(lua_istable(L, index)); // Stack: mt
|
|
|
|
lua_rawgetp_x(L, index, getClassKey()); // Stack: mt, class table (ct) | nil
|
|
if (! lua_istable(L, -1)) // Stack: mt, nil
|
|
{
|
|
lua_pop(L, 1); // Stack: mt
|
|
|
|
lua_rawgetp_x(L, index, getConstKey()); // Stack: mt, const table (co) | nil
|
|
if (! lua_istable(L, -1)) // Stack: mt, nil
|
|
return;
|
|
}
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief __index metamethod for a namespace or class static and non-static members.
|
|
*
|
|
* Retrieves functions from metatables and properties from propget tables. Looks through the class hierarchy if inheritance is present.
|
|
*/
|
|
|
|
inline std::optional<int> try_call_index_fallback(lua_State* L)
|
|
{
|
|
LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt
|
|
|
|
lua_rawgetp_x(L, -1, getIndexFallbackKey()); // Stack: mt, ifb (may be nil)
|
|
if (! lua_iscfunction(L, -1))
|
|
{
|
|
lua_pop(L, 1); // Stack: mt
|
|
return std::nullopt;
|
|
}
|
|
|
|
lua_pushvalue(L, 1); // Stack: mt, ifb, arg1
|
|
lua_pushvalue(L, 2); // Stack: mt, ifb, arg1, arg2
|
|
lua_call(L, 2, 1); // Stack: mt, ifbresult
|
|
|
|
if (! lua_isnoneornil(L, -1))
|
|
{
|
|
lua_remove(L, -2); // Stack: ifbresult
|
|
return 1;
|
|
}
|
|
|
|
lua_pop(L, 1); // Stack: mt
|
|
return std::nullopt;
|
|
}
|
|
|
|
inline std::optional<int> try_call_static_index_fallback(lua_State* L)
|
|
{
|
|
LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt
|
|
|
|
lua_rawgetp_x(L, -1, getStaticIndexFallbackKey()); // Stack: mt, ifb (may be nil)
|
|
if (! lua_iscfunction(L, -1))
|
|
{
|
|
lua_pop(L, 1); // Stack: mt
|
|
return std::nullopt;
|
|
}
|
|
|
|
lua_pushvalue(L, 2); // Stack: mt, ifb, arg1 (key only, no self for static)
|
|
lua_call(L, 1, 1); // Stack: mt, ifbresult
|
|
|
|
if (! lua_isnoneornil(L, -1))
|
|
{
|
|
lua_remove(L, -2); // Stack: ifbresult
|
|
return 1;
|
|
}
|
|
|
|
lua_pop(L, 1); // Stack: mt
|
|
return std::nullopt;
|
|
}
|
|
|
|
template <bool IsObject>
|
|
inline std::optional<int> try_call_index_extensible(lua_State* L, const char* key)
|
|
{
|
|
LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt
|
|
|
|
if constexpr (IsObject)
|
|
push_class_or_const_table(L, -1); // Stack: mt, cl | co
|
|
else
|
|
lua_rawgetp_x(L, -1, getStaticKey()); // Stack: mt, st
|
|
|
|
LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt, cl | co | st
|
|
rawgetfield(L, -1, key); // Stack: mt, st, ifbresult | nil
|
|
|
|
if (! lua_isnoneornil(L, -1)) // Stack: mt, cl | co | st, ifbresult
|
|
{
|
|
lua_remove(L, -2); // Stack: mt, ifbresult
|
|
lua_remove(L, -2); // Stack: ifbresult
|
|
return 1;
|
|
}
|
|
|
|
lua_pop(L, 2); // Stack: mt
|
|
return std::nullopt;
|
|
}
|
|
|
|
template <bool IsObject>
|
|
inline std::optional<int> try_call_parent_index_fallback(lua_State* L, const char* key)
|
|
{
|
|
LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt
|
|
|
|
if (key == nullptr)
|
|
return std::nullopt;
|
|
|
|
lua_rawgetp_x(L, -1, getParentKey()); // Stack: mt, parent list | nil
|
|
if (! lua_istable(L, -1))
|
|
{
|
|
lua_pop(L, 1); // Stack: mt
|
|
return std::nullopt;
|
|
}
|
|
|
|
const int parentListIndex = lua_absindex(L, -1);
|
|
const int parentCount = get_length(L, parentListIndex);
|
|
|
|
for (int i = 1; i <= parentCount; ++i)
|
|
{
|
|
lua_rawgeti(L, parentListIndex, i); // Stack: mt, parent list, parent mt
|
|
if (! lua_istable(L, -1))
|
|
{
|
|
lua_pop(L, 1);
|
|
continue;
|
|
}
|
|
|
|
const Options parentOptions = get_class_options(L, -1); // Stack: mt, parent list, parent mt
|
|
if (parentOptions.test(extensibleClass | ~allowOverridingMethods))
|
|
{
|
|
if (auto result = try_call_index_extensible<IsObject>(L, key))
|
|
{
|
|
lua_remove(L, -2); // Stack: mt, result
|
|
lua_remove(L, -2); // Stack: result
|
|
return *result;
|
|
}
|
|
}
|
|
|
|
lua_rawgetp_x(L, -1, getIndexFallbackKey()); // Stack: mt, parent list, parent mt, ifb | nil
|
|
if (lua_iscfunction(L, -1))
|
|
{
|
|
lua_pushvalue(L, 1); // Stack: mt, parent list, parent mt, ifb, arg1
|
|
lua_pushvalue(L, 2); // Stack: mt, parent list, parent mt, ifb, arg1, arg2
|
|
lua_call(L, 2, 1); // Stack: mt, parent list, parent mt, result
|
|
|
|
if (! lua_isnoneornil(L, -1))
|
|
{
|
|
lua_remove(L, -2); // Stack: mt, parent list, result
|
|
lua_remove(L, -2); // Stack: mt, result
|
|
lua_remove(L, -2); // Stack: result
|
|
return 1;
|
|
}
|
|
|
|
lua_pop(L, 1); // Stack: mt, parent list, parent mt
|
|
}
|
|
else
|
|
{
|
|
lua_pop(L, 1); // Stack: mt, parent list, parent mt
|
|
}
|
|
|
|
lua_pop(L, 1); // Stack: mt, parent list
|
|
}
|
|
|
|
lua_pop(L, 1); // Stack: mt
|
|
return std::nullopt;
|
|
}
|
|
|
|
template <bool IsObject>
|
|
inline int index_metamethod(lua_State* L)
|
|
{
|
|
#if LUABRIDGE_SAFE_STACK_CHECKS
|
|
luaL_checkstack(L, 6, detail::error_lua_stack_overflow);
|
|
#endif
|
|
|
|
LUABRIDGE_ASSERT(lua_istable(L, 1) || lua_isuserdata(L, 1)); // Stack (further not shown): table | userdata, name
|
|
|
|
lua_getmetatable(L, 1); // Stack: class/const table (mt)
|
|
LUABRIDGE_ASSERT(lua_istable(L, -1));
|
|
|
|
// Protect internal meta methods
|
|
const char* key = lua_tostring(L, 2);
|
|
if (key != nullptr && is_metamethod(key))
|
|
{
|
|
lua_pushnil(L);
|
|
return 1;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
if constexpr (IsObject)
|
|
{
|
|
// Repeat the lookup in the index fallback
|
|
if (auto result = try_call_index_fallback(L))
|
|
return *result;
|
|
}
|
|
else
|
|
{
|
|
// Repeat the lookup in the static index fallback
|
|
if (auto result = try_call_static_index_fallback(L))
|
|
return *result;
|
|
}
|
|
|
|
// Search into self or metatable
|
|
if (lua_istable(L, 1))
|
|
{
|
|
if constexpr (IsObject)
|
|
lua_pushvalue(L, 1); // Stack: mt, self
|
|
else
|
|
push_class_or_const_table(L, -1); // Stack: mt, cl | co
|
|
|
|
if (lua_istable(L, -1))
|
|
{
|
|
lua_pushvalue(L, 2); // Stack: mt, self | cl | co, field name
|
|
lua_rawget(L, -2); // Stack: mt, self | cl | co, field | nil
|
|
lua_remove(L, -2); // Stack: mt, field | nil
|
|
if (! lua_isnil(L, -1)) // Stack: mt, field
|
|
{
|
|
lua_remove(L, -2); // Stack: field
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
lua_pop(L, 1); // Stack: mt
|
|
}
|
|
|
|
lua_pushvalue(L, 2); // Stack: mt, field name
|
|
lua_rawget(L, -2); // Stack: mt, field | nil
|
|
if (! lua_isnil(L, -1)) // Stack: mt, field
|
|
{
|
|
lua_remove(L, -2); // Stack: field
|
|
return 1;
|
|
}
|
|
|
|
LUABRIDGE_ASSERT(lua_isnil(L, -1)); // Stack: mt, nil
|
|
lua_pop(L, 1); // Stack: mt
|
|
|
|
// Repeat the lookup in the index extensible, for method overrides
|
|
const Options options = get_class_options(L, -1); // Stack: mt
|
|
if (options.test(extensibleClass | allowOverridingMethods))
|
|
{
|
|
if (auto result = try_call_index_extensible<IsObject>(L, key))
|
|
return *result;
|
|
}
|
|
|
|
// Try in the propget key
|
|
lua_rawgetp_x(L, -1, getPropgetKey()); // Stack: mt, propget table (pg)
|
|
LUABRIDGE_ASSERT(lua_istable(L, -1));
|
|
|
|
lua_pushvalue(L, 2); // Stack: mt, pg, field name
|
|
lua_rawget(L, -2); // Stack: mt, pg, getter | nil
|
|
lua_remove(L, -2); // Stack: mt, getter | nil
|
|
|
|
if (lua_iscfunction(L, -1)) // Stack: mt, getter
|
|
{
|
|
lua_remove(L, -2); // Stack: getter
|
|
lua_pushvalue(L, 1); // Stack: getter, table | userdata
|
|
lua_call(L, 1, 1); // Stack: value
|
|
return 1;
|
|
}
|
|
|
|
LUABRIDGE_ASSERT(lua_isnil(L, -1)); // Stack: mt, nil
|
|
lua_pop(L, 1); // Stack: mt
|
|
|
|
// It may mean that the field may be in const table and it's constness violation.
|
|
|
|
// Search flattened parent list in declaration-order DFS.
|
|
lua_rawgetp_x(L, -1, getParentKey()); // Stack: mt, parent list | nil
|
|
|
|
if (lua_istable(L, -1))
|
|
{
|
|
const int parentListIndex = lua_absindex(L, -1);
|
|
const int parentCount = get_length(L, parentListIndex);
|
|
|
|
for (int i = 1; i <= parentCount; ++i)
|
|
{
|
|
lua_rawgeti(L, parentListIndex, i); // Stack: mt, parent list, parent mt
|
|
if (! lua_istable(L, -1))
|
|
{
|
|
lua_pop(L, 1);
|
|
continue;
|
|
}
|
|
|
|
lua_pushvalue(L, 2); // Stack: mt, parent list, parent mt, field name
|
|
lua_rawget(L, -2); // Stack: mt, parent list, parent mt, field | nil
|
|
if (! lua_isnil(L, -1))
|
|
{
|
|
lua_remove(L, -2); // Stack: mt, parent list, field
|
|
lua_remove(L, -2); // Stack: mt, field
|
|
lua_remove(L, -2); // Stack: field
|
|
return 1;
|
|
}
|
|
lua_pop(L, 1); // Stack: mt, parent list, parent mt
|
|
|
|
lua_rawgetp_x(L, -1, getPropgetKey()); // Stack: mt, parent list, parent mt, pg | nil
|
|
if (lua_istable(L, -1))
|
|
{
|
|
lua_pushvalue(L, 2); // Stack: mt, parent list, parent mt, pg, field name
|
|
lua_rawget(L, -2); // Stack: mt, parent list, parent mt, pg, getter | nil
|
|
lua_remove(L, -2); // Stack: mt, parent list, parent mt, getter | nil
|
|
if (lua_iscfunction(L, -1))
|
|
{
|
|
lua_remove(L, -2); // Stack: mt, parent list, getter
|
|
lua_remove(L, -2); // Stack: mt, getter
|
|
lua_remove(L, -2); // Stack: getter
|
|
lua_pushvalue(L, 1); // Stack: getter, table | userdata
|
|
lua_call(L, 1, 1); // Stack: value
|
|
return 1;
|
|
}
|
|
|
|
lua_pop(L, 1); // Stack: mt, parent list, parent mt
|
|
}
|
|
else
|
|
{
|
|
lua_pop(L, 1); // Stack: mt, parent list, parent mt
|
|
}
|
|
|
|
lua_pop(L, 1); // Stack: mt, parent list
|
|
}
|
|
}
|
|
|
|
lua_pop(L, 2); // Stack: -
|
|
break;
|
|
}
|
|
|
|
lua_getmetatable(L, 1); // Stack: class/const table (mt)
|
|
LUABRIDGE_ASSERT(lua_istable(L, -1));
|
|
|
|
const Options options = get_class_options(L, -1); // Stack: mt
|
|
if (options.test(extensibleClass | ~allowOverridingMethods))
|
|
{
|
|
if (auto result = try_call_index_extensible<IsObject>(L, key))
|
|
return *result;
|
|
}
|
|
|
|
if (auto result = try_call_parent_index_fallback<IsObject>(L, key))
|
|
return *result;
|
|
|
|
lua_pop(L, 1); // Stack: -
|
|
lua_pushnil(L);
|
|
return 1;
|
|
|
|
// no return
|
|
}
|
|
|
|
template <bool IsObject>
|
|
inline int index_metamethod_simple(lua_State* L)
|
|
{
|
|
#if LUABRIDGE_SAFE_STACK_CHECKS
|
|
luaL_checkstack(L, 3, detail::error_lua_stack_overflow);
|
|
#endif
|
|
|
|
LUABRIDGE_ASSERT(lua_istable(L, 1) || lua_isuserdata(L, 1));
|
|
|
|
// Fast path for simple userdata objects: when registration provides propget and
|
|
// method tables as closure upvalues, avoid metatable/pointer-key lookups.
|
|
if constexpr (IsObject)
|
|
{
|
|
if (lua_isuserdata(L, 1))
|
|
{
|
|
const char* key = lua_tostring(L, 2);
|
|
|
|
const auto rawlookup = [L](int tableIndex)
|
|
{
|
|
lua_pushvalue(L, 2);
|
|
lua_rawget(L, tableIndex);
|
|
};
|
|
|
|
rawlookup(lua_upvalueindex(1)); // Stack: getter | nil
|
|
if (lua_iscfunction(L, -1))
|
|
{
|
|
lua_pushvalue(L, 1); // Stack: getter, self
|
|
lua_call(L, 1, 1); // Stack: value
|
|
return 1;
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
if (key != nullptr && is_metamethod(key))
|
|
{
|
|
lua_pushnil(L);
|
|
return 1;
|
|
}
|
|
|
|
rawlookup(lua_upvalueindex(2)); // Stack: value | nil
|
|
if (! lua_isnil(L, -1))
|
|
return 1;
|
|
|
|
lua_pop(L, 1);
|
|
lua_pushnil(L);
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Fast path for simple static tables: capture propget/class/metatable as upvalues.
|
|
if (lua_istable(L, 1))
|
|
{
|
|
const char* key = lua_tostring(L, 2);
|
|
|
|
const auto rawlookup = [L](int tableIndex)
|
|
{
|
|
lua_pushvalue(L, 2);
|
|
lua_rawget(L, tableIndex);
|
|
};
|
|
|
|
if (key != nullptr && is_metamethod(key))
|
|
{
|
|
lua_pushnil(L);
|
|
return 1;
|
|
}
|
|
|
|
if (lua_istable(L, lua_upvalueindex(2)))
|
|
{
|
|
rawlookup(lua_upvalueindex(2)); // Stack: value | nil
|
|
if (! lua_isnil(L, -1))
|
|
return 1;
|
|
|
|
lua_pop(L, 1);
|
|
}
|
|
|
|
rawlookup(lua_upvalueindex(3)); // Stack: value | nil
|
|
if (! lua_isnil(L, -1))
|
|
return 1;
|
|
|
|
lua_pop(L, 1);
|
|
|
|
rawlookup(lua_upvalueindex(1)); // Stack: getter | nil
|
|
if (lua_iscfunction(L, -1))
|
|
{
|
|
lua_pushvalue(L, 1); // Stack: getter, self
|
|
lua_call(L, 1, 1); // Stack: value
|
|
return 1;
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
lua_pushnil(L);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
lua_getmetatable(L, 1); // Stack: mt
|
|
LUABRIDGE_ASSERT(lua_istable(L, -1));
|
|
|
|
const char* key = lua_tostring(L, 2);
|
|
|
|
const auto rawlookup = [L]()
|
|
{
|
|
lua_pushvalue(L, 2);
|
|
lua_rawget(L, -2);
|
|
};
|
|
|
|
// For userdata instance property access, checking propget first avoids an always-miss lookup
|
|
// in the class table for common property keys.
|
|
if (lua_isuserdata(L, 1))
|
|
{
|
|
lua_rawgetp_x(L, -1, getPropgetKey()); // Stack: mt, pg
|
|
LUABRIDGE_ASSERT(lua_istable(L, -1));
|
|
|
|
rawlookup(); // Stack: mt, pg, getter | nil
|
|
lua_remove(L, -2); // Stack: mt, getter | nil
|
|
|
|
if (lua_iscfunction(L, -1))
|
|
{
|
|
lua_remove(L, -2); // Stack: getter
|
|
lua_pushvalue(L, 1); // Stack: getter, self
|
|
lua_call(L, 1, 1); // Stack: value
|
|
return 1;
|
|
}
|
|
|
|
lua_pop(L, 1); // Stack: mt
|
|
}
|
|
if (key != nullptr && is_metamethod(key))
|
|
{
|
|
lua_pushnil(L);
|
|
return 1;
|
|
}
|
|
|
|
if (lua_istable(L, 1))
|
|
{
|
|
if constexpr (IsObject)
|
|
lua_pushvalue(L, 1); // Stack: mt, self
|
|
else
|
|
push_class_or_const_table(L, -1); // Stack: mt, cl | co
|
|
|
|
if (lua_istable(L, -1))
|
|
{
|
|
rawlookup(); // Stack: mt, self | cl | co, value | nil
|
|
lua_remove(L, -2); // Stack: mt, value | nil
|
|
if (! lua_isnil(L, -1))
|
|
{
|
|
lua_remove(L, -2); // Stack: value
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
lua_pop(L, 1); // Stack: mt
|
|
}
|
|
|
|
rawlookup(); // Stack: mt, value | nil
|
|
if (! lua_isnil(L, -1))
|
|
{
|
|
lua_remove(L, -2); // Stack: value
|
|
return 1;
|
|
}
|
|
|
|
lua_pop(L, 1); // Stack: mt
|
|
|
|
lua_rawgetp_x(L, -1, getPropgetKey()); // Stack: mt, pg
|
|
LUABRIDGE_ASSERT(lua_istable(L, -1));
|
|
|
|
rawlookup(); // Stack: mt, pg, getter | nil
|
|
lua_remove(L, -2); // Stack: mt, getter | nil
|
|
|
|
if (lua_iscfunction(L, -1))
|
|
{
|
|
lua_remove(L, -2); // Stack: getter
|
|
lua_pushvalue(L, 1); // Stack: getter, self
|
|
lua_call(L, 1, 1); // Stack: value
|
|
return 1;
|
|
}
|
|
|
|
lua_pop(L, 2); // Stack: -
|
|
lua_pushnil(L);
|
|
return 1;
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief __newindex metamethod for non-static members.
|
|
*
|
|
* Retrieves properties from propset tables.
|
|
*/
|
|
|
|
inline std::optional<int> try_call_newindex_fallback(lua_State* L)
|
|
{
|
|
LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt
|
|
|
|
lua_rawgetp_x(L, -1, getNewIndexFallbackKey()); // Stack: mt, nifb (may be nil)
|
|
if (! lua_iscfunction(L, -1))
|
|
{
|
|
lua_pop(L, 1); // Stack: mt
|
|
return std::nullopt;
|
|
}
|
|
|
|
lua_pushvalue(L, 1); // stack: mt, nifb, arg1
|
|
lua_pushvalue(L, 2); // stack: mt, nifb, arg1, arg2
|
|
lua_pushvalue(L, 3); // stack: mt, nifb, arg1, arg2, arg3
|
|
lua_call(L, 3, 0); // stack: mt
|
|
|
|
return 0;
|
|
}
|
|
|
|
inline std::optional<int> try_call_static_newindex_fallback(lua_State* L)
|
|
{
|
|
LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt
|
|
|
|
lua_rawgetp_x(L, -1, getStaticNewIndexFallbackKey()); // Stack: mt, nifb (may be nil)
|
|
if (! lua_iscfunction(L, -1))
|
|
{
|
|
lua_pop(L, 1); // Stack: mt
|
|
return std::nullopt;
|
|
}
|
|
|
|
lua_pushvalue(L, 2); // stack: mt, nifb, arg1 (key only, no self for static)
|
|
lua_pushvalue(L, 3); // stack: mt, nifb, arg1, arg2 (value)
|
|
lua_call(L, 2, 0); // stack: mt
|
|
|
|
return 0;
|
|
}
|
|
|
|
inline std::optional<int> try_call_newindex_extensible(lua_State* L, const char* key)
|
|
{
|
|
LUABRIDGE_ASSERT(key != nullptr);
|
|
LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt
|
|
|
|
// For Lua function values (instance methods added from Lua), capture the originating class
|
|
// table so the method is stored there rather than in a traversed-to parent class table.
|
|
// This prevents e.g. `function Derived:init()` from polluting `Base`'s method table.
|
|
//
|
|
// For non-function values (static properties like `Class.prop = val`), the original
|
|
// traversal-end behaviour is preserved: storing them in the nearest class table that
|
|
// already has a matching key (or the topmost base if none exists). This keeps static
|
|
// properties out of the derived class table, which is also used for instance extensible
|
|
// method lookup, avoiding unintended shadowing of per-instance properties accessed via an
|
|
// index-fallback metamethod registered on a non-extensible base class.
|
|
const bool value_is_function = lua_isfunction(L, 3);
|
|
|
|
if (value_is_function)
|
|
{
|
|
// Capture the original (most-derived) class table — always write here.
|
|
push_class_or_const_table(L, -1); // Stack: mt, orig_ct | nil
|
|
if (! lua_istable(L, -1)) // Stack: mt, nil
|
|
{
|
|
lua_pop(L, 1); // Stack: mt
|
|
return std::nullopt;
|
|
}
|
|
// Stack: mt, orig_ct
|
|
|
|
const int mtIndex = lua_absindex(L, -2);
|
|
const int origClassTableIndex = lua_absindex(L, -1);
|
|
const auto process_metatable = [L, key, origClassTableIndex](int candidateMtIndex)
|
|
{
|
|
push_class_or_const_table(L, candidateMtIndex); // Stack: ..., candidate_ct | nil
|
|
if (! lua_istable(L, -1))
|
|
{
|
|
lua_pop(L, 1);
|
|
return false;
|
|
}
|
|
|
|
lua_pushvalue(L, 2); // Stack: ..., candidate_ct, field name
|
|
lua_rawget(L, -2); // Stack: ..., candidate_ct, field | nil
|
|
|
|
if (lua_isnil(L, -1))
|
|
{
|
|
lua_pop(L, 2); // Stack: ...
|
|
return false;
|
|
}
|
|
|
|
if (! lua_iscfunction(L, -1))
|
|
{
|
|
lua_pop(L, 2); // Stack: ...
|
|
return true;
|
|
}
|
|
|
|
const Options options = get_class_options(L, -2);
|
|
if (! options.test(allowOverridingMethods))
|
|
raise_lua_error(L, "immutable member '%s'", key);
|
|
|
|
rawset_super_method(L, origClassTableIndex, key); // Stack: ..., candidate_ct
|
|
lua_pop(L, 1); // Stack: ...
|
|
return true;
|
|
};
|
|
|
|
(void) process_metatable(mtIndex);
|
|
|
|
lua_rawgetp_x(L, mtIndex, getParentKey()); // Stack: mt, orig_ct, parent list | nil
|
|
if (lua_istable(L, -1))
|
|
{
|
|
const int parentListIndex = lua_absindex(L, -1);
|
|
const int parentCount = get_length(L, parentListIndex);
|
|
|
|
for (int i = 1; i <= parentCount; ++i)
|
|
{
|
|
lua_rawgeti(L, parentListIndex, i); // Stack: mt, orig_ct, parent list, parent mt
|
|
if (lua_istable(L, -1))
|
|
{
|
|
if (process_metatable(lua_absindex(L, -1)))
|
|
{
|
|
lua_pop(L, 1); // Stack: mt, orig_ct, parent list
|
|
break;
|
|
}
|
|
}
|
|
|
|
lua_pop(L, 1); // Stack: mt, orig_ct, parent list
|
|
}
|
|
}
|
|
|
|
lua_pop(L, 1); // Stack: mt, orig_ct
|
|
|
|
// Stack: mt, orig_ct — write to the original (most-derived) class table.
|
|
lua_getmetatable(L, -1); // Stack: mt, orig_ct, orig_ct_mt
|
|
lua_pushvalue(L, 3); // Stack: mt, orig_ct, orig_ct_mt, arg3
|
|
rawsetfield(L, -2, key); // Stack: mt, orig_ct, orig_ct_mt
|
|
lua_pop(L, 2); // Stack: mt
|
|
return 0;
|
|
}
|
|
|
|
// Non-function value (static property): use original traversal-end write location.
|
|
const int rootMetatableIndex = lua_absindex(L, -1);
|
|
lua_pushvalue(L, rootMetatableIndex); // Stack: mt, target mt
|
|
const int targetMetatableIndex = lua_absindex(L, -1);
|
|
|
|
const auto process_metatable = [L, key, targetMetatableIndex](int candidateMtIndex)
|
|
{
|
|
push_class_or_const_table(L, candidateMtIndex); // Stack: ..., candidate_ct | nil
|
|
if (! lua_istable(L, -1))
|
|
{
|
|
lua_pop(L, 1);
|
|
return false;
|
|
}
|
|
|
|
lua_pushvalue(L, 2); // Stack: ..., candidate_ct, field name
|
|
lua_rawget(L, -2); // Stack: ..., candidate_ct, field | nil
|
|
|
|
if (lua_isnil(L, -1))
|
|
{
|
|
lua_pop(L, 2); // Stack: ...
|
|
return false;
|
|
}
|
|
|
|
lua_pushvalue(L, candidateMtIndex);
|
|
lua_replace(L, targetMetatableIndex);
|
|
|
|
if (! lua_iscfunction(L, -1))
|
|
{
|
|
lua_pop(L, 2); // Stack: ...
|
|
return true;
|
|
}
|
|
|
|
const Options options = get_class_options(L, -2);
|
|
if (! options.test(allowOverridingMethods))
|
|
raise_lua_error(L, "immutable member '%s'", key);
|
|
|
|
rawset_super_method(L, -2, key); // Stack: ..., candidate_ct
|
|
lua_pop(L, 1); // Stack: ...
|
|
return true;
|
|
};
|
|
|
|
(void) process_metatable(rootMetatableIndex);
|
|
|
|
lua_rawgetp_x(L, rootMetatableIndex, getParentKey()); // Stack: mt, target mt, parent list | nil
|
|
if (lua_istable(L, -1))
|
|
{
|
|
const int parentListIndex = lua_absindex(L, -1);
|
|
const int parentCount = get_length(L, parentListIndex);
|
|
|
|
for (int i = 1; i <= parentCount; ++i)
|
|
{
|
|
lua_rawgeti(L, parentListIndex, i); // Stack: mt, target mt, parent list, parent mt
|
|
if (! lua_istable(L, -1))
|
|
{
|
|
lua_pop(L, 1);
|
|
continue;
|
|
}
|
|
|
|
// Preserve old traversal-end behavior when no member exists in the chain.
|
|
lua_pushvalue(L, -1);
|
|
lua_replace(L, targetMetatableIndex);
|
|
|
|
if (process_metatable(lua_absindex(L, -1)))
|
|
{
|
|
lua_pop(L, 1); // Stack: mt, target mt, parent list
|
|
break;
|
|
}
|
|
|
|
lua_pop(L, 1); // Stack: mt, target mt, parent list
|
|
}
|
|
}
|
|
|
|
lua_pop(L, 1); // Stack: mt, target mt
|
|
|
|
push_class_or_const_table(L, targetMetatableIndex); // Stack: mt, target mt, ct | nil
|
|
if (! lua_istable(L, -1)) // Stack: mt, target mt, nil
|
|
{
|
|
lua_pop(L, 2); // Stack: mt
|
|
return std::nullopt;
|
|
}
|
|
|
|
lua_getmetatable(L, -1); // Stack: mt, target mt, ct, ct_mt
|
|
lua_pushvalue(L, 3); // Stack: mt, target mt, ct, ct_mt, arg3
|
|
rawsetfield(L, -2, key); // Stack: mt, target mt, ct, ct_mt
|
|
lua_pop(L, 3); // Stack: mt
|
|
return 0;
|
|
}
|
|
|
|
template <bool IsObject>
|
|
inline std::optional<int> try_call_parent_newindex(lua_State* L)
|
|
{
|
|
LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt
|
|
|
|
lua_rawgetp_x(L, -1, getParentKey()); // Stack: mt, parent list | nil
|
|
if (! lua_istable(L, -1))
|
|
{
|
|
lua_pop(L, 1); // Stack: mt
|
|
return std::nullopt;
|
|
}
|
|
|
|
const int parentListIndex = lua_absindex(L, -1);
|
|
const int parentCount = get_length(L, parentListIndex);
|
|
|
|
for (int i = 1; i <= parentCount; ++i)
|
|
{
|
|
lua_rawgeti(L, parentListIndex, i); // Stack: mt, parent list, parent mt
|
|
if (! lua_istable(L, -1))
|
|
{
|
|
lua_pop(L, 1);
|
|
continue;
|
|
}
|
|
|
|
lua_rawgetp_x(L, -1, getPropsetKey()); // Stack: mt, parent list, parent mt, ps | nil
|
|
if (lua_istable(L, -1))
|
|
{
|
|
lua_pushvalue(L, 2); // Stack: mt, parent list, parent mt, ps, field name
|
|
lua_rawget(L, -2); // Stack: mt, parent list, parent mt, ps, setter | nil
|
|
lua_remove(L, -2); // Stack: mt, parent list, parent mt, setter | nil
|
|
|
|
if (lua_iscfunction(L, -1))
|
|
{
|
|
lua_remove(L, -2); // Stack: mt, parent list, setter
|
|
lua_remove(L, -2); // Stack: mt, setter
|
|
lua_remove(L, -2); // Stack: setter
|
|
|
|
if constexpr (IsObject)
|
|
lua_pushvalue(L, 1); // Stack: setter, table | userdata
|
|
lua_pushvalue(L, 3); // Stack: setter, table | userdata, new value
|
|
lua_call(L, IsObject ? 2 : 1, 0); // Stack: -
|
|
return 0;
|
|
}
|
|
|
|
lua_pop(L, 1); // Stack: mt, parent list, parent mt
|
|
}
|
|
else
|
|
{
|
|
lua_pop(L, 1); // Stack: mt, parent list, parent mt
|
|
}
|
|
|
|
lua_rawgetp_x(L, -1, getNewIndexFallbackKey()); // Stack: mt, parent list, parent mt, nifb | nil
|
|
if (lua_iscfunction(L, -1))
|
|
{
|
|
lua_pushvalue(L, 1); // Stack: mt, parent list, parent mt, nifb, arg1
|
|
lua_pushvalue(L, 2); // Stack: mt, parent list, parent mt, nifb, arg1, arg2
|
|
lua_pushvalue(L, 3); // Stack: mt, parent list, parent mt, nifb, arg1, arg2, arg3
|
|
lua_call(L, 3, 0); // Stack: mt, parent list, parent mt
|
|
|
|
lua_pop(L, 3); // Stack: -
|
|
return 0;
|
|
}
|
|
|
|
lua_pop(L, 2); // Stack: mt, parent list
|
|
}
|
|
|
|
lua_pop(L, 1); // Stack: mt
|
|
return std::nullopt;
|
|
}
|
|
|
|
template <bool IsObject>
|
|
inline int newindex_metamethod(lua_State* L)
|
|
{
|
|
#if LUABRIDGE_SAFE_STACK_CHECKS
|
|
luaL_checkstack(L, 6, detail::error_lua_stack_overflow);
|
|
#endif
|
|
|
|
LUABRIDGE_ASSERT(lua_istable(L, 1) || lua_isuserdata(L, 1)); // Stack (further not shown): table | userdata, name, new value
|
|
|
|
lua_getmetatable(L, 1); // Stack: metatable (mt)
|
|
LUABRIDGE_ASSERT(lua_istable(L, -1));
|
|
|
|
const char* key = lua_tostring(L, 2);
|
|
|
|
const Options options = get_class_options(L, -1);
|
|
|
|
// Try in the property set table on the current class first.
|
|
lua_rawgetp_x(L, -1, getPropsetKey()); // Stack: mt, propset table (ps) | nil
|
|
if (! lua_istable(L, -1))
|
|
raise_lua_error(L, "no member named '%s'", key);
|
|
|
|
lua_pushvalue(L, 2); // Stack: mt, ps, field name
|
|
lua_rawget(L, -2); // Stack: mt, ps, setter | nil
|
|
lua_remove(L, -2); // Stack: mt, setter | nil
|
|
|
|
if (lua_iscfunction(L, -1)) // Stack: mt, setter
|
|
{
|
|
lua_remove(L, -2); // Stack: setter
|
|
if constexpr (IsObject)
|
|
lua_pushvalue(L, 1); // Stack: setter, table | userdata
|
|
lua_pushvalue(L, 3); // Stack: setter, table | userdata, new value
|
|
lua_call(L, IsObject ? 2 : 1, 0); // Stack: -
|
|
return 0;
|
|
}
|
|
|
|
lua_pop(L, 1); // Stack: mt
|
|
|
|
if constexpr (IsObject)
|
|
{
|
|
if (auto result = try_call_newindex_fallback(L))
|
|
return *result;
|
|
}
|
|
else
|
|
{
|
|
if (auto result = try_call_static_newindex_fallback(L))
|
|
return *result;
|
|
|
|
if (options.test(extensibleClass))
|
|
{
|
|
// For static extensible writes of plain values, store directly on the class table.
|
|
// Function values still go through try_call_newindex_extensible to preserve super_* wiring.
|
|
if (! lua_isfunction(L, 3))
|
|
{
|
|
lua_pushvalue(L, 3);
|
|
rawsetfield(L, 1, key);
|
|
return 0;
|
|
}
|
|
|
|
if (auto result = try_call_newindex_extensible(L,key))
|
|
return *result;
|
|
}
|
|
}
|
|
|
|
// Try in the propget key
|
|
lua_rawgetp_x(L, -1, getPropsetKey()); // Stack: mt, propset table (ps)
|
|
if (lua_istable(L, -1))
|
|
{
|
|
lua_pushvalue(L, 2); // Stack: mt, ps, field name
|
|
lua_rawget(L, -2); // Stack: mt, ps, setter | nil
|
|
lua_remove(L, -2); // Stack: mt, setter | nil
|
|
|
|
if (lua_iscfunction(L, -1)) // Stack: mt, setter
|
|
{
|
|
lua_remove(L, -2); // Stack: setter
|
|
if constexpr (IsObject)
|
|
lua_pushvalue(L, 1); // Stack: setter, table | userdata
|
|
lua_pushvalue(L, 3); // Stack: setter, table | userdata, new value
|
|
lua_call(L, IsObject ? 2 : 1, 0); // Stack: -
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
lua_pop(L, 1); // Stack: mt
|
|
|
|
if (auto result = try_call_parent_newindex<IsObject>(L))
|
|
return *result;
|
|
|
|
if constexpr (IsObject)
|
|
{
|
|
// Parent metamethods should win; extensible storage is the final fallback.
|
|
if (options.test(extensibleClass))
|
|
{
|
|
if (auto result = try_call_newindex_extensible(L, key))
|
|
return *result;
|
|
}
|
|
}
|
|
|
|
lua_pop(L, 1); // Stack: -
|
|
raise_lua_error(L, "no writable member '%s'", key);
|
|
return 0;
|
|
}
|
|
|
|
template <bool IsObject>
|
|
inline int newindex_metamethod_simple(lua_State* L)
|
|
{
|
|
#if LUABRIDGE_SAFE_STACK_CHECKS
|
|
luaL_checkstack(L, 3, detail::error_lua_stack_overflow);
|
|
#endif
|
|
|
|
LUABRIDGE_ASSERT(lua_istable(L, 1) || lua_isuserdata(L, 1));
|
|
|
|
// Fast path for simple userdata objects: propset table is captured as upvalue.
|
|
if constexpr (IsObject)
|
|
{
|
|
if (lua_isuserdata(L, 1))
|
|
{
|
|
const char* key = lua_tostring(L, 2);
|
|
|
|
if (! lua_istable(L, lua_upvalueindex(1)))
|
|
raise_lua_error(L, "no writable member '%s'", key);
|
|
|
|
lua_pushvalue(L, 2); // Stack: key
|
|
lua_rawget(L, lua_upvalueindex(1)); // Stack: setter | nil
|
|
|
|
if (lua_iscfunction(L, -1))
|
|
{
|
|
lua_pushvalue(L, 1); // Stack: setter, self
|
|
lua_pushvalue(L, 3); // Stack: setter, self, value
|
|
lua_call(L, 2, 0);
|
|
return 0;
|
|
}
|
|
|
|
luaL_error(L, "no writable member '%s'", key);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Fast path for simple static tables: propset table is captured as upvalue.
|
|
if (lua_istable(L, 1))
|
|
{
|
|
const char* key = lua_tostring(L, 2);
|
|
|
|
if (! lua_istable(L, lua_upvalueindex(1)))
|
|
raise_lua_error(L, "no writable member '%s'", key);
|
|
|
|
lua_pushvalue(L, 2); // Stack: key
|
|
lua_rawget(L, lua_upvalueindex(1)); // Stack: setter | nil
|
|
|
|
if (lua_iscfunction(L, -1))
|
|
{
|
|
lua_pushvalue(L, 3); // Stack: setter, value
|
|
lua_call(L, 1, 0);
|
|
return 0;
|
|
}
|
|
|
|
raise_lua_error(L, "no writable member '%s'", key);
|
|
}
|
|
}
|
|
|
|
lua_getmetatable(L, 1); // Stack: mt
|
|
LUABRIDGE_ASSERT(lua_istable(L, -1));
|
|
|
|
const char* key = lua_tostring(L, 2);
|
|
|
|
lua_rawgetp_x(L, -1, getPropsetKey()); // Stack: mt, ps | nil
|
|
if (! lua_istable(L, -1))
|
|
raise_lua_error(L, "no member named '%s'", key);
|
|
|
|
lua_pushvalue(L, 2); // Stack: mt, ps, key
|
|
lua_rawget(L, -2); // Stack: mt, ps, setter | nil
|
|
lua_remove(L, -2); // Stack: mt, setter | nil
|
|
|
|
if (lua_iscfunction(L, -1))
|
|
{
|
|
lua_remove(L, -2); // Stack: setter
|
|
if constexpr (IsObject)
|
|
lua_pushvalue(L, 1); // Stack: setter, self
|
|
lua_pushvalue(L, 3); // Stack: setter, self, value
|
|
lua_call(L, IsObject ? 2 : 1, 0);
|
|
return 0;
|
|
}
|
|
|
|
raise_lua_error(L, "no writable member '%s'", key);
|
|
return 0;
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief lua_CFunction to report an error writing to a read-only value.
|
|
*
|
|
* The name of the variable is in the first upvalue.
|
|
*/
|
|
inline int read_only_error(lua_State* L)
|
|
{
|
|
raise_lua_error(L, "'%s' is read-only", lua_tostring(L, lua_upvalueindex(1)));
|
|
return 0;
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief __tostring metamethod for a class.
|
|
*/
|
|
template <class C>
|
|
int tostring_metamethod(lua_State* L)
|
|
{
|
|
const void* ptr = lua_topointer(L, 1);
|
|
|
|
lua_getmetatable(L, -1); // Stack: metatable (mt)
|
|
lua_rawgetp_x(L, -1, getTypeKey()); // Stack: mt, classname (cn)
|
|
lua_remove(L, -2); // Stack: cn
|
|
|
|
std::stringstream ss;
|
|
ss << ": 0x" << std::hex << reinterpret_cast<std::uintptr_t>(const_cast<void*>(ptr));
|
|
lua_pushstring(L, ss.str().c_str()); // Stack: cn, address string (astr)
|
|
lua_concat(L, 2); // Stack: astr
|
|
|
|
return 1;
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief __destruct metamethod for a class.
|
|
*/
|
|
template <class C>
|
|
int destruct_metamethod(lua_State* L)
|
|
{
|
|
LUABRIDGE_ASSERT(lua_isuserdata(L, 1)); // Stack: userdata (ud)
|
|
const auto top = lua_gettop(L);
|
|
|
|
const int result = lua_getmetatable(L, 1); // Stack: ud, object metatable (ot) | nothing
|
|
if (result == 0)
|
|
return 0;
|
|
|
|
LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: ud, ot
|
|
|
|
lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey<C>()); // Stack: ud, ot, registry metatable (rt) | nil
|
|
if (lua_istable(L, -1)) // Stack: ud, ot, rt
|
|
{
|
|
rawgetfield(L, -1, "__destruct"); // Stack: ud, ot, rt, ud, function | nil
|
|
if (lua_isfunction(L, -1))
|
|
{
|
|
lua_pushvalue(L, 1); // Stack: ud, ot, rt, function, ud
|
|
lua_pcall(L, 1, 0, 0); // Stack: ud, ot, rt
|
|
}
|
|
}
|
|
|
|
lua_settop(L, top); // Stack: ud
|
|
return 0;
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief __gc metamethod for a class.
|
|
*/
|
|
template <class C>
|
|
int gc_metamethod(lua_State* L)
|
|
{
|
|
destruct_metamethod<C>(L);
|
|
|
|
Userdata* ud = Userdata::getExact<C>(L, 1);
|
|
LUABRIDGE_ASSERT(ud);
|
|
|
|
ud->~Userdata();
|
|
|
|
return 0;
|
|
}
|
|
|
|
//=================================================================================================
|
|
|
|
template <class T, class C = void>
|
|
struct property_getter;
|
|
|
|
/**
|
|
* @brief lua_CFunction to get a variable.
|
|
*
|
|
* This is used for global variables or class static data members. The pointer to the data is in the first upvalue.
|
|
*/
|
|
template <class T>
|
|
struct property_getter<T, void>
|
|
{
|
|
static int call(lua_State* L)
|
|
{
|
|
LUABRIDGE_ASSERT(lua_islightuserdata(L, lua_upvalueindex(1)));
|
|
|
|
T* ptr = static_cast<T*>(lua_touserdata(L, lua_upvalueindex(1)));
|
|
LUABRIDGE_ASSERT(ptr != nullptr);
|
|
|
|
auto result = Stack<T&>::push(L, *ptr);
|
|
if (! result)
|
|
raise_lua_error(L, "%s", result.error_cstr());
|
|
|
|
return 1;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief lua_CFunction to get a class data member.
|
|
*
|
|
* The pointer-to-member is in the first upvalue. The class userdata object is at the top of the Lua stack.
|
|
*/
|
|
template <class T, class C>
|
|
struct property_getter
|
|
{
|
|
static int call(lua_State* L)
|
|
{
|
|
auto c = Userdata::get<C>(L, 1, true);
|
|
if (! c)
|
|
raise_lua_error(L, "%s", c.error_cstr());
|
|
|
|
T C::** mp = static_cast<T C::**>(lua_touserdata(L, lua_upvalueindex(1)));
|
|
|
|
Result result;
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
try
|
|
{
|
|
#endif
|
|
result = Stack<T&>::push(L, (*c)->**mp);
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
raise_lua_error(L, "%s", e.what());
|
|
}
|
|
#endif
|
|
|
|
if (! result)
|
|
raise_lua_error(L, "%s", result.error_cstr());
|
|
|
|
return 1;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief Helper function to push a property getter on a table at a specific index.
|
|
*/
|
|
inline void add_property_getter(lua_State* L, const char* name, int tableIndex)
|
|
{
|
|
#if LUABRIDGE_SAFE_STACK_CHECKS
|
|
luaL_checkstack(L, 2, detail::error_lua_stack_overflow);
|
|
#endif
|
|
|
|
LUABRIDGE_ASSERT(name != nullptr);
|
|
LUABRIDGE_ASSERT(lua_istable(L, tableIndex));
|
|
LUABRIDGE_ASSERT(lua_iscfunction(L, -1)); // Stack: getter
|
|
|
|
lua_rawgetp_x(L, tableIndex, getPropgetKey()); // Stack: getter, propget table (pg)
|
|
lua_pushvalue(L, -2); // Stack: getter, pg, getter
|
|
rawsetfield(L, -2, name); // Stack: getter, pg
|
|
lua_pop(L, 2); // Stack: -
|
|
}
|
|
|
|
//=================================================================================================
|
|
|
|
template <class T, class C = void>
|
|
struct property_setter;
|
|
|
|
/**
|
|
* @brief lua_CFunction to set a variable.
|
|
*
|
|
* This is used for global variables or class static data members. The pointer to the data is in the first upvalue.
|
|
*/
|
|
template <class T>
|
|
struct property_setter<T, void>
|
|
{
|
|
static int call(lua_State* L)
|
|
{
|
|
LUABRIDGE_ASSERT(lua_islightuserdata(L, lua_upvalueindex(1)));
|
|
|
|
T* ptr = static_cast<T*>(lua_touserdata(L, lua_upvalueindex(1)));
|
|
LUABRIDGE_ASSERT(ptr != nullptr);
|
|
|
|
auto result = Stack<T>::get(L, 1);
|
|
if (! result)
|
|
raise_lua_error(L, "%s", result.error_cstr());
|
|
|
|
*ptr = std::move(*result);
|
|
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief lua_CFunction to set a class data member.
|
|
*
|
|
* The pointer-to-member is in the first upvalue. The class userdata object is at the top of the Lua stack.
|
|
*/
|
|
template <class T, class C>
|
|
struct property_setter
|
|
{
|
|
static int call(lua_State* L)
|
|
{
|
|
auto c = Userdata::get<C>(L, 1, false);
|
|
if (! c)
|
|
raise_lua_error(L, "%s", c.error_cstr());
|
|
|
|
T C::** mp = static_cast<T C::**>(lua_touserdata(L, lua_upvalueindex(1)));
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
try
|
|
{
|
|
#endif
|
|
auto result = Stack<T>::get(L, 2);
|
|
if (! result)
|
|
raise_lua_error(L, "%s", result.error_cstr());
|
|
|
|
(*c)->** mp = std::move(*result);
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
raise_lua_error(L, "%s", e.what());
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief Helper function to push a property setter on a table at a specific index.
|
|
*/
|
|
inline void add_property_setter(lua_State* L, const char* name, int tableIndex)
|
|
{
|
|
#if LUABRIDGE_SAFE_STACK_CHECKS
|
|
luaL_checkstack(L, 2, detail::error_lua_stack_overflow);
|
|
#endif
|
|
|
|
LUABRIDGE_ASSERT(name != nullptr);
|
|
LUABRIDGE_ASSERT(lua_istable(L, tableIndex));
|
|
LUABRIDGE_ASSERT(lua_iscfunction(L, -1)); // Stack: setter
|
|
|
|
lua_rawgetp_x(L, tableIndex, getPropsetKey()); // Stack: setter, propset table (ps)
|
|
lua_pushvalue(L, -2); // Stack: setter, ps, setter
|
|
rawsetfield(L, -2, name); // Stack: setter, ps
|
|
lua_pop(L, 2); // Stack: -
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief Function generator.
|
|
*/
|
|
template <class ArgsPack, std::size_t Start, class F>
|
|
decltype(auto) invoke_callable_from_stack(lua_State* L, F&& func)
|
|
{
|
|
return std::apply(std::forward<F>(func), make_arguments_list<ArgsPack, Start>(L));
|
|
}
|
|
|
|
template <class ArgsPack, std::size_t Start, class T, class F>
|
|
decltype(auto) invoke_member_callable_from_stack(lua_State* L, T* ptr, F&& func)
|
|
{
|
|
return std::apply(
|
|
std::forward<F>(func),
|
|
std::tuple_cat(std::tuple<T*>(ptr), make_arguments_list<ArgsPack, Start>(L)));
|
|
}
|
|
|
|
template <class ReturnType, class ArgsPack, std::size_t Start = 1u>
|
|
struct function
|
|
{
|
|
template <class F>
|
|
static int call(lua_State* L, F&& func)
|
|
{
|
|
Result result;
|
|
int numResults = 1;
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
try
|
|
{
|
|
#endif
|
|
if constexpr (detail::is_tuple_v<ReturnType>)
|
|
{
|
|
numResults = static_cast<int>(std::tuple_size_v<ReturnType>);
|
|
result = detail::push_tuple(L, invoke_callable_from_stack<ArgsPack, Start>(L, std::forward<F>(func)));
|
|
}
|
|
else
|
|
{
|
|
result = Stack<ReturnType>::push(L, invoke_callable_from_stack<ArgsPack, Start>(L, std::forward<F>(func)));
|
|
}
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
raise_lua_error(L, "%s", e.what());
|
|
}
|
|
#endif
|
|
|
|
if (! result)
|
|
raise_lua_error(L, "%s", result.error_cstr());
|
|
|
|
return numResults;
|
|
}
|
|
|
|
template <class T, class F>
|
|
static int call(lua_State* L, T* ptr, F&& func)
|
|
{
|
|
Result result;
|
|
int numResults = 1;
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
try
|
|
{
|
|
#endif
|
|
if constexpr (detail::is_tuple_v<ReturnType>)
|
|
{
|
|
numResults = static_cast<int>(std::tuple_size_v<ReturnType>);
|
|
result = detail::push_tuple(L, invoke_member_callable_from_stack<ArgsPack, Start>(L, ptr, std::forward<F>(func)));
|
|
}
|
|
else
|
|
{
|
|
result = Stack<ReturnType>::push(L, invoke_member_callable_from_stack<ArgsPack, Start>(L, ptr, std::forward<F>(func)));
|
|
}
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
raise_lua_error(L, "%s", e.what());
|
|
}
|
|
#endif
|
|
|
|
if (! result)
|
|
raise_lua_error(L, "%s", result.error_cstr());
|
|
|
|
return numResults;
|
|
}
|
|
};
|
|
|
|
template <class ArgsPack, std::size_t Start>
|
|
struct function<std::tuple<>, ArgsPack, Start> : function<void, ArgsPack, Start>
|
|
{
|
|
template <class F>
|
|
static int call(lua_State* L, F&& func)
|
|
{
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
try
|
|
{
|
|
#endif
|
|
invoke_callable_from_stack<ArgsPack, Start>(L, std::forward<F>(func));
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
raise_lua_error(L, "%s", e.what());
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
template <class T, class F>
|
|
static int call(lua_State* L, T* ptr, F&& func)
|
|
{
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
try
|
|
{
|
|
#endif
|
|
invoke_member_callable_from_stack<ArgsPack, Start>(L, ptr, std::forward<F>(func));
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
raise_lua_error(L, "%s", e.what());
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
template <class ArgsPack, std::size_t Start>
|
|
struct function<void, ArgsPack, Start>
|
|
{
|
|
template <class F>
|
|
static int call(lua_State* L, F&& func)
|
|
{
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
try
|
|
{
|
|
#endif
|
|
invoke_callable_from_stack<ArgsPack, Start>(L, std::forward<F>(func));
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
raise_lua_error(L, "%s", e.what());
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
template <class T, class F>
|
|
static int call(lua_State* L, T* ptr, F&& func)
|
|
{
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
try
|
|
{
|
|
#endif
|
|
invoke_member_callable_from_stack<ArgsPack, Start>(L, ptr, std::forward<F>(func));
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
raise_lua_error(L, "%s", e.what());
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
template <class T>
|
|
TypeResult<T*> get_member_object(lua_State* L, bool canBeConst)
|
|
{
|
|
const int selfIndex = lua_absindex(L, 1);
|
|
|
|
if (lua_getmetatable(L, selfIndex))
|
|
{
|
|
if (lua_type(L, lua_upvalueindex(2)) == LUA_TTABLE && lua_rawequal(L, -1, lua_upvalueindex(2)))
|
|
{
|
|
lua_pop(L, 1);
|
|
return Userdata::getExactPointer<T>(L, selfIndex);
|
|
}
|
|
|
|
if (canBeConst && lua_type(L, lua_upvalueindex(3)) == LUA_TTABLE && lua_rawequal(L, -1, lua_upvalueindex(3)))
|
|
{
|
|
lua_pop(L, 1);
|
|
return Userdata::getExactPointer<T>(L, selfIndex);
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
}
|
|
|
|
return Userdata::get<T>(L, selfIndex, canBeConst);
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief lua_CFunction to call a class member function with a return value.
|
|
*
|
|
* The member function pointer is in the first upvalue. The class userdata object is at the top of the Lua stack.
|
|
*/
|
|
template <class F, class T>
|
|
int invoke_member_function(lua_State* L)
|
|
{
|
|
using FnTraits = function_traits<F>;
|
|
|
|
LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1)));
|
|
|
|
auto ptr = get_member_object<T>(L, false);
|
|
if (! ptr)
|
|
raise_lua_error(L, "%s", ptr.error_cstr());
|
|
|
|
const F& func = *static_cast<const F*>(lua_touserdata(L, lua_upvalueindex(1)));
|
|
LUABRIDGE_ASSERT(func != nullptr);
|
|
|
|
return function<typename FnTraits::result_type, typename FnTraits::argument_types, 2>::call(L, *ptr, func);
|
|
}
|
|
|
|
template <class F, class T>
|
|
int invoke_const_member_function(lua_State* L)
|
|
{
|
|
using FnTraits = function_traits<F>;
|
|
|
|
LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1)));
|
|
|
|
auto ptr = get_member_object<T>(L, true);
|
|
if (! ptr)
|
|
raise_lua_error(L, "%s", ptr.error_cstr());
|
|
|
|
const F& func = *static_cast<const F*>(lua_touserdata(L, lua_upvalueindex(1)));
|
|
LUABRIDGE_ASSERT(func != nullptr);
|
|
|
|
return function<typename FnTraits::result_type, typename FnTraits::argument_types, 2>::call(L, *ptr, func);
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief lua_CFunction to call a class member lua_CFunction.
|
|
*
|
|
* The member function pointer is in the first upvalue. The object userdata ('this') value is at top ot the Lua stack.
|
|
*/
|
|
template <class T>
|
|
int invoke_member_cfunction(lua_State* L)
|
|
{
|
|
using F = int (T::*)(lua_State* L);
|
|
|
|
LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1)));
|
|
|
|
auto t = Userdata::get<T>(L, 1, false);
|
|
if (! t)
|
|
raise_lua_error(L, "%s", t.error_cstr());
|
|
|
|
const F& func = *static_cast<const F*>(lua_touserdata(L, lua_upvalueindex(1)));
|
|
LUABRIDGE_ASSERT(func != nullptr);
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
try
|
|
{
|
|
#endif
|
|
return ((*t)->*func)(L);
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
raise_lua_error(L, "%s", e.what());
|
|
}
|
|
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
template <class T>
|
|
int invoke_const_member_cfunction(lua_State* L)
|
|
{
|
|
using F = int (T::*)(lua_State * L) const;
|
|
|
|
LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1)));
|
|
|
|
auto t = Userdata::get<T>(L, 1, true);
|
|
if (! t)
|
|
raise_lua_error(L, "%s", t.error_cstr());
|
|
|
|
const F& func = *static_cast<const F*>(lua_touserdata(L, lua_upvalueindex(1)));
|
|
LUABRIDGE_ASSERT(func != nullptr);
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
try
|
|
{
|
|
#endif
|
|
return ((*t)->*func)(L);
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
raise_lua_error(L, "%s", e.what());
|
|
}
|
|
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief lua_CFunction to call on a object via function pointer.
|
|
*
|
|
* The proxy function pointer (lightuserdata) is in the first upvalue. The class userdata object is at the top of the Lua stack.
|
|
*/
|
|
template <class F>
|
|
int invoke_proxy_function(lua_State* L)
|
|
{
|
|
using FnTraits = function_traits<F>;
|
|
|
|
LUABRIDGE_ASSERT(lua_islightuserdata(L, lua_upvalueindex(1)));
|
|
|
|
auto func = reinterpret_cast<F>(lua_touserdata(L, lua_upvalueindex(1)));
|
|
LUABRIDGE_ASSERT(func != nullptr);
|
|
|
|
return function<typename FnTraits::result_type, typename FnTraits::argument_types, 1>::call(L, func);
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief lua_CFunction to call on a object via functor (lambda wrapped in a std::function).
|
|
*
|
|
* The proxy std::function (lightuserdata) is in the first upvalue. The class userdata object is at the top of the Lua stack.
|
|
*/
|
|
template <class F>
|
|
int invoke_proxy_functor(lua_State* L)
|
|
{
|
|
using FnTraits = function_traits<std::remove_reference_t<F>>;
|
|
|
|
LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1)));
|
|
|
|
auto& func = *align<std::remove_reference_t<F>>(lua_touserdata(L, lua_upvalueindex(1)));
|
|
|
|
return function<typename FnTraits::result_type, typename FnTraits::argument_types, 1>::call(L, func);
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief lua_CFunction to call safely by trapping exceptions
|
|
*/
|
|
#if LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING && LUABRIDGE_HAS_EXCEPTIONS
|
|
inline int invoke_safe_cfunction(lua_State* L)
|
|
{
|
|
LUABRIDGE_ASSERT(lua_iscfunction(L, lua_upvalueindex(1)));
|
|
|
|
auto func = lua_tocfunction(L, lua_upvalueindex(1));
|
|
|
|
try
|
|
{
|
|
return func(L);
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
raise_lua_error(L, "%s", e.what());
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief lua_CFunction to call on a object constructor via functor (lambda wrapped in a std::function).
|
|
*
|
|
* The proxy std::function (lightuserdata) is in the first upvalue. The class userdata object will be pushed at the top of the Lua stack.
|
|
*/
|
|
template <class F>
|
|
int invoke_proxy_constructor(lua_State* L)
|
|
{
|
|
using FnTraits = function_traits<F>;
|
|
|
|
LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1)));
|
|
|
|
auto& func = *align<F>(lua_touserdata(L, lua_upvalueindex(1)));
|
|
|
|
function<void, typename FnTraits::argument_types, 1>::call(L, func);
|
|
|
|
return 1;
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief lua_CFunction to call on a object constructor via functor (lambda wrapped in a std::function).
|
|
*
|
|
* The proxy std::function (lightuserdata) is in the first upvalue. The class userdata object will be pushed at the top of the Lua stack.
|
|
*/
|
|
template <class F>
|
|
int invoke_proxy_destructor(lua_State* L)
|
|
{
|
|
using FnTraits = function_traits<F>;
|
|
|
|
LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1)));
|
|
|
|
auto& func = *align<F>(lua_touserdata(L, lua_upvalueindex(1)));
|
|
|
|
function<void, typename FnTraits::argument_types, 1>::call(L, func);
|
|
|
|
return 1;
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief C++ storage for a single overload entry: arity and optional type checker.
|
|
*
|
|
* Stored inside an OverloadSet which is kept as a Lua full userdata (auto-GC'd).
|
|
*/
|
|
struct OverloadEntry
|
|
{
|
|
using TypeChecker = bool (*)(lua_State*, int start);
|
|
|
|
int arity; // -1 for variadic (lua_CFunction): always attempt
|
|
TypeChecker checker; // nullptr for variadic: skip type pre-checking
|
|
};
|
|
|
|
/**
|
|
* @brief C++ storage for all overloads of a function.
|
|
*
|
|
* Stored as a Lua full userdata so it is GC'd automatically when the closure is collected.
|
|
* The actual function closures are stored separately in a flat Lua table (upvalue 2).
|
|
*/
|
|
struct OverloadSet
|
|
{
|
|
std::vector<OverloadEntry> entries;
|
|
};
|
|
|
|
/**
|
|
* @brief Check a single argument type, skipping lua_State* (auto-injected, not on the Lua stack).
|
|
*/
|
|
template <class T>
|
|
bool overload_check_one_arg(lua_State* L, int& idx)
|
|
{
|
|
if constexpr (std::is_pointer_v<T> &&
|
|
std::is_same_v<std::remove_const_t<std::remove_pointer_t<T>>, lua_State>)
|
|
{
|
|
return true; // lua_State* is auto-injected by LuaBridge, not a Lua-visible argument
|
|
}
|
|
else
|
|
{
|
|
return Stack<T>::isInstance(L, idx++);
|
|
}
|
|
}
|
|
|
|
template <class ArgsPack, std::size_t... I>
|
|
bool overload_check_args_impl(lua_State* L, int start, std::index_sequence<I...>)
|
|
{
|
|
[[maybe_unused]] int idx = start;
|
|
return (overload_check_one_arg<std::tuple_element_t<I, ArgsPack>>(L, idx) && ...);
|
|
}
|
|
|
|
template <class ArgsPack>
|
|
bool overload_check_args(lua_State* L, int start)
|
|
{
|
|
return overload_check_args_impl<ArgsPack>(L, start,
|
|
std::make_index_sequence<std::tuple_size_v<ArgsPack>>{});
|
|
}
|
|
|
|
/**
|
|
* @brief Type checker instantiable as an OverloadEntry::TypeChecker function pointer.
|
|
*
|
|
* Checks that the Lua stack arguments starting at @p start match the types in @tparam ArgsPack,
|
|
* using Stack<T>::isInstance without raising errors. lua_State* arguments are skipped.
|
|
*/
|
|
template <class ArgsPack>
|
|
bool overload_type_checker(lua_State* L, int start)
|
|
{
|
|
return overload_check_args<ArgsPack>(L, start);
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief lua_CFunction to resolve an invocation between several overloads.
|
|
*
|
|
* upvalue[1] = OverloadSet full userdata — C++ vector of {arity, type_checker} per overload.
|
|
* upvalue[2] = flat Lua table {[1]=func1, [2]=func2, ...} — the actual function closures.
|
|
*
|
|
* Dispatch:
|
|
* 1. Arity check in C++ (no Lua call).
|
|
* 2. Type check via Stack<T>::isInstance in C++ (no pcall) — skips clearly mismatched overloads.
|
|
* 3. Only calls lua_pcall for type-matched candidates, eliminating failed pcalls for type mismatches.
|
|
*/
|
|
template <bool Member>
|
|
inline int try_overload_functions(lua_State* L)
|
|
{
|
|
const int nargs = lua_gettop(L);
|
|
const int effective_args = nargs - (Member ? 1 : 0);
|
|
const int start_arg = Member ? 2 : 1;
|
|
|
|
LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1)));
|
|
auto* overload_set = align<OverloadSet>(lua_touserdata(L, lua_upvalueindex(1)));
|
|
|
|
// push flat functions table (upvalue 2)
|
|
lua_pushvalue(L, lua_upvalueindex(2));
|
|
LUABRIDGE_ASSERT(lua_istable(L, -1));
|
|
const int idx_funcs = nargs + 1;
|
|
|
|
// create table to hold error messages
|
|
lua_createtable(L, static_cast<int>(overload_set->entries.size()), 0);
|
|
const int idx_errors = nargs + 2;
|
|
int nerrors = 0;
|
|
|
|
for (int i = 0; i < static_cast<int>(overload_set->entries.size()); ++i)
|
|
{
|
|
const auto& entry = overload_set->entries[i];
|
|
|
|
// fast arity check (C++, no Lua calls)
|
|
if (entry.arity >= 0 && entry.arity != effective_args)
|
|
{
|
|
lua_pushfstring(L, "Skipped overload #%d with unmatched arity of %d instead of %d", i, entry.arity, effective_args);
|
|
lua_rawseti(L, idx_errors, ++nerrors);
|
|
continue;
|
|
}
|
|
|
|
// fast type check (C++, no pcall) — avoids expensive pcall for clearly mismatched types
|
|
if (entry.checker != nullptr && !entry.checker(L, start_arg))
|
|
{
|
|
lua_pushfstring(L, "Skipped overload #%d with unmatched argument types", i);
|
|
lua_rawseti(L, idx_errors, ++nerrors);
|
|
continue;
|
|
}
|
|
|
|
// O(1) function lookup from flat table
|
|
lua_rawgeti(L, idx_funcs, i + 1);
|
|
LUABRIDGE_ASSERT(lua_isfunction(L, -1));
|
|
|
|
// push arguments
|
|
for (int j = 1; j <= nargs; ++j)
|
|
lua_pushvalue(L, j);
|
|
|
|
// call f, this pops the function and its args, pushes result(s)
|
|
const int err = lua_pcall(L, nargs, LUA_MULTRET, 0);
|
|
if (err == LUABRIDGE_LUA_OK)
|
|
{
|
|
// 2 extra items on stack below results: idx_funcs, idx_errors
|
|
return lua_gettop(L) - nargs - 2;
|
|
}
|
|
else if (err == LUA_ERRRUN)
|
|
{
|
|
// store error message and try next overload
|
|
lua_rawseti(L, idx_errors, ++nerrors);
|
|
}
|
|
else
|
|
{
|
|
lua_error_x(L); // critical error: rethrow
|
|
}
|
|
}
|
|
|
|
lua_Debug debug;
|
|
lua_getstack_info_x(L, 0, "n", &debug);
|
|
lua_pushfstring(L, "All %d overloads of %s returned an error:", nerrors, debug.name);
|
|
|
|
// Concatenate error messages of each overload
|
|
for (int i = 1; i <= nerrors; ++i)
|
|
{
|
|
lua_pushfstring(L, "\n%d: ", i);
|
|
lua_rawgeti(L, idx_errors, i);
|
|
}
|
|
lua_concat(L, nerrors * 2 + 1);
|
|
|
|
const char* message = lua_tostring(L, -1);
|
|
raise_lua_error(L, "%s", message ? message : "");
|
|
}
|
|
|
|
//=================================================================================================
|
|
|
|
// Lua CFunction
|
|
inline void push_function(lua_State* L, lua_CFunction fp, const char* debugname)
|
|
{
|
|
#if LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING && LUABRIDGE_HAS_EXCEPTIONS
|
|
lua_pushcfunction_x(L, fp, debugname);
|
|
lua_pushcclosure_x(L, invoke_safe_cfunction, debugname, 1);
|
|
#else
|
|
lua_pushcfunction_x(L, fp, debugname);
|
|
#endif
|
|
}
|
|
|
|
// Generic function pointer
|
|
template <class ReturnType, class... Params>
|
|
inline void push_function(lua_State* L, ReturnType (*fp)(Params...), const char* debugname)
|
|
{
|
|
using FnType = decltype(fp);
|
|
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(fp));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<FnType>, debugname, 1);
|
|
}
|
|
|
|
template <class ReturnType, class... Params>
|
|
inline void push_function(lua_State* L, ReturnType (*fp)(Params...) noexcept, const char* debugname)
|
|
{
|
|
using FnType = decltype(fp);
|
|
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(fp));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<FnType>, debugname, 1);
|
|
}
|
|
|
|
// Callable object (lambdas)
|
|
template <class F, class = std::enable_if<is_callable_v<F> && !std::is_pointer_v<F> && !std::is_member_function_pointer_v<F>>>
|
|
inline void push_function(lua_State* L, F&& f, const char* debugname)
|
|
{
|
|
lua_newuserdata_aligned<F>(L, std::forward<F>(f));
|
|
lua_pushcclosure_x(L, &invoke_proxy_functor<F>, debugname, 1);
|
|
}
|
|
|
|
//=================================================================================================
|
|
// Lua CFunction
|
|
template <class T>
|
|
void push_member_function(lua_State* L, lua_CFunction fp, const char* debugname)
|
|
{
|
|
#if LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING && LUABRIDGE_HAS_EXCEPTIONS
|
|
lua_pushcfunction_x(L, fp, debugname);
|
|
lua_pushcclosure_x(L, invoke_safe_cfunction, debugname, 1);
|
|
#else
|
|
lua_pushcfunction_x(L, fp, debugname);
|
|
#endif
|
|
}
|
|
|
|
// Generic function pointer
|
|
template <class T, class ReturnType, class... Params>
|
|
void push_member_function(lua_State* L, ReturnType (*fp)(T*, Params...), const char* debugname)
|
|
{
|
|
using FnType = decltype(fp);
|
|
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(fp));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<FnType>, debugname, 1);
|
|
}
|
|
|
|
template <class T, class ReturnType, class... Params>
|
|
void push_member_function(lua_State* L, ReturnType (*fp)(T&, Params...), const char* debugname)
|
|
{
|
|
using FnType = decltype(fp);
|
|
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(fp));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<FnType>, debugname, 1);
|
|
}
|
|
|
|
template <class T, class ReturnType, class... Params>
|
|
void push_member_function(lua_State* L, ReturnType (*fp)(T*, Params...) noexcept, const char* debugname)
|
|
{
|
|
using FnType = decltype(fp);
|
|
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(fp));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<FnType>, debugname, 1);
|
|
}
|
|
|
|
template <class T, class ReturnType, class... Params>
|
|
void push_member_function(lua_State* L, ReturnType (*fp)(T&, Params...) noexcept, const char* debugname)
|
|
{
|
|
using FnType = decltype(fp);
|
|
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(fp));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<FnType>, debugname, 1);
|
|
}
|
|
|
|
template <class T, class ReturnType, class... Params>
|
|
void push_member_function(lua_State* L, ReturnType (*fp)(const T*, Params...), const char* debugname)
|
|
{
|
|
using FnType = decltype(fp);
|
|
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(fp));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<FnType>, debugname, 1);
|
|
}
|
|
|
|
template <class T, class ReturnType, class... Params>
|
|
void push_member_function(lua_State* L, ReturnType (*fp)(const T&, Params...), const char* debugname)
|
|
{
|
|
using FnType = decltype(fp);
|
|
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(fp));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<FnType>, debugname, 1);
|
|
}
|
|
|
|
template <class T, class ReturnType, class... Params>
|
|
void push_member_function(lua_State* L, ReturnType (*fp)(const T*, Params...) noexcept, const char* debugname)
|
|
{
|
|
using FnType = decltype(fp);
|
|
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(fp));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<FnType>, debugname, 1);
|
|
}
|
|
|
|
template <class T, class ReturnType, class... Params>
|
|
void push_member_function(lua_State* L, ReturnType (*fp)(const T&, Params...) noexcept, const char* debugname)
|
|
{
|
|
using FnType = decltype(fp);
|
|
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(fp));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<FnType>, debugname, 1);
|
|
}
|
|
|
|
// Callable object (lambdas)
|
|
template <class T, class F, class = std::enable_if<
|
|
is_callable_v<F> &&
|
|
std::is_object_v<F> &&
|
|
!std::is_pointer_v<F> &&
|
|
!std::is_member_function_pointer_v<F>>>
|
|
void push_member_function(lua_State* L, F&& f, const char* debugname)
|
|
{
|
|
static_assert(std::is_same_v<T, remove_cvref_t<std::remove_pointer_t<function_argument_or_void_t<0, F>>>>);
|
|
|
|
lua_newuserdata_aligned<F>(L, std::forward<F>(f));
|
|
lua_pushcclosure_x(L, &invoke_proxy_functor<F>, debugname, 1);
|
|
}
|
|
|
|
// Non const member function pointer
|
|
template <class T, class U, class ReturnType, class... Params>
|
|
void push_member_function(lua_State* L, ReturnType (U::*mfp)(Params...), const char* debugname)
|
|
{
|
|
static_assert(std::is_same_v<T, U> || std::is_base_of_v<U, T>);
|
|
|
|
using F = decltype(mfp);
|
|
|
|
new (lua_newuserdata_x<F>(L, sizeof(F))) F(mfp);
|
|
lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey<T>());
|
|
lua_pushcclosure_x(L, &invoke_member_function<F, T>, debugname, 2);
|
|
}
|
|
|
|
template <class T, class U, class ReturnType, class... Params>
|
|
void push_member_function(lua_State* L, ReturnType (U::*mfp)(Params...) noexcept, const char* debugname)
|
|
{
|
|
static_assert(std::is_same_v<T, U> || std::is_base_of_v<U, T>);
|
|
|
|
using F = decltype(mfp);
|
|
|
|
new (lua_newuserdata_x<F>(L, sizeof(F))) F(mfp);
|
|
lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey<T>());
|
|
lua_pushcclosure_x(L, &invoke_member_function<F, T>, debugname, 2);
|
|
}
|
|
|
|
// Const member function pointer
|
|
template <class T, class U, class ReturnType, class... Params>
|
|
void push_member_function(lua_State* L, ReturnType (U::*mfp)(Params...) const, const char* debugname)
|
|
{
|
|
static_assert(std::is_same_v<T, U> || std::is_base_of_v<U, T>);
|
|
|
|
using F = decltype(mfp);
|
|
|
|
new (lua_newuserdata_x<F>(L, sizeof(F))) F(mfp);
|
|
lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey<T>());
|
|
lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getConstRegistryKey<T>());
|
|
lua_pushcclosure_x(L, &detail::invoke_const_member_function<F, T>, debugname, 3);
|
|
}
|
|
|
|
template <class T, class U, class ReturnType, class... Params>
|
|
void push_member_function(lua_State* L, ReturnType (U::*mfp)(Params...) const noexcept, const char* debugname)
|
|
{
|
|
static_assert(std::is_same_v<T, U> || std::is_base_of_v<U, T>);
|
|
|
|
using F = decltype(mfp);
|
|
|
|
new (lua_newuserdata_x<F>(L, sizeof(F))) F(mfp);
|
|
lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey<T>());
|
|
lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getConstRegistryKey<T>());
|
|
lua_pushcclosure_x(L, &detail::invoke_const_member_function<F, T>, debugname, 3);
|
|
}
|
|
|
|
// Non const member Lua CFunction pointer
|
|
template <class T, class U = T>
|
|
void push_member_function(lua_State* L, int (U::*mfp)(lua_State*), const char* debugname)
|
|
{
|
|
static_assert(std::is_same_v<T, U> || std::is_base_of_v<U, T>);
|
|
|
|
using F = decltype(mfp);
|
|
|
|
new (lua_newuserdata_x<F>(L, sizeof(F))) F(mfp);
|
|
lua_pushcclosure_x(L, &invoke_member_cfunction<T>, debugname, 1);
|
|
}
|
|
|
|
// Const member Lua CFunction pointer
|
|
template <class T, class U = T>
|
|
void push_member_function(lua_State* L, int (U::*mfp)(lua_State*) const, const char* debugname)
|
|
{
|
|
static_assert(std::is_same_v<T, U> || std::is_base_of_v<U, T>);
|
|
|
|
using F = decltype(mfp);
|
|
|
|
new (lua_newuserdata_x<F>(L, sizeof(F))) F(mfp);
|
|
lua_pushcclosure_x(L, &invoke_const_member_cfunction<T>, debugname, 1);
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief
|
|
*/
|
|
template <class T, class = std::enable_if_t<
|
|
(std::is_base_of_v<T, LuaRef> || !detail::is_callable_v<T*>)>>
|
|
void push_property_getter(lua_State* L, const T* value, const char* debugname)
|
|
{
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(const_cast<T*>(value)));
|
|
lua_pushcclosure_x(L, &property_getter<T>::call, debugname, 1);
|
|
}
|
|
|
|
template <class T>
|
|
void push_property_getter(lua_State* L, T (*getter)(), const char* debugname)
|
|
{
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(getter));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<T (*)()>, debugname, 1);
|
|
}
|
|
|
|
template <class T>
|
|
void push_property_getter(lua_State* L, T (*getter)() noexcept, const char* debugname)
|
|
{
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(getter));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<T (*)() noexcept>, debugname, 1);
|
|
}
|
|
|
|
template <class T, class = std::enable_if_t<
|
|
detail::is_callable_v<T>>>
|
|
void push_property_getter(lua_State* L, T getter, const char* debugname)
|
|
{
|
|
if constexpr (std::is_same_v<T, lua_CFunction>)
|
|
{
|
|
lua_pushcfunction_x(L, getter, debugname);
|
|
}
|
|
else
|
|
{
|
|
lua_newuserdata_aligned<T>(L, std::move(getter));
|
|
lua_pushcclosure_x(L, &invoke_proxy_functor<T>, debugname, 1);
|
|
}
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief
|
|
*/
|
|
template <class C, class T, class U>
|
|
void push_class_property_getter(lua_State* L, T (U::*value), const char* debugname)
|
|
{
|
|
using MemberValue = decltype(value);
|
|
|
|
new (lua_newuserdata_x<MemberValue>(L, sizeof(MemberValue))) MemberValue(value);
|
|
lua_pushcclosure_x(L, &property_getter<T, C>::call, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_getter(lua_State* L, T (C::*getter)() const, const char* debugname)
|
|
{
|
|
using GetType = decltype(getter);
|
|
|
|
new (lua_newuserdata_x<GetType>(L, sizeof(GetType))) GetType(getter);
|
|
lua_pushcclosure_x(L, &invoke_const_member_function<GetType, C>, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_getter(lua_State* L, T (C::*getter)() const noexcept, const char* debugname)
|
|
{
|
|
using GetType = decltype(getter);
|
|
|
|
new (lua_newuserdata_x<GetType>(L, sizeof(GetType))) GetType(getter);
|
|
lua_pushcclosure_x(L, &invoke_const_member_function<GetType, C>, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_getter(lua_State* L, T (C::*getter)(lua_State*) const, const char* debugname)
|
|
{
|
|
using GetType = decltype(getter);
|
|
|
|
new (lua_newuserdata_x<GetType>(L, sizeof(GetType))) GetType(getter);
|
|
lua_pushcclosure_x(L, &invoke_const_member_function<GetType, C>, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_getter(lua_State* L, T (C::*getter)(lua_State*) const noexcept, const char* debugname)
|
|
{
|
|
using GetType = decltype(getter);
|
|
|
|
new (lua_newuserdata_x<GetType>(L, sizeof(GetType))) GetType(getter);
|
|
lua_pushcclosure_x(L, &invoke_const_member_function<GetType, C>, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_getter(lua_State* L, T (*getter)(const C*), const char* debugname)
|
|
{
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(getter));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<T (*)(const C*)>, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_getter(lua_State* L, T (*getter)(const C&), const char* debugname)
|
|
{
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(getter));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<T (*)(const C&)>, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_getter(lua_State* L, T (*getter)(const C*, lua_State*), const char* debugname)
|
|
{
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(getter));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<T (*)(const C*, lua_State*)>, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_getter(lua_State* L, T (*getter)(const C&, lua_State*), const char* debugname)
|
|
{
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(getter));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<T (*)(const C&, lua_State*)>, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_getter(lua_State* L, T (*getter)(const C*) noexcept, const char* debugname)
|
|
{
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(getter));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<T (*)(const C*) noexcept>, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_getter(lua_State* L, T (*getter)(const C&) noexcept, const char* debugname)
|
|
{
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(getter));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<T (*)(const C&) noexcept>, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_getter(lua_State* L, T (*getter)(const C*, lua_State*) noexcept, const char* debugname)
|
|
{
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(getter));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<T (*)(const C*)>, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_getter(lua_State* L, T (*getter)(const C&, lua_State*) noexcept, const char* debugname)
|
|
{
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(getter));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<T (*)(const C&, lua_State*) noexcept>, debugname, 1);
|
|
}
|
|
|
|
template <class C>
|
|
void push_class_property_getter(lua_State* L, lua_CFunction getter, const char* debugname)
|
|
{
|
|
lua_pushcfunction_x(L, getter, debugname);
|
|
}
|
|
|
|
template <class C, class T, class = std::enable_if_t<
|
|
!std::is_pointer_v<T> && detail::is_callable_v<T>>>
|
|
void push_class_property_getter(lua_State* L, T getter, const char* debugname)
|
|
{
|
|
using FirstArg = detail::function_argument_t<0, T>;
|
|
static_assert(std::is_same_v<std::decay_t<std::remove_reference_t<std::remove_pointer_t<FirstArg>>>, C>);
|
|
|
|
lua_newuserdata_aligned<T>(L, std::move(getter));
|
|
lua_pushcclosure_x(L, &invoke_proxy_functor<T>, debugname, 1);
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief
|
|
*/
|
|
template <class T, class = std::enable_if_t<
|
|
(std::is_base_of_v<T, LuaRef> || !detail::is_callable_v<T*>)>>
|
|
void push_property_setter(lua_State* L, T* value, const char* debugname)
|
|
{
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(value));
|
|
lua_pushcclosure_x(L, &property_setter<T>::call, debugname, 1);
|
|
}
|
|
|
|
template <class T>
|
|
void push_property_setter(lua_State* L, void (*setter)(T), const char* debugname)
|
|
{
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(setter));
|
|
lua_pushcclosure_x(L, &detail::invoke_proxy_function<void (*)(T)>, debugname, 1);
|
|
}
|
|
|
|
template <class T>
|
|
void push_property_setter(lua_State* L, void (*setter)(T) noexcept, const char* debugname)
|
|
{
|
|
lua_pushlightuserdata(L, reinterpret_cast<void*>(setter));
|
|
lua_pushcclosure_x(L, &detail::invoke_proxy_function<void (*)(T) noexcept>, debugname, 1);
|
|
}
|
|
|
|
template <class T, class = std::enable_if_t<
|
|
detail::is_callable_v<T>>>
|
|
void push_property_setter(lua_State* L, T setter, const char* debugname)
|
|
{
|
|
if constexpr (std::is_same_v<T, lua_CFunction>)
|
|
{
|
|
lua_pushcfunction_x(L, setter, debugname);
|
|
}
|
|
else
|
|
{
|
|
lua_newuserdata_aligned<T>(L, std::move(setter));
|
|
lua_pushcclosure_x(L, &invoke_proxy_functor<T>, debugname, 1);
|
|
}
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief
|
|
*/
|
|
template <class C, class T, class U>
|
|
void push_class_property_setter(lua_State* L, T U::*value, const char* debugname)
|
|
{
|
|
using MemberValue = decltype(value);
|
|
|
|
new (lua_newuserdata_x<MemberValue>(L, sizeof(MemberValue))) MemberValue(value);
|
|
lua_pushcclosure_x(L, &property_setter<T, C>::call, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_setter(lua_State* L, void (C::*setter)(T), const char* debugname)
|
|
{
|
|
using SetType = decltype(setter);
|
|
|
|
new (lua_newuserdata_x<SetType>(L, sizeof(SetType))) SetType(setter);
|
|
lua_pushcclosure_x(L, &invoke_member_function<SetType, C>, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_setter(lua_State* L, void (C::*setter)(T) noexcept, const char* debugname)
|
|
{
|
|
using SetType = decltype(setter);
|
|
|
|
new (lua_newuserdata_x<SetType>(L, sizeof(SetType))) SetType(setter);
|
|
lua_pushcclosure_x(L, &invoke_member_function<SetType, C>, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_setter(lua_State* L, void (C::*setter)(T, lua_State*), const char* debugname)
|
|
{
|
|
using SetType = decltype(setter);
|
|
|
|
new (lua_newuserdata_x<SetType>(L, sizeof(SetType))) SetType(setter);
|
|
lua_pushcclosure_x(L, &invoke_member_function<SetType, C>, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_setter(lua_State* L, void (C::*setter)(T, lua_State*) noexcept, const char* debugname)
|
|
{
|
|
using SetType = decltype(setter);
|
|
|
|
new (lua_newuserdata_x<SetType>(L, sizeof(SetType))) SetType(setter);
|
|
lua_pushcclosure_x(L, &invoke_member_function<SetType, C>, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_setter(lua_State* L, void (*setter)(C*, T), const char* debugname)
|
|
{
|
|
lua_pushlightuserdata( L, reinterpret_cast<void*>(setter));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<void (*)(C*, T)>, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_setter(lua_State* L, void (*setter)(C&, T), const char* debugname)
|
|
{
|
|
lua_pushlightuserdata( L, reinterpret_cast<void*>(setter));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<void (*)(C&, T)>, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_setter(lua_State* L, void (*setter)(C*, T, lua_State*), const char* debugname)
|
|
{
|
|
lua_pushlightuserdata( L, reinterpret_cast<void*>(setter));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<void (*)(C*, T, lua_State*)>, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_setter(lua_State* L, void (*setter)(C&, T, lua_State*), const char* debugname)
|
|
{
|
|
lua_pushlightuserdata( L, reinterpret_cast<void*>(setter));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<void (*)(C&, T, lua_State*)>, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_setter(lua_State* L, void (*setter)(C*, T) noexcept, const char* debugname)
|
|
{
|
|
lua_pushlightuserdata( L, reinterpret_cast<void*>(setter));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<void (*)(C*, T) noexcept>, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_setter(lua_State* L, void (*setter)(C&, T) noexcept, const char* debugname)
|
|
{
|
|
lua_pushlightuserdata( L, reinterpret_cast<void*>(setter));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<void (*)(C&, T) noexcept>, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_setter(lua_State* L, void (*setter)(C*, T, lua_State*) noexcept, const char* debugname)
|
|
{
|
|
lua_pushlightuserdata( L, reinterpret_cast<void*>(setter));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<void (*)(C*, T, lua_State*) noexcept>, debugname, 1);
|
|
}
|
|
|
|
template <class C, class T>
|
|
void push_class_property_setter(lua_State* L, void (*setter)(C&, T, lua_State*) noexcept, const char* debugname)
|
|
{
|
|
lua_pushlightuserdata( L, reinterpret_cast<void*>(setter));
|
|
lua_pushcclosure_x(L, &invoke_proxy_function<void (*)(C&, T, lua_State*) noexcept>, debugname, 1);
|
|
}
|
|
|
|
template <class C>
|
|
void push_class_property_setter(lua_State* L, lua_CFunction setter, const char* debugname)
|
|
{
|
|
lua_pushcfunction_x(L, setter, debugname);
|
|
}
|
|
|
|
template <class C, class T, class = std::enable_if_t<
|
|
!std::is_pointer_v<T> && detail::is_callable_v<T>>>
|
|
void push_class_property_setter(lua_State* L, T setter, const char* debugname)
|
|
{
|
|
using FirstArg = detail::function_argument_t<0, T>;
|
|
static_assert(std::is_same_v<std::decay_t<std::remove_pointer_t<FirstArg>>, C>);
|
|
|
|
lua_newuserdata_aligned<T>(L, std::move(setter));
|
|
lua_pushcclosure_x(L, &invoke_proxy_functor<T>, debugname, 1);
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief
|
|
*/
|
|
inline void push_property_readonly(lua_State* L, const char* debugname)
|
|
{
|
|
lua_pushstring(L, debugname);
|
|
lua_pushcclosure_x(L, &detail::read_only_error, debugname, 1);
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief Constructor generators.
|
|
*
|
|
* These templates call operator new with the contents of a type/value list passed to the constructor. Two versions of call() are provided.
|
|
* One performs a regular new, the other performs a placement new.
|
|
*/
|
|
template <class T, class Args>
|
|
struct constructor
|
|
{
|
|
static T* construct(const Args& args)
|
|
{
|
|
auto alloc = [](auto&&... args) { return new T(std::forward<decltype(args)>(args)...); };
|
|
|
|
return std::apply(alloc, args);
|
|
}
|
|
|
|
static T* construct(void* ptr, const Args& args)
|
|
{
|
|
auto alloc = [ptr](auto&&... args) { return new (ptr) T(std::forward<decltype(args)>(args)...); };
|
|
|
|
return std::apply(alloc, args);
|
|
}
|
|
};
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief Placement constructor generators.
|
|
*/
|
|
template <class T>
|
|
struct placement_constructor
|
|
{
|
|
template <class F, class Args>
|
|
static T* construct(void* ptr, F&& func, const Args& args)
|
|
{
|
|
auto alloc = [ptr, func = std::forward<F>(func)](auto&&... args) { return func(ptr, std::forward<decltype(args)>(args)...); };
|
|
|
|
return std::apply(alloc, args);
|
|
}
|
|
};
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief Container allocator generators.
|
|
*/
|
|
template <class C>
|
|
struct container_constructor
|
|
{
|
|
template <class F, class Args>
|
|
static C construct(F&& func, const Args& args)
|
|
{
|
|
auto alloc = [func = std::forward<F>(func)](auto&&... args) { return func(std::forward<decltype(args)>(args)...); };
|
|
|
|
return std::apply(alloc, args);
|
|
}
|
|
};
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief External allocator generators.
|
|
*/
|
|
template <class T>
|
|
struct external_constructor
|
|
{
|
|
template <class F, class Args>
|
|
static T* construct(F&& func, const Args& args)
|
|
{
|
|
auto alloc = [func = std::forward<F>(func)](auto&&... args) { return func(std::forward<decltype(args)>(args)...); };
|
|
|
|
return std::apply(alloc, args);
|
|
}
|
|
};
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief lua_CFunction to construct a class object wrapped in a container.
|
|
*/
|
|
template <class C, class Args>
|
|
int constructor_container_proxy(lua_State* L)
|
|
{
|
|
using T = typename ContainerTraits<C>::Type;
|
|
|
|
T* object = nullptr;
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
try
|
|
{
|
|
#endif
|
|
object = constructor<T, Args>::construct(make_arguments_list<Args, 2>(L));
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
raise_lua_error(L, "%s", e.what());
|
|
}
|
|
#endif
|
|
|
|
auto result = UserdataSharedHelper<C, false>::push(L, object);
|
|
if (! result)
|
|
raise_lua_error(L, "%s", result.error_cstr());
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* @brief lua_CFunction to construct a class object in-place in the userdata.
|
|
*/
|
|
template <class T, class Args>
|
|
int constructor_placement_proxy(lua_State* L)
|
|
{
|
|
auto args = make_arguments_list<Args, 2>(L);
|
|
|
|
std::error_code ec;
|
|
auto* value = UserdataValue<T>::place(L, ec);
|
|
if (! value)
|
|
raise_lua_error(L, "%s", detail::ErrorCategory::errorString(ec.value()));
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
try
|
|
{
|
|
#endif
|
|
|
|
constructor<T, Args>::construct(value->getObject(), std::move(args));
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
raise_lua_error(L, "%s", e.what());
|
|
}
|
|
#endif
|
|
|
|
value->commit();
|
|
|
|
return 1;
|
|
}
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief Constructor forwarder.
|
|
*/
|
|
template <class T, class F>
|
|
struct constructor_forwarder
|
|
{
|
|
explicit constructor_forwarder(F f)
|
|
: m_func(std::move(f))
|
|
{
|
|
}
|
|
|
|
T* operator()(lua_State* L)
|
|
{
|
|
using FnTraits = function_traits<F>;
|
|
using FnArgs = remove_first_type_t<typename FnTraits::argument_types>;
|
|
|
|
auto args = make_arguments_list<FnArgs, 2>(L);
|
|
|
|
std::error_code ec;
|
|
auto* value = UserdataValue<T>::place(L, ec);
|
|
if (! value)
|
|
raise_lua_error(L, "%s", detail::ErrorCategory::errorString(ec.value()));
|
|
|
|
T* object = nullptr;
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
try
|
|
{
|
|
#endif
|
|
object = placement_constructor<T>::construct(value->getObject(), m_func, std::move(args));
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
raise_lua_error(L, "%s", e.what());
|
|
}
|
|
#endif
|
|
|
|
value->commit();
|
|
|
|
return object;
|
|
}
|
|
|
|
private:
|
|
F m_func;
|
|
};
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief Constructor forwarder.
|
|
*/
|
|
template <class T, class F>
|
|
struct destructor_forwarder
|
|
{
|
|
explicit destructor_forwarder(F f)
|
|
: m_func(std::move(f))
|
|
{
|
|
}
|
|
|
|
void operator()(lua_State* L)
|
|
{
|
|
auto value = Userdata::get<T>(L, -1, false);
|
|
if (! value)
|
|
raise_lua_error(L, "%s", value.error_cstr());
|
|
|
|
std::invoke(m_func, *value);
|
|
}
|
|
|
|
private:
|
|
F m_func;
|
|
};
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief Constructor forwarder.
|
|
*/
|
|
template <class T, class Alloc, class Dealloc>
|
|
struct factory_forwarder
|
|
{
|
|
explicit factory_forwarder(Alloc alloc, Dealloc dealloc)
|
|
: m_alloc(std::move(alloc))
|
|
, m_dealloc(std::move(dealloc))
|
|
{
|
|
}
|
|
|
|
T* operator()(lua_State* L)
|
|
{
|
|
using FnTraits = function_traits<Alloc>;
|
|
using FnArgs = typename FnTraits::argument_types;
|
|
|
|
T* object = nullptr;
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
try
|
|
{
|
|
#endif
|
|
object = external_constructor<T>::construct(m_alloc, make_arguments_list<FnArgs, 0>(L));
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
raise_lua_error(L, "%s", e.what());
|
|
}
|
|
#endif
|
|
|
|
std::error_code ec;
|
|
auto* value = UserdataValueExternal<T>::place(L, object, m_dealloc, ec);
|
|
if (! value)
|
|
raise_lua_error(L, "%s", detail::ErrorCategory::errorString(ec.value()));
|
|
|
|
return object;
|
|
}
|
|
|
|
private:
|
|
Alloc m_alloc;
|
|
Dealloc m_dealloc;
|
|
};
|
|
|
|
//=================================================================================================
|
|
/**
|
|
* @brief Container forwarder.
|
|
*/
|
|
template <class C, class F>
|
|
struct container_forwarder
|
|
{
|
|
explicit container_forwarder(F f)
|
|
: m_func(std::move(f))
|
|
{
|
|
}
|
|
|
|
C operator()(lua_State* L)
|
|
{
|
|
using FnTraits = function_traits<F>;
|
|
using FnArgs = typename FnTraits::argument_types;
|
|
|
|
alignas(C) std::byte object_storage[sizeof(C)];
|
|
C* object = nullptr;
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
try
|
|
{
|
|
#endif
|
|
object = ::new (object_storage) C(
|
|
container_constructor<C>::construct(m_func, make_arguments_list<FnArgs, 2>(L)));
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
if (object != nullptr)
|
|
std::destroy_at(object);
|
|
|
|
raise_lua_error(L, "%s", e.what());
|
|
}
|
|
#endif
|
|
|
|
LUABRIDGE_ASSERT(object != nullptr);
|
|
|
|
Result result;
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
try
|
|
{
|
|
#endif
|
|
result = UserdataSharedHelper<C, false>::push(L, *object);
|
|
|
|
#if LUABRIDGE_HAS_EXCEPTIONS
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
std::destroy_at(object);
|
|
|
|
raise_lua_error(L, "%s", e.what());
|
|
}
|
|
#endif
|
|
|
|
if (! result)
|
|
{
|
|
std::destroy_at(object);
|
|
raise_lua_error(L, "%s", result.error_cstr());
|
|
}
|
|
|
|
C ret = std::move(*object);
|
|
std::destroy_at(object);
|
|
return ret;
|
|
}
|
|
|
|
private:
|
|
F m_func;
|
|
};
|
|
|
|
} // namespace detail
|
|
} // namespace luabridge
|