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
,都会调用到该自定义函数。
自定义函数接收的参数,以及返回值的格式,将在下文说明。
检查流程
规则的检查流程示意图如下:
对于一条检查规则来说,其中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。那么步骤为:
- 找到特定的节点。操作目标是Item节点,该部分的值为节点在xml文件中的层次关系,节点间以
/
分隔。 本例中,Item节点的路径为Repository/Items/Item
。其中根节点可以以*
代替,得到*/Items/Item
- 判断该节点是否需要提取数据。此处需要检查Item子节点的Type值是否为Material,得到表达式:
Type==Material
。 - 需要提取的数据。此处为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.xml
、resource
、texture.xml
文件的路径 - Data:根据资源类型,为
resource.data
或texture.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.xml
、resource
或texture.xml
文件的路径 * Data:resource.data
或texture.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_RES
、res_filter.ALL_RES_GUID
、res_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字段对返回值的要求不一致
此时传入参数为: * 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"]
示例
可以参见公共规则库中的检查规则。此处给出两个规则的检查方法解释:
-
仓库中资源的GUID不能重复。规则定义为:
从检查需求出发,需要从resource.repository中找出所有资源的GUID,因此rpath字段为能够筛选出resource.repository文件的正则表达式, xpath字段为rpath: .*Repository.*resource.repository xpath: */Items/Item/GUID condition: ['满足表达式','d in res_filter.ALL_RES and len(res_filter.ALL_RES[d]) == 1']
*/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']
。 -
模型面数检查标准。规则定义为:
检查思路是:首先,从resource.repository中获取到所有类型为模型资源的GUID,然后,从这些模型资源属性文件(resource.xml)中,读取到面数信息并进行比较。 规则的rpath和xpath部分是为了获取Model类型资源的GUID;filter调用了函数rpath: .*Repository.*resource.repository xpath: */Items/Item.Type==Model,GUID filter: artfunc_res_filter.guidToRealPath subxpath: */ModelInfo/Root/Entity/NumFaces condition: ["<=", 5000]
res_filter.guidToRealPath
,根据资源GUID获取其属性文件的路径; 随后的subxpath字段是从资源属性文件中读取到模型面数信息,最后在condition字段中进行比较,是否满足<=5000的条件。