-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontext.go
More file actions
183 lines (158 loc) · 5 KB
/
context.go
File metadata and controls
183 lines (158 loc) · 5 KB
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
package rx
import (
"reflect"
"sync"
)
// Context carries a set of values down the rendering tree.
// This is used by UI elements to pass values between rendering passes.
// A build context can safely be shared between goroutines, and so can the children.
// The zero build context is valid, but only marginally useful, as it cannot be used to link nodes to widgets.
// Do not confuse it with the standard library’s [context.Context], which does allow to pass values, but also a lot more.
//
// For a good introduction and uses, the Dart [InheritedWidget] class is a good start.
//
// [InheritedWidget]: https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html
type Context struct {
ng *Engine
vx *vctx
}
type keyedEntity struct {
next *keyedEntity
key reflect.Type
val Entity
}
// Keep stores an entity of type T in the context.
// The entity will be available during the next cycle by calling the [Reuse] function.
// This is only required for elements where identity matters (e.g. drag / drop / transition).
//
// Most of the elements should not use Keep.
func Keep[T any](ctx Context, nd *Node) {
typ := reflect.TypeFor[T]()
p := &ctx.ng.k0
for (*p) != nil && (*p).key != typ {
p = &(*p).next
}
if nd.Entity == 0 {
nd.GiveKey(ctx)
}
*p = &keyedEntity{
key: typ,
val: nd.Entity,
}
}
// Reuse returns a node of type kept during the previous rendering cycle.
// If no node is kept at T (or was kept more than one rendering cycle ago), nil is returned.
func Reuse[T any](ctx Context) *Node {
typ := reflect.TypeFor[T]()
for p := ctx.ng.k1; p != nil; p = p.next {
if p.key == typ {
nd := ReuseFrom(ctx, p.val)
Keep[T](ctx, nd)
return nd
}
}
return nil
}
// [NoAction] is a marker context.
// The rendering engine is free to skip a rendering frame if [NoAction] is returned in reaction to an event.
var NoAction Context
// DoNothing returns the [NoAction] marker context
func DoNothing(ctx Context) Context { return NoAction }
// vctx is a lock-protected map.
type vctx struct {
ml sync.Mutex
kv map[reflect.Type]any
}
// WithValue adds a new value in the context, which should be passed down the building stack.
// Existing values of the same key are hidden, but not overwritten.
//
// # Concurrency note
//
// The happens-after relationship could look a bit counter-intuitive; without further synchronization, two goroutines G1 and G2 would be able to write their value, but read the value from the other goroutine.
// We believe this is an acceptable tradeoff as this is not a common case, and adding synchronization (e.g. through channels) is both trivial, and clearer anyway.
// We do ensure that the data structure remains valid from concurrent access.
func WithValue[T any](ctx Context, value T) Context { return WithValues(ctx, value) }
// WithValues set all values in context.
// If a value is an action, it is executed in place (use a dedicated go routine to delay execution).
func WithValues(ctx Context, values ...any) Context {
if ctx.vx == nil {
ctx.vx = &vctx{kv: make(map[reflect.Type]any)}
}
ctx.vx.ml.Lock()
for _, v := range values {
if act, ok := v.(Action); ok {
ctx.vx.ml.Unlock()
ctx = act(ctx)
ctx.vx.ml.Lock()
} else {
ctx.vx.kv[reflect.TypeOf(v)] = v
}
}
ctx.vx.ml.Unlock()
return ctx
}
// ValueOf returns a value of type T at key.
// If the type of T is invalid, the function panics.
func ValueOf[T any](ctx Context) T {
var z T
vx := ctx.vx
if vx == nil {
return z
}
val, ok := vx.kv[reflect.TypeFor[T]()]
if !ok {
return z
}
return val.(T)
}
// Mutate executes all mutators (which must be functions taking exactly one pointer)
// by loading the value from the context, modifying it with the mutator and storign it.
// If the type is not yet registered in the context, the zero value is used instead
// It panics if the mutators are of the wrong type
func Mutate(mutators ...any) Action {
return func(ctx Context) Context {
for _, m := range mutators {
tt := reflect.TypeOf(m)
if tt.Kind() != reflect.Func || tt.NumIn() != 1 || tt.In(0).Kind() != reflect.Pointer || tt.NumOut() != 0 {
panic("mutator must be functions of one pointer argument")
}
kt := tt.In(0).Elem()
ctx.vx.ml.Lock()
v := reflect.New(kt)
if vv, ok := ctx.vx.kv[kt]; ok {
v.Elem().Set(reflect.ValueOf(vv))
}
reflect.ValueOf(m).Call([]reflect.Value{v})
ctx.vx.kv[kt] = v.Elem().Interface()
ctx.vx.ml.Unlock()
}
return ctx
}
}
// LoadContext loads all values in context (cf [New])
func LoadContext(values ...any) Action {
return func(ctx Context) Context {
return WithValues(ctx, values...)
}
}
// Chain execute all actions in order
func Chain(actions ...Action) Action {
return func(ctx Context) Context {
for _, a := range actions {
ctx = a(ctx)
}
return ctx
}
}
// Toggle sets the value in context to W if it is missing, or zero otherwise
func Toggle[T comparable](w T) Action {
return func(ctx Context) Context {
v := ValueOf[T](ctx)
if v == w {
var z T
return WithValue(ctx, z)
} else {
return WithValue(ctx, w)
}
}
}