summaryrefslogtreecommitdiff
path: root/cad/src/graphics/drawing/DrawingSetCache.py
blob: a90f5446d7b861115bec69b9a73fa51869667bd3 (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
# Copyright 2009 Nanorex, Inc.  See LICENSE file for details. 
"""
DrawingSetCache.py -- cache of DrawingSets with associated drawing intents

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

History:

Bruce 090313 moved into its own file, from GLPane_drawingset_methods.py,
then factored a lot of access/modification code from its client code
remaining in that file into new methods in this class.

TODO: add usage tracking.
"""

import foundation.env as env

# soon: from foundation.changes import SelfUsageTrackingMixin, SubUsageTrackingMixin

from graphics.drawing.DrawingSet import DrawingSet

# ==

class DrawingSetCache(object,
                      # soon: SelfUsageTrackingMixin, SubUsageTrackingMixin #bruce 090313
                      ): #bruce 090227
    """
    A persistent cache of DrawingSets, one for each "drawing intent"
    specified in the input passed to self.incrementally_set_contents_to().

    (As we're presently used by GLPane_drawingset_methods, that's the
     same set of intents as are passed to glpane.draw_csdl.)

    Some attributes and methods are public.
    """
    
    # default values of instance variables
    
    saved_change_indicator = None #bruce 090309
        # public for set and compare (by '==')
    
    def __init__(self, cachename, temporary):
        self.cachename = cachename # (public, but not used externally [as of 090313])
        self.temporary = temporary # (public, not used *internally* [as of 090313])
        self._dsets = {} # maps drawing intent to DrawingSet;
            # presently private re access/modification,
            # and so are the dsets it contains
        return
    
    def destroy(self):
        for dset in self._dsets.values():
            dset.destroy()
        self._dsets = {}
        self.saved_change_indicator = None
        return

    def incrementally_set_contents_to(self,
                                      intent_to_csdls,
##                                      dset_change_indicator = None
                                      ):
        """
        Incrementally modify our DrawingSets (creating or destroying
        some of them, modifying others) so that their content matches
        that of the provided dict, intent_to_csdls, which we take ownership
        of and are allowed to arbitrarily modify.

        @param intent_to_csdls: a dict from drawing intents
            (about how a csdl's drawingset should be drawn -- we need to put
             each csdl into a dset which will be drawn in the way it intends) 
            to dicts of CSDLs (which maps csdl.csdl_id to csdl).
            We are allowed to arbitrarily modify this dict, so caller
            should not use it after passing it to us.

##        @param dset_change_indicator: if provided (and not false),
##            save this as the value of self.saved_change_indicator.
        """
        #bruce 090313 factored this method out of our client code
        
##        if dset_change_indicator:
##            self.saved_change_indicator = dset_change_indicator
##            ##### REVIEW: if not, save None?
        
        dsets = self._dsets
            # review: consider refactoring to turn code around all uses of this
            # into methods in DrawingSetCache
        
        # 1. handle existing dsets/intents in self
        #    (note: if client uses us non-incrementally, we are initially empty,
        #     so this step efficiently does nothing)
        #
        # - if any prior DrawingSets are completely unused, get rid of them
        #   (note: this defeats future optimizations based on intents which are
        #    "turned on and off", such as per-Part or per-graphicsMode
        #    intents; we'll need to rethink something if we want those here)
        #
        # - for the other prior DrawingSets, figure out csdls to remove and
        #   add; try to optimize for no change; try to do removals first,
        #   to save RAM in case cache updates during add are immediate
        
        for intent, dset in dsets.items(): # not iteritems!
            if intent not in intent_to_csdls:
                # this intent is not needed at all for this frame
                dset.destroy()
                del dsets[intent]
            else:
                # this intent is used this frame; update dset.CSDLs
                # (using a method which optimizes that into the
                #  minimal number of inlined removes and adds)
                csdls_wanted = intent_to_csdls.pop(intent)
                dset.set_CSDLs( csdls_wanted)
                del csdls_wanted # junk now, since set_CSDLs owns it
            continue

        # 2. handle new intents (if we were initially non-empty, i.e. being used
        #    incrementally) or all intents (when used non-incrementally):
        #    make new DrawingSets for whatever intents remain in intent_to_csdls
        
        for intent, csdls in intent_to_csdls.items():
            del intent_to_csdls[intent]
                # (this might save temporary ram, depending on python optims)
            dset = DrawingSet(csdls.itervalues())
            dsets[intent] = dset
                # always store them here; remove them later if non-incremental
            del intent, csdls, dset
                # notice bug of reusing these below (it happened, when this was inlined into caller)
            continue
        return

    def draw(self,
             glpane,
             intent_to_options_func,
             debug = False,
             destroy_as_drawn = False
            ):
        """
        Draw all our DrawingSets (in glpane) with appropriate options
        based on their drawing intents, as determined by
        intent_to_options_func.

        @param debug: if true, print debugging info.
        """
        
        if debug:
            print
            print env.redraw_counter ,
            print "  cache %r%s" % (self.cachename,
                                     self.temporary and " (temporary)" or "") ,
            print "  (for phase %r)" % glpane.drawing_phase
            pass
        
        for intent, dset in self._dsets.items():
            if debug:
                print "drawing dset, intent %r, %d items" % \
                      (intent, len(dset.CSDLs), )
                pass
            options = intent_to_options_func(intent)
            dset.draw(**options)
            if destroy_as_drawn:
                # don't save them any longer than needed, to save RAM
                # (without this, we'd destroy them all at once near start
                #  of next frame, using code above our call in client,
                #  or later in this frame)
                dset.destroy()
                del self._dsets[intent]
            continue
        return
    
    pass # end of class DrawingSetCache

# end