mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 08:15:59 -10:00
* Automatically generate the isValid() Lua API function for handle types If an ADE object's stored type has an isValid() member, ade_obj now registers an isValid function for it automatically. An explicit ADE_FUNC(isValid, ...) declared after the ADE_OBJ replaces the generated one, so types with special semantics (e.g. sound) keep their own. The 67 hand-written functions that merely delegated to the member are removed, and 26 types that previously exposed no isValid() gain one. Co-Authored-By: Claude Fable 5.1 <[email protected]> * Add ADE_OBJ_VALIDATOR for index-based Lua handles Int-index handles (shipclass, weaponclass, team, etc.) can now declare their validity predicate with a single ADE_OBJ_VALIDATOR or ADE_OBJ_VALIDATOR_RANGE line after the ADE_OBJ. The registered validator drives the generated isValid() function and is also honored by ade_set_args when the mod table asks for nil instead of invalid objects, which previously never applied to these handles. The 19 hand-written range-check functions are replaced, and loadout_ship, default_primary, default_secondary, and support_rearm_pool_team gain isValid(). Add a validator to HudGauge while we're at it. Also fully qualify the scripting namespace in LuaConvert.h so its templates can be instantiated from the unit tests. Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: Claude Fable 5.1 <[email protected]>
398 lines
13 KiB
C++
398 lines
13 KiB
C++
//
|
|
//
|
|
|
|
#ifndef FS2_OPEN_ADE_ARGS_H
|
|
#define FS2_OPEN_ADE_ARGS_H
|
|
|
|
#include "globalincs/pstypes.h"
|
|
#include "mod_table/mod_table.h"
|
|
#include "scripting/ade.h"
|
|
#include "scripting/lua/LuaConvert.h"
|
|
#include "scripting/lua/LuaFunction.h"
|
|
#include "scripting/lua/LuaTable.h"
|
|
#include <utf8/checked.h>
|
|
|
|
extern "C" {
|
|
#include <lauxlib.h>
|
|
#include <lualib.h>
|
|
}
|
|
|
|
namespace scripting {
|
|
|
|
namespace internal {
|
|
|
|
//*************************Lua hacks*************************
|
|
// WMC - Hack to allow for quick&easy return value parsing
|
|
extern int Ade_get_args_skip;
|
|
// WMC - Tell ade_get_args it is parsing scripting functions,
|
|
// which have no upvalues
|
|
extern bool Ade_get_args_lfunction;
|
|
|
|
struct get_args_state {
|
|
int nargs = -1;
|
|
int counted_args = -1;
|
|
|
|
int needed_args = -1;
|
|
int total_args = -1;
|
|
|
|
bool optional_args = false;
|
|
|
|
char funcname[128] = "\0";
|
|
};
|
|
|
|
bool get_single_arg(lua_State* L, const get_args_state& state, char fmt, bool* b);
|
|
template <typename T>
|
|
typename std::enable_if<std::is_floating_point<T>::value, bool>::type
|
|
get_single_arg(lua_State* L, const get_args_state& state, char fmt, T* f)
|
|
{
|
|
Assertion(fmt == 'f' || fmt == 'd', "Invalid character '%c' for number type!", fmt);
|
|
|
|
if (lua_isnumber(L, state.nargs)) {
|
|
*f = (T)lua_tonumber(L, state.nargs);
|
|
} else {
|
|
LuaError(L, "%s: Argument %d is an invalid type '%s'; number expected", state.funcname, state.nargs,
|
|
ade_get_type_string(L, state.nargs));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
template <typename T>
|
|
typename std::enable_if<std::is_integral<T>::value, bool>::type
|
|
get_single_arg(lua_State* L, const get_args_state& state, char fmt, T* i)
|
|
{
|
|
// fix is also an int for C++ so we need to check the format character to determine what should be done
|
|
Assertion(fmt == 'i' || fmt == 'x', "Invalid character '%c' for number type!", fmt);
|
|
|
|
if (lua_isnumber(L, state.nargs)) {
|
|
if (fmt == 'x') {
|
|
*i = (T)fl2f((float)lua_tonumber(L, state.nargs));
|
|
} else {
|
|
*i = (T)lua_tonumber(L, state.nargs);
|
|
}
|
|
} else {
|
|
LuaError(L, "%s: Argument %d is an invalid type '%s'; number expected", state.funcname, state.nargs,
|
|
ade_get_type_string(L, state.nargs));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
bool get_single_arg(lua_State* L, const get_args_state& state, char fmt, const char** s);
|
|
|
|
template <typename T>
|
|
bool ade_odata_getter_helper(lua_State* L, const get_args_state& state, char fmt, T&& od) {
|
|
Assertion(fmt == 'o', "Invalid character '%c' for object type!", fmt);
|
|
|
|
if (lua_isuserdata(L, state.nargs)) {
|
|
// Use the helper function
|
|
if (!luacpp::convert::popValue(L, std::forward<T>(od), state.nargs, false)) {
|
|
return false;
|
|
}
|
|
} else if (lua_isnil(L, state.nargs) && state.optional_args) {
|
|
// WMC - Modder has chosen to ignore this argument
|
|
} else {
|
|
LuaError(L, "%s: Argument %d is an invalid type '%s'; type '%s' expected", state.funcname, state.nargs,
|
|
ade_get_type_string(L, state.nargs), internal::getTableEntry(od.idx).GetName());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
template <typename T>
|
|
bool get_single_arg(lua_State* L, const get_args_state& state, char fmt, ade_odata_getter<T>&& od)
|
|
{
|
|
return ade_odata_getter_helper(L, state, fmt, std::forward<ade_odata_getter<T>>(od));
|
|
}
|
|
template <typename T>
|
|
bool get_single_arg(lua_State* L, const get_args_state& state, char fmt, ade_odata_ptr_getter<T>&& od)
|
|
{
|
|
return ade_odata_getter_helper(L, state, fmt, std::forward<ade_odata_ptr_getter<T>>(od));
|
|
}
|
|
|
|
bool get_single_arg(lua_State* L, const get_args_state& state, char fmt, luacpp::LuaTable* t);
|
|
bool get_single_arg(lua_State* L, const get_args_state& state, char fmt, luacpp::LuaFunction* f);
|
|
bool get_single_arg(lua_State* L, const get_args_state& state, char fmt, luacpp::LuaValue* f);
|
|
|
|
// This is not a template function so we can put the implementation in a source file
|
|
inline bool get_args_actual(lua_State* /*L*/, get_args_state& state, const char* fmt)
|
|
{
|
|
while (*fmt != '\0') {
|
|
switch(*fmt) {
|
|
case '|':
|
|
state.optional_args = true;
|
|
break;
|
|
case '*':
|
|
state.nargs++;
|
|
state.counted_args++;
|
|
break;
|
|
default:
|
|
Assertion(false, "Invalid format string '%s'!", fmt);
|
|
return false;
|
|
}
|
|
|
|
// Ignored parameters are still valid here so we skip them until we reach the end
|
|
++fmt;
|
|
}
|
|
// Now there should be nothing left in the parameter string
|
|
Assertion(strlen(fmt) == 0, "No class parameters left but format is not empty!");
|
|
return true;
|
|
}
|
|
|
|
template <typename T, typename... Args>
|
|
bool get_args_actual(lua_State* L, get_args_state& state, const char* fmt, T&& current, Args&&... args)
|
|
{
|
|
Assertion(strlen(fmt) > 0, "Format was empty but there were still parameters in the argument list!");
|
|
|
|
if (state.nargs > state.total_args) {
|
|
return true;
|
|
}
|
|
|
|
if (*fmt == '*') {
|
|
state.nargs++;
|
|
state.counted_args++;
|
|
return get_args_actual(L, state, fmt + 1, std::forward<T>(current), std::forward<Args>(args)...);
|
|
}
|
|
|
|
if (*fmt == '|') {
|
|
state.optional_args = true;
|
|
return get_args_actual(L, state, fmt + 1, std::forward<T>(current), std::forward<Args>(args)...);
|
|
}
|
|
|
|
if (!get_single_arg(L, state, *fmt, std::forward<T>(current))) {
|
|
if (!state.optional_args) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
state.nargs++;
|
|
state.counted_args++;
|
|
return get_args_actual(L, state, fmt + 1, std::forward<Args>(args)...);
|
|
}
|
|
|
|
} // namespace internal
|
|
|
|
/**
|
|
* @brief Parses arguments from the Lua stack
|
|
*
|
|
* based on "Programming in Lua"
|
|
*
|
|
* Parses arguments from string to variables given
|
|
* a '|' divides required and optional arguments.
|
|
* Returns 0 if a required argument is invalid,
|
|
* or there are too few arguments actually passed
|
|
*
|
|
* @note This function essentially takes objects
|
|
* from the stack in series, so it can easily be used
|
|
* to get the return values from a chunk of Lua code
|
|
* after it has been executed. See RunByteCode()
|
|
*
|
|
* @param L The lua state
|
|
* @param fmt The argument format string
|
|
* @param ... The parameters
|
|
* @return The number of arguments taken from the stack or 0 on error
|
|
*/
|
|
template <typename... Args>
|
|
inline int ade_get_args(lua_State* L, const char* fmt, Args&&... args)
|
|
{
|
|
// Capture these variables locally and reset them. This is needed in case we indirectly call this function again
|
|
// from below which would cause some weird issues. This can happen if we call a Lua API function which then causes a
|
|
// GC run which then calls one of our destructors.
|
|
const auto get_args_skip = internal::Ade_get_args_skip;
|
|
const auto get_args_lfunction = internal::Ade_get_args_lfunction;
|
|
internal::Ade_get_args_skip = 0;
|
|
internal::Ade_get_args_lfunction = false;
|
|
|
|
// Check that we have all the arguments that we need
|
|
// If we don't, return 0
|
|
internal::get_args_state state;
|
|
state.needed_args = (int)strlen(fmt);
|
|
state.total_args = lua_gettop(L) - get_args_skip;
|
|
|
|
if (strchr(fmt, '|') != nullptr) {
|
|
state.needed_args = (int)(strchr(fmt, '|') - fmt);
|
|
}
|
|
|
|
memset(state.funcname, 0, sizeof(state.funcname));
|
|
#ifndef NDEBUG
|
|
lua_Debug ar;
|
|
memset(&ar, 0, sizeof(ar));
|
|
if (lua_getstack(L, 0, &ar)) {
|
|
lua_getinfo(L, "nl", &ar);
|
|
strcpy_s(state.funcname, "");
|
|
if (ar.name != nullptr) {
|
|
strcat_s(state.funcname, ar.name);
|
|
}
|
|
if (ar.currentline > -1) {
|
|
char buf[33];
|
|
sprintf(buf, "%d", ar.currentline);
|
|
strcat_s(state.funcname, " (Line ");
|
|
strcat_s(state.funcname, buf);
|
|
strcat_s(state.funcname, ")");
|
|
}
|
|
}
|
|
#endif
|
|
if (!strlen(state.funcname)) {
|
|
// WMC - This was causing crashes with user-defined functions.
|
|
// WMC - Try and get at function name from upvalue
|
|
if (!get_args_lfunction && !lua_isnone(L, lua_upvalueindex(ADE_FUNCNAME_UPVALUE_INDEX))) {
|
|
if (lua_type(L, lua_upvalueindex(ADE_FUNCNAME_UPVALUE_INDEX)) == LUA_TSTRING)
|
|
strcpy_s(state.funcname, lua_tostring_nullsafe(L, lua_upvalueindex(ADE_FUNCNAME_UPVALUE_INDEX)));
|
|
}
|
|
|
|
// WMC - Totally unknown function
|
|
if (!strlen(state.funcname)) {
|
|
strcpy_s(state.funcname, "<UNKNOWN>");
|
|
}
|
|
}
|
|
if (state.total_args < state.needed_args) {
|
|
LuaError(L,
|
|
"Not enough arguments for '%s' - need %d, had %d. If you are using objects or handles, make sure that "
|
|
"you are using \":\" to access member functions, rather than \".\"",
|
|
state.funcname, state.needed_args, state.total_args);
|
|
return 0;
|
|
}
|
|
|
|
// Start throught
|
|
state.counted_args = 0;
|
|
|
|
// Are we parsing optional args yet?
|
|
state.optional_args = false;
|
|
|
|
state.nargs = 1 + get_args_skip;
|
|
state.total_args += get_args_skip;
|
|
if (!internal::get_args_actual(L, state, fmt, std::forward<Args>(args)...)) {
|
|
return 0;
|
|
}
|
|
|
|
return state.counted_args;
|
|
}
|
|
|
|
namespace internal {
|
|
struct set_args_state {
|
|
int nargs;
|
|
int setargs; // args actually set
|
|
};
|
|
|
|
// This is an extremely complicated way of telling the compiler that this function should not be called with pointers
|
|
template <typename T>
|
|
typename std::enable_if<std::is_same<typename std::remove_cv<T>::type, bool>::value, void>::type
|
|
set_single_arg(lua_State* L, char fmt, T b)
|
|
{
|
|
Assertion(fmt == 'b', "Invalid format character '%c' for boolean type!", fmt);
|
|
lua_pushboolean(L, b ? 1 : 0);
|
|
}
|
|
template <typename T>
|
|
typename std::enable_if<std::is_floating_point<T>::value, void>::type set_single_arg(lua_State* L, char fmt, T f)
|
|
{
|
|
Assertion(fmt == 'f' || fmt == 'd', "Invalid character '%c' for number type!", fmt);
|
|
lua_pushnumber(L, static_cast<lua_Number>(f));
|
|
}
|
|
template <typename T>
|
|
typename std::enable_if<!std::is_same<T, bool>::value && std::is_integral<T>::value, void>::type
|
|
set_single_arg(lua_State* L, char fmt, T i)
|
|
{
|
|
// fix is also an int for C++ so we need to check the format character to determine what should be done
|
|
Assertion(fmt == 'i' || fmt == 'x', "Invalid character '%c' for number type!", fmt);
|
|
|
|
if (fmt == 'x') {
|
|
lua_pushnumber(L, static_cast<lua_Number>(f2fl((fix)i)));
|
|
} else {
|
|
lua_pushinteger(L, static_cast<lua_Integer>(i));
|
|
}
|
|
}
|
|
void set_single_arg(lua_State* L, char fmt, const char* s);
|
|
void set_single_arg(lua_State* L, char fmt, const SCP_string& s);
|
|
template<typename T>
|
|
void set_single_arg(lua_State* L, char fmt, ade_odata_setter<T>&& od)
|
|
{
|
|
Assertion(fmt == 'o', "Invalid format character '%c' for object type!", fmt);
|
|
|
|
if (Lua_API_returns_nil_instead_of_invalid_object) {
|
|
// A registered validator wins; otherwise, if the underlying type has an isValid, it'll be evaluated;
|
|
// otherwise it'll be always true.
|
|
bool isValid = ade_object_is_valid(od.idx, od.value);
|
|
|
|
if (!isValid) {
|
|
lua_pushnil(L);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Use the common helper method
|
|
luacpp::convert::pushValue(L, std::forward<ade_odata_setter<T>>(od));
|
|
}
|
|
void set_single_arg(lua_State* /*L*/, char fmt, luacpp::LuaTable* table);
|
|
void set_single_arg(lua_State* /*L*/, char fmt, const luacpp::LuaTable& table);
|
|
|
|
void set_single_arg(lua_State* /*L*/, char fmt, luacpp::LuaFunction* func);
|
|
void set_single_arg(lua_State* /*L*/, char fmt, const luacpp::LuaFunction& func);
|
|
|
|
void set_single_arg(lua_State* /*L*/, char fmt, const luacpp::LuaValue& func);
|
|
|
|
// This is not a template function so we can put the implementation in a source file
|
|
inline void set_args_actual(lua_State* /*L*/, set_args_state& /*state*/, const char* fmt)
|
|
{
|
|
Assertion(strlen(fmt) == 0, "No class parameters left but format is not empty!");
|
|
}
|
|
|
|
template <typename T, typename... Args>
|
|
void set_args_actual(lua_State* L, set_args_state& state, const char* fmt, T&& current, Args&&... args)
|
|
{
|
|
Assertion(strlen(fmt) > 0, "Format was empty but there were still parameters in the argument list!");
|
|
|
|
if (*fmt == '*') {
|
|
lua_pushnil(L);
|
|
state.nargs++;
|
|
state.setargs++;
|
|
return set_args_actual(L, state, fmt + 1, std::forward<T>(current), std::forward<Args>(args)...);
|
|
}
|
|
|
|
set_single_arg(L, *fmt, std::forward<T>(current));
|
|
|
|
state.nargs++;
|
|
state.setargs++;
|
|
return set_args_actual(L, state, fmt + 1, std::forward<Args>(args)...);
|
|
}
|
|
} // namespace internal
|
|
|
|
// ade_set_args(state, arguments, variables)
|
|
//----------------------------------------------
|
|
// based on "Programming in Lua"
|
|
//
|
|
// Takes variables given and pushes them onto the
|
|
// Lua stack. Use it to return variables from a
|
|
// Lua scripting function.
|
|
//
|
|
// NOTE: You can also use this to push arguments
|
|
// on to the stack in series. See script_state::SetHookVar
|
|
template <typename... Args>
|
|
int ade_set_args(lua_State* L, const char* fmt, Args&&... args)
|
|
{
|
|
// Start throught
|
|
internal::set_args_state state{};
|
|
state.nargs = 0;
|
|
state.setargs = 0;
|
|
|
|
internal::set_args_actual(L, state, fmt, std::forward<Args>(args)...);
|
|
|
|
return state.setargs;
|
|
}
|
|
|
|
/**
|
|
* @brief Return a value in error. Normally this behaves just like ade_set_args, but it can be configured to return nil instead.
|
|
*
|
|
* Should be used if the value is not valid
|
|
*
|
|
* @ingroup ade_api
|
|
*/
|
|
template <typename... Args>
|
|
inline int ade_set_error(lua_State* L, const char* fmt, Args&&... args)
|
|
{
|
|
if (Lua_API_returns_nil_instead_of_invalid_object)
|
|
return 0; // ADE_RETURN_NIL
|
|
else
|
|
return ade_set_args(L, fmt, std::forward<Args>(args)...);
|
|
}
|
|
}
|
|
|
|
#endif //FS2_OPEN_ADE_ARGS_H
|