mirror of
https://github.com/ZDoom/Raze.git
synced 2024-11-15 17:01:28 +00:00
841 lines
32 KiB
C++
841 lines
32 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"
|
|
|
|
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 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;
|
|
FString PrintableName; // so that the VM can print meaningful info if something in this function goes wrong.
|
|
|
|
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> ¶ms, 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; };
|
|
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;
|
|
|
|
#define TP(n) typename P##n
|
|
#define VP(n) ValidateType<P##n>()
|
|
template<typename Ret> DirectNativeDesc(Ret(*func)()) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); }
|
|
template<typename Ret, TP(1)> DirectNativeDesc(Ret(*func)(P1)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); }
|
|
template<typename Ret, TP(1), TP(2)> DirectNativeDesc(Ret(*func)(P1,P2)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); }
|
|
template<typename Ret, TP(1), TP(2), TP(3)> DirectNativeDesc(Ret(*func)(P1,P2,P3)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); }
|
|
template<typename Ret, TP(1), TP(2), TP(3), TP(4)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); }
|
|
template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); }
|
|
template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); }
|
|
template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); }
|
|
template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); }
|
|
template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8), TP(9)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); }
|
|
template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8), TP(9), TP(10)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); VP(10); }
|
|
template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8), TP(9), TP(10), TP(11)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); VP(10); VP(11); }
|
|
template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8), TP(9), TP(10), TP(11), TP(12)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); VP(10); VP(11); VP(12); }
|
|
template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8), TP(9), TP(10), TP(11), TP(12), TP(13)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); VP(10); VP(11); VP(12); VP(13); }
|
|
template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8), TP(9), TP(10), TP(11), TP(12), TP(13), TP(14)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); VP(10); VP(11); VP(12); VP(13), VP(14); }
|
|
template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8), TP(9), TP(10), TP(11), TP(12), TP(13), TP(14), TP(15)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); VP(10); VP(11); VP(12); VP(13), VP(14), VP(15); }
|
|
#undef TP
|
|
#undef VP
|
|
|
|
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_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_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)
|
|
|
|
#endif
|