Skip to content

Commit 467c2db

Browse files
Working on periodic updates and fixes
1 parent 2cb171f commit 467c2db

241 files changed

Lines changed: 377 additions & 121 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

playground/gen.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// If you do not have go1.20.14 or a version supported by gopherjs,
2+
// see https://go.dev/doc/manage-install#installing-multiple
3+
//
14
//go:generate go1.20.14 run ./internal/cmd/precompile
25
//go:generate go1.20.14 install github.com/gopherjs/gopherjs
36
//go:generate env GOOS=js GOARCH=ecmascript gopherjs build -m .
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package common
2+
3+
// Throttle will reduce the amount of time that some event occurs.
4+
//
5+
// Calling update with a callback function to call will start a timer,
6+
// unless a timer has already been started. After a specific amount of time
7+
// since the timer was first started, the most recent callback is called and
8+
// the timer is stopped.
9+
//
10+
// This is useful for handling things like periodically evaluating some value
11+
// where the update may occur rapidly but we only want to occationally
12+
// perform the evaluation in the most up-to-date value.
13+
//
14+
// This interface allows the timers of the throttle to be replaced
15+
// with a mock implementation for testing.
16+
type Throttle interface {
17+
Update(callback func())
18+
Stop()
19+
}

playground/internal/common/undoRedo.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package common
22

