由於UnrealScript裡已經定義了一個名為double的struct在Object.uc如下:
struct {DOUBLE} double { var native const int A; var native const int B; };所以在本例中的倍精確度浮點數型別就改叫real以避免名稱衝突。當然real寫完之後其實是可以取代原來的double。這個struct只是給原生屬性佔位用的,實際上不能在UnrealScript中當成浮點數使用。
除了加入double型別外,當然還會示範如何加入基本的四則運算:
native(1111) static final operator(20) real + ( real A, real B ); native(1112) static final operator(20) real - ( real A, real B ); native(1113) static final operator(16) real * ( real A, real B ); native(1114) static final operator(16) real / ( real A, real B );先在UnNames.h中定義待會會用到的名稱:
REGISTER_NAME( 16, DoubleProperty) REGISTER_NAME( 17, Real)所有的內建型別都有對應的屬性類別,在UnType.h中定義對應double型別的UDoubleProperty類別:
/*----------------------------------------------------------------------------- UDoubleProperty. -----------------------------------------------------------------------------*/ // // Describes an IEEE 64-bit floating point variable. // class UDoubleProperty : public UProperty { DECLARE_CASTED_CLASS_INTRINSIC(UDoubleProperty,UProperty,0,Core,CASTCLASS_UFloatProperty) // Constructors. UDoubleProperty() {} UDoubleProperty( ECppProperty, INT InOffset, const TCHAR* InCategory, QWORD InFlags ) : UProperty( EC_CppProperty, InOffset, InCategory, InFlags ) {} // UProperty interface. void Link( FArchive& Ar, UProperty* Prev ); UBOOL Identical( const void* A, const void* B, DWORD PortFlags=0 ) const; void SerializeItem( FArchive& Ar, void* Value, INT MaxReadBytes, void* Defaults ) const; UBOOL NetSerializeItem( FArchive& Ar, UPackageMap* Map, void* Data ) const; FString GetCPPType( FString* ExtendedTypeText=NULL, DWORD CPPExportFlags=0 ) const; FString GetCPPMacroType( FString& ExtendedTypeText ) const; void ExportTextItem( FString& ValueStr, const BYTE* PropertyValue, const BYTE* DefaultValue, UObject* Parent, INT PortFlags ) const; const TCHAR* ImportText( const TCHAR* Buffer, BYTE* Data, INT PortFlags, UObject* Parent, FOutputDevice* ErrorText = NULL ) const; virtual void CopySingleValue( void* Dest, void* Src, UObject* SubobjectRoot=NULL, UObject* DestOwnerObject=NULL, struct FObjectInstancingGraph* InstanceGraph=NULL ) const; virtual void CopyCompleteValue( void* Dest, void* Src, UObject* SubobjectRoot=NULL, UObject* DestOwnerObject=NULL, struct FObjectInstancingGraph* InstanceGraph=NULL ) const; UBOOL HasValue( const BYTE* Data, DWORD PortFlags=0 ) const; void ClearValue( BYTE* Data, DWORD PortFlags=0 ) const; virtual INT GetMinAlignment() const; UBOOL GetPropertyValue( BYTE* PropertyValueAddress, UPropertyValue& out_PropertyValue ) const; UBOOL SetPropertyValue( BYTE* PropertyValueAddress, const UPropertyValue& PropertyValue ) const; };UPropertyValue是一個集合各種UnrealScript內建型別的union,也為它加入DOUBLE型別:
union UPropertyValue { ... DOUBLE DoubleValue; ... };實作如下:
/*----------------------------------------------------------------------------- UDoubleProperty. -----------------------------------------------------------------------------*/ INT UDoubleProperty::GetMinAlignment() const { return sizeof(DOUBLE); } void UDoubleProperty::Link( FArchive& Ar, UProperty* Prev ) { Super::Link( Ar, Prev ); ElementSize = sizeof(DOUBLE); Offset = Align((GetOuter()->GetClass()->ClassCastFlags & CASTCLASS_UStruct) ? ((UStruct*)GetOuter())->GetPropertiesSize() : 0, GetMinAlignment()); } void UDoubleProperty::CopySingleValue( void* Dest, void* Src, UObject* SubobjectRoot/*=NULL*/, UObject* DestOwnerObject/*=NULL*/, FObjectInstancingGraph* InstanceGraph/*=NULL*/ ) const { *(DOUBLE*)Dest = *(DOUBLE*)Src; } void UDoubleProperty::CopyCompleteValue( void* Dest, void* Src, UObject* SubobjectRoot/*=NULL*/, UObject* DestOwnerObject/*=NULL*/, FObjectInstancingGraph* InstanceGraph/*=NULL*/ ) const { if( ArrayDim==1 ) { *(DOUBLE*)Dest = *(DOUBLE*)Src; } else { appMemcpy( Dest, Src, ArrayDim*ElementSize ); } } UBOOL UDoubleProperty::Identical( const void* A, const void* B, DWORD PortFlags ) const { return *(DOUBLE*)A == (B ? *(DOUBLE*)B : 0); } void UDoubleProperty::SerializeItem( FArchive& Ar, void* Value, INT MaxReadBytes, void* Defaults ) const { Ar << *(DOUBLE*)Value; } UBOOL UDoubleProperty::NetSerializeItem( FArchive& Ar, UPackageMap* Map, void* Data ) const { Ar << *(DOUBLE*)Data; return 1; } FString UDoubleProperty::GetCPPType( FString* ExtendedTypeText/*=NULL*/, DWORD CPPExportFlags/*=0*/ ) const { return TEXT("DOUBLE"); } FString UDoubleProperty::GetCPPMacroType( FString& ExtendedTypeText ) const { return TEXT("DOUBLE"); } void UDoubleProperty::ExportTextItem( FString& ValueStr, const BYTE* PropertyValue, const BYTE* DefaultValue, UObject* Parent, INT PortFlags ) const { ValueStr += FString::Printf( TEXT("%f"), *(DOUBLE*)PropertyValue ); } const TCHAR* UDoubleProperty::ImportText( const TCHAR* Buffer, BYTE* Data, INT PortFlags, UObject* Parent, FOutputDevice* ErrorText ) const { if ( !ValidateImportFlags(PortFlags,ErrorText) ) return NULL; if ( *Buffer == TCHAR('+') || *Buffer == TCHAR('-') || *Buffer == TCHAR('.') || (*Buffer >= TCHAR('0') && *Buffer <= TCHAR('9')) ) { // only import this value if Buffer is numeric *(DOUBLE*)Data = appAtod(Buffer); while( *Buffer == TCHAR('+') || *Buffer == TCHAR('-') || *Buffer == TCHAR('.') || (*Buffer >= TCHAR('0') && *Buffer <= TCHAR('9')) ) { Buffer++; } if ( *Buffer == TCHAR('f') || *Buffer == TCHAR('F') ) { Buffer++; } } return Buffer; } UBOOL UDoubleProperty::HasValue( const BYTE* Data, DWORD PortFlags/*=0*/ ) const { if ( (PortFlags&PPF_LocalizedOnly) != 0 && !IsLocalized() ) { return FALSE; } return *(DOUBLE*)Data != 0.f; } void UDoubleProperty::ClearValue( BYTE* Data, DWORD PortFlags/*=0*/ ) const { // if we only want to clear localized values and this property isn't localized, don't clear the value if ( (PortFlags&PPF_LocalizedOnly) != 0 && !IsLocalized() ) { return; } *(DOUBLE*)Data = 0.f; } UBOOL UDoubleProperty::GetPropertyValue( BYTE* PropertyValueAddress, UPropertyValue& out_PropertyValue ) const { UBOOL bResult = FALSE; if ( PropertyValueAddress != NULL ) { out_PropertyValue.DoubleValue = *(DOUBLE*)PropertyValueAddress; bResult = TRUE; } return bResult; } UBOOL UDoubleProperty::SetPropertyValue( BYTE* PropertyValueAddress, const UPropertyValue& PropertyValue ) const { UBOOL bResult = FALSE; if ( PropertyValueAddress != NULL ) { *(DOUBLE*)PropertyValueAddress = PropertyValue.DoubleValue; bResult = TRUE; } return bResult; } IMPLEMENT_CLASS(UDoubleProperty);由於UDoubleProperty是固有類別,需要在CoreClasses.h中手動加入註冊:
#define AUTO_INITIALIZE_REGISTRANTS_CORE \ ... UDoubleProperty::StaticClass(); \也需要在SavePackage.cpp中加入核心類別之一:
void InitializeCoreClasses() { // initialize the tracking maps with the core classes UClass* CoreClassList[]= { ... UDoubleProperty::StaticClass(), ... };除了四則運算外,也需要為double加入對應的常數。另外為了可以在UnrealScript中支援log函式,順便加了轉字串的功能。這些函式需要宣告在UnObjBas.h裡:
class UObject { ... DECLARE_FUNCTION(execDoubleConst); DECLARE_FUNCTION(execDoubleToString); DECLARE_FUNCTION(execAdd_DoubleDouble); DECLARE_FUNCTION(execSubtract_DoubleDouble); DECLARE_FUNCTION(execMultiply_DoubleDouble); DECLARE_FUNCTION(execDivide_DoubleDouble);我們需要在UnStack.h為double常數和字串轉換定義對應的列舉值,分別對應到GNatives和GConversions陣列的索引值:
enum EExprToken { ... EX_DoubleConst = 0x5C, ... }; enum ECastToken { ... CST_DoubleToString = 0x61, ... }; struct FFrame : public FOutputDevice { ... DOUBLE ReadDouble();編譯器在剖析時會給每個內建型別一個整數值作為識別,所以要在UnScript.h的EPropertyType中加入對應double型別的成員:
enum EPropertyType { ... CPT_Double = 15, CPT_MAX = 16, }; inline DOUBLE FFrame::ReadDouble() { DOUBLE Result; #ifdef REQUIRES_ALIGNED_ACCESS appMemcpy(&Result, Code, sizeof(DOUBLE)); #else Result = *(DOUBLE*)Code; #endif Code += sizeof(DOUBLE); return Result; } inline VariableSizeType FFrame::ReadVariableSize( UField** ExpressionField/*=NULL*/ ) { ... if ( Field != NULL ) ... else { switch ( NullPropertyType ) { case CPT_Double: Result = sizeof(DOUBLE); break;加入新的指令時,要在ScriptSerialization.h中加入對應的序列化。不過由於op code大於等於EX_ExtendedNative的指令就不用,這邊就只需要處理double常數。
#ifdef SERIALIZEEXPR_INC ... else switch( Expr ) { ... case EX_DoubleConst: { XFER(DOUBLE); break; } ...編譯器在剖析時會把結果轉存token物件方便後續處理,為了新的double型別,也要加入對應的處理:
class FToken : public FPropertyBase { ... union { ... DOUBLE Double; // If CPT_Double. }; FString GetValue() { if ( TokenType == TOKEN_Const ) { if (IsVector()) ... else { switch ( Type ) { case CPT_Double: return FString::Printf(TEXT("%f"), Double); } } } void AttemptToConvertConstant( const FPropertyBase& NewType ) { check(TokenType==TOKEN_Const); switch( NewType.Type ) { case CPT_Double: {DOUBLE V(0.f); if( GetConstDouble (V) ) SetConstDouble (V); break;} ... } } void SetConstDouble( DOUBLE InDouble ) { (FPropertyBase&)*this = FPropertyBase(CPT_Double); Double = InDouble; TokenType = TOKEN_Const; } UBOOL GetConstDouble( DOUBLE& R ) const { if( TokenType==TOKEN_Const && Type==CPT_Double ) { R = Double; return 1; } else if( TokenType==TOKEN_Const && Type==CPT_Float ) { R = Float; return 1; } else if( TokenType==TOKEN_Const && Type==CPT_Int ) { R = Int; return 1; } else if( TokenType==TOKEN_Const && Type==CPT_Byte ) { R = Byte; return 1; } else return 0; } };編譯器的主要程式碼放在UnScrCom.cpp裡,因為增加了一個CPT_Double,所以也要對GConversions陣列作對應的調整。為了節省篇幅,在此就只實作字串轉換,其他整數和float對double間的轉換就留給讀者練習。由於CPT_Double是最後一個,所以GConversions最後一列就是對double的轉換,在此都填上CST_Max代表不能轉換。
static EName PropertyTypeToNameMap[CPT_MAX] = { ... // add new property types here NAME_DoubleProperty, }; static DWORD GConversions[CPT_MAX][CPT_MAX] = /* String */ { CST_Max, CST_ByteToString, CST_IntToString, CST_BoolToString, CST_FloatToString, CST_ObjectToString,CST_NameToString,CST_DelegateToString,CST_InterfaceToString,CST_Max, CST_Max, CST_VectorToString, CST_RotatorToString, CST_Max, CST_Max, CST_DoubleToString, }, ... /* Double */ { CST_Max, CST_Max, CST_Max, CST_Max, CST_Max, CST_Max, CST_Max, CST_Max, CST_Max, CST_Max, CST_Max, CST_Max, CST_Max, CST_Max, CST_Max, CST_Max, }, }; void FScriptCompiler::EmitConstant( FToken& Token ) { check(Token.TokenType==TOKEN_Const); switch( Token.Type ) { case CPT_Double: { Writer << EX_DoubleConst; Writer << Token.Double; break; } ... }在UnScrCom.cpp裡原本CONVERSION_TRUNCATION的值是104,但為了新增CONVERSION_INT_TO_DOUBLE所以只好加一。這些值表示轉換的成本,成本越小的轉換會被優先採用。為了維持原來的運算結果不變,所以讓int轉成double的成本比float高,這樣如果同時有float和double版本的運算子重載時,int引數會傾向採用原本的float版本。
/*----------------------------------------------------------------------------- Type conversion. -----------------------------------------------------------------------------*/ #define CONVERSION_INT_TO_FLOAT 103 #define CONVERSION_INT_TO_DOUBLE 104 #define CONVERSION_TRUNCATION 105 INT FScriptCompiler::ConversionCost ( ... else if( (Source.Type==CPT_Int || Source.Type==CPT_Byte) && Dest.Type==CPT_Double ) { // Conversion to float. //AddResultText("ConvertToFloat\r\n"); return CONVERSION_INT_TO_DOUBLE; } } UBOOL FScriptCompiler::CompileExpr ( ... else { // save this token in case we need it after recursing further (ex. for dynarray functions below) LastToken = Token; FScriptLocation AfterLastTokenLocation; if( Token.TokenName == NAME_None && RequiredType.Type == CPT_Delegate && Token.TokenType == TOKEN_Const ) ... else if (( Token.TokenName==NAME_Byte || Token.TokenName==NAME_Real ... { ... if( Token.TokenName==NAME_Vector ) ... else { EPropertyType T = Token.TokenName==NAME_Byte ? CPT_Byte : Token.TokenName==NAME_Real ? CPT_Double ... } UBOOL FScriptCompiler::GetVarType ( ... if( !GetIdentifier(VarType,1) ) ... else if( VarType.Matches(NAME_Real) ) { // Intrinsic Real type VarProperty = FPropertyBase(CPT_Double); } ... } const TCHAR* GetPropertyTypeText( EPropertyType Type ) { switch ( Type ) { CASE_TEXT(CPT_Double); ... }在UnScriptMacros.h中有一些方便從UnrealScript堆疊取出變數的巨集,也需要加入對應double的相關巨集:
#define P_GET_DOUBLE(var) DOUBLE var=0.f; Stack.Step( Stack.Object, &var ); #define P_GET_DOUBLE_OPTX(var,def) DOUBLE var=def; INIT_OPTX_EVAL Stack.Step( Stack.Object, &var ); #define P_GET_DOUBLE_REF(var) DOUBLE var##T=0.f; GPropAddr=0; Stack.Step( Stack.Object, &var##T ); if( GPropObject )GPropObject->NetDirty(GProperty); DOUBLE* p##var = (DOUBLE *)GPropAddr; DOUBLE& var = GPropAddr ? *(DOUBLE *)GPropAddr:var##T; #define P_GET_DOUBLE_OPTX_REF(var,def) DOUBLE var##T=def; GPropAddr=0; INIT_OPTX_EVAL Stack.Step( Stack.Object, &var##T ); if( GPropObject )GPropObject->NetDirty(GProperty); DOUBLE* p##var = (DOUBLE *)GPropAddr; DOUBLE& var = GPropAddr ? *(DOUBLE *)GPropAddr:var##T;最後是四則運算和其他double指令的實作:
/////////////////////////////////// // Double operators and functions // /////////////////////////////////// void UObject::execAdd_DoubleDouble( FFrame& Stack, RESULT_DECL ) { P_GET_DOUBLE(A); P_GET_DOUBLE(B); P_FINISH; *(DOUBLE*)Result = A + B; } IMPLEMENT_FUNCTION( UObject, 1111, execAdd_DoubleDouble ); void UObject::execSubtract_DoubleDouble( FFrame& Stack, RESULT_DECL ) { P_GET_DOUBLE(A); P_GET_DOUBLE(B); P_FINISH; *(DOUBLE*)Result = A - B; } IMPLEMENT_FUNCTION( UObject, 1112, execSubtract_DoubleDouble ); void UObject::execMultiply_DoubleDouble( FFrame& Stack, RESULT_DECL ) { P_GET_DOUBLE(A); P_GET_DOUBLE(B); P_FINISH; *(DOUBLE*)Result = A * B; } IMPLEMENT_FUNCTION( UObject, 1113, execMultiply_DoubleDouble ); void UObject::execDivide_DoubleDouble( FFrame& Stack, RESULT_DECL ) { P_GET_DOUBLE(A); P_GET_DOUBLE(B); P_FINISH; if (B == 0.0) { Stack.Logf(NAME_ScriptWarning,TEXT("Divide by zero")); } *(DOUBLE*)Result = A / B; } IMPLEMENT_FUNCTION( UObject, 1114, execDivide_DoubleDouble ); void UObject::execDoubleConst( FFrame& Stack, RESULT_DECL ) { *(DOUBLE*)Result = Stack.ReadDouble(); } IMPLEMENT_FUNCTION( UObject, EX_DoubleConst, execDoubleConst ); void UObject::execDoubleToString( FFrame& Stack, RESULT_DECL ) { P_GET_DOUBLE(D); *(FString*)Result = FString::Printf(TEXT("%.6f"),D); } IMPLEMENT_CAST_FUNCTION( UObject, CST_DoubleToString, execDoubleToString );編譯完成後,就可以在UnrealScript中使用double型別了:
exec function TestReal(real A, real B) { `log( A + B ); `log( A - B ); `log( A * B ); `log( A / B ); }甚至連UnrealScript Debugger現在也都可以支援double型別,在Watch視窗中正確顯示出的數值。
沒有留言:
張貼留言