2012年10月10日 星期三

在C++中存取UnrealScript物件屬性

在C++中要存取UnrealScript物件的屬性時,一般的做法是宣告此類別為原生類別,這樣在編譯UnrealScript時會自動產生對應的C++類別定義,就可讓C++認得此類別的成員。不過如果是非原生類別,難道就沒有其他方法可以存取其屬性嗎?答案是當然可以,不然用C++實作的UnrealScript虛擬機就寫不出來了。

由UnrealScript定義的類別必須繼承自Object,它在C++中的對應型別為UObject。由於Object這個名稱容易混淆,所以接下來皆以UObject稱之。每個UObject都會記得自己當初建立時的UnrealScript類別,在UnrealScript中可以用Object:Class屬性取得,在C++中可以用UObject::GetClass()函式取得。它在C++中是一個型別為UClass的物件,記錄著對應UnrealScript類別的相關資訊,可以從這裡得知這個類別包括屬性在內的所有成員。

取得屬性


以下程式碼展示如何在一個UObject中取得其所有屬性:
for( UProperty* Property = GetClass()->PropertyLink; Property; Property = Property->PropertyLinkNext )
{
    ...
}
UnrealScript屬性在C++中的對應型別稱為UProperty,它是一個基底類別,依據屬性的型別有各自的具象類別:


UnrealScript型別 屬性型別 C++型別
bool UBoolProperty BITFIELD
byte UByteProperty BYTE
int UIntProperty INT
float UFloatProperty FLOAT
enum UByteProperty BYTE
name UNameProperty FName
string UStrProperty FString
class UClassProperty UClass*
object UObjectProperty UObject*
component UComponentProperty UComponent*
struct UStructProperty native struct的對應型別
array UArrayProperty FScriptArray
delegate UDelegateProperty FScriptDelegate
interface UInterfaceProperty FScriptInterface

取得屬性值


UProperty有個成員叫做Offset會記錄該屬性在物件裡的位置,所以如果要取得一個INT屬性,利用下列程式碼即可:
(INT*)((BYTE*)this + Property->Offset)
其他的屬性型別也可如法炮製。

不過bool在C++中其實只佔一個位元,需要再用一個BitMask過濾:
(*(BITFIELD*)((BYTE*)this + Property->Offset)) & Property->BitMask
UnrealScript的static array在C++中其實就是內建陣列,它沒有對應的UProperty具象類別,而是將相關資訊記錄在UProperty的成員裡:
(BYTE*)this + Property->Offset + Property->ElementSize * Index
UnrealScript的dynamic array在C++中的對應型別為FScriptArray,UArrayProperty的Inner成員會記錄陣列元素的屬性,當要取得各別元素時可由此得知元素大小:
FScriptArray* Array = (FScriptArray*)((BYTE*)this + ArrayProperty->Offset);
(BYTE*) Array->GetData() + ArrayProperty->Inner->ElementSize * Index
UnrealScript的struct即使沒有定義對應的C++型別,還是可以從UStructProperty取得相關資訊來取出所有成員:
BYTE* Base = (BYTE*)this + StructProperty->Offset;

for( TFieldIterator<UProperty> It(StructProperty->Struct); It; ++It )
{
    Base + (*itr)->Offset
}

範例 


以下程式碼展示如何存取指定路徑的屬性,並且實作存取整數和布林的函式,其他型別都可以以此類推:
class PropertyAccess extends Object
    native;

static native function int GetInt(Object This, string Path, optional int DefaultValue);
static native function bool SetInt(Object This, string Path, int NewValue);        
static native function bool GetBool(Object This, string Path, optional bool DefaultValue);
static native function bool SetBool(Object This, string Path, bool NewValue);    

cpptext
{
    static UProperty* FindProperty( UObject* This, const FString& Path, void*& OutData );
    static UProperty* FindPropertyNonAggr( UProperty* Property, BYTE* Base, const FString& Path, void*& OutData );
    static UProperty* FindPropertyInArray( UArrayProperty* Outer, BYTE* Base, const FString& Path, void*& OutData );
    static UProperty* FindPropertyInStruct( UStructProperty* Outer, BYTE* Base, const FString& Path, void*& OutData );
    static UProperty* FindMemberPropertyInStruct( UStructProperty* Outer, BYTE* Base, const FString& Path, void*& OutData );
    static FName SplitPropertyPath( const FString& Path, INT& OutIndex, FString& OutRemaining );
}    
以下是實作碼:
INT UPropertyAccess::GetInt(UObject* This, FString Path, optional INT DefaultValue)
{
    INT Result = DefaultValue;
   
    void* Data = NULL;
    UProperty* Property = FindProperty( This, Path, Data );
    if( Property )
    {
       UIntProperty* IntProperty = Cast<UIntProperty>(Property);
       if( IntProperty )
       {
           Result = *(INT*)Data;
       }
    }
   
    return Result;
}

