Skip to content

Commit

Permalink
gave translations a dedicated scripted type.
Browse files Browse the repository at this point in the history
This is needed for implementing reliable serialization of custom translations. As long as they are merely ints they cannot be restored on loading a savegame because the serialization code does not know that these variables are special.
  • Loading branch information
coelckers committed Nov 9, 2023
1 parent 3781c43 commit f0c9b17
Show file tree
Hide file tree
Showing 19 changed files with 341 additions and 34 deletions.
2 changes: 2 additions & 0 deletions src/common/engine/namedef.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ xx(Voidptr)
xx(StateLabel)
xx(SpriteID)
xx(TextureID)
xx(TranslationID)
xx(Overlay)
xx(IsValid)
xx(IsNull)
Expand Down Expand Up @@ -272,6 +273,7 @@ xx(BuiltinFRandom)
xx(BuiltinNameToClass)
xx(BuiltinClassCast)
xx(BuiltinFunctionPtrCast)
xx(BuiltinFindTranslation)

xx(ScreenJobRunner)
xx(Action)
40 changes: 40 additions & 0 deletions src/common/engine/palettecontainer.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,46 @@ struct FRemapTable
private:
};


struct FTranslationID
{
public:
FTranslationID() = default;

private:
constexpr FTranslationID(int id) : ID(id)
{
}
public:
static constexpr FTranslationID fromInt(int i)
{
return FTranslationID(i);
}
FTranslationID(const FTranslationID& other) = default;
FTranslationID& operator=(const FTranslationID& other) = default;
bool operator !=(FTranslationID other) const
{
return ID != other.ID;
}
bool operator ==(FTranslationID other) const
{
return ID == other.ID;
}
bool operator ==(int other) const = delete;
bool operator !=(int other) const = delete;
constexpr int index() const
{
return ID;
}
constexpr bool isvalid() const
{
return ID > 0;
}
private:

int ID;
};

// A class that initializes unusued pointers to NULL. This is used so that when
// the TAutoGrowArray below is expanded, the new elements will be NULLed.
class FRemapTablePtr
Expand Down
2 changes: 1 addition & 1 deletion src/common/engine/sc_man_scanner.re
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ std2:
'vector2' { RET(TK_Vector2); }
'vector3' { RET(TK_Vector3); }
'map' { RET(TK_Map); }
'mapiterator' { RET(TK_MapIterator); }
'mapiterator' { RET(ParseVersion >= MakeVersion(4, 10, 0)? TK_MapIterator : TK_Identifier); }
'array' { RET(TK_Array); }
'function' { RET(ParseVersion >= MakeVersion(4, 12, 0)? TK_FunctionType : TK_Identifier); }
'in' { RET(TK_In); }
Expand Down
15 changes: 15 additions & 0 deletions src/common/engine/serializer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1196,6 +1196,21 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FTextureID &value, FTe
return arc;
}

//==========================================================================
//
//
//
//==========================================================================

FSerializer& Serialize(FSerializer& arc, const char* key, FTranslationID& value, FTranslationID* defval)
{
int v = value.index();
int* defv = (int*)defval;
Serialize(arc, key, v, defv);
value = FTranslationID::fromInt(v);
return arc;
}

//==========================================================================
//
// This never uses defval and instead uses 'null' as default
Expand Down
2 changes: 2 additions & 0 deletions src/common/engine/serializer.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class FSoundID;
union FRenderStyle;
class DObject;
class FTextureID;
struct FTranslationID;

inline bool nullcmp(const void *buffer, size_t length)
{
Expand Down Expand Up @@ -240,6 +241,7 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FSoundID &sid, FSoundI
FSerializer &Serialize(FSerializer &arc, const char *key, FString &sid, FString *def);
FSerializer &Serialize(FSerializer &arc, const char *key, NumericValue &sid, NumericValue *def);
FSerializer &Serialize(FSerializer &arc, const char *key, struct ModelOverride &sid, struct ModelOverride *def);
FSerializer& Serialize(FSerializer& arc, const char* key, FTranslationID& value, FTranslationID* defval);

void SerializeFunctionPointer(FSerializer &arc, const char *key, FunctionPointerValue *&p);

Expand Down
155 changes: 151 additions & 4 deletions src/common/scripting/backend/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@
#include "texturemanager.h"
#include "m_random.h"
#include "v_font.h"
#include "palettecontainer.h"


extern FRandom pr_exrandom;
FMemArena FxAlloc(65536);
CompileEnvironment compileEnvironment;
int R_FindCustomTranslation(FName name);

struct FLOP
{
Expand Down Expand Up @@ -161,6 +163,12 @@ void FCompileContext::CheckReturn(PPrototype *proto, FScriptPosition &pos)
PType* expected = ReturnProto->ReturnTypes[i];
PType* actual = proto->ReturnTypes[i];
if (swapped) std::swap(expected, actual);
// this must pass for older ZScripts.
if (Version < MakeVersion(4, 12, 0))
{
if (expected == TypeTranslationID) expected = TypeSInt32;
if (actual == TypeTranslationID) actual = TypeSInt32;
}

if (expected != actual && !AreCompatiblePointerTypes(expected, actual))
{ // Incompatible
Expand Down Expand Up @@ -993,6 +1001,19 @@ FxExpression *FxIntCast::Resolve(FCompileContext &ctx)

if (basex->ValueType->GetRegType() == REGT_INT)
{
if (basex->ValueType == TypeTranslationID)
{
// translation IDs must be entirely incompatible with ints, not even allowing an explicit conversion,
// but since the type was only introduced in version 4.12, older ZScript versions must allow this conversion.
if (ctx.Version < MakeVersion(4, 12, 0))
{
FxExpression* x = basex;
x->ValueType = ValueType;
basex = nullptr;
delete this;
return x;
}
}
if (basex->ValueType->isNumeric() || Explicit) // names can be converted to int, but only with an explicit type cast.
{
FxExpression *x = basex;
Expand All @@ -1006,7 +1027,7 @@ FxExpression *FxIntCast::Resolve(FCompileContext &ctx)
// Ugh. This should abort, but too many mods fell into this logic hole somewhere, so this serious error needs to be reduced to a warning. :(
// At least in ZScript, MSG_OPTERROR always means to report an error, not a warning so the problem only exists in DECORATE.
if (!basex->isConstant())
ScriptPosition.Message(MSG_OPTERROR, "Numeric type expected, got a name");
ScriptPosition.Message(MSG_OPTERROR, "Numeric type expected, got a %s", basex->ValueType->DescriptiveName());
else ScriptPosition.Message(MSG_OPTERROR, "Numeric type expected, got \"%s\"", static_cast<FxConstant*>(basex)->GetValue().GetName().GetChars());
FxExpression * x = new FxConstant(0, ScriptPosition);
delete this;
Expand Down Expand Up @@ -1127,7 +1148,8 @@ FxExpression *FxFloatCast::Resolve(FCompileContext &ctx)
{
// Ugh. This should abort, but too many mods fell into this logic hole somewhere, so this seroious error needs to be reduced to a warning. :(
// At least in ZScript, MSG_OPTERROR always means to report an error, not a warning so the problem only exists in DECORATE.
if (!basex->isConstant()) ScriptPosition.Message(MSG_OPTERROR, "Numeric type expected, got a name");
if (!basex->isConstant())
ScriptPosition.Message(MSG_OPTERROR, "Numeric type expected, got a %s", basex->ValueType->DescriptiveName());
else ScriptPosition.Message(MSG_OPTERROR, "Numeric type expected, got \"%s\"", static_cast<FxConstant*>(basex)->GetValue().GetName().GetChars());
FxExpression *x = new FxConstant(0.0, ScriptPosition);
delete this;
Expand Down Expand Up @@ -1534,6 +1556,107 @@ ExpEmit FxSoundCast::Emit(VMFunctionBuilder *build)
return to;
}

//==========================================================================
//
//
//
//==========================================================================

FxTranslationCast::FxTranslationCast(FxExpression* x)
: FxExpression(EFX_TranslationCast, x->ScriptPosition)
{
basex = x;
ValueType = TypeTranslationID;
}

//==========================================================================
//
//
//
//==========================================================================

FxTranslationCast::~FxTranslationCast()
{
SAFE_DELETE(basex);
}

//==========================================================================
//
//
//
//==========================================================================

FxExpression* FxTranslationCast::Resolve(FCompileContext& ctx)
{
CHECKRESOLVED();
SAFE_RESOLVE(basex, ctx);

if (basex->ValueType->isInt())
{
// 0 is a valid constant for translations, meaning 'no translation at all'. note that this conversion ONLY allows a constant!
if (basex->isConstant() && static_cast<FxConstant*>(basex)->GetValue().GetInt() == 0)
{
FxExpression* x = basex;
x->ValueType = TypeTranslationID;
basex = nullptr;
delete this;
return x;
}
if (ctx.Version < MakeVersion(4, 12, 0))
{
// only allow this conversion as a fallback
FxExpression* x = basex;
x->ValueType = TypeTranslationID;
basex = nullptr;
delete this;
return x;
}
}
else if (basex->ValueType == TypeString || basex->ValueType == TypeName)
{
if (basex->isConstant())
{
ExpVal constval = static_cast<FxConstant*>(basex)->GetValue();
FxExpression* x = new FxConstant(R_FindCustomTranslation(constval.GetName()), ScriptPosition);
x->ValueType = TypeTranslationID;
delete this;
return x;
}
else if (basex->ValueType == TypeString)
{
basex = new FxNameCast(basex, true);
basex = basex->Resolve(ctx);
}
return this;
}
ScriptPosition.Message(MSG_ERROR, "Cannot convert to translation ID");
delete this;
return nullptr;
}

//==========================================================================
//
//
//
//==========================================================================

ExpEmit FxTranslationCast::Emit(VMFunctionBuilder* build)
{
ExpEmit to(build, REGT_POINTER);

VMFunction* callfunc;
auto sym = FindBuiltinFunction(NAME_BuiltinFindTranslation);

assert(sym);
callfunc = sym->Variants[0].Implementation;

FunctionCallEmitter emitters(callfunc);
emitters.AddParameter(build, basex);
emitters.AddReturn(REGT_INT);
return emitters.EmitCall(build);
}


//==========================================================================
//
//
Expand Down Expand Up @@ -1766,6 +1889,14 @@ FxExpression *FxTypeCast::Resolve(FCompileContext &ctx)
delete this;
return x;
}
else if (ValueType == TypeTranslationID)
{
FxExpression* x = new FxTranslationCast(basex);
x = x->Resolve(ctx);
basex = nullptr;
delete this;
return x;
}
else if (ValueType == TypeColor)
{
FxExpression *x = new FxColorCast(basex);
Expand Down Expand Up @@ -4639,6 +4770,7 @@ ExpEmit FxConcat::Emit(VMFunctionBuilder *build)
else if (left->ValueType == TypeColor) cast = CAST_Co2S;
else if (left->ValueType == TypeSpriteID) cast = CAST_SID2S;
else if (left->ValueType == TypeTextureID) cast = CAST_TID2S;
else if (left->ValueType == TypeTranslationID) cast = CAST_U2S;
else if (op1.RegType == REGT_POINTER) cast = CAST_P2S;
else if (op1.RegType == REGT_INT) cast = CAST_I2S;
else assert(false && "Bad type for string concatenation");
Expand Down Expand Up @@ -4672,6 +4804,7 @@ ExpEmit FxConcat::Emit(VMFunctionBuilder *build)
else if (right->ValueType == TypeColor) cast = CAST_Co2S;
else if (right->ValueType == TypeSpriteID) cast = CAST_SID2S;
else if (right->ValueType == TypeTextureID) cast = CAST_TID2S;
else if (right->ValueType == TypeTranslationID) cast = CAST_U2S;
else if (op2.RegType == REGT_POINTER) cast = CAST_P2S;
else if (op2.RegType == REGT_INT) cast = CAST_I2S;
else assert(false && "Bad type for string concatenation");
Expand Down Expand Up @@ -5143,11 +5276,24 @@ FxExpression *FxConditional::Resolve(FCompileContext& ctx)
ValueType = truex->ValueType;
else if (falsex->IsPointer() && truex->ValueType == TypeNullPtr)
ValueType = falsex->ValueType;
// translation IDs need a bit of glue for compatibility and the 0 literal.
else if (truex->IsInteger() && falsex->ValueType == TypeTranslationID)
{
truex = new FxTranslationCast(truex);
truex = truex->Resolve(ctx);
ValueType = ctx.Version < MakeVersion(4, 12, 0)? TypeSInt32 : TypeTranslationID;
}
else if (falsex->IsInteger() && truex->ValueType == TypeTranslationID)
{
falsex = new FxTranslationCast(falsex);
falsex = falsex->Resolve(ctx);
ValueType = ctx.Version < MakeVersion(4, 12, 0) ? TypeSInt32 : TypeTranslationID;
}

else
ValueType = TypeVoid;
//else if (truex->ValueType != falsex->ValueType)

if (ValueType->GetRegType() == REGT_NIL)
if (truex == nullptr || falsex == nullptr || ValueType->GetRegType() == REGT_NIL)
{
ScriptPosition.Message(MSG_ERROR, "Incompatible types for ?: operator");
delete this;
Expand Down Expand Up @@ -8279,6 +8425,7 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
MethodName == NAME_Name ? TypeName :
MethodName == NAME_SpriteID ? TypeSpriteID :
MethodName == NAME_TextureID ? TypeTextureID :
MethodName == NAME_TranslationID ? TypeTranslationID :
MethodName == NAME_State ? TypeState :
MethodName == NAME_Color ? TypeColor : (PType*)TypeSound;

Expand Down
14 changes: 14 additions & 0 deletions src/common/scripting/backend/codegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ enum EFxType
EFX_StringCast,
EFX_ColorCast,
EFX_SoundCast,
EFX_TranslationCast,
EFX_TypeCast,
EFX_PlusSign,
EFX_MinusSign,
Expand Down Expand Up @@ -715,6 +716,19 @@ class FxSoundCast : public FxExpression
ExpEmit Emit(VMFunctionBuilder *build);
};

class FxTranslationCast : public FxExpression
{
FxExpression* basex;

public:

FxTranslationCast(FxExpression* x);
~FxTranslationCast();
FxExpression* Resolve(FCompileContext&);

ExpEmit Emit(VMFunctionBuilder* build);
};

class FxFontCast : public FxExpression
{
FxExpression *basex;
Expand Down
Loading

0 comments on commit f0c9b17

Please sign in to comment.