我觉得我需要提供一些关于我为什么要做我正在做的事情的背景信息,因为我想把它打开以接受建议和批评,并给可能会读到这篇文章的使用 Blender 的 XNA 程序员带来希望。考虑到这篇文章的长度,如果你不关心我的问题的细节,请随时跳到最后一段。
我正在开发一个 XNA 内容管道扩展项目,该项目读取 .blend 文件(由Blender创建)并将它们转换为可从 XNA 游戏加载的数据,以避免每次我都必须从 Blender 导出新的 .FBX 或 .OBJ 模型进行任何小的调整,以及(希望)为 Blender 的出色粒子和物理功能提供一些与 XNA 兼容的支持。
在不深入了解Blender 的内部工作原理的情况下,我想描述一下我对 .blend 文件如何工作的理解。如果您对该主题更了解,请纠正我。
Blender 将文件保存在“块”字节中。这些块中的大多数包含表示 3D 场景中的对象和设置的数据,文件中的最后一个块(称为 SDNA 块)包含可以被认为是非常简单的 C 样式结构,每个结构都有一个唯一的标识符,以及各种类型的几个领域。这些结构的字段可以是简单类型,例如int
或float
,也可以是 SDNA 块中定义的类型。
ID
例如,这是SDNA 结构的半伪代码表示:
structure IDPropertyData
{
void *pointer;
ListBase group;
int val;
int val2;
}
如您所见,字段*pointer
、val
和val2
可以在运行时由简单的类型值表示void*
或int
。但是,该group
字段的类型是ListBase
,它在文件的 SDNA 块中的其他地方定义。
这就是 C# 的新动态特性发挥作用的地方:我创建了一个类(称为BlenderObject
),给定一个 SDNA 结构(它是“SDNA 类型”)和一大块字节,通过存储自身的简单类型值,或其他BlenderObject
实例的集合,每个实例代表其“字段”之一。这允许我的库的用户编写如下代码:
//Get a BlendContent instance that contains the file's information.
BlendContent content = BlendContent.Read(filePath);
//Get the 0th (and only) block containing data for the scene (code "SC")
BlendFileBlock sceneBlock = content.FileBlocks["SC", 0];
//Get the BlenderObject that represents the scene
dynamic scene = sceneBlock.Object;
//Get the scene's "r" field, whose SDNA type is RenderData.
dynamic renderData = scene.r;
//Get the x and y resolution of the rendered scene
float
xParts = renderData.xparts,
yParts = renderData.yparts;
scene
andrenderData
都是“复杂”BlenderObject
实例(每个都有字段集合,而不是直接值),andxparts
和yparts
都是“简单”BlenderObject
实例(每个都有自己的直接、简单类型的值,而不是字段的集合)。如果其 SDNA 类型是程序集中的具体编译类型,则每个BlenderObject
行为都与您期望的完全一样,这是我使用动力学来表示 Blender 对象的目标。
为了简化使用我的库,我正在重载DynamicObject
's 方法,以使“简单”BlenderObject
实例表现为它们的直接值。例如,让foo
aBlenderObject
具有直接int
类型的值,例如 4。我希望能够使用 执行以下操作foo
:
string s = foo.ToString();
Console.WriteLine(s);
第一行的目的是调用foo
' 内部值的 ToString 方法,而不是调用foo
自身,因此foo
'TryInvokeMember
覆盖使用反射来调用Int32.ToString()
其值 4,而不是BlenderObject.ToString()
自身。这种反射方法调用效果很好(同样的概念也适用于数组类型直接值的索引器),除非我尝试如下操作:
string s = foo.Bar();
Console.WriteLine(s);
Bar
是在我的程序集中定义的扩展方法,因此反射foo
4 的值显然无法找到它,当然,也会引发异常。最后,我的问题是:
如何查找和/或缓存可应用于对象的扩展方法?我知道如何使用反射找到扩展方法,但是每次在动态BlenderObject
实例上调用扩展方法时这样做会非常慢。除了该问题中描述的方法之外,是否有更快的方法来查找扩展方法?如果没有,我应该如何去实习扩展方法,这样我只需要找到它们一次就可以再次快速访问它们?对不起,长寿,并提前感谢任何有用的答案/评论。
编辑:
正如@spender 回答的那样,解决我的问题的一种简单方法是使用字典(尽管我正在使用 aDictionary<Type, Dictionary<CallInfo, MethodInfo>>
来轻松使用InvokeMemberBinder
传递给的 CallInfo DynamicObject.TryInvokeMember
)。不过,我的实现给我带来了另一个问题:
如何获取调用程序集引用的程序集中定义的类型?例如,考虑以下代码: object Foo(dynamic blenderObject) { return blenderObject.x.Baz(); 如果此代码在引用我的 Blender 库的项目中,并且扩展方法Baz()
是在该项目引用的另一个程序集中定义的,但不是由我的 Blender 管道,我将如何Baz()
从我的 Blender 管道中查找?这甚至可能吗?