Skip to content

Commit f4e100b

Browse files
committed
simplify vdom pkg, remove more public methods and types (hooks implemented in comp)
1 parent aceff3a commit f4e100b

9 files changed

Lines changed: 368 additions & 337 deletions

File tree

tsunami/comp/comp.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type ComponentImpl struct {
2222
Mounted bool
2323

2424
// hooks
25-
Hooks []*vdom.Hook
25+
Hooks []*Hook
2626

2727
// #text component
2828
Text string

tsunami/comp/hooks.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
// Copyright 2025, Command Line Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package comp
5+
6+
import (
7+
"context"
8+
"log"
9+
"strconv"
10+
11+
"github.com/wavetermdev/waveterm/tsunami/vdom"
12+
"github.com/wavetermdev/waveterm/tsunami/vdomctx"
13+
)
14+
15+
// generic hook structure
16+
type Hook struct {
17+
Init bool // is initialized
18+
Idx int // index in the hook array
19+
Fn func() func() // for useEffect
20+
UnmountFn func() // for useEffect
21+
Val any // for useState, useMemo, useRef
22+
Deps []any
23+
}
24+
25+
type VDomContextImpl struct {
26+
Root *RootElem
27+
Comp *ComponentImpl
28+
HookIdx int
29+
RenderOpts *RenderOpts
30+
}
31+
32+
// Compile-time check to ensure VDomContextImpl implements vdomctx.VDomContext
33+
var _ vdomctx.VDomContext = (*VDomContextImpl)(nil)
34+
35+
// Implement vdomctx.VDomContext interface methods on VDomContextImpl
36+
37+
func MakeContextVal(root *RootElem, comp *ComponentImpl, opts *RenderOpts) *VDomContextImpl {
38+
return &VDomContextImpl{Root: root, Comp: comp, HookIdx: 0, RenderOpts: opts}
39+
}
40+
41+
func (vc *VDomContextImpl) getCompWaveId() string {
42+
if vc.Comp == nil {
43+
return ""
44+
}
45+
return vc.Comp.WaveId
46+
}
47+
48+
func (vc *VDomContextImpl) getOrderedHook() *Hook {
49+
if vc.Comp == nil {
50+
panic("tsunami hooks must be called within a component (vc.Comp is nil)")
51+
}
52+
for len(vc.Comp.Hooks) <= vc.HookIdx {
53+
vc.Comp.Hooks = append(vc.Comp.Hooks, &Hook{Idx: len(vc.Comp.Hooks)})
54+
}
55+
hookVal := vc.Comp.Hooks[vc.HookIdx]
56+
vc.HookIdx++
57+
return hookVal
58+
}
59+
60+
func (vc *VDomContextImpl) getCompName() string {
61+
if vc.Comp == nil || vc.Comp.Elem == nil {
62+
return ""
63+
}
64+
return vc.Comp.Elem.Tag
65+
}
66+
67+
func (vc *VDomContextImpl) UseRenderTs(ctx context.Context) int64 {
68+
return vc.Root.RenderTs
69+
}
70+
71+
func (vc *VDomContextImpl) UseId(ctx context.Context) string {
72+
return vc.getCompWaveId()
73+
}
74+
75+
func (vc *VDomContextImpl) UseState(ctx context.Context, initialVal any) (any, func(any), func(func(any) any)) {
76+
hookVal := vc.getOrderedHook()
77+
if !hookVal.Init {
78+
hookVal.Init = true
79+
hookVal.Val = initialVal
80+
}
81+
82+
setVal := func(newVal any) {
83+
hookVal.Val = newVal
84+
vc.Root.AddRenderWork(vc.getCompWaveId())
85+
}
86+
87+
setFuncVal := func(updateFunc func(any) any) {
88+
hookVal.Val = updateFunc(hookVal.Val)
89+
vc.Root.AddRenderWork(vc.getCompWaveId())
90+
}
91+
92+
return hookVal.Val, setVal, setFuncVal
93+
}
94+
95+
func (vc *VDomContextImpl) UseAtom(ctx context.Context, atomName string) (any, func(any), func(func(any) any)) {
96+
hookVal := vc.getOrderedHook()
97+
if !hookVal.Init {
98+
hookVal.Init = true
99+
closedWaveId := vc.getCompWaveId()
100+
hookVal.UnmountFn = func() {
101+
vc.Root.AtomSetUsedBy(atomName, closedWaveId, false)
102+
}
103+
}
104+
vc.Root.AtomSetUsedBy(atomName, vc.getCompWaveId(), true)
105+
atomVal := vc.Root.GetAtomVal(atomName)
106+
107+
setVal := func(newVal any) {
108+
vc.Root.SetAtomVal(atomName, newVal, true)
109+
vc.Root.AtomAddRenderWork(atomName)
110+
}
111+
112+
setFuncVal := func(updateFunc func(any) any) {
113+
currentVal := vc.Root.GetAtomVal(atomName)
114+
vc.Root.SetAtomVal(atomName, updateFunc(currentVal), true)
115+
vc.Root.AtomAddRenderWork(atomName)
116+
}
117+
118+
return atomVal, setVal, setFuncVal
119+
}
120+
121+
func (vc *VDomContextImpl) UseVDomRef(ctx context.Context) any {
122+
hookVal := vc.getOrderedHook()
123+
if !hookVal.Init {
124+
hookVal.Init = true
125+
refId := vc.getCompWaveId() + ":" + strconv.Itoa(hookVal.Idx)
126+
hookVal.Val = &vdom.VDomRef{Type: vdom.ObjectType_Ref, RefId: refId}
127+
}
128+
refVal, ok := hookVal.Val.(*vdom.VDomRef)
129+
if !ok {
130+
panic("UseVDomRef hook value is not a ref (possible out of order or conditional hooks)")
131+
}
132+
return refVal
133+
}
134+
135+
func (vc *VDomContextImpl) UseRef(ctx context.Context, hookInitialVal any) any {
136+
hookVal := vc.getOrderedHook()
137+
if !hookVal.Init {
138+
hookVal.Init = true
139+
hookVal.Val = hookInitialVal
140+
}
141+
return hookVal.Val
142+
}
143+
144+
func depsEqual(deps1 []any, deps2 []any) bool {
145+
if len(deps1) != len(deps2) {
146+
return false
147+
}
148+
for i := range deps1 {
149+
if deps1[i] != deps2[i] {
150+
return false
151+
}
152+
}
153+
return true
154+
}
155+
156+
func (vc *VDomContextImpl) UseEffect(ctx context.Context, fn func() func(), deps []any) {
157+
hookVal := vc.getOrderedHook()
158+
if !hookVal.Init {
159+
hookVal.Init = true
160+
hookVal.Fn = fn
161+
hookVal.Deps = deps
162+
vc.Root.AddEffectWork(vc.getCompWaveId(), hookVal.Idx)
163+
return
164+
}
165+
if depsEqual(hookVal.Deps, deps) {
166+
return
167+
}
168+
hookVal.Fn = fn
169+
hookVal.Deps = deps
170+
vc.Root.AddEffectWork(vc.getCompWaveId(), hookVal.Idx)
171+
}
172+
173+
func (vc *VDomContextImpl) UseResync(ctx context.Context) bool {
174+
if vc.RenderOpts == nil {
175+
return false
176+
}
177+
return vc.RenderOpts.Resync
178+
}
179+
180+
func (vc *VDomContextImpl) UseSetAppTitle(ctx context.Context, title string) {
181+
if vc.getCompName() != "App" {
182+
log.Printf("UseSetAppTitle can only be called from the App component")
183+
return
184+
}
185+
vc.Root.AppTitle = title
186+
}
187+
188+
func (vc *VDomContextImpl) QueueRefOp(ctx context.Context, op any) {
189+
typedOp := op.(vdom.VDomRefOperation)
190+
vc.Root.QueueRefOp(typedOp)
191+
}

tsunami/comp/rootelem.go

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,44 @@ import (
1111
"strconv"
1212
"strings"
1313
"sync"
14+
"unicode"
1415

1516
"github.com/google/uuid"
1617
"github.com/wavetermdev/waveterm/tsunami/rpctypes"
1718
"github.com/wavetermdev/waveterm/tsunami/util"
1819
"github.com/wavetermdev/waveterm/tsunami/vdom"
20+
"github.com/wavetermdev/waveterm/tsunami/vdomctx"
1921
)
2022

23+
const ChildrenPropKey = "children"
24+
2125
type RenderOpts struct {
2226
Resync bool
2327
}
2428

29+
type Atom struct {
30+
Val any
31+
Dirty bool
32+
UsedBy map[string]bool // component waveid -> true
33+
}
34+
35+
type EffectWorkElem struct {
36+
Id string
37+
EffectIndex int
38+
}
39+
2540
type RootElem struct {
2641
OuterCtx context.Context
2742
Root *ComponentImpl
2843
RenderTs int64
2944
AppTitle string
3045
CFuncs map[string]any
3146
CompMap map[string]*ComponentImpl // component waveid -> component
32-
EffectWorkQueue []*vdom.EffectWorkElem
47+
EffectWorkQueue []*EffectWorkElem
3348
NeedsRenderMap map[string]bool
34-
Atoms map[string]*vdom.Atom
49+
Atoms map[string]*Atom
3550
atomLock sync.Mutex
36-
RefOperations []rpctypes.VDomRefOperation
51+
RefOperations []vdom.VDomRefOperation
3752
}
3853

3954
func (r *RootElem) AddRenderWork(id string) {
@@ -44,7 +59,7 @@ func (r *RootElem) AddRenderWork(id string) {
4459
}
4560

4661
func (r *RootElem) AddEffectWork(id string, effectIndex int) {
47-
r.EffectWorkQueue = append(r.EffectWorkQueue, &vdom.EffectWorkElem{Id: id, EffectIndex: effectIndex})
62+
r.EffectWorkQueue = append(r.EffectWorkQueue, &EffectWorkElem{Id: id, EffectIndex: effectIndex})
4863
}
4964

5065
func (r *RootElem) GetDataMap() map[string]any {
@@ -64,7 +79,7 @@ func (r *RootElem) GetDataMap() map[string]any {
6479
func (r *RootElem) GetConfigMap() map[string]any {
6580
r.atomLock.Lock()
6681
defer r.atomLock.Unlock()
67-
82+
6883
result := make(map[string]any)
6984
for atomName, atom := range r.Atoms {
7085
if strings.HasPrefix(atomName, "$config.") {
@@ -80,24 +95,23 @@ func MakeRoot() *RootElem {
8095
Root: nil,
8196
CFuncs: make(map[string]any),
8297
CompMap: make(map[string]*ComponentImpl),
83-
Atoms: make(map[string]*vdom.Atom),
98+
Atoms: make(map[string]*Atom),
8499
}
85100
}
86101

87-
func (r *RootElem) ensureAtomNoLock(name string) *vdom.Atom {
102+
func (r *RootElem) ensureAtomNoLock(name string) *Atom {
88103
atom, ok := r.Atoms[name]
89104
if !ok {
90-
atom = &vdom.Atom{UsedBy: make(map[string]bool)}
105+
atom = &Atom{UsedBy: make(map[string]bool)}
91106
r.Atoms[name] = atom
92107
}
93108
return atom
94109
}
95110

96-
97111
func (r *RootElem) AtomSetUsedBy(atomName string, waveId string, used bool) {
98112
r.atomLock.Lock()
99113
defer r.atomLock.Unlock()
100-
114+
101115
atom := r.ensureAtomNoLock(atomName)
102116
if used {
103117
atom.UsedBy[waveId] = true
@@ -109,7 +123,7 @@ func (r *RootElem) AtomSetUsedBy(atomName string, waveId string, used bool) {
109123
func (r *RootElem) AtomAddRenderWork(atomName string) {
110124
r.atomLock.Lock()
111125
defer r.atomLock.Unlock()
112-
126+
113127
atom, ok := r.Atoms[atomName]
114128
if !ok {
115129
return
@@ -297,7 +311,7 @@ func (r *RootElem) render(elem *vdom.VDomElem, comp **ComponentImpl, opts *Rende
297311
r.renderText(elem.Text, comp)
298312
return
299313
}
300-
if vdom.IsBaseTag(elem.Tag) {
314+
if isBaseTag(elem.Tag) {
301315
// simple vdom, fragment, wave element
302316
r.renderSimple(elem, comp, opts)
303317
return
@@ -421,9 +435,9 @@ func (r *RootElem) renderComponent(cfunc any, elem *vdom.VDomElem, comp **Compon
421435
for k, v := range elem.Props {
422436
props[k] = v
423437
}
424-
props[vdom.ChildrenPropKey] = elem.Children
438+
props[ChildrenPropKey] = elem.Children
425439
vc := MakeContextVal(r, *comp, opts)
426-
ctx := vdom.WithRenderContext(r.OuterCtx, vc)
440+
ctx := vdomctx.WithRenderContext(r.OuterCtx, vc)
427441
renderedElem := callCFunc(cfunc, ctx, props)
428442
rtnElemArr := vdom.PartToElems(renderedElem)
429443
if len(rtnElemArr) == 0 {
@@ -472,11 +486,11 @@ func (r *RootElem) UpdateRef(updateRef rpctypes.VDomRefUpdate) {
472486
r.AddRenderWork(waveId)
473487
}
474488

475-
func (r *RootElem) QueueRefOp(op rpctypes.VDomRefOperation) {
489+
func (r *RootElem) QueueRefOp(op vdom.VDomRefOperation) {
476490
r.RefOperations = append(r.RefOperations, op)
477491
}
478492

479-
func (r *RootElem) GetRefOperations() []rpctypes.VDomRefOperation {
493+
func (r *RootElem) GetRefOperations() []vdom.VDomRefOperation {
480494
ops := r.RefOperations
481495
r.RefOperations = nil
482496
return ops
@@ -573,17 +587,16 @@ func VDomFuncCallFn(vdf *vdom.VDomFunc, event vdom.VDomEvent) {
573587
}
574588
}
575589

576-
func QueueRefOp(ctx context.Context, ref *vdom.VDomRef, op rpctypes.VDomRefOperation) {
577-
if ref == nil || !ref.HasCurrent {
578-
return
590+
func isBaseTag(tag string) bool {
591+
if tag == "" {
592+
return false
579593
}
580-
vcIf := vdom.GetRenderContext(ctx)
581-
if vcIf == nil {
582-
panic("QueueRefOp must be called within a component (no context)")
594+
if tag == vdom.TextTag || tag == vdom.WaveTextTag || tag == vdom.WaveNullTag || tag == vdom.FragmentTag {
595+
return true
583596
}
584-
vc := vcIf.(*VDomContextVal)
585-
if op.RefId == "" {
586-
op.RefId = ref.RefId
597+
if tag[0] == '#' {
598+
return true
587599
}
588-
vc.Root.QueueRefOp(op)
600+
firstChar := rune(tag[0])
601+
return unicode.IsLower(firstChar)
589602
}

0 commit comments

Comments
 (0)