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?