[Openmcl-devel] thread overview

Gary Byers gb at clozure.com
Mon Aug 23 19:25:38 PDT 2004

On Mon, 23 Aug 2004, alex crain wrote:

> I played around a little bit, and the delay happens well after the
> character is passed to the "Self Insert" command code and the buffer is
> updated on the hemlock side. I'm thinking somewhere on the cocoa side,
> maybe the buffer update notification is taking awhile to get to the
> hemlock window thread.
> :alex

When the Hemlock side is about to make a change to the buffer, it

(defun hi::document-begin-editing (document)
  (send (slot-value document 'textstorage)
        (@selector "beginEditing")
        :with-object (%null-ptr)
        :wait-until-done t))

e.g., it asks the Cocoa event thread to send a "beginEditing" message
to the textstorage, and waits until that message has been processed.

When it makes a change to the buffer, it calls something like (depending
on whether the change is an insertion, deletion, or modification:)

(defun hi::buffer-note-insertion (buffer mark n)
  (when (hi::bufferp buffer)
    (let* ((document (hi::buffer-document buffer))
	   (textstorage (if document (slot-value document 'textstorage))))
      (when textstorage
        (let* ((pos (mark-absolute-position mark)))
          (unless (eq (hi::mark-%kind mark) :right-inserting)
            (decf pos n))
	  (format t "~&insert: pos = ~d, n = ~d" pos n)
          (let* ((display (hemlock-buffer-string-cache (send textstorage 'string))))
            (reset-buffer-cache display)
            (update-line-cache-for-index display pos))
          (perform-edit-change-notification textstorage
                                            (@selector "noteInsertion:")


(defun perform-edit-change-notification (textstorage selector pos n)
  (let* ((number-for-pos
          (send (send (@class "NSNumber") 'alloc)
                :init-with-int pos))
          (send (send (@class "NSNumber") 'alloc)
                :init-with-int n)))
    (%stack-block ((paramptrs (ash 2 target::word-shift)))
      (setf (%get-ptr paramptrs 0) number-for-pos
            (%get-ptr paramptrs (ash 1 target::word-shift))
      (let* ((params (make-objc-instance 'ns:ns-array
                                         :with-objects paramptrs
                                         :count 2)))
        (send textstorage
                    :with-object params
                    :wait-until-done t)
              (send params 'release)
              (send number-for-pos 'release)
              (send number-for-n 'release)))))

e.g., we're once again performing a selector on the main thread and
waiting for it to complete.   On the way there, we invalidated the
line<->index cache (why do this unconditionally ?) and allocated some
NSNumbers and an NSArray to pass the parameters in.

When we're all done with the modification, we call:

(defun hi::document-end-editing (document)
  (send (slot-value document 'textstorage)
        (@selector "endEditing")
        :with-object (%null-ptr)
        :wait-until-done t))

All of this is is based on the assumption that it's only safe to do
beginEditing/edited:range:changeInLength/endEditing from the main thread.
I think that that assumption is probably overly conservative: forcing
all of the "Cocoa stuff" to happen in the main thread avoids some
potential serialization issues, but there are probably other (better)
ways of addressing those issues.

I tried changing this (conditionalizing those things that currently
try to force the main thread to do something to instead happen in
the hemlock thread) and things didn't break spectacularly; I type
so slowly that I'm not sure that I'd notice any speedup, but holding
down the "a" key and letting it autorepeat didn't seem any slower
in a Hemlock listener than in XEmacs (that may have more to do with
autorepeat delay than anything else.)

After we've done an endEditing, the Cocoa text system may decide
that it'd be a good idea to update the display.  (It'll probably
redraw some or all of the line(s) that the change is on.)  Since
beginEditing/endEditing can be nested, it may be possible to
defer updates a bit.  One way of doing that (that -might- work)
is to modify the code which pulls key events off of the per-thread
queue to issue beginEditing (if there isn't already an editing
operation in progress) and issue the matching endEditing if we
time out waiting for the next character (e.g., if there's already
a key event enqueued when we've finished with the previous one,
don't allow the display to update.)  This obviously needs a little
more thought, but being a little clever about this would probably
pay off some.

More information about the Openmcl-devel mailing list