.NET Core 3 MonoGame.Extended.Content.Pipeline

I’m trying to set up a .NET Core 3 project to use the MonoGame.Extended.Content.Pipeline.
My goal is to import a .tmx file into my game project.

I’m starting my project my using the dotnet new template, outlined here:
http://www.monogameextended.net/posts/getting-started-with-monogame-extended

I initially go to NuGet and added package MonoGame.Extended.Content.Pipeline (3.7.0).
It warns me about .NETFramework versioning issues (NU1701), which I am ignoring for the time being.
Oddly enough adding this to NoWarn doesn’t work - I’ve reported that to Microsoft.

I then open up the Content.mgcb, and go to Content properties > References.
This is my first question - how is it recommended I link this up?
Currently Nuget packages are placed automatically in my user folder, so the location of the DLL I’d normally reference is:
C:\Users[username].nuget\packages\monogame.extended.content.pipeline\3.7.0\lib\netstandard2.0\MonoGame.Extended.Content.Pipeline.dll

This stinks because:

  • If I share this code with someone else, this path won’t exist
  • If a build this project with a build agent, this path won’t exist
  • If I update the NuGet package the path will change at this will break

is there any way I can swap out part of this path with a MSBuild variable or something to make this work?
The only workaround I’ve come up with so far is to copy this DLL into a lib folder, which makes the project sharable but not easily NuGet upgradable.

Ok, so I do a workaround and set the path. This is where the next issue comes up. The MonoGame Pipeline Tool, upon selecting the DLL, gives me this error then crashes:

System.Reflection.ReflectionTypeLoadException: Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.
at System.Reflection.RuntimeModule.GetTypes(RuntimeModule module)
at System.Reflection.Assembly.GetTypes()
at MonoGame.Tools.Pipeline.PipelineTypes.ResolveAssemblies(IEnumerable`1 assemblyPaths)
at MonoGame.Tools.Pipeline.PipelineTypes.Load(PipelineProject project)
at MonoGame.Tools.Pipeline.PipelineController.ResolveTypes()
at MonoGame.Tools.Pipeline.PipelineController.OnReferencesModified()
at MonoGame.Tools.Pipeline.CellRefs.Edit(PixelLayout control)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.DispatcherOperation.InvokeImpl()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at MS.Internal.CulturePreservingExecutionContext.Run(CulturePreservingExecutionContext executionContext, ContextCallback callback, Object state)
at System.Windows.Threading.DispatcherOperation.Invoke()
at System.Windows.Threading.Dispatcher.ProcessQueue()
at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
at System.Windows.Application.RunDispatcher(Object ignore)
at System.Windows.Application.RunInternal(Window window)
at MonoGame.Tools.Pipeline.Program.Main(String[] args)

As a workaround, instead of using the UI I edit the Content.mgcb file by hand. However, this doesn’t seem to work and the ‘Tiled Map Importer - MonoGame.Extended’ and the ‘Tiled Map Processor - MonoGame.Extended’ don’t appear.

Any tips/help on wiring this all up from anyone?

  1. I haven’t tried referencing a nuget DLL from the Pipeline Tool, so I can’t tell you if this is the best approach, but here are some options:

  2. You can use the GeneratePathProperty to get a path to the NuGet package. (Note: as described in the PR, the variable automatically exists if the package has a tools folder.)

  3. As an alternative, the old approach would be to set the globalPackagesFolder in your nuget.config to a relative folder (e.g. ..\packages) and then reference the same folder when locating the DLL, but this defeats the purpose of a global NuGet cache.

  4. Is the DLL you’re referencing from the Pipeline Tool a netstandard2.0 or netstandard2.1? New features were added in netstandard2.1, so reflection could fail if a 2.0 lib/app attempts to load a netstandard2.1 or netcoreapp3.0. If that’s the issue then we need a netcoreapp3.0 version of the Pipeline Tool.

Thanks for the ideas!

So I went ahead and tried to use the GeneratePathProperty on the Nuget library, resulting in the following line in my csproj file:
<PackageReference Include="MonoGame.Extended.Content.Pipeline" GeneratePathProperty="true" Version="3.7.0" />

Unfortunately, however, when I tried to use this variable in the .mgcb file, it didn’t seem to work.
In fact, it didn’t seem to recognize and swap out the variable for the path at all. Here was the Output error, with my username swapped out as [Username]:
Failed to load assembly 'C:/Users/[Username]/Desktop/test/Content/$(PkgMonoGame_Extended_Content_Pipeline)/lib/netstandard2.0/MonoGame.Extended.Content.Pipeline.dll': Could not load file or assembly 'file:///C:\Users\[Username]\Desktop\test\Content\$(PkgMonoGame_Extended_Content_Pipeline)\lib\netstandard2.0\MonoGame.Extended.Content.Pipeline.dll' or one of its dependencies. The system cannot find the file specified.

It looks like only hard-coded paths can be used for References. Even using a variable like $(Platform) doesn’t seem to work.

I could certainly use the globalPackagesFolder change, but yeah, that has the potential to really screw up other developers on the same project. So that’s essentially out.

The DLL I’m pointing to for the Reference is MonoGame.Extended.Content.Pipeline version 3.7.0. It is a .NETStandard,Version=v2.0 project.

Ah sorry. I think to pass the reference into MGCB, you can set the MonoGameMGCBAdditionalArguments property to add arguments to MGCB.exe. The additional arguments default to /quiet, so if you don’t want to change that behavior, you could add the following to your project:

<PropertyGroup>
    <MonoGameMGCBAdditionalArguments>/quiet /reference:$(PkgMonoGame_Extended_Content_Pipeline)/lib/netstandard2.0/MonoGame.Extended.Content.Pipeline.dll</MonoGameMGCBAdditionalArguments>
</PropertyGroup>

I downgraded MonoGame.Extended.Content.Pipeline to 3.7.0.4 and my project build successfully.

@mackhax0r There isn’t a MonoGame.Extended.Content.Pipeline 3.7.0.4. My issue is with that Nuget library, and not the standard MonoGame.Content.Builder. I’ve been able to build and run games with MonoGame.Content.Builder 3.7.0.9 without an issue. I’m trying to use the MonoGame.Extended.Content.Pipeline so I can bring in Tiled map assets.

Meant MonoGame.Content.Builder, sorry. Yeah. 3.7.0.9 will not work for me. I got an error saying mgcb.exe exited with code 9009.

Ok so an update on this all.

The MonoGame.Extended.Content.Pipeline 3.7.0 reference crashed because it requires the MonoGame.Extended.Tiled.dll to be in the same directory to load properly in the Content Builder UI (previously it only required MonoGame.Extended.dll).

@mackhax0r it may be of interest to look at what version of the MonoGame Content Builder you have installed. MSBuild will reference this exe to build your content:
C:\Program Files (x86)\MSBuild\MonoGame\v3.0\Tools\MGCB.exe
You may want to check the version of it and/or try installing the newest version by clicking on the Development Builds links on this page:
http://www.monogame.net/downloads/

@Cory_Kroll Thanks for taking some time to look into this. I’d really like to get these problems properly sorted about before the next release and I’d love to get some help and hear your thoughts on how we might be able make this experience better.

NuGet packages and the MonoGame Content Builder have been a particular pain. They used to work together but ever since things have changed with the way NuGet installs packages there have been lots of problems like this one.

@craftworkgames Sure thing. I’ve been digging through the MonoGame.Framework and MonoGame.Extended sources trying to figure out an elegant way to wire this all up.

The issue is that with regular references in .mgcb, it is a relative path only. From what I can tell, there isn’t even a place in the code where global variables are used to swap anything out of the string for /reference:

Once could use some of tricks already discussed in this thread to create a variable in MSBuild, but that is only for the build process and wouldn’t put the reference into the .mgcb when using the MonoGame Pipeline Tool UI (which I would think we’d still want).

Looking at csproj files, there is a difference between locally pathed references and NuGet references (where there is an implied translation of where these paths go). I really feel like MonoGame needs something similar. Something like a /packageReference: in the .mgcb so that it knows to reference the global NuGet package store.

However, maybe trying to get a change done in the actual MonoGame.Framework is not the way to go. If not, the other option I was looking into was a way to make a NuGet package that creates a lib output folder as part of the build. I’ve seen things like this for packages that create x86 and x64 folders, and I’m hoping that maybe the same could be done for a NuGet. So after an initial build, the Nuget package would put the right files in the right place for the local references to work. Whewn NuGet is updated, those files would be too.

What are your thoughts?