You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openmw/extern/sol3/sol/usertype_storage.hpp

1162 lines
45 KiB
C++

// sol2
// The MIT License (MIT)
// Copyright (c) 2013-2021 Rapptz, ThePhD and contributors
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#ifndef SOL_USERTYPE_STORAGE_HPP
#define SOL_USERTYPE_STORAGE_HPP
#include <sol/usertype_core.hpp>
#include <sol/make_reference.hpp>
#include <bitset>
#include <unordered_map>
namespace sol { namespace u_detail {
struct usertype_storage_base;
template <typename T>
struct usertype_storage;
optional<usertype_storage_base&> maybe_get_usertype_storage_base(lua_State* L_, int index);
usertype_storage_base& get_usertype_storage_base(lua_State* L_, const char* gcmetakey);
template <typename T>
optional<usertype_storage<T>&> maybe_get_usertype_storage(lua_State* L_);
template <typename T>
usertype_storage<T>& get_usertype_storage(lua_State* L_);
using index_call_function = int(lua_State*, void*);
using change_indexing_mem_func = void (usertype_storage_base::*)(
lua_State*, submetatable_type, void*, stateless_stack_reference&, lua_CFunction, lua_CFunction, lua_CFunction, lua_CFunction);
struct index_call_storage {
index_call_function* index;
index_call_function* new_index;
void* binding_data;
};
struct new_index_call_storage : index_call_storage {
void* new_binding_data;
};
struct binding_base {
virtual void* data() = 0;
virtual ~binding_base() {
}
};
template <typename K, typename Fq, typename T = void>
struct binding : binding_base {
using uF = meta::unqualified_t<Fq>;
using F = meta::conditional_t<meta::is_c_str_of_v<uF, char>
#if SOL_IS_ON(SOL_CHAR8_T_I_)
|| meta::is_c_str_of_v<uF, char8_t>
#endif
|| meta::is_c_str_of_v<uF, char16_t> || meta::is_c_str_of_v<uF, char32_t> || meta::is_c_str_of_v<uF, wchar_t>,
std::add_pointer_t<std::add_const_t<std::remove_all_extents_t<Fq>>>, std::decay_t<Fq>>;
F data_;
template <typename... Args>
binding(Args&&... args) : data_(std::forward<Args>(args)...) {
}
virtual void* data() override {
return static_cast<void*>(std::addressof(data_));
}
template <bool is_index = true, bool is_variable = false>
static inline int call_with_(lua_State* L_, void* target) {
constexpr int boost = !detail::is_non_factory_constructor<F>::value && std::is_same<K, call_construction>::value ? 1 : 0;
auto& f = *static_cast<F*>(target);
return call_detail::call_wrapped<T, is_index, is_variable, boost>(L_, f);
}
template <bool is_index = true, bool is_variable = false>
static inline int call_(lua_State* L_) {
void* f = stack::get<void*>(L_, upvalue_index(usertype_storage_index));
return call_with_<is_index, is_variable>(L_, f);
}
template <bool is_index = true, bool is_variable = false>
static inline int call(lua_State* L_) {
int r = detail::typed_static_trampoline<decltype(&call_<is_index, is_variable>), (&call_<is_index, is_variable>)>(L_);
if constexpr (meta::is_specialization_of_v<uF, yielding_t>) {
return lua_yield(L_, r);
}
else {
return r;
}
}
template <bool is_index = true, bool is_variable = false>
static inline int index_call_with_(lua_State* L_, void* target) {
if constexpr (!is_variable) {
if constexpr (is_lua_c_function_v<std::decay_t<F>>) {
auto& f = *static_cast<std::decay_t<F>*>(target);
return stack::push(L_, f);
}
else {
// set up upvalues
// for a chained call
int upvalues = 0;
upvalues += stack::push(L_, nullptr);
upvalues += stack::push(L_, target);
auto cfunc = &call<is_index, is_variable>;
return stack::push(L_, c_closure(cfunc, upvalues));
}
}
else {
constexpr int boost = !detail::is_non_factory_constructor<F>::value && std::is_same<K, call_construction>::value ? 1 : 0;
auto& f = *static_cast<F*>(target);
return call_detail::call_wrapped<T, is_index, is_variable, boost>(L_, f);
}
}
template <bool is_index = true, bool is_variable = false>
static inline int index_call_(lua_State* L_) {
void* f = stack::get<void*>(L_, upvalue_index(usertype_storage_index));
return index_call_with_<is_index, is_variable>(L_, f);
}
template <bool is_index = true, bool is_variable = false>
static inline int index_call(lua_State* L_) {
int r = detail::typed_static_trampoline<decltype(&index_call_<is_index, is_variable>), (&index_call_<is_index, is_variable>)>(L_);
if constexpr (meta::is_specialization_of_v<uF, yielding_t>) {
return lua_yield(L_, r);
}
else {
return r;
}
}
};
inline int index_fail(lua_State* L_) {
if (lua_getmetatable(L_, 1) == 1) {
int metatarget = lua_gettop(L_);
stack::get_field<false, true>(L_, stack_reference(L_, raw_index(2)), metatarget);
return 1;
}
// With runtime extensibility, we can't
// hard-error things. They have to
// return nil, like regular table types
return stack::push(L_, lua_nil);
}
inline int index_target_fail(lua_State* L_, void*) {
return index_fail(L_);
}
inline int new_index_fail(lua_State* L_) {
return luaL_error(L_, "sol: cannot set (new_index) into this object: no defined new_index operation on usertype");
}
inline int new_index_target_fail(lua_State* L_, void*) {
return new_index_fail(L_);
}
struct string_for_each_metatable_func {
bool is_destruction = false;
bool is_index = false;
bool is_new_index = false;
bool is_static_index = false;
bool is_static_new_index = false;
bool poison_indexing = false;
bool is_unqualified_lua_CFunction = false;
bool is_unqualified_lua_reference = false;
std::string* p_key = nullptr;
reference* p_binding_ref = nullptr;
lua_CFunction call_func = nullptr;
index_call_storage* p_ics = nullptr;
usertype_storage_base* p_usb = nullptr;
void* p_derived_usb = nullptr;
lua_CFunction idx_call = nullptr, new_idx_call = nullptr, meta_idx_call = nullptr, meta_new_idx_call = nullptr;
change_indexing_mem_func change_indexing;
void operator()(lua_State* L_, submetatable_type smt_, stateless_reference& fast_index_table_) {
std::string& key = *p_key;
usertype_storage_base& usb = *p_usb;
index_call_storage& ics = *p_ics;
if (smt_ == submetatable_type::named) {
// do not override __call or
// other specific meta functions on named metatable:
// we need that for call construction
// and other amenities
return;
}
int fast_index_table_push = fast_index_table_.push(L_);
stateless_stack_reference t(L_, -fast_index_table_push);
if (poison_indexing) {
(usb.*change_indexing)(L_, smt_, p_derived_usb, t, idx_call, new_idx_call, meta_idx_call, meta_new_idx_call);
}
if (is_destruction
&& (smt_ == submetatable_type::reference || smt_ == submetatable_type::const_reference || smt_ == submetatable_type::named
|| smt_ == submetatable_type::unique)) {
// gc does not apply to us here
// for reference types (raw T*, std::ref)
// for the named metatable itself,
// or for unique_usertypes, which do their own custom destroyion
t.pop(L_);
return;
}
if (is_index || is_new_index || is_static_index || is_static_new_index) {
// do not serialize the new_index and index functions here directly
// we control those...
t.pop(L_);
return;
}
if (is_unqualified_lua_CFunction) {
stack::set_field<false, true>(L_, key, call_func, t.stack_index());
}
else if (is_unqualified_lua_reference) {
reference& binding_ref = *p_binding_ref;
stack::set_field<false, true>(L_, key, binding_ref, t.stack_index());
}
else {
stack::set_field<false, true>(L_, key, make_closure(call_func, nullptr, ics.binding_data), t.stack_index());
}
t.pop(L_);
}
};
struct lua_reference_func {
reference key;
reference value;
void operator()(lua_State* L_, submetatable_type smt_, stateless_reference& fast_index_table_) {
if (smt_ == submetatable_type::named) {
return;
}
int fast_index_table_push = fast_index_table_.push(L_);
stateless_stack_reference t(L_, -fast_index_table_push);
stack::set_field<false, true>(L_, key, value, t.stack_index());
t.pop(L_);
}
};
struct update_bases_func {
detail::inheritance_check_function base_class_check_func;
detail::inheritance_cast_function base_class_cast_func;
lua_CFunction idx_call, new_idx_call, meta_idx_call, meta_new_idx_call;
usertype_storage_base* p_usb;
void* p_derived_usb;
change_indexing_mem_func change_indexing;
void operator()(lua_State* L_, submetatable_type smt_, stateless_reference& fast_index_table_) {
int fast_index_table_push = fast_index_table_.push(L_);
stateless_stack_reference t(L_, -fast_index_table_push);
stack::set_field(L_, detail::base_class_check_key(), reinterpret_cast<void*>(base_class_check_func), t.stack_index());
stack::set_field(L_, detail::base_class_cast_key(), reinterpret_cast<void*>(base_class_cast_func), t.stack_index());
// change indexing, forcefully
(p_usb->*change_indexing)(L_, smt_, p_derived_usb, t, idx_call, new_idx_call, meta_idx_call, meta_new_idx_call);
t.pop(L_);
}
};
struct binding_data_equals {
void* binding_data;
binding_data_equals(void* b) : binding_data(b) {
}
bool operator()(const std::unique_ptr<binding_base>& ptr) const {
return binding_data == ptr->data();
}
};
struct usertype_storage_base {
public:
lua_State* m_L;
std::vector<std::unique_ptr<binding_base>> storage;
std::vector<std::unique_ptr<char[]>> string_keys_storage;
std::unordered_map<string_view, index_call_storage> string_keys;
std::unordered_map<stateless_reference, stateless_reference, stateless_reference_hash, stateless_reference_equals> auxiliary_keys;
stateless_reference value_index_table;
stateless_reference reference_index_table;
stateless_reference unique_index_table;
stateless_reference const_reference_index_table;
stateless_reference const_value_index_table;
stateless_reference named_index_table;
stateless_reference type_table;
stateless_reference gc_names_table;
stateless_reference named_metatable;
new_index_call_storage base_index;
new_index_call_storage static_base_index;
bool is_using_index;
bool is_using_new_index;
std::bitset<64> properties;
usertype_storage_base(lua_State* L_)
: m_L(L_)
, storage()
, string_keys()
, auxiliary_keys(0, stateless_reference_hash(L_), stateless_reference_equals(L_))
, value_index_table()
, reference_index_table()
, unique_index_table()
, const_reference_index_table()
, type_table(make_reference<stateless_reference>(L_, create))
, gc_names_table(make_reference<stateless_reference>(L_, create))
, named_metatable(make_reference<stateless_reference>(L_, create))
, base_index()
, static_base_index()
, is_using_index(false)
, is_using_new_index(false)
, properties() {
base_index.binding_data = nullptr;
base_index.index = index_target_fail;
base_index.new_index = new_index_target_fail;
base_index.new_binding_data = nullptr;
static_base_index.binding_data = nullptr;
static_base_index.index = index_target_fail;
static_base_index.new_binding_data = this;
static_base_index.new_index = new_index_target_set;
}
template <typename Fx>
void for_each_table(lua_State* L_, Fx&& fx) {
for (int i = 0; i < 6; ++i) {
submetatable_type smt = static_cast<submetatable_type>(i);
stateless_reference* p_fast_index_table = nullptr;
switch (smt) {
case submetatable_type::const_value:
p_fast_index_table = &this->const_value_index_table;
break;
case submetatable_type::reference:
p_fast_index_table = &this->reference_index_table;
break;
case submetatable_type::unique:
p_fast_index_table = &this->unique_index_table;
break;
case submetatable_type::const_reference:
p_fast_index_table = &this->const_reference_index_table;
break;
case submetatable_type::named:
p_fast_index_table = &this->named_index_table;
break;
case submetatable_type::value:
default:
p_fast_index_table = &this->value_index_table;
break;
}
fx(L_, smt, *p_fast_index_table);
}
}
void add_entry(string_view sv, index_call_storage ics) {
string_keys_storage.emplace_back(new char[sv.size()]);
std::unique_ptr<char[]>& sv_storage = string_keys_storage.back();
std::memcpy(sv_storage.get(), sv.data(), sv.size());
string_view stored_sv(sv_storage.get(), sv.size());
string_keys.insert_or_assign(std::move(stored_sv), std::move(ics));
}
template <typename T, typename... Bases>
void update_bases(lua_State* L_, bases<Bases...>) {
static_assert(sizeof(void*) <= sizeof(detail::inheritance_check_function),
"The size of this data pointer is too small to fit the inheritance checking function: Please file "
"a bug report.");
static_assert(sizeof(void*) <= sizeof(detail::inheritance_cast_function),
"The size of this data pointer is too small to fit the inheritance checking function: Please file "
"a bug report.");
static_assert(!meta::any_same<T, Bases...>::value, "base classes cannot list the original class as part of the bases");
if constexpr (sizeof...(Bases) > 0) {
(void)detail::swallow { 0, ((weak_derive<Bases>::value = true), 0)... };
void* derived_this = static_cast<void*>(static_cast<usertype_storage<T>*>(this));
update_bases_func for_each_fx;
for_each_fx.base_class_check_func = &detail::inheritance<T>::template type_check_with<Bases...>;
for_each_fx.base_class_cast_func = &detail::inheritance<T>::template type_cast_with<Bases...>;
for_each_fx.idx_call = &usertype_storage<T>::template index_call_with_bases<false, Bases...>;
for_each_fx.new_idx_call = &usertype_storage<T>::template index_call_with_bases<true, Bases...>;
for_each_fx.meta_idx_call = &usertype_storage<T>::template meta_index_call_with_bases<false, Bases...>;
for_each_fx.meta_new_idx_call = &usertype_storage<T>::template meta_index_call_with_bases<true, Bases...>;
for_each_fx.p_usb = this;
for_each_fx.p_derived_usb = derived_this;
for_each_fx.change_indexing = &usertype_storage_base::change_indexing;
for_each_fx.p_derived_usb = derived_this;
this->for_each_table(L_, for_each_fx);
}
else {
(void)L_;
}
}
void clear() {
if (value_index_table.valid(m_L)) {
stack::clear(m_L, value_index_table);
}
if (reference_index_table.valid(m_L)) {
stack::clear(m_L, reference_index_table);
}
if (unique_index_table.valid(m_L)) {
stack::clear(m_L, unique_index_table);
}
if (const_reference_index_table.valid(m_L)) {
stack::clear(m_L, const_reference_index_table);
}
if (const_value_index_table.valid(m_L)) {
stack::clear(m_L, const_value_index_table);
}
if (named_index_table.valid(m_L)) {
stack::clear(m_L, named_index_table);
}
if (type_table.valid(m_L)) {
stack::clear(m_L, type_table);
}
if (gc_names_table.valid(m_L)) {
stack::clear(m_L, gc_names_table);
}
if (named_metatable.valid(m_L)) {
auto pp = stack::push_pop(m_L, named_metatable);
int named_metatable_index = pp.index_of(named_metatable);
if (lua_getmetatable(m_L, named_metatable_index) == 1) {
stack::clear(m_L, absolute_index(m_L, -1));
}
stack::clear(m_L, named_metatable);
}
value_index_table.reset(m_L);
reference_index_table.reset(m_L);
unique_index_table.reset(m_L);
const_reference_index_table.reset(m_L);
const_value_index_table.reset(m_L);
named_index_table.reset(m_L);
type_table.reset(m_L);
gc_names_table.reset(m_L);
named_metatable.reset(m_L);
storage.clear();
string_keys.clear();
auxiliary_keys.clear();
string_keys_storage.clear();
}
template <bool is_new_index, typename Base>
static void base_walk_index(lua_State* L_, usertype_storage_base& self, bool& keep_going, int& base_result) {
using bases = typename base<Base>::type;
if (!keep_going) {
return;
}
(void)L_;
(void)self;
#if SOL_IS_ON(SOL_USE_UNSAFE_BASE_LOOKUP_I_)
usertype_storage_base& base_storage = get_usertype_storage<Base>(L_);
base_result = self_index_call<is_new_index, true>(bases(), L_, base_storage);
#else
optional<usertype_storage<Base>&> maybe_base_storage = maybe_get_usertype_storage<Base>(L_);
if (static_cast<bool>(maybe_base_storage)) {
base_result = self_index_call<is_new_index, true>(bases(), L_, *maybe_base_storage);
keep_going = base_result == base_walking_failed_index;
}
#endif // Fast versus slow, safe base lookup
}
template <bool is_new_index = false, bool base_walking = false, bool from_named_metatable = false, typename... Bases>
static inline int self_index_call(types<Bases...>, lua_State* L, usertype_storage_base& self) {
if constexpr (!from_named_metatable || !is_new_index) {
type k_type = stack::get<type>(L, 2);
if (k_type == type::string) {
index_call_storage* target = nullptr;
string_view k = stack::get<string_view>(L, 2);
{
auto it = self.string_keys.find(k);
if (it != self.string_keys.cend()) {
target = &it->second;
}
}
if (target != nullptr) {
// let the target decide what to do, unless it's named...
if constexpr (is_new_index) {
return (target->new_index)(L, target->binding_data);
}
else {
return (target->index)(L, target->binding_data);
}
}
}
else if (k_type != type::lua_nil && k_type != type::none) {
stateless_reference* target = nullptr;
{
stack_reference k = stack::get<stack_reference>(L, 2);
auto it = self.auxiliary_keys.find(k);
if (it != self.auxiliary_keys.cend()) {
target = &it->second;
}
}
if (target != nullptr) {
if constexpr (is_new_index) {
// set value and return
target->reset(L, 3);
return 0;
}
else {
// push target to return
// what we found
return stack::push(L, *target);
}
}
}
}
// retrieve bases and walk through them.
bool keep_going = true;
int base_result;
(void)keep_going;
(void)base_result;
(void)detail::swallow { 1, (base_walk_index<is_new_index, Bases>(L, self, keep_going, base_result), 1)... };
if constexpr (sizeof...(Bases) > 0) {
if (!keep_going) {
return base_result;
}
}
if constexpr (base_walking) {
// if we're JUST base-walking then don't index-fail, just
// return the false bits
return base_walking_failed_index;
}
else if constexpr (from_named_metatable) {
if constexpr (is_new_index) {
return self.static_base_index.new_index(L, self.static_base_index.new_binding_data);
}
else {
return self.static_base_index.index(L, self.static_base_index.binding_data);
}
}
else {
if constexpr (is_new_index) {
return self.base_index.new_index(L, self.base_index.new_binding_data);
}
else {
return self.base_index.index(L, self.base_index.binding_data);
}
}
}
void change_indexing(lua_State* L_, submetatable_type submetatable_, void* derived_this_, stateless_stack_reference& t_, lua_CFunction index_,
lua_CFunction new_index_, lua_CFunction meta_index_, lua_CFunction meta_new_index_) {
usertype_storage_base& this_base = *this;
void* base_this = static_cast<void*>(&this_base);
this->is_using_index |= true;
this->is_using_new_index |= true;
if (submetatable_ == submetatable_type::named) {
stack::set_field(L_, metatable_key, named_index_table, t_.stack_index());
stateless_stack_reference stack_metametatable(L_, -named_metatable.push(L_));
stack::set_field<false, true>(L_,
meta_function::index,
make_closure(meta_index_, nullptr, derived_this_, base_this, nullptr, toplevel_magic),
stack_metametatable.stack_index());
stack::set_field<false, true>(L_,
meta_function::new_index,
make_closure(meta_new_index_, nullptr, derived_this_, base_this, nullptr, toplevel_magic),
stack_metametatable.stack_index());
stack_metametatable.pop(L_);
}
else {
stack::set_field<false, true>(
L_, meta_function::index, make_closure(index_, nullptr, derived_this_, base_this, nullptr, toplevel_magic), t_.stack_index());
stack::set_field<false, true>(
L_, meta_function::new_index, make_closure(new_index_, nullptr, derived_this_, base_this, nullptr, toplevel_magic), t_.stack_index());
}
}
template <typename T = void, typename Key, typename Value>
void set(lua_State* L, Key&& key, Value&& value);
static int new_index_target_set(lua_State* L, void* target) {
usertype_storage_base& self = *static_cast<usertype_storage_base*>(target);
self.set(L, reference(L, raw_index(2)), reference(L, raw_index(3)));
return 0;
}
~usertype_storage_base() {
value_index_table.reset(m_L);
reference_index_table.reset(m_L);
unique_index_table.reset(m_L);
const_reference_index_table.reset(m_L);
const_value_index_table.reset(m_L);
named_index_table.reset(m_L);
type_table.reset(m_L);
gc_names_table.reset(m_L);
named_metatable.reset(m_L);
auto auxiliary_first = auxiliary_keys.cbegin();
auto auxiliary_last = auxiliary_keys.cend();
while (auxiliary_first != auxiliary_last) {
// save a copy to what we're going to destroy
auto auxiliary_target = auxiliary_first;
// move the iterator up by 1
++auxiliary_first;
// extract the node and destroy the key
auto extracted_node = auxiliary_keys.extract(auxiliary_target);
extracted_node.key().reset(m_L);
extracted_node.mapped().reset(m_L);
// continue if auxiliary_first hasn't been exhausted
}
}
};
template <typename T>
struct usertype_storage : usertype_storage_base {
using usertype_storage_base::usertype_storage_base;
template <bool is_new_index, bool from_named_metatable>
static inline int index_call_(lua_State* L) {
using bases = typename base<T>::type;
usertype_storage_base& self = stack::get<light<usertype_storage_base>>(L, upvalue_index(usertype_storage_index));
return self_index_call<is_new_index, false, from_named_metatable>(bases(), L, self);
}
template <bool is_new_index, bool from_named_metatable, typename... Bases>
static inline int index_call_with_bases_(lua_State* L) {
using bases = types<Bases...>;
usertype_storage_base& self = stack::get<light<usertype_storage_base>>(L, upvalue_index(usertype_storage_index));
return self_index_call<is_new_index, false, from_named_metatable>(bases(), L, self);
}
template <bool is_new_index>
static inline int index_call(lua_State* L) {
return detail::static_trampoline<&index_call_<is_new_index, false>>(L);
}
template <bool is_new_index, typename... Bases>
static inline int index_call_with_bases(lua_State* L) {
return detail::static_trampoline<&index_call_with_bases_<is_new_index, false, Bases...>>(L);
}
template <bool is_new_index>
static inline int meta_index_call(lua_State* L) {
return detail::static_trampoline<&index_call_<is_new_index, true>>(L);
}
template <bool is_new_index, typename... Bases>
static inline int meta_index_call_with_bases(lua_State* L) {
return detail::static_trampoline<&index_call_with_bases_<is_new_index, true, Bases...>>(L);
}
template <typename Key, typename Value>
inline void set(lua_State* L, Key&& key, Value&& value);
};
template <typename T, typename Key, typename Value>
void usertype_storage_base::set(lua_State* L, Key&& key, Value&& value) {
using ValueU = meta::unwrap_unqualified_t<Value>;
using KeyU = meta::unwrap_unqualified_t<Key>;
using Binding = binding<KeyU, ValueU, T>;
using is_var_bind = is_variable_binding<ValueU>;
if constexpr (std::is_same_v<KeyU, call_construction>) {
(void)key;
std::unique_ptr<Binding> p_binding = std::make_unique<Binding>(std::forward<Value>(value));
Binding& b = *p_binding;
this->storage.push_back(std::move(p_binding));
this->named_index_table.push(L);
absolute_index metametatable_index(L, -1);
std::string_view call_metamethod_name = to_string(meta_function::call);
lua_pushlstring(L, call_metamethod_name.data(), call_metamethod_name.size());
stack::push(L, nullptr);
stack::push(L, b.data());
lua_CFunction target_func = &b.template call<false, false>;
lua_pushcclosure(L, target_func, 2);
lua_rawset(L, metametatable_index);
this->named_index_table.pop(L);
}
else if constexpr (std::is_same_v<KeyU, base_classes_tag>) {
(void)key;
this->update_bases<T>(L, std::forward<Value>(value));
}
else if constexpr ((meta::is_string_like_or_constructible<KeyU>::value || std::is_same_v<KeyU, meta_function>)) {
std::string s = u_detail::make_string(std::forward<Key>(key));
auto storage_it = this->storage.end();
auto string_it = this->string_keys.find(s);
if (string_it != this->string_keys.cend()) {
const auto& binding_data = string_it->second.binding_data;
storage_it = std::find_if(this->storage.begin(), this->storage.end(), binding_data_equals(binding_data));
this->string_keys.erase(string_it);
}
std::unique_ptr<Binding> p_binding = std::make_unique<Binding>(std::forward<Value>(value));
Binding& b = *p_binding;
if (storage_it != this->storage.cend()) {
*storage_it = std::move(p_binding);
}
else {
this->storage.push_back(std::move(p_binding));
}
bool is_index = (s == to_string(meta_function::index));
bool is_new_index = (s == to_string(meta_function::new_index));
bool is_static_index = (s == to_string(meta_function::static_index));
bool is_static_new_index = (s == to_string(meta_function::static_new_index));
bool is_destruction = s == to_string(meta_function::garbage_collect);
bool poison_indexing = (!is_using_index || !is_using_new_index) && (is_var_bind::value || is_index || is_new_index);
void* derived_this = static_cast<void*>(static_cast<usertype_storage<T>*>(this));
index_call_storage ics;
ics.binding_data = b.data();
ics.index = is_index || is_static_index ? &Binding::template call_with_<true, is_var_bind::value>
: &Binding::template index_call_with_<true, is_var_bind::value>;
ics.new_index = is_new_index || is_static_new_index ? &Binding::template call_with_<false, is_var_bind::value>
: &Binding::template index_call_with_<false, is_var_bind::value>;
string_for_each_metatable_func for_each_fx;
for_each_fx.is_destruction = is_destruction;
for_each_fx.is_index = is_index;
for_each_fx.is_new_index = is_new_index;
for_each_fx.is_static_index = is_static_index;
for_each_fx.is_static_new_index = is_static_new_index;
for_each_fx.poison_indexing = poison_indexing;
for_each_fx.p_key = &s;
for_each_fx.p_ics = &ics;
if constexpr (is_lua_c_function_v<ValueU>) {
for_each_fx.is_unqualified_lua_CFunction = true;
for_each_fx.call_func = *static_cast<lua_CFunction*>(ics.binding_data);
}
else if constexpr (is_lua_reference_or_proxy_v<ValueU>) {
for_each_fx.is_unqualified_lua_reference = true;
for_each_fx.p_binding_ref = static_cast<reference*>(ics.binding_data);
}
else {
for_each_fx.call_func = &b.template call<false, is_var_bind::value>;
}
for_each_fx.p_usb = this;
for_each_fx.p_derived_usb = derived_this;
for_each_fx.idx_call = &usertype_storage<T>::template index_call<false>;
for_each_fx.new_idx_call = &usertype_storage<T>::template index_call<true>;
for_each_fx.meta_idx_call = &usertype_storage<T>::template meta_index_call<false>;
for_each_fx.meta_new_idx_call = &usertype_storage<T>::template meta_index_call<true>;
for_each_fx.change_indexing = &usertype_storage_base::change_indexing;
// set base index and base new_index
// functions here
if (is_index) {
this->base_index.index = ics.index;
this->base_index.binding_data = ics.binding_data;
}
if (is_new_index) {
this->base_index.new_index = ics.new_index;
this->base_index.new_binding_data = ics.binding_data;
}
if (is_static_index) {
this->static_base_index.index = ics.index;
this->static_base_index.binding_data = ics.binding_data;
}
if (is_static_new_index) {
this->static_base_index.new_index = ics.new_index;
this->static_base_index.new_binding_data = ics.binding_data;
}
this->for_each_table(L, for_each_fx);
this->add_entry(s, std::move(ics));
}
else {
// the reference-based implementation might compare poorly and hash
// poorly in some cases...
if constexpr (is_lua_reference_v<KeyU> && is_lua_reference_v<ValueU>) {
if (key.get_type() == type::string) {
stack::push(L, key);
std::string string_key = stack::pop<std::string>(L);
this->set<T>(L, string_key, std::forward<Value>(value));
}
else {
lua_reference_func ref_additions_fx { key, value };
this->for_each_table(L, ref_additions_fx);
this->auxiliary_keys.insert_or_assign(std::forward<Key>(key), std::forward<Value>(value));
}
}
else {
reference ref_key = make_reference(L, std::forward<Key>(key));
reference ref_value = make_reference(L, std::forward<Value>(value));
lua_reference_func ref_additions_fx { ref_key, ref_value };
this->for_each_table(L, ref_additions_fx);
this->auxiliary_keys.insert_or_assign(std::move(ref_key), std::move(ref_value));
}
}
}
template <typename T>
template <typename Key, typename Value>
void usertype_storage<T>::set(lua_State* L, Key&& key, Value&& value) {
static_cast<usertype_storage_base&>(*this).set<T>(L, std::forward<Key>(key), std::forward<Value>(value));
}
template <typename T>
inline void clear_usertype_registry_names(lua_State* L) {
using u_traits = usertype_traits<T>;
using u_const_traits = usertype_traits<const T>;
using u_unique_traits = usertype_traits<d::u<T>>;
using u_ref_traits = usertype_traits<T*>;
using u_const_ref_traits = usertype_traits<T const*>;
stack_reference registry(L, raw_index(LUA_REGISTRYINDEX));
registry.push();
// eliminate all named entries for this usertype
// in the registry (luaL_newmetatable does
// [name] = new table
// in registry upon creation
stack::set_field(L, &u_traits::metatable()[0], lua_nil, registry.stack_index());
stack::set_field(L, &u_const_traits::metatable()[0], lua_nil, registry.stack_index());
stack::set_field(L, &u_const_ref_traits::metatable()[0], lua_nil, registry.stack_index());
stack::set_field(L, &u_ref_traits::metatable()[0], lua_nil, registry.stack_index());
stack::set_field(L, &u_unique_traits::metatable()[0], lua_nil, registry.stack_index());
registry.pop();
}
template <typename T>
inline int destroy_usertype_storage(lua_State* L) noexcept {
clear_usertype_registry_names<T>(L);
return detail::user_alloc_destroy<usertype_storage<T>>(L);
}
template <typename T>
inline usertype_storage<T>& create_usertype_storage(lua_State* L) {
const char* gcmetakey = &usertype_traits<T>::gc_table()[0];
// Make sure userdata's memory is properly in lua first,
// otherwise all the light userdata we make later will become invalid
int usertype_storage_push_count = stack::push<user<usertype_storage<T>>>(L, no_metatable, L);
stack_reference usertype_storage_ref(L, -usertype_storage_push_count);
// create and push onto the stack a table to use as metatable for this GC
// we create a metatable to attach to the regular gc_table
// so that the destructor is called for the usertype storage
int usertype_storage_metatabe_count = stack::push(L, new_table(0, 1));
stack_reference usertype_storage_metatable(L, -usertype_storage_metatabe_count);
// set the destroyion routine on the metatable
stack::set_field(L, meta_function::garbage_collect, &destroy_usertype_storage<T>, usertype_storage_metatable.stack_index());
// set the metatable on the usertype storage userdata
stack::set_field(L, metatable_key, usertype_storage_metatable, usertype_storage_ref.stack_index());
usertype_storage_metatable.pop();
// set the usertype storage and its metatable
// into the global table...
stack::set_field<true>(L, gcmetakey, usertype_storage_ref);
usertype_storage_ref.pop();
// then retrieve the lua-stored version so we have a well-pinned
// reference that does not die
stack::get_field<true>(L, gcmetakey);
usertype_storage<T>& target_umt = stack::pop<user<usertype_storage<T>>>(L);
return target_umt;
}
inline optional<usertype_storage_base&> maybe_as_usertype_storage_base(lua_State* L, int index) {
if (type_of(L, index) != type::lightuserdata) {
return nullopt;
}
usertype_storage_base& base_storage = *static_cast<usertype_storage_base*>(stack::get<void*>(L, index));
return base_storage;
}
inline optional<usertype_storage_base&> maybe_get_usertype_storage_base_inside(lua_State* L, int index) {
// okay, maybe we're looking at a table that is nested?
if (type_of(L, index) != type::table) {
return nullopt;
}
stack::get_field(L, meta_function::storage, index);
auto maybe_storage_base = maybe_as_usertype_storage_base(L, -1);
lua_pop(L, 1);
return maybe_storage_base;
}
inline optional<usertype_storage_base&> maybe_get_usertype_storage_base(lua_State* L, int index) {
// If we can get the index directly as this type, go for it
auto maybe_already_is_usertype_storage_base = maybe_as_usertype_storage_base(L, index);
if (maybe_already_is_usertype_storage_base) {
return maybe_already_is_usertype_storage_base;
}
return maybe_get_usertype_storage_base_inside(L, index);
}
inline optional<usertype_storage_base&> maybe_get_usertype_storage_base(lua_State* L, const char* gcmetakey) {
stack::get_field<true>(L, gcmetakey);
auto maybe_storage = maybe_as_usertype_storage_base(L, lua_gettop(L));
lua_pop(L, 1);
return maybe_storage;
}
inline usertype_storage_base& get_usertype_storage_base(lua_State* L, const char* gcmetakey) {
stack::get_field<true>(L, gcmetakey);
stack::record tracking;
usertype_storage_base& target_umt = stack::stack_detail::unchecked_unqualified_get<user<usertype_storage_base>>(L, -1, tracking);
lua_pop(L, 1);
return target_umt;
}
template <typename T>
inline optional<usertype_storage<T>&> maybe_get_usertype_storage(lua_State* L) {
const char* gcmetakey = &usertype_traits<T>::gc_table()[0];
stack::get_field<true>(L, gcmetakey);
int target = lua_gettop(L);
if (!stack::check<user<usertype_storage<T>>>(L, target)) {
return nullopt;
}
usertype_storage<T>& target_umt = stack::pop<user<usertype_storage<T>>>(L);
return target_umt;
}
template <typename T>
inline usertype_storage<T>& get_usertype_storage(lua_State* L) {
const char* gcmetakey = &usertype_traits<T>::gc_table()[0];
stack::get_field<true>(L, gcmetakey);
usertype_storage<T>& target_umt = stack::pop<user<usertype_storage<T>>>(L);
return target_umt;
}
template <typename T>
inline void clear_usertype_storage(lua_State* L) {
using u_traits = usertype_traits<T>;
const char* gcmetakey = &u_traits::gc_table()[0];
stack::get_field<true>(L, gcmetakey);
if (!stack::check<user<usertype_storage<T>>>(L)) {
lua_pop(L, 1);
return;
}
usertype_storage<T>& target_umt = stack::pop<user<usertype_storage<T>>>(L);
target_umt.clear();
clear_usertype_registry_names<T>(L);
stack::set_field<true>(L, gcmetakey, lua_nil);
}
template <typename T, automagic_flags enrollment_flags>
inline int register_usertype(lua_State* L_, automagic_enrollments enrollments_ = {}) {
using u_traits = usertype_traits<T>;
using u_const_traits = usertype_traits<const T>;
using u_unique_traits = usertype_traits<d::u<T>>;
using u_ref_traits = usertype_traits<T*>;
using u_const_ref_traits = usertype_traits<T const*>;
using uts = usertype_storage<T>;
// always have __new_index point to usertype_storage method
// have __index always point to regular fast-lookup
// meta_method table
// if __new_index is invoked, runtime-swap
// to slow __index if necessary
// (no speed penalty because function calls
// are all read-only -- only depend on __index
// to retrieve function and then call happens VIA Lua)
// __type entry:
// table contains key -> value lookup,
// where key is entry in metatable
// and value is type information as a string as
// best as we can give it
// name entry:
// string that contains raw class name,
// as defined from C++
// is entry:
// checks if argument supplied is of type T
// __storage entry:
// a light userdata pointing to the storage
// mostly to enable this new abstraction
// to not require the type name `T`
// to get at the C++ usertype storage within
// we then let typical definitions potentially override these intrinsics
// it's the user's fault if they override things or screw them up:
// these names have been reserved and documented since sol2
// STEP 0: tell the old usertype (if it exists)
// to fuck off
clear_usertype_storage<T>(L_);
// STEP 1: Create backing store for usertype storage
// Pretty much the most important step.
// STEP 2: Create Lua tables used for fast method indexing.
// This is done inside of the storage table's constructor
usertype_storage<T>& storage = create_usertype_storage<T>(L_);
usertype_storage_base& base_storage = storage;
void* light_storage = static_cast<void*>(&storage);
void* light_base_storage = static_cast<void*>(&base_storage);
// STEP 3: set up GC escape hatch table entirely
storage.gc_names_table.push(L_);
stateless_stack_reference gnt(L_, -1);
stack::set_field(L_, submetatable_type::named, &u_traits::gc_table()[0], gnt.stack_index());
stack::set_field(L_, submetatable_type::const_value, &u_const_traits::metatable()[0], gnt.stack_index());
stack::set_field(L_, submetatable_type::const_reference, &u_const_ref_traits::metatable()[0], gnt.stack_index());
stack::set_field(L_, submetatable_type::reference, &u_ref_traits::metatable()[0], gnt.stack_index());
stack::set_field(L_, submetatable_type::unique, &u_unique_traits::metatable()[0], gnt.stack_index());
stack::set_field(L_, submetatable_type::value, &u_traits::metatable()[0], gnt.stack_index());
gnt.pop(L_);
// STEP 4: add some useful information to the type table
stateless_stack_reference stacked_type_table(L_, -storage.type_table.push(L_));
stack::set_field(L_, "name", detail::demangle<T>(), stacked_type_table.stack_index());
stack::set_field(L_, "is", &detail::is_check<T>, stacked_type_table.stack_index());
stacked_type_table.pop(L_);
// STEP 5: create and hook up metatable,
// add intrinsics
// this one is the actual meta-handling table,
// the next one will be the one for
int for_each_backing_metatable_calls = 0;
auto for_each_backing_metatable = [&](lua_State* L_, submetatable_type smt_, stateless_reference& fast_index_table_) {
// Pointer types, AKA "references" from C++
const char* metakey = nullptr;
switch (smt_) {
case submetatable_type::const_value:
metakey = &u_const_traits::metatable()[0];
break;
case submetatable_type::reference:
metakey = &u_ref_traits::metatable()[0];
break;
case submetatable_type::unique:
metakey = &u_unique_traits::metatable()[0];
break;
case submetatable_type::const_reference:
metakey = &u_const_ref_traits::metatable()[0];
break;
case submetatable_type::named:
metakey = &u_traits::user_metatable()[0];
break;
case submetatable_type::value:
default:
metakey = &u_traits::metatable()[0];
break;
}
luaL_newmetatable(L_, metakey);
if (smt_ == submetatable_type::named) {
// the named table itself
// gets the associated name value
storage.named_metatable.reset(L_, -1);
lua_pop(L_, 1);
// but the thing we perform the methods on
// is still the metatable of the named
// table
lua_createtable(L_, 0, 6);
}
stateless_stack_reference t(L_, -1);
fast_index_table_.reset(L_, t.stack_index());
stack::set_field<false, true>(L_, meta_function::type, storage.type_table, t.stack_index());
// destructible? serialize default destructor here
// otherwise, not destructible: serialize a "hey you messed up"
switch (smt_) {
case submetatable_type::const_reference:
case submetatable_type::reference:
case submetatable_type::named:
break;
case submetatable_type::unique:
if constexpr (std::is_destructible_v<T>) {
stack::set_field<false, true>(L_, meta_function::garbage_collect, &detail::unique_destroy<T>, t.stack_index());
}
else {
stack::set_field<false, true>(L_, meta_function::garbage_collect, &detail::cannot_destroy<T>, t.stack_index());
}
break;
case submetatable_type::value:
case submetatable_type::const_value:
default:
if constexpr (std::is_destructible_v<T>) {
stack::set_field<false, true>(L_, meta_function::garbage_collect, detail::make_destructor<T>(), t.stack_index());
}
else {
stack::set_field<false, true>(L_, meta_function::garbage_collect, &detail::cannot_destroy<T>, t.stack_index());
}
break;
}
static_assert(sizeof(void*) <= sizeof(detail::inheritance_check_function),
"The size of this data pointer is too small to fit the inheritance checking function: file a bug "
"report.");
static_assert(sizeof(void*) <= sizeof(detail::inheritance_cast_function),
"The size of this data pointer is too small to fit the inheritance checking function: file a bug "
"report.");
stack::set_field<false, true>(L_, detail::base_class_check_key(), reinterpret_cast<void*>(&detail::inheritance<T>::type_check), t.stack_index());
stack::set_field<false, true>(L_, detail::base_class_cast_key(), reinterpret_cast<void*>(&detail::inheritance<T>::type_cast), t.stack_index());
auto prop_fx = detail::properties_enrollment_allowed(for_each_backing_metatable_calls, storage.properties, enrollments_);
auto insert_fx = [&L_, &t, &storage](meta_function mf, lua_CFunction reg) {
stack::set_field<false, true>(L_, mf, reg, t.stack_index());
storage.properties[static_cast<std::size_t>(mf)] = true;
};
detail::insert_default_registrations<T>(insert_fx, prop_fx);
// There are no variables, so serialize the fast function stuff
// be sure to reset the index stuff to the non-fast version
// if the user ever adds something later!
if (smt_ == submetatable_type::named) {
// add escape hatch storage pointer and gc names
stack::set_field<false, true>(L_, meta_function::storage, light_base_storage, t.stack_index());
stack::set_field<false, true>(L_, meta_function::gc_names, storage.gc_names_table, t.stack_index());
// fancy new_indexing when using the named table
{
absolute_index named_metatable_index(L_, -storage.named_metatable.push(L_));
stack::set_field<false, true>(L_, metatable_key, t, named_metatable_index);
storage.named_metatable.pop(L_);
}
stack_reference stack_metametatable(L_, -storage.named_index_table.push(L_));
stack::set_field<false, true>(L_,
meta_function::index,
make_closure(uts::template meta_index_call<false>, nullptr, light_storage, light_base_storage, nullptr, toplevel_magic),
stack_metametatable.stack_index());
stack::set_field<false, true>(L_,
meta_function::new_index,
make_closure(uts::template meta_index_call<true>, nullptr, light_storage, light_base_storage, nullptr, toplevel_magic),
stack_metametatable.stack_index());
stack_metametatable.pop();
}
else {
// otherwise just plain for index,
// and elaborated for new_index
stack::set_field<false, true>(L_, meta_function::index, t, t.stack_index());
stack::set_field<false, true>(L_,
meta_function::new_index,
make_closure(uts::template index_call<true>, nullptr, light_storage, light_base_storage, nullptr, toplevel_magic),
t.stack_index());
storage.is_using_new_index = true;
}
++for_each_backing_metatable_calls;
fast_index_table_.reset(L_, t.stack_index());
t.pop(L_);
};
storage.for_each_table(L_, for_each_backing_metatable);
// can only use set AFTER we initialize all the metatables
if constexpr (std::is_default_constructible_v<T> && has_flag(enrollment_flags, automagic_flags::default_constructor)) {
if (enrollments_.default_constructor) {
storage.set(L_, meta_function::construct, constructors<T()>());
}
}
// return the named metatable we want names linked into
storage.named_metatable.push(L_);
return 1;
}
}} // namespace sol::u_detail
#endif // SOL_USERTYPE_STORAGE_HPP