• unreal3对象属性自动从配置文件中加载的机制


    unrealscript中有两个与属性自动配置相关的关键字:

    config/globalconfig

    当把它们应用于属性时,对象在创建后,该属性的初始值会被自动设置为相对应ini文件中的值。

    举例来说,如有一个类:

    class HNet extends Object
        config(game)
        native(net);
    //var globalconfig string host;
    var config string host;
    function test() { `Log("HNet test, host is:"@host); }

    声明一个带config修饰的属性host,

    那么在创建该类实例时,其host会自动设为配置在DefaultGame.ini中的值,其section为【包名.类名】,key就是属性名【host】:

    [HGame.HNet]
    host=hz.19v5.com

    如果ini中没有配置的话,默认值就是空了(或者说该类型自身的默认值)

    那么globalconfig区别何在呢?这就与子类的变化有关了

    假设有一个子类:

    class HHttp extends HNet
        config(Net)
        ;
        
    function test()
    {
        `Log("HHttp test, host is:"@host);
    }

    它有自己的config文件,那么它的host属性,就会去自己的ini中寻找,上例中就是DefaultNet.ini了,

    [HGame.HHttp]
    host=baidu.com

    如果它里面也配置了相应字段,那么在HHttp的实例中,host的值会是baidu.com,而非hz.19v5.com。

    而如果作为基类的HNet有这样一种需求:认为其host属性的初始值很重要,不应该被子类乱改,那么就可以改用globalconfig:

    用globalconfig修饰的属性,一定会在它的当前类绑定的ini文件中寻找初始值,而无视实际所处子类的ini变更。

    在上例中如果host前使用的是globalconfig,那么即使是HHttp类的实例,其host初始值也仍是hz.19v5.com。

    功能基本如上所述,下面归纳一下代码中的实现逻辑:

    1、首先,UnObjBas.h里定了对应这两个修饰符的标记:

    #define CPF_Config       DECLARE_UINT64(0x0000000000004000) // Property should be loaded/saved as permanent profile.
    #define CPF_GlobalConfig DECLARE_UINT64(0x0000000000040000) // Load config from base class, not subclass.

    2、在编译脚本时UnSrcCom.cpp,检测到属性声明中有这两修饰符时,会贴上相应标记:

            FToken Specifier;
            GetToken(Specifier);
            if( Specifier.Matches(NAME_Const) )
            {
                Flags      |= CPF_Const;
                IsVariable  = TRUE;
            }
            else if( Specifier.Matches(NAME_Config) )
            {
                Flags      |= CPF_Config;
                IsVariable  = TRUE;
            }
            else if( Specifier.Matches(NAME_GlobalConfig) )
            {
                Flags      |= CPF_GlobalConfig | CPF_Config;
                IsVariable  = TRUE;
            }

    3、生成的UClass结构里两个关键属性:字段表和默认对象

    UField*          Children
    UObject*          ClassDefaultObject

    在脚本里声明的每个属性都是一个UProperty变量,而它正是UField的子类:

    //
    // An UnrealScript variable.
    //
    class UProperty : public UField
    {
        DECLARE_ABSTRACT_CASTED_CLASS_INTRINSIC(UProperty,UField,0,Core,CASTCLASS_UProperty)
        DECLARE_WITHIN(UField)
    
        // Persistent variables.
        INT            ArrayDim;
        INT            ElementSize;
        QWORD        PropertyFlags;
        WORD            RepOffset;
        WORD            RepIndex;
    

    另外,“默认对象”ClassDefaultObject是unrealscript里的一个特殊概念,每个UClass类都有一个,其作用是:

    a、在类加载时,先把ClassDefaultObject创建好,并把各属性初始值(defaultproperties块里的、config文件里的)设置好;
    b、以后每次创建实例对象时,都从ClassDefaultObject上拷贝各属性的初始值。

    4、创建ClassDefaultObject的地方:

    native类:也就是每个UClass初始化时Register自己的地方 纯脚本类:很明显,就是从package里加载数据,刚好加载到一个类时

    5、ClassDefaultObject读取初始值的地方:

    与脚本类的创建不远,同在LoadAllObjects里。

    LoadConfig里的逻辑很复杂,但总括起来就两点:去哪里找数据和怎么读,各自都依据情形有大量衍生。但基本的读数据过程都是:

        for ( UProperty* Property = ConfigClass->PropertyLink; Property; Property = Property->PropertyLinkNext )
        {
            
            const UBOOL bGlobalConfig = (Property->PropertyFlags&CPF_GlobalConfig) != 0;
            UClass* OwnerClass = Property->GetOwnerClass();
    
            UClass* BaseClass = bGlobalConfig ? OwnerClass : ConfigClass;
            if ( !bPerObject )
            {
                ClassSection = BaseClass->GetPathName();
            }
    
            // globalconfig properties should always use the owning class's config file
            // specifying a value for InFilename will override this behavior (as it does with normal properties)
            const FString& PropFileName = (bGlobalConfig && InFilename == NULL) ? OwnerClass->GetConfigName() : Filename;
    
            FString Key = Property->GetName();
            debugfSuppressed(NAME_DevSave, TEXT("   Loading value for %s from [%s]"), *Key, *ClassSection);
            UArrayProperty* Array = Cast<UArrayProperty>( Property );
            if( Array == NULL )
            {
                for( INT i=0; i<Property->ArrayDim; i++ )
                {
                    if( Property->ArrayDim!=1 )
                    {
                        Key = FString::Printf(TEXT("%s[%i]"), *Property->GetName(), i);
                    }
    
                    FString Value;
                    if( GConfig->GetString( *ClassSection, *Key, Value, *PropFileName ) )
                    {
                        if (Property->ImportText(*Value, (BYTE*)this + Property->Offset + i*Property->ElementSize, PPF_ConfigOnly, this) == NULL)
                        {
                            // this should be an error as the properties from the .ini / .int file are not correctly being read in and probably are affecting things in subtle ways
                            warnf(NAME_Error, TEXT("LoadConfig (%s): import failed for %s in: %s"), *GetPathName(), *Property->GetName(), *Value);
                        }
              }
           }
         }
         else
           ……
       ……
       }
    }

    也就是遍历每个属性ConfigClass->PropertyLink,逐个解析其配置源、类型(单个还是数组)等问题后,然后一个Property->ImportText,就把数据读进来了。

    注意这里对bGlobalConfig的判断:

     const FString& PropFileName = (bGlobalConfig && InFilename == NULL) ? OwnerClass->GetConfigName() : Filename;

    也就是据此决定了从哪个类的配置文件中去获取数据,实现了上面提到的功能。

    6、普通实例创建时从ClassDefaultObject中复制属性数据

    也就是在UObject::InitProperties中,完成必要属性(有设定过初值的)的复制:

    // Construct anything required.
        if( DefaultsClass && DefaultData )
        {
            for( UProperty* P=DefaultsClass->ConstructorLink; P; P=P->ConstructorLinkNext )
            {
                if( P->Offset < DefaultsCount )
                {
                    // skip if SourceOwnerObject != NULL and this is a transient property - in this
                    // situation, the new value for the property has already been copied from the class defaults
                    // in the block of code above
                    if ( SubobjectRoot == NULL || !P->HasAnyPropertyFlags(CPF_Transient|CPF_DuplicateTransient) )
                    {
                        // the heap memory allocated at this address is owned the source object, so
                        // zero out the existing data so that the UProperty code doesn't attempt to
                        // de-allocate it before allocating the memory for the new value
                        appMemzero( Data + P->Offset, P->GetSize() );//bad for bools, but not a real problem because they aren't constructed!!
                        P->CopyCompleteValue( Data + P->Offset, DefaultData + P->Offset, SubobjectRoot ? SubobjectRoot : DestObject, DestObject, InstanceGraph );
                    }
                }
            }
        }

    总结:通过以上6步,实现了unrealscript class里带config/globalconfig修饰符属性的自动从配置中加载的功能。

  • 相关阅读:
    如何解决Windows 10系统下设备的声音问题
    mutex与semaphore的区别
    大端与小端,大尾与小尾,高尾端与低尾端,主机字节序与网络字节序
    详解C语言的htons和htonl函数、大尾端、小尾端
    sockaddr与sockaddr_in结构体简介
    使用socket()函数创建套接字
    struct socket 结构详解
    C语言函数sscanf()的用法
    使用 Socket 通信实现 FTP 客户端程序(来自IBM)
    C语言文件的读写
  • 原文地址:https://www.cnblogs.com/wellbye/p/5372616.html
Copyright © 2020-2023  润新知