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_data.cpp
scripting/thingdef_properties.cpp scripting/thingdef_properties.cpp
scripting/backend/codegen.cpp scripting/backend/codegen.cpp
scripting/backend/scopebarrier.cpp
scripting/backend/dynarrays.cpp scripting/backend/dynarrays.cpp
scripting/backend/vmbuilder.cpp scripting/backend/vmbuilder.cpp
scripting/backend/vmdisasm.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_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_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_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; template<class T> class TObjPtr;

View file

@ -37,6 +37,10 @@ enum
VARF_Transient = (1<<17), // don't auto serialize field. VARF_Transient = (1<<17), // don't auto serialize field.
VARF_Meta = (1<<18), // static class data (by necessity read only.) 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_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 ------------------------------------------------------- // An action function -------------------------------------------------------

View file

@ -117,6 +117,21 @@ bool E_UnregisterHandler(DStaticEventHandler* handler)
return true; 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) bool E_CheckHandler(DStaticEventHandler* handler)
{ {
for (DStaticEventHandler* lhandler = E_FirstEventHandler; lhandler; lhandler = lhandler->next) for (DStaticEventHandler* lhandler = E_FirstEventHandler; lhandler; lhandler = lhandler->next)
@ -521,6 +536,18 @@ DEFINE_ACTION_FUNCTION(DStaticEventHandler, SetOrder)
return 0; 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) DEFINE_ACTION_FUNCTION(DEventHandler, Create)
{ {
PARAM_PROLOGUE; PARAM_PROLOGUE;
@ -658,6 +685,7 @@ DEFINE_EMPTY_HANDLER(DStaticEventHandler, UiProcess);
DEFINE_EMPTY_HANDLER(DStaticEventHandler, InputProcess); DEFINE_EMPTY_HANDLER(DStaticEventHandler, InputProcess);
DEFINE_EMPTY_HANDLER(DStaticEventHandler, ConsoleProcess); DEFINE_EMPTY_HANDLER(DStaticEventHandler, ConsoleProcess);
DEFINE_EMPTY_HANDLER(DStaticEventHandler, NetworkProcess);
// =========================================== // ===========================================
// //
@ -1068,6 +1096,8 @@ static DConsoleEvent* E_SetupConsoleEvent()
void DStaticEventHandler::ConsoleProcess(int player, FString name, int arg1, int arg2, int arg3) void DStaticEventHandler::ConsoleProcess(int player, FString name, int arg1, int arg2, int arg3)
{ {
if (player < 0)
{
IFVIRTUAL(DStaticEventHandler, ConsoleProcess) IFVIRTUAL(DStaticEventHandler, ConsoleProcess)
{ {
// don't create excessive DObjects if not going to be processed anyway // don't create excessive DObjects if not going to be processed anyway
@ -1085,6 +1115,27 @@ void DStaticEventHandler::ConsoleProcess(int player, FString name, int arg1, int
VMValue params[2] = { (DStaticEventHandler*)this, e }; VMValue params[2] = { (DStaticEventHandler*)this, e };
GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr); 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++) for (int i = 0; i < argn; i++)
arg[i] = atoi(argv[2 + i]); arg[i] = atoi(argv[2 + i]);
// call networked // call networked
Net_WriteByte(DEM_NETEVENT); E_SendNetworkEvent(argv[1], arg[0], arg[1], arg[2]);
Net_WriteString(argv[1]);
Net_WriteByte(argn);
for (int i = 0; i < 3; i++)
Net_WriteLong(arg[i]);
} }
} }

View file

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

View file

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

View file

@ -4765,24 +4765,24 @@ static int ScriptCall(unsigned argc, int32_t *args)
auto cls = PClass::FindClass(clsname); auto cls = PClass::FindClass(clsname);
if (!cls) 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)); auto funcsym = dyn_cast<PFunction>(cls->Symbols.FindSymbol(funcname, true));
if (funcsym == nullptr) 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; auto func = funcsym->Variants[0].Implementation;
if (func->ImplicitArgs > 0) 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; TArray<VMValue> params;
for (unsigned i = 2; i < argc; i++) for (unsigned i = 2; i < argc; i++)
{ {
if (func->Proto->ArgumentTypes.Size() < i - 1) 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]; auto argtype = func->Proto->ArgumentTypes[i - 2];
// The only types allowed are int, bool, double, Name, Sound, Color and String // 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 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()) 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. // 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)) 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 // 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); } 'virtual' { RET(TK_Virtual); }
'override' { RET(TK_Override); } 'override' { RET(TK_Override); }
'vararg' { RET(TK_VarArg); } 'vararg' { RET(TK_VarArg); }
'ui' { RET(TK_UI); }
'play' { RET(TK_Play); }
'clearscope' { RET(TK_ClearScope); }
'virtualscope' { RET(TK_VirtualScope); }
'super' { RET(TK_Super); } 'super' { RET(TK_Super); }
'global' { RET(TK_Global); } 'global' { RET(TK_Global); }
'stop' { RET(TK_Stop); } 'stop' { RET(TK_Stop); }

View file

@ -112,6 +112,10 @@ xx(TK_Optional, "'optional'")
xx(TK_Export, "'expert'") xx(TK_Export, "'expert'")
xx(TK_Virtual, "'virtual'") xx(TK_Virtual, "'virtual'")
xx(TK_VarArg, "'vararg'") 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_Override, "'override'")
xx(TK_Super, "'super'") xx(TK_Super, "'super'")
xx(TK_Null, "'null'") xx(TK_Null, "'null'")

View file

@ -89,6 +89,37 @@ static const FLOP FxFlops[] =
{ NAME_TanH, FLOP_TANH, [](double v) { return g_tanh(v); } }, { 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 // 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_ReadOnly)) return false;
if (!(flags & VARF_InternalAccess)) return true; if (!(flags & VARF_InternalAccess)) return true;
@ -5023,6 +5055,7 @@ FxNew::FxNew(FxExpression *v)
{ {
val = new FxClassTypeCast(NewClassPointer(RUNTIME_CLASS(DObject)), v, false); val = new FxClassTypeCast(NewClassPointer(RUNTIME_CLASS(DObject)), v, false);
ValueType = NewPointer(RUNTIME_CLASS(DObject)); ValueType = NewPointer(RUNTIME_CLASS(DObject));
CallingFunction = nullptr;
} }
//========================================================================== //==========================================================================
@ -5047,6 +5080,7 @@ FxExpression *FxNew::Resolve(FCompileContext &ctx)
CHECKRESOLVED(); CHECKRESOLVED();
SAFE_RESOLVE(val, ctx); SAFE_RESOLVE(val, ctx);
CallingFunction = ctx.Function;
if (!val->ValueType->IsKindOf(RUNTIME_CLASS(PClassPointer))) if (!val->ValueType->IsKindOf(RUNTIME_CLASS(PClassPointer)))
{ {
ScriptPosition.Message(MSG_ERROR, "Class type expected"); ScriptPosition.Message(MSG_ERROR, "Class type expected");
@ -5056,8 +5090,28 @@ FxExpression *FxNew::Resolve(FCompileContext &ctx)
if (val->isConstant()) if (val->isConstant())
{ {
auto cls = static_cast<PClass *>(static_cast<FxConstant*>(val)->GetValue().GetPointer()); 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); ValueType = NewPointer(cls);
} }
return this; return this;
} }
@ -5072,7 +5126,7 @@ ExpEmit FxNew::Emit(VMFunctionBuilder *build)
ExpEmit from = val->Emit(build); ExpEmit from = val->Emit(build);
from.Free(build); from.Free(build);
ExpEmit to(build, REGT_POINTER); 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; return to;
} }
@ -6245,7 +6299,7 @@ FxExpression *FxLocalVariable::Resolve(FCompileContext &ctx)
bool FxLocalVariable::RequestAddress(FCompileContext &ctx, bool *writable) bool FxLocalVariable::RequestAddress(FCompileContext &ctx, bool *writable)
{ {
AddressRequested = true; AddressRequested = true;
if (writable != nullptr) *writable = !ctx.CheckReadOnly(Variable->VarFlags); if (writable != nullptr) *writable = !ctx.CheckWritable(Variable->VarFlags);
return true; return true;
} }
@ -6463,7 +6517,7 @@ FxGlobalVariable::FxGlobalVariable(PField* mem, const FScriptPosition &pos)
bool FxGlobalVariable::RequestAddress(FCompileContext &ctx, bool *writable) bool FxGlobalVariable::RequestAddress(FCompileContext &ctx, bool *writable)
{ {
AddressRequested = true; AddressRequested = true;
if (writable != nullptr) *writable = AddressWritable && !ctx.CheckReadOnly(membervar->Flags); if (writable != nullptr) *writable = AddressWritable && !ctx.CheckWritable(membervar->Flags);
return true; return true;
} }
@ -6653,7 +6707,7 @@ FxStackVariable::~FxStackVariable()
bool FxStackVariable::RequestAddress(FCompileContext &ctx, bool *writable) bool FxStackVariable::RequestAddress(FCompileContext &ctx, bool *writable)
{ {
AddressRequested = true; AddressRequested = true;
if (writable != nullptr) *writable = AddressWritable && !ctx.CheckReadOnly(membervar->Flags); if (writable != nullptr) *writable = AddressWritable && !ctx.CheckWritable(membervar->Flags);
return true; return true;
} }
@ -6751,8 +6805,34 @@ bool FxStructMember::RequestAddress(FCompileContext &ctx, bool *writable)
return false; return false;
} }
AddressRequested = true; AddressRequested = true;
if (writable != nullptr) *writable = (AddressWritable && !ctx.CheckReadOnly(membervar->Flags) && 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)); (!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; return true;
} }
@ -6772,7 +6852,7 @@ FxExpression *FxStructMember::Resolve(FCompileContext &ctx)
if (!classx->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)) if (!classx->ValueType->IsKindOf(RUNTIME_CLASS(PPointer))
|| !static_cast<PPointer *>(classx->ValueType)->PointedType->IsKindOf(RUNTIME_CLASS(AActor))) || !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; delete this;
return nullptr; return nullptr;
} }
@ -6782,12 +6862,36 @@ FxExpression *FxStructMember::Resolve(FCompileContext &ctx)
return x->Resolve(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))) if (classx->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)))
{ {
PPointer *ptrtype = dyn_cast<PPointer>(classx->ValueType); PPointer *ptrtype = dyn_cast<PPointer>(classx->ValueType);
if (ptrtype == nullptr || !ptrtype->PointedType->IsKindOf(RUNTIME_CLASS(PStruct))) 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; delete this;
return nullptr; return nullptr;
} }
@ -6799,7 +6903,8 @@ FxExpression *FxStructMember::Resolve(FCompileContext &ctx)
{ {
auto parentfield = static_cast<FxMemberBase *>(classx)->membervar; auto parentfield = static_cast<FxMemberBase *>(classx)->membervar;
// PFields are garbage collected so this will be automatically taken care of later. // 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; newfield->BitValue = membervar->BitValue;
static_cast<FxMemberBase *>(classx)->membervar = newfield; static_cast<FxMemberBase *>(classx)->membervar = newfield;
classx->isresolved = false; // re-resolve the parent so it can also check if it can be optimized away. 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))) 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; delete this;
return nullptr; return nullptr;
} }
@ -7371,6 +7476,32 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
return nullptr; 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)) if (!CheckFunctionCompatiblity(ScriptPosition, ctx.Function, afd))
{ {
delete this; delete this;
@ -7593,8 +7724,18 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
break; break;
case NAME_New: 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]); func = new FxNew(ArgList[0]);
ArgList[0] = nullptr; ArgList[0] = nullptr;
} }
@ -7651,13 +7792,14 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
PStruct *cls; PStruct *cls;
bool staticonly = false; bool staticonly = false;
bool novirtual = false; bool novirtual = false;
bool isreadonly = false;
PStruct *ccls = nullptr; PStruct *ccls = nullptr;
if (ctx.Class == nullptr) if (ctx.Class == nullptr)
{ {
// There's no way that a member function call can resolve to a constant so abort right away. // 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; delete this;
return nullptr; return nullptr;
} }
@ -7666,7 +7808,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
{ {
if (a == nullptr) if (a == nullptr)
{ {
ScriptPosition.Message(MSG_ERROR, "Empty function argument."); ScriptPosition.Message(MSG_ERROR, "Empty function argument");
delete this; delete this;
return nullptr; return nullptr;
} }
@ -7677,6 +7819,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
auto id = static_cast<FxIdentifier *>(Self)->Identifier; 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 // 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. // 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; if (id == NAME_String) ccls = TypeStringStruct;
else ccls = FindStructType(id, ctx); else ccls = FindStructType(id, ctx);
if (ccls != nullptr) static_cast<FxIdentifier *>(Self)->noglobal = true; if (ccls != nullptr) static_cast<FxIdentifier *>(Self)->noglobal = true;
@ -7688,7 +7831,6 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
{ {
if (ccls != nullptr) if (ccls != nullptr)
{ {
// [ZZ] substitute ccls for String internal type.
if (!ccls->IsKindOf(RUNTIME_CLASS(PClass)) || static_cast<PClass *>(ccls)->bExported) if (!ccls->IsKindOf(RUNTIME_CLASS(PClass)) || static_cast<PClass *>(ccls)->bExported)
{ {
cls = ccls; cls = ccls;
@ -7717,6 +7859,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
Self = new FxSelf(ScriptPosition); Self = new FxSelf(ScriptPosition);
Self->ValueType = NewPointer(cls); Self->ValueType = NewPointer(cls);
} }
else novirtual = false;
} }
} }
} }
@ -7757,7 +7900,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
{ {
if (ArgList.Size() > 0) 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; delete this;
return nullptr; return nullptr;
} }
@ -7801,7 +7944,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
{ {
if (ArgList.Size() > 0) 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; delete this;
return nullptr; return nullptr;
} }
@ -7895,7 +8038,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
{ {
if (ArgList.Size() > 0) 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; delete this;
return nullptr; return nullptr;
} }
@ -7955,7 +8098,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
{ {
if (ArgList.Size() > 0) 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; delete this;
return nullptr; return nullptr;
} }
@ -7968,7 +8111,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
} }
else 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; delete this;
return nullptr; return nullptr;
} }
@ -7976,22 +8119,15 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
else if (Self->ValueType->IsKindOf(RUNTIME_CLASS(PStruct))) else if (Self->ValueType->IsKindOf(RUNTIME_CLASS(PStruct)))
{ {
bool writable; bool writable;
if (Self->RequestAddress(ctx, &writable) && writable)
{ // [ZZ] allow const method to be called on a readonly struct
isreadonly = !(Self->RequestAddress(ctx, &writable) && writable);
cls = static_cast<PStruct*>(Self->ValueType); cls = static_cast<PStruct*>(Self->ValueType);
Self->ValueType = NewPointer(Self->ValueType); Self->ValueType = NewPointer(Self->ValueType);
} }
else else
{ {
// Cannot be made writable so we cannot use its methods. ScriptPosition.Message(MSG_ERROR, "Invalid expression on left hand side of %s", MethodName.GetChars());
ScriptPosition.Message(MSG_ERROR, "Invalid expression on left hand side of %s\n", MethodName.GetChars());
delete this;
return nullptr;
}
}
else
{
ScriptPosition.Message(MSG_ERROR, "Invalid expression on left hand side of %s\n", MethodName.GetChars());
delete this; delete this;
return nullptr; return nullptr;
} }
@ -8009,7 +8145,50 @@ isresolved:
if (afd == nullptr) 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; delete this;
return nullptr; return nullptr;
} }
@ -8022,14 +8201,14 @@ isresolved:
auto ccls = dyn_cast<PClass>(cls); auto ccls = dyn_cast<PClass>(cls);
if (clstype == nullptr || ccls == nullptr || !clstype->IsDescendantOf(ccls)) 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; delete this;
return nullptr; return nullptr;
} }
else else
{ {
// Todo: If this is a qualified call to a parent class function, let it through (but this needs to disable virtual calls later.) // 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; delete this;
return nullptr; 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. // 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)) 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; delete this;
return nullptr; return nullptr;
} }
@ -8264,6 +8443,7 @@ FxVMFunctionCall::FxVMFunctionCall(FxExpression *self, PFunction *func, FArgumen
ArgList = std::move(args); ArgList = std::move(args);
EmitTail = false; EmitTail = false;
NoVirtual = novirtual; NoVirtual = novirtual;
CallingFunction = nullptr;
} }
//========================================================================== //==========================================================================
@ -8337,6 +8517,7 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx)
return nullptr; return nullptr;
} }
CallingFunction = ctx.Function;
if (ArgList.Size() > 0) if (ArgList.Size() > 0)
{ {
bool foundvarargs = false; bool foundvarargs = false;
@ -8544,6 +8725,17 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build)
ExpEmit selfemit; ExpEmit selfemit;
if (Function->Variants[0].Flags & VARF_Method) 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); assert(Self != nullptr);
selfemit = Self->Emit(build); selfemit = Self->Emit(build);
assert((selfemit.RegType == REGT_POINTER) || (selfemit.Fixed && selfemit.Target)); assert((selfemit.RegType == REGT_POINTER) || (selfemit.Fixed && selfemit.Target));

