Refactoring #3

Merged
itten merged 87 commits from refactoring into main 2026-09-01 23:52:36 +03:00
3 changed files with 36 additions and 39 deletions
Showing only changes of commit e4e8da2568 - Show all commits
+29 -38
View File
@@ -358,7 +358,7 @@ func main() {
video string video string
audio string audio string
} }
// One control channel: grant (empty params) or reconnect (with params). // Reconnect requests from GUI or automatic retry
control := make(chan reconnectParams, 1) control := make(chan reconnectParams, 1)
videoBridge := playback.NewVideoBridge() videoBridge := playback.NewVideoBridge()
@@ -377,7 +377,7 @@ func main() {
audioSrc = nil audioSrc = nil
} }
// Try once. Return error if fails — caller loops back to select // Try once. Return error if fails — caller loops back to select
// and can pick up new reconnect params or a new grant. // and can pick up newer reconnect request.
if params.video != "" && params.audio != "" { if params.video != "" && params.audio != "" {
s, e := source.OpenSameDomainSync(params.domain, params.video, params.audio) s, e := source.OpenSameDomainSync(params.domain, params.video, params.audio)
if e == nil { if e == nil {
@@ -438,7 +438,7 @@ func main() {
} }
go func() { go func() {
// Audio-only mode: independent loop, no grant/staged handshake. // Audio-only mode: independent loop.
if audioSrc != nil && syncSrc == nil && videoSrc == nil { if audioSrc != nil && syncSrc == nil && videoSrc == nil {
for { for {
select { select {
@@ -503,31 +503,34 @@ func main() {
} }
} }
// Video (with or without sync) mode: grant/staged handshake. // Video bridge provides backpressure: only one borrowed frame is in flight.
for { for {
params := <-control select {
if params.video != "" || params.audio != "" { case <-ctx.Done():
// Reconnect request from Connect button or auto-retry. return
select {
case <-control: // drain any pending grant case params := <-control:
default:
}
if rerr := reopen(params); rerr != nil { if rerr := reopen(params); rerr != nil {
if errors.Is(rerr, context.Canceled) { if errors.Is(rerr, context.Canceled) {
return return
} }
log.Printf("source: reopen failed: %v, retrying", rerr) log.Printf("source: reopen failed: %v, retrying", rerr)
select { select {
case <-time.After(500 * time.Millisecond): case <-time.After(500 * time.Millisecond):
case <-ctx.Done(): case <-ctx.Done():
return return
} }
select { select {
case control <- params: case control <- params:
default: default:
// Preserve an already queued, potentially newer request.
} }
} }
continue continue
default:
} }
var videoFrame playback.VideoFrame var videoFrame playback.VideoFrame
@@ -538,15 +541,16 @@ func main() {
return return
} }
log.Printf("source: %v", err) log.Printf("source: %v", err)
// Drain any pending grant, then send reconnect. // Request a reconnect after the read failure.
select { params := reconnectParams{
case <-control: domain: domainStr,
default: video: videoStr,
audio: audioStr,
} }
select { select {
case control <- reconnectParams{domain: domainStr, video: videoStr, audio: audioStr}: case control <- params:
case <-ctx.Done(): default:
return // Preserve an already queued, potentially newer request.
} }
continue continue
} }
@@ -570,14 +574,15 @@ func main() {
return return
} }
log.Printf("source: %v", err) log.Printf("source: %v", err)
select { params := reconnectParams{
case <-control: domain: domainStr,
default: video: videoStr,
audio: audioStr,
} }
select { select {
case control <- reconnectParams{domain: domainStr, video: videoStr, audio: audioStr}: case control <- params:
case <-ctx.Done(): default:
return // Preserve an already queued, potentially newer request.
} }
continue continue
} }
@@ -604,7 +609,6 @@ func main() {
running := true running := true
resized := false resized := false
granted := false
fullscreen := args.IsFullscreen fullscreen := args.IsFullscreen
if fullscreen { if fullscreen {
sdl.SetWindowFullscreen(windowHandler, true) sdl.SetWindowFullscreen(windowHandler, true)
@@ -662,15 +666,6 @@ func main() {
} }
resized = false resized = false
} }
if !granted {
select {
case control <- reconnectParams{}:
granted = true
case <-ctx.Done():
running = false
continue
}
}
var shownIndex uint64 var shownIndex uint64
hasFrame := false hasFrame := false
@@ -697,15 +692,11 @@ func main() {
} }
shownIndex = pendingFrame.Frame.Index shownIndex = pendingFrame.Frame.Index
granted = false
hasFrame = true hasFrame = true
} else if frameErr != nil && } else if frameErr != nil &&
!errors.Is(frameErr, context.DeadlineExceeded) && !errors.Is(frameErr, context.DeadlineExceeded) &&
!errors.Is(frameErr, context.Canceled) { !errors.Is(frameErr, context.Canceled) {
panic(frameErr) panic(frameErr)
} else {
// No frame arrived before the deadline.
granted = false
} }
// stats // stats
+1 -1
View File
@@ -14,7 +14,7 @@ Size=200,200
Collapsed=0 Collapsed=0
[Window][Connection] [Window][Connection]
Pos=1250,701 Pos=500,330
Size=523,153 Size=523,153
Collapsed=0 Collapsed=0
+6
View File
@@ -0,0 +1,6 @@
That makes sense. The GUI is likely more responsive because frame staging is now serialized with rendering:
- The background goroutine no longer copies a large frame into Vulkan-mapped memory concurrently with GUI/render work.
- StageFrame waits for the GPU fence before writing, removing CPU/GPU memory contention and undefined synchronization.
- The bridge creates deterministic backpressure: the source cannot begin another frame until the current payload is staged.
- The main thread now controls the complete render sequence instead of coordinating loosely through two channels.
So we fixed both correctness and scheduling stability without adding another frame copy.