UBOOL UPropertyAccess::SetInt(UObject* This, const FString& Path, INT NewValue)
{
    void* Data = NULL;
    UProperty* Property = FindProperty( This, Path, Data );
    if( Property )
    {
       UIntProperty* IntProperty = Cast<UIntProperty>(Property);
       if( IntProperty )
       {
           *(INT*)Data = NewValue;
           return TRUE;
       }
    }
   
    return FALSE;
}

UBOOL UPropertyAccess::GetBool(UObject* This, FString Path, optional UBOOL DefaultValue)
{
    UBOOL Result = DefaultValue;
   
    void* Data = NULL;
    UProperty* Property = FindProperty( This, Path, Data );
    if( Property )
    {
       UBoolProperty* BoolProperty = Cast<UBoolProperty>(Property);
       if( BoolProperty )
       {
           Result = (*(BITFIELD*)Data & BoolProperty->BitMask) ? TRUE : FALSE;
       }
    }
   
    return Result;
}

UBOOL UPropertyAccess::SetBool(UObject* This, const FString& Path, UBOOL NewValue)
{
    void* Data = NULL;
    UProperty* Property = FindProperty( This, Path, Data );
    if( Property )
    {
       UBoolProperty* BoolProperty = Cast<UBoolProperty>(Property);
       if( BoolProperty )
       {
           *(INT*)Data = NewValue;
           return TRUE;
       }
    }
   
    return FALSE;
}

UProperty* UPropertyAccess::FindProperty( UObject* This, const FString& Path, void*& OutData )
{
    if( This )
    {
       for( UProperty* Property = This->GetClass()->PropertyLink; Property; Property = Property->PropertyLinkNext )
       {
           if( UArrayProperty* ArrayProperty = Cast<UArrayProperty>(Property) )
           {
               UProperty* Found = FindPropertyInArray( ArrayProperty, (BYTE*)This + Property->Offset, Path, OutData );
               if( Found )
                   return Found;
           }
           else if( UStructProperty* StructProperty = Cast<UStructProperty>(Property) )
           {
               UProperty* Found = FindPropertyInStruct( StructProperty, (BYTE*)This + Property->Offset, Path, OutData );
               if( Found )
                   return Found;
           }
           else
           {
               UProperty* Found = FindPropertyNonAggr( Property, (BYTE*)This + Property->Offset, Path, OutData );
               if( Found )
                   return Found;
           }            
       }
    }
   
    return NULL;
}

UProperty* UPropertyAccess::FindPropertyNonAggr( UProperty* Property, BYTE* Base, const FString& Path, void*& OutData )
{
    FString RemainingPath;
    INT ElementIndex;
    FName OuterName = SplitPropertyPath( Path, ElementIndex, RemainingPath );
   
    if( Property->GetFName() == OuterName && RemainingPath.Len() == 0 )
    {
       BYTE* ElementData = Base;
       if( ElementIndex >= 0 && ElementIndex < Property->ArrayDim )
       {
           // abc[123]
           ElementData += Property->ElementSize * ElementIndex;
       }
       
       OutData = ElementData;
       return Property;
    }
   
    return NULL;
}

UProperty* UPropertyAccess::FindPropertyInArray( UArrayProperty* Outer, BYTE* Base, const FString& Path, void*& OutData )
{
    FString RemainingPath;
    INT ElementIndex;
    FName OuterName = SplitPropertyPath( Path, ElementIndex, RemainingPath );
   
    if( Outer->GetFName() == OuterName )
    {
       if( ElementIndex == INDEX_NONE )
       {
           if( RemainingPath.Len() == 0 )
           {
               OutData = Base;
               return Outer;
           }
       }
       else
       {
           FScriptArray* Array = (FScriptArray*) Base;
           if( Array->IsValidIndex(ElementIndex) )
           {
               BYTE* ElementData = (BYTE*) Array->GetData() + Outer->Inner->ElementSize * ElementIndex;
               if( RemainingPath.Len() == 0 )
               {
                   OutData = ElementData;
                   return Outer->Inner;
               }
               else if( UStructProperty* StructProperty = Cast<UStructProperty>( Outer->Inner ) )
               {
                   return FindMemberPropertyInStruct( StructProperty, ElementData, RemainingPath, OutData );
               }
           }
       }
    }
   
    return NULL;
}

UProperty* UPropertyAccess::FindPropertyInStruct( UStructProperty* Outer, BYTE* Base, const FString& Path, void*& OutData )
{
    FString RemainingPath;
    INT ElementIndex;
    FName OuterName = SplitPropertyPath( Path, ElementIndex, RemainingPath );
   
    if( Outer->GetFName() == OuterName )
    {
       BYTE* ElementData = Base;
       if( ElementIndex >= 0 && ElementIndex < Outer->ArrayDim )
       {
           ElementData += Outer->ElementSize * ElementIndex;
       }
       
       if( RemainingPath.Len() == 0 )
       {
           // abc or abc[123]
           OutData = ElementData;
           return Outer;
       }
       else
       {
           // abc.zyz or abc[123].xyz
           return FindMemberPropertyInStruct( Outer, ElementData, RemainingPath, OutData );
       }
    }
   
    return NULL;
}

UProperty* UPropertyAccess::FindMemberPropertyInStruct( UStructProperty* Outer, BYTE* Base, const FString& Path, void*& OutData )
{
    for( TFieldIterator<UProperty> itr(Outer->Struct); itr; ++itr )
    {
       UProperty* MemberProperty = *itr;
       if( UArrayProperty* ArrayProperty = Cast<UArrayProperty>(MemberProperty) )
       {
           UProperty* Found = FindPropertyInArray( ArrayProperty, (BYTE*)Base + MemberProperty->Offset, Path, OutData );
           if( Found )
               return Found;
       }
       else if( UStructProperty* StructProperty = Cast<UStructProperty>(MemberProperty) )
       {
           UProperty* Found = FindPropertyInStruct( StructProperty, (BYTE*)Base + MemberProperty->Offset, Path, OutData );
           if( Found )
               return Found;
       }
       else
       {
           UProperty* Found = FindPropertyNonAggr( MemberProperty, (BYTE*)Base + MemberProperty->Offset, Path, OutData );
           if( Found )
               return Found;
       }  
    }
   
    return NULL;
}

FName UPropertyAccess::SplitPropertyPath( const FString& Path, INT& OutIndex, FString& OutRemaining )
{
    FName OuterName;
    INT DotPos = Path.InStr( TEXT(".") );
    INT IndexLeftPos = Path.InStr( TEXT("[") );
    INT IndexRightPos = Path.InStr( TEXT("]") );
    if( DotPos == INDEX_NONE )
    {
       if( IndexLeftPos != INDEX_NONE && IndexRightPos != INDEX_NONE &&
           IndexLeftPos < IndexRightPos && IndexRightPos == Path.Len() - 1 )
       {
           // abc[123]
           OuterName = *Path.Left(IndexLeftPos);
           OutIndex = appAtoi( *Path.Mid(IndexLeftPos+1) );
       }
       else
       {
           // abc
           OuterName = *Path;
           OutIndex = INDEX_NONE;
       }
    }
    else
    {
       if( IndexLeftPos != INDEX_NONE && IndexRightPos != INDEX_NONE &&
           IndexLeftPos < IndexRightPos && IndexRightPos == DotPos - 1 )
       {
           // abc[123].xyz
           OuterName = *Path.Left(IndexLeftPos);
           OutIndex = appAtoi( *Path.Mid(IndexLeftPos+1) );
           OutRemaining = Path.Mid(DotPos+1);
       }
       else
       {
           // abc.xyz
           OuterName = *Path.Left(DotPos);
           OutIndex = INDEX_NONE;
           OutRemaining = Path.Mid(DotPos+1);
       }    
    }
   
    return OuterName;
}
為了縮短範示程式的長度,SplitPropertyPath()並沒有去處理錯誤的輸入路徑。

沒有留言:

張貼留言