View file

@ -45,6 +45,7 @@
#include "s_sound.h" #include "s_sound.h"
#include "actor.h" #include "actor.h"
#include "vmbuilder.h" #include "vmbuilder.h"
#include "scopebarrier.h"
#define CHECKRESOLVED() if (isresolved) return this; isresolved=true; #define CHECKRESOLVED() if (isresolved) return this; isresolved=true;
@ -95,7 +96,7 @@ struct FCompileContext
void HandleJumps(int token, FxExpression *handler); void HandleJumps(int token, FxExpression *handler);
void CheckReturn(PPrototype *proto, FScriptPosition &pos); void CheckReturn(PPrototype *proto, FScriptPosition &pos);
bool CheckReadOnly(int flags); bool CheckWritable(int flags);
FxLocalVariableDeclaration *FindLocalVariable(FName name); FxLocalVariableDeclaration *FindLocalVariable(FName name);
}; };
@ -1208,6 +1209,7 @@ private:
class FxNew : public FxExpression class FxNew : public FxExpression
{ {
FxExpression *val; FxExpression *val;
PFunction *CallingFunction;
public: public:
@ -1326,6 +1328,7 @@ public:
PField *membervar; PField *membervar;
bool AddressRequested = false; bool AddressRequested = false;
bool AddressWritable = true; bool AddressWritable = true;
int BarrierSide = -1; // [ZZ] some magic
FxMemberBase(EFxType type, PField *f, const FScriptPosition &p); FxMemberBase(EFxType type, PField *f, const FScriptPosition &p);
}; };
@ -1707,6 +1710,7 @@ class FxVMFunctionCall : public FxExpression
// for multi assignment // for multi assignment
int AssignCount = 0; int AssignCount = 0;
TArray<ExpEmit> ReturnRegs; TArray<ExpEmit> ReturnRegs;
PFunction *CallingFunction;
public: public:
FxVMFunctionCall(FxExpression *self, PFunction *func, FArgumentList &args, const FScriptPosition &pos, bool novirtual); 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) if (funcflags & VARF_Method)
{ {
// implied self pointer // 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 (argflags != nullptr) argflags->Push(VARF_Implicit | VARF_ReadOnly);
if (argnames != nullptr) argnames->Push(NAME_self); if (argnames != nullptr) argnames->Push(NAME_self);
} }
if (funcflags & VARF_Action) if (funcflags & VARF_Action)
{ {
assert(!(funcflags & VARF_ReadOnly));
// implied caller and callingstate pointers // implied caller and callingstate pointers
if (args != nullptr) 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. // 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; 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. 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); 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()); 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. // 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()); 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. // 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("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("ceilingplane", secplanestruct, myoffsetof(sector_t, ceilingplane), VARF_Native | VARF_ReadOnly);
sectorstruct->AddNativeField("floorplane", secplanestruct, myoffsetof(sector_t, floorplane), VARF_Native); sectorstruct->AddNativeField("floorplane", secplanestruct, myoffsetof(sector_t, floorplane), VARF_Native | VARF_ReadOnly);

View file

@ -8,6 +8,10 @@
#include "doomerrors.h" #include "doomerrors.h"
#include "memarena.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; class DObject;
extern FMemArena ClassDataAllocator; extern FMemArena ClassDataAllocator;
@ -704,6 +708,8 @@ public:
bool Native; bool Native;
bool Final = false; // cannot be overridden bool Final = false; // cannot be overridden
bool Unsafe = false; // Contains references to class fields that are unsafe for psp and item state calls. 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 BYTE ImplicitArgs = 0; // either 0 for static, 1 for method or 3 for action
unsigned VirtualIndex = ~0u; unsigned VirtualIndex = ~0u;
FName Name; FName Name;

View file

@ -664,13 +664,27 @@ begin:
VMReturn returns[MAX_RETURNS]; VMReturn returns[MAX_RETURNS];
int numret; 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); FillReturns(reg, f, returns, pc+1, C);
if (call->Native) if (call->Native)
{ {
try try
{ {
VMCycles[0].Unclock(); 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(); VMCycles[0].Clock();
} }
catch (CVMAbortException &err) catch (CVMAbortException &err)
@ -686,7 +700,7 @@ begin:
VMCalls[0]++; VMCalls[0]++;
VMScriptFunction *script = static_cast<VMScriptFunction *>(call); VMScriptFunction *script = static_cast<VMScriptFunction *>(call);
VMFrame *newf = stack->AllocFrame(script); VMFrame *newf = stack->AllocFrame(script);
VMFillParams(reg.param + f->NumParam - B, newf, B); VMFillParams(reg.param + f->NumParam - b, newf, b);
try try
{ {
numret = Exec(stack, script->Code, returns, C); numret = Exec(stack, script->Code, returns, C);
@ -803,7 +817,11 @@ begin:
{ {
b = B; b = B;
PClass *cls = (PClass*)(pc->op == OP_NEW ? reg.a[b] : konsta[b].v); 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()); 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.a[a] = cls->CreateNew();
reg.atag[a] = ATAG_OBJECT; reg.atag[a] = ATAG_OBJECT;
NEXTOP; 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) ::= . { 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) 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) 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; } class_flags(X) ::= class_flags(A) REPLACES dottable_id(B). { X.Flags = A.Flags; X.Replaces = B; }
/*----- Dottable Identifier -----*/ /*----- Dottable Identifier -----*/
@ -326,7 +328,9 @@ struct_def(X) ::= STRUCT(T) IDENTIFIER(A) struct_flags(S) LBRACE opt_struct_body
%type struct_flags{ClassFlagsBlock} %type struct_flags{ClassFlagsBlock}
struct_flags(X) ::= . { X.Flags = 0; } 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) ::= . { X = NULL; }
opt_struct_body(X) ::= struct_body(X). 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) ::= 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) ::= 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) ::= 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) ::= . { X.Int = 0; X.SourceLoc = stat->sc->GetMessageLine(); }
func_const(X) ::= CONST(T). { X.Int = ZCC_FuncConst; X.SourceLoc = T.SourceLoc; } 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); 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()); s->strct->Symbol = new PSymbolType(s->NodeName(), s->Type());
syms->AddSymbol(s->strct->Symbol); syms->AddSymbol(s->strct->Symbol);
@ -597,11 +607,33 @@ void ZCCCompiler::CreateClassTypes()
c->cls->Type = nullptr; 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->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->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()); c->cls->Symbol = new PSymbolType(c->NodeName(), c->Type());
OutNamespace->Symbols.AddSymbol(c->cls->Symbol); 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. // For structs only allow 'deprecated', for classes exclude function qualifiers.
int notallowed = forstruct? 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_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_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) 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_Deprecated) varflags |= VARF_Deprecated;
if (field->Flags & ZCC_ReadOnly) varflags |= VARF_ReadOnly; if (field->Flags & ZCC_ReadOnly) varflags |= VARF_ReadOnly;
if (field->Flags & ZCC_Transient) varflags |= VARF_Transient; 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) if (field->Flags & ZCC_Native)
{ {
varflags |= VARF_Native | VARF_Transient; 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) if (field->Flags & ZCC_Meta)
{ {
varflags |= VARF_Meta | VARF_Static | VARF_ReadOnly; // metadata implies readonly 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) 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 else
{ {
@ -1218,7 +1277,7 @@ bool ZCCCompiler::CompileProperties(PClass *type, TArray<ZCC_Property *> &Proper
FString ZCCCompiler::FlagsToString(uint32_t flags) 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; FString build;
for (size_t i = 0; i < countof(flagnames); i++) 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); } 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) 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_Virtual) varflags |= VARF_Virtual;
if (f->Flags & ZCC_Override) varflags |= VARF_Override; if (f->Flags & ZCC_Override) varflags |= VARF_Override;
if (f->Flags & ZCC_VarArg) varflags |= VARF_VarArg; 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)) if ((f->Flags & ZCC_VarArg) && !(f->Flags & ZCC_Native))
{ {
Error(f, "'VarArg' can only be used with native methods"); Error(f, "'VarArg' can only be used with native methods");
} }
if (f->Flags & ZCC_Action) 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. // Non-Actors cannot have action functions.
if (!c->Type()->IsKindOf(RUNTIME_CLASS(PClassActor))) 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 (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'. if (varflags & VARF_Override) varflags &= ~VARF_Virtual; // allow 'virtual override'.
// Only one of these flags may be used. // Only one of these flags may be used.
static int exclude[] = { ZCC_Virtual, ZCC_Override, ZCC_Action, ZCC_Static }; 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; int fc = 0;
FString build; for (size_t i = 0; i < countof(exclude); i++)
for (int i = 0; i < 4; i++)
{ {
if (f->Flags & exclude[i]) if (f->Flags & exclude[i])
{ {
fc++; fc++;
if (build.Len() > 0) build += ", "; excludeflags |= exclude[i];
build += print[i];
} }
} }
if (fc > 1) 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; varflags |= VARF_Method;
} }
if (varflags & VARF_Override) varflags |= VARF_Virtual; // Now that the flags are checked, make all override functions virtual as well. 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) if (f->Flags & ZCC_Native)
{ {
varflags |= VARF_Native; varflags |= VARF_Native;
afd = FindFunction(c->Type(), FName(f->Name).GetChars()); afd = FindFunction(c->Type(), FName(f->Name).GetChars());
if (afd == nullptr) 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 else
{ {
@ -2232,7 +2360,7 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool
} }
else if (hasoptionals) 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. // TBD: disallow certain types? For now, let everything pass that isn't an array.
args.Push(type); 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); 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()); PClass *clstype = static_cast<PClass *>(c->Type());
if (varflags & VARF_Virtual) if (varflags & VARF_Virtual)
{ {
if (sym->Variants[0].Implementation == nullptr) 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; return;
} }
if (varflags & VARF_Final) if (varflags & VARF_Final)
{
sym->Variants[0].Implementation->Final = true; sym->Variants[0].Implementation->Final = true;
} if (varflags & VARF_ReadOnly)
sym->Variants[0].Implementation->FuncConst = true;
if (forclass) if (forclass)
{ {
int vindex = clstype->FindVirtualIndex(sym->SymbolName, sym->Variants[0].Proto); 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()); 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; clstype->Virtuals[vindex] = sym->Variants[0].Implementation;
sym->Variants[0].Implementation->VirtualIndex = vindex; sym->Variants[0].Implementation->VirtualIndex = vindex;
} }

