Monday, November 26, 2012

Speeding up TextArea modifications under Cocoa

When doing a lot of manipulation to a TextArea's contents under Cocoa, performance can suffer due to the underlying NSTextView doing work on layout and glyph selection. This can be sped up by telling the text view that you're going to begin editing. You can do this by using a few Cocoa Declares.

The Declare statements are relatively simple:

Declare Function documentView Lib "AppKit" Selector "documentView" ( obj As Integer ) As Integer
Declare Function textStorage Lib "AppKit" Selector "textStorage" ( obj As Integer ) As Integer
Declare Sub beginEditing Lib "AppKit" Selector "beginEditing" ( obj As Integer )
Declare Sub endEditing Lib "AppKit" Selector "endEditing" ( obj As Integer )

These Declares give you access to methods for enabling "batch-editing mode" for the underlying NSTextView.

First you want to get the text storage for the document, which is a two-step process. In the first step, you take the TextArea's Handle property (an NSScrollView instance) and ask for its document view:

Dim docView As Integer
docView = documentView(MyTextArea.Handle)

Now you get the NSTextStorage for the NSTextView:

Dim storage As Integer
storage = textStorage(docView)

With the text storage, you can now enable batch-editing mode by calling beginEditing:

beginEditing(storage)

Now you can make your significant changes to the TextArea:

For i As Integer = 0 To 5000
  MyTextArea.AppendText("Lorem ipsum dolor sit amet. ")
Next

And when you are finished disable batch-editing mode:

endEditing(storage)

So how much does this improve performance? In my tests, the For loop by itself takes about 4.3 seconds to complete. Using batch-edit mode with these Declares drops it to 0.02 seconds. That's almost instantaneous!

If you find you are going to use these Declare often, you might want to add them to a module as Extension methods so that you can call them more easily, which I'll leave as an exercise for the reader.

7 comments:

Rick said...
This comment has been removed by the author.
Rick said...

This kills the multiplatform concept. I would prefer you guys create new commands for that (in my example [Enable/Discable]FastControlUpdates) and make all necessary steps under the hood for all platforms, in some platforms it just could generate no code or a proper code. Like:

EnableFastControlUpdates(Listbox)
DoManyUpdatesAndChangesToTheListBox()
DisableFastControlUpdates(Listbox)

Geoff Perlman said...

@ Rick - It appears that there is not an equivalent for other platforms, but we are looking into that.

Markus Winter said...

"If you find you are going to use these Declare often, you might want to add them to a module as Extension methods so that you can call them more easily, which I'll leave as an exercise for the reader."

Beginners soooooo love REAL.studio when they read this ...

I often see three blind spots among developers:

- languages other than English (yes, there is live outside the US)

- color usage (there are color blind people who can't even see your highlights)

- assuming everyone else is as comfortable around programming concepts as you are

Rick said...

I know a multi-platform language that has what I told. They even go further, they have a command like "StartFastUpdates", and it affects the entire window, with all the controls. It avoids all update/refreshes on the screen for all platforms, taking care with different approaches for each platform under the hood to speed up processing, and after a "StopFastUpdates" it enables the repaint of the affected parts. There is no need for anyone to think and to know specifics of any Operating System. The intention of a multi-platform language is to do the best in all platforms avoiding the programmer to know other languages and specifics of the platform to accomplish usual things. I simply hate workarounds taken as solutions.

Markus Winter said...
This comment has been removed by the author.
Paul Lefebvre said...

From Kem Tekinay:
If you install the current version of MacOSLib into your project (https://github.com/macoslib/macoslib), these functions (and more) are built in so you can do:

dim tv as new NSTextView( myTextArea )
dim ts as NSTextStorage = tv.TextStorage
ts.BeginEditing
...
ts.EndEditing

I have a colorization method that was taking about a second to add about 3000+ colors to a 6000+ character string. Once I added this code, it dropped to about 40 ms.