Skip to content

Instantly share code, notes, and snippets.

@xiaoxiao921
Last active July 1, 2023 16:12
Show Gist options
  • Save xiaoxiao921/fc7bfedcd7309856095106c69b0eabd5 to your computer and use it in GitHub Desktop.
Save xiaoxiao921/fc7bfedcd7309856095106c69b0eabd5 to your computer and use it in GitHub Desktop.
Some C++ Templates
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