Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

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?

share|improve this question
    
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. –  Jonathan Dickinson Jan 8 '12 at 19:45
    
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 –  DMan Jan 8 '12 at 19:52
    
@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 :) –  Mike C Jan 8 '12 at 19:55
    
@DMan Thanks! If all else fails, that ought to at least help me get the job done. –  Mike C Jan 8 '12 at 19:55

2 Answers 2

up vote 1 down vote accepted

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;
share|improve this answer
    
+1 First time I've heard about Expression. Have any good references to at least understand what you did there? –  David Gouveia Jan 8 '12 at 20:25
    
@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. –  Jonathan Dickinson Jan 8 '12 at 20:39
    
@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). –  Jonathan Dickinson Jan 8 '12 at 20:40
    
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? –  David Gouveia Jan 8 '12 at 20:43
    
@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. –  Jonathan Dickinson Jan 8 '12 at 20:50

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});
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.