0
\$\begingroup\$

I'm writing my own level editor and recently got the exporter working so now I'm working on the importer. My save file structure looks essentially like this:

[ObjectType]
    Property=Value
    ContentProperty:ContentType=ContentName
[/ObjectType]

The problem is in loading the content. I'm trying to find out how to load some content with the given content type at runtime. Technically, I could do a switch-case of each type

switch(ContentType)
{
    case Texture2D:
        Content.Load<Texture2D>(ContentName);
        break;
    case Model:
        Content.Load<Model>(ContentName);
        break;
    ...
}

but that just seems messy (and I can't find a reliable listing of all of the default content types in XNA).

Is there a way for me to load the content with the proper type without having to list out all possible types?

\$\endgroup\$
4
  • \$\begingroup\$ I don't think so :(. What I would do is write a code generation plugin for Visual Studio that grabs all the references and finds the runtime types and creates your switch statement. You could possibly do it via reflection: but maybe that wouldn't work on the XBox. I'll do a bit of messing around to see how it would work on PC. \$\endgroup\$ Jan 8, 2012 at 19:45
  • \$\begingroup\$ Here's a list of all the default supported content types in XNA that I found through Google: msdn.microsoft.com/en-us/library/bb447762.aspx \$\endgroup\$
    – DMan
    Jan 8, 2012 at 19:52
  • \$\begingroup\$ @JonathanDickinson Sadly, that's the feeling I was getting :/ At least it's not the end of the world if I can't, I just figured that would be cool to do :) \$\endgroup\$
    – Mike Cluck
    Jan 8, 2012 at 19:55
  • \$\begingroup\$ @DMan Thanks! If all else fails, that ought to at least help me get the job done. \$\endgroup\$
    – Mike Cluck
    Jan 8, 2012 at 19:55

2 Answers 2

1
\$\begingroup\$

I had a look at the code in ILSpy, and it's heavily generic. There isn't a private Read(Type type) method available.

Your option on the PC to keep things quick is to use Expression objects - basically compile an expression to make sure things stay quick; otherwise use reflection (which can be slower) to invoke the method.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Content;
using System.Linq.Expressions;
using System.Reflection;

namespace WindowsGame1
{
    static class ContentLoader
    {
#if !XBOX
        private static readonly Dictionary<string, Func<ContentManager, string, object>> _calls = new Dictionary<string, Func<ContentManager, string, object>>(StringComparer.Ordinal);
#endif
        private static readonly MethodInfo _loadMethod = typeof(ContentManager).GetMethod("Load", BindingFlags.Public | BindingFlags.Instance);

        public static object Load(this ContentManager manager, string typeName, string assetName)
        {
#if XBOX
            var mi = _loadMethod.MakeGenericMethod(Type.GetType(typeName, true));
            return mi.Invoke(manager, new object[] { assetName });
#else
            Func<ContentManager, string, object> load;
            lock (_calls)
            {
                if (!_calls.TryGetValue(typeName, out load))
                    _calls.Add(typeName, load = CreateLoader(typeName));
            }
            return load(manager, assetName);
#endif
        }

        private static Func<ContentManager, string, object> CreateLoader(string typeName)
        {
            // Create the method info.
            var type = Type.GetType(typeName, true);
            var mi = _loadMethod.MakeGenericMethod(type);

            var paramContentManager = Expression.Parameter(typeof(ContentManager), "contentManager");
            var paramAssetName = Expression.Parameter(typeof(string), "assetName");

            // Create the call and cast-down.
            var call = Expression.Call(paramContentManager, mi, paramAssetName);
            var convert = Expression.Convert(call, typeof(object));
            var lambda = Expression.Lambda<Func<ContentManager, string, object>>(convert, paramContentManager, paramAssetName);

            return lambda.Compile();
        }
    }
}

Remember that most-likely you will need to use the assembly-qualified type name. So for instance:

var content = Content.Load("Microsoft.Xna.Framework.Graphics.SpriteFont, Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553", "SpriteFont1");

To get the AQFQN of a type simple inspect it:

var tn = typeof(SpriteFont).AssemblyQualifiedName;
\$\endgroup\$
5
  • \$\begingroup\$ +1 First time I've heard about Expression. Have any good references to at least understand what you did there? \$\endgroup\$ Jan 8, 2012 at 20:25
  • \$\begingroup\$ @DavidGouveia Expression is heavy (10x simpler than ILEmit though). The only place I learnt about it was merely experimentation. Declare a public static T Test<T, U>(Expression<Func<T, U>> expr) and call it such as Test(x => 1 + 1). You really just need to experiment with it - I never found an literature: it's basically a watered-down version of CodeDOM that can go directly to a DynamicMethod (which is GC-collectible). Excellent stuff, makes you feel like a real hacker. \$\endgroup\$ Jan 8, 2012 at 20:39
  • \$\begingroup\$ @DavidGouveia oh, it's a compiler feature, so the compiler will translate a lambda expression into an Expression if your method signature uses it (this is exactly how Linq2Sql works). \$\endgroup\$ Jan 8, 2012 at 20:40
  • \$\begingroup\$ Are you a mind reader? I thought that up, and you got around to answering it before I even wrote it down... So what's the actual difference between a lambda expression and an Expression object? \$\endgroup\$ Jan 8, 2012 at 20:43
  • \$\begingroup\$ @DavidGouveia lol :). That's the exact thing - the compiler also turns inline SQL (select from...) into a lambda expression (that uses the Select etc. extension methods) before passing it to a method, and if the method uses Expression you get your SQL generation (it visits the Expression objects) - there is your full circle of C# 3.5 features. Experiment with it and you will understand the wonderment that is Anders Hejlsburg. \$\endgroup\$ Jan 8, 2012 at 20:50
1
\$\begingroup\$

If you can use Reflection on your target platform, then it's possible as follows:

var contentLoadMethod = typeof(ContentManager).GetMethod("Load").MakeGenericMethod(ContentType);
contentLoadMethod.Invoke(Content, new [] {ContentName});
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .