1package sheet23import (4 "src.rest/cat/012/internal/formula"5 "src.rest/cat/012/internal/functions"6)78// Some functions (JEV.*) are answered by a hosted model. The engine never9// talks to the network: it describes each question as a RemoteCall and10// asks the workbook's RemoteSource for the answer. Until the answer arrives the cell shows11// Loading…, and the source queues the call; when answers arrive the UI12// calls RecalcAnswered, which recomputes the formulas waiting for them.13// The functions are volatile, so any other change looks again too.1415// The questions and answers are the function library's16// (functions/remote.go); the engine's API names them too.17type (18 RemoteCall = functions.RemoteCall19 RemoteAnswer = functions.RemoteAnswer20 RemoteSource = functions.RemoteSource21)2223// SetRemote sets what answers the workbook's JEV functions: nil when no24// API key is configured, and the functions then evaluate to ErrNoRemote.25// It recomputes them, so a loaded file's questions are asked.26func (w *Workbook) SetRemote(r RemoteSource) {27 w.remote = r28 w.recalc(nil)29}3031// SetRemote on a sheet is its workbook's.32func (s *Sheet) SetRemote(r RemoteSource) { s.wb.SetRemote(r) }3334var (35 // Pending is shown while an answer is on its way.36 Pending = functions.Pending37 // ErrNoRemote means JEV functions can't run: there is no API key.38 ErrNoRemote = functions.ErrNoRemote39 // ErrRemote is a question the model couldn't answer.40 ErrRemote = functions.ErrRemote41)4243// IsPending reports whether v is waiting for a remote answer.44func IsPending(v Value) bool { return functions.IsPending(v) }4546// RecalcVolatile recomputes volatile formulas and their dependents on47// every sheet, e.g. after remote answers arrive. It doesn't touch the48// undo history.49func (s *Sheet) RecalcVolatile() { s.wb.recalc(nil) }5051// RemoteCalls returns the questions the formula at a asks, with their52// current inputs, so the UI can show details or re-ask them.53func (s *Sheet) RemoteCalls(a Addr) []RemoteCall {54 c := s.cells.get(a)55 if c == nil || c.expr == nil {56 return nil57 }58 get := s.wb.values(s).lib59 var calls []RemoteCall60 var walk func(Node)61 walk = func(n Node) {62 if call, ok := n.(formula.Call); ok {63 if f := funcOf(call); f.Remote() {64 if q, err := f.Question(call.Args, get); err == nil {65 calls = append(calls, q)66 }67 }68 }69 formula.EachChild(n, walk)70 }71 walk(s.bound(a, c))72 return calls73}7475// wait notes that the formula being evaluated is waiting for the answer76// to c.77func (w *Workbook) wait(c RemoteCall) {78 if w.evaluating.s == nil {79 return80 }81 k := c.Key()82 if w.waiting == nil {83 w.waiting = map[string]map[loc]struct{}{}84 }85 if w.waiting[k] == nil {86 w.waiting[k] = map[loc]struct{}{}87 }88 w.waiting[k][w.evaluating] = struct{}{}89}9091// RecalcAnswered recomputes the formulas that were waiting for the92// answers to calls, and what depends on them, on every sheet: with93// thousands of JEV cells, an answer costs the cells that asked it, not94// all of them. It doesn't touch the undo history.95func (w *Workbook) RecalcAnswered(calls []RemoteCall) {96 var changed []loc97 for _, c := range calls {98 k := c.Key()99 for l := range w.waiting[k] {100 if l.s.live && l.s.cells.has(l.a) {101 changed = append(changed, l)102 }103 }104 delete(w.waiting, k)105 }106 if len(changed) > 0 {107 w.recalcFrom(changed, false)108 }109}