我觉得我需要提供一些背景信息来说明我为什么要做我正在做的事情,因为我想接受建议和批评,并给可能阅读本文的使用 Blender 的 XNA 程序员带来希望。考虑到这篇文章的长度,如果您不关心我的问题的具体细节,请随时跳到最后一段。
我正在开发一个 XNA Content Pipeline 扩展项目,该项目读取 .blend 文件(由 Blender 创建)并进行转换它们可以从 XNA 游戏加载数据,以避免每次我进行任何小调整时都必须从 Blender 导出新的 .FBX 或 .OBJ 模型,以及(希望)为Blender 令人惊叹的粒子和物理功能。
在不深入了解 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
和 renderData
都是“复杂”的 BlenderObject
实例(每个都有一个集合字段,而不是直接值),并且 xparts
和 yparts
都是“简单”BlenderObject
实例(每个实例都有一个直接的、简单的- 为其本身键入值,而不是集合字段)。如果每个 BlenderObject
的 SDNA 类型是程序集中的具体编译类型,则它的行为方式与您期望的完全一致,这是我使用动态来表示 Blender 对象的目标。
为了简化我的库的使用,我正在努力重载 DynamicObject
的方法,使“简单”BlenderObject
实例的行为与其直接值相同。例如,让 foo
是一个 BlenderObject
,其直接 int
类型值为 4。我希望能够做到以下为 foo
:
string s = foo.ToString();
Console.WriteLine(s);
第一行的目的是调用 foo
内部值的 ToString 方法,而不是 foo
本身,所以 foo
的TryInvokeMember
重写使用反射在其值 4 上调用 Int32.ToString()
,而不是对其自身调用 BlenderObject.ToString()
。这种反射方法调用效果很好(同样的概念也适用于数组类型直接值的索引器),除非我尝试如下操作:
string s = foo.Bar();
Console.WriteLine(s);
Bar
是我的程序集中定义的扩展方法,因此反映 foo
的值 4 显然无法找到它,当然,还会抛出异常。最后,我的问题是:
如何找到和/或缓存可应用于对象的扩展方法?我知道如何通过反射查找扩展方法,但每次都这样做在动态 BlenderObject
实例上调用扩展方法会非常很慢。除了该问题中描述的方法之外,是否有更快的方法来查找扩展方法?如果没有,我应该如何实习扩展方法,这样我只需要找到它们一次就可以快速再次访问它们?抱歉这么久了,并提前感谢任何有用的答案/评论。
编辑:
正如@spender回答的那样,解决我的问题的一个简单方法是使用字典(尽管我使用的是Dictionary>
轻松使用传递给 DynamicObject.TryInvokeMember
的 InvokeMemberBinder
提供的 CallInfo)。不过,我的实现给我带来了另一个问题:
如何获取调用程序集引用的程序集中定义的类型?
例如,考虑以下代码:
对象 Foo(动态 BlenderObject)
{
返回 BlenderObject.x.Baz();
}
如果此代码位于引用我的 Blender 库的项目中,并且扩展方法 Baz()
是在该项目引用的另一个程序集中定义的,但不是由我的 Blender 管道引用的,那么我将如何查找 < code>Baz() 来自我的 Blender 管道?这可能吗?
I feel like I need to give a little background information on why I'm doing what I'm doing, because I want to open it to suggestions and criticism, and give hope to Blender-using XNA programmers that might read this. Considering the length of this post, please feel free to skip to the last paragraph if you don't care about the nitty-gritty details of my problem.
I'm working on an XNA Content Pipeline extension project that reads .blend files (created by Blender) and converts them to data loadable from an XNA game, to avoid having to export a new .FBX or .OBJ model from Blender every time I make any little tweak, as well as (hopefully) make some XNA-compatible support for Blender's awesome particle and physics capabilities.
Without diving too deep into Blender's inner workings, I'd like to describe my understanding of how .blend files work. Please correct me if you're more knowledgeable on the subject.
Blender saves files in "blocks" of bytes. Most of these blocks contain data representing objects and settings in the 3D scene, and the last block in the file (called the SDNA block), contains what can be thought of as very simple C-style structures, each of which has a unique identifier, as well as several fields of various types. Fields of these structures may be of simple types, such as int
or float
, or they may be of the types defined in the SDNA block.
For example, here's a semi-pseudo-code representation of the ID
SDNA structure:
structure IDPropertyData
{
void *pointer;
ListBase group;
int val;
int val2;
}
As you can see, the fields *pointer
, val
, and val2
can be represented at runtime by simple values of type void*
or int
. The group
field, however, is of type ListBase
, which is defined elsewhere in the file's SDNA block.
This is where C#'s new dynamic features come to play: I've made a class (called BlenderObject
) that, given an SDNA structure (it's "SDNA type"), and a chunk of bytes, behaves as an instance of that structure by storing either a simple-type value for itself, or a collection of other BlenderObject
instances, each representing one of its "fields". This allows the user of my library to write code like the following:
//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
and renderData
are both "complex" BlenderObject
instances (each having a collection of fields, rather than a direct value), and xparts
, and yparts
are both "simple" BlenderObject
instances (each having a direct, simple-type value for itself, rather than a collection of fields). Each BlenderObject
behaves exactly the way you'd expect it to if its SDNA type was a concretely compiled type in the assembly, which is my goal in using dynamics to represent Blender objects.
To simplify using my library, I'm working on overloading DynamicObject
's methods in a way that makes "simple" BlenderObject
instances behave as their direct values. For example, let foo
be a BlenderObject
with a direct int
-type value of, let's say, 4. I want to be able to do the following with foo
:
string s = foo.ToString();
Console.WriteLine(s);
The intention of the first line is to invoke the ToString method of foo
's inner value, not of foo
itself, so the foo
's TryInvokeMember
override uses reflection to invoke Int32.ToString()
on its value of 4, rather than BlenderObject.ToString()
on itself. This reflective method-calling works great (and so does the same concept applied to indexers for array-type direct values), except when I try something like the following:
string s = foo.Bar();
Console.WriteLine(s);
Bar
is an extension method defined in my assembly, and so reflecting foo
's value of 4 obviously fails to find it, and, of course, exceptions are thrown. My question, finally, is this:
How can I find and/or cache extension methods that can be applied to an object? I know how to find extension methods with reflection, but doing this every time an extension method is called on a dynamic BlenderObject
instance would be really slow. Is there a faster way to find extension methods, other than the one described in that question? If not, how should I go about interning extension methods so I only need to find them once and can quickly access them again? Sorry for the longevity, and thanks in advance for any helpful answers/comments.
EDIT:
As @spender answered, an easy way to solve my problem is with a dictionary (although I'm using a Dictionary<Type, Dictionary<CallInfo, MethodInfo>>
to easily use the CallInfo provided by the InvokeMemberBinder
passed to DynamicObject.TryInvokeMember
). My implementation has brought me to another question, though:
How can I get the types defined in assemblies referenced by the calling assembly?
For example, consider this code:
object Foo(dynamic blenderObject)
{
return blenderObject.x.Baz();
}
If this code's in a project that references my Blender library, and the extension method Baz()
is defined in another assembly referenced by that project, but not by my blender pipeline, how would I go about finding Baz()
from within my Blender pipeline? Is this even possible?
发布评论
评论(1)
我只需将发现的方法缓存在前面的
Dictionary>
中。查找速度会很快。I'd simply cache the discovered methods in a
Dictionary<Type,ICollection<MethodInfo>>
up front. The lookup will be fast.