33
type UndoRedoStack interface {
44
PerformUndo(cb CodeBoxWrapper)
5-
65
PerformRedo(cb CodeBoxWrapper)
76

87
// AddBreak makes any prior changes not joined into any future changes.

playground/internal/page/globals.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@ package page
22

33
import (
44
"sync"
5+
"time"
56

67
"github.com/gopherjs/gopherjs.github.io/playground/internal/common"
78
"github.com/gopherjs/gopherjs.github.io/playground/internal/runner"
89
"github.com/gopherjs/gopherjs.github.io/playground/internal/snippets"
910
"github.com/gopherjs/gopherjs.github.io/playground/internal/undoRedo"
1011
)
1112

13+
const (
14+
verbose = false
15+
prefetchDur = 3 * time.Second
16+
)
17+
1218
// globals are the global objects used by the react components.
1319
//
1420
// Since passing Go structs through React props will cause problems
@@ -19,13 +25,15 @@ import (
1925
// This could be problematic if we ever want to have multiple code editors
2026
// on the same page, but that is not a use case we currently have to support.
2127
var globals = struct {
22-
UndoRedo func() common.UndoRedoStack
23-
SnippetsStore func() common.SnippetStore
24-
Runner func() common.Runner
28+
UndoRedo func() common.UndoRedoStack
29+
SnippetsStore func() common.SnippetStore
30+
Runner func() common.Runner
31+
PrefetchThrottle func() common.Throttle
2532
}{
26-
UndoRedo: OnceValue(undoRedo.NewStack),
27-
SnippetsStore: OnceValue(snippets.NewLocalStore), // TODO(grantnelson-wf): Switch to snippets.NewStore before deploying
28-
Runner: OnceValue(func() common.Runner { return runner.New(runner.NewFetcher(false)) }),
33+
UndoRedo: OnceValue(undoRedo.NewStack),
34+
SnippetsStore: OnceValue(snippets.NewLocalStore), // TODO(grantnelson-wf): Switch to snippets.NewStore before deploying
35+
Runner: OnceValue(func() common.Runner { return runner.New(verbose, runner.NewFetcher(verbose)) }),
36+
PrefetchThrottle: OnceValue(func() common.Throttle { return runner.NewThrottle(prefetchDur) }),
2937
}
3038

3139
// OnceValue initializes a value on the first call

playground/internal/page/outputBox.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ func outputBoxComponent(props react.Props) *react.Element {
133133
children[i] = outputLine(i, classType, content)
134134
}
135135

136+
// TODO(grantnelson-wf): Check if the output-box-inner div is what is causing
137+
// a lot of output to suddenly cause the page to resize.
138+
136139
return react.Div(react.Props{
137140
`id`: `output-box`,
138141
`ref`: outputBoxRef,

playground/internal/page/playground.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,12 @@ func playgroundComponent(props react.Props) *react.Element {
140140
println("Save key pressed") // TODO(grantnelson-wf): Implement by running format or just do nothing?
141141
}, []any{})
142142

143-
// TODO(grantnelson-wf): Implement a periodic read of the code after a change to preload packages.
143+
// Whenever the code is changed wait a little bit then try to preload imported packages.
144+
react.UseEffect(func() {
145+
globals.PrefetchThrottle().Update(func() {
146+
globals.Runner().Preload(code)
147+
})
148+
}, []any{code})
144149

145150
// Create a callback for when the run button is pressed.
146151
onRun := react.UseCallback(func() {

playground/internal/runner/runner.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ import (
2121
const pseudoFileName = `prog.go`
2222

2323
type runner struct {
24-
cache *packageCache
24+
verbose bool
25+
cache *packageCache
2526
}
2627

27-
func New(fetcher common.Fetcher) common.Runner {
28+
func New(verbose bool, fetcher common.Fetcher) common.Runner {
2829
return &runner{
29-
cache: newPackageCache(fetcher),
30+
verbose: verbose,
31+
cache: newPackageCache(fetcher),
3032
}
3133
}
3234

@@ -67,13 +69,18 @@ func (r *runner) Compile(goCode string, then func(string, error)) {
6769
}
6870

6971
func (r *runner) syncCompile(goCode string) (string, error) {
72+
if r.verbose {
73+
println(`starting compile`)
74+
}
75+
7076
fileSet := token.NewFileSet()
7177
file, err := parser.ParseFile(fileSet, pseudoFileName, goCode, parser.ParseComments)
7278
if err != nil {
7379
return ``, err
7480
}
7581

76-
// TODO: Need to detect if [goCode] has no main method but instead has Test, Bench, or Example, methods.
82+
// TODO(grantnelson-wf): Idea: We could detect if [goCode] has no main method and
83+
// instead has Test, Bench, or Example, methods, then run the code as a test.
7784

7885
root := &sources.Sources{
7986
ImportPath: `main`,
@@ -88,15 +95,24 @@ func (r *runner) syncCompile(goCode string) (string, error) {
8895

8996
allSources, err := r.collectAllSources(root)
9097
if err != nil {
98+
if r.verbose {
99+
println(`compile failed`)
100+
}
91101
return ``, err
92102
}
93103

94104
archives, err := r.prepareAndCompilePackages(root.ImportPath, allSources)
95105
if err != nil {
106+
if r.verbose {
107+
println(`compile failed`)
108+
}
96109
return ``, err
97110
}
98111

99112
jsCode := r.write(archives)
113+
if r.verbose {
114+
println(`done compiling`)
115+
}
100116
return jsCode, nil
101117
}
102118

@@ -187,6 +203,9 @@ func (r *runner) write(allPkgs []*compiler.Archive) string {
187203
}
188204

189205
func (r *runner) Run(jsCode string) {
206+
if r.verbose {
207+
println(`running compiled code`)
208+
}
190209
js.Global.Set("$checkForDeadlock", true)
191210
js.Global.Call("eval", js.InternalObject(jsCode))
192211
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package runner
2+
3+
import (
4+
"sync"
5+
"time"
6+
7+
"github.com/gopherjs/gopherjs.github.io/playground/internal/common"
8+
)
9+
10+
type throttleImp struct {
11+
lock *sync.Mutex
12+
dur time.Duration
13+
timer *time.Timer
14+
pending bool
15+
callback func()
16+
}
17+
18+
func NewThrottle(dur time.Duration) common.Throttle {
19+
return &throttleImp{
20+
lock: &sync.Mutex{},
21+
dur: dur,
22+
timer: nil,
23+
pending: false,
24+
callback: nil,
25+
}
26+
}
27+
28+
func (t *throttleImp) Update(callback func()) {
29+
t.lock.Lock()
30+
defer t.lock.Unlock()
31+
32+
t.callback = callback
33+
if t.timer == nil {
34+
t.timer = time.AfterFunc(t.dur, t.timeout)
35+
} else if !t.pending {
36+
t.timer.Reset(t.dur)
37+
}
38+
t.pending = true
39+
}
40+
41+
func (t *throttleImp) timeout() {
42+
t.lock.Lock()
43+
defer t.lock.Unlock()
44+
45+
if t.callback != nil {
46+
t.callback()
47+
t.callback = nil
48+
}
49+
t.pending = false
50+
}
51+
52+
func (t *throttleImp) Stop() {
53+
t.lock.Lock()
54+
defer t.lock.Unlock()
55+
56+
if t.timer != nil {
57+
t.timer.Stop()
58+
t.timer = nil
59+
}
60+
t.callback = nil
61+
}

0 commit comments

Comments
 (0)