feat(sidecar): wire Snapshot handler with serialization lock

Synchronizes backend.snapshot() so concurrent runners observe a
serialized hierarchy+screenshot pair, eliminating the cross-fade race
where two parallel reads describe different frames.
This commit is contained in:
pj committed 2026-05-31 15:23:07 +05:30
1 parent 4bc3147d2a
commit 389bbf10d1
1 file changed
+19
@@ -16,6 +16,7 @@ import dev.sanderling.driver.v1.Point
import dev.sanderling.driver.v1.PressKeyRequest
import dev.sanderling.driver.v1.RecentLogsRequest
import dev.sanderling.driver.v1.Selector
import dev.sanderling.driver.v1.SnapshotResponse
import dev.sanderling.driver.v1.SwipeRequest
import dev.sanderling.driver.v1.Text
import io.grpc.stub.StreamObserver
@@ -27,6 +28,7 @@ class DriverService(
) : DriverGrpc.DriverImplBase() {
private val launchedBundleId = AtomicReference<String?>(null)
private val snapshotLock = Any()
override fun launch(request: LaunchRequest, responseObserver: StreamObserver<Empty>) {
runRpc(responseObserver) {
@@ -116,6 +118,23 @@ class DriverService(
}
}
override fun snapshot(request: Empty, responseObserver: StreamObserver<SnapshotResponse>) {
runRpc(responseObserver) {
val sample = synchronized(snapshotLock) { backend.snapshot() }
val (png, width, height) = sample.screenshot
SnapshotResponse.newBuilder()
.setHierarchy(HierarchyJSON.newBuilder().setJson(sample.hierarchyJson).build())
.setScreenshot(
Image.newBuilder()
.setPng(ByteString.copyFrom(png))
.setWidth(width)
.setHeight(height)
.build(),
)
.build()
}
}
override fun waitForIdle(request: Duration, responseObserver: StreamObserver<Empty>) {
runRpc(responseObserver) {
backend.waitForIdle(request.millis)