summaryrefslogtreecommitdiff
path: root/cad/src/graphics/widgets/GLPane_drawingset_methods.py
blob: 0d6f07dc1878bd8741bbe037253139957778ae45 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
# Copyright 2009 Nanorex, Inc.  See LICENSE file for details. 
"""
GLPane_drawingset_methods.py -- DrawingSet/CSDL helpers for GLPane_minimal

@author: Bruce
@version: $Id$
@copyright: 2009 Nanorex, Inc.  See LICENSE file for details.

History:

Bruce 090219 refactored this out of yesterday's additions to
Part.after_drawing_model.
"""

from utilities.debug_prefs import debug_pref
from utilities.debug_prefs import Choice_boolean_False, Choice_boolean_True

from utilities.debug import print_compact_traceback

import foundation.env as env

from graphics.drawing.DrawingSetCache import DrawingSetCache

from graphics.widgets.GLPane_csdl_collector import GLPane_csdl_collector
from graphics.widgets.GLPane_csdl_collector import fake_GLPane_csdl_collector

_DEBUG_DSETS = False

# ==

class GLPane_drawingset_methods(object):
    """
    DrawingSet/CSDL helpers for GLPane_minimal, as a mixin class
    """
    # todo, someday: split our intended mixin-target, GLPane_minimal,
    # into a base class GLPane_minimal_base which doesn't inherit us,
    # which we can inherit to explain to pylint that we *do* have a
    # drawing_phase attribute, and the rest. These need to be in
    # separate modules to avoid an import cycle. We can't inherit
    # GLPane_minimal itself -- that is not only an import cycle
    # but a superclass-cycle! [bruce 090227 comment]
    
    _csdl_collector = None # allocated on demand

    _csdl_collector_class = fake_GLPane_csdl_collector
        # Note: this attribute is modified dynamically.
        # This default value is appropriate for drawing which does not
        # occur between matched calls of _before/_after_drawing_csdls, since
        # drawing then is deprecated but needs to work,
        # so this class will work, but warn when created.
        # Its "normal" value is used between matched calls
        # of _before/_after_drawing_csdls.

    _always_remake_during_movies = False # True in some subclasses

    _remake_display_lists = True # might change at start and end of each frame

    def __get_csdl_collector(self):
        """
        get method for self.csdl_collector property:
        
        Initialize self._csdl_collector if necessary, and return it.
        """
        try:
            ## print "__get_csdl_collector", self
            if not self._csdl_collector:
                ## print "alloc in __get_csdl_collector", self
                self._csdl_collector = self._csdl_collector_class( self)
                # note: self._csdl_collector_class changes dynamically
            return self._csdl_collector
        except:
            # without this try/except, python will report any exception in here
            # (or at least any AttributeError) as if it was an AttributeError
            # on self.csdl_collector, discarding all info about the nature
            # and code location of the actual error! [bruce 090220]
            ### TODO: flush all output streams in print_compact_traceback;
            # in current code, the following prints before we finish printing
            # whatever print statement had the exception partway through, if one did
            print_compact_traceback("\nfollowing exception is *really* this one, inside __get_csdl_collector: ")
            print
            raise
        pass
    
    def __set_csdl_collector(self):
        """
        set method for self.csdl_collector property; should never be called
        """
        assert 0
    
    def __del_csdl_collector(self):
        """
        del method for self.csdl_collector property
        """
        ## print "\ndel csdl_collector", self
        self._csdl_collector = None

    csdl_collector = property(__get_csdl_collector, __set_csdl_collector, __del_csdl_collector)
        # accessed only in this class (as of 090317)

    def _has_csdl_collector(self): # not used as of 090317
        """
        @return: whether we presently have an allocated csdl_collector
                 (which would be returned by self.csdl_collector).
        @rtype: boolean
        """
        return self._csdl_collector is not None

    _current_drawingset_cache_policy = None # or a tuple of (temporary, cachename)
    
    def _before_drawing_csdls(self,
                             bare_primitives = False,
                             dset_change_indicator = None ):
        """
        [private submethod of _call_func_that_draws_model]
        
        Whenever some CSDLs are going to be drawn (or more precisely,
        collected for drawing, since they might be redrawn again later
        due to reuse_cached_drawingsets) by self.draw_csdl,
        call this first, draw them (I mean pass them to self.draw_csdl),
        and then call self._after_drawing_csdls.

        @param bare_primitives: when True, also open up a new CSDL
            and collect all "bare primitives" (not drawn into other CSDLs)
            into it, and draw it at the end. The new CSDL will be marked to
            permit reentrancy of ColorSorter.start, and will not be allowed
            to open up a "catchall display list" (an nfr for CSDL) since that
            might lead to nested display list compiles, not allowed by OpenGL.

        @param dset_change_indicator: if provided and not false,
            and if a certain debug_pref is enabled, then if _after_drawing_csdls
            would use a drawingset_cache and one already
            exists and has the same value of dset_change_indicator
            saved from our last use of that cache, we assume there
            is no need for the caller to remake the drawingsets in
            that cache, which we tell it (the caller) by returning True.
            (This is never fully correct -- for details see caller docstring.
             Soon [090313] we intend to add usage_tracking to make it much
             closer to being correct.)

        @return: usually False; True in special cases explained in the
            docstring for our dset_change_indicator parameter. WARNING:
            when we return true, our caller is REQUIRED (not just permitted)
            to immediately call _after_drawing_csdls (with certain options,
            see current calling code) without doing anything to its cached
            drawingsets/csdls -- i.e. to skip its usual "drawing".
        """
        # as of 090317, defined and used only in this class; will be refactored;
        # some other files have comments about it which will need revision then

        # someday we might take other args, e.g. an "intent map"
        del self.csdl_collector 
        self._csdl_collector_class = GLPane_csdl_collector
            # instantiated the first time self.csdl_collector is accessed
            # (which might be just below, depending on prefs and options)

        self._remake_display_lists = self._compute_remake_display_lists_now()
            # note: this affects how we use both debug_prefs, below.

        res = False # return value, modified below

        cache = None # set to actual DrawingSetCache if we find or make one
        
        cache_began_usage_tracking = False # modified if it did that
            # [not yet fully implemented or used as of 090317, but should cause no harm]
        
        if debug_pref("GLPane: use DrawingSets to draw model?",
                      Choice_boolean_True, #bruce 090225 revised
                      non_debug = True,
                      prefs_key = "v1.2/GLPane: use DrawingSets?" ):
            if self._remake_display_lists:
                self.csdl_collector.setup_for_drawingsets()
                    # sets self.csdl_collector.use_drawingsets, and more
                    # (note: this is independent of self.permit_shaders,
                    #  since DrawingSets can be used even if shaders are not)
                self._current_drawingset_cache_policy = self._choose_drawingset_cache_policy()
                if debug_pref("GLPane: reuse cached DrawingSets? (has bugs)",
                              Choice_boolean_False,
                              non_debug = True,
                              prefs_key = True
                              ):
                    if dset_change_indicator:
                        policy = self._current_drawingset_cache_policy
                            # policy is None or a tuple of (temporary, cachename) [misnamed?]
                        cache = self._find_or_make_dset_cache_to_use(policy, make = False)
                        if cache:
                            if cache.saved_change_indicator == dset_change_indicator:
                                res = True
                        # NOTE: the following debug_pref is not yet implemented,
                        # and this method plus a few others will be heavily refactored
                        # before it is, since keeping track of (planned) usage tracking
                        # is getting too messy without that.
                        # So I if 0'd it (sort of) but left it in for illustration.
                        # [bruce 090317 comment]
                        if 0 and debug_pref("GLPane: usage-track cached DrawingSets?", #bruce 090313
                                      Choice_boolean_False, #####True,
                                      non_debug = True, # for now -- remove when works
                                      prefs_key = True
                                      ):
                            if not res:
                                # if we're reusing dset_cache, i.e. not remaking it,
                                # then no need to track usage "while remaking it";
                                # this must be synchronized with _after_drawing_csdls
                                # calling end_tracking_usage -- thus the requirements
                                # (new as of 090313) about caller honoring return value res
                                # (see our docstring); for robustness (since _after can't
                                # deduce this perfectly, at least not clearly) we also
                                # set a flag about whether we're doing this in the cache.
                                cache = self._find_or_make_dset_cache_to_use(policy, make = True)
                                    # (note make = True, different than earlier call)
                                cache.begin_tracking_usage() #### args?
                                #}
                                cache_began_usage_tracking = True
                                #} # fix word order (or just refactor this mess)
                                cache.track_use() ###### WRONG, do this when we draw it! (in _after I think)
                                #}
                                pass
                            pass
                        pass
                    pass
                pass
            pass
# following will be done in a better way when we refactor [bruce 090317 comment]:
##        if cache:
##            cache.is_usage_tracking = cache_began_usage_tracking ####### how do we know it's false if it's not?
##            #}
##            pass

        if debug_pref("GLPane: highlight atoms in CSDLs?",
                      Choice_boolean_True, #bruce 090225 revised
                          # maybe: scrap the pref or make it not non_debug
                      non_debug = True,
                      prefs_key = "v1.2/GLPane: highlight atoms in CSDLs?" ):
            if bare_primitives and self._remake_display_lists:
                self.csdl_collector.setup_for_bare_primitives()

        return res

    def _compute_remake_display_lists_now(self): #bruce 090224
        """
        [can be overridden in subclasses, but isn't so far]
        """
        # as of 090317, defined and used only in this class
        remake_during_movies = debug_pref(
            "GLPane: remake display lists during movies?",
            Choice_boolean_True,
                # Historically this was hardcoded to False;
                # but I don't know whether it's still a speedup
                # to avoid remaking them (on modern graphics cards),
                # or perhaps a slowdown, so I'm making it optional.
                # Also, when active it will disable shader primitives,
                # forcing use of polygonal primitives instead;
                #### REVIEW whether it makes sense at all in that case.
                # [bruce 090224]
                # update: I'll make it default True, since that's more reliable,
                # and we might not have time to test it.
                # [bruce 090225]
            non_debug = True,
            prefs_key = "v1.2/GLPane: remake display lists during movies?" )

        # whether to actually remake is more complicated -- it depends on self
        # (thumbviews always remake) and on movie_is_playing flag (external).
        remake_during_movies = remake_during_movies or \
                               self._always_remake_during_movies
        remake_now = remake_during_movies or not self._movie_is_playing()
        if remake_now != self._remake_display_lists:
            # (kluge: knows how calling code uses value)
            # leave this in until we've tested the performance of movie playing
            # for both prefs values; it's not verbose
            print "fyi: setting _remake_display_lists = %r" % remake_now
        return remake_now

    def _movie_is_playing(self): #bruce 090224 split this out of ChunkDrawer
        """
        [can be overridden in subclasses, but isn't so far]
        """
        # as of 090317, defined and used only in this class
        return env.mainwindow().movie_is_playing #bruce 051209
            # warning: use of env.mainwindow is a KLUGE;
            # could probably be fixed, but needs review for thumbviews

    def draw_csdl(self, csdl, selected = False, highlight_color = None):
        """
        Depending on prefs, either draw csdl now (with the given draw options),
        or make sure it will be in a DrawingSet which will be drawn later
        with those options.
        """
        # as of 090317, called in many drawing methods elsewhere (and one here),
        # defined only here; calls and API won't change in planned upcoming
        # refactoring, but implem might.
        
        # future: to optimize rigid drag, options (aka "drawing intent")
        # will also include which dynamic transform (if any) to draw it inside.
        csdl_collector = self.csdl_collector
        intent = (bool(selected), highlight_color)
            # note: intent must match the "inverse code" in
            # self._draw_options(), and must be a suitable
            # dict key.
            # someday: include symbolic "dynamic transform" in intent,
            # to optimize rigid drag. (see scratch/TransformNode.py)
        if csdl_collector.use_drawingsets:
            csdl_collector.collect_csdl(csdl, intent)
        else:
            options = self._draw_options(intent)
            csdl.draw(**options)
        return

    def _draw_options(self, intent): #bruce 090226
        """
        Given a drawing intent (as created inside self.draw_csdl),
        return suitable options (as a dict) to be passed to either
        CSDL.draw or DrawingSet.draw.
        """
        # as of 090317, defined and used only in this class,
        # but logically, could be overridden in subclasses;
        # will probably not be changed during planned upcoming refactoring
        
        selected, highlight_color = intent # must match the code in draw_csdl
        if highlight_color is None:
            return dict(selected = selected)
        else:
            return dict(selected = selected,
                        highlighted = True,
                        highlight_color = highlight_color )
        pass
    
    def _after_drawing_csdls(self,
                            error = False,
                            reuse_cached_dsets_unchanged = False,
                            dset_change_indicator = None ):
        """
        [private submethod of _call_func_that_draws_model]

        @see: _before_drawing_csdls

        @param error: if the caller knows, it can pass an error flag
                      to indicate whether drawing succeeded or failed.
                      If it's known to have failed, we might not do some
                      things we normally do. Default value is False
                      since most calls don't pass anything. (#REVIEW: good? true?)

        @param reuse_cached_dsets_unchanged: whether to do what it says.
            Typically equal to the return value of the preceding call
            of _before_drawing_csdls.

        @param dset_change_indicator: if true, store in the dset cache
            (whether found or made, modified or not) as .saved_change_indicator.
        """
        # as of 090317, defined and used only in this class; will be refactored
        
        self._remake_display_lists = self._compute_remake_display_lists_now()
        
        if not error:
            if self.csdl_collector.bare_primitives:
                # this must come before the _draw_drawingsets below
                csdl = self.csdl_collector.finish_bare_primitives()
                self.draw_csdl(csdl)
                pass
            if self.csdl_collector.use_drawingsets:
                self._draw_drawingsets(
                    reuse_cached_dsets_unchanged = reuse_cached_dsets_unchanged,
                    dset_change_indicator = dset_change_indicator
                 )
                # note, the DrawingSets themselves last between draw calls,
                # and are stored elsewhere in self. self.csdl_collector has
                # attributes used to collect necessary info during a
                # draw call for updating the DrawingSets before drawing them.
                pass
            pass
        del self.csdl_collector
        del self._csdl_collector_class # expose class default value
        return

    def _whole_model_drawingset_change_indicator(self):
        """
        [should be overridden in subclasses which want to cache drawingsets]
        """
        # as of 090317, overridden in one subclass, called only in this class
        # (in _call_func_that_draws_model); might remain unchanged after
        # planned upcoming refactoring
        
        return None # disable this optim by default
    
    def _call_func_that_draws_model(self,
                                    func,
                                    prefunc = None,
                                    postfunc = None,
                                    bare_primitives = None,
                                    drawing_phase = None,
                                    whole_model = True
                                   ):
        """
        If whole_model is False (*not* the default):
        
        Call func() between calls of self._before_drawing_csdls(**kws)
        and self._after_drawing_csdls(). Return whatever func() returns
        (or raise whatever exception it raises, but call
         _after_drawing_csdls even when raising an exception).

        func should usually be something which calls before/after_drawing_model
        inside it, e.g. one of the standard functions for drawing the
        entire model (e.g. part.draw or graphicsMode.Draw),
        or self._call_func_that_draws_objects which draws a portion of
        the model between those methods.

        If whole_model is True (default):

        Sometimes act as above, but other times do the same drawing
        that func would have done, without actually calling it,
        by reusing cached DrawingSets whenever nothing changed
        to make them out of date.

        Either way, always call prefunc (if provided) before, and
        postfunc (if provided) after, calling or not calling func.
        Those calls participate in the effect of our exception-protection
        and drawing_phase behaviors, but they stay out of the whole_model-
        related optimizations (so they are called even if func is not).
        
        @warning: prefunc and postfunc are not called between
        _before_drawing_csdls and _after_drawing_csdls, so they
        should not use csdls for drawing. (If we need to revise this,
        we might want to pass a list of funcs rather than a single func,
        with each one optimized separately for sometimes not being
        called; or more likely, refactor this entirely to make each
        func and its redraw-vs-reuse conditions a GraphicsRule object.)

        @param bare_primitives: passed to _before_drawing_csdls.

        @param drawing_phase: if provided, drawing_phase must be '?'
            on entry; we'll set it as specified only during this call.
            If not provided, we don't check it or change it
            (typically, in that case, caller ought to do something
            to set it properly itself). The value of drawing_phase
            matters and needs to correspond to what's drawn by func,
            because it determines the "drawingset_cache" (see that
            term in the code for details).

        @param whole_model: whether func draws the whole model.
            Default True. Permits optimizations when the model appearance
            doesn't change since it was last drawn.
        """
        # as of 090317, defined only here, called here and elsewhere
        # (in many places); likely to remain unchanged in our API for other
        # methods and client objects, even after planned upcoming refactoring,
        # though we may replace *some* of its calls with something modified,
        # as if inlining the refactored form.

        # todo: convert some older callers to pass drawing_phase
        # rather than implementing their own similar behavior.
        if drawing_phase is not None:
            assert self.drawing_phase == '?'
            self.set_drawing_phase(drawing_phase)
        if prefunc:
            try:
                prefunc()
            except:
                msg = "bug: exception in %r calling prefunc %r: skipping it" % (self, prefunc)
                print_compact_traceback(msg + ": ")
                pass
            pass
        if whole_model:
            dset_change_indicator = self._whole_model_drawingset_change_indicator()
            # note: if this is not false, then if a certain debug_pref is enabled,
            # then if we'd use a drawingset_cache and one already
            # exists and has the same value of dset_change_indicator
            # saved from our last use of that cache, we assume there
            # is no need to remake the drawingsets and therefore
            # no need to call func at all; instead we just redraw
            # the saved drawingsets. This is never correct -- in practice
            # some faster but nonnull alternative to func would need
            # to be called -- but is useful for testing the maximum
            # speedup possible from an "incremental remake of drawing"
            # optimization, and is a prototype for correct versions
            # of similar optimizations. [bruce 090309]
        else:
            dset_change_indicator = None
        skip_dset_remake = self._before_drawing_csdls(
                        bare_primitives = bare_primitives,
                        dset_change_indicator = dset_change_indicator
         )
        # WARNING: if skip_dset_remake, we are REQUIRED (as of 090313)
        # (not just permitted, as before)
        # [or at least we would have been if I had finished implementing
        #  usage tracking -- see comment elsewhere for status -- bruce 090317]
        # to do nothing to any of our cached dsets or csdl collectors
        # (i.e. to do no drawing) before calling _after_drawing_csdls.
        # Fortunately we call it just below, so it's easy to verify
        # this requirement -- just don't forget to think about this
        # if you modify the following code. (### Todo: refactor this to
        # make it more failsafe, e.g. pass func to a single method
        # which encapsulates _before_drawing_csdls and _after_drawing_csdls...
        # but wait, isn't *this* method that single method? Ok, just make
        # them private [done] and document them as only for use by this
        # method [done]. Or maybe a smaller part of this really *should* be
        # a single method.... [yes, or worse -- 090317])
        error = True
        res = None
        try:
            if not skip_dset_remake:
                res = func()
            error = False
        finally:
            # (note: this is usually what does much of the actual drawing
            #  requested by func())
            self._after_drawing_csdls(
                error,
                reuse_cached_dsets_unchanged = skip_dset_remake,
                dset_change_indicator = dset_change_indicator
             )
            if postfunc:
                try:
                    postfunc()
                except:
                    msg = "bug: exception in %r calling postfunc %r: skipping it" % (self, postfunc)
                    print_compact_traceback(msg + ": ")
                    pass
                pass
            if drawing_phase is not None:
                self.set_drawing_phase('?')
        return res

    def set_drawing_phase(self, drawing_phase):
        """
        [overridden in subclasses of the class we mix into;
         see those for doc]
        """
        # as of 090317, called in many places, overridden in some;
        # this will remain true after planned upcoming refactoring
        return

    def _call_func_that_draws_objects(self, func, part, bare_primitives = None):
        """
        Like _call_func_that_draws_model,
        but also wraps func with the part methods
        before/after_drawing_model, necessary when drawing
        some portion of a Part (or when drawing all of it,
        but typical methods to draw all of it already do this
        themselves, e.g. part.draw or graphicsMode.Draw).

        This method's name is meant to indicate that you can pass us a func
        which draws one or more model objects, or even all of them,
        as long as it doesn't already bracket its individual object .draw calls
        with the Part methods before/after_drawing_model,
        like the standard functions for drawing the entire model do.
        """
        # as of 090317, defined only here, called only elsewhere;
        # likely to remain unchanged in our API for other methods,
        # even after planned upcoming refactoring
        def func2():
            part.before_drawing_model()
            error = True
            try:
                func()
                error = False
            finally:
                part.after_drawing_model(error)
            return
        self._call_func_that_draws_model( func2,
                                          bare_primitives = bare_primitives,
                                          whole_model = False )
        return

    _dset_caches = None # or map from cachename to persistent DrawingSetCache
        ### review: make _dset_caches per-Part? Probably not -- might be a big
        # user of memory or VRAM, and the CSDLs persist so it might not matter
        # too much. OTOH, Part-switching will be slower without doing this.
        # Consider doing it only for the last two Parts, or so.
        # Be sure to delete it for destroyed Parts.
        #
        # todo: delete this when deleting an assy, or next using a new one
        # (not very important -- could reduce VRAM in some cases,
        #  but won't matter after loading a new similar file)
    
    def _draw_drawingsets(self,
                          reuse_cached_dsets_unchanged = False,
                          dset_change_indicator = None ):
        """
        Using info collected in self.csdl_collector,
        update our cached DrawingSets for this frame
        (unless reuse_cached_dsets_unchanged is true),
        then draw them.
        """
        ### REVIEW: move inside csdl_collector?
        # or into some new cooperating object, which keeps the DrawingSets?

        # as of 090317, defined and used only in this class (in _after_drawing_csdls)

        incremental = debug_pref("GLPane: use incremental DrawingSets?",
                          Choice_boolean_True, #bruce 090226, since it works
                          non_debug = True,
                          prefs_key = "v1.2/GLPane: incremental DrawingSets?" )

        csdl_collector = self.csdl_collector
        intent_to_csdls = \
            csdl_collector.grab_intent_to_csdls_dict()
            # grab (and reset) the current value of the
            # dict from intent (about how a csdl's drawingset should be drawn)
            # to a set of csdls (as a dict from their csdl_id's),
            # which includes exactly the CSDLs which should be drawn in this
            # frame (whether or not they were drawn in the last frame).
            # We own this dict, and can destructively modify it as convenient
            # (though ownership is needed only in our incremental case below).

        if reuse_cached_dsets_unchanged:
            # note: we assume reuse_cached_dsets_unchanged is never true
            # unless caller verified that the cached dsets in question
            # actually exist, so we don't need to check this
            assert incremental # simple sanity check of caller behavior
            intent_to_csdls.clear() # [no longer needed as of 090313]

        # set dset_cache to the DrawingSetCache we should use;
        # if it's temporary, make sure not to store it in any dict
        if incremental:
            # figure out (temporary, cachename)
            cache_before = self._current_drawingset_cache_policy # chosen during _before_drawing_csdls
            cache_after = self._choose_drawingset_cache_policy() # chosen now
            ## assert cache_after == cache_before
            if not (cache_after == cache_before):
                print "bug: _choose_drawingset_cache_policy differs: before %r vs after %r" % \
                      (cache_before, cache_after)
            dset_cache = self._find_or_make_dset_cache_to_use( cache_before)
        else:
            # not incremental:
            # destroy all old caches (matters after runtime prefs change)
            if self._dset_caches:
                for cachename, cache in self._dset_caches.items():
                    cache.destroy()
                self._dset_caches = None
                pass
            # make a new DrawingSetCache to use
            temporary, cachename = True, None
            dset_cache = DrawingSetCache(cachename, temporary)
            del temporary, cachename
            pass


        ##### REVIEW: do this inside some method of dset_cache?
        # This might relate to its upcoming usage_tracking features. [090313]
        if dset_change_indicator:
            dset_cache.saved_change_indicator = dset_change_indicator
            ##### REVIEW: if not, save None?

        if not reuse_cached_dsets_unchanged:
            # (note: if not incremental, then dset_cache is empty, so the
            #  following method just fills it non-incrementally in that case,
            #  so we don't need to pass an incremental flag to the method)
            dset_cache.incrementally_set_contents_to(
                intent_to_csdls,
##                dset_change_indicator = dset_change_indicator
             )

        del intent_to_csdls

        # draw all current DrawingSets
        dset_cache.draw( self,
                         self._draw_options,
                         debug = _DEBUG_DSETS,
                         destroy_as_drawn = dset_cache.temporary or not incremental
                             # review (not urgent): can this value be simplified
                             # due to relations between
                             # dset_cache.temporary and incremental?
                       )
        
        if dset_cache.temporary:
            #### REVIEW: do this in dset_cache.draw when destroy_as_drawn is true,
            # or when some other option is passed?
        
            # note: dset_cache is guaranteed not to be in any dict we have
            dset_cache.destroy()
        
        return

    def _find_or_make_dset_cache_to_use(self, policy, make = True):
        """
        Find, or make (if option permits), the DrawingSetCache to use,
        based on policy, which can be None if not make,
        or can be (temporary, cachename).

        @return: existing or new DrawingSetCache, or None if not make and
            no existing one is found.
        """
        # as of 090317, called only in this class, in _before_drawing_csdls and
        # (during _after_drawing_csdls) in _draw_drawingsets;
        # defined only in this class
        if not make and not policy:
            return None
        temporary, cachename = policy
        if not make:
            return not temporary and \
                   self._dset_caches and \
                   self._dset_caches.get(cachename)
        else:
            if temporary:
                dset_cache = DrawingSetCache(cachename, temporary)
            else:
                if self._dset_caches is None:
                    self._dset_caches = {}
                dset_cache = self._dset_caches.get(cachename)
                if not dset_cache:
                    dset_cache = DrawingSetCache(cachename, temporary)
                    self._dset_caches[cachename] = dset_cache
                    pass
                pass
            return dset_cache
        pass

    def _choose_drawingset_cache_policy(self): #bruce 090227
        """
        Based on self.drawing_phase, decide which cache to keep DrawingSets in
        and whether it should be temporary.

        @return: (temporary, cachename)
        @rtype: (bool, string)

        [overridden in subclasses of the class we mix into;
         for calling, private to this mixin class]
        """
        # as of 090317, called only in this class, in _before_drawing_csdls and
        # (during _after_drawing_csdls) in _draw_drawingsets;
        # defined here and overridden in one other class
        return False, None

    pass # end of mixin class GLPane_drawingset_methods

# end