Skip to content

Messiah美术资源检查流程

介绍

本文是对ArtEase资源检查流程的介绍,重点介绍了如何针对Messiah美术资源进行正确性检查。目前线上运行的代码版本是1.x。 2.0版本是正在开发中的全新Messiah资源正确性检查,在检查性能、易用性方面着重进行了优化工作,并拓宽了支持的功能,将于近期上线。2.0版本将尽量兼容1.x版本的设定, 但也有少数设定将不再兼容或推荐使用。后文介绍中,以2.0版本标注的内容,将仅适用于2.0之后的版本。

规则定义

检查规则的规则包括两部分,一部分是检查规则的描述,用于显示和出错信息中,比如name字段表示规则名称,严重程度字段表示规则的严重性。 这些字段仅用于显示,不参与资源检查流程,这里不做更多介绍。
另一类字段是检查规则的定义,其值将传入检查过程的对应步骤,下面将一一介绍。

  • rpath:值为正则表达式,或者是一个预定义的文件集合。

当该字段以artfunc_开头时,表明该规则要检查的文件列表是一个预定义的集合。可用的预定义集合见下文内容。

当该正则表达式能够匹配待检查文件的绝对路径时,该文件将会被该规则检查。

  • not_rpath:值为正则表达式。

当该正则表达式能够匹配待检查文件的绝对路径时,该文件将不会被该规则检查。

  • xpath/subxpath:值为字符串类型。
    根据检查文件类型的不同,该字段有不同的写法,后文将会进行介绍。 该字段用于从待检查文件中提取特定的值,参与后续检查流程。

  • filter/subfilter:值为正则表达式,或者是执行的函数名。
    该字段用于过滤或转换上一步提取到的值。

当该字段以artfunc_开头时,表明该步骤将要执行一个函数,函数可以是内置的函数,或者是自定义函数。

当该字段的值为正则表达式时,当该正则表达式能匹配上一步返回的数据时,才继续后续的检查流程。

  • not_filter:值为正则表达式。

当该正则表达式能够匹配上一步返回的数据时,将不会继续后续的检查流程。

  • condition:值为列表格式。

第一个元素的值为检查方法,如果该方法以func_开头,则执行内置的或自定义检查函数,其余元素均为检查方法的参数。

2.0版本还支持以messiah_condfunc.开头的字符串,表示执行messiah_condfunc模块中的检查函数,该模块中的检查函数通常为某个检查规则专用。

下文将介绍该字段可用的检查方法,以及各个方法的参数要求。 该步骤的返回结果,决定了该规则对当前检查文件的检查结果。

  • func_str:自定义函数的代码。
    将在filter, condition, subfilter字段中,通过设置其字段的值符合执行函数的格式,以在检查过程中调用。
    例如,检查函数的函数名为custom_func,则在filter/subfilter字段的值为artfunc_custom_func, 或者condition字段值列表的第一个元素为func_custom_func,都会调用到该自定义函数。

自定义函数接收的参数,以及返回值的格式,将在下文说明。

检查流程

规则的检查流程示意图如下: flow
对于一条检查规则来说,其中rpath, xpath, condition三个字段是必须的,其他字段是可选的。

每个步骤接收上一个步骤的返回值作为其输入参数,condition步骤的返回值将作为规则对该文件的检查结果。

  • rpath: 返回检查目录中,满足路径符合rpath正则表达式的所有文件的路径列表
  • not_rpath: 接收文件路径列表,返回不满足not_rpath正则表达式的文件路径列表
  • xpath: 接收文件路径列表,返回从这些文件中提取到的数据的列表,返回列表中的每一个元素,是从某一个文件中提取出的数据,通常也是列表格式。
  • filter: 接收一组数据,返回正则过滤或者执行函数转换后的一组数据。
  • not_filter: 接收一组数据,返回其正则不能匹配的数据。
  • subxpath: 与xpath相同的输入输出格式。通常当filter为res_filter.guidToRealPath或者res_filter.guidToResourcePath(2.0版本)时,会有subxpath与subfilter步骤。
  • subfilter: 与filter相同的输入输出格式,接收subxpath的输出结果。
  • condition: 接收一组数据,并依次检查其中每个数据是否满足检查条件,不满足的数据将作为错误资源数据返回。

