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,,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) != ;
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=; i<Property->ArrayDim; i++ )
{
if( Property->ArrayDim!= )
{
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修饰符属性的自动从配置中加载的功能。

unreal3对象属性自动从配置文件中加载的机制的更多相关文章

  1. Winform中设置DevExpress的RadioGroup的items从配置文件中加载

    场景 DevExpress的RadioGroup的items选项如果是不确定的话,需要其从配置文件中加载. 实现 在项目目录下新建Config文件夹,文件夹下新建xml配置文件. <?xml v ...

  2. Prism 学习:从配置文件中加载 Module

    之前我们已经了解过,如何从指定的目录中来加载 Module(原文),现在我们来看,如何从应用程序的配置文件中来加载 Module.以这种方式来加载 Module 的优点包括:1. 被加载的 Modul ...

  3. spring 配置参数从配置文件中加载到PropertiesFactoryBean 和配置参数从数据库加载到PropertiesFactoryBean 的实现,及项目中的相关应用

    1.加载.properties文件中的配置参数加载到PropertiesFactoryBean容器中 <bean id="configProperties" class=&q ...

  4. Spring中查看加载配置文件中 加载类的个数及详情

    断点到: org.springframework.beans.factory.support.DefaultListableBeanFactory#getBeanDefinitionCount 显示该 ...

  5. C#构造方法(函数) C#方法重载 C#字段和属性 MUI实现上拉加载和下拉刷新 SVN常用功能介绍(二) SVN常用功能介绍(一) ASP.NET常用内置对象之——Server sql server——子查询 C#接口 字符串的本质 AJAX原生JavaScript写法

    C#构造方法(函数)   一.概括 1.通常创建一个对象的方法如图: 通过  Student tom = new Student(); 创建tom对象,这种创建实例的形式被称为构造方法. 简述:用来初 ...

  6. Flexigrid从对象中加载数据

    (有问题,在找…………) Flexigrid是用来动态加载数据的一种比较好(老)的Jquery表插件,然后有些时候,我们需要其从本地或者jQuery对象中加载数据,比如有这么个需求,页面显示中有两个表 ...

  7. web 中加载配置文件

    1.web.xml中配置   <!-- 加载配置文件 -->   <listener>      <description>ServletContextListen ...

  8. web.xml中如何设置配置文件的加载路径

    web应用程序通过Tomcat等容器启动时,会首先加载web.xml文件,通常我们工程中的各种配置文件,如日志.数据库.spring的文件等都在此时被加载,下面是两种常用的配置文件加载路径,即配置文件 ...

  9. Spring中加载xml配置文件的六种方式

    Spring中加载xml配置文件的六种方式 博客分类: Spring&EJB XMLSpringWebBeanBlog  因为目前正在从事一个项目,项目中一个需求就是所有的功能都是插件的形式装 ...

随机推荐

  1. make自动生成依赖文件的两种形式

    最近编译源文件发现当修改头文件后,make并不会自动把包含此头文件的所有源文件重新编译,而每次都是需要把对应的中间文件清除才能重新编译,非常的麻烦.因此需要make自动对源文件所依赖的头文件进行管理, ...

  2. 在同一个机器上运行两个jboss修改配置

    http://blog.sina.com.cn/s/blog_8ebe17aa0101mnft.html 解决办法:修改 \jboss-4.0.4.GA\server\default\conf 目录下 ...

  3. WebApi:自定义筛选器

    最近在项目中有这样一个需求,记录每次Api访问的调用时间,运行时间,传入数据,返回数据等信息. 第一反应就是添加一个类,用来实现相应的功能,然后在方法的代码中添加,但是这样的话,需要修改所有的方法的代 ...

  4. Linux中cp和scp命令的使用方法

    Linux为我们提供了两个用于文件copy的命令,一个是cp,一个是scp,但是他们略有不同. cp --- 主要是用于在同一台电脑上,在不同的目录之间来回copy文件 scp --- 主要是在不同的 ...

  5. css绝对定位问题

    <!doctype html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  6. python遍历删除列表的方法

    for item in list(somelist): somelist.remove(item)

  7. 使用php添加定时任务

    1.  php执行外部命令的函数:       system(),exec(),passthru()      注意点:     1.调用的路径,相对路径有时候不是很靠谱.           sys ...

  8. Android将Activity 打 jar包 (解决资源文件不能打包的问题)

    转载地址:http://blog.csdn.net/xiaanming/article/details/9257853 最近有一个需要,我们公司做了一个apk客户端,然后其他的公司可以根据自己的需要来 ...

  9. 写CSS的布局

    刚写页面的时候写CSS觉得一个选择器中的每个声明分别占一行看起来舒服些,但随着页面大了,写的东西多了,看起来就很乱了.所以每个声明连着写其实更加好些

  10. 性能测试框架Locust初学笔记

    Locust初探 Locust是一款类似于Jmeter开源负载测试工具,所不同的是它是用python实现,并支持python脚本. locust提供web ui界面,能够方便用户实时监控脚本运行状态. ...