Skip to content

Commit

Permalink
update manager
Browse files Browse the repository at this point in the history
  • Loading branch information
maxence-charriere committed Oct 17, 2023
1 parent 59a48f2 commit 19e4353
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 4 deletions.
1 change: 1 addition & 0 deletions pkg/app/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ type nodeContext struct {
sessionStorage BrowserStorage
dispatch func(func())
defere func(func())
async func(func())
updateComponent func(Composer)
preventComponentUpdate func(Composer)
}
Expand Down
1 change: 1 addition & 0 deletions pkg/app/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ func makeTestContext() Context {
sessionStorage: sessionStorage,
dispatch: func(f func()) { f() },
defere: func(f func()) { f() },
async: func(f func()) { f() },
updateComponent: func(Composer) {},
preventComponentUpdate: func(Composer) {},
}
Expand Down
61 changes: 57 additions & 4 deletions pkg/app/enginex.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/url"
"strings"
"sync"
"time"

"github.com/maxence-charriere/go-app/v9/pkg/errors"
)
Expand All @@ -23,6 +24,9 @@ type engineX struct {
body UI
initBrowserOnce sync.Once
browser browser
dispatches chan func()
defers chan func()
goroutines sync.WaitGroup
}

func newEngineX(ctx context.Context, routes *router, resolveURL func(string) string, origin *url.URL, newBody func() HTMLBody) *engineX {
Expand Down Expand Up @@ -52,6 +56,8 @@ func newEngineX(ctx context.Context, routes *router, resolveURL func(string) str
sessionStorage: sessionStorage,
newBody: newBody,
nodes: nodeManager{},
dispatches: make(chan func(), 4096),
defers: make(chan func(), 4096),
}
}

Expand Down Expand Up @@ -140,6 +146,7 @@ func (e *engineX) baseContext() Context {
sessionStorage: e.sessionStorage,
dispatch: e.dispatch,
defere: e.defere,
async: e.async,
updateComponent: func(c Composer) {},
preventComponentUpdate: func(c Composer) {},
}
Expand Down Expand Up @@ -170,11 +177,57 @@ func (e *engineX) load(v Composer) {
}

func (e *engineX) dispatch(v func()) {
// TODO implementd
v()
e.dispatches <- v
}

func (e *engineX) defere(v func()) {
// TODO implement
v()
e.defers <- v
}

func (e *engineX) async(v func()) {
e.goroutines.Add(1)
go func() {
v()
e.goroutines.Done()
}()
}

func (e *engineX) Start(framerate int) {
activeFrameDuration := time.Second / time.Duration(framerate)
iddleFrameDuration := time.Hour
currentFrameDuration := iddleFrameDuration

frames := time.NewTicker(currentFrameDuration)
defer frames.Stop()

for {
select {
case dispatch := <-e.dispatches:
if currentFrameDuration != activeFrameDuration {
frames.Reset(activeFrameDuration)
currentFrameDuration = activeFrameDuration
}
dispatch()

case <-frames.C:
// perform all updated

e.executeDefers()

frames.Reset(iddleFrameDuration)
currentFrameDuration = iddleFrameDuration
}
}
}

func (e *engineX) executeDefers() {
for {
select {
case defere := <-e.defers:
defere()

default:
return
}
}
}
45 changes: 45 additions & 0 deletions pkg/app/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package app

// updateManager manages scheduled updates for UI components. It ensures that
// components are updated in an order respecting their depth in the UI
// hierarchy.
type updateManager struct {
pending []map[Composer]struct{}
}

// Add queues the given component for an update.
func (m *updateManager) Add(v Composer) {
depth := int(v.depth())
if len(m.pending) <= depth {
size := max(depth+1, 100)
pending := make([]map[Composer]struct{}, size)
copy(pending, m.pending)
m.pending = pending
}

updates := m.pending[depth]
if updates == nil {
updates = make(map[Composer]struct{})
m.pending[depth] = updates
}
updates[v] = struct{}{}
}

// Done removes the given Composer from the update queue, marking it as updated.
func (m *updateManager) Done(v Composer) {
depth := v.depth()
if len(m.pending) <= int(depth) {
return
}
delete(m.pending[depth], v)
}

// ForEach iterates over all queued components, invoking the provided function
// on each.
func (m *updateManager) ForEach(do func(Composer)) {
for _, updates := range m.pending {
for compo := range updates {
do(compo)
}
}
}
69 changes: 69 additions & 0 deletions pkg/app/update_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package app

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestUpdateManagerAdd(t *testing.T) {
t.Run("component is queued", func(t *testing.T) {
var m updateManager

compo := &hello{}
m.Add(compo)
require.Len(t, m.pending, 100)
_, ok := m.pending[0][compo]
require.True(t, ok)
})

t.Run("pending components size is increased", func(t *testing.T) {
var m updateManager

compo := &hello{}
m.Add(compo)
require.Len(t, m.pending, 100)

compo2 := &bar{Compo: Compo{treeDepth: 200}}
m.Add(compo2)
require.Len(t, m.pending, 201)

_, added := m.pending[0][compo]
require.True(t, added)

_, added2 := m.pending[200][compo2]
require.True(t, added2)
})
}

func TestUpdateManagerDone(t *testing.T) {
t.Run("component is removed from pending", func(t *testing.T) {
var m updateManager

compo := &hello{}
m.Add(compo)
_, ok := m.pending[0][compo]
require.True(t, ok)

m.Done(compo)
_, ok = m.pending[0][compo]
require.False(t, ok)
})

t.Run("non added component is skipped", func(t *testing.T) {
var m updateManager
m.Done(&hello{})
})
}

func TestUpdateManagerForEach(t *testing.T) {
var m updateManager

compo := &hello{}
m.Add(compo)

m.ForEach(func(c Composer) {
m.Done(c)
})
require.Empty(t, m.pending[0])
}

0 comments on commit 19e4353

Please sign in to comment.