Skip to content

Commit e290924

Browse files
committed
synchronize atoms, implement GET /api/data
1 parent 4b9154e commit e290924

6 files changed

Lines changed: 111 additions & 21 deletions

File tree

tsunami/app/serverhandlers.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func NewHTTPHandlers(client *Client) *HTTPHandlers {
4040
func (h *HTTPHandlers) RegisterHandlers(mux *http.ServeMux, assetsFS *embed.FS, staticFS *embed.FS) {
4141
mux.HandleFunc("/api/render", h.handleRender)
4242
mux.HandleFunc("/api/updates", h.handleSSE)
43+
mux.HandleFunc("/api/data", h.handleData)
4344
mux.HandleFunc("/files/", h.handleAssetsUrl)
4445

4546
// Add handler for static files at /static/ path
@@ -155,6 +156,28 @@ func (h *HTTPHandlers) processFrontendUpdate(feUpdate *rpctypes.VDomFrontendUpda
155156
return update, nil
156157
}
157158

159+
func (h *HTTPHandlers) handleData(w http.ResponseWriter, r *http.Request) {
160+
defer func() {
161+
panicErr := util.PanicHandler("handleData", recover())
162+
if panicErr != nil {
163+
http.Error(w, fmt.Sprintf("internal server error: %v", panicErr), http.StatusInternalServerError)
164+
}
165+
}()
166+
167+
if r.Method != http.MethodGet {
168+
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
169+
return
170+
}
171+
172+
result := h.Client.Root.GetDataMap()
173+
174+
w.Header().Set("Content-Type", "application/json")
175+
if err := json.NewEncoder(w).Encode(result); err != nil {
176+
log.Printf("failed to encode data response: %v", err)
177+
http.Error(w, "failed to encode response", http.StatusInternalServerError)
178+
}
179+
}
180+
158181
func (h *HTTPHandlers) handleAssetsUrl(w http.ResponseWriter, r *http.Request) {
159182
defer func() {
160183
panicErr := util.PanicHandler("handleAssetsUrl", recover())

tsunami/comp/root_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type TestContext struct {
2222
}
2323

2424
func Page(ctx context.Context, props map[string]any) any {
25-
clicked, setClicked := vdom.UseState(ctx, false)
25+
clicked, setClicked, _ := vdom.UseState(ctx, false)
2626
var clickedDiv *vdom.VDomElem
2727
if clicked {
2828
clickedDiv = vdom.Bind(`<div>clicked</div>`, nil)
@@ -45,7 +45,7 @@ func Page(ctx context.Context, props map[string]any) any {
4545

4646
func Button(ctx context.Context, props map[string]any) any {
4747
ref := vdom.UseVDomRef(ctx)
48-
clName, setClName := vdom.UseState(ctx, "button")
48+
clName, setClName, _ := vdom.UseState(ctx, "button")
4949
vdom.UseEffect(ctx, func() func() {
5050
fmt.Printf("Button useEffect\n")
5151
setClName("button mounted")

tsunami/comp/rootelem.go

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"reflect"
1111
"strconv"
1212
"strings"
13+
"sync"
1314

1415
"github.com/google/uuid"
1516
"github.com/wavetermdev/waveterm/tsunami/rpctypes"
@@ -30,6 +31,7 @@ type RootElem struct {
3031
EffectWorkQueue []*vdom.EffectWorkElem
3132
NeedsRenderMap map[string]bool
3233
Atoms map[string]*vdom.Atom
34+
atomLock sync.Mutex
3335
RefOperations []rpctypes.VDomRefOperation
3436
}
3537

@@ -44,6 +46,20 @@ func (r *RootElem) AddEffectWork(id string, effectIndex int) {
4446
r.EffectWorkQueue = append(r.EffectWorkQueue, &vdom.EffectWorkElem{Id: id, EffectIndex: effectIndex})
4547
}
4648

49+
func (r *RootElem) GetDataMap() map[string]any {
50+
r.atomLock.Lock()
51+
defer r.atomLock.Unlock()
52+
53+
result := make(map[string]any)
54+
for atomName, atom := range r.Atoms {
55+
if strings.HasPrefix(atomName, "$data.") {
56+
strippedName := strings.TrimPrefix(atomName, "$data.")
57+
result[strippedName] = atom.Val
58+
}
59+
}
60+
return result
61+
}
62+
4763
func MakeRoot() *RootElem {
4864
return &RootElem{
4965
Root: nil,
@@ -53,7 +69,7 @@ func MakeRoot() *RootElem {
5369
}
5470
}
5571

56-
func (r *RootElem) GetAtom(name string) *vdom.Atom {
72+
func (r *RootElem) ensureAtomNoLock(name string) *vdom.Atom {
5773
atom, ok := r.Atoms[name]
5874
if !ok {
5975
atom = &vdom.Atom{UsedBy: make(map[string]bool)}
@@ -62,12 +78,47 @@ func (r *RootElem) GetAtom(name string) *vdom.Atom {
6278
return atom
6379
}
6480

81+
82+
func (r *RootElem) AtomSetUsedBy(atomName string, waveId string, used bool) {
83+
r.atomLock.Lock()
84+
defer r.atomLock.Unlock()
85+
86+
atom := r.ensureAtomNoLock(atomName)
87+
if used {
88+
atom.UsedBy[waveId] = true
89+
} else {
90+
delete(atom.UsedBy, waveId)
91+
}
92+
}
93+
94+
func (r *RootElem) AtomAddRenderWork(atomName string) {
95+
r.atomLock.Lock()
96+
defer r.atomLock.Unlock()
97+
98+
atom, ok := r.Atoms[atomName]
99+
if !ok {
100+
return
101+
}
102+
for compId := range atom.UsedBy {
103+
r.AddRenderWork(compId)
104+
}
105+
}
106+
65107
func (r *RootElem) GetAtomVal(name string) any {
66-
atom := r.GetAtom(name)
108+
r.atomLock.Lock()
109+
defer r.atomLock.Unlock()
110+
111+
atom, ok := r.Atoms[name]
112+
if !ok {
113+
return nil
114+
}
67115
return atom.Val
68116
}
69117

70118
func (r *RootElem) GetStateSync(full bool) []rpctypes.VDomStateSync {
119+
r.atomLock.Lock()
120+
defer r.atomLock.Unlock()
121+
71122
stateSync := make([]rpctypes.VDomStateSync, 0)
72123
for atomName, atom := range r.Atoms {
73124
if atom.Dirty || full {
@@ -79,7 +130,10 @@ func (r *RootElem) GetStateSync(full bool) []rpctypes.VDomStateSync {
79130
}
80131

81132
func (r *RootElem) SetAtomVal(name string, val any, markDirty bool) {
82-
atom := r.GetAtom(name)
133+
r.atomLock.Lock()
134+
defer r.atomLock.Unlock()
135+
136+
atom := r.ensureAtomNoLock(name)
83137
if !markDirty {
84138
atom.Val = val
85139
return

tsunami/comp/vdomcontext.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,20 @@ func (vc *VDomContextVal) AddEffectWork(id string, effectIndex int) {
2727
vc.Root.AddEffectWork(id, effectIndex)
2828
}
2929

30-
func (vc *VDomContextVal) GetAtom(atomName string) *vdom.Atom {
31-
return vc.Root.GetAtom(atomName)
30+
func (vc *VDomContextVal) AtomSetUsedBy(atomName string, waveId string, used bool) {
31+
vc.Root.AtomSetUsedBy(atomName, waveId, used)
32+
}
33+
34+
func (vc *VDomContextVal) AtomAddRenderWork(atomName string) {
35+
vc.Root.AtomAddRenderWork(atomName)
36+
}
37+
38+
func (vc *VDomContextVal) GetAtomVal(atomName string) any {
39+
return vc.Root.GetAtomVal(atomName)
40+
}
41+
42+
func (vc *VDomContextVal) SetAtomVal(atomName string, val any, markDirty bool) {
43+
vc.Root.SetAtomVal(atomName, val, markDirty)
3244
}
3345

3446
func (vc *VDomContextVal) GetRenderTs() int64 {

tsunami/vdom/vdom.go

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -320,27 +320,25 @@ func useAtom[T any](ctx context.Context, atomName string) (T, func(T), func(func
320320
hookVal.Init = true
321321
closedWaveId := vc.GetCompWaveId()
322322
hookVal.UnmountFn = func() {
323-
atom := vc.GetAtom(atomName)
324-
delete(atom.UsedBy, closedWaveId)
323+
vc.AtomSetUsedBy(atomName, closedWaveId, false)
325324
}
326325
}
327-
atom := vc.GetAtom(atomName)
328-
atom.UsedBy[vc.GetCompWaveId()] = true
329-
atomVal, ok := atom.Val.(T)
326+
vc.AtomSetUsedBy(atomName, vc.GetCompWaveId(), true)
327+
atomVal, ok := vc.GetAtomVal(atomName).(T)
330328
if !ok {
331-
panic(fmt.Sprintf("UseAtom %q value type mismatch (expected %T, got %T)", atomName, atomVal, atom.Val))
329+
panic(fmt.Sprintf("UseAtom %q value type mismatch (expected %T, got %T)", atomName, atomVal, vc.GetAtomVal(atomName)))
332330
}
333331
setVal := func(newVal T) {
334-
atom.Val = newVal
335-
for waveId := range atom.UsedBy {
336-
vc.AddRenderWork(waveId)
337-
}
332+
vc.SetAtomVal(atomName, newVal, true)
333+
vc.AtomAddRenderWork(atomName)
338334
}
339335
setFuncVal := func(updateFunc func(T) T) {
340-
atom.Val = updateFunc(atom.Val.(T))
341-
for waveId := range atom.UsedBy {
342-
vc.AddRenderWork(waveId)
336+
currentVal, ok := vc.GetAtomVal(atomName).(T)
337+
if !ok {
338+
panic(fmt.Sprintf("UseAtom %q value type mismatch in setFuncVal", atomName))
343339
}
340+
vc.SetAtomVal(atomName, updateFunc(currentVal), true)
341+
vc.AtomAddRenderWork(atomName)
344342
}
345343
return atomVal, setVal, setFuncVal
346344
}

tsunami/vdom/vdom_context.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ var vdomContextKey = vdomContextKeyType{}
1414
type VDomContext interface {
1515
AddRenderWork(id string)
1616
AddEffectWork(id string, effectIndex int)
17-
GetAtom(atomName string) *Atom
17+
AtomSetUsedBy(atomName string, waveId string, used bool)
18+
AtomAddRenderWork(atomName string)
19+
GetAtomVal(atomName string) any
20+
SetAtomVal(atomName string, val any, markDirty bool)
1821
GetRenderTs() int64
1922
GetCompWaveId() string
2023
GetOrderedHook() *Hook

0 commit comments

Comments
 (0)