Re: Dealing with exceptions in a drawing stack
Re: Dealing with exceptions in a drawing stack
- Subject: Re: Dealing with exceptions in a drawing stack
- From: Graham Cox <email@hidden>
- Date: Tue, 20 Jan 2009 09:54:05 +1100
On 20 Jan 2009, at 5:24 am, I. Savant wrote:
On Jan 19, 2009, at 12:57 PM, Paul Bruneau wrote:
I'm confused by it, since I have been directed in the past by
people who know that The Cocoa Way is to have objects know how to
draw themselves--via a category if desired.
Is there differing opinion on this? (apparently there is...)
This is tough to generalize. There is not only differing opinion,
but also plenty of room for debate. :-)
In the Sketch example, there are different shapes (including
"text"), each of which handles drawing somewhat differently. I
suspect the decision not to put this into the canvas view is more a
matter of organization than architecture. I happen to agree with the
decision.
There's nothing about the MVC design pattern that demands all
drawing code belongs in the view. In the particular case of a
drawing application where the shapes to be drawn *are* the data
model, how to draw a shape could be viewed as "business logic" for
the model.
My design follows this approach - the objects draw themselves,
because, as you say, the objects *are* the data model. Where an object
"carries" additional data that isn't directly part of the drawing, it
does so by referencing a separate structure which is handled through
conventional controllers and other views as necessary.
Since all of this is handled through DrawKit (http://apptree.net/drawkitmain.htm
), you can download it and take it apart if you like - no part of the
data model of my app that is not part of the business of rendering
objects is called at drawRect: time. If I gave that impression I
didn't intend to! However the drawing methodology itself is fairly
complex, involving as it does multiple layers, different kinds of
objects within a layer, and each object having different methodologies
for editing its geometry (e.g. editing the control points of a path
versus changing its bounding rect, etc) and the visual appearance of
each object handed off to a "style" object that is built up from lists
of stroke, fill, gradient, image, text and other operations. The point
is that it's very versatile and powerful. I'm also working on making
the drawing storage scalable, which adds a further degree of
complexity, since storing tens of thousands of objects in a simple
array starts to become inefficient (to clarify - nothing is drawn that
doesn't need to be drawn according to the view's update rectangles,
but at some point you need to decide which objects are affected by an
update. Iterating a long list and testing every object is inefficient,
so I'm experimenting with BSP storage where given an update region a
much smaller subset of objects can be instantly returned).
It's also, for the most part, robust - and getting more so. Where
different objects are involved in the rendering process, I'm
introducing formal protocols in place of informal ones so that non-
conformance is easier to detect. This has already realised benefits of
eliminating a couple of places where the wrong objects ended up
getting accidentally passed to rendering code which was then throwing
exceptions - so the proper handling of try/catch is only one strategy
among several for making the drawing architecture as solid as possible.
Incidentally, if this seems like overkill, a glance at the larger
screen shot here might help: http://mapdiva.com. This shows what I'm
working on and why simple handling of stroke/fill a la Sketch just
doesn't cut it! (and even this screenshot barely scratches the surface
of what's possible). I would also mention that currently the drawing
time is still hugely dominated by Core Graphics, so it's not like my
drawing approach is causing poor performance.
IMHO objects that can draw themselves should be NSCell sub-classes or
NSView sub-classes (possibly using some primitive drawing rendering
support from NSImage, NSString, etc.) and not some model object that
has drawing code tacked onto it. These view classes should be told or
be able to ask for the data they need to use at render time, ideally
pre-prepared / pre-fetched as makes sense.
Here I'd disagree. My drawing objects are based neither on NSView nor
NSCell, they start from NSObject and gradually specialise, first with
properties that are common to all "drawables" and then subclassing for
more esoteric variations. NSView is too heavyweight, especially when
you could end up with tens of thousands of objects, and NSCell, while
lightweight, is also very much designed for use in framework tables,
matrixes and controls. Objects that are part of a drawing are not
really "control-like" so the NSCell model isn't really that good a
fit. It also means a lot of code isn't directly available to the
application developer so often behaviours have to be inferred, whereas
by writing your own "drawable" object you have complete control over
its code and behaviour.
In my case the "drawables" are just as lightweight as NSCell -
probably more so as they don't need to be compatible with existing
controls, tables or matrixes.
I don't get this at all (not just the quoted comments, but going all
the way back to Graham's original statement).
-- If a failure in the drawing code destroys the *data model*
(thereby preventing it from being saved) then there's something
terribly wrong with the drawing code's design. (Surely, being
*drawing* code it only needs read-only access to the data model. Or,
if it has drawing-related status to update in the data model, it
shouldn't be making structural alterations to the data model whose
failure could leave the model in an un-savable state.)
Well, it's not really in an unsaveable state. The data model itself
isn't damaged by exceptions thrown from the drawing code. However, the
visual appearance of the document is badly upset (scrollbars suddenly
disappear, rulers get drawn in the middle of the view, etc). The user
*thinks* something has gone terribly wrong, panics, and typically
closes the document without saving. In fact saving at that point will
work OK, but we're finding that users typically think it's become
"corrupt" and are loth to save over an earlier version of the document
because they think that what they see is what they will get - i.e.
saving the document with a corrupted view will make the problem
permanent. The end result is the same though - the user has "lost"
their data, even though in fact they didn't need to. This is why I
need to ensure that a minor glitch when drawing doesn't get blown out
of all proportion.
IMO, the issue is (at least initially) a matter of documentation.
The NSGraphicsContext class reference is ambiguous and incomplete --
it's not clear whether the current context is saved *in* each state
on the state stack, or whether the state stack is *in* the current
context, or whether the state *is* the context. There's apparently a
setGraphicsState: method which *looks* like it might be what Graham
needs to set things back to a known state, but the parameter to this
method isn't documented anywhere I could find.
The header file is no help at all. The comments refer to save/
restore *context* methods (as opposed to save/restore *state*
methods) which apparently don't exist anywhere.
If the documentation was clear, I think Graham's strategy for
dealing with exceptions would also become clear.
Yep, I guess this is getting at the heart of my problem (Thanks for
putting it so much more succinctly than I have managed so far!). I
guess I'm hoping someone familiar with NSGraphicsContext can help me
understand if saving and restoring a context explictly is legal (what
happens to all the saved states that were stacked up in the process?)
or if not, what is an appropriate alternative. In practice it seems to
work, but I need to know that I'm not storing up trouble for myself.
How about wrapping save/restore in a macro, and expanding them to use
@try/@finally? For example:
#define SAVE_GSTATE @try { [NSGraphicsContext saveGraphicsState];
#define RESTORE_GSTATE } @finally { [NSGraphicsContext
restoreGraphicsState]; }
This a great, simple idea. I will look into doing this.
Thanks to everyone who chipped in, a very useful discussion.
cheers, Graham
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden