[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: OBJECT-ORIENTED CLX



> From: "David C. Martin" <dcmartin@cs.wisc.edu>
>
> I tend to agree with JAK that it would be better to leave CLX alone and work
> on a higher-level abstraction (e.g. CommonWindows) at which to provide OO. 

I guess I don't agree with JAK about this at all.  We can always keep
envisioning the great all-encompassing object-oriented window system, but
at some point somebody has to design and build it.  OO-CLX is a first-pass
at such a design.  Moreover, CLX is an interface to X11, not a "generic"
window system.  As pointed out earlier, the problem with a generic window
system is that the common denominator between all window systems is much
too low.  If CLX can be used as an implementation of a generic window
system, then that's good.  Similarly if the Mac toolkit can be used to
implement the same generic window system on another machine, then that's
good too.  None of that has any bearing on the design of an X11 interface
though.

I do agree with JAK that there are many places in the CL kernel which could
be objectified.  Pathnames and streams are obvious ones.  The fact that
this hasn't happened yet for pathnames and streams shouldn't prevent us
from designing CLX in the way we'd like all things to be ultimately.

I want to reiterate again why I think approach 1 to event dispatching is
best.  One could imagine writing a hairy method combination for which could
create combined methods that dispatched off the types of slots inside
object, or a subclass of generic-function which knew how to discriminate on
SATISFIES type-specifiers.  However, there's a simple way to get
essentially this same functionality without meta-programming.  The solution
is to write two generic-functions:

	(defmethod handle-event ((w my-window) (e button-press-event))
	  (handle-button-press-event w
				     (slot-value e 'code)
				     ...))

	(defmethod handle-button-press-event ((w my-window)
					      (code (eql :button-1-press))
					      ...)
	  ...)

You may argue that this would take twice as long (two dispatches), but who
knows how long a SATISFIES type-specifier would take.

The reason I'm not suggesting this approach is that I've found in practice
that the second (inner) method is almost always what you want.  Now if the
call to HANDLE-BUTTON-PRESS-EVENT was done by PROCESS-EVENT directly, this
is exactly approach 1.  This problem doesn't typically come up in most CLOS
programming because people write their methods to discriminate on the
objects that matter, not on some parent object which holds the objects that
matter in instance variables.

As I pointed out in my original message, X is not going to start sending
out new events which are subtypes of events defined in the protocol spec,
so why should the user be allowed to subclass event objects?  Also, why
cons (or use resources) for event objects that are immediately going to be
destructured in the discrimination process?  This is *very* expensive.  

Also approach 1 is roughly equivalent to what CLX is doing now with its
handler call-back functions.  Instead of taking keyword arguments though,
the arguments are in-place so methods can discriminate on them if they
choose to.

> From: "David C. Martin" <dcmartin@cs.wisc.edu>
>
> I was concerned with this performance problem, but wanted to retain the ability
> to let the novice user write an event loop consisting of:
> 
> (loop (dispatch-event (get-next-event)))
> 
> In order to provide optimizations, I added a hashing mechanism on something
> I called the _descriptor_ of an event (e.g. type) which could be quickly
> retrieved from the raw (i.e. C language) event information.  This descriptor
> could then be hashed to a particular function or method which would expect
> a raw event argument and might make use of low-level functions in order to 
> perform the handling.
>
> Interestingly, the small loop did not have to change at all, but I was able
> to hand-optimized and in-line the critical handlers for my window objects to
> provide performance in the range of 18ms for the null event case.

I was really trying to avoid the need to hand-code special event handlers.
It is extremely difficult for the users to do to optimize his application. 

> I have always thought that it was best to provide several layers to any system,
> with the top-level being easy to use, but perhaps slow when the novice defined
> new objects, and the lower-levels integrating cleanly into the higher layers 
> and providing the expert with the ability to produce highly optimized code 
> which could make use of the underlying support system (e.g. CLX or XCL).

I think this approach requires the users to be experts to get any real work
done.  The problem with streamlining one layer and building an "extensible"
and easy to use layer on top of it is that it creates a very large gap
between what's easily doable and what's tollerable.  Pushing
object-oriented programming to a layer on top of CLX has exactly this
pitfall.  I'd rather see CLX be designed with performance AND extensablity
in mind up front.

> From: Kerry Kimbrough <Kimbrough@dsg.csc.ti.com>
>
> I don't have CLUE performance numbers handy, but I'm certain allocation of event
> objects is no bottleneck.  Total user response time for typing/echoing a
> character in a text editor contact is always adequate for interaction (could be
> better, though).  This includes lookup of the handler method, which in CLUE can
> be complex and which has not even been optimized in the current version.

Has anyone really done any performance analysis comparing CLUE to the
XToolKit for instance.  I think they'd be surprised. 

Other comments:

> From: kempf@Sun.COM
> 
> This new metaclass is a good idea, but why restrict it just to X? Why not
> have a DISTRIBUTED-OBJECT metaclass?

Because the way this meta-class operates strongly depends on what is
distributed and where.  Surely a class which communicates with a database
would have a different meta-class than one which communicates with the X
server.  Are you suggesting a meta-meta-class?  {e.g. type-of(window) =
x-class; type-of(x-class) = distributed-object; type-of(personnel-record) =
db-class; type-of(db-class) = distributed-object;
type-of(distributed-object) = ?? }

> From: kempf@Sun.COM
>
> >In this meta-class, a new slot-descriptor is defined,
> >X-SLOT-DESCRIPTOR, which allows an additional strategy of allocation,
> >namely :X-SERVER.  Specifying :X-SERVER allocation does not reserve space
> >in the actual CLOS object for the value, but instead defines reader and
> >writer methods which actually perform the X protocol requests.  (There may
>
> I don't understand why this is needed. This could be done with an
> X-SERVER-MIXIN which is mixed with all the Lisp classes representing
> X objects, then defining the appropriate qualified methods on the
> slot accessor functions to perform the protocol requests. Is performance
> a concern? If so, then won't the round trip server time dwarf the
> amount of time needed to do the qualified method invocation?
> My understanding of X is that the types of resources in the server
> are not extensible. If so, then the qualified methods for slot access
> can be defined and delivered as a nonextensible interface. So the
> flexibility of a metaclass isn't needed.

The idea here was to streamline the definition process (and indirectly
document CLX's operation/intention).   The way I envision this actually
working is that information regarding the actual protocol request will be
declaratively specified with the slot descriptor information:

	(defclass drawable (x-plist-mixin
			    external-object)
	  (...
	   (outside-left
	    :type int16
	    :initform 0
	    :initarg :outside-left
	    :reader outside-left
	    :allocation :x-server
	    :request-buffer-size 16
	    :request-buffer-position 12
	    :request-buffer-request-code ...)
	   ...))

And this would automatically generate something like:

	(defmethod outside-left ((d drawable))
	  (with-request-buffer (b :size 16
				  :request-code ...)
	    (buffer-get b :type 'int16 :position 12)))

	(defmethod (setf outside-left) ((d drawable)) (value)
	  (with-request-buffer (b :size 16
				  :request-code ...)
	    (buffer-set b value :type 'int16 :position 12)))

(I am of course exercising both imaginative and wishful thinking here.)
Maybe a real implementation would have the methods explicitly written, and
the :X-SERVER slot-descriptors simply would not allocate space in the
X-CLASS instances.  Note that the :X-SERVER declaration is necessary
because not all slots in an instance will be allocated in the server.  In
particular, subclasses of window will want to hold their own local data.

Sure, a new meta-class is not required, but then again, meta-classes are
never required.  Anything defined with them could be defined by regular
classes.  They just make life easier for the programmer, and things more
understandable by the user.

> >One difficulty with the old CLX model is that some objects are created in
> >the server process simultaneous with their creation in the Lisp process.
> >This includes WINDOWs, PIXMAPs, COLORMAPs and CURSORs.  Other objects are
> >created in the server process some time after their creation in the Lisp
> >process.  This includes DISPLAYs, and FONTs.  This object-oriented
> >version of CLX takes the uniform approach of delaying the creation of all
> >server allocated objects until the OPEN-OBJECT generic function is called.
> 
> This will solve the problem of object creation, but it doesn't solve the
> problem of co-ordinating asynchronous state changes in the client and server. 
> This is a general problem with programming using distributed objects. 

No.  WITH-STATE is still needed for that.  Also things like
WITHOUT-INTERRUPTS. 

> OK, now I understand how you propose to solve the cache consistency problem
> between client and server-by simply not caching any state in the client!
> Hmmm. What about performance? Having to go round trip to the server just
> to figure out the height of a character in a multifont display could
> slow display of a complex multifont view to a crawl. 

The implementation is free to cache whatever it sees fit.  Xenon in
particular did this and allowed certain events to update the cache.  This
way, many server requests were eliminated. 

> From: Kerry Kimbrough <Kimbrough@dsg.csc.ti.com>
>
> I think it's a good idea to have a version of CLX which consists of CLOS classes
> and generic functions and which thereby meshes directly with CLOS.  For the most
> part, I regard this as matter of implementation.  Very little of the existing
> CLX programmer interface ought to change. 

On the surface this may seem true, but there is a fundamental difference.
Requiring CLX to be object-oriented would change the way applications would
use it.  The function names could stay the same, but given the ability to
subclass CLX objects and override or specialize primitive methods, a
toolkit layer like CLUE could be implemented without building yet another
paradigm (CONTACTS).  CONTACTS are also only the tip of the iceberg.  What
happens when the user wants to specialize COLOR, or FONT?  Xenon allowed
this and it made it possible for users to write things like and OPEN FONT
method which looked in the color map for a "close" color, (were "close
enough" was itself a specializable criterion (method)).

>  Why do the cursor and glyph-cursor classes define :x-server slots?  These
>  values cannot be inquired via a protocol request.  Same thing is true for
>  other classes and other slots (e.g.  background slot of window).  Such slots
>  are really just "initargs".  

Yes, you're right that some of those slots shouldn't be :x-server
allocated (I got carried away).  

> But, if you separate make-instance and
>  open-object, then these have to be :allocation :instance. This sorta defeats
>  the idea of not allocating copies of server resources; is this an argument
>  against open-object?

I was thinking that you could just save the init-plist on the object when
you did a MAKE-INSTANCE call.  This would require just 1 slot.  OPEN-OBJECT
would then take the init-plist apart.  CLOSE-OBJECT could build a new
"init-plist" for the next time the object is opened, by querying the server
for all the object's attributes before it actually issued the destroy
request.  The alternative is to actually allocate all the :X-SERVER slots
with :INSTANCE allocation, and use these slots as caches when necessary. 

Regardless of whether or not we separate the lisp object creation from the
X server object creation, I think CLX should present itself as an
object-oriented interface.  In doing so, the meta-classes and slot
allocations are still useful concepts. 

Warren