raze/source/common/scripting/vm/vm.h
Christoph Oelckers 762263671a exported the main structures in Blood that make up a BloodActor.
nnext stuff was omitted for now.
This also eliminates the DUDEEXTRA_STATS substructure and integrates its content directly into DUDEEXTRA.
2023-10-01 22:25:08 +02:00

850 lines
30 KiB
C++

/*
** vm.h
** VM <-> native interface
**
**---------------------------------------------------------------------------
** Copyright -2016 Randy Heit
** Copyright 2016 Christoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#ifndef VM_H
#define VM_H
#include "autosegs.h"
#include "zstring.h"
#include "vectors.h"
#include "quaternion.h"
#include "cmdlib.h"
#include "engineerrors.h"
#include "memarena.h"
#include "name.h"
#include "scopebarrier.h"
#include <type_traits>
class DObject;
union VMOP;
class VMScriptFunction;
extern FMemArena ClassDataAllocator;
#define MAX_RETURNS 8 // Maximum number of results a function called by script code can return
#define MAX_TRY_DEPTH 8 // Maximum number of nested TRYs in a single function
void JitRelease();
extern void (*VM_CastSpriteIDToString)(FString* a, unsigned int b);
typedef unsigned char VM_UBYTE;
typedef signed char VM_SBYTE;
typedef unsigned short VM_UHALF;
typedef signed short VM_SHALF;
typedef unsigned int VM_UWORD;
typedef signed int VM_SWORD;
#define VM_EPSILON (1/65536.0)
// Register types for VMParam
enum
{
REGT_INT = 0,
REGT_FLOAT = 1,
REGT_STRING = 2,
REGT_POINTER = 3,
REGT_TYPE = 3,
REGT_KONST = 4,
REGT_MULTIREG2 = 8,
REGT_MULTIREG3 = 16, // (e.g. a vector)
REGT_MULTIREG = 8 | 16 | 64,
REGT_ADDROF = 32, // used with PARAM: pass address of this register
REGT_MULTIREG4 = 64,
REGT_NIL = 128 // parameter was omitted
};
#define RET_FINAL (0x80) // Used with RET and RETI in the destination slot: this is the final return value
enum EVMAbortException
{
X_OTHER,
X_READ_NIL,
X_WRITE_NIL,
X_TOO_MANY_TRIES,
X_ARRAY_OUT_OF_BOUNDS,
X_DIVISION_BY_ZERO,
X_BAD_SELF,
X_FORMAT_ERROR
};
class CVMAbortException : public CEngineError
{
public:
static FString stacktrace;
CVMAbortException(EVMAbortException reason, const char *moreinfo, va_list ap);
void MaybePrintMessage();
};
// This must be a separate function because the VC compiler would otherwise allocate memory on the stack for every separate instance of the exception object that may get thrown.
[[noreturn]] void ThrowAbortException(EVMAbortException reason, const char *moreinfo, ...);
[[noreturn]] void ThrowAbortException(VMScriptFunction *sfunc, VMOP *line, EVMAbortException reason, const char *moreinfo, ...);
void ClearGlobalVMStack();
struct VMReturn
{
void *Location;
VM_UBYTE RegType; // Same as VMParam RegType, except REGT_KONST is invalid; only used by asserts
void SetInt(int val)
{
assert(RegType == REGT_INT);
*(int *)Location = val;
}
void SetFloat(double val)
{
assert(RegType == REGT_FLOAT);
*(double *)Location = val;
}
void SetVector4(const double val[4])
{
assert(RegType == (REGT_FLOAT|REGT_MULTIREG4));
((double *)Location)[0] = val[0];
((double *)Location)[1] = val[1];
((double *)Location)[2] = val[2];
((double *)Location)[3] = val[3];
}
void SetVector4(const DVector4 &val)
{
assert(RegType == (REGT_FLOAT | REGT_MULTIREG4));
((double *)Location)[0] = val[0];
((double *)Location)[1] = val[1];
((double *)Location)[2] = val[2];
((double *)Location)[3] = val[3];
}
void SetQuaternion(const DQuaternion &val)
{
assert(RegType == (REGT_FLOAT | REGT_MULTIREG4));
((double *)Location)[0] = val[0];
((double *)Location)[1] = val[1];
((double *)Location)[2] = val[2];
((double *)Location)[3] = val[3];
}
void SetVector(const double val[3])
{
assert(RegType == (REGT_FLOAT|REGT_MULTIREG3));
((double *)Location)[0] = val[0];
((double *)Location)[1] = val[1];
((double *)Location)[2] = val[2];
}
void SetVector(const DVector3 &val)
{
assert(RegType == (REGT_FLOAT | REGT_MULTIREG3));
((double *)Location)[0] = val[0];
((double *)Location)[1] = val[1];
((double *)Location)[2] = val[2];
}
void SetVector2(const double val[2])
{
assert(RegType == (REGT_FLOAT|REGT_MULTIREG2));
((double *)Location)[0] = val[0];
((double *)Location)[1] = val[1];
}
void SetVector2(const DVector2 &val)
{
assert(RegType == (REGT_FLOAT | REGT_MULTIREG2));
((double *)Location)[0] = val[0];
((double *)Location)[1] = val[1];
}
void SetString(const FString &val)
{
assert(RegType == REGT_STRING);
*(FString *)Location = val;
}
void SetPointer(void *val)
{
assert(RegType == REGT_POINTER);
*(void **)Location = val;
}
void SetConstPointer(const void *val)
{
assert(RegType == REGT_POINTER);
*(const void **)Location = val;
}
void SetObject(DObject *val)
{
assert(RegType == REGT_POINTER);
*(void **)Location = val;
}
void IntAt(int *loc)
{
Location = loc;
RegType = REGT_INT;
}
void FloatAt(double *loc)
{
Location = loc;
RegType = REGT_FLOAT;
}
void Vec2At(DVector2 *loc)
{
Location = loc;
RegType = REGT_FLOAT | REGT_MULTIREG2;
}
void Vec3At(DVector3 *loc)
{
Location = loc;
RegType = REGT_FLOAT | REGT_MULTIREG3;
}
void StringAt(FString *loc)
{
Location = loc;
RegType = REGT_STRING;
}
void PointerAt(void **loc)
{
Location = loc;
RegType = REGT_POINTER;
}
VMReturn() { }
VMReturn(int *loc) { IntAt(loc); }
VMReturn(double *loc) { FloatAt(loc); }
VMReturn(DVector2 *loc) { Vec2At(loc); }
VMReturn(DVector3 *loc) { Vec3At(loc); }
VMReturn(FString *loc) { StringAt(loc); }
VMReturn(void **loc) { PointerAt(loc); }
};
struct VMRegisters;
struct TypedVMValue
{
union
{
int i;
void *a;
double f;
struct { int pad[3]; VM_UBYTE Type; };
struct { int foo[4]; } biggest;
const FString *sp;
};
const FString &s() const { return *sp; }
TypedVMValue()
{
a = NULL;
Type = REGT_NIL;
}
TypedVMValue(const TypedVMValue &o)
{
biggest = o.biggest;
}
TypedVMValue(int v)
{
i = v;
Type = REGT_INT;
}
TypedVMValue(double v)
{
f = v;
Type = REGT_FLOAT;
}
TypedVMValue(const FString *s)
{
sp = s;
Type = REGT_STRING;
}
TypedVMValue(DObject *v)
{
a = v;
Type = REGT_POINTER;
}
TypedVMValue(void *v)
{
a = v;
Type = REGT_POINTER;
}
TypedVMValue &operator=(const TypedVMValue &o)
{
biggest = o.biggest;
return *this;
}
TypedVMValue &operator=(int v)
{
i = v;
Type = REGT_INT;
return *this;
}
TypedVMValue &operator=(double v)
{
f = v;
Type = REGT_FLOAT;
return *this;
}
TypedVMValue &operator=(const FString *v)
{
sp = v;
Type = REGT_STRING;
return *this;
}
TypedVMValue &operator=(DObject *v)
{
a = v;
Type = REGT_POINTER;
return *this;
}
};
struct VMValue
{
union
{
int i;
void *a;
double f;
struct { int foo[2]; } biggest;
const FString *sp;
};
const FString &s() const { return *sp; }
VMValue()
{
a = NULL;
}
VMValue(const VMValue &o)
{
biggest = o.biggest;
}
VMValue(int v)
{
i = v;
}
VMValue(unsigned int v)
{
i = v;
}
VMValue(double v)
{
f = v;
}
VMValue(const char *s) = delete;
VMValue(const FString &s) = delete;
VMValue(const FString *s)
{
sp = s;
}
VMValue(void *v)
{
a = v;
}
VMValue &operator=(const VMValue &o)
{
biggest = o.biggest;
return *this;
}
VMValue &operator=(const TypedVMValue &o)
{
memcpy(&biggest, &o.biggest, sizeof(biggest));
return *this;
}
VMValue &operator=(int v)
{
i = v;
return *this;
}
VMValue &operator=(double v)
{
f = v;
return *this;
}
VMValue &operator=(const FString *v)
{
sp = v;
return *this;
}
VMValue &operator=(const FString &v) = delete;
VMValue &operator=(const char *v) = delete;
VMValue &operator=(DObject *v)
{
a = v;
return *this;
}
int ToInt(int Type)
{
if (Type == REGT_INT)
{
return i;
}
if (Type == REGT_FLOAT)
{
return int(f);
}
if (Type == REGT_STRING)
{
return (int)s().ToLong();
}
// FIXME
return 0;
}
double ToDouble(int Type)
{
if (Type == REGT_FLOAT)
{
return f;
}
if (Type == REGT_INT)
{
return i;
}
if (Type == REGT_STRING)
{
return s().ToDouble();
}
// FIXME
return 0;
}
};
class VMFunction
{
public:
bool Unsafe = false;
uint8_t ImplicitArgs = 0; // either 0 for static, 1 for method or 3 for action
int VarFlags = 0; // [ZZ] this replaces 5+ bool fields
unsigned VirtualIndex = ~0u;
FName Name;
const uint8_t *RegTypes = nullptr;
TArray<TypedVMValue> DefaultArgs;
const char* QualifiedName = nullptr;
const char* PrintableName = nullptr; // same as QualifiedName, but can have additional annotations.
class PPrototype *Proto;
TArray<uint32_t> ArgFlags; // Should be the same length as Proto->ArgumentTypes
int(*ScriptCall)(VMFunction *func, VMValue *params, int numparams, VMReturn *ret, int numret) = nullptr;
VMFunction(FName name = NAME_None) : ImplicitArgs(0), Name(name), Proto(NULL)
{
AllFunctions.Push(this);
}
virtual ~VMFunction() {}
void *operator new(size_t size)
{
return ClassDataAllocator.Alloc(size);
}
void operator delete(void *block) {}
void operator delete[](void *block) {}
static void DeleteAll()
{
for (auto f : AllFunctions)
{
f->~VMFunction();
}
AllFunctions.Clear();
// also release any JIT data
JitRelease();
}
static void CreateRegUseInfo()
{
for (auto f : AllFunctions)
{
f->CreateRegUse();
}
}
static TArray<VMFunction *> AllFunctions;
protected:
void CreateRegUse();
};
// Use this in the prototype for a native function.
#ifdef NDEBUG
#define VM_ARGS VMValue *param, int numparam, VMReturn *ret, int numret
#define VM_ARGS_NAMES param, numparam, ret, numret
#define VM_INVOKE(param, numparam, ret, numret, reginfo) (param), (numparam), (ret), (numret)
#else
#define VM_ARGS VMValue *param, int numparam, VMReturn *ret, int numret, const uint8_t *reginfo
#define VM_ARGS_NAMES param, numparam, ret, numret, reginfo
#define VM_INVOKE(param, numparam, ret, numret, reginfo) (param), (numparam), (ret), (numret), (reginfo)
#endif
class VMNativeFunction : public VMFunction
{
public:
typedef int (*NativeCallType)(VM_ARGS);
// 8 is VARF_Native. I can't write VARF_Native because of circular references between this and dobject/dobjtype.
VMNativeFunction() : NativeCall(NULL) { VarFlags = 8; ScriptCall = &VMNativeFunction::NativeScriptCall; }
VMNativeFunction(NativeCallType call) : NativeCall(call) { VarFlags = 8; ScriptCall = &VMNativeFunction::NativeScriptCall; }
VMNativeFunction(NativeCallType call, FName name) : VMFunction(name), NativeCall(call) { VarFlags = 8; ScriptCall = &VMNativeFunction::NativeScriptCall; }
// Return value is the number of results.
NativeCallType NativeCall;
// Function pointer to a native function to be called directly by the JIT using the platform calling convention
void *DirectNativeCall = nullptr;
private:
static int NativeScriptCall(VMFunction *func, VMValue *params, int numparams, VMReturn *ret, int numret);
};
int VMCall(VMFunction *func, VMValue *params, int numparams, VMReturn *results, int numresults/*, VMException **trap = NULL*/);
int VMCallWithDefaults(VMFunction *func, TArray<VMValue> &params, VMReturn *results, int numresults/*, VMException **trap = NULL*/);
inline int VMCallAction(VMFunction *func, VMValue *params, int numparams, VMReturn *results, int numresults/*, VMException **trap = NULL*/)
{
return VMCall(func, params, numparams, results, numresults);
}
// Use these to collect the parameters in a native function.
// variable name <x> at position <p>
[[noreturn]]
void NullParam(const char *varname);
#ifndef NDEBUG
bool AssertObject(void * ob);
#endif
#define PARAM_NULLCHECK(ptr, var) (ptr == nullptr? NullParam(#var), ptr : ptr)
// This cannot assert because there is no info for varargs
#define PARAM_VA_POINTER(x) const uint8_t *x = (const uint8_t *)param[numparam-1].a;
// For required parameters.
#define PARAM_INT_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); int x = param[p].i;
#define PARAM_UINT_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); unsigned x = param[p].i;
#define PARAM_BOOL_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); bool x = !!param[p].i;
#define PARAM_NAME_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); FName x = ENamedName(param[p].i);
#define PARAM_SOUND_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); FSoundID x = FSoundID::fromInt(param[p].i);
#define PARAM_COLOR_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); PalEntry x = param[p].i;
#define PARAM_FLOAT_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_FLOAT); double x = param[p].f;
#define PARAM_ANGLE_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_FLOAT); DAngle x = DAngle::fromDeg(param[p].f);
#define PARAM_FANGLE_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_FLOAT); FAngle x = FAngle::fromDeg(param[p].f);
#define PARAM_STRING_VAL_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_STRING); FString x = param[p].s();
#define PARAM_STRING_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_STRING); const FString &x = param[p].s();
#define PARAM_STATELABEL_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); int x = param[p].i;
#define PARAM_STATE_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); FState *x = (FState *)StateLabels.GetState(param[p].i, self->GetClass());
#define PARAM_STATE_ACTION_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); FState *x = (FState *)StateLabels.GetState(param[p].i, stateowner->GetClass());
#define PARAM_POINTER_AT(p,x,type) assert((p) < numparam); assert(reginfo[p] == REGT_POINTER); type *x = (type *)param[p].a;
#define PARAM_OUTPOINTER_AT(p,x,type) assert((p) < numparam); type *x = (type *)param[p].a;
#define PARAM_POINTERTYPE_AT(p,x,type) assert((p) < numparam); assert(reginfo[p] == REGT_POINTER); type x = (type )param[p].a;
#define PARAM_OBJECT_AT(p,x,type) assert((p) < numparam); assert(reginfo[p] == REGT_POINTER && AssertObject(param[p].a)); type *x = (type *)param[p].a; assert(x == NULL || x->IsKindOf(RUNTIME_CLASS(type)));
#define PARAM_CLASS_AT(p,x,base) assert((p) < numparam); assert(reginfo[p] == REGT_POINTER); base::MetaClass *x = (base::MetaClass *)param[p].a; assert(x == NULL || x->IsDescendantOf(RUNTIME_CLASS(base)));
#define PARAM_POINTER_NOT_NULL_AT(p,x,type) assert((p) < numparam); assert(reginfo[p] == REGT_POINTER); type *x = (type *)PARAM_NULLCHECK(param[p].a, #x);
#define PARAM_OBJECT_NOT_NULL_AT(p,x,type) assert((p) < numparam); assert(reginfo[p] == REGT_POINTER && (AssertObject(param[p].a))); type *x = (type *)PARAM_NULLCHECK(param[p].a, #x); assert(x == NULL || x->IsKindOf(RUNTIME_CLASS(type)));
#define PARAM_CLASS_NOT_NULL_AT(p,x,base) assert((p) < numparam); assert(reginfo[p] == REGT_POINTER); base::MetaClass *x = (base::MetaClass *)PARAM_NULLCHECK(param[p].a, #x); assert(x == NULL || x->IsDescendantOf(RUNTIME_CLASS(base)));
// The above, but with an automatically increasing position index.
#define PARAM_PROLOGUE int paramnum = -1;
#define PARAM_INT(x) ++paramnum; PARAM_INT_AT(paramnum,x)
#define PARAM_UINT(x) ++paramnum; PARAM_UINT_AT(paramnum,x)
#define PARAM_BOOL(x) ++paramnum; PARAM_BOOL_AT(paramnum,x)
#define PARAM_NAME(x) ++paramnum; PARAM_NAME_AT(paramnum,x)
#define PARAM_SOUND(x) ++paramnum; PARAM_SOUND_AT(paramnum,x)
#define PARAM_COLOR(x) ++paramnum; PARAM_COLOR_AT(paramnum,x)
#define PARAM_FLOAT(x) ++paramnum; PARAM_FLOAT_AT(paramnum,x)
#define PARAM_ANGLE(x) ++paramnum; PARAM_ANGLE_AT(paramnum,x)
#define PARAM_FANGLE(x) ++paramnum; PARAM_FANGLE_AT(paramnum,x)
#define PARAM_STRING(x) ++paramnum; PARAM_STRING_AT(paramnum,x)
#define PARAM_STRING_VAL(x) ++paramnum; PARAM_STRING_VAL_AT(paramnum,x)
#define PARAM_STATELABEL(x) ++paramnum; PARAM_STATELABEL_AT(paramnum,x)
#define PARAM_STATE(x) ++paramnum; PARAM_STATE_AT(paramnum,x)
#define PARAM_STATE_ACTION(x) ++paramnum; PARAM_STATE_ACTION_AT(paramnum,x)
#define PARAM_POINTER(x,type) ++paramnum; PARAM_POINTER_AT(paramnum,x,type)
#define PARAM_OUTPOINTER(x,type) ++paramnum; PARAM_OUTPOINTER_AT(paramnum,x,type)
#define PARAM_POINTERTYPE(x,type) ++paramnum; PARAM_POINTERTYPE_AT(paramnum,x,type)
#define PARAM_OBJECT(x,type) ++paramnum; PARAM_OBJECT_AT(paramnum,x,type)
#define PARAM_CLASS(x,base) ++paramnum; PARAM_CLASS_AT(paramnum,x,base)
#define PARAM_CLASS(x,base) ++paramnum; PARAM_CLASS_AT(paramnum,x,base)
#define PARAM_POINTER_NOT_NULL(x,type) ++paramnum; PARAM_POINTER_NOT_NULL_AT(paramnum,x,type)
#define PARAM_OBJECT_NOT_NULL(x,type) ++paramnum; PARAM_OBJECT_NOT_NULL_AT(paramnum,x,type)
#define PARAM_CLASS_NOT_NULL(x,base) ++paramnum; PARAM_CLASS_NOT_NULL_AT(paramnum,x,base)
typedef int(*actionf_p)(VM_ARGS);
struct FieldDesc
{
const char *ClassName;
const char *FieldName;
size_t FieldOffset;
unsigned FieldSize;
int BitValue;
};
namespace
{
// Traits for the types we are interested in
template<typename T> struct native_is_valid { static const bool value = false; static const bool retval = false; };
template<typename T> struct native_is_valid<T*> { static const bool value = true; static const bool retval = true; };
template<typename T> struct native_is_valid<T&> { static const bool value = true; static const bool retval = true; };
template<> struct native_is_valid<void> { static const bool value = true; static const bool retval = true; };
template<> struct native_is_valid<int> { static const bool value = true; static const bool retval = true; };
// [RL0] this is disabled for now due to graf's concerns
// template<> struct native_is_valid<FName> { static const bool value = true; static const bool retval = true; static_assert(sizeof(FName) == sizeof(int)); static_assert(std::is_pod_v<FName>);};
template<> struct native_is_valid<unsigned int> { static const bool value = true; static const bool retval = true; };
template<> struct native_is_valid<double> { static const bool value = true; static const bool retval = true; };
template<> struct native_is_valid<bool> { static const bool value = true; static const bool retval = false;}; // Bool as return does not work!
}
// Compile time validation of direct native functions
struct DirectNativeDesc
{
DirectNativeDesc() = default;
template<typename Ret, typename... Params> DirectNativeDesc(Ret(*func)(Params...)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); (ValidateType<Params>(), ...); }
template<typename T> void ValidateType() { static_assert(native_is_valid<T>::value, "Argument type is not valid as a direct native parameter or return type"); }
template<typename T> void ValidateRet() { static_assert(native_is_valid<T>::retval, "Return type is not valid as a direct native parameter or return type"); }
operator void *() const { return Ptr; }
void *Ptr;
};
struct AFuncDesc
{
const char *ClassName;
const char *FuncName;
actionf_p Function;
VMNativeFunction **VMPointer;
DirectNativeDesc DirectNative;
};
#if defined(_MSC_VER)
#pragma section(SECTION_AREG,read)
#pragma section(SECTION_FREG,read)
#define MSVC_ASEG __declspec(allocate(SECTION_AREG))
#define MSVC_FSEG __declspec(allocate(SECTION_FREG))
#define GCC_ASEG
#define GCC_FSEG
#else
#define MSVC_ASEG
#define MSVC_FSEG
#define GCC_ASEG __attribute__((section(SECTION_AREG))) __attribute__((used))
#define GCC_FSEG __attribute__((section(SECTION_FREG))) __attribute__((used))
#endif
// Macros to handle action functions. These are here so that I don't have to
// change every single use in case the parameters change.
#define DEFINE_ACTION_FUNCTION_NATIVE(cls, name, native) \
static int AF_##cls##_##name(VM_ARGS); \
VMNativeFunction *cls##_##name##_VMPtr; \
static const AFuncDesc cls##_##name##_Hook = { #cls, #name, AF_##cls##_##name, &cls##_##name##_VMPtr, native }; \
extern AFuncDesc const *const cls##_##name##_HookPtr; \
MSVC_ASEG AFuncDesc const *const cls##_##name##_HookPtr GCC_ASEG = &cls##_##name##_Hook; \
static int AF_##cls##_##name(VM_ARGS)
#define DEFINE_ACTION_FUNCTION_NATIVE0(cls, name, native) \
static int AF_##cls##_##name(VM_ARGS); \
VMNativeFunction *cls##_##name##_VMPtr; \
static const AFuncDesc cls##_##name##_Hook = { #cls, #name, AF_##cls##_##name, &cls##_##name##_VMPtr }; \
extern AFuncDesc const *const cls##_##name##_HookPtr; \
MSVC_ASEG AFuncDesc const *const cls##_##name##_HookPtr GCC_ASEG = &cls##_##name##_Hook; \
static int AF_##cls##_##name(VM_ARGS)
#define DEFINE_ACTION_FUNCTION(cls, name) \
static int AF_##cls##_##name(VM_ARGS); \
VMNativeFunction *cls##_##name##_VMPtr; \
static const AFuncDesc cls##_##name##_Hook = { #cls, #name, AF_##cls##_##name, &cls##_##name##_VMPtr }; \
extern AFuncDesc const *const cls##_##name##_HookPtr; \
MSVC_ASEG AFuncDesc const *const cls##_##name##_HookPtr GCC_ASEG = &cls##_##name##_Hook; \
static int AF_##cls##_##name(VM_ARGS)
// cls is the scripted class name, icls the internal one (e.g. player_t vs. Player)
#define DEFINE_FIELD_X(cls, icls, name) \
static const FieldDesc VMField_##icls##_##name = { "A" #cls, #name, (unsigned)myoffsetof(icls, name), (unsigned)sizeof(icls::name), 0 }; \
extern FieldDesc const *const VMField_##icls##_##name##_HookPtr; \
MSVC_FSEG FieldDesc const *const VMField_##icls##_##name##_HookPtr GCC_FSEG = &VMField_##icls##_##name;
// This is for cases where the internal size does not match the part that gets exported.
#define DEFINE_FIELD_UNSIZED(cls, icls, name) \
static const FieldDesc VMField_##icls##_##name = { "A" #cls, #name, (unsigned)myoffsetof(icls, name), ~0u, 0 }; \
extern FieldDesc const *const VMField_##icls##_##name##_HookPtr; \
MSVC_FSEG FieldDesc const *const VMField_##icls##_##name##_HookPtr GCC_FSEG = &VMField_##icls##_##name;
#define DEFINE_FIELD_NAMED_X(cls, icls, name, scriptname) \
static const FieldDesc VMField_##cls##_##scriptname = { "A" #cls, #scriptname, (unsigned)myoffsetof(icls, name), (unsigned)sizeof(icls::name), 0 }; \
extern FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr; \
MSVC_FSEG FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr GCC_FSEG = &VMField_##cls##_##scriptname;
#define DEFINE_FIELD_X_BIT(cls, icls, name, bitval) \
static const FieldDesc VMField_##icls##_##name = { "A" #cls, #name, (unsigned)myoffsetof(icls, name), (unsigned)sizeof(icls::name), bitval }; \
extern FieldDesc const *const VMField_##icls##_##name##_HookPtr; \
MSVC_FSEG FieldDesc const *const VMField_##icls##_##name##_HookPtr GCC_FSEG = &VMField_##cls##_##name;
#define DEFINE_FIELD(cls, name) \
static const FieldDesc VMField_##cls##_##name = { #cls, #name, (unsigned)myoffsetof(cls, name), (unsigned)sizeof(cls::name), 0 }; \
extern FieldDesc const *const VMField_##cls##_##name##_HookPtr; \
MSVC_FSEG FieldDesc const *const VMField_##cls##_##name##_HookPtr GCC_FSEG = &VMField_##cls##_##name;
#define DEFINE_FIELD_NAMED(cls, name, scriptname) \
static const FieldDesc VMField_##cls##_##scriptname = { #cls, #scriptname, (unsigned)myoffsetof(cls, name), (unsigned)sizeof(cls::name), 0 }; \
extern FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr; \
MSVC_FSEG FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr GCC_FSEG = &VMField_##cls##_##scriptname;
#define DEFINE_FIELD_NAMED_UNSIZED(cls, name, scriptname) \
static const FieldDesc VMField_##cls##_##scriptname = { #cls, #scriptname, (unsigned)myoffsetof(cls, name), ~0u, 0 }; \
extern FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr; \
MSVC_FSEG FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr GCC_FSEG = &VMField_##cls##_##scriptname;
#define DEFINE_FIELD_BIT(cls, name, scriptname, bitval) \
static const FieldDesc VMField_##cls##_##scriptname = { #cls, #scriptname, (unsigned)myoffsetof(cls, name), (unsigned)sizeof(cls::name), bitval }; \
extern FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr; \
MSVC_FSEG FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr GCC_FSEG = &VMField_##cls##_##scriptname;
#define DEFINE_GLOBAL(name) \
static const FieldDesc VMGlobal_##name = { "", #name, (size_t)&name, (unsigned)sizeof(name), 0 }; \
extern FieldDesc const *const VMGlobal_##name##_HookPtr; \
MSVC_FSEG FieldDesc const *const VMGlobal_##name##_HookPtr GCC_FSEG = &VMGlobal_##name;
#define DEFINE_GLOBAL_NAMED(iname, name) \
static const FieldDesc VMGlobal_##name = { "", #name, (size_t)&iname, (unsigned)sizeof(iname), 0 }; \
extern FieldDesc const *const VMGlobal_##name##_HookPtr; \
MSVC_FSEG FieldDesc const *const VMGlobal_##name##_HookPtr GCC_FSEG = &VMGlobal_##name;
#define DEFINE_GLOBAL_UNSIZED(name) \
static const FieldDesc VMGlobal_##name = { "", #name, (size_t)&name, ~0u, 0 }; \
extern FieldDesc const *const VMGlobal_##name##_HookPtr; \
MSVC_FSEG FieldDesc const *const VMGlobal_##name##_HookPtr GCC_FSEG = &VMGlobal_##name;
class AActor;
#define ACTION_RETURN_STATE(v) do { FState *state = v; if (numret > 0) { assert(ret != NULL); ret->SetPointer(state); return 1; } return 0; } while(0)
#define ACTION_RETURN_CONST_POINTER(v) do { const void *state = v; if (numret > 0) { assert(ret != NULL); ret->SetConstPointer(state); return 1; } return 0; } while(0)
#define ACTION_RETURN_POINTER(v) do { void *state = v; if (numret > 0) { assert(ret != NULL); ret->SetPointer(state); return 1; } return 0; } while(0)
#define ACTION_RETURN_OBJECT(v) do { auto state = v; if (numret > 0) { assert(ret != NULL); ret->SetObject(state); return 1; } return 0; } while(0)
#define ACTION_RETURN_FLOAT(v) do { double u = v; if (numret > 0) { assert(ret != nullptr); ret->SetFloat(u); return 1; } return 0; } while(0)
#define ACTION_RETURN_VEC2(v) do { DVector2 u = v; if (numret > 0) { assert(ret != nullptr); ret[0].SetVector2(u); return 1; } return 0; } while(0)
#define ACTION_RETURN_VEC3(v) do { DVector3 u = v; if (numret > 0) { assert(ret != nullptr); ret[0].SetVector(u); return 1; } return 0; } while(0)
#define ACTION_RETURN_VEC4(v) do { DVector4 u = v; if (numret > 0) { assert(ret != nullptr); ret[0].SetVector4(u); return 1; } return 0; } while(0)
#define ACTION_RETURN_QUAT(v) do { DQuaternion u = v; if (numret > 0) { assert(ret != nullptr); ret[0].SetQuaternion(u); return 1; } return 0; } while(0)
#define ACTION_RETURN_INT(v) do { int u = v; if (numret > 0) { assert(ret != NULL); ret->SetInt(u); return 1; } return 0; } while(0)
#define ACTION_RETURN_BOOL(v) ACTION_RETURN_INT(v)
#define ACTION_RETURN_STRING(v) do { FString u = v; if (numret > 0) { assert(ret != NULL); ret->SetString(u); return 1; } return 0; } while(0)
// Checks to see what called the current action function
#define ACTION_CALL_FROM_ACTOR() (stateinfo == nullptr || stateinfo->mStateType == STATE_Actor)
#define ACTION_CALL_FROM_PSPRITE() (self->player && stateinfo != nullptr && stateinfo->mStateType == STATE_Psprite)
#define ACTION_CALL_FROM_INVENTORY() (stateinfo != nullptr && stateinfo->mStateType == STATE_StateChain)
// Standard parameters for all action functions
// self - Actor this action is to operate on (player if a weapon)
// stateowner - Actor this action really belongs to (may be an item)
// callingstate - State this action was called from
#define PARAM_ACTION_PROLOGUE(type) \
PARAM_PROLOGUE; \
PARAM_OBJECT_NOT_NULL (self, AActor); \
PARAM_OBJECT (stateowner, type) \
PARAM_POINTER (stateinfo, FStateParamInfo) \
// Number of action paramaters
#define NAP 3
#define PARAM_SELF_PROLOGUE(type) \
PARAM_PROLOGUE; \
PARAM_OBJECT_NOT_NULL(self, type);
// for structs we cannot do a class validation
#define PARAM_SELF_STRUCT_PROLOGUE(type) \
PARAM_PROLOGUE; \
PARAM_POINTER_NOT_NULL(self, type);
class PFunction;
VMFunction *FindVMFunction(PClass *cls, const char *name);
#define DECLARE_VMFUNC(cls, name) static VMFunction *name; if (name == nullptr) name = FindVMFunction(RUNTIME_CLASS(cls), #name);
FString FStringFormat(VM_ARGS, int offset = 0);
#define IFVM(cls, funcname) \
static VMFunction * func = nullptr; \
if (func == nullptr) { \
PClass::FindFunction(&func, #cls, #funcname); \
assert(func); \
} \
if (func != nullptr)
unsigned GetVirtualIndex(PClass *cls, const char *funcname);
#define IFVIRTUALPTR(self, cls, funcname) \
static unsigned VIndex = ~0u; \
if (VIndex == ~0u) { \
VIndex = GetVirtualIndex(RUNTIME_CLASS(cls), #funcname); \
assert(VIndex != ~0u); \
} \
auto clss = self->GetClass(); \
VMFunction *func = clss->Virtuals.Size() > VIndex? clss->Virtuals[VIndex] : nullptr; \
if (func != nullptr)
#define IFVIRTUAL(cls, funcname) IFVIRTUALPTR(this, cls, funcname)
#define IFVIRTUALPTRNAME(self, cls, funcname) \
static unsigned VIndex = ~0u; \
if (VIndex == ~0u) { \
VIndex = GetVirtualIndex(PClass::FindClass(cls), #funcname); \
assert(VIndex != ~0u); \
} \
auto clss = self->GetClass(); \
VMFunction *func = clss->Virtuals.Size() > VIndex? clss->Virtuals[VIndex] : nullptr; \
if (func != nullptr)
#define IFOVERRIDENVIRTUALPTRNAME(self, clsname, funcname) \
static VMFunction *orig_func = nullptr; \
static unsigned VIndex = ~0u; \
if (VIndex == ~0u) { \
PClass *cls = PClass::FindClass(clsname); \
VIndex = GetVirtualIndex(cls, #funcname); \
orig_func = cls->Virtuals.Size() > VIndex? cls->Virtuals[VIndex] : nullptr; \
assert(VIndex != ~0u); \
} \
auto *clss = self->GetClass(); \
VMFunction *func = clss->Virtuals.Size() > VIndex? clss->Virtuals[VIndex] : nullptr; \
if (func && func != orig_func )
#endif