diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b7f85541c..950cfe9c0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1232,6 +1232,7 @@ set (PCH_SOURCES scripting/thingdef_data.cpp scripting/thingdef_properties.cpp scripting/backend/codegen.cpp + scripting/backend/scopebarrier.cpp scripting/backend/dynarrays.cpp scripting/backend/vmbuilder.cpp scripting/backend/vmdisasm.cpp diff --git a/src/dobject.h b/src/dobject.h index d99fbee2b..cc6672cdf 100644 --- a/src/dobject.h +++ b/src/dobject.h @@ -207,7 +207,9 @@ enum EObjectFlags OF_Transient = 1 << 11, // Object should not be archived (references to it will be nulled on disk) OF_Spawned = 1 << 12, // Thinker was spawned at all (some thinkers get deleted before spawning) OF_Released = 1 << 13, // Object was released from the GC system and should not be processed by GC function - OF_Abstract = 1 << 14, // Marks a class that cannot be created with CreateNew + OF_Abstract = 1 << 14, // Marks a class that cannot be created with new() function at all + OF_UI = 1 << 15, // Marks a class that defaults to VARF_UI for it's fields/methods + OF_Play = 1 << 16, // Marks a class that defaults to VARF_Play for it's fields/methods }; template class TObjPtr; diff --git a/src/dobjtype.h b/src/dobjtype.h index 1a30b42ed..1d134bd07 100644 --- a/src/dobjtype.h +++ b/src/dobjtype.h @@ -37,6 +37,10 @@ enum VARF_Transient = (1<<17), // don't auto serialize field. VARF_Meta = (1<<18), // static class data (by necessity read only.) VARF_VarArg = (1<<19), // [ZZ] vararg: don't typecheck values after ... in function signature + VARF_UI = (1<<20), // [ZZ] ui: object is ui-scope only (can't modify playsim) + VARF_Play = (1<<21), // [ZZ] play: object is playsim-scope only (can't access ui) + VARF_VirtualScope = (1<<22), // [ZZ] virtualscope: object should use the scope of the particular class it's being used with (methods only) + VARF_ClearScope = (1<<23), // [ZZ] clearscope: this method ignores the member access chain that leads to it and is always plain data. }; // An action function ------------------------------------------------------- diff --git a/src/events.cpp b/src/events.cpp index a0780fb92..9946a1059 100755 --- a/src/events.cpp +++ b/src/events.cpp @@ -117,6 +117,21 @@ bool E_UnregisterHandler(DStaticEventHandler* handler) return true; } +bool E_SendNetworkEvent(FString name, int arg1, int arg2, int arg3) +{ + if (gamestate != GS_LEVEL) + return false; + + Net_WriteByte(DEM_NETEVENT); + Net_WriteString(name); + Net_WriteByte(3); + Net_WriteLong(arg1); + Net_WriteLong(arg2); + Net_WriteLong(arg3); + + return true; +} + bool E_CheckHandler(DStaticEventHandler* handler) { for (DStaticEventHandler* lhandler = E_FirstEventHandler; lhandler; lhandler = lhandler->next) @@ -521,6 +536,18 @@ DEFINE_ACTION_FUNCTION(DStaticEventHandler, SetOrder) return 0; } +DEFINE_ACTION_FUNCTION(DEventHandler, SendNetworkEvent) +{ + PARAM_PROLOGUE; + PARAM_STRING(name); + PARAM_INT(arg1); + PARAM_INT(arg2); + PARAM_INT(arg3); + // + + ACTION_RETURN_BOOL(E_SendNetworkEvent(name, arg1, arg2, arg3)); +} + DEFINE_ACTION_FUNCTION(DEventHandler, Create) { PARAM_PROLOGUE; @@ -658,6 +685,7 @@ DEFINE_EMPTY_HANDLER(DStaticEventHandler, UiProcess); DEFINE_EMPTY_HANDLER(DStaticEventHandler, InputProcess); DEFINE_EMPTY_HANDLER(DStaticEventHandler, ConsoleProcess); +DEFINE_EMPTY_HANDLER(DStaticEventHandler, NetworkProcess); // =========================================== // @@ -1068,22 +1096,45 @@ static DConsoleEvent* E_SetupConsoleEvent() void DStaticEventHandler::ConsoleProcess(int player, FString name, int arg1, int arg2, int arg3) { - IFVIRTUAL(DStaticEventHandler, ConsoleProcess) + if (player < 0) { - // don't create excessive DObjects if not going to be processed anyway - if (func == DStaticEventHandler_ConsoleProcess_VMPtr) - return; - DConsoleEvent* e = E_SetupConsoleEvent(); + IFVIRTUAL(DStaticEventHandler, ConsoleProcess) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_ConsoleProcess_VMPtr) + return; + DConsoleEvent* e = E_SetupConsoleEvent(); - // - e->Player = player; - e->Name = name; - e->Args[0] = arg1; - e->Args[1] = arg2; - e->Args[2] = arg3; + // + e->Player = player; + e->Name = name; + e->Args[0] = arg1; + e->Args[1] = arg2; + e->Args[2] = arg3; - VMValue params[2] = { (DStaticEventHandler*)this, e }; - GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr); + VMValue params[2] = { (DStaticEventHandler*)this, e }; + GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr); + } + } + else + { + IFVIRTUAL(DStaticEventHandler, NetworkProcess) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_NetworkProcess_VMPtr) + return; + DConsoleEvent* e = E_SetupConsoleEvent(); + + // + e->Player = player; + e->Name = name; + e->Args[0] = arg1; + e->Args[1] = arg2; + e->Args[2] = arg3; + + VMValue params[2] = { (DStaticEventHandler*)this, e }; + GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr); + } } } @@ -1136,10 +1187,6 @@ CCMD(netevent) for (int i = 0; i < argn; i++) arg[i] = atoi(argv[2 + i]); // call networked - Net_WriteByte(DEM_NETEVENT); - Net_WriteString(argv[1]); - Net_WriteByte(argn); - for (int i = 0; i < 3; i++) - Net_WriteLong(arg[i]); + E_SendNetworkEvent(argv[1], arg[0], arg[1], arg[2]); } } diff --git a/src/events.h b/src/events.h index fa63e7325..b83890223 100755 --- a/src/events.h +++ b/src/events.h @@ -60,6 +60,9 @@ bool E_Responder(event_t* ev); // splits events into InputProcess and UiProcess // this executes on console/net events. void E_Console(int player, FString name, int arg1, int arg2, int arg3); +// send networked event. unified function. +bool E_SendNetworkEvent(FString name, int arg1, int arg2, int arg3); + // check if there is anything that should receive GUI events bool E_CheckUiProcessors(); // check if we need native mouse due to UiProcessors diff --git a/src/menu/menu.cpp b/src/menu/menu.cpp index 1c8e0b39b..a481dd24b 100644 --- a/src/menu/menu.cpp +++ b/src/menu/menu.cpp @@ -796,7 +796,16 @@ void M_ClearMenus() void M_Init (void) { - M_ParseMenuDefs(); + try + { + M_ParseMenuDefs(); + } + catch (CVMAbortException &err) + { + err.MaybePrintMessage(); + Printf("%s", err.stacktrace.GetChars()); + I_FatalError("Failed to initialize menus"); + } M_CreateMenus(); } diff --git a/src/p_acs.cpp b/src/p_acs.cpp index 027b614ea..8c2859479 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -4765,24 +4765,24 @@ static int ScriptCall(unsigned argc, int32_t *args) auto cls = PClass::FindClass(clsname); if (!cls) { - I_Error("ACS call to unknown class in script function %s.%s", cls, funcname); + I_Error("ACS call to unknown class in script function %s.%s", clsname, funcname); } auto funcsym = dyn_cast(cls->Symbols.FindSymbol(funcname, true)); if (funcsym == nullptr) { - I_Error("ACS call to unknown script function %s.%s", cls, funcname); + I_Error("ACS call to unknown script function %s.%s", clsname, funcname); } auto func = funcsym->Variants[0].Implementation; if (func->ImplicitArgs > 0) { - I_Error("ACS call to non-static script function %s.%s", cls, funcname); + I_Error("ACS call to non-static script function %s.%s", clsname, funcname); } TArray params; for (unsigned i = 2; i < argc; i++) { if (func->Proto->ArgumentTypes.Size() < i - 1) { - I_Error("Too many parameters in call to %s.%s", cls, funcname); + I_Error("Too many parameters in call to %s.%s", clsname, funcname); } auto argtype = func->Proto->ArgumentTypes[i - 2]; // The only types allowed are int, bool, double, Name, Sound, Color and String @@ -4812,7 +4812,7 @@ static int ScriptCall(unsigned argc, int32_t *args) } else { - I_Error("Invalid type %s in call to %s.%s", argtype->DescriptiveName(), cls, funcname); + I_Error("Invalid type %s in call to %s.%s", argtype->DescriptiveName(), clsname, funcname); } } if (func->Proto->ArgumentTypes.Size() > params.Size()) @@ -4820,7 +4820,7 @@ static int ScriptCall(unsigned argc, int32_t *args) // Check if we got enough parameters. That means we either cover the full argument list of the function or the next argument is optional. if (!(funcsym->Variants[0].ArgFlags[params.Size()] & VARF_Optional)) { - I_Error("Insufficient parameters in call to %s.%s", cls, funcname); + I_Error("Insufficient parameters in call to %s.%s", clsname, funcname); } } // The return value can be the same types as the parameter types, plus void diff --git a/src/sc_man_scanner.re b/src/sc_man_scanner.re index d4a1254d6..ba839813f 100644 --- a/src/sc_man_scanner.re +++ b/src/sc_man_scanner.re @@ -170,6 +170,10 @@ std2: 'virtual' { RET(TK_Virtual); } 'override' { RET(TK_Override); } 'vararg' { RET(TK_VarArg); } + 'ui' { RET(TK_UI); } + 'play' { RET(TK_Play); } + 'clearscope' { RET(TK_ClearScope); } + 'virtualscope' { RET(TK_VirtualScope); } 'super' { RET(TK_Super); } 'global' { RET(TK_Global); } 'stop' { RET(TK_Stop); } diff --git a/src/sc_man_tokens.h b/src/sc_man_tokens.h index 0227dffa9..320601414 100644 --- a/src/sc_man_tokens.h +++ b/src/sc_man_tokens.h @@ -112,6 +112,10 @@ xx(TK_Optional, "'optional'") xx(TK_Export, "'expert'") xx(TK_Virtual, "'virtual'") xx(TK_VarArg, "'vararg'") +xx(TK_UI, "'ui'") +xx(TK_Play, "'play'") +xx(TK_ClearScope, "'clearscope'") +xx(TK_VirtualScope, "'virtualscope'") xx(TK_Override, "'override'") xx(TK_Super, "'super'") xx(TK_Null, "'null'") diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index 8e41f44ab..eb92fe974 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -89,6 +89,37 @@ static const FLOP FxFlops[] = { NAME_TanH, FLOP_TANH, [](double v) { return g_tanh(v); } }, }; + +//========================================================================== +// +// [ZZ] Magic methods to be used in vmexec.h for runtime checking of scope +// +//========================================================================== + +// this can be imported in vmexec.h +void FScopeBarrier_ValidateNew(PClass* cls, PFunction* callingfunc) +{ + int outerside = callingfunc->Variants.Size() ? FScopeBarrier::SideFromFlags(callingfunc->Variants[0].Flags) : FScopeBarrier::Side_Virtual; + if (outerside == FScopeBarrier::Side_Virtual) + outerside = FScopeBarrier::SideFromObjectFlags(callingfunc->OwningClass->ObjectFlags); + int innerside = FScopeBarrier::SideFromObjectFlags(cls->ObjectFlags); + if ((outerside != innerside) && (innerside != FScopeBarrier::Side_PlainData)) // "cannot construct ui class ... from data context" + ThrowAbortException(X_OTHER, "Cannot construct %s class %s from %s context", FScopeBarrier::StringFromSide(innerside), cls->TypeName.GetChars(), FScopeBarrier::StringFromSide(outerside)); +} +// this can be imported in vmexec.h +void FScopeBarrier_ValidateCall(PFunction* calledfunc, PFunction* callingfunc, PClass* selftype) +{ + // [ZZ] anonymous blocks have 0 variants, so give them Side_Virtual. + int outerside = callingfunc->Variants.Size() ? FScopeBarrier::SideFromFlags(callingfunc->Variants[0].Flags) : FScopeBarrier::Side_Virtual; + if (outerside == FScopeBarrier::Side_Virtual) + outerside = FScopeBarrier::SideFromObjectFlags(callingfunc->OwningClass->ObjectFlags); + int innerside = FScopeBarrier::SideFromFlags(calledfunc->Variants[0].Flags); + if (innerside == FScopeBarrier::Side_Virtual) + innerside = FScopeBarrier::SideFromObjectFlags(selftype->ObjectFlags); + if ((outerside != innerside) && (innerside != FScopeBarrier::Side_PlainData)) + ThrowAbortException(X_OTHER, "Cannot call %s function %s from %s context", FScopeBarrier::StringFromSide(innerside), calledfunc->SymbolName.GetChars(), FScopeBarrier::StringFromSide(outerside)); +} + //========================================================================== // // FCompileContext @@ -167,7 +198,8 @@ void FCompileContext::CheckReturn(PPrototype *proto, FScriptPosition &pos) } } -bool FCompileContext::CheckReadOnly(int flags) +// [ZZ] I find it really dumb that something called CheckReadOnly returns false for readonly. renamed. +bool FCompileContext::CheckWritable(int flags) { if (!(flags & VARF_ReadOnly)) return false; if (!(flags & VARF_InternalAccess)) return true; @@ -5023,6 +5055,7 @@ FxNew::FxNew(FxExpression *v) { val = new FxClassTypeCast(NewClassPointer(RUNTIME_CLASS(DObject)), v, false); ValueType = NewPointer(RUNTIME_CLASS(DObject)); + CallingFunction = nullptr; } //========================================================================== @@ -5047,6 +5080,7 @@ FxExpression *FxNew::Resolve(FCompileContext &ctx) CHECKRESOLVED(); SAFE_RESOLVE(val, ctx); + CallingFunction = ctx.Function; if (!val->ValueType->IsKindOf(RUNTIME_CLASS(PClassPointer))) { ScriptPosition.Message(MSG_ERROR, "Class type expected"); @@ -5056,8 +5090,28 @@ FxExpression *FxNew::Resolve(FCompileContext &ctx) if (val->isConstant()) { auto cls = static_cast(static_cast(val)->GetValue().GetPointer()); + if (cls->ObjectFlags & OF_Abstract) + { + ScriptPosition.Message(MSG_ERROR, "Cannot instantiate abstract class %s", cls->TypeName.GetChars()); + delete this; + return nullptr; + } + + // + int outerside = ctx.Function && ctx.Function->Variants.Size() ? FScopeBarrier::SideFromFlags(ctx.Function->Variants[0].Flags) : FScopeBarrier::Side_Virtual; + if (outerside == FScopeBarrier::Side_Virtual) + outerside = FScopeBarrier::SideFromObjectFlags(ctx.Class->ObjectFlags); + int innerside = FScopeBarrier::SideFromObjectFlags(cls->ObjectFlags); + if ((outerside != innerside) && (innerside != FScopeBarrier::Side_PlainData)) // "cannot construct ui class ... from data context" + { + ScriptPosition.Message(MSG_ERROR, "Cannot construct %s class %s from %s context", FScopeBarrier::StringFromSide(innerside), cls->TypeName.GetChars(), FScopeBarrier::StringFromSide(outerside)); + delete this; + return nullptr; + } + ValueType = NewPointer(cls); } + return this; } @@ -5072,7 +5126,7 @@ ExpEmit FxNew::Emit(VMFunctionBuilder *build) ExpEmit from = val->Emit(build); from.Free(build); ExpEmit to(build, REGT_POINTER); - build->Emit(from.Konst ? OP_NEW_K : OP_NEW, to.RegNum, from.RegNum); + build->Emit(from.Konst ? OP_NEW_K : OP_NEW, to.RegNum, from.RegNum, build->GetConstantAddress(CallingFunction, ATAG_OBJECT)); return to; } @@ -6245,7 +6299,7 @@ FxExpression *FxLocalVariable::Resolve(FCompileContext &ctx) bool FxLocalVariable::RequestAddress(FCompileContext &ctx, bool *writable) { AddressRequested = true; - if (writable != nullptr) *writable = !ctx.CheckReadOnly(Variable->VarFlags); + if (writable != nullptr) *writable = !ctx.CheckWritable(Variable->VarFlags); return true; } @@ -6463,7 +6517,7 @@ FxGlobalVariable::FxGlobalVariable(PField* mem, const FScriptPosition &pos) bool FxGlobalVariable::RequestAddress(FCompileContext &ctx, bool *writable) { AddressRequested = true; - if (writable != nullptr) *writable = AddressWritable && !ctx.CheckReadOnly(membervar->Flags); + if (writable != nullptr) *writable = AddressWritable && !ctx.CheckWritable(membervar->Flags); return true; } @@ -6653,7 +6707,7 @@ FxStackVariable::~FxStackVariable() bool FxStackVariable::RequestAddress(FCompileContext &ctx, bool *writable) { AddressRequested = true; - if (writable != nullptr) *writable = AddressWritable && !ctx.CheckReadOnly(membervar->Flags); + if (writable != nullptr) *writable = AddressWritable && !ctx.CheckWritable(membervar->Flags); return true; } @@ -6751,8 +6805,34 @@ bool FxStructMember::RequestAddress(FCompileContext &ctx, bool *writable) return false; } AddressRequested = true; - if (writable != nullptr) *writable = (AddressWritable && !ctx.CheckReadOnly(membervar->Flags) && - (!classx->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)) || !static_cast(classx->ValueType)->IsConst)); + if (writable != nullptr) + { + // [ZZ] original check. + bool bWritable = (AddressWritable && !ctx.CheckWritable(membervar->Flags) && + (!classx->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)) || !static_cast(classx->ValueType)->IsConst)); + // [ZZ] self in a const function is not writable. + if (bWritable) // don't do complex checks on early fail + { + if ((classx->ExprType == EFX_Self) && (ctx.Function && (ctx.Function->Variants[0].Flags & VARF_ReadOnly))) + bWritable = false; + } + // [ZZ] implement write barrier between different scopes + if (bWritable) + { + int outerflags = 0; + if (ctx.Function) + { + outerflags = ctx.Function->Variants[0].Flags; + if (((outerflags & (VARF_VirtualScope | VARF_Virtual)) == (VARF_VirtualScope | VARF_Virtual)) && ctx.Class) + outerflags = FScopeBarrier::FlagsFromSide(FScopeBarrier::SideFromObjectFlags(ctx.Class->ObjectFlags)); + } + FScopeBarrier scopeBarrier(outerflags, FScopeBarrier::FlagsFromSide(BarrierSide), membervar->SymbolName.GetChars()); + if (!scopeBarrier.writable) + bWritable = false; + } + + *writable = bWritable; + } return true; } @@ -6772,7 +6852,7 @@ FxExpression *FxStructMember::Resolve(FCompileContext &ctx) if (!classx->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)) || !static_cast(classx->ValueType)->PointedType->IsKindOf(RUNTIME_CLASS(AActor))) { - ScriptPosition.Message(MSG_ERROR, "'Default' requires an actor type."); + ScriptPosition.Message(MSG_ERROR, "'Default' requires an actor type"); delete this; return nullptr; } @@ -6782,12 +6862,36 @@ FxExpression *FxStructMember::Resolve(FCompileContext &ctx) return x->Resolve(ctx); } + // [ZZ] support magic + int outerflags = 0; + if (ctx.Function) + { + outerflags = ctx.Function->Variants[0].Flags; + if (((outerflags & (VARF_VirtualScope | VARF_Virtual)) == (VARF_VirtualScope | VARF_Virtual)) && ctx.Class) + outerflags = FScopeBarrier::FlagsFromSide(FScopeBarrier::SideFromObjectFlags(ctx.Class->ObjectFlags)); + } + FScopeBarrier scopeBarrier(outerflags, membervar->Flags, membervar->SymbolName.GetChars()); + if (!scopeBarrier.readable) + { + ScriptPosition.Message(MSG_ERROR, "%s", scopeBarrier.readerror.GetChars()); + delete this; + return nullptr; + } + + BarrierSide = scopeBarrier.sidelast; + if (classx->ExprType == EFX_StructMember && ExprType == EFX_StructMember) // note: only do this for structs now + { + FxStructMember* pmember = (FxStructMember*)classx; + if (BarrierSide == FScopeBarrier::Side_PlainData && pmember) + BarrierSide = pmember->BarrierSide; + } + if (classx->ValueType->IsKindOf(RUNTIME_CLASS(PPointer))) { PPointer *ptrtype = dyn_cast(classx->ValueType); if (ptrtype == nullptr || !ptrtype->PointedType->IsKindOf(RUNTIME_CLASS(PStruct))) { - ScriptPosition.Message(MSG_ERROR, "Member variable requires a struct or class object."); + ScriptPosition.Message(MSG_ERROR, "Member variable requires a struct or class object"); delete this; return nullptr; } @@ -6799,7 +6903,8 @@ FxExpression *FxStructMember::Resolve(FCompileContext &ctx) { auto parentfield = static_cast(classx)->membervar; // PFields are garbage collected so this will be automatically taken care of later. - auto newfield = new PField(NAME_None, membervar->Type, membervar->Flags | parentfield->Flags, membervar->Offset + parentfield->Offset); + // [ZZ] call ChangeSideInFlags to ensure that we don't get ui+play + auto newfield = new PField(NAME_None, membervar->Type, FScopeBarrier::ChangeSideInFlags(membervar->Flags | parentfield->Flags, BarrierSide), membervar->Offset + parentfield->Offset); newfield->BitValue = membervar->BitValue; static_cast(classx)->membervar = newfield; classx->isresolved = false; // re-resolve the parent so it can also check if it can be optimized away. @@ -6841,7 +6946,7 @@ FxExpression *FxStructMember::Resolve(FCompileContext &ctx) { if (!(classx->RequestAddress(ctx, &AddressWritable))) { - ScriptPosition.Message(MSG_ERROR, "unable to dereference left side of %s", membervar->SymbolName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Unable to dereference left side of %s", membervar->SymbolName.GetChars()); delete this; return nullptr; } @@ -7371,6 +7476,32 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx) return nullptr; } + // [ZZ] validate call + PClass* cls = (PClass*)ctx.Class; + int outerflags = 0; + if (ctx.Function) + { + outerflags = ctx.Function->Variants[0].Flags; + if (((outerflags & (VARF_VirtualScope | VARF_Virtual)) == (VARF_VirtualScope | VARF_Virtual)) && ctx.Class) + outerflags = FScopeBarrier::FlagsFromSide(FScopeBarrier::SideFromObjectFlags(ctx.Class->ObjectFlags)); + } + int innerflags = afd->Variants[0].Flags; + int innerside = FScopeBarrier::SideFromFlags(innerflags); + // [ZZ] check this at compile time. this would work for most legit cases. + if (innerside == FScopeBarrier::Side_Virtual) + { + innerside = FScopeBarrier::SideFromObjectFlags(cls->ObjectFlags); + innerflags = FScopeBarrier::FlagsFromSide(innerside); + } + FScopeBarrier scopeBarrier(outerflags, innerflags, MethodName.GetChars()); + if (!scopeBarrier.callable) + { + ScriptPosition.Message(MSG_ERROR, "%s", scopeBarrier.callerror.GetChars()); + delete this; + return nullptr; + } + + // [ZZ] this is only checked for VARF_Methods in the other place. bug? if (!CheckFunctionCompatiblity(ScriptPosition, ctx.Function, afd)) { delete this; @@ -7593,8 +7724,18 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx) break; case NAME_New: - if (CheckArgSize(MethodName, ArgList, 1, 1, ScriptPosition)) + if (CheckArgSize(MethodName, ArgList, 0, 1, ScriptPosition)) { + // [ZZ] allow implicit new() call to mean "create current class instance" + if (!ArgList.Size() && !ctx.Class->IsKindOf(RUNTIME_CLASS(PClass))) + { + ScriptPosition.Message(MSG_ERROR, "Cannot use implicit new() in a struct"); + delete this; + return nullptr; + } + else if (!ArgList.Size()) + ArgList.Push(new FxConstant((PClass*)ctx.Class, NewClassPointer((PClass*)ctx.Class), ScriptPosition)); + func = new FxNew(ArgList[0]); ArgList[0] = nullptr; } @@ -7651,13 +7792,14 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) PStruct *cls; bool staticonly = false; bool novirtual = false; + bool isreadonly = false; PStruct *ccls = nullptr; if (ctx.Class == nullptr) { // There's no way that a member function call can resolve to a constant so abort right away. - ScriptPosition.Message(MSG_ERROR, "Expression is not constant."); + ScriptPosition.Message(MSG_ERROR, "Expression is not constant"); delete this; return nullptr; } @@ -7666,7 +7808,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) { if (a == nullptr) { - ScriptPosition.Message(MSG_ERROR, "Empty function argument."); + ScriptPosition.Message(MSG_ERROR, "Empty function argument"); delete this; return nullptr; } @@ -7677,6 +7819,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) auto id = static_cast(Self)->Identifier; // If the left side is a class name for a static member function call it needs to be resolved manually // because the resulting value type would cause problems in nearly every other place where identifiers are being used. + // [ZZ] substitute ccls for String internal type. if (id == NAME_String) ccls = TypeStringStruct; else ccls = FindStructType(id, ctx); if (ccls != nullptr) static_cast(Self)->noglobal = true; @@ -7688,7 +7831,6 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) { if (ccls != nullptr) { - // [ZZ] substitute ccls for String internal type. if (!ccls->IsKindOf(RUNTIME_CLASS(PClass)) || static_cast(ccls)->bExported) { cls = ccls; @@ -7717,6 +7859,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) Self = new FxSelf(ScriptPosition); Self->ValueType = NewPointer(cls); } + else novirtual = false; } } } @@ -7757,7 +7900,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) { if (ArgList.Size() > 0) { - ScriptPosition.Message(MSG_ERROR, "too many parameters in call to %s", MethodName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Too many parameters in call to %s", MethodName.GetChars()); delete this; return nullptr; } @@ -7801,7 +7944,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) { if (ArgList.Size() > 0) { - ScriptPosition.Message(MSG_ERROR, "too many parameters in call to %s", MethodName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Too many parameters in call to %s", MethodName.GetChars()); delete this; return nullptr; } @@ -7895,7 +8038,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) { if (ArgList.Size() > 0) { - ScriptPosition.Message(MSG_ERROR, "too many parameters in call to %s", MethodName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Too many parameters in call to %s", MethodName.GetChars()); delete this; return nullptr; } @@ -7945,7 +8088,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) auto x = new FxGetParentClass(Self); return x->Resolve(ctx); } - + if (Self->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)) && !Self->ValueType->IsKindOf(RUNTIME_CLASS(PClassPointer))) { auto ptype = static_cast(Self->ValueType)->PointedType; @@ -7955,7 +8098,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) { if (ArgList.Size() > 0) { - ScriptPosition.Message(MSG_ERROR, "too many parameters in call to %s", MethodName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Too many parameters in call to %s", MethodName.GetChars()); delete this; return nullptr; } @@ -7968,7 +8111,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) } else { - ScriptPosition.Message(MSG_ERROR, "Left hand side of %s must point to a class object\n", MethodName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Left hand side of %s must point to a class object", MethodName.GetChars()); delete this; return nullptr; } @@ -7976,22 +8119,15 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) else if (Self->ValueType->IsKindOf(RUNTIME_CLASS(PStruct))) { bool writable; - if (Self->RequestAddress(ctx, &writable) && writable) - { - cls = static_cast(Self->ValueType); - Self->ValueType = NewPointer(Self->ValueType); - } - else - { - // Cannot be made writable so we cannot use its methods. - ScriptPosition.Message(MSG_ERROR, "Invalid expression on left hand side of %s\n", MethodName.GetChars()); - delete this; - return nullptr; - } + + // [ZZ] allow const method to be called on a readonly struct + isreadonly = !(Self->RequestAddress(ctx, &writable) && writable); + cls = static_cast(Self->ValueType); + Self->ValueType = NewPointer(Self->ValueType); } else { - ScriptPosition.Message(MSG_ERROR, "Invalid expression on left hand side of %s\n", MethodName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Invalid expression on left hand side of %s", MethodName.GetChars()); delete this; return nullptr; } @@ -8009,7 +8145,50 @@ isresolved: if (afd == nullptr) { - ScriptPosition.Message(MSG_ERROR, "Unknown function %s\n", MethodName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Unknown function %s", MethodName.GetChars()); + delete this; + return nullptr; + } + + if (isreadonly && !(afd->Variants[0].Flags & VARF_ReadOnly)) + { + // Cannot be made writable so we cannot use its methods. + // [ZZ] Why this esoteric message? + ScriptPosition.Message(MSG_ERROR, "Readonly struct on left hand side of %s not allowed", MethodName.GetChars()); + delete this; + return nullptr; + } + + // [ZZ] if self is a struct or a class member, check if it's valid to call this function at all. + // implement more magic + int outerflags = 0; + if (ctx.Function) + { + outerflags = ctx.Function->Variants[0].Flags; + if (((outerflags & (VARF_VirtualScope | VARF_Virtual)) == (VARF_VirtualScope | VARF_Virtual)) && ctx.Class) + outerflags = FScopeBarrier::FlagsFromSide(FScopeBarrier::SideFromObjectFlags(ctx.Class->ObjectFlags)); + } + int innerflags = afd->Variants[0].Flags; + int innerside = FScopeBarrier::SideFromFlags(innerflags); + // [ZZ] check this at compile time. this would work for most legit cases. + if (innerside == FScopeBarrier::Side_Virtual) + { + innerside = FScopeBarrier::SideFromObjectFlags(cls->ObjectFlags); + innerflags = FScopeBarrier::FlagsFromSide(innerside); + } + else if (innerside != FScopeBarrier::Side_Clear) + { + if (Self->ExprType == EFX_StructMember) + { + FxStructMember* pmember = (FxStructMember*)Self; + if (innerside == FScopeBarrier::Side_PlainData) + innerflags = FScopeBarrier::ChangeSideInFlags(innerflags, pmember->BarrierSide); + } + } + FScopeBarrier scopeBarrier(outerflags, innerflags, MethodName.GetChars()); + if (!scopeBarrier.callable) + { + ScriptPosition.Message(MSG_ERROR, "%s", scopeBarrier.callerror.GetChars()); delete this; return nullptr; } @@ -8022,14 +8201,14 @@ isresolved: auto ccls = dyn_cast(cls); if (clstype == nullptr || ccls == nullptr || !clstype->IsDescendantOf(ccls)) { - ScriptPosition.Message(MSG_ERROR, "Cannot call non-static function %s::%s from here\n", cls->TypeName.GetChars(), MethodName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Cannot call non-static function %s::%s from here", cls->TypeName.GetChars(), MethodName.GetChars()); delete this; return nullptr; } else { // Todo: If this is a qualified call to a parent class function, let it through (but this needs to disable virtual calls later.) - ScriptPosition.Message(MSG_ERROR, "Qualified member call to parent class %s::%s is not yet implemented\n", cls->TypeName.GetChars(), MethodName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Qualified member call to parent class %s::%s is not yet implemented", cls->TypeName.GetChars(), MethodName.GetChars()); delete this; return nullptr; } @@ -8057,7 +8236,7 @@ isresolved: // Functions with no Actor usage may not be called through a pointer because they will lose their context. if (!(afd->Variants[0].UseFlags & SUF_ACTOR)) { - ScriptPosition.Message(MSG_ERROR, "Function %s cannot be used with a non-self object\n", afd->SymbolName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Function %s cannot be used with a non-self object", afd->SymbolName.GetChars()); delete this; return nullptr; } @@ -8264,6 +8443,7 @@ FxVMFunctionCall::FxVMFunctionCall(FxExpression *self, PFunction *func, FArgumen ArgList = std::move(args); EmitTail = false; NoVirtual = novirtual; + CallingFunction = nullptr; } //========================================================================== @@ -8337,6 +8517,7 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx) return nullptr; } + CallingFunction = ctx.Function; if (ArgList.Size() > 0) { bool foundvarargs = false; @@ -8544,6 +8725,17 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build) ExpEmit selfemit; if (Function->Variants[0].Flags & VARF_Method) { +#if 0 + // [ZZ] + if (Function->Variants[0].Implementation && Function->Variants[0].Implementation->BarrierSide == FScopeBarrier::Side_Virtual) + { + // pass this even before Self, because otherwise we can't silently advance the arguments. + // this is not even implicit arguments. + build->Emit(OP_PARAM, 0, REGT_POINTER | REGT_KONST, build->GetConstantAddress(Function, ATAG_OBJECT)); + build->Emit(OP_PARAM, 0, REGT_POINTER | REGT_KONST, build->GetConstantAddress(CallingFunction, ATAG_OBJECT)); + count += 2; + } +#endif assert(Self != nullptr); selfemit = Self->Emit(build); assert((selfemit.RegType == REGT_POINTER) || (selfemit.Fixed && selfemit.Target)); diff --git a/src/scripting/backend/codegen.h b/src/scripting/backend/codegen.h index abbcbd54e..000688341 100644 --- a/src/scripting/backend/codegen.h +++ b/src/scripting/backend/codegen.h @@ -45,6 +45,7 @@ #include "s_sound.h" #include "actor.h" #include "vmbuilder.h" +#include "scopebarrier.h" #define CHECKRESOLVED() if (isresolved) return this; isresolved=true; @@ -95,7 +96,7 @@ struct FCompileContext void HandleJumps(int token, FxExpression *handler); void CheckReturn(PPrototype *proto, FScriptPosition &pos); - bool CheckReadOnly(int flags); + bool CheckWritable(int flags); FxLocalVariableDeclaration *FindLocalVariable(FName name); }; @@ -1208,6 +1209,7 @@ private: class FxNew : public FxExpression { FxExpression *val; + PFunction *CallingFunction; public: @@ -1326,6 +1328,7 @@ public: PField *membervar; bool AddressRequested = false; bool AddressWritable = true; + int BarrierSide = -1; // [ZZ] some magic FxMemberBase(EFxType type, PField *f, const FScriptPosition &p); }; @@ -1707,6 +1710,7 @@ class FxVMFunctionCall : public FxExpression // for multi assignment int AssignCount = 0; TArray ReturnRegs; + PFunction *CallingFunction; public: FxVMFunctionCall(FxExpression *self, PFunction *func, FArgumentList &args, const FScriptPosition &pos, bool novirtual); diff --git a/src/scripting/backend/scopebarrier.cpp b/src/scripting/backend/scopebarrier.cpp new file mode 100644 index 000000000..4d9d6d2fc --- /dev/null +++ b/src/scripting/backend/scopebarrier.cpp @@ -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)); + } +} diff --git a/src/scripting/backend/scopebarrier.h b/src/scripting/backend/scopebarrier.h new file mode 100644 index 000000000..ed4f25a10 --- /dev/null +++ b/src/scripting/backend/scopebarrier.h @@ -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); +}; + diff --git a/src/scripting/thingdef.cpp b/src/scripting/thingdef.cpp index d0b7df449..c0e444893 100644 --- a/src/scripting/thingdef.cpp +++ b/src/scripting/thingdef.cpp @@ -110,12 +110,13 @@ void SetImplicitArgs(TArray *args, TArray *argflags, TArrayPush(NewPointer(cls)); + if (args != nullptr) args->Push(NewPointer(cls, !!(funcflags & VARF_ReadOnly))); if (argflags != nullptr) argflags->Push(VARF_Implicit | VARF_ReadOnly); if (argnames != nullptr) argnames->Push(NAME_self); } if (funcflags & VARF_Action) { + assert(!(funcflags & VARF_ReadOnly)); // implied caller and callingstate pointers if (args != nullptr) { @@ -163,6 +164,10 @@ PFunction *CreateAnonymousFunction(PClass *containingclass, PType *returntype, i // Functions that only get flagged for actors do not need the additional two context parameters. int fflags = (flags& (SUF_OVERLAY | SUF_WEAPON | SUF_ITEM)) ? VARF_Action | VARF_Method : VARF_Method; + // [ZZ] give anonymous functions the scope of their class + // (just give them VARF_Play, whatever) + fflags |= VARF_Play; + rets[0] = returntype != nullptr? returntype : TypeError; // Use TypeError as placeholder if we do not know the return type yet. SetImplicitArgs(&args, &argflags, &argnames, containingclass, fflags, flags); @@ -194,7 +199,7 @@ PFunction *FindClassMemberFunction(PStruct *selfcls, PStruct *funccls, FName nam { sc.Message(MSG_ERROR, "%s is not a member function of %s", name.GetChars(), selfcls->TypeName.GetChars()); } - else if (funcsym->Variants[0].Flags & VARF_Private && symtable != &funccls->Symbols) + else if ((funcsym->Variants[0].Flags & VARF_Private) && symtable != &funccls->Symbols) { // private access is only allowed if the symbol table belongs to the class in which the current function is being defined. sc.Message(MSG_ERROR, "%s is declared private and not accessible", symbol->SymbolName.GetChars()); diff --git a/src/scripting/thingdef_data.cpp b/src/scripting/thingdef_data.cpp index c50b7f65b..a09134d58 100644 --- a/src/scripting/thingdef_data.cpp +++ b/src/scripting/thingdef_data.cpp @@ -858,8 +858,8 @@ void InitThingdef() // As a result, the size has to be set to something large and arbritrary because it can change between maps. This will need some serious improvement when things get cleaned up. sectorstruct->AddNativeField("lines", NewPointer(NewResizableArray(NewPointer(linestruct, false)), false), myoffsetof(sector_t, Lines), VARF_Native); - sectorstruct->AddNativeField("ceilingplane", secplanestruct, myoffsetof(sector_t, ceilingplane), VARF_Native); - sectorstruct->AddNativeField("floorplane", secplanestruct, myoffsetof(sector_t, floorplane), VARF_Native); + sectorstruct->AddNativeField("ceilingplane", secplanestruct, myoffsetof(sector_t, ceilingplane), VARF_Native | VARF_ReadOnly); + sectorstruct->AddNativeField("floorplane", secplanestruct, myoffsetof(sector_t, floorplane), VARF_Native | VARF_ReadOnly); diff --git a/src/scripting/vm/vm.h b/src/scripting/vm/vm.h index cda7a1c58..06de1e2c1 100644 --- a/src/scripting/vm/vm.h +++ b/src/scripting/vm/vm.h @@ -8,6 +8,10 @@ #include "doomerrors.h" #include "memarena.h" +// [ZZ] there are serious circular references between this and the rest of ZScript code, so it needs to be done like this +// these are used in vmexec.h +void FScopeBarrier_ValidateNew(PClass* cls, PFunction* callingfunc); +void FScopeBarrier_ValidateCall(PFunction* calledfunc, PFunction* callingfunc, PClass* selftype); class DObject; extern FMemArena ClassDataAllocator; @@ -704,6 +708,8 @@ public: bool Native; bool Final = false; // cannot be overridden bool Unsafe = false; // Contains references to class fields that are unsafe for psp and item state calls. + bool FuncConst = false; // [ZZ] readonly function + int BarrierSide = 0; // [ZZ] FScopeBarrier::Side BYTE ImplicitArgs = 0; // either 0 for static, 1 for method or 3 for action unsigned VirtualIndex = ~0u; FName Name; @@ -712,7 +718,7 @@ public: class PPrototype *Proto; - VMFunction(FName name = NAME_None) : Native(false), ImplicitArgs(0), Name(name), Proto(NULL) + VMFunction(FName name = NAME_None) : Native(false), ImplicitArgs(0), Name(name), Proto(NULL) { AllFunctions.Push(this); } diff --git a/src/scripting/vm/vmexec.h b/src/scripting/vm/vmexec.h index c37e29cbf..cd3925adc 100644 --- a/src/scripting/vm/vmexec.h +++ b/src/scripting/vm/vmexec.h @@ -664,13 +664,27 @@ begin: VMReturn returns[MAX_RETURNS]; int numret; + b = B; +#if 0 + // [ZZ] hax! + if (call->BarrierSide == 3) // :( - this is Side_Virtual. Side_Virtual should receive special arguments. + { + PFunction* calledfunc = (PFunction*)(reg.param + f->NumParam - b)[0].a; + PFunction* callingfunc = (PFunction*)(reg.param + f->NumParam - b)[1].a; + DObject* dobj = (DObject*)(reg.param + f->NumParam - b)[2].a; // this is the self pointer. it should be in, since Side_Virtual functions are always non-static methods. + PClass* selftype = dobj->GetClass(); + FScopeBarrier_ValidateCall(calledfunc, callingfunc, selftype); + b -= 2; + } +#endif + FillReturns(reg, f, returns, pc+1, C); if (call->Native) { try { VMCycles[0].Unclock(); - numret = static_cast(call)->NativeCall(reg.param + f->NumParam - B, call->DefaultArgs, B, returns, C); + numret = static_cast(call)->NativeCall(reg.param + f->NumParam - b, call->DefaultArgs, b, returns, C); VMCycles[0].Clock(); } catch (CVMAbortException &err) @@ -686,7 +700,7 @@ begin: VMCalls[0]++; VMScriptFunction *script = static_cast(call); VMFrame *newf = stack->AllocFrame(script); - VMFillParams(reg.param + f->NumParam - B, newf, B); + VMFillParams(reg.param + f->NumParam - b, newf, b); try { numret = Exec(stack, script->Code, returns, C); @@ -803,7 +817,11 @@ begin: { b = B; PClass *cls = (PClass*)(pc->op == OP_NEW ? reg.a[b] : konsta[b].v); + PFunction *callingfunc = (PFunction*)konsta[C].o; // [ZZ] due to how this is set, it's always const if (cls->ObjectFlags & OF_Abstract) ThrowAbortException(X_OTHER, "Cannot instantiate abstract class %s", cls->TypeName.GetChars()); + // [ZZ] validate readonly and between scope construction + if (callingfunc) + FScopeBarrier_ValidateNew(cls, callingfunc); reg.a[a] = cls->CreateNew(); reg.atag[a] = ATAG_OBJECT; NEXTOP; diff --git a/src/scripting/zscript/zcc-parse.lemon b/src/scripting/zscript/zcc-parse.lemon index 6d3e8a856..f80a09974 100644 --- a/src/scripting/zscript/zcc-parse.lemon +++ b/src/scripting/zscript/zcc-parse.lemon @@ -213,6 +213,8 @@ class_ancestry(X) ::= COLON dottable_id(A). { X = A; /*X-overwrites-A*/ } class_flags(X) ::= . { X.Flags = 0; X.Replaces = NULL; } class_flags(X) ::= class_flags(A) ABSTRACT. { X.Flags = A.Flags | ZCC_Abstract; X.Replaces = A.Replaces; } class_flags(X) ::= class_flags(A) NATIVE. { X.Flags = A.Flags | ZCC_Native; X.Replaces = A.Replaces; } +class_flags(X) ::= class_flags(A) UI. { X.Flags = A.Flags | ZCC_UIFlag; X.Replaces = A.Replaces; } +class_flags(X) ::= class_flags(A) PLAY. { X.Flags = A.Flags | ZCC_Play; X.Replaces = A.Replaces; } class_flags(X) ::= class_flags(A) REPLACES dottable_id(B). { X.Flags = A.Flags; X.Replaces = B; } /*----- Dottable Identifier -----*/ @@ -326,7 +328,9 @@ struct_def(X) ::= STRUCT(T) IDENTIFIER(A) struct_flags(S) LBRACE opt_struct_body %type struct_flags{ClassFlagsBlock} struct_flags(X) ::= . { X.Flags = 0; } -struct_flags(X) ::= NATIVE. { X.Flags = ZCC_Native; } +struct_flags(X) ::= struct_flags(A) UI. { X.Flags = A.Flags | ZCC_UIFlag; } +struct_flags(X) ::= struct_flags(A) PLAY. { X.Flags = A.Flags | ZCC_Play; } +struct_flags(X) ::= struct_flags(A) NATIVE. { X.Flags = A.Flags | ZCC_Native; } opt_struct_body(X) ::= . { X = NULL; } opt_struct_body(X) ::= struct_body(X). @@ -999,6 +1003,10 @@ decl_flag(X) ::= DEPRECATED(T). { X.Int = ZCC_Deprecated; X.SourceLoc = T.Sourc decl_flag(X) ::= VIRTUAL(T). { X.Int = ZCC_Virtual; X.SourceLoc = T.SourceLoc; } decl_flag(X) ::= OVERRIDE(T). { X.Int = ZCC_Override; X.SourceLoc = T.SourceLoc; } decl_flag(X) ::= VARARG(T). { X.Int = ZCC_VarArg; X.SourceLoc = T.SourceLoc; } +decl_flag(X) ::= UI(T). { X.Int = ZCC_UIFlag; X.SourceLoc = T.SourceLoc; } +decl_flag(X) ::= PLAY(T). { X.Int = ZCC_Play; X.SourceLoc = T.SourceLoc; } +decl_flag(X) ::= CLEARSCOPE(T). { X.Int = ZCC_ClearScope; X.SourceLoc = T.SourceLoc; } +decl_flag(X) ::= VIRTUALSCOPE(T). { X.Int = ZCC_VirtualScope; X.SourceLoc = T.SourceLoc; } func_const(X) ::= . { X.Int = 0; X.SourceLoc = stat->sc->GetMessageLine(); } func_const(X) ::= CONST(T). { X.Int = ZCC_FuncConst; X.SourceLoc = T.SourceLoc; } diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index 5574fcc18..f8e9980a2 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -495,6 +495,16 @@ void ZCCCompiler::CreateStructTypes() { s->strct->Type = NewStruct(s->NodeName(), outer); } + + if ((s->strct->Flags & (ZCC_UIFlag | ZCC_Play)) == (ZCC_UIFlag | ZCC_Play)) + { + Error(s->strct, "Struct %s has incompatible flags", s->NodeName().GetChars()); + } + + if (s->strct->Flags & ZCC_UIFlag) + s->Type()->ObjectFlags |= OF_UI; + if (s->strct->Flags & ZCC_Play) + s->Type()->ObjectFlags |= OF_Play; s->strct->Symbol = new PSymbolType(s->NodeName(), s->Type()); syms->AddSymbol(s->strct->Symbol); @@ -597,11 +607,33 @@ void ZCCCompiler::CreateClassTypes() c->cls->Type = nullptr; } } - if (c->cls->Flags & ZCC_Abstract) - { - c->Type()->ObjectFlags |= OF_Abstract; - } if (c->Type() == nullptr) c->cls->Type = parent->FindClassTentative(c->NodeName()); + if (c->cls->Flags & ZCC_Abstract) + c->Type()->ObjectFlags |= OF_Abstract; + // + static int incompatible[] = { ZCC_UIFlag, ZCC_Play, ZCC_ClearScope }; + int incompatiblecnt = 0; + for (size_t k = 0; k < countof(incompatible); k++) + if (incompatible[k] & c->cls->Flags) incompatiblecnt++; + + if (incompatiblecnt > 1) + { + Error(c->cls, "Class %s has incompatible flags", c->NodeName().GetChars()); + } + + if (c->cls->Flags & ZCC_UIFlag) + c->Type()->ObjectFlags = (c->Type()->ObjectFlags&~OF_Play) | OF_UI; + if (c->cls->Flags & ZCC_Play) + c->Type()->ObjectFlags = (c->Type()->ObjectFlags&~OF_UI) | OF_Play; + if (parent->ObjectFlags & (OF_UI | OF_Play)) // parent is either ui or play + { + if (c->cls->Flags & (ZCC_UIFlag | ZCC_Play)) + { + Error(c->cls, "Can't change class scope in class %s", c->NodeName().GetChars()); + } + c->Type()->ObjectFlags = (c->Type()->ObjectFlags & ~(OF_UI | OF_Play)) | (parent->ObjectFlags & (OF_UI | OF_Play)); + } + c->Type()->bExported = true; // this class is accessible to script side type casts. (The reason for this flag is that types like PInt need to be skipped.) c->cls->Symbol = new PSymbolType(c->NodeName(), c->Type()); OutNamespace->Symbols.AddSymbol(c->cls->Symbol); @@ -1050,8 +1082,8 @@ bool ZCCCompiler::CompileFields(PStruct *type, TArray &Fiel // For structs only allow 'deprecated', for classes exclude function qualifiers. int notallowed = forstruct? - ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Meta | ZCC_Extension : - ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Extension; + ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Meta | ZCC_Extension | ZCC_VirtualScope | ZCC_ClearScope : + ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Extension | ZCC_VirtualScope | ZCC_ClearScope; if (field->Flags & notallowed) { @@ -1066,12 +1098,39 @@ bool ZCCCompiler::CompileFields(PStruct *type, TArray &Fiel if (field->Flags & ZCC_Deprecated) varflags |= VARF_Deprecated; if (field->Flags & ZCC_ReadOnly) varflags |= VARF_ReadOnly; if (field->Flags & ZCC_Transient) varflags |= VARF_Transient; + if (type->ObjectFlags & OF_UI) + varflags |= VARF_UI; + if (type->ObjectFlags & OF_Play) + varflags |= VARF_Play; + if (field->Flags & ZCC_UIFlag) + varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_UI); + if (field->Flags & ZCC_Play) + varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_Play); + if (field->Flags & ZCC_ClearScope) + varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_PlainData); if (field->Flags & ZCC_Native) { varflags |= VARF_Native | VARF_Transient; } + static int excludescope[] = { ZCC_UIFlag, ZCC_Play, ZCC_ClearScope }; + int excludeflags = 0; + int fc = 0; + for (size_t i = 0; i < countof(excludescope); i++) + { + if (field->Flags & excludescope[i]) + { + fc++; + excludeflags |= excludescope[i]; + } + } + if (fc > 1) + { + Error(field, "Invalid combination of scope qualifiers %s on field %s", FlagsToString(excludeflags).GetChars(), FName(field->Names->Name).GetChars()); + varflags &= ~(VARF_UI | VARF_Play); // make plain data + } + if (field->Flags & ZCC_Meta) { varflags |= VARF_Meta | VARF_Static | VARF_ReadOnly; // metadata implies readonly @@ -1121,7 +1180,7 @@ bool ZCCCompiler::CompileFields(PStruct *type, TArray &Fiel } else if (hasnativechildren) { - Error(field, "Cannot add field %s to %s. %s has native children which means it size may not change.", FName(name->Name).GetChars(), type->TypeName.GetChars(), type->TypeName.GetChars()); + Error(field, "Cannot add field %s to %s. %s has native children which means it size may not change", FName(name->Name).GetChars(), type->TypeName.GetChars(), type->TypeName.GetChars()); } else { @@ -1218,7 +1277,7 @@ bool ZCCCompiler::CompileProperties(PClass *type, TArray &Proper FString ZCCCompiler::FlagsToString(uint32_t flags) { - const char *flagnames[] = { "native", "static", "private", "protected", "latent", "final", "meta", "action", "deprecated", "readonly", "funcconst", "abstract", "extension", "virtual", "override", "transient", "vararg" }; + const char *flagnames[] = { "native", "static", "private", "protected", "latent", "final", "meta", "action", "deprecated", "readonly", "const", "abstract", "extend", "virtual", "override", "transient", "vararg", "ui", "play", "clearscope", "virtualscope" }; FString build; for (size_t i = 0; i < countof(flagnames); i++) @@ -2017,7 +2076,7 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool } while (t != f->Type); } - int notallowed = ZCC_Latent | ZCC_Meta | ZCC_ReadOnly | ZCC_FuncConst | ZCC_Abstract; + int notallowed = ZCC_Latent | ZCC_Meta | ZCC_ReadOnly | ZCC_Abstract; if (f->Flags & notallowed) { @@ -2064,12 +2123,48 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool if (f->Flags & ZCC_Virtual) varflags |= VARF_Virtual; if (f->Flags & ZCC_Override) varflags |= VARF_Override; if (f->Flags & ZCC_VarArg) varflags |= VARF_VarArg; + if (f->Flags & ZCC_FuncConst) varflags |= VARF_ReadOnly; // FuncConst method is internally marked as VARF_ReadOnly + if (c->Type()->ObjectFlags & OF_UI) + varflags |= VARF_UI; + if (c->Type()->ObjectFlags & OF_Play) + varflags |= VARF_Play; + if (f->Flags & ZCC_FuncConst) + varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_PlainData); // const implies clearscope. this is checked a bit later to also not have ZCC_Play/ZCC_UIFlag. + if (f->Flags & ZCC_UIFlag) + varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_UI); + if (f->Flags & ZCC_Play) + varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_Play); + if (f->Flags & ZCC_ClearScope) + varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_Clear); + if (f->Flags & ZCC_VirtualScope) + varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_Virtual); + + // [ZZ] supporting const self for actors is quite a cumbersome task because there's no concept of a const pointer (?) + // either way, it doesn't make sense, because you can call any method on a readonly class instance. + + // The above is nonsense. This needs to work and needs to be implemented properly. + /* + if ((f->Flags & ZCC_FuncConst) && (c->Type()->IsKindOf(RUNTIME_CLASS(PClass)))) + { + Error(f, "'Const' on a method can only be used in structs"); + } + */ + if ((f->Flags & ZCC_VarArg) && !(f->Flags & ZCC_Native)) { Error(f, "'VarArg' can only be used with native methods"); } if (f->Flags & ZCC_Action) { + if (varflags & VARF_ReadOnly) + { + Error(f, "Action functions cannot be declared const"); + varflags &= ~VARF_ReadOnly; + } + if (varflags & VARF_UI) + { + Error(f, "Action functions cannot be declared UI"); + } // Non-Actors cannot have action functions. if (!c->Type()->IsKindOf(RUNTIME_CLASS(PClassActor))) { @@ -2089,36 +2184,69 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool } if (f->Flags & ZCC_Static) varflags = (varflags & ~VARF_Method) | VARF_Final, implicitargs = 0; // Static implies Final. - if (varflags & VARF_Override) varflags &= ~VARF_Virtual; // allow 'virtual override'. // Only one of these flags may be used. static int exclude[] = { ZCC_Virtual, ZCC_Override, ZCC_Action, ZCC_Static }; - static const char * print[] = { "virtual", "override", "action", "static" }; + int excludeflags = 0; int fc = 0; - FString build; - for (int i = 0; i < 4; i++) + for (size_t i = 0; i < countof(exclude); i++) { if (f->Flags & exclude[i]) { fc++; - if (build.Len() > 0) build += ", "; - build += print[i]; + excludeflags |= exclude[i]; } } if (fc > 1) { - Error(f, "Invalid combination of qualifiers %s on function %s.", FName(f->Name).GetChars(), build.GetChars()); + Error(f, "Invalid combination of qualifiers %s on function %s", FlagsToString(excludeflags).GetChars(), FName(f->Name).GetChars()); varflags |= VARF_Method; } if (varflags & VARF_Override) varflags |= VARF_Virtual; // Now that the flags are checked, make all override functions virtual as well. + // [ZZ] this doesn't make sense either. + if ((varflags&(VARF_ReadOnly | VARF_Method)) == VARF_ReadOnly) // non-method const function + { + Error(f, "'Const' on a static method is not supported"); + } + + // [ZZ] neither this + if ((varflags&(VARF_VirtualScope | VARF_Method)) == VARF_VirtualScope) // non-method virtualscope function + { + Error(f, "'VirtualScope' on a static method is not supported"); + } + + // you can't have a const function belonging to either ui or play. + // const is intended for plain data to signify that you can call a method on readonly variable. + if ((f->Flags & ZCC_FuncConst) && (f->Flags & (ZCC_UIFlag | ZCC_Play | ZCC_VirtualScope))) + { + Error(f, "Invalid combination of qualifiers %s on function %s", FlagsToString(f->Flags&(ZCC_FuncConst | ZCC_UIFlag | ZCC_Play | ZCC_VirtualScope)).GetChars(), FName(f->Name).GetChars()); + } + + static int excludescope[] = { ZCC_UIFlag, ZCC_Play, ZCC_ClearScope, ZCC_VirtualScope }; + excludeflags = 0; + fc = 0; + for (size_t i = 0; i < countof(excludescope); i++) + { + if (f->Flags & excludescope[i]) + { + fc++; + excludeflags |= excludescope[i]; + } + } + if (fc > 1) + { + Error(f, "Invalid combination of scope qualifiers %s on function %s", FlagsToString(excludeflags).GetChars(), FName(f->Name).GetChars()); + varflags &= ~(VARF_UI | VARF_Play); // make plain data + } + if (f->Flags & ZCC_Native) { varflags |= VARF_Native; afd = FindFunction(c->Type(), FName(f->Name).GetChars()); if (afd == nullptr) { - Error(f, "The function '%s.%s' has not been exported from the executable.", c->Type()->TypeName.GetChars(), FName(f->Name).GetChars()); + Error(f, "The function '%s.%s' has not been exported from the executable", c->Type()->TypeName.GetChars(), FName(f->Name).GetChars()); } else { @@ -2232,7 +2360,7 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool } else if (hasoptionals) { - Error(p, "All arguments after the first optional one need also be optional."); + Error(p, "All arguments after the first optional one need also be optional"); } // TBD: disallow certain types? For now, let everything pass that isn't an array. args.Push(type); @@ -2287,18 +2415,26 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool sym->Variants[0].Implementation->DefaultArgs = std::move(argdefaults); } + if (sym->Variants[0].Implementation != nullptr) + { + // [ZZ] unspecified virtual function inherits old scope. virtual function scope can't be changed. + sym->Variants[0].Implementation->BarrierSide = FScopeBarrier::SideFromFlags(varflags); + } + PClass *clstype = static_cast(c->Type()); if (varflags & VARF_Virtual) { if (sym->Variants[0].Implementation == nullptr) { - Error(f, "Virtual function %s.%s not present.", c->Type()->TypeName.GetChars(), FName(f->Name).GetChars()); + Error(f, "Virtual function %s.%s not present", c->Type()->TypeName.GetChars(), FName(f->Name).GetChars()); return; } + if (varflags & VARF_Final) - { sym->Variants[0].Implementation->Final = true; - } + if (varflags & VARF_ReadOnly) + sym->Variants[0].Implementation->FuncConst = true; + if (forclass) { int vindex = clstype->FindVirtualIndex(sym->SymbolName, sym->Variants[0].Proto); @@ -2316,6 +2452,23 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool { Error(f, "Attempt to override final function %s", FName(f->Name).GetChars()); } + // you can't change ui/play/clearscope for a virtual method. + if (f->Flags & (ZCC_UIFlag|ZCC_Play|ZCC_ClearScope|ZCC_VirtualScope)) + { + Error(f, "Attempt to change scope for virtual function %s", FName(f->Name).GetChars()); + } + // you can't change const qualifier for a virtual method + if (oldfunc->FuncConst != sym->Variants[0].Implementation->FuncConst) + { + Error(f, "Attempt to change const qualifier for virtual function %s", FName(f->Name).GetChars()); + } + // inherit scope of original function if override not specified + sym->Variants[0].Implementation->BarrierSide = oldfunc->BarrierSide; + sym->Variants[0].Flags = FScopeBarrier::ChangeSideInFlags(sym->Variants[0].Flags, oldfunc->BarrierSide); + // inherit const from original function + if ((sym->Variants[0].Implementation->FuncConst = oldfunc->FuncConst)) + sym->Variants[0].Flags |= VARF_ReadOnly; + clstype->Virtuals[vindex] = sym->Variants[0].Implementation; sym->Variants[0].Implementation->VirtualIndex = vindex; } diff --git a/src/scripting/zscript/zcc_parser.cpp b/src/scripting/zscript/zcc_parser.cpp index 439b495b4..761af9541 100644 --- a/src/scripting/zscript/zcc_parser.cpp +++ b/src/scripting/zscript/zcc_parser.cpp @@ -137,6 +137,10 @@ static void InitTokenMap() TOKENDEF (TK_Latent, ZCC_LATENT); TOKENDEF (TK_Virtual, ZCC_VIRTUAL); TOKENDEF (TK_VarArg, ZCC_VARARG); + TOKENDEF (TK_UI, ZCC_UI); + TOKENDEF (TK_Play, ZCC_PLAY); + TOKENDEF (TK_ClearScope, ZCC_CLEARSCOPE); + TOKENDEF (TK_VirtualScope, ZCC_VIRTUALSCOPE); TOKENDEF (TK_Override, ZCC_OVERRIDE); TOKENDEF (TK_Final, ZCC_FINAL); TOKENDEF (TK_Meta, ZCC_META); diff --git a/src/scripting/zscript/zcc_parser.h b/src/scripting/zscript/zcc_parser.h index 95bafd54f..c414c0e9c 100644 --- a/src/scripting/zscript/zcc_parser.h +++ b/src/scripting/zscript/zcc_parser.h @@ -36,7 +36,11 @@ enum ZCC_Virtual = 1 << 13, ZCC_Override = 1 << 14, ZCC_Transient = 1 << 15, - ZCC_VarArg = 1 << 16 + ZCC_VarArg = 1 << 16, + ZCC_UIFlag = 1 << 17, // there's also token called ZCC_UI + ZCC_Play = 1 << 18, + ZCC_ClearScope = 1 << 19, + ZCC_VirtualScope = 1 << 20, }; // Function parameter modifiers diff --git a/src/zstring.h b/src/zstring.h index b925a39b1..e486a0e8b 100644 --- a/src/zstring.h +++ b/src/zstring.h @@ -450,13 +450,4 @@ template<> struct THashTraits int Compare(const FString &left, const FString &right) { return left.Compare(right); } }; -class FStringNoInit -{ - char mem[sizeof(FString)]; - operator FString&() - { - return *reinterpret_cast(&mem); - } -}; - #endif diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt index ea17fecde..eb0a3895f 100644 --- a/wadsrc/static/zscript/actor.txt +++ b/wadsrc/static/zscript/actor.txt @@ -450,13 +450,13 @@ class Actor : Thinker native native static int FindUniqueTid(int start = 0, int limit = 0); native void SetShade(color col); - native string GetTag(string defstr = ""); + native string GetTag(string defstr = "") const; native void SetTag(string defstr = ""); native double GetBobOffset(double frac = 0); native void ClearCounters(); native bool GiveBody (int num, int max=0); native bool HitFloor(); - native bool isTeammate(Actor other); + native bool isTeammate(Actor other) const; native int PlayerNumber(); native void SetFriendPlayer(PlayerInfo player); native void SoundAlert(Actor target, bool splash = false, double maxdist = 0); @@ -468,15 +468,15 @@ class Actor : Thinker native native bool UpdateWaterLevel (bool splash = true); native bool IsZeroDamage(); native void ClearInterpolation(); - native Vector3 PosRelative(sector sec); + native Vector3 PosRelative(sector sec) const; native void HandleSpawnFlags(); native void ExplodeMissile(line lin = null, Actor target = null, bool onsky = false); native void RestoreDamage(); - native int SpawnHealth(); + native int SpawnHealth() const; native void SetDamage(int dmg); - native double Distance2D(Actor other); - native double Distance3D(Actor other); + native double Distance2D(Actor other) const; + native double Distance3D(Actor other) const; native void SetOrigin(vector3 newpos, bool moving); native void SetXYZ(vector3 newpos); native Actor GetPointer(int aaptr); @@ -511,7 +511,7 @@ class Actor : Thinker native native void BloodSplatter (Vector3 pos, double hitangle, bool axe = false); native bool HitWater (sector sec, Vector3 pos, bool checkabove = false, bool alert = true, bool force = false); native void PlaySpawnSound(Actor missile); - native bool CountsAsKill(); + native bool CountsAsKill() const; native bool Teleport(Vector3 pos, double angle, int flags); native void TraceBleed(int damage, Actor missile); @@ -559,21 +559,21 @@ class Actor : Thinker native native void LinkToWorld(LinkContext ctx = null); native void UnlinkFromWorld(out LinkContext ctx = null); native bool CanSeek(Actor target); - native double AngleTo(Actor target, bool absolute = false); + native double AngleTo(Actor target, bool absolute = false) const; native void AddZ(double zadd, bool moving = true); native void SetZ(double z); - native vector2 Vec2To(Actor other); - native vector3 Vec3To(Actor other); - native vector3 Vec3Offset(double x, double y, double z, bool absolute = false); - native vector3 Vec3Angle(double length, double angle, double z = 0, bool absolute = false); - native vector2 Vec2Angle(double length, double angle, bool absolute = false); - native vector2 Vec2Offset(double x, double y, bool absolute = false); - native vector3 Vec2OffsetZ(double x, double y, double atz, bool absolute = false); - native void VelFromAngle(double speed = 0, double angle = 0); - native void Vel3DFromAngle(double speed, double angle, double pitch); + native vector2 Vec2To(Actor other) const; + native vector3 Vec3To(Actor other) const; + native vector3 Vec3Offset(double x, double y, double z, bool absolute = false) const; + native vector3 Vec3Angle(double length, double angle, double z = 0, bool absolute = false) const; + native vector2 Vec2Angle(double length, double angle, bool absolute = false) const; + native vector2 Vec2Offset(double x, double y, bool absolute = false) const; + native vector3 Vec2OffsetZ(double x, double y, double atz, bool absolute = false) const; + native void VelFromAngle(double speed = 0, double angle = 0) const; + native void Vel3DFromAngle(double speed, double angle, double pitch) const; native void Thrust(double speed = 0, double angle = 0); - native bool isFriend(Actor other); - native bool isHostile(Actor other); + native bool isFriend(Actor other) const; + native bool isHostile(Actor other) const; native void AdjustFloorClip(); native DropItem GetDropItems(); native void CopyFriendliness (Actor other, bool changeTarget, bool resetHealth = true); @@ -588,8 +588,8 @@ class Actor : Thinker native native void Howl(); native void DrawSplash (int count, double angle, int kind); native void GiveSecret(bool printmsg = true, bool playsound = true); - native double GetCameraHeight(); - native double GetGravity(); + native double GetCameraHeight() const; + native double GetGravity() const; native bool CheckClass(class checkclass, int ptr_select = AAPTR_DEFAULT, bool match_superclass = false); native void AddInventory(Inventory inv); @@ -597,7 +597,7 @@ class Actor : Thinker native native void ClearInventory(); native bool GiveInventory(class type, int amount, bool givecheat = false); native bool TakeInventory(class itemclass, int amount, bool fromdecorate = false, bool notakeinfinite = false); - native Inventory FindInventory(class itemtype, bool subclass = false); + native Inventory FindInventory(class itemtype, bool subclass = false) const; native Inventory GiveInventoryType(class itemtype); native Inventory DropInventory (Inventory item, int amt = -1); native bool UseInventory(Inventory item); @@ -613,7 +613,7 @@ class Actor : Thinker native native double GetDistance(bool checkz, int ptr = AAPTR_TARGET); native double GetAngle(int flags, int ptr = AAPTR_TARGET); native double GetZAt(double px = 0, double py = 0, double angle = 0, int flags = 0, int pick_pointer = AAPTR_DEFAULT); - native int GetSpawnHealth(); + native int GetSpawnHealth() const; native double GetCrouchFactor(int ptr = AAPTR_PLAYER1); native double GetCVar(string cvar); native int GetPlayerInput(int inputnum, int ptr = AAPTR_DEFAULT); diff --git a/wadsrc/static/zscript/base.txt b/wadsrc/static/zscript/base.txt index f5eda9022..a15b78de8 100644 --- a/wadsrc/static/zscript/base.txt +++ b/wadsrc/static/zscript/base.txt @@ -331,10 +331,10 @@ class Object native native static uint MSTime(); native Name GetClassName(); - native void Destroy(); + native virtualscope void Destroy(); // This does not call into the native method of the same name to avoid problems with objects that get garbage collected late on shutdown. - virtual void OnDestroy() {} + virtual virtualscope void OnDestroy() {} } class BrokenLines : Object native @@ -344,7 +344,7 @@ class BrokenLines : Object native native String StringAt(int line); } -class Thinker : Object native +class Thinker : Object native play { enum EStatnums { @@ -386,7 +386,6 @@ class Thinker : Object native class ThinkerIterator : Object native { - native static ThinkerIterator Create(class type = "Actor", int statnum=Thinker.MAX_STATNUM+1); native Thinker Next(bool exact = false); native void Reinit(); diff --git a/wadsrc/static/zscript/events.txt b/wadsrc/static/zscript/events.txt index 24707055f..6452d19e0 100755 --- a/wadsrc/static/zscript/events.txt +++ b/wadsrc/static/zscript/events.txt @@ -1,6 +1,6 @@ class BaseEvent native { } // just a base class. it doesn't inherit from Object on the scripting side so you can't call Destroy() on it and break everything. -class RenderEvent : BaseEvent native +class RenderEvent : BaseEvent native ui { native readonly Vector3 ViewPos; native readonly double ViewAngle; @@ -10,7 +10,7 @@ class RenderEvent : BaseEvent native native readonly Actor Camera; } -class WorldEvent : BaseEvent native +class WorldEvent : BaseEvent native play { // for loaded/unloaded native readonly bool IsSaveGame; @@ -28,7 +28,7 @@ class WorldEvent : BaseEvent native native readonly double DamageAngle; } -class PlayerEvent : BaseEvent native +class PlayerEvent : BaseEvent native play { // this is the player number that caused the event. // note: you can get player struct from this by using players[e.PlayerNumber] @@ -37,7 +37,7 @@ class PlayerEvent : BaseEvent native native readonly bool IsReturn; } -class UiEvent : BaseEvent native +class UiEvent : BaseEvent native ui { // d_gui.h enum EGUIEvent @@ -121,7 +121,7 @@ class UiEvent : BaseEvent native native readonly bool IsAlt; } -class InputEvent : BaseEvent native +class InputEvent : BaseEvent native play { enum EGenericEvent { @@ -279,7 +279,7 @@ class ConsoleEvent : BaseEvent native native readonly int Args[3]; } -class StaticEventHandler : Object native +class StaticEventHandler : Object native play { // static event handlers CAN register other static event handlers. // unlike EventHandler.Create that will not create them. @@ -307,8 +307,8 @@ class StaticEventHandler : Object native virtual native void WorldTick(WorldEvent e); // - virtual native void RenderFrame(RenderEvent e); - virtual native void RenderOverlay(RenderEvent e); + virtual native ui void RenderFrame(RenderEvent e); + virtual native ui void RenderOverlay(RenderEvent e); // virtual native void PlayerEntered(PlayerEvent e); @@ -317,11 +317,12 @@ class StaticEventHandler : Object native virtual native void PlayerDisconnected(PlayerEvent e); // - virtual native bool UiProcess(UiEvent e); + virtual native ui bool UiProcess(UiEvent e); virtual native bool InputProcess(InputEvent e); // - virtual native void ConsoleProcess(ConsoleEvent e); + virtual native ui void ConsoleProcess(ConsoleEvent e); + virtual native void NetworkProcess(ConsoleEvent e); // this value will be queried on Register() to decide the relative order of this handler to every other. // this is most useful in UI systems. @@ -342,4 +343,6 @@ class EventHandler : StaticEventHandler native static native bool Register(StaticEventHandler handler); static native bool Unregister(StaticEventHandler handler); + + clearscope static native void SendNetworkEvent(String name, int arg1 = 0, int arg2 = 0, int arg3 = 0); } diff --git a/wadsrc/static/zscript/inventory/inventory.txt b/wadsrc/static/zscript/inventory/inventory.txt index cac9b9b8f..16a83c7a5 100644 --- a/wadsrc/static/zscript/inventory/inventory.txt +++ b/wadsrc/static/zscript/inventory/inventory.txt @@ -751,7 +751,7 @@ class Inventory : Actor native virtual bool Use (bool pickup) { return false; } virtual double GetSpeedFactor() { return 1; } virtual bool GetNoTeleportFreeze() { return false; } - virtual void AlterWeaponSprite(VisStyle vis, in out int changed) {} + virtual ui void AlterWeaponSprite(VisStyle vis, in out int changed) {} virtual void OwnerDied() {} virtual Color GetBlend () { return 0; } @@ -818,7 +818,7 @@ class Inventory : Actor native // //=========================================================================== - virtual bool DrawPowerup(int x, int y) { return false; } + virtual ui bool DrawPowerup(int x, int y) { return false; } //=========================================================================== // diff --git a/wadsrc/static/zscript/inventory/powerups.txt b/wadsrc/static/zscript/inventory/powerups.txt index 3b01c97b3..f6b14a051 100644 --- a/wadsrc/static/zscript/inventory/powerups.txt +++ b/wadsrc/static/zscript/inventory/powerups.txt @@ -285,7 +285,7 @@ class Powerup : Inventory // //=========================================================================== - virtual bool isBlinking() + virtual bool isBlinking() const { return (EffectTics <= BLINKTHRESHOLD && (EffectTics & 8) && !bNoScreenBlink); } @@ -934,7 +934,7 @@ class PowerFlight : Powerup +INVENTORY.HUBPOWER } - bool HitCenterFrame; + ui bool HitCenterFrame; //=========================================================================== // diff --git a/wadsrc/static/zscript/mapdata.txt b/wadsrc/static/zscript/mapdata.txt index 5a216ea5e..3d18f9f5e 100644 --- a/wadsrc/static/zscript/mapdata.txt +++ b/wadsrc/static/zscript/mapdata.txt @@ -1,5 +1,5 @@ -struct SectorPortal native +struct SectorPortal native play { enum EType { @@ -31,12 +31,12 @@ struct SectorPortal native }; -struct Vertex native +struct Vertex native play { native readonly Vector2 p; } -struct Side native +struct Side native play { enum ETexpart { @@ -100,7 +100,7 @@ struct Side native }; -struct Line native +struct Line native play { enum ELineFlags { @@ -143,7 +143,7 @@ struct Line native native int special; native int args[5]; // <--- hexen-style arguments (expanded to ZDoom's full width) native double alpha; // <--- translucency (0=invisibile, FRACUNIT=opaque) - native Side sidedef[2]; + native readonly Side sidedef[2]; native readonly double bbox[4]; // bounding box, for the extent of the LineDef. native readonly Sector frontsector, backsector; native int validcount; // if == validcount, already checked @@ -171,25 +171,25 @@ struct Line native } } -struct SecPlane native +struct SecPlane native play { native Vector3 Normal; native double D; native double negiC; - native bool isSlope(); - native int PointOnSide(Vector3 pos); - native double ZatPoint (Vector2 v); - native double ZatPointDist(Vector2 v, double dist); - native bool isEqual(Secplane other); + native bool isSlope() const; + native int PointOnSide(Vector3 pos) const; + native double ZatPoint (Vector2 v) const; + native double ZatPointDist(Vector2 v, double dist) const; + native bool isEqual(Secplane other) const; native void ChangeHeight(double hdiff); - native double GetChangedHeight(double hdiff); + native double GetChangedHeight(double hdiff) const; native double HeightDiff(double oldd, double newd = 0.0); - native double PointToDist(Vector2 xy, double z); + native double PointToDist(Vector2 xy, double z) const; } // This encapsulates all info Doom's original 'special' field contained - for saving and transferring. -struct SecSpecial +struct SecSpecial play { Name damagetype; int damageamount; @@ -199,7 +199,7 @@ struct SecSpecial int Flags; } -struct Sector native +struct Sector native play { //secplane_t floorplane, ceilingplane; // defined internally //FDynamicColormap *ColorMap; diff --git a/wadsrc/static/zscript/menu/menu.txt b/wadsrc/static/zscript/menu/menu.txt index 9f59b3239..81727ad2b 100644 --- a/wadsrc/static/zscript/menu/menu.txt +++ b/wadsrc/static/zscript/menu/menu.txt @@ -48,7 +48,7 @@ struct JoystickConfig native } -class Menu : Object native +class Menu : Object native ui { enum EMenuKey { @@ -287,7 +287,7 @@ class Menu : Object native } -class MenuDescriptor : Object native +class MenuDescriptor : Object native ui { native Name mMenuName; native String mNetgameMessage; diff --git a/wadsrc/static/zscript/menu/menuitembase.txt b/wadsrc/static/zscript/menu/menuitembase.txt index 35663d8b2..9b758c117 100644 --- a/wadsrc/static/zscript/menu/menuitembase.txt +++ b/wadsrc/static/zscript/menu/menuitembase.txt @@ -4,7 +4,7 @@ // //============================================================================= -class MenuItemBase : Object native +class MenuItemBase : Object native ui { protected native double mXpos, mYpos; protected native Name mAction; diff --git a/wadsrc/static/zscript/shared/player.txt b/wadsrc/static/zscript/shared/player.txt index e8e022ef3..3821ff002 100644 --- a/wadsrc/static/zscript/shared/player.txt +++ b/wadsrc/static/zscript/shared/player.txt @@ -150,7 +150,7 @@ class PlayerPawn : Actor native } // This is for SBARINFO. - int, int GetEffectTicsForItem(class item) + int, int GetEffectTicsForItem(class item) const { let pg = (class)(item); if (pg != null) @@ -167,10 +167,10 @@ class PlayerPawn : Actor native return -1, -1; } - native int GetMaxHealth(bool withupgrades = false); + native int GetMaxHealth(bool withupgrades = false) const; native bool ResetAirSupply (bool playgasp = false); native void CheckWeaponSwitch(class item); - native static String GetPrintableDisplayName(Class cls); + native clearscope static String GetPrintableDisplayName(Class cls); } @@ -192,7 +192,7 @@ class PlayerChunk : PlayerPawn } } -class PSprite : Object native +class PSprite : Object native play { enum PSPLayers { @@ -239,7 +239,7 @@ enum EPlayerState PST_GONE // Player has left the game } -struct PlayerInfo native // this is what internally is known as player_t +struct PlayerInfo native play // this is what internally is known as player_t { // technically engine constants but the only part of the playsim using them is the player. const NOFIXEDCOLORMAP = -1; @@ -340,22 +340,22 @@ usercmd_t original_cmd; native void PoisonDamage(Actor source, int damage, bool playPainSound); native void SetPsprite(int id, State stat, bool pending = false); native void SetSafeFlash(Weapon weap, State flashstate, int index); - native PSprite GetPSprite(int id); - native PSprite FindPSprite(int id); + native PSprite GetPSprite(int id) const; + native PSprite FindPSprite(int id) const; native void SetLogNumber (int text); native void SetLogText (String text); native void DropWeapon(); native void BringUpWeapon(); - native String GetUserName(); - native Color GetColor(); - native int GetColorSet(); - native int GetPlayerClassNum(); - native int GetSkin(); - native bool GetNeverSwitch(); - native int GetGender(); - native int GetTeam(); - native float GetAutoaim(); + native String GetUserName() const; + native Color GetColor() const; + native int GetColorSet() const; + native int GetPlayerClassNum() const; + native int GetSkin() const; + native bool GetNeverSwitch() const; + native int GetGender() const; + native int GetTeam() const; + native float GetAutoaim() const; native void SetFOV(float fov); }