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
audio string
}
// One control channel: grant (empty params) or reconnect (with params).
// Reconnect requests from GUI or automatic retry
control := make(chan reconnectParams, 1)
videoBridge := playback.NewVideoBridge()
@@ -377,7 +377,7 @@ func main() {
audioSrc = nil
}
// 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 != "" {
s, e := source.OpenSameDomainSync(params.domain, params.video, params.audio)
if e == nil {
@@ -438,7 +438,7 @@ func main() {
}
go func() {
// Audio-only mode: independent loop, no grant/staged handshake.
// Audio-only mode: independent loop.
if audioSrc != nil && syncSrc == nil && videoSrc == nil {
for {
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 {
params := <-control
if params.video != "" || params.audio != "" {
// Reconnect request from Connect button or auto-retry.
select {
case <-control: // drain any pending grant
default:
}
select {
case <-ctx.Done():
return
case params := <-control:
if rerr := reopen(params); rerr != nil {
if errors.Is(rerr, context.Canceled) {
return
}
log.Printf("source: reopen failed: %v, retrying", rerr)
select {
case <-time.After(500 * time.Millisecond):
case <-ctx.Done():
return
}
select {
case control <- params:
default:
// Preserve an already queued, potentially newer request.
}
}
continue
default:
}
var videoFrame playback.VideoFrame
@@ -538,15 +541,16 @@ func main() {
return
}
log.Printf("source: %v", err)
// Drain any pending grant, then send reconnect.
select {
case <-control:
default:
// Request a reconnect after the read failure.
params := reconnectParams{
domain: domainStr,
video: videoStr,
audio: audioStr,
}
select {
case control <- reconnectParams{domain: domainStr, video: videoStr, audio: audioStr}:
case <-ctx.Done():
return
case control <- params:
default:
// Preserve an already queued, potentially newer request.
}
continue
}
@@ -570,14 +574,15 @@ func main() {
return
}
log.Printf("source: %v", err)
select {
case <-control:
default:
params := reconnectParams{
domain: domainStr,
video: videoStr,
audio: audioStr,
}
select {
case control <- reconnectParams{domain: domainStr, video: videoStr, audio: audioStr}:
case <-ctx.Done():
return
case control <- params:
default:
// Preserve an already queued, potentially newer request.
}
continue
}
@@ -604,7 +609,6 @@ func main() {
running := true
resized := false
granted := false
fullscreen := args.IsFullscreen
if fullscreen {
sdl.SetWindowFullscreen(windowHandler, true)
@@ -662,15 +666,6 @@ func main() {
}
resized = false
}
if !granted {
select {
case control <- reconnectParams{}:
granted = true
case <-ctx.Done():
running = false
continue
}
}
var shownIndex uint64
hasFrame := false
@@ -697,15 +692,11 @@ func main() {
}
shownIndex = pendingFrame.Frame.Index
granted = false
hasFrame = true
} else if frameErr != nil &&
!errors.Is(frameErr, context.DeadlineExceeded) &&
!errors.Is(frameErr, context.Canceled) {
panic(frameErr)
} else {
// No frame arrived before the deadline.
granted = false
}
// stats
+1 -1
View File
@@ -14,7 +14,7 @@ Size=200,200
Collapsed=0
[Window][Connection]
Pos=1250,701
Pos=500,330
Size=523,153
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.