xpath格式

Xpath字段用于从特定的文件中提取出需要的数据,根据文件类型的不同,xpath字段的格式也有所不同。 由于messiah资源信息文件(包括resource.repository, .iworld, .ilevel文件)都是xml格式,此处将详细介绍xml文件xpath字段的格式。

xml格式文件的xpath

注意:当前线上版本(1.x)的部分xpath写法在即将上线版本(2.0)中不支持,且在2.0版本中支持了更多的格式,请注意标签中注明的版本信息。

注意:1.x版本使用python的xml.etree.ElementTree模块进行xml解析,2.0版本使用lxml.etree模块进行xml解析。

对于xml文件,提取数据包括三个步骤:
1. 找到特定的节点(Element); 2. 根据节点的值、子节点的值以及节点的属性(attribute),判断是否是需要提取数据的节点; 3. 根据表达式的定义,提取出需要的值。

xml格式文件的xpath表达式即由这三部分组成。我们从一个简单的例子开始,介绍xpath表达式的格式。

这是messiah资源中一个典型的resource.repository文件:

<?xml version="1.0" encoding="utf-8"?>
<Repository>
    <Items>
        <Item>
            <Type>Material</Type>
            <Flags>0</Flags>
            <GUID>914fa3a0-62cd-4b5a-83df-92fdf494534a</GUID>
            <Package>Test/Test_Demo</Package>
            <Class>Material</Class>
            <Deps>9dc97d9a-64b5-49fb-acfa-c1704ebc2ef2</Deps>
            <Deps>12345678-9876-1234-abcd-1234567890ab</Deps>
            <Name>Hole_mat</Name>
            <Annotation>
                <SourcePath />
                <CreationTime>0</CreationTime>
            </Annotation>
        </Item>
        <Item>
            <Type>Material</Type>
            <Flags>0</Flags>
            <GUID>e081bf41-a561-4750-83af-78c80468bc6c</GUID>
            <Package>Test/Test_Demo</Package>
            <Class>Material</Class>
            <Name>Trim_mat</Name>
            <Annotation>
                <SourcePath />
                <CreationTime>0</CreationTime>
            </Annotation>
        </Item>
        <Item>
            <Type>Mesh</Type>
            <Flags>0</Flags>
            <GUID>e5ce0940-f689-404d-968f-775317804069</GUID>
            <Package>Test/Test_Demo</Package>
            <Class>Mesh</Class>
            <Name>Wall</Name>
            <Annotation>
                <SourcePath />
                <CreationTime>0</CreationTime>
                <Anno>
                  <Key>CollisionShape</Key>
                  <Value>e4ea79e4-3505-4e96-bbc0-bc567a1204e5</Value>
                </Anno>
            </Annotation>
        </Item>
    </Items>
</Repository>

我们想要找到所有类型为Material资源的GUID。那么步骤为:

  1. 找到特定的节点。操作目标是Item节点,该部分的值为节点在xml文件中的层次关系,节点间以/分隔。 本例中,Item节点的路径为Repository/Items/Item。其中根节点可以以*代替,得到*/Items/Item
  2. 判断该节点是否需要提取数据。此处需要检查Item子节点的Type值是否为Material,得到表达式:Type==Material
  3. 需要提取的数据。此处为GUID子节点的值,因此表达式子节点的tag:GUID

步骤1表达式与步骤2表达式之间用英文句号.分隔,步骤2表达式与步骤3表达式之间用英文逗号,分隔, 得到完整的xpath表达式:*/Items/Item.Type==Material,GUID

以上面文件为例,xpath步骤的返回结果为:['914fa3a0-62cd-4b5a-83df-92fdf494534a', 'e081bf41-a561-4750-83af-78c80468bc6c']

一个完整的xpath表达式的结构为:节点路径(.需要检查的子节点表达式)(,需要提取的节点名)(:需要提取的属性名)2.0版本的xpath表达式结构为:节点路径([@需要检查的节点属性表达式])(.需要检查的子节点表达式)(,需要提取的节点名)(:需要提取的属性名)。 其中括号包括的内容均为可选内容。

下面将详细介绍xml xpath表达式各个部分所支持的格式。

  • 节点路径

