NullReferenceException when trying to load SpriteFont in editor

Okay so I’m struggling with a problem and don’t quite know what more to do. I’ve been building a game framework/engine that I can use for myself and it consists of the game engine itself and a separate WinForms editor application (using MonoGame.Forms) that references the engine dll that I can use to generate the data files used by the engine (scene files, entity types, materials, etc). This editor application is completely separate from the game and runs its own Game subclass. When the user selects a project folder and a scene file from the game the Editor attempts to load any assets it needs from that folder.

So far I’ve been sidestepping the content pipeline by loading textures and sounds via the FromStream methods. The problem is now that I’m including text I have to use the content pipeline since I haven’t found any methods to load sprite fonts from streams. And I can’t seem to get the spritefont to load in the editor. When I run the game project it all works correctly and the font loads and is drawn to the screen. When I try to open that project in the editor I get a crash when loading the spritefont caused by a NullReferenceException.

Here’s the code I use to load assets:

public Resource GetResource<Resource>(string resourceFile, string bundle, bool reload = false) where Resource : Cv_Resource, new()
    {
        var resource = new Resource();
        resource.File = resourceFile;

        var isOwnedByResManager = resource.VIsManuallyManaged();
        Cv_ResourceData resData;
        if (/** file has been loaded already**/)
        {
            /** return asset in memory **/
        }
        else
        {
            Cv_ResourceBundle resBundle;

            if (!m_ResourceBundles.TryGetValue(bundle, out resBundle))
            {
                /** could not find resource bundle (a subclass of ContentManager) in the list **/
            }

            if (isOwnedByResManager)
            {
                /** do stuff with files that don't use the content pipeline **/
            }
            else
            {
                /** calls VLoad of the specific resource type, which loads the resource **/
                if (!resource.VLoad(resourceFile, null, out size, resBundle))
                {
                    /** error loading resource **/
                }
            }
        }
        
        return resource;
    }

And here’s the Font resource VLoad method:

public bool VLoad(string resourceFile, Stream resourceStream, out int size, Cv_ResourceBundle bundle)
    {
	try {
            var resource = Path.GetFileNameWithoutExtension(resourceFile);
            /** EXCEPTION IN THIS NEXT LINE **/
            var font = bundle.Load<SpriteFont>(resource); /** bundle is a subclass of ContentManager so this just loads the content as you would do normally in MonoGame **/

	    var resData = new Cv_SpriteFontData();
	    resData.Font = font;
	    ResourceData = resData;

            /** Do some other stuff here **/
			
	   return true;
	}
	catch (Exception e)
	{
		/** handle the error **/
	}
    }

In the editor I instantiate the resource bundles when I open the project folder and set their RootDirectory to a subdirectory in that folder (in this case a directory called Assets). This works well when running the game engine on the same directory as the Assets folder, but since the editor runs separately it appears to be having trouble loading the sprite font. Inside the Assets folder I have both the xnb and spritefont files generated by the content project and there appears to be no problem with these because they load correctly in the game.

Here’s the error I get when I try to load a scene with text in it:

