diff --git a/src/dobject.h b/src/dobject.h index 6605cea5a9..b5f7ef8c43 100644 --- a/src/dobject.h +++ b/src/dobject.h @@ -207,6 +207,8 @@ enum EObjectFlags 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 new() function at all OF_NoNew = 1 << 15, // Marks a class that can only be created with new() in the exact class that has this keyword + OF_UI = 1 << 16, // Marks a class that defaults to VARF_UI for it's fields/methods + OF_Play = 1 << 17, // 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 0151e6c119..cc023c66f9 100644 --- a/src/dobjtype.h +++ b/src/dobjtype.h @@ -37,6 +37,8 @@ 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) }; // An action function ------------------------------------------------------- diff --git a/src/sc_man_scanner.re b/src/sc_man_scanner.re index 57a3940ef1..69dcd4397d 100644 --- a/src/sc_man_scanner.re +++ b/src/sc_man_scanner.re @@ -171,6 +171,9 @@ std2: 'override' { RET(TK_Override); } 'vararg' { RET(TK_VarArg); } 'nonew' { RET(TK_NoNew); } + 'ui' { RET(TK_UI); } + 'play' { RET(TK_Play); } + 'allowui' { RET(TK_AllowUI); } '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 b278de38e9..1dc2055041 100644 --- a/src/sc_man_tokens.h +++ b/src/sc_man_tokens.h @@ -113,6 +113,9 @@ xx(TK_Export, "'expert'") xx(TK_Virtual, "'virtual'") xx(TK_VarArg, "'vararg'") xx(TK_NoNew, "'nonew'") +xx(TK_UI, "'ui'") +xx(TK_Play, "'play'") +xx(TK_AllowUI, "'allowui'") xx(TK_Override, "'override'") xx(TK_Super, "'super'") xx(TK_Null, "'null'") diff --git a/src/scripting/backend/codegen.h b/src/scripting/backend/codegen.h index 3c43777168..429cfd1864 100644 --- a/src/scripting/backend/codegen.h +++ b/src/scripting/backend/codegen.h @@ -70,6 +70,130 @@ class FxCompoundStatement; class FxLocalVariableDeclaration; typedef TDeletingArray FArgumentList; +// [ZZ] this is kind of related to compile context as well +struct FPlayUIBarrier +{ + 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, + Side_Play + }; + 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. + int SideFromFlags(int flags) + { + if (flags & VARF_UI) + return Side_UI; + if (flags & VARF_Play) + return Side_Play; + return Side_PlainData; + } + + // used for errors + const char* StringFromSide(int side) + { + switch (side) + { + case Side_PlainData: + return "data"; + case Side_UI: + return "ui"; + case Side_Play: + return "play"; + default: + return "unknown"; + } + } + + FPlayUIBarrier() + { + sidefrom = -1; + sidelast = -1; + callable = true; + readable = true; + writable = true; + } + + FPlayUIBarrier(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 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 + 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; + readerror.Format("Can't read %s field %s from %s context", StringFromSide(sideto), StringFromSide(sidefrom)); + } + + if (!readable) + { + writable = false; + callable = false; + 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; + 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; + callerror.Format("Can't call %s field %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom)); + } + } +}; + struct FCompileContext { FxExpression *ControlStmt = nullptr; diff --git a/src/scripting/vm/vm.h b/src/scripting/vm/vm.h index aa191c34ef..bcdf8d17b4 100644 --- a/src/scripting/vm/vm.h +++ b/src/scripting/vm/vm.h @@ -702,6 +702,9 @@ 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 ScopeUI = false; // [ZZ] 'ui' method + bool ScopePlay = false; // [ZZ] 'play' method + bool FuncConst = false; // [ZZ] const qualifier for methods - these can be called on readonly BYTE ImplicitArgs = 0; // either 0 for static, 1 for method or 3 for action unsigned VirtualIndex = ~0u; FName Name; diff --git a/src/scripting/zscript/zcc-parse.lemon b/src/scripting/zscript/zcc-parse.lemon index f158bff2de..c801950b5d 100644 --- a/src/scripting/zscript/zcc-parse.lemon +++ b/src/scripting/zscript/zcc-parse.lemon @@ -214,6 +214,9 @@ 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) NONEW. { X.Flags = A.Flags | ZCC_NoNew; 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) ALLOWUI. { X.Flags = A.Flags | ZCC_AllowUI; X.Replaces = A.Replaces; } class_flags(X) ::= class_flags(A) REPLACES dottable_id(B). { X.Flags = A.Flags; X.Replaces = B; } /*----- Dottable Identifier -----*/ @@ -327,7 +330,10 @@ 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) ALLOWUI. { X.Flags = A.Flags | ZCC_AllowUI; } +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). @@ -1000,6 +1006,9 @@ 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) ::= ALLOWUI(T). { X.Int = ZCC_AllowUI; 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 cb93a59652..81764a2dfd 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -496,6 +496,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); @@ -602,8 +612,24 @@ void ZCCCompiler::CreateClassTypes() if (c->cls->Flags & ZCC_Abstract) c->Type()->ObjectFlags |= OF_Abstract; // [ZZ] inherit nonew keyword - if (c->cls->Flags & ZCC_NoNew || (parent && parent->ObjectFlags & OF_NoNew)) + if (c->cls->Flags & ZCC_NoNew || (parent->ObjectFlags & OF_NoNew)) c->Type()->ObjectFlags |= OF_NoNew; + // + static int incompatible[] = { ZCC_UIFlag, ZCC_Play, ZCC_AllowUI }; + int incompatiblecnt = 0; + for (int 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 || ((parent->ObjectFlags & OF_UI) && !(c->cls->Flags & ZCC_AllowUI))) + c->Type()->ObjectFlags = (c->Type()->ObjectFlags&~OF_Play) | OF_UI; + if (c->cls->Flags & ZCC_Play || ((parent->ObjectFlags & OF_Play) && !(c->cls->Flags & ZCC_AllowUI))) + c->Type()->ObjectFlags = (c->Type()->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); @@ -1063,12 +1089,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 = (varflags&~VARF_Play) | VARF_UI; + if (field->Flags & ZCC_Play) + varflags = (varflags&~VARF_UI) | VARF_Play; + if (field->Flags & ZCC_AllowUI) + varflags = (varflags&~(VARF_UI | VARF_Play)); if (field->Flags & ZCC_Native) { varflags |= VARF_Native | VARF_Transient; } + static int excludescope[] = { ZCC_UIFlag, ZCC_Play, ZCC_AllowUI }; + int excludeflags = 0; + int fc = 0; + for (int 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 @@ -1101,11 +1154,11 @@ bool ZCCCompiler::CompileFields(PStruct *type, TArray &Fiel fd = FindField(querytype, FName(name->Name).GetChars()); if (fd == nullptr) { - Error(field, "The member variable '%s.%s' has not been exported from the executable.", type->TypeName.GetChars(), FName(name->Name).GetChars()); + Error(field, "The member variable '%s.%s' has not been exported from the executable", type->TypeName.GetChars(), FName(name->Name).GetChars()); } else if (thisfieldtype->Size != fd->FieldSize && fd->BitValue == 0) { - Error(field, "The member variable '%s.%s' has mismatching sizes in internal and external declaration. (Internal = %d, External = %d)", type->TypeName.GetChars(), FName(name->Name).GetChars(), fd->FieldSize, thisfieldtype->Size); + Error(field, "The member variable '%s.%s' has mismatching sizes in internal and external declaration (Internal = %d, External = %d)", type->TypeName.GetChars(), FName(name->Name).GetChars(), fd->FieldSize, thisfieldtype->Size); } // Q: Should we check alignment, too? A mismatch may be an indicator for bad assumptions. else @@ -1117,7 +1170,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 { @@ -1212,7 +1265,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", "nonew", "ui", "play", "allowui" }; FString build; for (size_t i = 0; i < countof(flagnames); i++) @@ -2014,7 +2067,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) { @@ -2061,6 +2114,20 @@ 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 = (varflags&~(VARF_Play | VARF_UI)); // const implies allowui. this is checked a bit later to also not have ZCC_Play/ZCC_UIFlag. + if (f->Flags & ZCC_UIFlag) + varflags = (varflags&~VARF_Play) | VARF_UI; + if (f->Flags & ZCC_Play) + varflags = (varflags&~VARF_UI) | VARF_Play; + if (f->Flags & ZCC_AllowUI) + varflags = (varflags&~(VARF_Play | VARF_UI)); + if ((f->Flags & ZCC_VarArg) && !(f->Flags & ZCC_Native)) { Error(f, "'VarArg' can only be used with native methods"); @@ -2086,36 +2153,57 @@ 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 (int 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. + // 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))) + { + Error(f, "Invalid combination of qualifiers %s on function %s", FlagsToString(f->Flags&(ZCC_FuncConst | ZCC_UIFlag | ZCC_Play)).GetChars(), FName(f->Name).GetChars()); + } + + static int excludescope[] = { ZCC_UIFlag, ZCC_Play, ZCC_AllowUI }; + excludeflags = 0; + fc = 0; + for (int 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 { @@ -2229,7 +2317,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); @@ -2289,13 +2377,20 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool { 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; - } + // [ZZ] unspecified virtual function inherits old scope. virtual function scope can't be changed. + if (f->Flags & ZCC_UIFlag) // only direct specification here (varflags can also have owning class scope applied, we don't want that) + sym->Variants[0].Implementation->ScopeUI = true; + if (f->Flags & ZCC_Play) // only direct specification here + sym->Variants[0].Implementation->ScopePlay = true; + if (varflags & VARF_ReadOnly) + sym->Variants[0].Implementation->FuncConst = true; + if (forclass) { int vindex = clstype->FindVirtualIndex(sym->SymbolName, sym->Variants[0].Proto); @@ -2313,6 +2408,27 @@ 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/allowui for a virtual method. + if ((oldfunc->ScopePlay != sym->Variants[0].Implementation->ScopePlay) || + (oldfunc->ScopeUI != sym->Variants[0].Implementation->ScopeUI)) + { + 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 != (varflags & VARF_ReadOnly)) + { + Error(f, "Attempt to change const qualifier for virtual function %s", FName(f->Name).GetChars()); + } + // inherit scope of original function + if (sym->Variants[0].Implementation->ScopeUI = oldfunc->ScopeUI) + sym->Variants[0].Flags = (sym->Variants[0].Flags&~(VARF_Play)) | VARF_UI; + else if (sym->Variants[0].Implementation->ScopePlay = oldfunc->ScopePlay) + sym->Variants[0].Flags = (sym->Variants[0].Flags&~(VARF_UI)) | VARF_Play; + else sym->Variants[0].Flags = (sym->Variants[0].Flags&~(VARF_UI | VARF_Play)); + // 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 4bfb8d0f24..2586cb8c5e 100644 --- a/src/scripting/zscript/zcc_parser.cpp +++ b/src/scripting/zscript/zcc_parser.cpp @@ -137,6 +137,9 @@ 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_AllowUI, ZCC_ALLOWUI); TOKENDEF (TK_NoNew, ZCC_NONEW); TOKENDEF (TK_Override, ZCC_OVERRIDE); TOKENDEF (TK_Final, ZCC_FINAL); diff --git a/src/scripting/zscript/zcc_parser.h b/src/scripting/zscript/zcc_parser.h index 12a5be06b7..72f81c1cca 100644 --- a/src/scripting/zscript/zcc_parser.h +++ b/src/scripting/zscript/zcc_parser.h @@ -38,6 +38,9 @@ enum ZCC_Transient = 1 << 15, ZCC_VarArg = 1 << 16, ZCC_NoNew = 1 << 17, + ZCC_UIFlag = 1 << 18, // there's also token called ZCC_UI + ZCC_Play = 1 << 19, + ZCC_AllowUI = 1 << 20, }; // Function parameter modifiers