节点路径表示将进行属性、子节点值检测以及数据提取的节点,xpath表达式其他部分的操作对象,都为 根据xml的层级,从根的tag依次写到当前节点tag,tag之间以/分隔。根节点的tag可以以*代替。

节点tag支持一定程度的模糊查找,以*开头或结尾,表示任意字符串。例如Item*将匹配对应层级下tag以Item开头的所有节点。

节点tag还可以为单个*,表示该层级的所有节点。例如*/Items/*将表示根节点下所有Items节点的所有子节点。

  • 需要检查的子节点值表达式

值检查表达式的结构为:节点名 ==或!= 需要节点名满足的值。

节点名为需要检查值的子节点tag名,节点值为字符串或者是正则表达式,判断条件为相同(==)或不同(!=) 检查值以反引号(`)开头和结尾,其包括的内容将被作为正则表达式,其他情况将作为一般字符串。注意,一般字符串为字母+数字+下划线,不能包括.:&|等特殊字符。 当检查值为一般字符串时,需要tag的值与检查值满足判断条件;当检查值为正则表达式时,判断条件相同表示正则表达式能匹配节点的值,不同表示正则表达式不匹配节点的值。

表达式之间可以以逻辑运算符连接,&(与运算符)表示其连接的两个表达式都需要成立,|(或运算符)表示其连接的两个表达式中任意一个成立即可。 请注意逻辑运算符没有定义优先级,请避免混用与运算符和或运算符。

示例:

  • Type==Material,表示<Type>节点值</Type>的值需要是Material。
  • Name==`^Mesh_`,表示<Name>节点值</Name>的值需要以Mesh_开头(能匹配^Mesh_这个正则表达式)。
  • Type==Material&Name==`^Mesh_`,表示上述两个条件都需要满足

2.0版本对检查表达式进行了扩展,增强的功能有: * 支持对所有层级的子节点进行值检查,层级间用.分隔。例如需要检查Item节点的Annotation子节点的Anno子节点的Key子节点的值,那么节点名可以写成Annotation.Anno.Key。 * 检查值可以以双引号来包括字符串,将支持除了双引号和反引号以外的所有字符。

  • 2.0版本 需要检查的节点属性表达式

属性检查表达式的格式与上述节点值检查表达式相同。

  • 需要提取值的节点名

该部分为需要提取值的节点名,如果需要返回多个子节点的值,则用英文逗号(,)分隔子节点的tag。如需要提取<Name></Name>子节点的值,则该部分表达式为Name。 如果需要同时提取<Package></Package><Name></Name>两个子节点的值,那么该部分的表达式为Package,Name

当只提取一个节点时,返回值为节点值的列表。 当提取的节点值超过一个时,返回格式为:

[
  {
    "节点名1": ["节点值1"],
    "节点名2": ["节点值2"],
  },
  {
    "节点名1": ["节点值3"],
    "节点名2": ["节点值4", "节点值5"],
  }
]

此外,该部分的一个特殊值是{dom},将会返回节点的xml.etree.ElementTree.Element对象。

2.0版本对支持提取的节点进行了扩展,支持提取节点任意层级的子节点的值。例如,需要提取Item节点的Annotation子节点的Anno子节点的Value子节点的值,可以写成Annotation.Anno.Value。 一个完整的示例:找到Mesh依赖的CollisionShape的资源GUID,那么在2.0版本可以写成:*/Items/Item.Type==Mesh&Annotation.Anno.Key==CollisionShape,Annotation.Anno.Value

  • 需要提取值的属性名

该部分的格式与上述提取节点值表达式的格式相同。

  • 其他规则

  • 不支持一个xpath表达式中,同时包含提取节点值和提取节点属性。

  • 当"需要检查的子节点表达式"与"需要检查的节点属性表达式"两部分都不存在时,将会对满足节点路径的所有节点进行数据提取操作。
  • 不推荐的写法 当xpath表达式只有节点路径部分时,将返回满足节点路径的所有节点的值。

  • 一些xpath表达式示例(以目标文件为resource.repository为例)

    • */Items/Item.Type==Texture,GUID:获得所有类型为Texture的资源的GUID
    • */Items/Item.Type==Model&Name==`^char_`,Deps:获取所有类型为Model并且名字以char_开头(满足正则表达式^char_)资源的依赖资源GUID列表
    • */Items/Item/GUID:(不推荐的写法)获取所有资源的GUID。在2.0版本中,推荐写为*/Items/Item.GUID

