wireform-core-0.2.0.0: src/Wireform/Transport/Receive.hs
{-# LANGUAGE RankNTypes #-}
{- | Receive-side magic-ring transport.
The parser consumes bytes from a 'ReceiveTransport'. The producer
(a network recv loop, a TLS context, an io_uring backend, an
in-memory test fixture, …) writes into the magic ring at @head@
and the parser drains @[tail, head)@.
== Cursor model
'Word64' positions are monotonic (never wrap during process
lifetime). The byte at logical position @p@ lives at
@ringBase + (p .&. ringMask)@ in the ring.
== Why the ring is stored as three primitive fields
'ReceiveTransport' embeds the ring's base pointer, size, and mask
directly rather than carrying a 'Wireform.Ring.Internal.MagicRing'.
That keeps the type free of the ring's phantom @s@ parameter, so
the rank-2 scope safety of 'Wireform.Ring.withMagicRing' is something
callers opt into at the slice layer rather than something that
ripples through every 'ReceiveTransport'-using package.
'receiveRing' reconstructs a polymorphic 'MagicRing' for callers
that still want to call 'ringBase' / 'ringSize' / 'ringMask' on a
record value.
-}
module Wireform.Transport.Receive (
ReceiveTransport (..),
ReceiveWait (..),
receiveRing,
) where
import Control.Exception (SomeException)
import Data.Word (Word64, Word8)
import Foreign.Ptr (Ptr)
import Wireform.Ring.Internal (MagicRing (..))
{- | A producer-side cursor + a slot to wait on more data.
The parser interacts with data exclusively through this record.
Implementations bind one to a socket, TLS context, io_uring
instance, or an in-memory fixture.
-}
data ReceiveTransport = ReceiveTransport
{ receiveRingBase :: {-# UNPACK #-} !(Ptr Word8)
-- ^ Base address of the ring's first mapping.
, receiveRingSize :: {-# UNPACK #-} !Int
-- ^ Physical ring size (N, not 2N). Always a power of two.
, receiveRingMask :: {-# UNPACK #-} !Int
-- ^ @ringSize - 1@, cached for cheap @pos .&. mask@.
, receiveLoadHead :: !(IO Word64)
{- ^ Read the producer's current head position
(monotonically increasing).
-}
, receiveAdvanceTail :: !(Word64 -> IO ())
{- ^ Consumer publishes a new tail position
(monotonically increasing). The transport may use this for
backpressure or buffer reuse.
-}
, receiveWaitData :: !(Word64 -> IO ReceiveWait)
{- ^ Block until head advances past the given position.
Parks on the IO manager when blocking (same thread as the
parser).
-}
, receiveClose :: !(IO ())
-- ^ Release resources. Idempotent.
}
-- | Outcome of waiting for more data.
data ReceiveWait
= -- | New head position.
ReceiveMoreData {-# UNPACK #-} !Word64
| {- | Clean end (sticky: once returned, all subsequent calls
return this).
-}
ReceiveEndOfInput
| -- | Producer-side failure (sticky).
ReceiveFailed !SomeException
deriving stock (Show)
{- | Reconstruct the underlying 'MagicRing'. Polymorphic in the
phantom @s@: the resulting handle does /not/ inherit the scope of
whatever ring originally produced these bytes, so callers should
treat it as un-scoped (i.e. equivalent to the pre-@MagicRing s@
world). Use 'Wireform.Ring.withMagicRing' directly when you need
the type-system-enforced safety.
-}
receiveRing :: ReceiveTransport -> MagicRing s
receiveRing t = MagicRing (receiveRingBase t) (receiveRingSize t)
{-# INLINE receiveRing #-}