AngelFont timing problems

Monkey Targets Forums/Android/AngelFont timing problems

Midimaster(Posted 2013) [#1]
[*** EDIT ***]
Now I know, the error messages descriped here are not caused by angelfont.
[*** EDIT ***]

I try to optimize my game in loading. While all pictures are loaded async the angelFont now needs the longest time: 9 long seconds without any chance of informing the user. It looks a bit like "hang up".

I load a "unicode" font with ~790 characters. The unicode.png has a size of 272kB.

With the "angel_verdana" font the problems does not appear! Also the problem does not come if I do not use async loading. It seems to be a combination of both:





"I/[Monkey](22450): OnRender at 4074"
....says that there is a first OnRender() at msec=4.074. The screen is still black.

Now the app needs 4.5 sec until I have the next OnRender() at msec=8.674. But there is this strange "SQLite... " error message....

And this too:
"E/dalvikvm(22616): Could not find class 'android.os.UserManager', referenced from method xg.b"

From msec=8.674 I can see the text on the screen. The app is running stabil. But there is the same error message at msec=10.341 again!


I isolated the problem into a demo code. Here the OnRender() works between the error messages. But in the main program, the second OnRender() appears after a "monster" break of 9sec




Did anybody see something like this too ever? Does anybody know what happens? Is there a solution?

In fact the app runs stabil also with this problem, but the long time of "hang-up" is annoying!

Here is a ZIP (264k) with the unicode font:

http://www.blitzforum.de/upload/file.php?id=12623

(You will also find the modifyed angelfont.monkey there to use more than 255 character)


Gerry Quinn(Posted 2013) [#2]
I've never had problems, but I don't use large character sets - in fact I pare away most of the Latin supplements leaving me with about 100 characters. That said, my latest game loads six such fonts without any problems. I should note that I call the original AngelFont constructor rather than the new-fangled LoadFontXML.

Are you sure the problem is not with LoadImageAsync?


Midimaster(Posted 2013) [#3]
Yes, not without the LoadImageAsync()! And you need not to combine AngelFont and LoadImageAsync to get the problems as you can see in the sample code. It is already enough, that any image is loaded async.

Now I thought, that I perhaps made a mistake, when pimped the "angelfont.monkey". I had to size the arrays from 255 to 1000. Maybe this caused a memory leak?

I need unicode character, because I often offer translation for a lot of languages like "russian". F.e. >30% of my downloads come from russia!

I use the possibility of *.txt file only! Never tested the XML possibilities of AngelFont.


Nobuyuki(Posted 2013) [#4]
Have you checked the profiler for allocations? Large string allocations are what crippled AngelFont for me. Try loading a font with the new XML loading code I mentioned on the AngelFont thread, or possibly the plaintext .fnt support modification (see below) and see if your problems go away.

VariFont is still on my list of future projects, but I wonder if the board would benefit from making an official fork of AngelFont with some of the fixes the community has made in the meantime, including:

1. unicode support (Map<Char> over array lookup for chars > 255, etc)
2. proper paged font support using font metadata instead of "autodetection"
3. Less string-harassing, faster XML loader ( http://www.monkeycoder.co.nz/Community/post.php?topic=141&post=54892 )
4. A non-outdated ctor
5. Plaintext .fnt support ( http://monkeycoder.co.nz/Community/posts.php?topic=4964#54435 )


Edit: I've started a fork of AngelFont to start incorporating some of the various hacks, fixes, and improvements made since the pack-in version of AngelFont. I might start a thread about it later, but for now, here it is:

https://github.com/nobuyukinyuu/angelfont-tryouts


Midimaster(Posted 2013) [#5]
Ok, I now recognized, that the error messages are not caused by AngelFont or Monkey. It looks like, but it has no relationship. The error message seems to appear always after 4sec and again after 8sec on this smartphone (Samsung GT 5300).

The only problem, which remains is the long period AngelFont needs to read the 730 lines of character description.

@Nobuyuki
I noticed that you have speed up the reading of the string lines succesfully. But it was in the XML part, which I never use. Could you descripe me, what wa the trick? So I can apply this changes to the TXT part?

The TXT part does not need the config.monkey


Gerry Quinn(Posted 2013) [#6]
If you are not changing the font frequently, maybe you could find a way to compile this data into your Monkey program.

It looks like reading it involves a ton of string manipulation, which may be falling foul of the garbage collector. You might be able to bypass a lot of this by pre-processing the data and putting it directly into the Angelfont loader/constructor.


Midimaster(Posted 2013) [#7]
New test results:

The method LoadFont() needs only 3.5 sec to scan the 730 lines. Then the app jumps into a first OnRender() and text "3500" appears the screen.

Now a strange pause happens and the next OnRender() is after another 6 sec of waiting <and shows "9500".

I struggled thru the lines of LoadFont() and could circle the problem to one line:



So I resume: It is not a problem of garbage collecting the 730 string lines. But the creation of 730 array elements members causes the big pause afterwards.

The Array is an array of a class "Char"

Class Char
	Field asc:Int
	Field x:Int
	Field y:Int
	
	Field width:Int
	Field height:Int = 0
	
	Field xOffset:Int = 0
	Field yOffset:Int = 0
	Field xAdvance:Int = 0
	
	Field page:Int = 0
	
	
	Method New(x:Int,y:Int, w:Int, h:Int, xoff:Int=0, yoff:Int=0, xadv:Int=0, page:Int=0)
		Self.x = x
		Self.y = y
		Self.width = w
		Self.height = h
		
		Self.xOffset = xoff
		Self.yOffset = yoff
		Self.xAdvance = xadv
		Self.page = page
	End
....


Because of the unicode values the array needs a size of 8500 elements. Not all elements are used: only the 730 which are defined in the txt file are created. This means the array has 7770 "holes".

Might this be the reason for the problem?


If I fill the array continuosly without "holes", the problems is still there, but smaller... With real 730 elements in a array sized on max 750 elements the pause is still there but only 2 sec.


Nobuyuki(Posted 2013) [#8]
@Midimaster
The optimizations in loading are best seen on Android -- the trick is that less string manipulation means less new strings are created (because as you know they're immutable, and changing them makes a new String), meaning less GC. I haven't tested the speed of LoadPlain() or the new LoadFontXML() to LoadFont(), because I consider LoadFont() depreciated as it requires a special tool to produce the txt files. Tests will reveal actual efficiency!

If you want better memory usage, I recommend changing chars:Char[65536] to an IntMap<Char>. From what I understand, indexing will be reduced from O(1) to O(log n) because you no longer know "exactly" where the Char you need is. An alternative would be some sorta hashtable, but (admitting my coding inexperience here) I've never dealt with the things, so I wouldn't know where to advise on that.

I'm currently evaluating what is the best way to proceed with regards to unicode support..... the angelfont hack in monkey-utf8 used the array for ASCII chars for backwards compatibility, and IntMap<Char> for everything else. Speed may have taken a hit due to the branching used by the added Ifs, but again, untested. I made an assumption that the vast use of ascii and the (essentially free) gets from the char array was more optimal with the cost of using an If-Then-Else before passing the lookup to the IntMap<Char> for the extended character than it would be just using the IntMap<Char> for everything, including ascii.


Midimaster(Posted 2013) [#9]
Hello Nobuyuki

I do a lot of tests at the moment to find out, what the big pause causes. First I thought it is the heavy use of strings. but isolated they were very fast.

Then I had a look on the big array, But isolated it is very fast too!


All problems I descripe here are problems in starting up a font. I never got performance problems when using it later. For me it sound like your last post was talking about optimizings on using the font.

I never worked with an IntMap. I will have a look on it.


A duration of 9sec for a reading process is in fact not really a problem. But for the user it looks like the app hangs up. So I now plan a workaround, where the reading of the 730 lines is divided in 10 part a' 73 lines and between this parts a short reaction on the screen, or something like this.


I keep on investigating and will tell the resluts here....


Midimaster(Posted 2013) [#10]
Now I could optimize the loading of large AngelFonts. I did not solve the problem of calling 730 times the Char.New(), but I could eliminate all String operations.

I scanned the text with the 730 lines of character desriptions with a BlitzMax function. All parameters were stored as ShortInt in a bank.

BLITZMAX CODE:
SuperStrict

Global Gesamt$=LoadText("unicode_64.txt")
Global Lines$[]= Gesamt.Split(Chr(10))

Global Bank:TBank=CreateBank(2*8*Lines.Length+16)

Global Nr%

For Local Line$ =EachIn Lines
	Local  Values$[]= Line.Split(",")
	If Values.Length<8 Continue
	If Int(Values[0])=0 Continue
	For Local j%=0 To 7
		Bank.PokeShort 2*(Nr*8+j), Int(Values[j])
	Next
	Nr=Nr+1
Next

Bank.Save "unicode_64.bin"


Then this binary file is loaded in Monkey:

MONKEY CODE:
Class AngelFont
.....

	Method LoadFontFast:Void(url:String, FontImage:Image=NULL)
		Print "LOAD FONT START TIME=" + Millisecs() 
		
		Local Bank:DataBuffer = DataBuffer.Load("monkey://data/" + url  + ".bin")	
		
		For Local Nr%=0 To (Bank.Length/16-16)
			Local id%,x%,y%,w%,h%,xoff%,yoff%,adv%
			Local da%=Nr*2*8
			id=Bank.PeekShort(da)
			x=Bank.PeekShort(da+2)
			y=Bank.PeekShort(da+4)
			w=Bank.PeekShort(da+6)
			h=Bank.PeekShort(da+8)
			
			xoff=Bank.PeekShort(da+10)
			yoff=Bank.PeekShort(da+12)
			adv=Bank.PeekShort(da+14)
			chars[id] = New Char(x, y, w, h, xoff, yoff, adv, 0)

			If h > Self.height Self.height = h
			If yoff < Self.heightOffset Self.heightOffset = yoff
		Next
	
		If FontImage<>Null
			image[0] =FontImage
		Else
			image[0] = LoadImage(url+".png")
		Endif 
		Print "LOAD FONT EXIT TIME=" + Millisecs() 
	End Method



Gerry Quinn(Posted 2013) [#11]
Did it work?


Midimaster(Posted 2013) [#12]
Yes, it works like normal. I did not change anything in the real time parts like drawing, etc... I only try to optimize the loading procedure.

On my very slow Samsung GT S5300 (Android 2.3) I have heavy performance problems while loading a font with 730 characters. With the new DataBuffer I need 3 seconds instead of 9 seconds.

The performance problems seem to be a combination of heavy string use and heavy calls of the Char.New(). With the elimination of all string operations the half way seems to be done... but only the half way...

I had no success in eliminating the Class "Char" and replacing it by direct call of the already loaded DataBuffer...

	Method DrawViaBank:Void(txt:String, x:Int, y:Int)
		xOffset = 0
		Local da%
		For Local i:= 0 Until txt.Length
			Local c_xoff%, c_yoff%, c_x%, c_y%, c_w%, c_h%, c_adv%
			Local asc:Int = txt[i]
			If asc>MAX_CHARS Then asc= "*"[0]
			
			da=Wo[asc]
			c_x=CharBuffer.PeekShort(da+2)
			c_y=CharBuffer.PeekShort(da+4)
			c_w=CharBuffer.PeekShort(da+6)
			c_h=CharBuffer.PeekShort(da+8)
			
			c_xoff=CharBuffer.PeekShort(da+10)
			c_yoff=CharBuffer.PeekShort(da+12)
			c_adv=CharBuffer.PeekShort(da+14)
			DrawImageRect FontImage, x+xOffset+c_xoff,y+c_yoff, c_x, c_y, c_w, c_h
			xOffset += c_adv
		Next
		
	End



Nobuyuki(Posted 2013) [#13]
Midimaster:
Now, as for allocation with Char..... that might be inevitable unless an extensive rewrite is made to the way AngelFont allocates memory. Right now, everything's stored in Char objects, which need to be instanced one at a time. It may be more efficient to load all parameters inside arrays of primitive types, and resize them on load.

May I ask why the DrawViaBank() method doesn't work? Is your data structure sound? Are there any visible symptoms of it not working?

Edit: Also, Char seems to have an autoboxing method in it. It might be worth looking into whether it's used somewhere non-obvious in angelfont for type conversion.


Midimaster(Posted 2013) [#14]
The DrawViaBank worked, but brought not a better performance...


Nobuyuki(Posted 2013) [#15]
No better performance despite the fact chars are no longer being allocated? Or, do you still allocate the chars for backwards compatibility? DataBuffer should allocate the memory in one big block natively, so in theory it could be faster than allocating the chars in a loop. Of course, it could also be not faster at all :D


Midimaster(Posted 2013) [#16]
I wrote a sample code not longer based on AngelFont. The sample only had two functions "LoadFontBank" and "DrawViaBank" without any kerning, no functions about Height(), etc... With this minimum the texts were written correct to the display.

But therefor I needed a table to find the correct data piece for a character. So I had to add an array Wo%[8000]. With this the (loading) time was again not better as with Chars().

As 3 seconds on the old Android 2.3 smartphone were acceptable and on the newer devices there is no pause, I decided to stop and return to use the Chars(). So I need not to write all features of AngelFont new and I can use them.

Now Only the LoadFont() is replaced by an LoadFontFast() which loads the INT table instead of string lines. And LoadFontFast() now takes the image as an parameter and does not load it by itself. This was important for me, because my image is 1024x1024 and has a size of 272kB. Now I pre-load it async and when it is ready I start LoadFontFast()

My last idea to eliminate the 3 seconds pause is still the idea of moving the data reading to the DrawText(): If a character already has a corresponding Char[] the Character is drawn normal. If not... the DataBuffer will be searched for the corresponding data part and this Char[] will be created.

This would only create those Chars[] which are necessary. Because normally I have 730 characters avaiable, but only max 100 are used in one language on a certain device.


Gerry Quinn(Posted 2013) [#17]
When I asked did it work I meant did it fix the problem - apparently not :-(

Have you the same problems on other devices (or even the emulator)?


Nobuyuki(Posted 2013) [#18]
@Midimaster

I would avoid trying to allocate on first Draw, this would cause a large hiccup in your app in inconvenient places. Perhaps you can make a "preloader" method for AngelFont instead where you can supply it an appropriate string of characters to be used, or overload your loading method of choice to run this preloader if supplied an appropriate string.

"Chars used" strings are common in east asian languages when resources are at a premium -- only chars used in the game's strings file are added to the font atlas and (in the past) sometimes the final string file itself was modified to be tightly-packed or compressed.

I'm starting to lean more and more towards the use of an IntMap<Char> for everything, all things considered. We can solve the fetch speed down the line by either 1. Writing a faster container format (one using hashtables or a customized splay tree of some kind), and/or 2. Reducing the number of lookups that need to be made in the first place.

The latter can be done by caching common string outputs directly to a texture for later recall, but would probably be better suited to a different font library, because it would change the typical usage patterns of AngelFont. It could be hacked in, though, by having a StringMap<Image> to check for recent strings (or deliberately-cached strings) and skipping the traditional blit process. I plan on doing something like this with VariFont in the future.