condition字段可用的检查方法

condition字段定义了该条规则最终使用的检查方法,用于对前序步骤提取出的数据进行检查,其结果决定了该文件是否通过该条规则的检查。

condition字段的格式为列表,所有元素都是字符串类型,第一个元素为检查方法,其他元素为检查方法的参数。 检查函数接收的数据为待检查的数据列表,返回检查结果和检查不通过的数据。

目前支持这些检查方法: * 存在:待检查的数据存在(不为空、False、None,在python中判断为真值)

格式为:["存在"]

  • 不存在:待检查的数据不存在(空、False、None等非真值)

格式为:["不存在"]

  • 存在于:待检查的数据存在于某个集合中

格式为:["存在于", "值需要存在的集合"]。 需要检查的集合,可以为内置的预定义集合(见下文),2.0版本支持一个给定的列表。其他现有规则中的用法均不推荐。

当集合以artfunc_开头时,则作为内置的预定义集合处理,如果集合为dict类型,则判断给定的值是否在字典的key中。 2.0版本当集合字符串以[开头并以]结尾时,则将字符串作为列表处理,需要满足python的列表格式,项目间以英文逗号分隔。

例如,需要检查GUID是否存在资源文件,可以写成:["存在于", "artfunc_res_filter.ALL_RES_GUID"]

  • 不存在于:待检查的数据不存在于某个集合中

格式为:["不存在于", "需要检查的集合"]。 集合格式与“存在于”检查条件的相同。

  • 空集合:检查对象是一个空的集合(长度为0)

格式为:["空集合"]

  • 非空集合:检查对象是一个非空的集合(长度不为0)

格式为:["非空集合"]

  • 唯一:按照待检查的数据的来源归类,同一来源内的数据值均不相同,需要元素是可哈希的类型

格式为:["唯一"]

  • 简单比较运算:比较检查对象是否满足一个简单的数学比较运算。

格式为:["运算符", "值"]。 其中,运算符需要为(>, <, >=, <=, ==, !=)中的一个,如果条件中的值可以被转化为数字,则会将待检查的值与条件都转换为数字类型进行比较运算, 否则进行字符串类型的比较运算

  • 满足表达式

其格式为:["满足表达式", "表达式"]。 表达式需要为python表达式的格式,其返回值为bool类型。在表达式种,可用d变量表示需要检查的值,以及使用内置的预定义集合(见下文)。

例如,需要检查resource.repository中的GUID是否重复,则可以写:["满足表达式", "d in res_filter.ALL_RES and len(res_filter.ALL_RES[d]) == 1"]

  • 自定义检查函数

当检查方法以func_开头时,将执行检查函数,并将检查函数的返回值作为规则执行结果。具体见下文内容。

内置的预定义集合和方法

预定义集合

Messiah美术资源正确性检查的执行流程中,在所有规则开始运行之前,会收集Package目录下所有资源的资源信息,根据信息来源以及信息内容,包括两部分: * 从资源仓库下的resource.repository文件中读取,获得的信息包括资源的属性信息,如GUID、Type、Name、Package、Deps等。 * 遍历资源仓库中的文件获取,记录了资源GUID与其属性文件的路径的对应关系。对于大部分资源,其属性文件为资源所在目录中的resource.xml或者resource文件; 对于纹理资源为texture.xml,部分资源还会包括一些额外信息,如resource.data文件的路径。