System.NullReferenceException: Object reference not set to an instance of an object.
   at Microsoft.Xna.Framework.Content.Texture2DReader.Read(ContentReader reader, Texture2D existingInstance)
   at Microsoft.Xna.Framework.Content.ContentTypeReader`1.Read(ContentReader input, Object existingInstance)
   at Microsoft.Xna.Framework.Content.ContentReader.InnerReadObject[T](T existingInstance)
   at Microsoft.Xna.Framework.Content.SpriteFontReader.Read(ContentReader input, SpriteFont existingInstance)
   at Microsoft.Xna.Framework.Content.ContentTypeReader`1.Read(ContentReader input, Object existingInstance)
   at Microsoft.Xna.Framework.Content.ContentReader.InnerReadObject[T](T existingInstance)
   at Microsoft.Xna.Framework.Content.ContentReader.ReadObject[T]()
   at Microsoft.Xna.Framework.Content.ContentReader.ReadAsset[T]()
   at Microsoft.Xna.Framework.Content.ContentManager.ReadAsset[T](String assetName, Action`1 recordDisposableObject)
   at Microsoft.Xna.Framework.Content.ContentManager.Load[T](String assetName)
   at Caravel.Core.Resource.Cv_SpriteFontResource.VLoad(String resourceFile, Stream resourceStream, Int32& size, Cv_ResourceBundle bundle)
	Function: VLoad

I’ve checked the name of the asset and the RootDirectory before calling Load and they appear to be correct. So I don’t know what to do. Is the content manager unable to load files outside the directory where the app is running? Or am I missing something stupid here? Do you have any suggestions on what I should be doing to get the spritefonts to load correctly both in game and when loading scenes in the editor?

Edit: Also, I’m using MonoGame 3.5.

Thank you for reading this wall of text.

Hi @jocamar

You can try to set the current directory to the one with your file.
Directory.SetCurrentDirectory().

If that doesn’t work , then you need to use a custom ContentManager.
That’s the one I am using.

It will try to find the file from the calling library, CurrentDirectory and the Host folder.
With this I can load dll modules from various places, “/Editor/bin/Modules”, “/projects/Game1/Modules” and each one comes with it’s own “/Conent/” folder.

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Microsoft.Xna.Framework.Content;

namespace tainicom.ProtonType.Engine.Models
{
    public class ProjectContentManager: ContentManager
    {
        Dictionary<string, object> loadedAssets = new Dictionary<string, object>();
        List<IDisposable> disposableAssets = new List<IDisposable>();
        private string _projectLocation = "";

        
        public ProjectContentManager(IServiceProvider serviceProvider, string rootDirectory): base(serviceProvider, rootDirectory)
        {
        }
        
        public ProjectContentManager(IServiceProvider serviceProvider): base(serviceProvider)
        {
        }

        protected override Stream OpenStream(string assetName)
        {
            Stream stream;
            if (Path.IsPathRooted(assetName))
            {
                var assetPath = Path.Combine(RootDirectory, assetName);
                stream = File.OpenRead(assetPath + ".xnb");
            }
            else
            {
                stream = base.OpenStream(assetName);
            }
                        
            return stream;
        }
        
        public override T Load<T>(string assetName)
        {
            if (loadedAssets.ContainsKey(assetName))
                return (T)loadedAssets[assetName];

            string callerLocation   = Path.GetDirectoryName(Assembly.GetCallingAssembly().Location) + "\\";
            //string hostLocation     = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + "\\";
            string currentLocation  = Directory.GetCurrentDirectory();
            string projectLocation = _projectLocation;

            string assetPath = assetName; //default value
            if(File.Exists(Path.Combine(callerLocation, RootDirectory, assetName) + ".xnb"))
                assetPath = Path.Combine(callerLocation, RootDirectory, assetName);
            else if (File.Exists(Path.Combine(currentLocation, RootDirectory, assetName) + ".xnb"))
                assetPath = Path.Combine(currentLocation, RootDirectory, assetName);
            else if (File.Exists(Path.Combine(projectLocation, RootDirectory, assetName) + ".xnb"))
                assetPath = Path.Combine(projectLocation, RootDirectory, assetName);
            
            T asset = ReadAsset<T>(assetPath, RecordDisposableAsset);
            loadedAssets.Add(assetName, asset);

            return asset;
        }
        
        public override void Unload()
        {
            foreach (IDisposable disposable in disposableAssets)
                disposable.Dispose();

            loadedAssets.Clear();
            disposableAssets.Clear();
        }
        
        void RecordDisposableAsset(IDisposable disposable)
        {
            disposableAssets.Add(disposable);
        }

        public IDisposable UnloadAsset(string assetName)
        {   
            if(!loadedAssets.ContainsKey(assetName))
                return null;
            
            object asset = loadedAssets[assetName];          
            loadedAssets.Remove(assetName);

            if (asset is IDisposable)
            {
                if (disposableAssets.Contains((IDisposable)asset))
                    disposableAssets.Remove((IDisposable)asset);
            }

            return asset as IDisposable;
        }
    }
}

Hi @nkast , thanks for taking the time to read and help me. I tried setting the current directory and I tried adding your OpenStream and Load methods to my class. Unfortunately the error still persists. Here’s a screenshot of the code and the local variables at the time of the exception:

As you can see I’m passing FramerateCounterFont as the spritefont, which is in C:\Users\jocamar\Caravel\Assets (both the xnb and spritefont files). My editor is running from “C:\Users\jocamar\CaravelEditor\bin\Debug\” and my project directory is set to “C:\Users\jocamar\Caravel”. The root directory of that particular content manager is “Assets”.

The file is found at that location since the assetPath variable is updated to be “C:\Users\jocamar\Caravel\Assets\FramerateCounterFont”. So I don’t know what to do. :confused:

I don’t know what to make of this, the error doesn’t say much.
Is the device fully initialized at this point? some issue with file permissions perhaps?
Since the error comes from Texture2DReader.Read() I’d say to upgrade to 3.6 or to the latest 3.7- develop build and see if it’s fixed or if you get a different type of error. The only way to go from there is to add the monogame source project and caught the exception in-place to see what object exactly is null.

I tried upgrading to 3.6 and I still get the same error. I’ll have to try building monogame from source to see what’s happening. I didn’t think loading fonts from the editor could be this complicated. :confused:

This might be a stupid suggestion, but it is worth a try.

Add .xnb to the file name

Alright after some digging around and compiling Monogame myself I found out that the problem was me being stupid. The reason I was getting those crashes was because the Graphics Device was null, because I never called Run on my game instance when running the editor. I did this because Monogame.Forms already handles window creation and graphics device initialization and so I just called the Initialize, Update and Draw methods of my class inside the Monogame.Forms methods.

Every other resource worked correctly because when initializing the editor I pass the Monogame.Forms object’s GraphicsDevice to my class, and use that when drawing sprites to the screen. And since I am using FromStream to load textures I never needed the GraphicsDevice. But as I’m using the Content Pipeline to load the spritefonts it was having the error I reported above in the editor, but not in the game (as that initialized the GraphicsDevice when Run was called).

So what I ended up doing was passing the Monogame.Forms’s UpdateWindow object’s services to where I initialize my ContentManagers, and instantiating them through the constructor that takes an IServiceProvider object. This solved my issue.

Thank you all for you help. :slight_smile: