Implement full architecture scaffolding with logic and frame receiver goroutines

- Logic goroutine: receives config events, recomputes layout, sends frames
- Frame receiver goroutine: reads frames, stores under mutex, calls w.Invalidate()
- Main event loop: renders frames under mutex, sends config events to logic
- Proper channel topology: configChan, frameChan, inputChan
- Mutex around elems shared between goroutines
- Window resize triggers layout recomputation in logic goroutine

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
Greg Pomerantz 2026-05-08 17:03:21 -04:00
parent 869f0b13ac
commit 556f72643f
2 changed files with 55 additions and 14 deletions

View File

@ -3,6 +3,7 @@ package main
import ( import (
"log" "log"
"os" "os"
"sync"
"gioui.org/app" "gioui.org/app"
"gioui.org/op" "gioui.org/op"
@ -31,41 +32,81 @@ func run(w *app.Window) error {
shaper := text.NewShaper() shaper := text.NewShaper()
renderer := ui.New(ui.Theme{FontSize: 14}, shaper) renderer := ui.New(ui.Theme{FontSize: 14}, shaper)
// Channel for window configuration events (resize, orientation) // Channels for communication between goroutines
configChan := make(chan ui.ConfigEvent) configChan := make(chan ui.ConfigEvent)
frameChan := make(chan []ui.Element)
inputChan := make(chan []ui.InputEvent)
// Shared state protected by mutex
var mu sync.Mutex
var elems []ui.Element
// Initial screen size // Initial screen size
screenWidth := unit.Dp(390) screenWidth := unit.Dp(390)
screenHeight := unit.Dp(844) screenHeight := unit.Dp(844)
// Compute initial elements // Compute initial elements
elems := ui.EditorLayout(screenWidth, screenHeight) elems = ui.EditorLayout(screenWidth, screenHeight)
// Start logic goroutine
go logic(w, shaper, screenWidth, screenHeight, configChan, frameChan, inputChan)
// Start frame receiver goroutine
go frameReceiver(w, &mu, &elems, frameChan)
// Main event loop
for { for {
switch e := w.Event().(type) { switch e := w.Event().(type) {
case app.DestroyEvent: case app.DestroyEvent:
return e.Err return e.Err
case app.ConfigEvent: case app.ConfigEvent:
// Send config event to logic goroutine (here, the main goroutine) // Send config event to logic goroutine
configChan <- ui.ConfigEvent{ configChan <- ui.ConfigEvent{
Width: unit.Dp(e.Config.Size.X), Width: unit.Dp(e.Config.Size.X),
Height: unit.Dp(e.Config.Size.Y), Height: unit.Dp(e.Config.Size.Y),
} }
case app.FrameEvent: case app.FrameEvent:
// Process any pending config events // Acquire mutex, read frame, draw, release mutex
mu.Lock()
currentElems := elems
mu.Unlock()
gtx := app.NewContext(&ops, e)
renderer.Draw(gtx, currentElems)
e.Frame(&ops)
}
}
}
// logic runs in a separate goroutine and owns all application state.
func logic(w *app.Window, shaper *text.Shaper, screenWidth, screenHeight unit.Dp,
configChan <-chan ui.ConfigEvent, frameChan chan<- []ui.Element, inputChan <-chan []ui.InputEvent) {
// Initial elements
elems := ui.EditorLayout(screenWidth, screenHeight)
for {
select { select {
case cfg := <-configChan: case cfg := <-configChan:
// Update screen size and recompute layout // Update screen size and recompute layout
screenWidth = cfg.Width screenWidth = cfg.Width
screenHeight = cfg.Height screenHeight = cfg.Height
elems = ui.EditorLayout(screenWidth, screenHeight) elems = ui.EditorLayout(screenWidth, screenHeight)
default: frameChan <- elems
// No config event, use existing elements case <-inputChan:
} // Process input (not implemented in mockup)
frameChan <- elems
gtx := app.NewContext(&ops, e)
renderer.Draw(gtx, elems)
e.Frame(&ops)
} }
} }
} }
// frameReceiver runs in a separate goroutine and bridges frameChan to the main loop.
func frameReceiver(w *app.Window, mu *sync.Mutex, elems *[]ui.Element, frameChan <-chan []ui.Element) {
for {
frame := <-frameChan
mu.Lock()
*elems = frame
w.Invalidate()
mu.Unlock()
}
}

BIN
pad

Binary file not shown.