这些信息在检查中,用于获取资源的基本属性(resource.repository中的信息)并检查,获取资源详细信息并检查,或者检查资源GUID、虚拟路径等是否重复、依赖资源是否缺失等。 其存储变量为: * 从resource.repository中读取的资源信息:res_filter.ALL_RES,其数据格式为: Dict[str, List[Tuple[str, str, str, List[str], str]]]。字典的key为资源的GUID,值为列表(因为重复GUID的情况下,一个GUID可能对应多个资源)。 其五元组类型的子元素为资源基本信息的定义,定义为(Package, Name, Type, Deps, 资源所在仓库的路径)

  • 从硬盘文件获取的资源属性文件路径信息:res_filter.ALL_RES_GUID,其数据格式为: Dict[str, List[Dict[str, str]]]。字典的key为资源的GUID,值为列表(可能存在相同的GUID目录)。其字典类型的子元素为资源文件路径的信息,包括以下key:
  • Resource: 根据资源类型,为resource.xmlresourcetexture.xml文件的路径
  • Data:根据资源类型,为resource.datatexture.data文件的路径
  • HDR:.hdr文件路径
  • Source: 文件名以source开头的文件路径

  • 从iworld文件中读取的所有ilevel文件路径:res_filter.ALL_USED_FILTER,数据格式为:Set[str],是所有在iworld文件中出现的ilevel文件的绝对路径集合。

在规则的rpath、condition字段以及自定义函数中,可能会用到这些预定义集合。在rpath、condition字段中使用的方法为:artfunc_+名称,如artfunc_res_filter.ALL_RES。在自定义代码中,需要import res_filter模块后调用。

def custom_check_func(**pdict):
    import res_filter
    res_filter.ALL_RES_GUID

2.0版本对预定义集合的定义格式和记录位置做了较大调整: * 从resource.repository中读取的资源信息:res_store.res_in_repo,格式为Dict[str, Tuple[MessiahResource]]。字典的key为GUID,值为MessiahResource类型实例的元组。

MessiahResource类包括以下成员: * GUID: 资源的GUID * Package: 资源所在package * Name: 资源名 * Type: 资源类型 * Deps: Tuple[str]类型,资源依赖资源的GUID元组 * Repository: 资源所在的仓库名 * VirtualPath: 资源的虚拟路径,格式为Package/Name * RepositoryFullPath: 资源所在仓库的绝对路径

  • 从硬盘文件获取的资源属性文件路径信息:res_store.res_in_disk,格式为Dict[str, Tuple[ResourceFile]]。字典的key为GUID,值为ResourceFile类型实例的元组。

    ResourceFile类包括以下成员: * GUID: 资源GUID * Repository: 资源所在仓库名 * Type: 资源类型 * Resource: 资源属性文件路径,根据资源类型,为resource.xmlresourcetexture.xml文件的路径 * Data: resource.datatexture.data文件的路径,不存在则为None * HDR: .hdr文件的路径,不存在则为None * Source: 文件名以source开头的文件路径,不存在则为None

  • 虚拟路径与资源基本属性的映射字典:res_store.res_virt_info,格式为Dict[str, Tuple[MessiahResource]]。字典的key为虚拟路径。

  • 所有存在于iworld文件中的ilevel文件路径:res_store.ilevels

2.0版本继续兼容1.x版本中res_filter.ALL_RESres_filter.ALL_RES_GUIDres_filter.ALL_USED_ILEVEL的变量名,但建议使用res_store模块下的成员,以获得更好的代码可读性。

预定义方法

检查框架有一些预定义的方法,可在filter和condition字段使用。这些方法中大部分是为某些特定的检查需求和检查规则而编写,filter字段有部分通用的方法。

filter字段中常用的方法: * res_filter.guidToRealPath:获取GUID资源信息文件的路径。这是检查规则中最常用的一个预定义方法。 当filter字段为该方法时,通常会有subxpath字段进一步的从资源属性文件中提取出需要检查的值。

2.0版本 为该函数引入更有意义的别名:res_filter.guidToResourcePath。 * res_filter.getResourcesInILevel:获取ilevel文件中引用的所有资源的GUID列表。 * 2.0版本 res_filter.validGUID:返回传入列表中所有符合GUID格式的字符串。

condition字段中会使用的方法,这些方法通常由特定的检查规则使用: * condfunc.compressed_src_tex:检查原始贴图是否经过压缩,报错未压缩贴图 * condfunc.foliage_shader:检查物体使用的材质是否在允许的列表中 * condfunc.rigidbody_deps:检查物理资源依赖的资源是否缺失 * condfunc.effect_collision_non_local:带碰撞的特效不能为local * condfunc.effect_emitter_count_check:检查特效发射器的数量与特效所依赖的材质层数是否一致 * g83_runner.srcTexConflict:检查贴图在属性文件中的大小与tga文件大小是否一致

