Refactoring #3
+29
-38
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
Reference in New Issue
Block a user