Cocos2DXNA / cocos2d-xna

XNA Port of Cocos2d-X
www.cocos2dxna.com
226 stars 123 forks source link

iOS: FNT file loading does not work #22

Closed totallyeviljake closed 11 years ago

totallyeviljake commented 11 years ago

Loading of FNT content files does not work.

Mar 3 10:40:24 jacobs-iPhone cocos2dtests[477] : Unhandled managed exception: Could not load fonts/konqa32.fnt asset as a non-content file! (Microsoft.Xna.Framework.Content.ContentLoadException) at Microsoft.Xna.Framework.Content.ContentManager.ReadAsset[CCBMFontConfiguration](System.String assetName, System.Action`1 recordDisposableObject) [0x00000] in :0 at Microsoft.Xna.Framework.Content.ContentManager.Load[T](System.String assetName) [0x00000] in :0 at cocos2d.CCBMFontConfiguration.Create (System.String fntFile) [0x00000] in :0 at cocos2d.CCLabelBMFont.FNTConfigLoadFile (System.String file) [0x00000] in :0 at cocos2d.CCLabelBMFont.InitWithString (System.String theString, System.String fntFile, Single width, CCTextAlignment alignment, CCPoint imageOffset) [0x00000] in :0 at cocos2d.CCLabelBMFont.Create (System.String str, System.String fntFile, Single width, CCTextAlignment alignment, CCPoint imageOffset) [0x00000] in :0 at cocos2d.CCLabelBMFont.Create (System.String str, System.String fntFile) [0x00000] in :0 at tests.LayerScaleTest.OnEnter () [0x00000] in :0 at cocos2d.CCNode.OnEnter () [0x00000] in :0 at tests.TestScene.OnEnter () [0x00000] in :0 at cocos2d.CCDirector.SetNextScene () [0x00000] in :0

totallyeviljake commented 11 years ago

BMFontConfiguration uses the ReflectiveReader, which does not make it into the binary on a device build.

totallyeviljake commented 11 years ago

this error for this issue is from the file name being ".fnt" instead of without the suffix.

totallyeviljake commented 11 years ago

these menu and label tests work on the simulator.

totallyeviljake commented 11 years ago

we are also building for the device with linking disabled.

totallyeviljake commented 11 years ago

This is for Xamarin:

Microsoft.Xna.Framework.Content.ReflectiveReader`1[[cocos2d.CCBMFontConfiguration, cocos2d-xna, Version=2.0.3.0, Culture=neutral, PublicKeyToken=null]]

Ran this (label) test just now, third test (Atlas3.cs) loads an FNT file, which is caught in ContentTypeReaderManager.cs (MonoGame) line 161.

The exception here is that it can not find the type listed above.

here is the code excerpt:

            else
            {
                //System.Diagnostics.Debug.WriteLine(originalReaderTypeString);

                // Need to resolve namespace differences
                string readerTypeString = originalReaderTypeString;

                readerTypeString = PrepareType(readerTypeString);

                var l_readerType = Type.GetType(readerTypeString);
                if (l_readerType != null)
                {
                    try
                    {
                        contentReaders[i] = l_readerType.GetDefaultConstructor().Invoke(null) as ContentTypeReader;
                    }
                    catch (TargetInvocationException ex)
                    {
                        // If you are getting here, the Mono runtime is most likely not able to JIT the type.
                        // In particular, MonoTouch needs help instantiating types that are only defined in strings in Xnb files. 
                        throw new InvalidOperationException(
                            "Failed to get default constructor for ContentTypeReader. To work around, add a creation function to ContentTypeReaderManager.AddTypeCreator() " +
                            "with the following failed type string: " + originalReaderTypeString);
                    }
                }
                else
                    throw new ContentLoadException("Could not find matching content reader of type " + originalReaderTypeString + " (" + readerTypeString + ")");
            }

In my program.cs I added the following to bypass the AOT optimization:

    public override void FinishedLaunching (UIApplication app)
    {
        // More shameless hacking to bypass AOT
        var hHack = new ReflectiveReader<CCBMFontConfiguration>();
        var hFoo = new PlistDocument.PlistDocumentReader ();

        // Fun begins..

        game = new Game1();
        game.Run();
    }

This works on the simulator, does not work on the device. I have the linker setting set to "do not link".

This is on an iPad G1 running iOS 5.1

totallyeviljake commented 11 years ago

In the above, here is the code that throws the exception:

                else
                    throw new ContentLoadException("Could not find matching content reader of type " + originalReaderTypeString + " (" + readerTypeString + ")");

There are no other errors in the output console. Just the exception associated with the error described above.

migueldeicaza commented 11 years ago

What is the value of readerTypeString before the call to Type.GetType?

totallyeviljake commented 11 years ago

You mean - what happens here:

readerTypeString = PrepareType(readerTypeString);

Will have to capture that again in another debug session.

totallyeviljake commented 11 years ago

readerTypeString = Microsoft.Xna.Framework.Content.ReflectiveReader`1[[cocos2d.CCBMFontConfiguration, cocos2d-xna]]

totallyeviljake commented 11 years ago

ha, Sebastien was right - cocos2d-xna.iOS (and I fixed that once already). so now I get this error:

System.InvalidOperationException: Failed to get default constructor for ContentTypeReader. To work around, add a creation function to ContentTypeReaderManager.AddTypeCreator() with the following failed type string: Microsoft.Xna.Framework.Content.DictionaryReader2[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[cocos2d.CCBMFontConfiguration+ccBMFontDef, cocos2d-xna, Version=2.0.3.0, Culture=neutral, PublicKeyToken=null]] at Microsoft.Xna.Framework.Content.ContentTypeReaderManager.LoadAssetReaders () [0x0019f] in /Users/admin/MonoGame/MonoGame.Framework/Content/ContentTypeReaderManager.cs:155 at Microsoft.Xna.Framework.Content.ContentReader.InitializeTypeReaders () [0x0000c] in /Users/admin/MonoGame/MonoGame.Framework/Content/ContentReader.cs:111 at Microsoft.Xna.Framework.Content.ContentReader.ReadAsset[CCBMFontConfiguration] () [0x00000] in <filename unknown>:0 at Microsoft.Xna.Framework.Content.ContentManager.ReadAsset[CCBMFontConfiguration] (System.String assetName, System.Action1 recordDisposableObject) [0x00000] in :0 at Microsoft.Xna.Framework.Content.ContentManager.Load[CCBMFontConfiguration](System.String assetName) [0x00000] in :0 at cocos2d.CCBMFontConfiguration.Create (System.String fntFile) [0x00000] in /Users/admin/cocos2d-xna/cocos2d/label_nodes/CCBMFontConfiguration.cs:40 at cocos2d.CCLabelBMFont.FNTConfigLoadFile (System.String file) [0x0002b] in /Users/admin/cocos2d-xna/cocos2d/label_nodes/CCLabelBMFont.cs:727 at cocos2d.CCLabelBMFont.InitWithString (System.String theString, System.String fntFile, Single width, CCTextAlignment alignment, CCPoint imageOffset) [0x00047] in /Users/admin/cocos2d-xna/cocos2d/label_nodes/CCLabelBMFont.cs:226 at cocos2d.CCLabelBMFont.Create (System.String str, System.String fntFile, Single width, CCTextAlignment alignment, CCPoint imageOffset) [0x00006] in /Users/admin/cocos2d-xna/cocos2d/label_nodes/CCLabelBMFont.cs:208 at cocos2d.CCLabelBMFont.Create (System.String str, System.String fntFile) [0x00000] in /Users/admin/cocos2d-xna/cocos2d/label_nodes/CCLabelBMFont.cs:197 at tests.Atlas3..ctor () [0x00064] in /Users/admin/cocos2d-xna/tests/tests/classes/tests/LabelTest/Atlas3.cs:27 at tests.AtlasTestScene.createAtlasLayer (Int32 nIndex) [0x00081] in /Users/admin/cocos2d-xna/tests/tests/classes/tests/LabelTest/AtlasTestScene.cs:60 at tests.AtlasTestScene.nextAtlasAction () [0x0001c] in /Users/admin/cocos2d-xna/tests/tests/classes/tests/LabelTest/AtlasTestScene.cs:31 at cocos2d.AtlasDemo.nextCallback (cocos2d.CCObject pSender) [0x00006] in /Users/admin/cocos2d-xna/tests/tests/classes/tests/LabelTest/LabelTest.cs:102 at cocos2d.CCMenuItem.Activate () [0x00016] in /Users/admin/cocos2d-xna/cocos2d/menu_nodes/CCMenuItem.cs:96 at cocos2d.CCMenu.TouchEnded (cocos2d.CCTouch touch, cocos2d.CCEvent e) [0x00029] in /Users/admin/cocos2d-xna/cocos2d/menu_nodes/CCMenu.cs:302 at cocos2d.CCTouchDispatcher.Touches (System.Collections.Generic.List1 pTouches, cocos2d.CCEvent pEvent, Int32 uIndex) [0x00112] in /Users/admin/cocos2d-xna/cocos2d/touch_dispatcher/CCTouchDispatcher.cs:225 at cocos2d.CCTouchDispatcher.TouchesEnded (System.Collections.Generic.List1 touches, cocos2d.CCEvent pEvent) [0x0000b] in /Users/admin/cocos2d-xna/cocos2d/touch_dispatcher/CCTouchDispatcher.cs:51 at cocos2d.CCApplication.ProcessTouch () [0x002a7] in /Users/admin/cocos2d-xna/cocos2d/Platform/CCApplication.cs:514 at cocos2d.CCApplication.Update (Microsoft.Xna.Framework.GameTime gameTime) [0x00025] in /Users/admin/cocos2d-xna/cocos2d/Platform/CCApplication.cs:185 at Microsoft.Xna.Framework.Game.m__2B (IUpdateable updateable, Microsoft.Xna.Framework.GameTime gameTime) [0x00000] in /Users/admin/MonoGame/MonoGame.Framework/Game.cs:582 at Microsoft.Xna.Framework.Game+SortingFilteringCollection1[Microsoft.Xna.Framework.IUpdateable].ForEachFilteredItem[GameTime] (System.Action2 action, Microsoft.Xna.Framework.GameTime userData) [0x00000] in :0 at Microsoft.Xna.Framework.Game.Update (Microsoft.Xna.Framework.GameTime gameTime) [0x00000] in /Users/admin/MonoGame/MonoGame.Framework/Game.cs:586 at tests.Game1.Update (Microsoft.Xna.Framework.GameTime gameTime) [0x00022] in /Users/admin/cocos2d-xna/tests/tests/Game1.cs:65 at Microsoft.Xna.Framework.Game.DoUpdate (Microsoft.Xna.Framework.GameTime gameTime) [0x00017] in /Users/admin/MonoGame/MonoGame.Framework/Game.cs:671 at Microsoft.Xna.Framework.Game.Tick () [0x00111] in /Users/admin/MonoGame/MonoGame.Framework/Game.cs:495 at Microsoft.Xna.Framework.iOSGamePlatform.Tick () [0x0002d] in /Users/admin/MonoGame/MonoGame.Framework/iOS/iOSGamePlatform.cs:223 at Microsoft.Xna.Framework.iOSGameView.DoTick () [0x00000] in /Users/admin/MonoGame/MonoGame.Framework/iOS/iOSGameView.cs:202 at MonoTouch.UIKit.UIApplication.Main (System.String[] args, System.String principalClassName, System.String delegateClassName) [0x0004c] in /Developer/MonoTouch/Source/monotouch/src/UIKit/UIApplication.cs:38 at tests.Program.Main (System.String[] args) [0x00000] in /Users/admin/cocos2d-xna/tests/tests/Program.cs:51

totallyeviljake commented 11 years ago

i ran it again in the simulator and sure enough it works there.

migueldeicaza commented 11 years ago

Question: is there a chance that we can just move the font loader to Cocos2D and forget about using MonoGame for it?

kjpou1 commented 11 years ago

From what I am gathering it is an AOT issue and not a linker issue with iOS. http://docs.xamarin.com/guides/ios/advanced_topics/limitations

Specifically from this line here right?

var l_readerType = Type.GetType(readerTypeString);

That is nasty. Have never looked at that part of the code in MG nor run into this issue before so will have to read about it. Never really ran into the issue so never occurred to me.

totallyeviljake commented 11 years ago

yes the font and other cocos2d resources can be loaded from embedded resources instead of using the content manager. the FNT file loader will need to revert back to the cocos2d-x variety of its implementation though.

totallyeviljake commented 11 years ago

there is an embedded resource content loader ... maybe we could use that instead.

spouliot commented 11 years ago

Microsoft.Xna.Framework.Content.DictionaryReader2[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[cocos2d.CCBMFontConfiguration+ccBMFontDef, cocos2d-xna, Version=2.0.3.0, Culture=neutral, PublicKeyToken=null]]

^ there should not be a reference on "mscorlib, Version=4.0.0.0" on an iOS project.

totallyeviljake commented 11 years ago

ahha! So we have to build the iOS content on a windows box, and that must be where the mscorlib reference originates.

totallyeviljake commented 11 years ago

@tomspilman does the MGCB work on iOS yet, or do we still have to build on Windows?

tomspilman commented 11 years ago

It was working for textures and audio files. We add SpriteFont support using pure .NET APIs, but i don't think it has been tested on Mac yet.

@RayBatts @dellis1972 - Have we tested this?

totallyeviljake commented 11 years ago

I get new exception message now when loading FNT files. I think this is from @raybatts recent changes:

around, add a creation function to ContentTypeReaderManager.AddTypeCreator() with the following failed type string: Microsoft.Xna.Framework.Content.DictionaryReader`2[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[cocos2d.CCBMFontConfiguration+ccBMFontDef, cocos2d-xna, Version=2.0.3.0, Culture=neutral, PublicKeyToken=null] at Microsoft.Xna.Framework.Content.ContentTypeReaderManager.LoadAssetReaders () [0x0019f]

cool!

totallyeviljake commented 11 years ago

Besides the Parallax test that uses GetData, this is the last issue on iOS as far as I can tell.

kjpou1 commented 11 years ago

Have any ideas how we can tackle this one? The CCBMFontConfiguration is not a Reader like the PList is.

Also am thinking that MGCB is not going to fix this issue. In the loading code all the version information is stripped out and I think it is need to actually load the class dynamically. PrepareType strips out some of the information on the class.

totallyeviljake commented 11 years ago

this works on the simulator, just not after AOT takes over. I'll verify again, but that appears to be the problem. if we can't get around AOT on this one, then we will need to (1) not allow the ReflectiveReader on iOS in MonoGame, and (2) rewrite this font reader to NOT use reflection...

kjpou1 commented 11 years ago

I started adding

#if IOS
[MonoTouch.Foundation.Preserve (AllMembers = true)]
#endif 

To the members of CCBMFontConfiguration and for ccBMFontDef and it is now crashing with reflective reader of CCRect. Added the same there and it is still having the problem.

totallyeviljake commented 11 years ago

there's an assembly level preserve attribute, right? I think that I added that once too.

Armed uses the reflective reader - @raybatts had posted a PR fix for that - I wonder if they are having a similar problem with reflecting on generics...

kjpou1 commented 11 years ago

I have it this far:

Microsoft.Xna.Framework.Content.ReflectiveReader`1[[cocos2d.CCRect, cocos2d-xna, Version=2.0.3.0, Culture=neutral, PublicKeyToken=null]]

2013-03-14 07:06:42.251 cocos2dtests[15397:907] Unhandled managed exception: Failed to get default constructor for ContentTypeReader. To work around, add a creation function to ContentTypeReaderManager.AddTypeCreator() with the following failed type string: Microsoft.Xna.Framework.Content.ReflectiveReader`1[cocos2d.CCRect, cocos2d-xna, Version=2.0.3.0, Culture=neutral, PublicKeyToken=null] at Microsoft.Xna.Framework.Content.ContentTypeReaderManager.LoadAssetReaders () [0x0019f] in /Users/Jimmy/Public/Share/MonoMacSource/kjpgit/MonoGame/MonoGame.Framework/Content/ContentTypeReaderManager.cs:155 at Microsoft.Xna.Framework.Content.ContentReader.InitializeTypeReaders () [0x0000c] in /Users/Jimmy/Public/Share/MonoMacSource/kjpgit/MonoGame/MonoGame.Framework/Content/ContentReader.cs:111 at Microsoft.Xna.Framework.Content.ContentReader.ReadAsset[CCBMFontConfiguration]() [0x00000] in :0

totallyeviljake commented 11 years ago

CCRect is a struct with no default ctor. After CCRect you'll have to do CCSize and CCPoint. That'll take you to the primitive ordinal types. CCRect, CCSize, and CCpoint are all structs.

totallyeviljake commented 11 years ago

So, from the .NET MSDN help:

Structs cannot contain explicit parameterless constructors. Struct members are automatically initialized to their default values.

http://msdn.microsoft.com/en-us/library/aa288208(v=vs.71).aspx

Hmmmmm

kjpou1 commented 11 years ago

Yeah have almost got them. Am on tKerningElement at the moment. It is hobbling along.

I have created ContentReaders for all of those.

kjpou1 commented 11 years ago

Having problems with tKerningHashElement with it being nested.

kjpou1 commented 11 years ago

Ok I think it is all working. I do not have access to an actual device, oh by the way as of last night I am able to test on an actual device, because I am remoted in. It looks like my machine went down which is weird.

I was in the middle of git add for all the classes when the connection failed so do not have the commits done yet. Let me see when or if the machine comes back.

tomspilman commented 11 years ago

FYI. I'm going to keep out of this conversation because I have absolutely no clue what anyone is discussing here. :)

RayBatts commented 11 years ago

Same as Tom. I've never even heard of a .fnt file before this.

Armed uses the reflective reader - @raybatts had posted a PR fix for that - I wonder if they are having a similar problem with reflecting on generics.

Yes, there are some problems with using generics on MonoTouch. As discussed though, the [MonoTouch.Foundation.Preserve (AllMembers = true)] attribute prevents it from being stripped and crashing at runtime. The fix I posted for the ReflectiveReader didn't have to do with generics though, nor do I think it caused this issue.

totallyeviljake commented 11 years ago

@RayBatts @tomspilman - FNT is a bitmap font description (like a poor mans version of spritefont)

We read in the FNT key (tells us where the characters are at in the bitmap) using the ReflectiveReader. The members of the FNT data structure use a generic dictionary and some struct members. The problem appears to be that the content loader needs to create instances of the structs using the default ctor, but there is no default ctor on a struct. So @kjpou1 has been adding custom type creators for these members.

I was just curious if you had seen the same problem with Armed on an iOS device (not the simulator).

I tried using Preserve but that didn't solve the problem for us. This one is a case of not being able to find the default ctor (TargetInvocation) rather than a missing type. @RayBatts changes to MonoGame helped to pick up on that detailed exception coming out of MonoGame.

spouliot commented 11 years ago

@RayBatts By default (which is "Link SDK assemblies" for devices) the linker does not remove anything from non-SDK assemblies. IOW it only removes code from Xamarin-shipping assemblies.

So unless you are using "Link all assemblies" using [Preserve] on in your own code won't change anything (as far as the linker is concerned).

If you ever doubt that the linker is an issue for your project you can: a) turn it off (i.e. Don't link) on a device build; or b) turn it on (i.e. Link SDK) on a simulator build (which is off by default); and compare results.

totallyeviljake commented 11 years ago

@spouliot I think the culprit of all this woe has been AOT and not the final linker. there must be some step in AOT that removes some generic or unused classes, as far as it can tell anyway. I dunno, I am having a hard time understanding why this works on the simulator and not on the device. We don't have the linker enabled on our device build of cocos2d-xna and it still doesn't work the same.

spouliot commented 11 years ago

It could be the AOT compiler. It sometime needs a bit of help, e.g. for nested generics, to detect (and compile) all the generic code that is required at runtime (not always easy to detect).

The fact that it works on the simulator (with the same linking options) makes it even more likely to be an AOT issue as the simulator uses the JIT (not the AOT) compiler.

OTOH you generally get a EngineExecutionException when, at runtime, the application finds out the required code is missing (i.e. if it was not compiled). If uncatched that exception can cause a crash too (or it could be catched and not reported, wrapped, inside another exception). If a runtime crash occurs then the crash report should hint about this.

kjpou1 commented 11 years ago

Hey guys

Here is a "Potential" fix for this issue for now. It work on the physical device as well as simulator.

https://github.com/totallyevil/cocos2d-xna/pull/56

Edit:

Also, am not saying I like this mod at all but what @RayBatts did was the perfect solution to at least get it rolling for now until we get a definitive resolution. Thanks Ray because I was looking at doing the same type of modification when I found your code.

kjpou1 commented 11 years ago

Jake

Which one? I have it up and running right now on my iPhone.

kjpou1 commented 11 years ago

I created 5 new readers.

CCPointReader CCRectReader CCSizeReader KerningHashElement CCBMFontPaddingReader

The others were for DictionaryReader.

Testing on iPhone 4 iOS 6.1.2

They should be in the platform/ContentReaders

        ContentTypeReaderManager.AddTypeCreator (
            "Microsoft.Xna.Framework.Content.DictionaryReader`2[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[cocos2d.CCBMFontConfiguration+ccBMFontDef, cocos2d-xna, Version=2.0.3.0, Culture=neutral, PublicKeyToken=null]]",
            ( ) => new DictionaryReader<Int32, CCBMFontConfiguration.ccBMFontDef> ()

            );

        ContentTypeReaderManager.AddTypeCreator (
            "Microsoft.Xna.Framework.Content.DictionaryReader`2[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[cocos2d.CCBMFontConfiguration+tKerningHashElement, cocos2d-xna, Version=2.0.3.0, Culture=neutral, PublicKeyToken=null]]",
            ( ) => new DictionaryReader<Int32, CCBMFontConfiguration.tKerningHashElement> ()

            );
        ContentTypeReaderManager.AddTypeCreator (
            "Microsoft.Xna.Framework.Content.ReflectiveReader`1[[cocos2d.CCRect, cocos2d-xna, Version=2.0.3.0, Culture=neutral, PublicKeyToken=null]]",
            ( ) => new CCRectReader ()

            );

        ContentTypeReaderManager.AddTypeCreator (
            "Microsoft.Xna.Framework.Content.ReflectiveReader`1[[cocos2d.CCPoint, cocos2d-xna, Version=2.0.3.0, Culture=neutral, PublicKeyToken=null]]",
            ( ) => new CCPointReader ()

            );
        ContentTypeReaderManager.AddTypeCreator (
            "Microsoft.Xna.Framework.Content.ReflectiveReader`1[[cocos2d.CCSize, cocos2d-xna, Version=2.0.3.0, Culture=neutral, PublicKeyToken=null]]",
            ( ) => new CCSizeReader ()

            );

        ContentTypeReaderManager.AddTypeCreator (
            "Microsoft.Xna.Framework.Content.ReflectiveReader`1[[cocos2d.CCBMFontConfiguration+tKerningHashElement, cocos2d-xna, Version=2.0.3.0, Culture=neutral, PublicKeyToken=null]]",
            ( ) => new KerningHashElementReader ()

            );

        ContentTypeReaderManager.AddTypeCreator (
            "Microsoft.Xna.Framework.Content.ReflectiveReader`1[[cocos2d.CCBMFontConfiguration+ccBMFontPadding, cocos2d-xna, Version=2.0.3.0, Culture=neutral, PublicKeyToken=null]]",
            ( ) => new CCBMFontPaddingtReader ()

            );
kjpou1 commented 11 years ago

Jake

Did not push the main mod last night. Thought it was already done but I guess it was not. Will send the PR.

kjpou1 commented 11 years ago

Here is the missing part:

https://github.com/totallyevil/cocos2d-xna/pull/57

Sorry about that.

totallyeviljake commented 11 years ago

no worries. with your mac dying, I figured something was lost ..

totallyeviljake commented 11 years ago

MenuTest and Label Test both work.