由於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視窗中正確顯示出的數值。
沒有留言:
張貼留言