Last active
July 1, 2023 16:12
-
-
Save xiaoxiao921/fc7bfedcd7309856095106c69b0eabd5 to your computer and use it in GitHub Desktop.
Some C++ Templates
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
template<typename T> | |
concept _is_pointer_ = requires(T a) | |
{ | |
*a; | |
}; | |
#pragma once | |
#include "lua/sol.hpp" | |
namespace lua::native | |
{ | |
// Used for filtering types out of std::tuple types. | |
template<template<class> class Pred, class Sequence> | |
struct filter; | |
template<template<class> class Pred, class Sequence> | |
struct filter_remove_ptr; | |
// Called in our case when the predicate returned true. | |
// Return the E type as is. | |
template<bool> | |
struct zero_or_one | |
{ | |
template<class E> | |
using type = std::tuple<E>; | |
}; | |
// Called in our case when the predicate returned true. | |
// Return E type but with pointer qualifier removed. | |
// Needed in our case for preparing in pointer params. | |
template<bool> | |
struct zero_or_one_remove_ptr | |
{ | |
template<class E> | |
using type = std::tuple<std::remove_pointer_t<E>>; | |
}; | |
// Called in our case when the predicate returned false. | |
// Meaning that we want to get rid of the E type from the tuple. | |
template<> | |
struct zero_or_one<false> | |
{ | |
template<class E> | |
using type = std::tuple<>; | |
}; | |
// Same as zero_or_one. | |
template<> | |
struct zero_or_one_remove_ptr<false> | |
{ | |
template<class E> | |
using type = std::tuple<>; | |
}; | |
// iterative case | |
template<template<class> class Pred, class... Es> | |
struct filter<Pred, std::tuple<Es...>> | |
{ | |
using type = decltype(std::tuple_cat(std::declval<typename zero_or_one<Pred<Es>::value>::template type<Es>>()...)); | |
}; | |
// iterative case | |
template<template<class> class Pred, class... Es> | |
struct filter_remove_ptr<Pred, std::tuple<Es...>> | |
{ | |
using type = decltype(std::tuple_cat(std::declval<typename zero_or_one_remove_ptr<Pred<Es>::value>::template type<Es>>()...)); | |
}; | |
// template struct used with the `filter` template struct technique. | |
// in our case used for filtering out const char* types from std::tuple types. | |
template<class _Ty> | |
struct is_not_const_char_str : std::bool_constant<!std::is_same_v<_Ty, const char*>> | |
{ | |
}; | |
// hacky predicate used in this case with the filter_remove_ptr template struct. | |
template<class _Ty> | |
struct true_pred : std::bool_constant<true> | |
{ | |
}; | |
// template struct for fixing up the given native function. | |
// A native function needs to have its return types and pointer params fixed up for lua when: | |
// when any primitive pointer parameter are present in the function defition: | |
// things like int* or float* are not a thing in Lua, so what the lua code will pass to us will always | |
// be things like int, float, even though the native function was expecting pointers, | |
// what this struct will do is generate a wrapper function that will attempt to fix this problem. | |
// Fix up 1: all pointer params except const char* (because const char* can never be an out param afaik in the gta native api) | |
// are returned to lua, lua support multiple return values just fine and sol handles it through std::tuple, which is what we use below | |
// Fix up 2: every in pointer params (types like int* or float*, except const char*) are passed as if they were for example int or float, | |
// and then ref to them are made and this is what get passed to the native invoker. | |
// that way native functions like ENTITY::DELETE_ENTITY works. | |
template<typename FunctionSignature, FunctionSignature func> | |
struct lua_native_wrapper_impl; | |
template<typename ReturnType, typename... Args, ReturnType (*func)(Args...)> | |
struct lua_native_wrapper_impl<ReturnType (*)(Args...), func> | |
{ | |
static constexpr size_t args_count = sizeof...(Args); | |
static inline std::tuple<Args...> args; | |
using TupleOutParamTypesAndConstChars = filter<std::is_pointer, std::tuple<Args...>>::type; | |
using TupleOutParamTypes = filter<is_not_const_char_str, TupleOutParamTypesAndConstChars>::type; | |
using TupleOutParamTypesNoPtr = filter_remove_ptr<true_pred, TupleOutParamTypes>::type; | |
using TupleReturnTypeAndOutParamTypesNoPtr = decltype(std::tuple_cat(std::declval<std::tuple<ReturnType>>(), std::declval<TupleOutParamTypesNoPtr>())); | |
static inline std::conditional_t<std::is_same_v<ReturnType, void>, TupleOutParamTypesNoPtr, TupleReturnTypeAndOutParamTypesNoPtr> out_params; | |
// Pass in pointer params as if theye were just non pointer params, | |
// because lua can't pass to us pointers, | |
// so if they pass something we consider it must be the value directly, | |
// and we make a ref out of it and pass it down the native function invoker | |
template<typename Head, typename... Rest> | |
static void prepare_args(Head& arg, Rest&... rest) | |
{ | |
constexpr size_t tuple_index = ((size_t)args_count - sizeof...(Rest)) - 1; | |
if constexpr (std::is_pointer_v<Head> && !std::is_same_v<Head, const char*>) | |
{ | |
std::get<tuple_index>(args) = (std::remove_pointer_t<Head*>)&arg; | |
} | |
else | |
{ | |
std::get<tuple_index>(args) = arg; | |
} | |
if constexpr (sizeof...(Rest)) | |
{ | |
prepare_args<Rest...>(rest...); | |
} | |
} | |
// Return to lua the pointers params, the gta native api use this a lot (and C in general too), for returning multiple out parameters to the caller. | |
template<size_t args_index, size_t out_params_index> | |
static void prepare_args_2() | |
{ | |
using tuple_element_type = std::tuple_element<args_index, decltype(args)>::type; | |
if constexpr (std::is_pointer_v<tuple_element_type> && !std::is_same_v<tuple_element_type, const char*>) | |
{ | |
using out_param_tuple_element_type = std::tuple_element<out_params_index, decltype(out_params)>::type; | |
if constexpr (std::is_same_v<std::remove_pointer_t<tuple_element_type>, out_param_tuple_element_type>) | |
{ | |
std::get<out_params_index>(out_params) = (std::remove_pointer_t<tuple_element_type>)*std::get<args_index>(args); | |
if constexpr (args_index + 1 < sizeof...(Args)) | |
{ | |
prepare_args_2<args_index + 1, out_params_index + 1>(); | |
} | |
return; | |
} | |
} | |
if constexpr (args_index + 1 < sizeof...(Args)) | |
{ | |
prepare_args_2<args_index + 1, out_params_index>(); | |
} | |
} | |
// Wrap the original native function invocation. | |
static auto wrap(Args... args_) | |
{ | |
if constexpr (sizeof...(Args)) | |
{ | |
prepare_args<Args...>(args_...); | |
} | |
if constexpr (std::is_same_v<ReturnType, void>) | |
{ | |
std::apply(func, args); | |
if constexpr (sizeof...(Args)) | |
{ | |
prepare_args_2<0, 0>(); | |
} | |
} | |
else | |
{ | |
auto res = std::apply(func, args); | |
std::get<0>(out_params) = res; | |
if constexpr (sizeof...(Args)) | |
{ | |
prepare_args_2<0, 1>(); | |
} | |
} | |
return out_params; | |
} | |
}; | |
// small helper that call into the impl. | |
template<typename FunctionSignature, FunctionSignature func> | |
inline constexpr auto lua_native_wrapper = lua_native_wrapper_impl<FunctionSignature, func>::wrap; | |
// template struct for checking if the passed native function | |
// needs to have its return types and pointer params fixed up for lua. | |
template<typename FunctionSignature, FunctionSignature func> | |
struct is_no_out_params_func_impl; | |
template<typename ReturnType, typename... Args, ReturnType (*func)(Args...)> | |
struct is_no_out_params_func_impl<ReturnType (*)(Args...), func> | |
{ | |
static constexpr bool value() | |
{ | |
using TupleOutParamTypesAndConstChars = filter<std::is_pointer, std::tuple<Args...>>::type; | |
using TupleOutParamTypes = filter<is_not_const_char_str, TupleOutParamTypesAndConstChars>::type; | |
return std::is_same_v<ReturnType, void> && std::is_same_v<TupleOutParamTypes, std::tuple<>>; | |
} | |
}; | |
// small helper that call into the impl. | |
template<typename FunctionSignature, FunctionSignature func> | |
inline constexpr auto is_no_out_params_func = is_no_out_params_func_impl<FunctionSignature, func>::value(); | |
// used for wrapping the native functions inside our natives.hpp file. | |
// determine if the native function need wrapping code for lua. | |
// returns the wrapped native function or just the original func if no fix up were needed. | |
template<auto func> | |
inline constexpr auto make_lua_native_wrapper() | |
{ | |
if constexpr (is_no_out_params_func<decltype(func), func>) | |
{ | |
return func; | |
} | |
else | |
{ | |
return lua_native_wrapper<decltype(func), func>; | |
} | |
} | |
} | |
static int EntityDelete(int* ent, int* ent2) | |
//static void EntityDelete(int* ent, int* ent2) | |
{ | |
std::cout << "Original Entity Delete: " << *ent << " | " << ent2 << std::endl; | |
*ent = 0x69; | |
return 0xFF; | |
} | |
int main() | |
{ | |
constexpr auto wrapped = make_lua_native_wrapper<EntityDelete>(); | |
int* myInt = (int*)5; // simulate lua... | |
int* myInt2 = (int*)10; // simulate lua... | |
auto outt = wrapped(myInt, myInt2); | |
//std::cout << "Original Entity Delete: " << std::get<0>(outt) << " | " << std::get<1>(outt) << std::endl; | |
std::cout << "Original Entity Delete: " << std::get<0>(outt) << " | " << std::get<1>(outt) << " | " << std::get<2>(outt) << std::endl; | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment