-
-
Notifications
You must be signed in to change notification settings - Fork 302
/
ctx.go
219 lines (184 loc) · 4.62 KB
/
ctx.go
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
package mg
import (
"context"
"github.com/ugorji/go/codec"
"margo.sh/mgpf"
"margo.sh/vfs"
"reflect"
"regexp"
"sync"
"time"
)
var (
// StatusPrefix is the prefix used for all status elements.
StatusPrefix = "‣ "
_ context.Context = (*Ctx)(nil)
)
// Ctx holds data about the current request/reduction.
//
// To create a new instance, use Store.NewCtx()
//
// NOTE: Ctx should be treated as readonly and users should not assign to any
// of its fields or the fields of any of its members.
// If a field must be updated, you should use one of the methods like Copy
//
// Unless a field is tagged with `mg.Nillable:"true"`, it will never be nil
// and if updated, no field should be set to nil
type Ctx struct {
// State is the current state of the world
*State
// Action is the action that was dispatched.
// It's a hint telling reducers about some action that happened,
// e.g. that the view is about to be saved or that it was changed.
Action Action `mg.Nillable:"true"`
// KVMap is an in-memory cache of data with for the lifetime of the Ctx.
*KVMap
// Store is the global store
Store *Store
// Log is the global logger
Log *Logger
Cookie string
Profile *mgpf.Profile
VFS *vfs.FS
doneC chan struct{}
cancelOnce *sync.Once
handle codec.Handle
defr *redFns
}
// newCtx creates a new Ctx
// if st is nil, the state will be set to the equivalent of Store.state.new()
// if p is nil a new Profile will be created with cookie as its name
func newCtx(sto *Store, st *State, act Action, cookie string, p *mgpf.Profile) *Ctx {
if st == nil {
st = sto.state.new()
}
if st.Config == nil {
st = st.SetConfig(sto.cfg)
}
if p == nil {
p = mgpf.NewProfile(cookie)
}
return &Ctx{
State: st,
Action: act,
KVMap: &KVMap{},
Store: sto,
Log: sto.ag.Log,
Cookie: cookie,
Profile: p,
VFS: vFS,
doneC: make(chan struct{}),
cancelOnce: &sync.Once{},
handle: sto.ag.handle,
defr: &redFns{},
}
}
// Deadline implements context.Context.Deadline
func (*Ctx) Deadline() (time.Time, bool) {
return time.Time{}, false
}
// Cancel cancels the ctx by arranging for the Ctx.Done() channel to be closed.
// Canceling this Ctx cancels all other Ctxs Copy()ed from it.
func (mx *Ctx) Cancel() {
mx.cancelOnce.Do(func() {
close(mx.doneC)
})
}
// Done implements context.Context.Done()
func (mx *Ctx) Done() <-chan struct{} {
return mx.doneC
}
// Err implements context.Context.Err()
func (mx *Ctx) Err() error {
select {
case <-mx.Done():
return context.Canceled
default:
return nil
}
}
// Value implements context.Context.Value() but always returns nil
func (mx *Ctx) Value(k interface{}) interface{} {
return nil
}
// AgentName returns the name of the agent if set
// if set, it's usually the agent name as used in the command `margo.sh [run...] $agent`
func (mx *Ctx) AgentName() string {
return mx.Store.ag.Name
}
// ActionIs returns true if Ctx.Action is the same type as any of those in actions
// for convenience, it returns true if actions is nil
func (mx *Ctx) ActionIs(actions ...Action) bool {
if actions == nil {
return true
}
typ := reflect.TypeOf(mx.Action)
for _, act := range actions {
if reflect.TypeOf(act) == typ {
return true
}
}
return false
}
// LangIs is equivalent to View.LangIs(langs...)
func (mx *Ctx) LangIs(langs ...Lang) bool {
return mx.View.LangIs(langs...)
}
// CommonPatterns is equivalent to View.CommonPatterns()
func (mx *Ctx) CommonPatterns() []*regexp.Regexp {
return mx.View.CommonPatterns()
}
// Copy create a shallow copy of the Ctx.
//
// It applies the functions in updaters to the new object.
// Updating the new Ctx via these functions is preferred to assigning to the new object
func (mx *Ctx) Copy(updaters ...func(*Ctx)) *Ctx {
x := *mx
mx = &x
for _, f := range updaters {
f(mx)
}
return mx
}
func (mx *Ctx) SetState(st *State) *Ctx {
if mx.State == st {
return mx
}
mx = mx.Copy()
mx.State = st
return mx
}
func (mx *Ctx) SetView(v *View) *Ctx {
return mx.SetState(mx.State.SetView(v))
}
// Begin is a short-hand for Ctx.Store.Begin
func (mx *Ctx) Begin(t Task) *TaskTicket {
return mx.Store.Begin(t)
}
func (mx *Ctx) Defer(f ReduceFn) {
mx.defr.prepend(f)
}
type redFns struct {
sync.RWMutex
l []ReduceFn
}
func (r *redFns) prepend(f ReduceFn) {
r.Lock()
defer r.Unlock()
r.l = append([]ReduceFn{f}, r.l...)
}
func (r *redFns) append(f ReduceFn) {
r.Lock()
defer r.Unlock()
r.l = append(r.l[:len(r.l):len(r.l)], f)
}
func (r *redFns) reduction(mx *Ctx) *Ctx {
r.RLock()
l := r.l
r.l = nil
r.RUnlock()
for _, reduce := range l {
mx = mx.SetState(reduce(mx))
}
return mx
}