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
|
# Copyright 2006-2007 Nanorex, Inc. See LICENSE file for details.
"""
NewInval.py [to be cannibalized -- as of 061231, obs or mostly obs]
$Id$
061231 moved the displist comments and code from here to DisplayListChunk.py;
what remains here is obs or mostly obs
"""
# ==
# Tue 060919
# env + expr -> understood expr (in that env), an obj with attrs (oresmic?)
# expr + env/place -> placed expr;
# done by a recompute rule, so it might get invalidated, and it's an attrval which is itself usage tracked
# env is divided up - for us, expr interp rules, state (model, transient, optim, displists), glpane & its state and coords
# and we might yet have to declare or divide it based on what changes rarely, per drawable instance, or per frame (affects inval code)
# write these funcs so that their api does not preclude them from memoizing, or force them to
# they can be env methods, since env determines how they all work and holds the memos
# subenv for every expr level as i interpret, if necessary, or is it just a sub-place?
# but it might only exist during interp -- it's parallel to, but perhaps more transient than, yet more general than,
# transient-state container-hierarchy
# do that code walkthough for a simple example - simple MT with displists (on paper)
# then sort that code into methods on objects, then classes, then write it down in this style.
# overall - recompute rule in drawing env, from expr to its implem (where to send draw events)
# uses understood expr, which tells flags about whether it uses transient state, class or constructorfunc for creating drawable...
# expr object - return the head, and arglist incl options or that there is none, and lexical class from parser if any
# expr in env - understand it, incl how to make it, then make it (in place) (I guess place != env, env is that part of place
# which affects understanding of expr, maybe not including the part that has changing vars at runtime, or even changing opt vals)
# (but the understanding might say "opt X has type T" and then that might be what decided the val was in the other place,
# for env opt vals? not sure.)
# ==
class _Lval: ####@@@@ compare to later version in scratch5.py
"I own a resettable formula (like a recompute callable, but usage-tracks), and maintain a lazy invalidatable .value."
_invalid = True ### ok? what about propogation?
_memoized_value = None
_version_counter = 1 ###k ??
# ... [usual stuff removed]
def get_value(self, args_each_time = None): # args_each_time can only be passed if you call this method explicitly
"called whether or not we have the value"
if self._invalid:
self._recompute(args_each_time)
return self._memoized_value
def _recompute(self, args_each_time):
"only called when we're invalid"
new = self._compute_a_new_value(args_each_time)
if new != self._memoized_value: # != ok? not for all numeric arrays? or is it? I forget. state_utils should say.
self._version_counter += 1 ###k ??
self._memoized_value = new
self._invalid = False
return
def _compute_a_new_value(self, args_each_time):
"return a newly computed value" ### what about usage tracking??
return self._func(args_each_time)
###k actually call a formula, and let it usage-track for us, and we'll subscribe our inval to what we used.
### but does caller or this method do that? caller, since it might do funny things like choose memo place based on usage.
pass
class KidLval(_Lval):
# this might have its own way to compute, ie a built-in python-coded formula-func, tho it might let superclass do usage tracking.
# does that mean its own kind of formula, or an implicit builtin formula (since formulas do usage tracking)?
def _raw_recompute(self, args_each_time): #####@@@@@ call me
parent, index = args_each_time
# this is where we put the code to ask the parent how to make that kid, and to make it,
# but also, perhaps, to compare the inputs to last time and decide whether we really need to remake it (not sure).
codeformula = parent._get_kid_codeformula(index) ####IMPLEM - this can change at runtime for the same kid, i think -- always remake then
lexenv = parent._get_kid_lexenv(index) ####IMPLEM -- lexenv is an immutable and unchanging object, it's what we want here
###E compare codeformula to old value -- stored where??
## worse -- is it really a formula? how do we monitor changes? when do they mean we need a new kid? usually it's just 'code expr'...
# lets assume that's what it is, for now.
if 'need to recompute':
'make a kid from lexenv, with index & code'
code = codeformula
kid = make_an_instance(code, lexenv, place)
####@@@@ also place? or, do we not even store that, but use it only at runtime? that might be a de-optim, but gives good flexibility --
# lets instance run in multiple copies -- OTOH only if entire index chain is the same, so maybe it's a silly idea.
#### wait, put in the index, relative to the parent... is it in the lexenv? I think so. no, only in spirit, as optim we separate it.
return kid
return None ##k??
pass
def make_an_instance(code, lexenv, place):
"somebody decided a new instance needs to be made, in a certain place (incl index_chain, storage)... make it."
nim(pseudocode)
return code(lexenv, place) # pseudocode ###@@@
# why not just pass the place-related make args, and decide locally what they contain (index, stores, etc), so we can change that later?
# and also the place-related each-frame args?? or do we have none of those? the old reasoning says none: they're often stable,
# so when they change we want to notice explicitly, so we optim for when they're stable.
# if we tried to let one instance live in two places, the optim data would all differ so we'd defeat the purpose.
# ... so the place-related args are all in make, and we might as well just call them the place!
class HelperClass:
def __init__(self):
self._kid_codeformulas = {} # convenience dict, of exceptions to whatever general rule we might have for these (??)
self._kid_lexenvfuncs = {} # ditto, but missing or None ones are ok and mean "identity" (maybe)
#e maybe one for coords? only in scenegraph model, not needed if we have _move
self._kid_lvals = {} # this might define which kids we actually have (not sure, if set of kids could shrink & change back in one step)
def _define_kid(self, index, codeformula = None, lexenvfunc = None): ### in general, is it best to pass these, or have a func to compute them?
####@@@@ decide by considering fancier-indexed iterators like Cube or Table.
### future note/digr:
### with fancy indexed sets with incremental inval, we'd need to receive inval signals for their pieces (values at indices).
### I imagine we could receive inval signals, new values, or mod-functions, or mod-func plus result
### (all of which are various portions of an "arrow" from old to new value of the piece in question).
"[private] incremental make (or ensure made) of a kid"
# pseudocode:
new = not self._kid_lvals.has_key(index) # but even if not new, we might need to remake it... does that depend on code being new?
# if so, is it automatic, from a diffing lval for the code, that will inval the kid-instance? #####@@@@@
if new:
self._kid_lvals[index] = KidLval(self, index) #### i don't like passing self (re cycles) -- maybe it won't need to store it??
# what it might need it for is recomputing kid properties... maybe we can pass it self each time it needs that??
# when it needs it is when we ask for the kid-value. hmm. Ditto for index I suppose?
if codeformula is not None: # otherwise we're not preventing a subclass method from computing this (??)
self._kid_codeformulas[index] = codeformula
if lexenvfunc is not None: #### hmm, identity None is ambiguous with don't-change-it None, WRONG but tolerable for now ####@@@@
self._kid_lexenvfuncs[index] = lexenvfunc
#e something to be sure it's really made, or will be on demand; the kid needs to know its index
#e something to see if we redefined those inputs? no, just guess we probably did if we called this
self._kid_lvals[index].invalidate()
#e how do we undefine a kid?
return
def _get_kid(self, index):
###e process saved-up kid index creations, if we ever buffer index creations from fancy indexed-set inputs
lval = self._kid_lvals[index] # error if this does not exist, since keys define legal kids
kid = lval.get_value( (self, index) ) # get the value -- the formula is assume to need this tuple of args, each time it runs,
# and since we pass them, we're responsible for tracking invals in them and invalidating the kid
# (as opposed to usage tracking occuring) ####@@@@ how does usage tracking fit in?
return kid
def _get_kid_lexenv(self, index):
###E memoize this -- really we want a recompute rule, i think... can it ever change? I don't think so...
#e take parent lexenv, with localvars added, and use index to modify the various staterefs...
# but how exactly does index interact, with different staterefs?
# ... work that out later, ie write simple code now, quick to write & later read/revise.
# uses of index: find transient state,
# perhaps in various layers of differing permanence (ie same index-chain in different objects),
# with varying sharing (ie different index-chains could point to same place, in some layer-objs but not others),
# and also use it to get back to current coords: (index, parent) -> get to parent coords, then tell parent to get to mine via index.
# something like: dict(_index = index, _inherit = parent-env) # where parent index chain is visible in _inherit, I guess... not sure & kluge.
#### BTW it's so common for the index to be the only changing thing in the lexenv, that we might pass it in parallel as an optim/clarification.
# then we ought to build up the chain explicitly as (local-index, parent-chain), and leave room for interning it as a key int, i suppose.
# digr: do we intern it separately in separate layers? we might need to if they are persistent! (or intern it only in the most persistent one?)
return self._lexenv # stub, and besides, are we supposed to store it? i suppose we might as well... could always zap if we finalize self...
pass
# ==
|