Skip to content

fix: stop callers disposing the surface everyone shares - #4

Merged
Agash merged 1 commit into
mainfrom
fix/shared-iosurface-peer
Aug 14, 2026
Merged

fix: stop callers disposing the surface everyone shares#4
Agash merged 1 commit into
mainfrom
fix/shared-iosurface-peer

Conversation

@Agash

@Agash Agash commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Fixes the two failing transport tests in #3.

Root cause: not the test host. macOS bindings keep one managed peer per native object, and a Syphon server publishes into a single recycled surface — so the surface a client delivers is the same managed instance the server writes through. TryGetFrame documented "dispose it once consumed", and the polling loops did; the first dispose zeroed that shared handle and every later read reported no size, no planes, no pixels. It reproduces in a plain local run, and passing was only ever a matter of how the loop parity fell (which is why the sample's runs stayed green).

Changes

  • SyphonClient.TryGetFrame / SyphonServer.AcquireSurface keep the peer alive and document that the surface belongs to the library. Ownership is unchanged — owns: true still consumes the shim's retain; retain count measured flat over 60 frames.
  • IOSurfaceExtensions.PlaneCount reports 1 for a packed surface (IOSurface itself reports 0), and PlaneInfo answers plane 0 from the surface instead of letting -[IOSurface widthOfPlaneAtIndex:] raise NSGenericException.
  • Tests, sample and README updated; regression tests added for repeated frames staying readable, packed-surface plane info, and acquire returning the recycled instance.

API compatibility: no signature changes; the zero-copy publish/receive paths are untouched. Code that disposed a frame still compiles — it was already breaking itself.

Verified locally: 8/8 tests pass across repeated runs (previously 2 failed), Release solution build clean with 0 warnings, and the sample's probe and crosstest both pass.

The transport tests got a surface with no size and no planes, and it was
never about the test host: macOS bindings keep one managed peer per native
object, and a server publishes into one recycled surface. So every frame a
client delivers is the same managed instance the server writes through -
and TryGetFrame told callers to dispose it. The first dispose zeroed that
shared handle, and every later read came back empty. The polling loops in
the tests and the sample disposed the previous frame each iteration, which
made whether a run passed a matter of how the parity fell.

Frames and acquired surfaces now belong to the library: both hold the peer
so it is not finalized between calls, and both document that callers read
the pixels rather than take the surface. Ownership is unchanged - owns:true
still consumes the shim's retain, and the count stays flat across a frame
loop.

Also fix the plane helpers the second failure exposed. IOSurface reports no
planes for a packed surface and its per-plane accessors raise
NSGenericException there, so PlaneCount now answers 1 as it documents and
PlaneInfo describes plane 0 from the surface itself.

Closes #3
@Agash
Agash merged commit 70e564a into main Aug 14, 2026
6 checks passed
@Agash
Agash deleted the fix/shared-iosurface-peer branch August 14, 2026 11:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant