This commit is contained in:
Rachael Alexanderson 2017-03-04 07:28:02 -05:00
commit b8b5360de1
31 changed files with 844 additions and 179 deletions

View file

@ -1232,6 +1232,7 @@ set (PCH_SOURCES
scripting/thingdef_data.cpp
scripting/thingdef_properties.cpp
scripting/backend/codegen.cpp
scripting/backend/scopebarrier.cpp
scripting/backend/dynarrays.cpp
scripting/backend/vmbuilder.cpp
scripting/backend/vmdisasm.cpp

View file

@ -207,7 +207,9 @@ enum EObjectFlags
OF_Transient = 1 << 11, // Object should not be archived (references to it will be nulled on disk)
OF_Spawned = 1 << 12, // Thinker was spawned at all (some thinkers get deleted before spawning)
OF_Released = 1 << 13, // Object was released from the GC system and should not be processed by GC function
OF_Abstract = 1 << 14, // Marks a class that cannot be created with CreateNew
OF_Abstract = 1 << 14, // Marks a class that cannot be created with new() function at all
OF_UI = 1 << 15, // Marks a class that defaults to VARF_UI for it's fields/methods
OF_Play = 1 << 16, // Marks a class that defaults to VARF_Play for it's fields/methods
};
template<class T> class TObjPtr;

View file

@ -37,6 +37,10 @@ enum
VARF_Transient = (1<<17), // don't auto serialize field.
VARF_Meta = (1<<18), // static class data (by necessity read only.)
VARF_VarArg = (1<<19), // [ZZ] vararg: don't typecheck values after ... in function signature
VARF_UI = (1<<20), // [ZZ] ui: object is ui-scope only (can't modify playsim)
VARF_Play = (1<<21), // [ZZ] play: object is playsim-scope only (can't access ui)
VARF_VirtualScope = (1<<22), // [ZZ] virtualscope: object should use the scope of the particular class it's being used with (methods only)
VARF_ClearScope = (1<<23), // [ZZ] clearscope: this method ignores the member access chain that leads to it and is always plain data.
};
// An action function -------------------------------------------------------

View file

@ -117,6 +117,21 @@ bool E_UnregisterHandler(DStaticEventHandler* handler)
return true;
}
bool E_SendNetworkEvent(FString name, int arg1, int arg2, int arg3)
{
if (gamestate != GS_LEVEL)
return false;
Net_WriteByte(DEM_NETEVENT);
Net_WriteString(name);
Net_WriteByte(3);
Net_WriteLong(arg1);
Net_WriteLong(arg2);
Net_WriteLong(arg3);
return true;
}
bool E_CheckHandler(DStaticEventHandler* handler)
{
for (DStaticEventHandler* lhandler = E_FirstEventHandler; lhandler; lhandler = lhandler->next)
@ -521,6 +536,18 @@ DEFINE_ACTION_FUNCTION(DStaticEventHandler, SetOrder)
return 0;
}
DEFINE_ACTION_FUNCTION(DEventHandler, SendNetworkEvent)
{
PARAM_PROLOGUE;
PARAM_STRING(name);
PARAM_INT(arg1);
PARAM_INT(arg2);
PARAM_INT(arg3);
//
ACTION_RETURN_BOOL(E_SendNetworkEvent(name, arg1, arg2, arg3));
}
DEFINE_ACTION_FUNCTION(DEventHandler, Create)
{
PARAM_PROLOGUE;
@ -658,6 +685,7 @@ DEFINE_EMPTY_HANDLER(DStaticEventHandler, UiProcess);
DEFINE_EMPTY_HANDLER(DStaticEventHandler, InputProcess);
DEFINE_EMPTY_HANDLER(DStaticEventHandler, ConsoleProcess);
DEFINE_EMPTY_HANDLER(DStaticEventHandler, NetworkProcess);
// ===========================================
//
@ -1068,22 +1096,45 @@ static DConsoleEvent* E_SetupConsoleEvent()
void DStaticEventHandler::ConsoleProcess(int player, FString name, int arg1, int arg2, int arg3)
{
IFVIRTUAL(DStaticEventHandler, ConsoleProcess)
if (player < 0)
{
// don't create excessive DObjects if not going to be processed anyway
if (func == DStaticEventHandler_ConsoleProcess_VMPtr)
return;
DConsoleEvent* e = E_SetupConsoleEvent();
IFVIRTUAL(DStaticEventHandler, ConsoleProcess)
{
// don't create excessive DObjects if not going to be processed anyway
if (func == DStaticEventHandler_ConsoleProcess_VMPtr)
return;
DConsoleEvent* e = E_SetupConsoleEvent();
//
e->Player = player;
e->Name = name;
e->Args[0] = arg1;
e->Args[1] = arg2;
e->Args[2] = arg3;
//
e->Player = player;
e->Name = name;
e->Args[0] = arg1;
e->Args[1] = arg2;
e->Args[2] = arg3;
VMValue params[2] = { (DStaticEventHandler*)this, e };
GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr);
VMValue params[2] = { (DStaticEventHandler*)this, e };
GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr);
}
}
else
{
IFVIRTUAL(DStaticEventHandler, NetworkProcess)
{
// don't create excessive DObjects if not going to be processed anyway
if (func == DStaticEventHandler_NetworkProcess_VMPtr)
return;
DConsoleEvent* e = E_SetupConsoleEvent();
//
e->Player = player;
e->Name = name;
e->Args[0] = arg1;
e->Args[1] = arg2;
e->Args[2] = arg3;
VMValue params[2] = { (DStaticEventHandler*)this, e };
GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr);
}
}
}
@ -1136,10 +1187,6 @@ CCMD(netevent)
for (int i = 0; i < argn; i++)
arg[i] = atoi(argv[2 + i]);
// call networked
Net_WriteByte(DEM_NETEVENT);
Net_WriteString(argv[1]);
Net_WriteByte(argn);
for (int i = 0; i < 3; i++)
Net_WriteLong(arg[i]);
E_SendNetworkEvent(argv[1], arg[0], arg[1], arg[2]);
}
}

View file

@ -60,6 +60,9 @@ bool E_Responder(event_t* ev); // splits events into InputProcess and UiProcess
// this executes on console/net events.
void E_Console(int player, FString name, int arg1, int arg2, int arg3);
// send networked event. unified function.
bool E_SendNetworkEvent(FString name, int arg1, int arg2, int arg3);
// check if there is anything that should receive GUI events
bool E_CheckUiProcessors();
// check if we need native mouse due to UiProcessors

View file

@ -796,7 +796,16 @@ void M_ClearMenus()
void M_Init (void)
{
M_ParseMenuDefs();
try
{
M_ParseMenuDefs();
}
catch (CVMAbortException &err)
{
err.MaybePrintMessage();
Printf("%s", err.stacktrace.GetChars());
I_FatalError("Failed to initialize menus");
}
M_CreateMenus();
}

View file

@ -4765,24 +4765,24 @@ static int ScriptCall(unsigned argc, int32_t *args)
auto cls = PClass::FindClass(clsname);
if (!cls)
{
I_Error("ACS call to unknown class in script function %s.%s", cls, funcname);
I_Error("ACS call to unknown class in script function %s.%s", clsname, funcname);
}
auto funcsym = dyn_cast<PFunction>(cls->Symbols.FindSymbol(funcname, true));
if (funcsym == nullptr)
{
I_Error("ACS call to unknown script function %s.%s", cls, funcname);
I_Error("ACS call to unknown script function %s.%s", clsname, funcname);
}
auto func = funcsym->Variants[0].Implementation;
if (func->ImplicitArgs > 0)
{
I_Error("ACS call to non-static script function %s.%s", cls, funcname);
I_Error("ACS call to non-static script function %s.%s", clsname, funcname);
}
TArray<VMValue> params;
for (unsigned i = 2; i < argc; i++)
{
if (func->Proto->ArgumentTypes.Size() < i - 1)
{
I_Error("Too many parameters in call to %s.%s", cls, funcname);
I_Error("Too many parameters in call to %s.%s", clsname, funcname);
}
auto argtype = func->Proto->ArgumentTypes[i - 2];
// The only types allowed are int, bool, double, Name, Sound, Color and String
@ -4812,7 +4812,7 @@ static int ScriptCall(unsigned argc, int32_t *args)
}
else
{
I_Error("Invalid type %s in call to %s.%s", argtype->DescriptiveName(), cls, funcname);
I_Error("Invalid type %s in call to %s.%s", argtype->DescriptiveName(), clsname, funcname);
}
}
if (func->Proto->ArgumentTypes.Size() > params.Size())
@ -4820,7 +4820,7 @@ static int ScriptCall(unsigned argc, int32_t *args)
// Check if we got enough parameters. That means we either cover the full argument list of the function or the next argument is optional.
if (!(funcsym->Variants[0].ArgFlags[params.Size()] & VARF_Optional))
{
I_Error("Insufficient parameters in call to %s.%s", cls, funcname);
I_Error("Insufficient parameters in call to %s.%s", clsname, funcname);
}
}
// The return value can be the same types as the parameter types, plus void

View file

@ -170,6 +170,10 @@ std2:
'virtual' { RET(TK_Virtual); }
'override' { RET(TK_Override); }
'vararg' { RET(TK_VarArg); }
'ui' { RET(TK_UI); }
'play' { RET(TK_Play); }
'clearscope' { RET(TK_ClearScope); }
'virtualscope' { RET(TK_VirtualScope); }
'super' { RET(TK_Super); }
'global' { RET(TK_Global); }
'stop' { RET(TK_Stop); }

View file

@ -112,6 +112,10 @@ xx(TK_Optional, "'optional'")
xx(TK_Export, "'expert'")
xx(TK_Virtual, "'virtual'")
xx(TK_VarArg, "'vararg'")
xx(TK_UI, "'ui'")
xx(TK_Play, "'play'")
xx(TK_ClearScope, "'clearscope'")
xx(TK_VirtualScope, "'virtualscope'")
xx(TK_Override, "'override'")
xx(TK_Super, "'super'")
xx(TK_Null, "'null'")

View file

@ -89,6 +89,37 @@ static const FLOP FxFlops[] =
{ NAME_TanH, FLOP_TANH, [](double v) { return g_tanh(v); } },
};
//==========================================================================
//
// [ZZ] Magic methods to be used in vmexec.h for runtime checking of scope
//
//==========================================================================
// this can be imported in vmexec.h
void FScopeBarrier_ValidateNew(PClass* cls, PFunction* callingfunc)
{
int outerside = callingfunc->Variants.Size() ? FScopeBarrier::SideFromFlags(callingfunc->Variants[0].Flags) : FScopeBarrier::Side_Virtual;
if (outerside == FScopeBarrier::Side_Virtual)
outerside = FScopeBarrier::SideFromObjectFlags(callingfunc->OwningClass->ObjectFlags);
int innerside = FScopeBarrier::SideFromObjectFlags(cls->ObjectFlags);
if ((outerside != innerside) && (innerside != FScopeBarrier::Side_PlainData)) // "cannot construct ui class ... from data context"
ThrowAbortException(X_OTHER, "Cannot construct %s class %s from %s context", FScopeBarrier::StringFromSide(innerside), cls->TypeName.GetChars(), FScopeBarrier::StringFromSide(outerside));
}
// this can be imported in vmexec.h
void FScopeBarrier_ValidateCall(PFunction* calledfunc, PFunction* callingfunc, PClass* selftype)
{
// [ZZ] anonymous blocks have 0 variants, so give them Side_Virtual.
int outerside = callingfunc->Variants.Size() ? FScopeBarrier::SideFromFlags(callingfunc->Variants[0].Flags) : FScopeBarrier::Side_Virtual;
if (outerside == FScopeBarrier::Side_Virtual)
outerside = FScopeBarrier::SideFromObjectFlags(callingfunc->OwningClass->ObjectFlags);
int innerside = FScopeBarrier::SideFromFlags(calledfunc->Variants[0].Flags);
if (innerside == FScopeBarrier::Side_Virtual)
innerside = FScopeBarrier::SideFromObjectFlags(selftype->ObjectFlags);
if ((outerside != innerside) && (innerside != FScopeBarrier::Side_PlainData))
ThrowAbortException(X_OTHER, "Cannot call %s function %s from %s context", FScopeBarrier::StringFromSide(innerside), calledfunc->SymbolName.GetChars(), FScopeBarrier::StringFromSide(outerside));
}
//==========================================================================
//
// FCompileContext
@ -167,7 +198,8 @@ void FCompileContext::CheckReturn(PPrototype *proto, FScriptPosition &pos)
}
}
bool FCompileContext::CheckReadOnly(int flags)
// [ZZ] I find it really dumb that something called CheckReadOnly returns false for readonly. renamed.
bool FCompileContext::CheckWritable(int flags)
{
if (!(flags & VARF_ReadOnly)) return false;
if (!(flags & VARF_InternalAccess)) return true;
@ -5023,6 +5055,7 @@ FxNew::FxNew(FxExpression *v)
{
val = new FxClassTypeCast(NewClassPointer(RUNTIME_CLASS(DObject)), v, false);
ValueType = NewPointer(RUNTIME_CLASS(DObject));
CallingFunction = nullptr;
}
//==========================================================================
@ -5047,6 +5080,7 @@ FxExpression *FxNew::Resolve(FCompileContext &ctx)
CHECKRESOLVED();
SAFE_RESOLVE(val, ctx);
CallingFunction = ctx.Function;
if (!val->ValueType->IsKindOf(RUNTIME_CLASS(PClassPointer)))
{
ScriptPosition.Message(MSG_ERROR, "Class type expected");
@ -5056,8 +5090,28 @@ FxExpression *FxNew::Resolve(FCompileContext &ctx)
if (val->isConstant())
{
auto cls = static_cast<PClass *>(static_cast<FxConstant*>(val)->GetValue().GetPointer());
if (cls->ObjectFlags & OF_Abstract)
{
ScriptPosition.Message(MSG_ERROR, "Cannot instantiate abstract class %s", cls->TypeName.GetChars());
delete this;
return nullptr;
}
//
int outerside = ctx.Function && ctx.Function->Variants.Size() ? FScopeBarrier::SideFromFlags(ctx.Function->Variants[0].Flags) : FScopeBarrier::Side_Virtual;
if (outerside == FScopeBarrier::Side_Virtual)
outerside = FScopeBarrier::SideFromObjectFlags(ctx.Class->ObjectFlags);
int innerside = FScopeBarrier::SideFromObjectFlags(cls->ObjectFlags);
if ((outerside != innerside) && (innerside != FScopeBarrier::Side_PlainData)) // "cannot construct ui class ... from data context"
{
ScriptPosition.Message(MSG_ERROR, "Cannot construct %s class %s from %s context", FScopeBarrier::StringFromSide(innerside), cls->TypeName.GetChars(), FScopeBarrier::StringFromSide(outerside));
delete this;
return nullptr;
}
ValueType = NewPointer(cls);
}
return this;
}
@ -5072,7 +5126,7 @@ ExpEmit FxNew::Emit(VMFunctionBuilder *build)
ExpEmit from = val->Emit(build);
from.Free(build);
ExpEmit to(build, REGT_POINTER);
build->Emit(from.Konst ? OP_NEW_K : OP_NEW, to.RegNum, from.RegNum);
build->Emit(from.Konst ? OP_NEW_K : OP_NEW, to.RegNum, from.RegNum, build->GetConstantAddress(CallingFunction, ATAG_OBJECT));
return to;
}
@ -6245,7 +6299,7 @@ FxExpression *FxLocalVariable::Resolve(FCompileContext &ctx)
bool FxLocalVariable::RequestAddress(FCompileContext &ctx, bool *writable)
{
AddressRequested = true;
if (writable != nullptr) *writable = !ctx.CheckReadOnly(Variable->VarFlags);
if (writable != nullptr) *writable = !ctx.CheckWritable(Variable->VarFlags);
return true;
}
@ -6463,7 +6517,7 @@ FxGlobalVariable::FxGlobalVariable(PField* mem, const FScriptPosition &pos)
bool FxGlobalVariable::RequestAddress(FCompileContext &ctx, bool *writable)
{
AddressRequested = true;
if (writable != nullptr) *writable = AddressWritable && !ctx.CheckReadOnly(membervar->Flags);
if (writable != nullptr) *writable = AddressWritable && !ctx.CheckWritable(membervar->Flags);
return true;
}
@ -6653,7 +6707,7 @@ FxStackVariable::~FxStackVariable()
bool FxStackVariable::RequestAddress(FCompileContext &ctx, bool *writable)
{
AddressRequested = true;
if (writable != nullptr) *writable = AddressWritable && !ctx.CheckReadOnly(membervar->Flags);
if (writable != nullptr) *writable = AddressWritable && !ctx.CheckWritable(membervar->Flags);
return true;
}
@ -6751,8 +6805,34 @@ bool FxStructMember::RequestAddress(FCompileContext &ctx, bool *writable)
return false;
}
AddressRequested = true;
if (writable != nullptr) *writable = (AddressWritable && !ctx.CheckReadOnly(membervar->Flags) &&
(!classx->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)) || !static_cast<PPointer*>(classx->ValueType)->IsConst));
if (writable != nullptr)
{
// [ZZ] original check.
bool bWritable = (AddressWritable && !ctx.CheckWritable(membervar->Flags) &&
(!classx->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)) || !static_cast<PPointer*>(classx->ValueType)->IsConst));
// [ZZ] self in a const function is not writable.
if (bWritable) // don't do complex checks on early fail
{
if ((classx->ExprType == EFX_Self) && (ctx.Function && (ctx.Function->Variants[0].Flags & VARF_ReadOnly)))
bWritable = false;
}
// [ZZ] implement write barrier between different scopes
if (bWritable)
{
int outerflags = 0;
if (ctx.Function)
{
outerflags = ctx.Function->Variants[0].Flags;
if (((outerflags & (VARF_VirtualScope | VARF_Virtual)) == (VARF_VirtualScope | VARF_Virtual)) && ctx.Class)
outerflags = FScopeBarrier::FlagsFromSide(FScopeBarrier::SideFromObjectFlags(ctx.Class->ObjectFlags));
}
FScopeBarrier scopeBarrier(outerflags, FScopeBarrier::FlagsFromSide(BarrierSide), membervar->SymbolName.GetChars());
if (!scopeBarrier.writable)
bWritable = false;
}
*writable = bWritable;
}
return true;
}
@ -6772,7 +6852,7 @@ FxExpression *FxStructMember::Resolve(FCompileContext &ctx)
if (!classx->ValueType->IsKindOf(RUNTIME_CLASS(PPointer))
|| !static_cast<PPointer *>(classx->ValueType)->PointedType->IsKindOf(RUNTIME_CLASS(AActor)))
{
ScriptPosition.Message(MSG_ERROR, "'Default' requires an actor type.");
ScriptPosition.Message(MSG_ERROR, "'Default' requires an actor type");
delete this;
return nullptr;
}
@ -6782,12 +6862,36 @@ FxExpression *FxStructMember::Resolve(FCompileContext &ctx)
return x->Resolve(ctx);
}
// [ZZ] support magic
int outerflags = 0;
if (ctx.Function)
{
outerflags = ctx.Function->Variants[0].Flags;
if (((outerflags & (VARF_VirtualScope | VARF_Virtual)) == (VARF_VirtualScope | VARF_Virtual)) && ctx.Class)
outerflags = FScopeBarrier::FlagsFromSide(FScopeBarrier::SideFromObjectFlags(ctx.Class->ObjectFlags));
}
FScopeBarrier scopeBarrier(outerflags, membervar->Flags, membervar->SymbolName.GetChars());
if (!scopeBarrier.readable)
{
ScriptPosition.Message(MSG_ERROR, "%s", scopeBarrier.readerror.GetChars());
delete this;
return nullptr;
}
BarrierSide = scopeBarrier.sidelast;
if (classx->ExprType == EFX_StructMember && ExprType == EFX_StructMember) // note: only do this for structs now
{
FxStructMember* pmember = (FxStructMember*)classx;
if (BarrierSide == FScopeBarrier::Side_PlainData && pmember)
BarrierSide = pmember->BarrierSide;
}
if (classx->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)))
{
PPointer *ptrtype = dyn_cast<PPointer>(classx->ValueType);
if (ptrtype == nullptr || !ptrtype->PointedType->IsKindOf(RUNTIME_CLASS(PStruct)))
{
ScriptPosition.Message(MSG_ERROR, "Member variable requires a struct or class object.");
ScriptPosition.Message(MSG_ERROR, "Member variable requires a struct or class object");
delete this;
return nullptr;
}
@ -6799,7 +6903,8 @@ FxExpression *FxStructMember::Resolve(FCompileContext &ctx)
{
auto parentfield = static_cast<FxMemberBase *>(classx)->membervar;
// PFields are garbage collected so this will be automatically taken care of later.
auto newfield = new PField(NAME_None, membervar->Type, membervar->Flags | parentfield->Flags, membervar->Offset + parentfield->Offset);
// [ZZ] call ChangeSideInFlags to ensure that we don't get ui+play
auto newfield = new PField(NAME_None, membervar->Type, FScopeBarrier::ChangeSideInFlags(membervar->Flags | parentfield->Flags, BarrierSide), membervar->Offset + parentfield->Offset);
newfield->BitValue = membervar->BitValue;
static_cast<FxMemberBase *>(classx)->membervar = newfield;
classx->isresolved = false; // re-resolve the parent so it can also check if it can be optimized away.
@ -6841,7 +6946,7 @@ FxExpression *FxStructMember::Resolve(FCompileContext &ctx)
{
if (!(classx->RequestAddress(ctx, &AddressWritable)))
{
ScriptPosition.Message(MSG_ERROR, "unable to dereference left side of %s", membervar->SymbolName.GetChars());
ScriptPosition.Message(MSG_ERROR, "Unable to dereference left side of %s", membervar->SymbolName.GetChars());
delete this;
return nullptr;
}
@ -7371,6 +7476,32 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
return nullptr;
}
// [ZZ] validate call
PClass* cls = (PClass*)ctx.Class;
int outerflags = 0;
if (ctx.Function)
{
outerflags = ctx.Function->Variants[0].Flags;
if (((outerflags & (VARF_VirtualScope | VARF_Virtual)) == (VARF_VirtualScope | VARF_Virtual)) && ctx.Class)
outerflags = FScopeBarrier::FlagsFromSide(FScopeBarrier::SideFromObjectFlags(ctx.Class->ObjectFlags));
}
int innerflags = afd->Variants[0].Flags;
int innerside = FScopeBarrier::SideFromFlags(innerflags);
// [ZZ] check this at compile time. this would work for most legit cases.
if (innerside == FScopeBarrier::Side_Virtual)
{
innerside = FScopeBarrier::SideFromObjectFlags(cls->ObjectFlags);
innerflags = FScopeBarrier::FlagsFromSide(innerside);
}
FScopeBarrier scopeBarrier(outerflags, innerflags, MethodName.GetChars());
if (!scopeBarrier.callable)
{
ScriptPosition.Message(MSG_ERROR, "%s", scopeBarrier.callerror.GetChars());
delete this;
return nullptr;
}
// [ZZ] this is only checked for VARF_Methods in the other place. bug?
if (!CheckFunctionCompatiblity(ScriptPosition, ctx.Function, afd))
{
delete this;
@ -7593,8 +7724,18 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
break;
case NAME_New:
if (CheckArgSize(MethodName, ArgList, 1, 1, ScriptPosition))
if (CheckArgSize(MethodName, ArgList, 0, 1, ScriptPosition))
{
// [ZZ] allow implicit new() call to mean "create current class instance"
if (!ArgList.Size() && !ctx.Class->IsKindOf(RUNTIME_CLASS(PClass)))
{
ScriptPosition.Message(MSG_ERROR, "Cannot use implicit new() in a struct");
delete this;
return nullptr;
}
else if (!ArgList.Size())
ArgList.Push(new FxConstant((PClass*)ctx.Class, NewClassPointer((PClass*)ctx.Class), ScriptPosition));
func = new FxNew(ArgList[0]);
ArgList[0] = nullptr;
}
@ -7651,13 +7792,14 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
PStruct *cls;
bool staticonly = false;
bool novirtual = false;
bool isreadonly = false;
PStruct *ccls = nullptr;
if (ctx.Class == nullptr)
{
// There's no way that a member function call can resolve to a constant so abort right away.
ScriptPosition.Message(MSG_ERROR, "Expression is not constant.");
ScriptPosition.Message(MSG_ERROR, "Expression is not constant");
delete this;
return nullptr;
}
@ -7666,7 +7808,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
{
if (a == nullptr)
{
ScriptPosition.Message(MSG_ERROR, "Empty function argument.");
ScriptPosition.Message(MSG_ERROR, "Empty function argument");
delete this;
return nullptr;
}
@ -7677,6 +7819,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
auto id = static_cast<FxIdentifier *>(Self)->Identifier;
// If the left side is a class name for a static member function call it needs to be resolved manually
// because the resulting value type would cause problems in nearly every other place where identifiers are being used.
// [ZZ] substitute ccls for String internal type.
if (id == NAME_String) ccls = TypeStringStruct;
else ccls = FindStructType(id, ctx);
if (ccls != nullptr) static_cast<FxIdentifier *>(Self)->noglobal = true;
@ -7688,7 +7831,6 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
{
if (ccls != nullptr)
{
// [ZZ] substitute ccls for String internal type.
if (!ccls->IsKindOf(RUNTIME_CLASS(PClass)) || static_cast<PClass *>(ccls)->bExported)
{
cls = ccls;
@ -7717,6 +7859,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
Self = new FxSelf(ScriptPosition);
Self->ValueType = NewPointer(cls);
}
else novirtual = false;
}
}
}
@ -7757,7 +7900,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
{
if (ArgList.Size() > 0)
{
ScriptPosition.Message(MSG_ERROR, "too many parameters in call to %s", MethodName.GetChars());
ScriptPosition.Message(MSG_ERROR, "Too many parameters in call to %s", MethodName.GetChars());
delete this;
return nullptr;
}
@ -7801,7 +7944,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
{
if (ArgList.Size() > 0)
{
ScriptPosition.Message(MSG_ERROR, "too many parameters in call to %s", MethodName.GetChars());
ScriptPosition.Message(MSG_ERROR, "Too many parameters in call to %s", MethodName.GetChars());
delete this;
return nullptr;
}
@ -7895,7 +8038,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
{
if (ArgList.Size() > 0)
{
ScriptPosition.Message(MSG_ERROR, "too many parameters in call to %s", MethodName.GetChars());
ScriptPosition.Message(MSG_ERROR, "Too many parameters in call to %s", MethodName.GetChars());
delete this;
return nullptr;
}
@ -7945,7 +8088,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
auto x = new FxGetParentClass(Self);
return x->Resolve(ctx);
}
if (Self->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)) && !Self->ValueType->IsKindOf(RUNTIME_CLASS(PClassPointer)))
{
auto ptype = static_cast<PPointer *>(Self->ValueType)->PointedType;
@ -7955,7 +8098,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
{
if (ArgList.Size() > 0)
{
ScriptPosition.Message(MSG_ERROR, "too many parameters in call to %s", MethodName.GetChars());
ScriptPosition.Message(MSG_ERROR, "Too many parameters in call to %s", MethodName.GetChars());
delete this;
return nullptr;
}
@ -7968,7 +8111,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
}
else
{
ScriptPosition.Message(MSG_ERROR, "Left hand side of %s must point to a class object\n", MethodName.GetChars());
ScriptPosition.Message(MSG_ERROR, "Left hand side of %s must point to a class object", MethodName.GetChars());
delete this;
return nullptr;
}
@ -7976,22 +8119,15 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
else if (Self->ValueType->IsKindOf(RUNTIME_CLASS(PStruct)))
{
bool writable;
if (Self->RequestAddress(ctx, &writable) && writable)
{
cls = static_cast<PStruct*>(Self->ValueType);
Self->ValueType = NewPointer(Self->ValueType);
}
else
{
// Cannot be made writable so we cannot use its methods.
ScriptPosition.Message(MSG_ERROR, "Invalid expression on left hand side of %s\n", MethodName.GetChars());
delete this;
return nullptr;
}
// [ZZ] allow const method to be called on a readonly struct
isreadonly = !(Self->RequestAddress(ctx, &writable) && writable);
cls = static_cast<PStruct*>(Self->ValueType);
Self->ValueType = NewPointer(Self->ValueType);
}
else
{
ScriptPosition.Message(MSG_ERROR, "Invalid expression on left hand side of %s\n", MethodName.GetChars());
ScriptPosition.Message(MSG_ERROR, "Invalid expression on left hand side of %s", MethodName.GetChars());
delete this;
return nullptr;
}
@ -8009,7 +8145,50 @@ isresolved:
if (afd == nullptr)
{
ScriptPosition.Message(MSG_ERROR, "Unknown function %s\n", MethodName.GetChars());
ScriptPosition.Message(MSG_ERROR, "Unknown function %s", MethodName.GetChars());
delete this;
return nullptr;
}
if (isreadonly && !(afd->Variants[0].Flags & VARF_ReadOnly))
{
// Cannot be made writable so we cannot use its methods.
// [ZZ] Why this esoteric message?
ScriptPosition.Message(MSG_ERROR, "Readonly struct on left hand side of %s not allowed", MethodName.GetChars());
delete this;
return nullptr;
}
// [ZZ] if self is a struct or a class member, check if it's valid to call this function at all.
// implement more magic
int outerflags = 0;
if (ctx.Function)
{
outerflags = ctx.Function->Variants[0].Flags;
if (((outerflags & (VARF_VirtualScope | VARF_Virtual)) == (VARF_VirtualScope | VARF_Virtual)) && ctx.Class)
outerflags = FScopeBarrier::FlagsFromSide(FScopeBarrier::SideFromObjectFlags(ctx.Class->ObjectFlags));
}
int innerflags = afd->Variants[0].Flags;
int innerside = FScopeBarrier::SideFromFlags(innerflags);
// [ZZ] check this at compile time. this would work for most legit cases.
if (innerside == FScopeBarrier::Side_Virtual)
{
innerside = FScopeBarrier::SideFromObjectFlags(cls->ObjectFlags);
innerflags = FScopeBarrier::FlagsFromSide(innerside);
}
else if (innerside != FScopeBarrier::Side_Clear)
{
if (Self->ExprType == EFX_StructMember)
{
FxStructMember* pmember = (FxStructMember*)Self;
if (innerside == FScopeBarrier::Side_PlainData)
innerflags = FScopeBarrier::ChangeSideInFlags(innerflags, pmember->BarrierSide);
}
}
FScopeBarrier scopeBarrier(outerflags, innerflags, MethodName.GetChars());
if (!scopeBarrier.callable)
{
ScriptPosition.Message(MSG_ERROR, "%s", scopeBarrier.callerror.GetChars());
delete this;
return nullptr;
}
@ -8022,14 +8201,14 @@ isresolved:
auto ccls = dyn_cast<PClass>(cls);
if (clstype == nullptr || ccls == nullptr || !clstype->IsDescendantOf(ccls))
{
ScriptPosition.Message(MSG_ERROR, "Cannot call non-static function %s::%s from here\n", cls->TypeName.GetChars(), MethodName.GetChars());
ScriptPosition.Message(MSG_ERROR, "Cannot call non-static function %s::%s from here", cls->TypeName.GetChars(), MethodName.GetChars());
delete this;
return nullptr;
}
else
{
// Todo: If this is a qualified call to a parent class function, let it through (but this needs to disable virtual calls later.)
ScriptPosition.Message(MSG_ERROR, "Qualified member call to parent class %s::%s is not yet implemented\n", cls->TypeName.GetChars(), MethodName.GetChars());
ScriptPosition.Message(MSG_ERROR, "Qualified member call to parent class %s::%s is not yet implemented", cls->TypeName.GetChars(), MethodName.GetChars());
delete this;
return nullptr;
}
@ -8057,7 +8236,7 @@ isresolved:
// Functions with no Actor usage may not be called through a pointer because they will lose their context.
if (!(afd->Variants[0].UseFlags & SUF_ACTOR))
{
ScriptPosition.Message(MSG_ERROR, "Function %s cannot be used with a non-self object\n", afd->SymbolName.GetChars());
ScriptPosition.Message(MSG_ERROR, "Function %s cannot be used with a non-self object", afd->SymbolName.GetChars());
delete this;
return nullptr;
}
@ -8264,6 +8443,7 @@ FxVMFunctionCall::FxVMFunctionCall(FxExpression *self, PFunction *func, FArgumen
ArgList = std::move(args);
EmitTail = false;
NoVirtual = novirtual;
CallingFunction = nullptr;
}
//==========================================================================
@ -8337,6 +8517,7 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx)
return nullptr;
}
CallingFunction = ctx.Function;
if (ArgList.Size() > 0)
{
bool foundvarargs = false;
@ -8544,6 +8725,17 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build)
ExpEmit selfemit;
if (Function->Variants[0].Flags & VARF_Method)
{
#if 0
// [ZZ]
if (Function->Variants[0].Implementation && Function->Variants[0].Implementation->BarrierSide == FScopeBarrier::Side_Virtual)
{
// pass this even before Self, because otherwise we can't silently advance the arguments.
// this is not even implicit arguments.
build->Emit(OP_PARAM, 0, REGT_POINTER | REGT_KONST, build->GetConstantAddress(Function, ATAG_OBJECT));
build->Emit(OP_PARAM, 0, REGT_POINTER | REGT_KONST, build->GetConstantAddress(CallingFunction, ATAG_OBJECT));
count += 2;
}
#endif
assert(Self != nullptr);
selfemit = Self->Emit(build);
assert((selfemit.RegType == REGT_POINTER) || (selfemit.Fixed && selfemit.Target));

View file

@ -45,6 +45,7 @@
#include "s_sound.h"
#include "actor.h"
#include "vmbuilder.h"
#include "scopebarrier.h"
#define CHECKRESOLVED() if (isresolved) return this; isresolved=true;
@ -95,7 +96,7 @@ struct FCompileContext
void HandleJumps(int token, FxExpression *handler);
void CheckReturn(PPrototype *proto, FScriptPosition &pos);
bool CheckReadOnly(int flags);
bool CheckWritable(int flags);
FxLocalVariableDeclaration *FindLocalVariable(FName name);
};
@ -1208,6 +1209,7 @@ private:
class FxNew : public FxExpression
{
FxExpression *val;
PFunction *CallingFunction;
public:
@ -1326,6 +1328,7 @@ public:
PField *membervar;
bool AddressRequested = false;
bool AddressWritable = true;
int BarrierSide = -1; // [ZZ] some magic
FxMemberBase(EFxType type, PField *f, const FScriptPosition &p);
};
@ -1707,6 +1710,7 @@ class FxVMFunctionCall : public FxExpression
// for multi assignment
int AssignCount = 0;
TArray<ExpEmit> ReturnRegs;
PFunction *CallingFunction;
public:
FxVMFunctionCall(FxExpression *self, PFunction *func, FArgumentList &args, const FScriptPosition &pos, bool novirtual);

View file

@ -0,0 +1,152 @@
#include "scopebarrier.h"
#include "dobject.h"
// Note: the same object can't be both UI and Play. This is checked explicitly in the field construction and will cause esoteric errors here if found.
int FScopeBarrier::SideFromFlags(int flags)
{
if (flags & VARF_UI)
return Side_UI;
if (flags & VARF_Play)
return Side_Play;
if (flags & VARF_VirtualScope)
return Side_Virtual;
if (flags & VARF_ClearScope)
return Side_Clear;
return Side_PlainData;
}
// same as above, but from object flags
int FScopeBarrier::SideFromObjectFlags(int flags)
{
if (flags & OF_UI)
return Side_UI;
if (flags & OF_Play)
return Side_Play;
return Side_PlainData;
}
//
int FScopeBarrier::FlagsFromSide(int side)
{
switch (side)
{
case Side_Play:
return VARF_Play;
case Side_UI:
return VARF_UI;
case Side_Virtual:
return VARF_VirtualScope;
case Side_Clear:
return VARF_ClearScope;
default:
return 0;
}
}
// used for errors
const char* FScopeBarrier::StringFromSide(int side)
{
switch (side)
{
case Side_PlainData:
return "data";
case Side_UI:
return "ui";
case Side_Play:
return "play";
case Side_Virtual:
return "virtualscope"; // should not happen!
case Side_Clear:
return "clearscope"; // should not happen!
default:
return "unknown";
}
}
// this modifies VARF_ flags and sets the side properly.
int FScopeBarrier::ChangeSideInFlags(int flags, int side)
{
flags &= ~(VARF_UI | VARF_Play | VARF_VirtualScope | VARF_ClearScope);
flags |= FlagsFromSide(side);
return flags;
}
FScopeBarrier::FScopeBarrier()
{
sidefrom = -1;
sidelast = -1;
callable = true;
readable = true;
writable = true;
}
FScopeBarrier::FScopeBarrier(int flags1, int flags2, const char* name)
{
sidefrom = -1;
sidelast = -1;
callable = true;
readable = true;
writable = true;
AddFlags(flags1, flags2, name);
}
// AddFlags modifies ALLOWED actions by flags1->flags2.
// This is used for comparing a.b.c.d access - if non-allowed field is seen anywhere in the chain, anything after it is non-allowed.
// This struct is used so that the logic is in a single place.
void FScopeBarrier::AddFlags(int flags1, int flags2, const char* name)
{
// note: if it's already non-readable, don't even try advancing
if (!readable)
return;
// we aren't interested in any other flags
// - update: including VARF_VirtualScope. inside the function itself, we treat it as if it's PlainData.
flags1 &= VARF_UI | VARF_Play;
flags2 &= VARF_UI | VARF_Play | VARF_ReadOnly;
if (sidefrom < 0) sidefrom = SideFromFlags(flags1);
if (sidelast < 0) sidelast = sidefrom;
// flags1 = what's trying to access
// flags2 = what's being accessed
int sideto = SideFromFlags(flags2);
// plain data inherits whatever scope modifiers that context or field container has.
// i.e. play String bla; is play, and all non-specified methods/fields inside it are play as well.
if (sideto != Side_PlainData)
sidelast = sideto;
else sideto = sidelast;
if ((sideto == Side_UI) && (sidefrom != Side_UI)) // only ui -> ui is readable
{
readable = false;
if (name) readerror.Format("Can't read %s field %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom));
}
if (!readable)
{
writable = false;
callable = false;
if (name)
{
writeerror.Format("Can't write %s field %s from %s context (not readable)", StringFromSide(sideto), name, StringFromSide(sidefrom));
callerror.Format("Can't call %s function %s from %s context (not readable)", StringFromSide(sideto), name, StringFromSide(sidefrom));
}
return;
}
if (writable && (sidefrom != sideto)) // only matching types are writable (plain data implicitly takes context type by default, unless overridden)
{
writable = false;
if (name) writeerror.Format("Can't write %s field %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom));
}
if (callable && (sidefrom != sideto) && !(flags2 & VARF_ReadOnly)) // readonly on methods is used for plain data stuff that can be called from ui/play context.
{
callable = false;
if (name) callerror.Format("Can't call %s function %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom));
}
}

View file

@ -0,0 +1,52 @@
#pragma once
#include "zstring.h"
//
// [ZZ] this really should be in codegen.h, but vmexec needs to access it
struct FScopeBarrier
{
bool callable;
bool readable;
bool writable;
// this is the error message
FString callerror;
FString readerror;
FString writeerror;
// this is used to make the error message.
enum Side
{
Side_PlainData = 0,
Side_UI = 1,
Side_Play = 2,
Side_Virtual = 3, // do NOT change the value
Side_Clear = 4
};
int sidefrom;
int sidelast;
// Note: the same object can't be both UI and Play. This is checked explicitly in the field construction and will cause esoteric errors here if found.
static int SideFromFlags(int flags);
// same as above, but from object flags
static int SideFromObjectFlags(int flags);
//
static int FlagsFromSide(int side);
// used for errors
static const char* StringFromSide(int side);
// this modifies VARF_ flags and sets the side properly.
static int ChangeSideInFlags(int flags, int side);
FScopeBarrier();
FScopeBarrier(int flags1, int flags2, const char* name);
// AddFlags modifies ALLOWED actions by flags1->flags2.
// This is used for comparing a.b.c.d access - if non-allowed field is seen anywhere in the chain, anything after it is non-allowed.
// This struct is used so that the logic is in a single place.
void AddFlags(int flags1, int flags2, const char* name);
};

View file

@ -110,12 +110,13 @@ void SetImplicitArgs(TArray<PType *> *args, TArray<DWORD> *argflags, TArray<FNam
if (funcflags & VARF_Method)
{
// implied self pointer
if (args != nullptr) args->Push(NewPointer(cls));
if (args != nullptr) args->Push(NewPointer(cls, !!(funcflags & VARF_ReadOnly)));
if (argflags != nullptr) argflags->Push(VARF_Implicit | VARF_ReadOnly);
if (argnames != nullptr) argnames->Push(NAME_self);
}
if (funcflags & VARF_Action)
{
assert(!(funcflags & VARF_ReadOnly));
// implied caller and callingstate pointers
if (args != nullptr)
{
@ -163,6 +164,10 @@ PFunction *CreateAnonymousFunction(PClass *containingclass, PType *returntype, i
// Functions that only get flagged for actors do not need the additional two context parameters.
int fflags = (flags& (SUF_OVERLAY | SUF_WEAPON | SUF_ITEM)) ? VARF_Action | VARF_Method : VARF_Method;
// [ZZ] give anonymous functions the scope of their class
// (just give them VARF_Play, whatever)
fflags |= VARF_Play;
rets[0] = returntype != nullptr? returntype : TypeError; // Use TypeError as placeholder if we do not know the return type yet.
SetImplicitArgs(&args, &argflags, &argnames, containingclass, fflags, flags);
@ -194,7 +199,7 @@ PFunction *FindClassMemberFunction(PStruct *selfcls, PStruct *funccls, FName nam
{
sc.Message(MSG_ERROR, "%s is not a member function of %s", name.GetChars(), selfcls->TypeName.GetChars());
}
else if (funcsym->Variants[0].Flags & VARF_Private && symtable != &funccls->Symbols)
else if ((funcsym->Variants[0].Flags & VARF_Private) && symtable != &funccls->Symbols)
{
// private access is only allowed if the symbol table belongs to the class in which the current function is being defined.
sc.Message(MSG_ERROR, "%s is declared private and not accessible", symbol->SymbolName.GetChars());

View file

@ -858,8 +858,8 @@ void InitThingdef()
// As a result, the size has to be set to something large and arbritrary because it can change between maps. This will need some serious improvement when things get cleaned up.
sectorstruct->AddNativeField("lines", NewPointer(NewResizableArray(NewPointer(linestruct, false)), false), myoffsetof(sector_t, Lines), VARF_Native);
sectorstruct->AddNativeField("ceilingplane", secplanestruct, myoffsetof(sector_t, ceilingplane), VARF_Native);
sectorstruct->AddNativeField("floorplane", secplanestruct, myoffsetof(sector_t, floorplane), VARF_Native);
sectorstruct->AddNativeField("ceilingplane", secplanestruct, myoffsetof(sector_t, ceilingplane), VARF_Native | VARF_ReadOnly);
sectorstruct->AddNativeField("floorplane", secplanestruct, myoffsetof(sector_t, floorplane), VARF_Native | VARF_ReadOnly);

View file

@ -8,6 +8,10 @@
#include "doomerrors.h"
#include "memarena.h"
// [ZZ] there are serious circular references between this and the rest of ZScript code, so it needs to be done like this
// these are used in vmexec.h
void FScopeBarrier_ValidateNew(PClass* cls, PFunction* callingfunc);
void FScopeBarrier_ValidateCall(PFunction* calledfunc, PFunction* callingfunc, PClass* selftype);
class DObject;
extern FMemArena ClassDataAllocator;
@ -704,6 +708,8 @@ public:
bool Native;
bool Final = false; // cannot be overridden
bool Unsafe = false; // Contains references to class fields that are unsafe for psp and item state calls.
bool FuncConst = false; // [ZZ] readonly function
int BarrierSide = 0; // [ZZ] FScopeBarrier::Side
BYTE ImplicitArgs = 0; // either 0 for static, 1 for method or 3 for action
unsigned VirtualIndex = ~0u;
FName Name;
@ -712,7 +718,7 @@ public:
class PPrototype *Proto;
VMFunction(FName name = NAME_None) : Native(false), ImplicitArgs(0), Name(name), Proto(NULL)
VMFunction(FName name = NAME_None) : Native(false), ImplicitArgs(0), Name(name), Proto(NULL)
{
AllFunctions.Push(this);
}

View file

@ -664,13 +664,27 @@ begin:
VMReturn returns[MAX_RETURNS];
int numret;
b = B;
#if 0
// [ZZ] hax!
if (call->BarrierSide == 3) // :( - this is Side_Virtual. Side_Virtual should receive special arguments.
{
PFunction* calledfunc = (PFunction*)(reg.param + f->NumParam - b)[0].a;
PFunction* callingfunc = (PFunction*)(reg.param + f->NumParam - b)[1].a;
DObject* dobj = (DObject*)(reg.param + f->NumParam - b)[2].a; // this is the self pointer. it should be in, since Side_Virtual functions are always non-static methods.
PClass* selftype = dobj->GetClass();
FScopeBarrier_ValidateCall(calledfunc, callingfunc, selftype);
b -= 2;
}
#endif
FillReturns(reg, f, returns, pc+1, C);
if (call->Native)
{
try
{
VMCycles[0].Unclock();
numret = static_cast<VMNativeFunction *>(call)->NativeCall(reg.param + f->NumParam - B, call->DefaultArgs, B, returns, C);
numret = static_cast<VMNativeFunction *>(call)->NativeCall(reg.param + f->NumParam - b, call->DefaultArgs, b, returns, C);
VMCycles[0].Clock();
}
catch (CVMAbortException &err)
@ -686,7 +700,7 @@ begin:
VMCalls[0]++;
VMScriptFunction *script = static_cast<VMScriptFunction *>(call);
VMFrame *newf = stack->AllocFrame(script);
VMFillParams(reg.param + f->NumParam - B, newf, B);
VMFillParams(reg.param + f->NumParam - b, newf, b);
try
{
numret = Exec(stack, script->Code, returns, C);
@ -803,7 +817,11 @@ begin:
{
b = B;
PClass *cls = (PClass*)(pc->op == OP_NEW ? reg.a[b] : konsta[b].v);
PFunction *callingfunc = (PFunction*)konsta[C].o; // [ZZ] due to how this is set, it's always const
if (cls->ObjectFlags & OF_Abstract) ThrowAbortException(X_OTHER, "Cannot instantiate abstract class %s", cls->TypeName.GetChars());
// [ZZ] validate readonly and between scope construction
if (callingfunc)
FScopeBarrier_ValidateNew(cls, callingfunc);
reg.a[a] = cls->CreateNew();
reg.atag[a] = ATAG_OBJECT;
NEXTOP;

View file

@ -213,6 +213,8 @@ class_ancestry(X) ::= COLON dottable_id(A). { X = A; /*X-overwrites-A*/ }
class_flags(X) ::= . { X.Flags = 0; X.Replaces = NULL; }
class_flags(X) ::= class_flags(A) ABSTRACT. { X.Flags = A.Flags | ZCC_Abstract; X.Replaces = A.Replaces; }
class_flags(X) ::= class_flags(A) NATIVE. { X.Flags = A.Flags | ZCC_Native; X.Replaces = A.Replaces; }
class_flags(X) ::= class_flags(A) UI. { X.Flags = A.Flags | ZCC_UIFlag; X.Replaces = A.Replaces; }
class_flags(X) ::= class_flags(A) PLAY. { X.Flags = A.Flags | ZCC_Play; X.Replaces = A.Replaces; }
class_flags(X) ::= class_flags(A) REPLACES dottable_id(B). { X.Flags = A.Flags; X.Replaces = B; }
/*----- Dottable Identifier -----*/
@ -326,7 +328,9 @@ struct_def(X) ::= STRUCT(T) IDENTIFIER(A) struct_flags(S) LBRACE opt_struct_body
%type struct_flags{ClassFlagsBlock}
struct_flags(X) ::= . { X.Flags = 0; }
struct_flags(X) ::= NATIVE. { X.Flags = ZCC_Native; }
struct_flags(X) ::= struct_flags(A) UI. { X.Flags = A.Flags | ZCC_UIFlag; }
struct_flags(X) ::= struct_flags(A) PLAY. { X.Flags = A.Flags | ZCC_Play; }
struct_flags(X) ::= struct_flags(A) NATIVE. { X.Flags = A.Flags | ZCC_Native; }
opt_struct_body(X) ::= . { X = NULL; }
opt_struct_body(X) ::= struct_body(X).
@ -999,6 +1003,10 @@ decl_flag(X) ::= DEPRECATED(T). { X.Int = ZCC_Deprecated; X.SourceLoc = T.Sourc
decl_flag(X) ::= VIRTUAL(T). { X.Int = ZCC_Virtual; X.SourceLoc = T.SourceLoc; }
decl_flag(X) ::= OVERRIDE(T). { X.Int = ZCC_Override; X.SourceLoc = T.SourceLoc; }
decl_flag(X) ::= VARARG(T). { X.Int = ZCC_VarArg; X.SourceLoc = T.SourceLoc; }
decl_flag(X) ::= UI(T). { X.Int = ZCC_UIFlag; X.SourceLoc = T.SourceLoc; }
decl_flag(X) ::= PLAY(T). { X.Int = ZCC_Play; X.SourceLoc = T.SourceLoc; }
decl_flag(X) ::= CLEARSCOPE(T). { X.Int = ZCC_ClearScope; X.SourceLoc = T.SourceLoc; }
decl_flag(X) ::= VIRTUALSCOPE(T). { X.Int = ZCC_VirtualScope; X.SourceLoc = T.SourceLoc; }
func_const(X) ::= . { X.Int = 0; X.SourceLoc = stat->sc->GetMessageLine(); }
func_const(X) ::= CONST(T). { X.Int = ZCC_FuncConst; X.SourceLoc = T.SourceLoc; }

View file

@ -495,6 +495,16 @@ void ZCCCompiler::CreateStructTypes()
{
s->strct->Type = NewStruct(s->NodeName(), outer);
}
if ((s->strct->Flags & (ZCC_UIFlag | ZCC_Play)) == (ZCC_UIFlag | ZCC_Play))
{
Error(s->strct, "Struct %s has incompatible flags", s->NodeName().GetChars());
}
if (s->strct->Flags & ZCC_UIFlag)
s->Type()->ObjectFlags |= OF_UI;
if (s->strct->Flags & ZCC_Play)
s->Type()->ObjectFlags |= OF_Play;
s->strct->Symbol = new PSymbolType(s->NodeName(), s->Type());
syms->AddSymbol(s->strct->Symbol);
@ -597,11 +607,33 @@ void ZCCCompiler::CreateClassTypes()
c->cls->Type = nullptr;
}
}
if (c->cls->Flags & ZCC_Abstract)
{
c->Type()->ObjectFlags |= OF_Abstract;
}
if (c->Type() == nullptr) c->cls->Type = parent->FindClassTentative(c->NodeName());
if (c->cls->Flags & ZCC_Abstract)
c->Type()->ObjectFlags |= OF_Abstract;
//
static int incompatible[] = { ZCC_UIFlag, ZCC_Play, ZCC_ClearScope };
int incompatiblecnt = 0;
for (size_t k = 0; k < countof(incompatible); k++)
if (incompatible[k] & c->cls->Flags) incompatiblecnt++;
if (incompatiblecnt > 1)
{
Error(c->cls, "Class %s has incompatible flags", c->NodeName().GetChars());
}
if (c->cls->Flags & ZCC_UIFlag)
c->Type()->ObjectFlags = (c->Type()->ObjectFlags&~OF_Play) | OF_UI;
if (c->cls->Flags & ZCC_Play)
c->Type()->ObjectFlags = (c->Type()->ObjectFlags&~OF_UI) | OF_Play;
if (parent->ObjectFlags & (OF_UI | OF_Play)) // parent is either ui or play
{
if (c->cls->Flags & (ZCC_UIFlag | ZCC_Play))
{
Error(c->cls, "Can't change class scope in class %s", c->NodeName().GetChars());
}
c->Type()->ObjectFlags = (c->Type()->ObjectFlags & ~(OF_UI | OF_Play)) | (parent->ObjectFlags & (OF_UI | OF_Play));
}
c->Type()->bExported = true; // this class is accessible to script side type casts. (The reason for this flag is that types like PInt need to be skipped.)
c->cls->Symbol = new PSymbolType(c->NodeName(), c->Type());
OutNamespace->Symbols.AddSymbol(c->cls->Symbol);
@ -1050,8 +1082,8 @@ bool ZCCCompiler::CompileFields(PStruct *type, TArray<ZCC_VarDeclarator *> &Fiel
// For structs only allow 'deprecated', for classes exclude function qualifiers.
int notallowed = forstruct?
ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Meta | ZCC_Extension :
ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Extension;
ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Meta | ZCC_Extension | ZCC_VirtualScope | ZCC_ClearScope :
ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Extension | ZCC_VirtualScope | ZCC_ClearScope;
if (field->Flags & notallowed)
{
@ -1066,12 +1098,39 @@ bool ZCCCompiler::CompileFields(PStruct *type, TArray<ZCC_VarDeclarator *> &Fiel
if (field->Flags & ZCC_Deprecated) varflags |= VARF_Deprecated;
if (field->Flags & ZCC_ReadOnly) varflags |= VARF_ReadOnly;
if (field->Flags & ZCC_Transient) varflags |= VARF_Transient;
if (type->ObjectFlags & OF_UI)
varflags |= VARF_UI;
if (type->ObjectFlags & OF_Play)
varflags |= VARF_Play;
if (field->Flags & ZCC_UIFlag)
varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_UI);
if (field->Flags & ZCC_Play)
varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_Play);
if (field->Flags & ZCC_ClearScope)
varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_PlainData);
if (field->Flags & ZCC_Native)
{
varflags |= VARF_Native | VARF_Transient;
}
static int excludescope[] = { ZCC_UIFlag, ZCC_Play, ZCC_ClearScope };
int excludeflags = 0;
int fc = 0;
for (size_t i = 0; i < countof(excludescope); i++)
{
if (field->Flags & excludescope[i])
{
fc++;
excludeflags |= excludescope[i];
}
}
if (fc > 1)
{
Error(field, "Invalid combination of scope qualifiers %s on field %s", FlagsToString(excludeflags).GetChars(), FName(field->Names->Name).GetChars());
varflags &= ~(VARF_UI | VARF_Play); // make plain data
}
if (field->Flags & ZCC_Meta)
{
varflags |= VARF_Meta | VARF_Static | VARF_ReadOnly; // metadata implies readonly
@ -1121,7 +1180,7 @@ bool ZCCCompiler::CompileFields(PStruct *type, TArray<ZCC_VarDeclarator *> &Fiel
}
else if (hasnativechildren)
{
Error(field, "Cannot add field %s to %s. %s has native children which means it size may not change.", FName(name->Name).GetChars(), type->TypeName.GetChars(), type->TypeName.GetChars());
Error(field, "Cannot add field %s to %s. %s has native children which means it size may not change", FName(name->Name).GetChars(), type->TypeName.GetChars(), type->TypeName.GetChars());
}
else
{
@ -1218,7 +1277,7 @@ bool ZCCCompiler::CompileProperties(PClass *type, TArray<ZCC_Property *> &Proper
FString ZCCCompiler::FlagsToString(uint32_t flags)
{
const char *flagnames[] = { "native", "static", "private", "protected", "latent", "final", "meta", "action", "deprecated", "readonly", "funcconst", "abstract", "extension", "virtual", "override", "transient", "vararg" };
const char *flagnames[] = { "native", "static", "private", "protected", "latent", "final", "meta", "action", "deprecated", "readonly", "const", "abstract", "extend", "virtual", "override", "transient", "vararg", "ui", "play", "clearscope", "virtualscope" };
FString build;
for (size_t i = 0; i < countof(flagnames); i++)
@ -2017,7 +2076,7 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool
} while (t != f->Type);
}
int notallowed = ZCC_Latent | ZCC_Meta | ZCC_ReadOnly | ZCC_FuncConst | ZCC_Abstract;
int notallowed = ZCC_Latent | ZCC_Meta | ZCC_ReadOnly | ZCC_Abstract;
if (f->Flags & notallowed)
{
@ -2064,12 +2123,48 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool
if (f->Flags & ZCC_Virtual) varflags |= VARF_Virtual;
if (f->Flags & ZCC_Override) varflags |= VARF_Override;
if (f->Flags & ZCC_VarArg) varflags |= VARF_VarArg;
if (f->Flags & ZCC_FuncConst) varflags |= VARF_ReadOnly; // FuncConst method is internally marked as VARF_ReadOnly
if (c->Type()->ObjectFlags & OF_UI)
varflags |= VARF_UI;
if (c->Type()->ObjectFlags & OF_Play)
varflags |= VARF_Play;
if (f->Flags & ZCC_FuncConst)
varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_PlainData); // const implies clearscope. this is checked a bit later to also not have ZCC_Play/ZCC_UIFlag.
if (f->Flags & ZCC_UIFlag)
varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_UI);
if (f->Flags & ZCC_Play)
varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_Play);
if (f->Flags & ZCC_ClearScope)
varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_Clear);
if (f->Flags & ZCC_VirtualScope)
varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_Virtual);
// [ZZ] supporting const self for actors is quite a cumbersome task because there's no concept of a const pointer (?)
// either way, it doesn't make sense, because you can call any method on a readonly class instance.
// The above is nonsense. This needs to work and needs to be implemented properly.
/*
if ((f->Flags & ZCC_FuncConst) && (c->Type()->IsKindOf(RUNTIME_CLASS(PClass))))
{
Error(f, "'Const' on a method can only be used in structs");
}
*/
if ((f->Flags & ZCC_VarArg) && !(f->Flags & ZCC_Native))
{
Error(f, "'VarArg' can only be used with native methods");
}
if (f->Flags & ZCC_Action)
{
if (varflags & VARF_ReadOnly)
{
Error(f, "Action functions cannot be declared const");
varflags &= ~VARF_ReadOnly;
}
if (varflags & VARF_UI)
{
Error(f, "Action functions cannot be declared UI");
}
// Non-Actors cannot have action functions.
if (!c->Type()->IsKindOf(RUNTIME_CLASS(PClassActor)))
{
@ -2089,36 +2184,69 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool
}
if (f->Flags & ZCC_Static) varflags = (varflags & ~VARF_Method) | VARF_Final, implicitargs = 0; // Static implies Final.
if (varflags & VARF_Override) varflags &= ~VARF_Virtual; // allow 'virtual override'.
// Only one of these flags may be used.
static int exclude[] = { ZCC_Virtual, ZCC_Override, ZCC_Action, ZCC_Static };
static const char * print[] = { "virtual", "override", "action", "static" };
int excludeflags = 0;
int fc = 0;
FString build;
for (int i = 0; i < 4; i++)
for (size_t i = 0; i < countof(exclude); i++)
{
if (f->Flags & exclude[i])
{
fc++;
if (build.Len() > 0) build += ", ";
build += print[i];
excludeflags |= exclude[i];
}
}
if (fc > 1)
{
Error(f, "Invalid combination of qualifiers %s on function %s.", FName(f->Name).GetChars(), build.GetChars());
Error(f, "Invalid combination of qualifiers %s on function %s", FlagsToString(excludeflags).GetChars(), FName(f->Name).GetChars());
varflags |= VARF_Method;
}
if (varflags & VARF_Override) varflags |= VARF_Virtual; // Now that the flags are checked, make all override functions virtual as well.
// [ZZ] this doesn't make sense either.
if ((varflags&(VARF_ReadOnly | VARF_Method)) == VARF_ReadOnly) // non-method const function
{
Error(f, "'Const' on a static method is not supported");
}
// [ZZ] neither this
if ((varflags&(VARF_VirtualScope | VARF_Method)) == VARF_VirtualScope) // non-method virtualscope function
{
Error(f, "'VirtualScope' on a static method is not supported");
}
// you can't have a const function belonging to either ui or play.
// const is intended for plain data to signify that you can call a method on readonly variable.
if ((f->Flags & ZCC_FuncConst) && (f->Flags & (ZCC_UIFlag | ZCC_Play | ZCC_VirtualScope)))
{
Error(f, "Invalid combination of qualifiers %s on function %s", FlagsToString(f->Flags&(ZCC_FuncConst | ZCC_UIFlag | ZCC_Play | ZCC_VirtualScope)).GetChars(), FName(f->Name).GetChars());
}
static int excludescope[] = { ZCC_UIFlag, ZCC_Play, ZCC_ClearScope, ZCC_VirtualScope };
excludeflags = 0;
fc = 0;
for (size_t i = 0; i < countof(excludescope); i++)
{
if (f->Flags & excludescope[i])
{
fc++;
excludeflags |= excludescope[i];
}
}
if (fc > 1)
{
Error(f, "Invalid combination of scope qualifiers %s on function %s", FlagsToString(excludeflags).GetChars(), FName(f->Name).GetChars());
varflags &= ~(VARF_UI | VARF_Play); // make plain data
}
if (f->Flags & ZCC_Native)
{
varflags |= VARF_Native;
afd = FindFunction(c->Type(), FName(f->Name).GetChars());
if (afd == nullptr)
{
Error(f, "The function '%s.%s' has not been exported from the executable.", c->Type()->TypeName.GetChars(), FName(f->Name).GetChars());
Error(f, "The function '%s.%s' has not been exported from the executable", c->Type()->TypeName.GetChars(), FName(f->Name).GetChars());
}
else
{
@ -2232,7 +2360,7 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool
}
else if (hasoptionals)
{
Error(p, "All arguments after the first optional one need also be optional.");
Error(p, "All arguments after the first optional one need also be optional");
}
// TBD: disallow certain types? For now, let everything pass that isn't an array.
args.Push(type);
@ -2287,18 +2415,26 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool
sym->Variants[0].Implementation->DefaultArgs = std::move(argdefaults);
}
if (sym->Variants[0].Implementation != nullptr)
{
// [ZZ] unspecified virtual function inherits old scope. virtual function scope can't be changed.
sym->Variants[0].Implementation->BarrierSide = FScopeBarrier::SideFromFlags(varflags);
}
PClass *clstype = static_cast<PClass *>(c->Type());
if (varflags & VARF_Virtual)
{
if (sym->Variants[0].Implementation == nullptr)
{
Error(f, "Virtual function %s.%s not present.", c->Type()->TypeName.GetChars(), FName(f->Name).GetChars());
Error(f, "Virtual function %s.%s not present", c->Type()->TypeName.GetChars(), FName(f->Name).GetChars());
return;
}
if (varflags & VARF_Final)
{
sym->Variants[0].Implementation->Final = true;
}
if (varflags & VARF_ReadOnly)
sym->Variants[0].Implementation->FuncConst = true;
if (forclass)
{
int vindex = clstype->FindVirtualIndex(sym->SymbolName, sym->Variants[0].Proto);
@ -2316,6 +2452,23 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool
{
Error(f, "Attempt to override final function %s", FName(f->Name).GetChars());
}
// you can't change ui/play/clearscope for a virtual method.
if (f->Flags & (ZCC_UIFlag|ZCC_Play|ZCC_ClearScope|ZCC_VirtualScope))
{
Error(f, "Attempt to change scope for virtual function %s", FName(f->Name).GetChars());
}
// you can't change const qualifier for a virtual method
if (oldfunc->FuncConst != sym->Variants[0].Implementation->FuncConst)
{
Error(f, "Attempt to change const qualifier for virtual function %s", FName(f->Name).GetChars());
}
// inherit scope of original function if override not specified
sym->Variants[0].Implementation->BarrierSide = oldfunc->BarrierSide;
sym->Variants[0].Flags = FScopeBarrier::ChangeSideInFlags(sym->Variants[0].Flags, oldfunc->BarrierSide);
// inherit const from original function
if ((sym->Variants[0].Implementation->FuncConst = oldfunc->FuncConst))
sym->Variants[0].Flags |= VARF_ReadOnly;
clstype->Virtuals[vindex] = sym->Variants[0].Implementation;
sym->Variants[0].Implementation->VirtualIndex = vindex;
}

View file

@ -137,6 +137,10 @@ static void InitTokenMap()
TOKENDEF (TK_Latent, ZCC_LATENT);
TOKENDEF (TK_Virtual, ZCC_VIRTUAL);
TOKENDEF (TK_VarArg, ZCC_VARARG);
TOKENDEF (TK_UI, ZCC_UI);
TOKENDEF (TK_Play, ZCC_PLAY);
TOKENDEF (TK_ClearScope, ZCC_CLEARSCOPE);
TOKENDEF (TK_VirtualScope, ZCC_VIRTUALSCOPE);
TOKENDEF (TK_Override, ZCC_OVERRIDE);
TOKENDEF (TK_Final, ZCC_FINAL);
TOKENDEF (TK_Meta, ZCC_META);

View file

@ -36,7 +36,11 @@ enum
ZCC_Virtual = 1 << 13,
ZCC_Override = 1 << 14,
ZCC_Transient = 1 << 15,
ZCC_VarArg = 1 << 16
ZCC_VarArg = 1 << 16,
ZCC_UIFlag = 1 << 17, // there's also token called ZCC_UI
ZCC_Play = 1 << 18,
ZCC_ClearScope = 1 << 19,
ZCC_VirtualScope = 1 << 20,
};
// Function parameter modifiers

View file

@ -450,13 +450,4 @@ template<> struct THashTraits<FString>
int Compare(const FString &left, const FString &right) { return left.Compare(right); }
};
class FStringNoInit
{
char mem[sizeof(FString)];
operator FString&()
{
return *reinterpret_cast<FString*>(&mem);
}
};
#endif

View file

@ -450,13 +450,13 @@ class Actor : Thinker native
native static int FindUniqueTid(int start = 0, int limit = 0);
native void SetShade(color col);
native string GetTag(string defstr = "");
native string GetTag(string defstr = "") const;
native void SetTag(string defstr = "");
native double GetBobOffset(double frac = 0);
native void ClearCounters();
native bool GiveBody (int num, int max=0);
native bool HitFloor();
native bool isTeammate(Actor other);
native bool isTeammate(Actor other) const;
native int PlayerNumber();
native void SetFriendPlayer(PlayerInfo player);
native void SoundAlert(Actor target, bool splash = false, double maxdist = 0);
@ -468,15 +468,15 @@ class Actor : Thinker native
native bool UpdateWaterLevel (bool splash = true);
native bool IsZeroDamage();
native void ClearInterpolation();
native Vector3 PosRelative(sector sec);
native Vector3 PosRelative(sector sec) const;
native void HandleSpawnFlags();
native void ExplodeMissile(line lin = null, Actor target = null, bool onsky = false);
native void RestoreDamage();
native int SpawnHealth();
native int SpawnHealth() const;
native void SetDamage(int dmg);
native double Distance2D(Actor other);
native double Distance3D(Actor other);
native double Distance2D(Actor other) const;
native double Distance3D(Actor other) const;
native void SetOrigin(vector3 newpos, bool moving);
native void SetXYZ(vector3 newpos);
native Actor GetPointer(int aaptr);
@ -511,7 +511,7 @@ class Actor : Thinker native
native void BloodSplatter (Vector3 pos, double hitangle, bool axe = false);
native bool HitWater (sector sec, Vector3 pos, bool checkabove = false, bool alert = true, bool force = false);
native void PlaySpawnSound(Actor missile);
native bool CountsAsKill();
native bool CountsAsKill() const;
native bool Teleport(Vector3 pos, double angle, int flags);
native void TraceBleed(int damage, Actor missile);
@ -559,21 +559,21 @@ class Actor : Thinker native
native void LinkToWorld(LinkContext ctx = null);
native void UnlinkFromWorld(out LinkContext ctx = null);
native bool CanSeek(Actor target);
native double AngleTo(Actor target, bool absolute = false);
native double AngleTo(Actor target, bool absolute = false) const;
native void AddZ(double zadd, bool moving = true);
native void SetZ(double z);
native vector2 Vec2To(Actor other);
native vector3 Vec3To(Actor other);
native vector3 Vec3Offset(double x, double y, double z, bool absolute = false);
native vector3 Vec3Angle(double length, double angle, double z = 0, bool absolute = false);
native vector2 Vec2Angle(double length, double angle, bool absolute = false);
native vector2 Vec2Offset(double x, double y, bool absolute = false);
native vector3 Vec2OffsetZ(double x, double y, double atz, bool absolute = false);
native void VelFromAngle(double speed = 0, double angle = 0);
native void Vel3DFromAngle(double speed, double angle, double pitch);
native vector2 Vec2To(Actor other) const;
native vector3 Vec3To(Actor other) const;
native vector3 Vec3Offset(double x, double y, double z, bool absolute = false) const;
native vector3 Vec3Angle(double length, double angle, double z = 0, bool absolute = false) const;
native vector2 Vec2Angle(double length, double angle, bool absolute = false) const;
native vector2 Vec2Offset(double x, double y, bool absolute = false) const;
native vector3 Vec2OffsetZ(double x, double y, double atz, bool absolute = false) const;
native void VelFromAngle(double speed = 0, double angle = 0) const;
native void Vel3DFromAngle(double speed, double angle, double pitch) const;
native void Thrust(double speed = 0, double angle = 0);
native bool isFriend(Actor other);
native bool isHostile(Actor other);
native bool isFriend(Actor other) const;
native bool isHostile(Actor other) const;
native void AdjustFloorClip();
native DropItem GetDropItems();
native void CopyFriendliness (Actor other, bool changeTarget, bool resetHealth = true);
@ -588,8 +588,8 @@ class Actor : Thinker native
native void Howl();
native void DrawSplash (int count, double angle, int kind);
native void GiveSecret(bool printmsg = true, bool playsound = true);
native double GetCameraHeight();
native double GetGravity();
native double GetCameraHeight() const;
native double GetGravity() const;
native bool CheckClass(class<Actor> checkclass, int ptr_select = AAPTR_DEFAULT, bool match_superclass = false);
native void AddInventory(Inventory inv);
@ -597,7 +597,7 @@ class Actor : Thinker native
native void ClearInventory();
native bool GiveInventory(class<Inventory> type, int amount, bool givecheat = false);
native bool TakeInventory(class<Inventory> itemclass, int amount, bool fromdecorate = false, bool notakeinfinite = false);
native Inventory FindInventory(class<Inventory> itemtype, bool subclass = false);
native Inventory FindInventory(class<Inventory> itemtype, bool subclass = false) const;
native Inventory GiveInventoryType(class<Inventory> itemtype);
native Inventory DropInventory (Inventory item, int amt = -1);
native bool UseInventory(Inventory item);
@ -613,7 +613,7 @@ class Actor : Thinker native
native double GetDistance(bool checkz, int ptr = AAPTR_TARGET);
native double GetAngle(int flags, int ptr = AAPTR_TARGET);
native double GetZAt(double px = 0, double py = 0, double angle = 0, int flags = 0, int pick_pointer = AAPTR_DEFAULT);
native int GetSpawnHealth();
native int GetSpawnHealth() const;
native double GetCrouchFactor(int ptr = AAPTR_PLAYER1);
native double GetCVar(string cvar);
native int GetPlayerInput(int inputnum, int ptr = AAPTR_DEFAULT);

View file

@ -331,10 +331,10 @@ class Object native
native static uint MSTime();
native Name GetClassName();
native void Destroy();
native virtualscope void Destroy();
// This does not call into the native method of the same name to avoid problems with objects that get garbage collected late on shutdown.
virtual void OnDestroy() {}
virtual virtualscope void OnDestroy() {}
}
class BrokenLines : Object native
@ -344,7 +344,7 @@ class BrokenLines : Object native
native String StringAt(int line);
}
class Thinker : Object native
class Thinker : Object native play
{
enum EStatnums
{
@ -386,7 +386,6 @@ class Thinker : Object native
class ThinkerIterator : Object native
{
native static ThinkerIterator Create(class<Object> type = "Actor", int statnum=Thinker.MAX_STATNUM+1);
native Thinker Next(bool exact = false);
native void Reinit();

View file

@ -1,6 +1,6 @@
class BaseEvent native { } // just a base class. it doesn't inherit from Object on the scripting side so you can't call Destroy() on it and break everything.
class RenderEvent : BaseEvent native
class RenderEvent : BaseEvent native ui
{
native readonly Vector3 ViewPos;
native readonly double ViewAngle;
@ -10,7 +10,7 @@ class RenderEvent : BaseEvent native
native readonly Actor Camera;
}
class WorldEvent : BaseEvent native
class WorldEvent : BaseEvent native play
{
// for loaded/unloaded
native readonly bool IsSaveGame;
@ -28,7 +28,7 @@ class WorldEvent : BaseEvent native
native readonly double DamageAngle;
}
class PlayerEvent : BaseEvent native
class PlayerEvent : BaseEvent native play
{
// this is the player number that caused the event.
// note: you can get player struct from this by using players[e.PlayerNumber]
@ -37,7 +37,7 @@ class PlayerEvent : BaseEvent native
native readonly bool IsReturn;
}
class UiEvent : BaseEvent native
class UiEvent : BaseEvent native ui
{
// d_gui.h
enum EGUIEvent
@ -121,7 +121,7 @@ class UiEvent : BaseEvent native
native readonly bool IsAlt;
}
class InputEvent : BaseEvent native
class InputEvent : BaseEvent native play
{
enum EGenericEvent
{
@ -279,7 +279,7 @@ class ConsoleEvent : BaseEvent native
native readonly int Args[3];
}
class StaticEventHandler : Object native
class StaticEventHandler : Object native play
{
// static event handlers CAN register other static event handlers.
// unlike EventHandler.Create that will not create them.
@ -307,8 +307,8 @@ class StaticEventHandler : Object native
virtual native void WorldTick(WorldEvent e);
//
virtual native void RenderFrame(RenderEvent e);
virtual native void RenderOverlay(RenderEvent e);
virtual native ui void RenderFrame(RenderEvent e);
virtual native ui void RenderOverlay(RenderEvent e);
//
virtual native void PlayerEntered(PlayerEvent e);
@ -317,11 +317,12 @@ class StaticEventHandler : Object native
virtual native void PlayerDisconnected(PlayerEvent e);
//
virtual native bool UiProcess(UiEvent e);
virtual native ui bool UiProcess(UiEvent e);
virtual native bool InputProcess(InputEvent e);
//
virtual native void ConsoleProcess(ConsoleEvent e);
virtual native ui void ConsoleProcess(ConsoleEvent e);
virtual native void NetworkProcess(ConsoleEvent e);
// this value will be queried on Register() to decide the relative order of this handler to every other.
// this is most useful in UI systems.
@ -342,4 +343,6 @@ class EventHandler : StaticEventHandler native
static native bool Register(StaticEventHandler handler);
static native bool Unregister(StaticEventHandler handler);
clearscope static native void SendNetworkEvent(String name, int arg1 = 0, int arg2 = 0, int arg3 = 0);
}

View file

@ -751,7 +751,7 @@ class Inventory : Actor native
virtual bool Use (bool pickup) { return false; }
virtual double GetSpeedFactor() { return 1; }
virtual bool GetNoTeleportFreeze() { return false; }
virtual void AlterWeaponSprite(VisStyle vis, in out int changed) {}
virtual ui void AlterWeaponSprite(VisStyle vis, in out int changed) {}
virtual void OwnerDied() {}
virtual Color GetBlend () { return 0; }
@ -818,7 +818,7 @@ class Inventory : Actor native
//
//===========================================================================
virtual bool DrawPowerup(int x, int y) { return false; }
virtual ui bool DrawPowerup(int x, int y) { return false; }
//===========================================================================
//

View file

@ -285,7 +285,7 @@ class Powerup : Inventory
//
//===========================================================================
virtual bool isBlinking()
virtual bool isBlinking() const
{
return (EffectTics <= BLINKTHRESHOLD && (EffectTics & 8) && !bNoScreenBlink);
}
@ -934,7 +934,7 @@ class PowerFlight : Powerup
+INVENTORY.HUBPOWER
}
bool HitCenterFrame;
ui bool HitCenterFrame;
//===========================================================================
//

View file

@ -1,5 +1,5 @@
struct SectorPortal native
struct SectorPortal native play
{
enum EType
{
@ -31,12 +31,12 @@ struct SectorPortal native
};
struct Vertex native
struct Vertex native play
{
native readonly Vector2 p;
}
struct Side native
struct Side native play
{
enum ETexpart
{
@ -100,7 +100,7 @@ struct Side native
};
struct Line native
struct Line native play
{
enum ELineFlags
{
@ -143,7 +143,7 @@ struct Line native
native int special;
native int args[5]; // <--- hexen-style arguments (expanded to ZDoom's full width)
native double alpha; // <--- translucency (0=invisibile, FRACUNIT=opaque)
native Side sidedef[2];
native readonly Side sidedef[2];
native readonly double bbox[4]; // bounding box, for the extent of the LineDef.
native readonly Sector frontsector, backsector;
native int validcount; // if == validcount, already checked
@ -171,25 +171,25 @@ struct Line native
}
}
struct SecPlane native
struct SecPlane native play
{
native Vector3 Normal;
native double D;
native double negiC;
native bool isSlope();
native int PointOnSide(Vector3 pos);
native double ZatPoint (Vector2 v);
native double ZatPointDist(Vector2 v, double dist);
native bool isEqual(Secplane other);
native bool isSlope() const;
native int PointOnSide(Vector3 pos) const;
native double ZatPoint (Vector2 v) const;
native double ZatPointDist(Vector2 v, double dist) const;
native bool isEqual(Secplane other) const;
native void ChangeHeight(double hdiff);
native double GetChangedHeight(double hdiff);
native double GetChangedHeight(double hdiff) const;
native double HeightDiff(double oldd, double newd = 0.0);
native double PointToDist(Vector2 xy, double z);
native double PointToDist(Vector2 xy, double z) const;
}
// This encapsulates all info Doom's original 'special' field contained - for saving and transferring.
struct SecSpecial
struct SecSpecial play
{
Name damagetype;
int damageamount;
@ -199,7 +199,7 @@ struct SecSpecial
int Flags;
}
struct Sector native
struct Sector native play
{
//secplane_t floorplane, ceilingplane; // defined internally
//FDynamicColormap *ColorMap;

View file

@ -48,7 +48,7 @@ struct JoystickConfig native
}
class Menu : Object native
class Menu : Object native ui
{
enum EMenuKey
{
@ -287,7 +287,7 @@ class Menu : Object native
}
class MenuDescriptor : Object native
class MenuDescriptor : Object native ui
{
native Name mMenuName;
native String mNetgameMessage;

View file

@ -4,7 +4,7 @@
//
//=============================================================================
class MenuItemBase : Object native
class MenuItemBase : Object native ui
{
protected native double mXpos, mYpos;
protected native Name mAction;

View file

@ -150,7 +150,7 @@ class PlayerPawn : Actor native
}
// This is for SBARINFO.
int, int GetEffectTicsForItem(class<Inventory> item)
int, int GetEffectTicsForItem(class<Inventory> item) const
{
let pg = (class<PowerupGiver>)(item);
if (pg != null)
@ -167,10 +167,10 @@ class PlayerPawn : Actor native
return -1, -1;
}
native int GetMaxHealth(bool withupgrades = false);
native int GetMaxHealth(bool withupgrades = false) const;
native bool ResetAirSupply (bool playgasp = false);
native void CheckWeaponSwitch(class<Inventory> item);
native static String GetPrintableDisplayName(Class<Actor> cls);
native clearscope static String GetPrintableDisplayName(Class<Actor> cls);
}
@ -192,7 +192,7 @@ class PlayerChunk : PlayerPawn
}
}
class PSprite : Object native
class PSprite : Object native play
{
enum PSPLayers
{
@ -239,7 +239,7 @@ enum EPlayerState
PST_GONE // Player has left the game
}
struct PlayerInfo native // this is what internally is known as player_t
struct PlayerInfo native play // this is what internally is known as player_t
{
// technically engine constants but the only part of the playsim using them is the player.
const NOFIXEDCOLORMAP = -1;
@ -340,22 +340,22 @@ usercmd_t original_cmd;
native void PoisonDamage(Actor source, int damage, bool playPainSound);
native void SetPsprite(int id, State stat, bool pending = false);
native void SetSafeFlash(Weapon weap, State flashstate, int index);
native PSprite GetPSprite(int id);
native PSprite FindPSprite(int id);
native PSprite GetPSprite(int id) const;
native PSprite FindPSprite(int id) const;
native void SetLogNumber (int text);
native void SetLogText (String text);
native void DropWeapon();
native void BringUpWeapon();
native String GetUserName();
native Color GetColor();
native int GetColorSet();
native int GetPlayerClassNum();
native int GetSkin();
native bool GetNeverSwitch();
native int GetGender();
native int GetTeam();
native float GetAutoaim();
native String GetUserName() const;
native Color GetColor() const;
native int GetColorSet() const;
native int GetPlayerClassNum() const;
native int GetSkin() const;
native bool GetNeverSwitch() const;
native int GetGender() const;
native int GetTeam() const;
native float GetAutoaim() const;
native void SetFOV(float fov);
}