← Back to file list Raw

src/CScriptFieldsManager.h

#pragma once
#include "sol/sol.hpp"
#include <string>
#include <map>
#include <unordered_map>
#include <memory>
#include <functional>
#include <optional>
#include "U_StringTemplate.h"
static bool lua_equal(const sol::object& a, const sol::object& b)
{
sol::state_view lua = a.lua_state();
if (a.get_type() != b.get_type())
{
return false;
}
switch (a.get_type())
{
case sol::type::nil:
return true;
case sol::type::boolean:
return a.as<bool>() == b.as<bool>();
case sol::type::number:
return a.as<double>() == b.as<double>();
case sol::type::string:
return a.as<std::string>() == b.as<std::string>();
default:
break;
}
sol::function eq = lua["__cpp_eq"];
if (!eq.valid())
{
lua["__cpp_eq"] = lua.safe_script
(
"return function(a, b) return a == b end",
sol::script_pass_on_error
);
eq = lua["__cpp_eq"];
}
return eq(a, b);
}
class CScriptFieldBase
{
public:
CScriptFieldBase() = delete;
CScriptFieldBase(bool readonly = false) : ReadOnly(readonly) {}
bool ReadOnly = false;
virtual void SetObject(sol::object obj) = 0;
virtual sol::object GetObject(sol::state_view s) = 0;
};
template<typename T>
class CScriptField : public CScriptFieldBase
{
public:
CScriptField() : CScriptFieldBase(false) {}
CScriptField(const T& val, bool readonly = false) : CScriptFieldBase(readonly), Value(val) {}
CScriptField(bool readonly) : CScriptFieldBase(readonly) {}
T Value;
void SetObject(sol::object obj) override
{
if constexpr(std::is_same_v<T, sol::object>)
{
Value = obj;
}
else
{
Value = obj.as<T>();
}
};
sol::object GetObject(sol::state_view s) override
{
if constexpr(std::is_same_v<T, sol::object>)
{
return Value;
}
else
{
return sol::make_object<T>(s, Value);
}
}
};
template<typename T>
class CProxyScriptField : public CScriptFieldBase
{
public:
CProxyScriptField() = delete;
CProxyScriptField(T& val, bool readonly = false) : CScriptFieldBase(readonly), Value(val) {}
T& Value;
void SetObject(sol::object obj) override
{
if constexpr(std::is_same_v<T, sol::object>)
{
Value = obj;
}
else
{
Value = obj.as<T>();
}
};
sol::object GetObject(sol::state_view s) override
{
if constexpr(std::is_same_v<T, sol::object>)
{
return Value;
}
else
{
return sol::make_object<T>(s, Value);
}
}
};
class CFunctionalScriptField : public CScriptFieldBase
{
public:
using SetterFunc = std::function<void(sol::object)>;
using GetterFunc = std::function<sol::object(sol::state_view)>;
using SetterFuncPtr = std::unique_ptr<SetterFunc>;
using GetterFuncPtr = std::unique_ptr<GetterFunc>;
SetterFuncPtr Setter;
GetterFuncPtr Getter;
CFunctionalScriptField() = delete;
CFunctionalScriptField(GetterFunc getter, SetterFunc setter, bool readonly = false);
CFunctionalScriptField(GetterFunc getter); //only readonly
void SetObject(sol::object obj) override;
sol::object GetObject(sol::state_view s) override;
};
class CScriptFieldsManager //manages __index and __newindex
{
public:
class CKeyBase
{
public:
virtual ~CKeyBase() = default;
virtual bool operator==(const sol::object& obj) const { return false; }
virtual bool operator==(const CKeyBase* obj) const { return false; }
virtual std::string GetString() const { return std::string(); }
virtual std::size_t Hash() const { return 0; }
};
template<typename T>
class CKey : public CKeyBase
{
public:
T Value;
CKey(T val) : Value(val) {} //TODO is ref needed?
bool operator==(const sol::object& obj) const override
{
if constexpr (std::is_same_v<T, sol::object>)
{
return obj.valid() && lua_equal(obj, Value);
}
else
{
return obj.valid() && obj.is<T>() && obj.as<T>() == Value;
}
}
bool operator==(const CKeyBase* other) const override
{
const CKey<T>* otherKey = dynamic_cast<const CKey<T>*>(other);
return otherKey && otherKey->Value == Value;
}
std::string GetString() const override
{
if constexpr (std::is_same_v<T, sol::object>)
{
if(Value.template is<std::string>())
{
return "sol::object (string): " + Value.template as<std::string>();
}
else if(Value.template is<int>())
{
return "sol::object (int): " + std::to_string(Value.template as<int>());
}
return "sol::object";
}
else if constexpr (std::is_same_v<T, sol::table>)
{
return "sol::table";
}
else if constexpr (std::is_same_v<T, std::string>)
{
return Value;
}
else if constexpr (std::is_same_v<T, std::wstring>)
{
return "std::wstring";
}
else
{
return std::to_string(Value);
}
}
std::size_t Hash() const override
{
if constexpr (std::is_same_v<T, sol::object>)
{
if (!Value.valid())
{
return 0;
}
sol::type t = Value.get_type();
switch (t)
{
case sol::type::number:
return std::hash<double>()(Value.template as<double>());
case sol::type::string:
return std::hash<std::string>()(Value.template as<std::string>());
case sol::type::boolean:
return std::hash<bool>()(Value.template as<bool>());
case sol::type::userdata:
case sol::type::lightuserdata:
case sol::type::table:
case sol::type::function:
{
lua_State* L = Value.lua_state();
Value.push();
const void* ptr = lua_topointer(L, -1);
lua_pop(L, 1);
return std::hash<const void*>()(ptr);
}
case sol::type::nil:
default:
return 0;
}
}
else
{
return std::hash<T>()(Value);
}
}
};
using CKeyPtr = std::unique_ptr<CKeyBase>;
struct CKeyPtrHash
{
std::size_t operator()(const CKeyPtr& k) const
{
return k ? k->Hash() : 0;
}
};
struct CKeyPtrEqual
{
bool operator()(const CKeyPtr& lhs, const CKeyPtr& rhs) const
{
if (!lhs || !rhs) { return lhs == rhs; }
return *lhs == rhs.get();
}
};
using Optional = std::optional<sol::object>;
using IndexFunc = std::function<Optional(sol::table, sol::object, sol::this_state)>;
using NewIndexFunc = std::function<std::optional<sol::object>(sol::table, sol::object, sol::object, sol::this_state)>;
bool FieldCreationAbility = true;
std::unordered_map<CKeyPtr, CKeyPtr, CKeyPtrHash, CKeyPtrEqual> Aliases;
std::unordered_map<CKeyPtr, std::unique_ptr<CScriptFieldBase>, CKeyPtrHash, CKeyPtrEqual> Fields;
std::vector<IndexFunc> IndexCheckers;
std::vector<NewIndexFunc> NewIndexCheckers;
CScriptFieldBase* AddField(std::unique_ptr<CKeyBase>&& key, std::unique_ptr<CScriptFieldBase>&& ptr)
{
if (!ptr || !key) { return nullptr; }
auto ret = Fields.emplace(std::make_pair(std::move(key), std::move(ptr)));
return ret.second ? ret.first->second.get() : nullptr;
}
template<typename T>
CScriptFieldBase* AddField(const T& key, std::unique_ptr<CScriptFieldBase>&& ptr)
{
auto normalized_key = normalize_string(key);
using KeyType = decltype(normalized_key);
auto key_ptr = std::make_unique<CKey<KeyType>>(normalized_key);
return AddField(std::unique_ptr<CKeyBase>(std::move(key_ptr)), std::move(ptr));
}
auto FindAliasForObject(sol::object obj)
{
return std::find_if(Aliases.begin(), Aliases.end(), [obj](auto& kv) -> bool
{
return (kv.first && obj.valid()) && (*kv.first) == obj;
});
}
auto GetFieldForObject(sol::object obj)
{
auto alias = FindAliasForObject(obj);
if(alias != Aliases.end())
{
auto& aliased_key = alias->second;
auto aliased_it = std::find_if(Fields.begin(), Fields.end(), [&aliased_key, obj](auto& kv) -> bool
{
return (kv.first && obj.valid()) && ((*kv.first) == aliased_key.get());
});
if(aliased_it != Fields.end()) { return aliased_it; }
}
auto it = std::find_if(Fields.begin(), Fields.end(), [obj](auto& kv) -> bool
{
return (kv.first && obj.valid()) && (*kv.first) == obj;
});
return it;
}
auto FindAlias(CKeyBase* alias)
{
return std::find_if(Aliases.begin(), Aliases.end(), [&alias](auto& kv) -> bool
{
return (kv.first && alias) && (*kv.first) == alias;
});
}
auto GetFieldIterator(CKeyBase* key)
{
if(!key) { return Fields.end(); }
auto alias = FindAlias(key);
if(alias != Aliases.end())
{
auto& aliased_key = alias->second;
auto aliased_it = std::find_if(Fields.begin(), Fields.end(), [&aliased_key](auto& kv) -> bool
{
return (kv.first && aliased_key && kv.second) && ((*kv.first) == aliased_key.get() && kv.second.operator bool());
});
if(aliased_it != Fields.end()) { return aliased_it; }
}
auto it = std::find_if(Fields.begin(), Fields.end(), [&key](auto& kv) -> bool
{
return (kv.first && kv.second && key) && ((*kv.first) == key && kv.second.operator bool());
});
return it;
}
auto GetFieldIterator(std::unique_ptr<CKeyBase>&& key)
{
return GetFieldIterator(key.get());
}
template<typename T>
auto GetFieldIterator(const T& key)
{
auto key_ptr = std::make_unique<CKey<T>>(key);
return GetFieldIterator(static_cast<CKeyBase*>(key_ptr.get()));
}
CScriptFieldBase* GetField(CKeyBase* key)
{
auto it = GetFieldIterator(key);
return (it != Fields.end()) ? it->second.get() : nullptr;
}
CScriptFieldBase* GetField(std::unique_ptr<CKeyBase>&& key)
{
return GetField(key.get());
}
template<typename T>
CScriptFieldBase* GetField(const T& key)
{
auto key_ptr = std::make_unique<CKey<T>>(key);
return GetField(static_cast<CKeyBase*>(key_ptr.get()));
}
void SetMetaFunctions(sol::table meta_table)
{
meta_table["__index"] = [this](sol::table table, sol::object key, sol::this_state s) -> sol::object
{
auto it = GetFieldForObject(key);
if(it != Fields.end())
{
return it->second->GetObject(sol::state_view(s));
}
for(auto& checker : IndexCheckers)
{
auto ret = checker(table, key, s);
if(ret.has_value())
{
return ret.value();
}
}
return sol::lua_nil;
};
meta_table["__newindex"] = [this](sol::table table, sol::object key, sol::object value, sol::this_state s) -> void
{
auto it = GetFieldForObject(key);
if(it != Fields.end() && !it->second->ReadOnly)
{
return it->second->SetObject(value);
}
bool checkerFound = false;
for(auto& checker : NewIndexCheckers)
{
auto ret = checker(table, key, value, s);
if(ret.has_value())
{
ret.value() = value;
checkerFound = true;
}
}
if(!checkerFound && it == Fields.end() && FieldCreationAbility)
{
//auto field = std::make_unique<CScriptField<sol::object>>(value);
//AddField(key, std::move(field));
table.raw_set(key, value);
}
};
}
bool AddAlias(std::unique_ptr<CKeyBase>&& alias, std::unique_ptr<CKeyBase>&& target_key)
{
if(!alias || !target_key || FindAlias(alias.get()) != Aliases.end()) { return false; }
auto ret = Aliases.emplace(std::make_pair(std::move(alias), std::move(target_key)));
return ret.second;
}
template<typename TypeAlias>
bool AddAlias(const TypeAlias& alias, std::unique_ptr<CKeyBase>&& target_key)
{
auto alias_ptr = std::make_unique<CKey<TypeAlias>>(alias);
return AddAlias(std::unique_ptr<CKeyBase>(std::move(alias_ptr)), std::move(target_key));
}
template<typename TypeTarget>
bool AddAlias(std::unique_ptr<CKeyBase>&& alias, const TypeTarget& target_key)
{
auto target_ptr = std::make_unique<CKey<TypeTarget>>(target_key);
return AddAlias(std::move(alias), std::unique_ptr<CKeyBase>(std::move(target_ptr)));
}
template<typename TypeAlias, typename TypeTarget>
bool AddAlias(const TypeAlias& alias, const TypeTarget& target_key)
{
auto alias_ptr = std::make_unique<CKey<TypeAlias>>(alias);
auto target_ptr = std::make_unique<CKey<TypeTarget>>(target_key);
return AddAlias(std::unique_ptr<CKeyBase>(std::move(alias_ptr)), std::unique_ptr<CKeyBase>(std::move(target_ptr)));
}
bool IsAliasExist(CKeyBase* alias)
{
return FindAlias(alias) != Aliases.end();
}
bool IsAliasExist(std::unique_ptr<CKeyBase>&& alias)
{
return FindAlias(alias.get()) != Aliases.end();
}
template<typename TypeAlias>
bool IsAliasExist(const TypeAlias& alias)
{
auto alias_ptr = std::make_unique<CKey<TypeAlias>>(alias);
return FindAlias(alias_ptr.get()) != Aliases.end();
}
bool DeleteAlias(std::unique_ptr<CKeyBase>&& alias)
{
auto it = FindAlias(alias.get());
if(it == Aliases.end()) { return false; }
Aliases.erase(it);
return true;
}
bool DeleteAlias(CKeyBase* alias)
{
auto it = FindAlias(alias);
if(it == Aliases.end()) { return false; }
Aliases.erase(it);
return true;
}
template<typename TypeAlias>
bool DeleteAlias(const TypeAlias& alias)
{
auto alias_ptr = std::make_unique<CKey<TypeAlias>>(alias);
auto it = FindAlias(alias_ptr.get());
if(it == Aliases.end()) { return false; }
Aliases.erase(it);
return true;
}
bool IsFieldExist(std::unique_ptr<CKeyBase>&& key)
{
return GetFieldIterator(static_cast<CKeyBase*>(key.get())) != Fields.end();
}
bool IsFieldExist(CKeyBase* key)
{
return GetFieldIterator(key) != Fields.end();
}
template<typename T>
bool IsFieldExist(const T& key)
{
auto key_ptr = std::make_unique<CKey<T>>(key);
return GetFieldIterator(static_cast<CKeyBase*>(key_ptr.get())) != Fields.end();
}
void AddIndexChecker(const IndexFunc& func)
{
IndexCheckers.push_back(func);
}
void AddNewIndexChecker(const NewIndexFunc& func)
{
NewIndexCheckers.push_back(func);
}
static std::string GetDefaultKey() { return "__fields_manager"; }
static bool HasFieldsManager(sol::table tabl)
{
sol::object fieldsMan_obj = tabl[GetDefaultKey()];
return fieldsMan_obj.valid();
}
sol::table CreateMetaTable(sol::table def_table)
{
sol::table meta = sol::state_view(def_table.lua_state()).create_table();
SetMetaFunctions(meta);
def_table[sol::metatable_key] = meta;
return meta;
}
static std::shared_ptr<CScriptFieldsManager> CreateFieldsManager(sol::table tabl, bool instantMeta = false)
{
sol::object fieldsMan = tabl.get<sol::object>(GetDefaultKey());
if(fieldsMan != sol::lua_nil) { return fieldsMan.as<std::shared_ptr<CScriptFieldsManager>>(); }
auto fieldsManager = std::make_shared<CScriptFieldsManager>();
tabl[GetDefaultKey()] = fieldsManager;
if(instantMeta)
{
fieldsManager->CreateMetaTable(tabl);
}
return fieldsManager;
}
static std::pair<std::shared_ptr<CScriptFieldsManager>, bool> ValidateFieldsManager(sol::table tabl, bool instantMeta = false)
{
sol::object fieldsMan_obj = tabl[GetDefaultKey()];
if(fieldsMan_obj.valid())
{
return { fieldsMan_obj.as<std::shared_ptr<CScriptFieldsManager>>(), true };
}
return { CreateFieldsManager(tabl, instantMeta), false };
}
};
using CScriptFieldsManagerPtr = std::shared_ptr<CScriptFieldsManager>;