82 lines
2.2 KiB
Go
82 lines
2.2 KiB
Go
package vdom
|
|
|
|
import "encoding/json"
|
|
|
|
// Schedule is installed by the wasm runtime at startup; a signal write
|
|
// triggers a re-render. On the server it is nil (renders are one-shot).
|
|
var Schedule func()
|
|
|
|
// Signal holds state. On the client, writing it schedules a re-render.
|
|
type Signal[T any] struct{ v T }
|
|
|
|
func NewSignal[T any](initial T) *Signal[T] {
|
|
s := &Signal[T]{v: initial}
|
|
if active != nil {
|
|
active.adopt(s) // server component round-trip: restore prior value, track for snapshot
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (s *Signal[T]) Get() T { return s.v }
|
|
|
|
func (s *Signal[T]) Set(v T) {
|
|
s.v = v
|
|
if Schedule != nil {
|
|
Schedule()
|
|
}
|
|
}
|
|
|
|
func (s *Signal[T]) Update(fn func(T) T) { s.Set(fn(s.v)) }
|
|
|
|
func (s *Signal[T]) snapshot() []byte { b, _ := json.Marshal(s.v); return b }
|
|
func (s *Signal[T]) restoreFrom(b []byte) { _ = json.Unmarshal(b, &s.v) }
|
|
|
|
// ---- signal-state round-trip for server components ----
|
|
//
|
|
// A server component is stateless on the server: its signal values are
|
|
// serialized and round-tripped through the client. While a Collector is active
|
|
// (during a server-component render on the server), NewSignal restores each
|
|
// signal's value from the incoming snapshot by creation order (hook-order), and
|
|
// remembers it so the new values can be snapshotted back out.
|
|
|
|
type signalState interface {
|
|
snapshot() []byte
|
|
restoreFrom([]byte)
|
|
}
|
|
|
|
// Collector captures the signals created during a server render.
|
|
type Collector struct {
|
|
restore [][]byte
|
|
idx int
|
|
sigs []signalState
|
|
}
|
|
|
|
var active *Collector
|
|
|
|
func (c *Collector) adopt(s signalState) {
|
|
if c.idx < len(c.restore) {
|
|
s.restoreFrom(c.restore[c.idx])
|
|
}
|
|
c.idx++
|
|
c.sigs = append(c.sigs, s)
|
|
}
|
|
|
|
// BeginCollect starts collecting signals, restoring them from `restore` (which
|
|
// may be nil for an initial mount). Call EndCollect when construction is done.
|
|
func BeginCollect(restore [][]byte) *Collector {
|
|
active = &Collector{restore: restore}
|
|
return active
|
|
}
|
|
|
|
// EndCollect stops collecting (rendering/handlers may still read the signals).
|
|
func EndCollect() { active = nil }
|
|
|
|
// Snapshot returns the current values of the collected signals, in order.
|
|
func (c *Collector) Snapshot() [][]byte {
|
|
out := make([][]byte, len(c.sigs))
|
|
for i, s := range c.sigs {
|
|
out[i] = s.snapshot()
|
|
}
|
|
return out
|
|
}
|