View file

@ -137,6 +137,10 @@ static void InitTokenMap()
TOKENDEF (TK_Latent, ZCC_LATENT); TOKENDEF (TK_Latent, ZCC_LATENT);
TOKENDEF (TK_Virtual, ZCC_VIRTUAL); TOKENDEF (TK_Virtual, ZCC_VIRTUAL);
TOKENDEF (TK_VarArg, ZCC_VARARG); 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_Override, ZCC_OVERRIDE);
TOKENDEF (TK_Final, ZCC_FINAL); TOKENDEF (TK_Final, ZCC_FINAL);
TOKENDEF (TK_Meta, ZCC_META); TOKENDEF (TK_Meta, ZCC_META);

View file

@ -36,7 +36,11 @@ enum
ZCC_Virtual = 1 << 13, ZCC_Virtual = 1 << 13,
ZCC_Override = 1 << 14, ZCC_Override = 1 << 14,
ZCC_Transient = 1 << 15, 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 // 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); } 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 #endif

View file

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

View file

@ -331,10 +331,10 @@ class Object native
native static uint MSTime(); native static uint MSTime();
native Name GetClassName(); 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. // 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 class BrokenLines : Object native
@ -344,7 +344,7 @@ class BrokenLines : Object native
native String StringAt(int line); native String StringAt(int line);
} }
class Thinker : Object native class Thinker : Object native play
{ {
enum EStatnums enum EStatnums
{ {
@ -386,7 +386,6 @@ class Thinker : Object native
class ThinkerIterator : Object native class ThinkerIterator : Object native
{ {
native static ThinkerIterator Create(class<Object> type = "Actor", int statnum=Thinker.MAX_STATNUM+1); native static ThinkerIterator Create(class<Object> type = "Actor", int statnum=Thinker.MAX_STATNUM+1);
native Thinker Next(bool exact = false); native Thinker Next(bool exact = false);
native void Reinit(); 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 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 Vector3 ViewPos;
native readonly double ViewAngle; native readonly double ViewAngle;
@ -10,7 +10,7 @@ class RenderEvent : BaseEvent native
native readonly Actor Camera; native readonly Actor Camera;
} }
class WorldEvent : BaseEvent native class WorldEvent : BaseEvent native play
{ {
// for loaded/unloaded // for loaded/unloaded
native readonly bool IsSaveGame; native readonly bool IsSaveGame;
@ -28,7 +28,7 @@ class WorldEvent : BaseEvent native
native readonly double DamageAngle; native readonly double DamageAngle;
} }
class PlayerEvent : BaseEvent native class PlayerEvent : BaseEvent native play
{ {
// this is the player number that caused the event. // this is the player number that caused the event.
// note: you can get player struct from this by using players[e.PlayerNumber] // 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; native readonly bool IsReturn;
} }
class UiEvent : BaseEvent native class UiEvent : BaseEvent native ui
{ {
// d_gui.h // d_gui.h
enum EGUIEvent enum EGUIEvent
@ -121,7 +121,7 @@ class UiEvent : BaseEvent native
native readonly bool IsAlt; native readonly bool IsAlt;
} }
class InputEvent : BaseEvent native class InputEvent : BaseEvent native play
{ {
enum EGenericEvent enum EGenericEvent
{ {
@ -279,7 +279,7 @@ class ConsoleEvent : BaseEvent native
native readonly int Args[3]; native readonly int Args[3];
} }
class StaticEventHandler : Object native class StaticEventHandler : Object native play
{ {
// static event handlers CAN register other static event handlers. // static event handlers CAN register other static event handlers.
// unlike EventHandler.Create that will not create them. // 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 WorldTick(WorldEvent e);
// //
virtual native void RenderFrame(RenderEvent e); virtual native ui void RenderFrame(RenderEvent e);
virtual native void RenderOverlay(RenderEvent e); virtual native ui void RenderOverlay(RenderEvent e);
// //
virtual native void PlayerEntered(PlayerEvent e); virtual native void PlayerEntered(PlayerEvent e);
@ -317,11 +317,12 @@ class StaticEventHandler : Object native
virtual native void PlayerDisconnected(PlayerEvent e); 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 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 value will be queried on Register() to decide the relative order of this handler to every other.
// this is most useful in UI systems. // this is most useful in UI systems.
@ -342,4 +343,6 @@ class EventHandler : StaticEventHandler native
static native bool Register(StaticEventHandler handler); static native bool Register(StaticEventHandler handler);
static native bool Unregister(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 bool Use (bool pickup) { return false; }
virtual double GetSpeedFactor() { return 1; } virtual double GetSpeedFactor() { return 1; }
virtual bool GetNoTeleportFreeze() { return false; } 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 void OwnerDied() {}
virtual Color GetBlend () { return 0; } 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); return (EffectTics <= BLINKTHRESHOLD && (EffectTics & 8) && !bNoScreenBlink);
} }
@ -934,7 +934,7 @@ class PowerFlight : Powerup
+INVENTORY.HUBPOWER +INVENTORY.HUBPOWER
} }
bool HitCenterFrame; ui bool HitCenterFrame;
//=========================================================================== //===========================================================================
// //

View file

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

View file

@ -48,7 +48,7 @@ struct JoystickConfig native
} }
class Menu : Object native class Menu : Object native ui
{ {
enum EMenuKey enum EMenuKey
{ {
@ -287,7 +287,7 @@ class Menu : Object native
} }
class MenuDescriptor : Object native class MenuDescriptor : Object native ui
{ {
native Name mMenuName; native Name mMenuName;
native String mNetgameMessage; 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 double mXpos, mYpos;
protected native Name mAction; protected native Name mAction;

View file

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