自定义函数

当检查的逻辑较为复杂,仅靠标准的检查流程和内置函数,无法满足检查需求,或者实现流程复杂而不清晰时,可以利用自定义函数功能,编写函数代码,并在filter和condition字段中调用,以达到检查规则的目的。

注意:在1.x版本中,所有规则中的自定义函数是全局(globals命名空间中)生效的,并且即使规则没有启用,也会在全局空间中执行其自定义函数。 因此不同规则下如果存在相同名称的自定义函数,会导致函数的覆盖。

自定义函数的参数和返回数据格式

在检查流程中,参数将以命名参数的形式传到自定义参数,因此自定义函数的通常格式为:

def custom_func(**pdict):
    # 逻辑代码
    return ret_value  # 返回值,filter和condition字段对返回值的要求不一致
* 函数用于filter/subfilter字段

此时传入参数为: * data: 需要处理的数据,通常为列表类型 * filepath: 数据来源的文件路径

返回值为List类型,表示处理/转换后的数据。

  • 函数用于condition字段

此时传入参数为: * data: 需要检查的数据,通常为列表类型 * filepath: 检查数据来源文件的路径 * condition: 规则condition字段的值

返回值为Tuple[bool, List]类型,第一个元素为检查结果,True表示检查通过,False表示检查不通过;第二个元素为检查出的错误数据的列表。

2.0版本中,用于condition字段的自定义函数仅需返回一个记录了检查错误数据的List,返回的列表为空则表示检查通过,否则检查不通过。 兼容1.x版本的返回数据格式,但第一个元素的值将不再使用。

一般情况下,避免使用自定义函数作为condition字段,而是通过filter字段的自定义函数提取出需要检查的值,然后使用通用的检查方法来完成检查。 需要使用自定义检查函数的情况:通用的检查方法难以达成数据检查目标(如需要从多个关联的文件中提取数据并检查),需要自定义错误数据的描述信息。

检查规则中的调用方法

  • 在filter字段调用。调用方式为filter的字段填上artfunc_+自定义函数的函数名。如自定义函数的名字是custom_filter_func,则filter字段需要填artfunc_custom_filter_func

  • 在condition字段调用。调用方式为func_+自定义函数的函数名。如自定义函数的名字是custom_check_func,则condition字段需要填["func_custom_check_func"]

示例

可以参见公共规则库中的检查规则。此处给出两个规则的检查方法解释:

  1. 仓库中资源的GUID不能重复。规则定义为:

    rpath: .*Repository.*resource.repository
    xpath: */Items/Item/GUID
    condition: ['满足表达式','d in res_filter.ALL_RES and len(res_filter.ALL_RES[d]) == 1']
    
    从检查需求出发,需要从resource.repository中找出所有资源的GUID,因此rpath字段为能够筛选出resource.repository文件的正则表达式, xpath字段为*/Items/Item/GUID,可以提取出所有资源的GUID。 根据前文对res_filter.ALL_RES这个字典的描述,如果存在相同GUID的多个资源,则这些资源都会保存在其值中。因此检查是否有重复的GUID,只需要检查其值的长度是否大于1。 因此condition字段为['满足表达式','d in res_filter.ALL_RES and len(res_filter.ALL_RES[d]) == 1']

  2. 模型面数检查标准。规则定义为:

    rpath: .*Repository.*resource.repository
    xpath: */Items/Item.Type==Model,GUID
    filter: artfunc_res_filter.guidToRealPath
    subxpath: */ModelInfo/Root/Entity/NumFaces
    condition: ["<=", 5000]
    
    检查思路是:首先,从resource.repository中获取到所有类型为模型资源的GUID,然后,从这些模型资源属性文件(resource.xml)中,读取到面数信息并进行比较。 规则的rpath和xpath部分是为了获取Model类型资源的GUID;filter调用了函数res_filter.guidToRealPath,根据资源GUID获取其属性文件的路径; 随后的subxpath字段是从资源属性文件中读取到模型面数信息,最后在condition字段中进行比较,是否满足<=5000的条件。