Skip to content

Commit 59c1b0f

Browse files
committed
docs: add Swift Concurrency integration recipe (AsyncSequence)
1 parent 00acc1b commit 59c1b0f

2 files changed

Lines changed: 92 additions & 0 deletions

File tree

INTEGRATIONS.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Integrating with Swift Concurrency
2+
3+
`CobsCodec` is stdlib-only and Foundation-free by design, so it ships no
4+
`Stream` / Combine glue that would tie it to Apple platforms. Instead, the
5+
public `CobsStreamDecoder` is enough to build the adapter you want in **your**
6+
code. Here is the idiomatic one: an `AsyncSequence` that turns a raw byte stream
7+
into the COBS packets carried on it.
8+
9+
This is a copy-paste recipe, **not** part of the `CobsCodec` module. It is
10+
verified against the module's real API.
11+
12+
## `AsyncSequence<UInt8>` → decoded packets
13+
14+
```swift
15+
import CobsCodec
16+
17+
public struct CobsFrames<Base: AsyncSequence>: AsyncSequence where Base.Element == UInt8 {
18+
public typealias Element = [UInt8]
19+
20+
let base: Base
21+
let reduced: Bool
22+
let sentinel: UInt8
23+
24+
public struct AsyncIterator: AsyncIteratorProtocol {
25+
var base: Base.AsyncIterator
26+
let decoder: CobsStreamDecoder
27+
var ready: [[UInt8]] = []
28+
var next = 0
29+
30+
public mutating func next() async throws -> [UInt8]? {
31+
while true {
32+
if next < ready.count {
33+
defer { next += 1 }
34+
return ready[next]
35+
}
36+
ready.removeAll(keepingCapacity: true)
37+
next = 0
38+
guard let byte = try await base.next() else { return nil }
39+
let frames = try decoder.feed([byte])
40+
if !frames.isEmpty { ready = frames }
41+
}
42+
}
43+
}
44+
45+
public func makeAsyncIterator() -> AsyncIterator {
46+
AsyncIterator(
47+
base: base.makeAsyncIterator(),
48+
decoder: CobsStreamDecoder(reduced: reduced, sentinel: sentinel))
49+
}
50+
}
51+
52+
extension AsyncSequence where Element == UInt8 {
53+
/// Lazily reframes this byte stream (e.g. a serial port's `bytes`) into the
54+
/// COBS packets carried on it. Each delimiter closes a frame.
55+
public func cobsFrames(reduced: Bool = false, sentinel: UInt8 = 0) -> CobsFrames<Self> {
56+
CobsFrames(base: self, reduced: reduced, sentinel: sentinel)
57+
}
58+
}
59+
```
60+
61+
Use it wherever you have an async byte stream:
62+
63+
```swift
64+
for try await packet in serialPort.bytes.cobsFrames() {
65+
handle(packet) // one decoded COBS packet
66+
}
67+
```
68+
69+
`CobsStreamDecoder` also takes `maxFrameLength` (a guard against unbounded
70+
buffering on a noisy link) and `skipEmpty` — thread them through `cobsFrames`
71+
the same way as `sentinel` / `reduced` if you need them.
72+
73+
## Encoding
74+
75+
The write direction needs no adapter: `Framing.frame(_:reduced:sentinel:)`
76+
already returns a ready-to-send `[UInt8]` (COBS + delimiter) for each packet, so
77+
mapping a sequence of packets to wire bytes is a one-liner.
78+
79+
## What stays in the core
80+
81+
`Cobs` / `Cobsr` encode/decode (with sentinel and in-place variants), `Framing`
82+
for `0x00`-delimited streams, the incremental `CobsStreamDecoder`, and the
83+
`encodingOverhead` / `maxEncodedLength` helpers. The adapter above is just the
84+
seam where Swift Concurrency meets that API.

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,14 @@ AMD Ryzen 7 3800XT under WSL2 — indicative (includes result allocation):
104104
| `Cobs.decode` | ~1150 MB/s |
105105
| `Cobsr.encode` | ~850 MB/s |
106106

107+
## Integrations
108+
109+
The module stays stdlib-only and Foundation-free, so framework glue lives in
110+
your app rather than in the package. [INTEGRATIONS.md](INTEGRATIONS.md) has a
111+
verified copy-paste recipe: an `AsyncSequence` adapter (`bytes.cobsFrames()`)
112+
that reframes a raw byte stream into decoded packets, built on the public
113+
`CobsStreamDecoder`.
114+
107115
## Background
108116

109117
Stuart Cheshire and Mary Baker, "Consistent Overhead Byte Stuffing",

0 commit comments

Comments
 (0)