@zio.dev/zio-blocks 0.0.28 → 0.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/guides/query-dsl-extending.md +1 -1
- package/guides/query-dsl-fluent-builder.md +1 -1
- package/guides/query-dsl-reified-optics.md +1 -1
- package/guides/query-dsl-sql.md +1 -1
- package/guides/zio-schema-migration.md +6 -6
- package/index.md +62 -16
- package/package.json +1 -1
- package/path-interpolator.md +70 -9
- package/reference/allows.md +96 -0
- package/reference/codec.md +8 -8
- package/reference/combinators.md +345 -0
- package/reference/context.md +639 -67
- package/reference/docs.md +1 -1
- package/reference/http-model.md +1716 -0
- package/reference/json-differ.md +320 -0
- package/reference/json-patch.md +1 -1
- package/reference/media-type.md +2 -2
- package/reference/resource-management-di/index.md +49 -0
- package/reference/resource-management-di/resource.md +1125 -0
- package/{scope.md → reference/resource-management-di/scope.md} +1 -1
- package/reference/resource-management-di/wire.md +832 -0
- package/reference/schema-evolution/as.md +4 -4
- package/reference/schema-evolution/into.md +2 -2
- package/reference/schema-expr.md +2 -2
- package/reference/type-class-derivation.md +31 -31
- package/ringbuffer.md +249 -0
- package/sidebars.js +14 -2
|
@@ -39,13 +39,13 @@ The bidirectional data flow looks like this:
|
|
|
39
39
|
`As` is part of `zio-blocks-schema`:
|
|
40
40
|
|
|
41
41
|
```scala
|
|
42
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.
|
|
42
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.29"
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
For Scala.js and Scala Native, use `%%%`:
|
|
46
46
|
|
|
47
47
|
```scala
|
|
48
|
-
libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.
|
|
48
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.29"
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
Supported Scala versions: 2.13.x and 3.x.
|
|
@@ -240,7 +240,7 @@ trait As[A, B] {
|
|
|
240
240
|
|
|
241
241
|
```scala
|
|
242
242
|
val revAs: As[LongBox, IntBox] = boxAs.reverse
|
|
243
|
-
// revAs: As[LongBox, IntBox] = zio.blocks.schema.As$$anon$1@
|
|
243
|
+
// revAs: As[LongBox, IntBox] = zio.blocks.schema.As$$anon$1@29407f04
|
|
244
244
|
|
|
245
245
|
revAs.into(LongBox(5L))
|
|
246
246
|
// res11: Either[SchemaError, IntBox] = Right(IntBox(5))
|
|
@@ -302,7 +302,7 @@ We import `As.reverseInto` and use it to obtain the reverse `Into[Int, String]`:
|
|
|
302
302
|
import As.reverseInto
|
|
303
303
|
|
|
304
304
|
val intToStr: Into[Int, String] = reverseInto[String, Int]
|
|
305
|
-
// intToStr: Into[Int, String] = zio.blocks.schema.AsLowPriorityImplicits$$Lambda$
|
|
305
|
+
// intToStr: Into[Int, String] = zio.blocks.schema.AsLowPriorityImplicits$$Lambda$16771/0x00007f092a81f330@1cdf3706
|
|
306
306
|
intToStr.into(42)
|
|
307
307
|
// res14: Either[SchemaError, String] = Right("42")
|
|
308
308
|
```
|
|
@@ -69,13 +69,13 @@ Compare this to a manual implementation:
|
|
|
69
69
|
`Into` is part of the `zio-blocks-schema` module:
|
|
70
70
|
|
|
71
71
|
```scala
|
|
72
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.
|
|
72
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.29"
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
For Scala.js:
|
|
76
76
|
|
|
77
77
|
```scala
|
|
78
|
-
libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.
|
|
78
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.29"
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
Supported Scala versions: 2.13.x and 3.x.
|
package/reference/schema-expr.md
CHANGED
|
@@ -70,13 +70,13 @@ val result: Either[OpticCheck, Seq[Boolean]] = combined.eval(alice)
|
|
|
70
70
|
## Installation
|
|
71
71
|
|
|
72
72
|
```scala
|
|
73
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.
|
|
73
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.29"
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
For cross-platform (Scala.js):
|
|
77
77
|
|
|
78
78
|
```scala
|
|
79
|
-
libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.
|
|
79
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.29"
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
Supported Scala versions: 2.13.x and 3.x.
|
|
@@ -107,7 +107,7 @@ trait Deriver[TC[_]] {
|
|
|
107
107
|
def deriveRecord[F[_, _], A](
|
|
108
108
|
fields: IndexedSeq[Term[F, A, ?]],
|
|
109
109
|
typeId: TypeId[A],
|
|
110
|
-
binding: Binding
|
|
110
|
+
binding: Binding.Record[A],
|
|
111
111
|
doc: Doc,
|
|
112
112
|
modifiers: Seq[Modifier.Reflect],
|
|
113
113
|
defaultValue: Option[A],
|
|
@@ -194,7 +194,7 @@ object DeriveShow extends Deriver[Show] {
|
|
|
194
194
|
override def derivePrimitive[A](
|
|
195
195
|
primitiveType: PrimitiveType[A],
|
|
196
196
|
typeId: TypeId[A],
|
|
197
|
-
binding: Binding
|
|
197
|
+
binding: Binding.Primitive[A],
|
|
198
198
|
doc: Doc,
|
|
199
199
|
modifiers: Seq[Modifier.Reflect],
|
|
200
200
|
defaultValue: Option[A],
|
|
@@ -213,7 +213,7 @@ object DeriveShow extends Deriver[Show] {
|
|
|
213
213
|
override def deriveRecord[F[_, _], A](
|
|
214
214
|
fields: IndexedSeq[Term[F, A, ?]],
|
|
215
215
|
typeId: TypeId[A],
|
|
216
|
-
binding: Binding
|
|
216
|
+
binding: Binding.Record[A],
|
|
217
217
|
doc: Doc,
|
|
218
218
|
modifiers: Seq[Modifier.Reflect],
|
|
219
219
|
defaultValue: Option[A],
|
|
@@ -255,7 +255,7 @@ object DeriveShow extends Deriver[Show] {
|
|
|
255
255
|
override def deriveVariant[F[_, _], A](
|
|
256
256
|
cases: IndexedSeq[Term[F, A, ?]],
|
|
257
257
|
typeId: TypeId[A],
|
|
258
|
-
binding: Binding
|
|
258
|
+
binding: Binding.Variant[A],
|
|
259
259
|
doc: Doc,
|
|
260
260
|
modifiers: Seq[Modifier.Reflect],
|
|
261
261
|
defaultValue: Option[A],
|
|
@@ -289,7 +289,7 @@ object DeriveShow extends Deriver[Show] {
|
|
|
289
289
|
override def deriveSequence[F[_, _], C[_], A](
|
|
290
290
|
element: Reflect[F, A],
|
|
291
291
|
typeId: TypeId[C[A]],
|
|
292
|
-
binding: Binding
|
|
292
|
+
binding: Binding.Seq[C, A],
|
|
293
293
|
doc: Doc,
|
|
294
294
|
modifiers: Seq[Modifier.Reflect],
|
|
295
295
|
defaultValue: Option[C[A]],
|
|
@@ -315,7 +315,7 @@ object DeriveShow extends Deriver[Show] {
|
|
|
315
315
|
key: Reflect[F, K],
|
|
316
316
|
value: Reflect[F, V],
|
|
317
317
|
typeId: TypeId[M[K, V]],
|
|
318
|
-
binding: Binding
|
|
318
|
+
binding: Binding.Map[M, K, V],
|
|
319
319
|
doc: Doc,
|
|
320
320
|
modifiers: Seq[Modifier.Reflect],
|
|
321
321
|
defaultValue: Option[M[K, V]],
|
|
@@ -341,7 +341,7 @@ object DeriveShow extends Deriver[Show] {
|
|
|
341
341
|
}
|
|
342
342
|
|
|
343
343
|
override def deriveDynamic[F[_, _]](
|
|
344
|
-
binding: Binding
|
|
344
|
+
binding: Binding.Dynamic,
|
|
345
345
|
doc: Doc,
|
|
346
346
|
modifiers: Seq[Modifier.Reflect],
|
|
347
347
|
defaultValue: Option[DynamicValue],
|
|
@@ -380,7 +380,7 @@ object DeriveShow extends Deriver[Show] {
|
|
|
380
380
|
override def deriveWrapper[F[_, _], A, B](
|
|
381
381
|
wrapped: Reflect[F, B],
|
|
382
382
|
typeId: TypeId[A],
|
|
383
|
-
binding: Binding
|
|
383
|
+
binding: Binding.Wrapper[A, B],
|
|
384
384
|
doc: Doc,
|
|
385
385
|
modifiers: Seq[Modifier.Reflect],
|
|
386
386
|
defaultValue: Option[A],
|
|
@@ -414,7 +414,7 @@ When the derivation process encounters a primitive type (e.g., `String`, `Int`),
|
|
|
414
414
|
def derivePrimitive[A](
|
|
415
415
|
primitiveType: PrimitiveType[A],
|
|
416
416
|
typeId: TypeId[A],
|
|
417
|
-
binding: Binding
|
|
417
|
+
binding: Binding.Primitive[A],
|
|
418
418
|
doc: Doc,
|
|
419
419
|
modifiers: Seq[Modifier.Reflect],
|
|
420
420
|
defaultValue: Option[A],
|
|
@@ -444,7 +444,7 @@ When the derivation process encounters a record type (e.g., a case class), it ca
|
|
|
444
444
|
def deriveRecord[F[_, _], A](
|
|
445
445
|
fields: IndexedSeq[Term[F, A, ?]],
|
|
446
446
|
typeId: TypeId[A],
|
|
447
|
-
binding: Binding
|
|
447
|
+
binding: Binding.Record[A],
|
|
448
448
|
doc: Doc,
|
|
449
449
|
modifiers: Seq[Modifier.Reflect],
|
|
450
450
|
defaultValue: Option[A],
|
|
@@ -512,7 +512,7 @@ When the derivation process encounters a variant type (e.g., a sealed trait with
|
|
|
512
512
|
def deriveVariant[F[_, _], A](
|
|
513
513
|
cases: IndexedSeq[Term[F, A, ?]],
|
|
514
514
|
typeId: TypeId[A],
|
|
515
|
-
binding: Binding
|
|
515
|
+
binding: Binding.Variant[A],
|
|
516
516
|
doc: Doc,
|
|
517
517
|
modifiers: Seq[Modifier.Reflect],
|
|
518
518
|
defaultValue: Option[A],
|
|
@@ -556,7 +556,7 @@ When the derivation process encounters a sequence type (e.g., `List[A]`), it cal
|
|
|
556
556
|
def deriveSequence[F[_, _], C[_], A](
|
|
557
557
|
element: Reflect[F, A],
|
|
558
558
|
typeId: TypeId[C[A]],
|
|
559
|
-
binding: Binding
|
|
559
|
+
binding: Binding.Seq[C, A],
|
|
560
560
|
doc: Doc,
|
|
561
561
|
modifiers: Seq[Modifier.Reflect],
|
|
562
562
|
defaultValue: Option[C[A]],
|
|
@@ -590,7 +590,7 @@ def deriveMap[F[_, _], M[_, _], K, V](
|
|
|
590
590
|
key: Reflect[F, K],
|
|
591
591
|
value: Reflect[F, V],
|
|
592
592
|
typeId: TypeId[M[K, V]],
|
|
593
|
-
binding: Binding
|
|
593
|
+
binding: Binding.Map[M, K, V],
|
|
594
594
|
doc: Doc,
|
|
595
595
|
modifiers: Seq[Modifier.Reflect],
|
|
596
596
|
defaultValue: Option[M[K, V]],
|
|
@@ -620,11 +620,11 @@ The derivation process for maps is similar to sequences, but we have two child i
|
|
|
620
620
|
|
|
621
621
|
### Dynamic Derivation
|
|
622
622
|
|
|
623
|
-
When the derivation process encounters a dynamic type (e.g., `DynamicValue`), it calls the `deriveDynamic` method of the `Deriver`. This method receives a `Binding
|
|
623
|
+
When the derivation process encounters a dynamic type (e.g., `DynamicValue`), it calls the `deriveDynamic` method of the `Deriver`. This method receives a `Binding.Dynamic` representing the dynamic type, along with other metadata such as documentation, modifiers, default values, and examples:
|
|
624
624
|
|
|
625
625
|
```scala
|
|
626
626
|
def deriveDynamic[F[_, _]](
|
|
627
|
-
binding: Binding
|
|
627
|
+
binding: Binding.Dynamic,
|
|
628
628
|
doc: Doc,
|
|
629
629
|
modifiers: Seq[Modifier.Reflect],
|
|
630
630
|
defaultValue: Option[DynamicValue],
|
|
@@ -667,7 +667,7 @@ When the derivation process encounters a wrapper type (e.g., a value class, opaq
|
|
|
667
667
|
def deriveWrapper[F[_, _], A, B](
|
|
668
668
|
wrapped: Reflect[F, B],
|
|
669
669
|
typeId: TypeId[A],
|
|
670
|
-
binding: Binding
|
|
670
|
+
binding: Binding.Wrapper[A, B],
|
|
671
671
|
doc: Doc,
|
|
672
672
|
modifiers: Seq[Modifier.Reflect],
|
|
673
673
|
defaultValue: Option[A],
|
|
@@ -875,7 +875,7 @@ object DeriveGen extends Deriver[Gen] {
|
|
|
875
875
|
override def derivePrimitive[A](
|
|
876
876
|
primitiveType: PrimitiveType[A],
|
|
877
877
|
typeId: TypeId[A],
|
|
878
|
-
binding: Binding
|
|
878
|
+
binding: Binding.Primitive[A],
|
|
879
879
|
doc: Doc,
|
|
880
880
|
modifiers: Seq[Modifier.Reflect],
|
|
881
881
|
defaultValue: Option[A],
|
|
@@ -913,7 +913,7 @@ object DeriveGen extends Deriver[Gen] {
|
|
|
913
913
|
override def deriveRecord[F[_, _], A](
|
|
914
914
|
fields: IndexedSeq[Term[F, A, ?]],
|
|
915
915
|
typeId: TypeId[A],
|
|
916
|
-
binding: Binding
|
|
916
|
+
binding: Binding.Record[A],
|
|
917
917
|
doc: Doc,
|
|
918
918
|
modifiers: Seq[Modifier.Reflect],
|
|
919
919
|
defaultValue: Option[A],
|
|
@@ -957,7 +957,7 @@ object DeriveGen extends Deriver[Gen] {
|
|
|
957
957
|
override def deriveVariant[F[_, _], A](
|
|
958
958
|
cases: IndexedSeq[Term[F, A, ?]],
|
|
959
959
|
typeId: TypeId[A],
|
|
960
|
-
binding: Binding
|
|
960
|
+
binding: Binding.Variant[A],
|
|
961
961
|
doc: Doc,
|
|
962
962
|
modifiers: Seq[Modifier.Reflect],
|
|
963
963
|
defaultValue: Option[A],
|
|
@@ -990,7 +990,7 @@ object DeriveGen extends Deriver[Gen] {
|
|
|
990
990
|
override def deriveSequence[F[_, _], C[_], A](
|
|
991
991
|
element: Reflect[F, A],
|
|
992
992
|
typeId: TypeId[C[A]],
|
|
993
|
-
binding: Binding
|
|
993
|
+
binding: Binding.Seq[C, A],
|
|
994
994
|
doc: Doc,
|
|
995
995
|
modifiers: Seq[Modifier.Reflect],
|
|
996
996
|
defaultValue: Option[C[A]],
|
|
@@ -1033,7 +1033,7 @@ object DeriveGen extends Deriver[Gen] {
|
|
|
1033
1033
|
key: Reflect[F, K],
|
|
1034
1034
|
value: Reflect[F, V],
|
|
1035
1035
|
typeId: TypeId[M[K, V]],
|
|
1036
|
-
binding: Binding
|
|
1036
|
+
binding: Binding.Map[M, K, V],
|
|
1037
1037
|
doc: Doc,
|
|
1038
1038
|
modifiers: Seq[Modifier.Reflect],
|
|
1039
1039
|
defaultValue: Option[M[K, V]],
|
|
@@ -1069,7 +1069,7 @@ object DeriveGen extends Deriver[Gen] {
|
|
|
1069
1069
|
* content.
|
|
1070
1070
|
*/
|
|
1071
1071
|
override def deriveDynamic[F[_, _]](
|
|
1072
|
-
binding: Binding
|
|
1072
|
+
binding: Binding.Dynamic,
|
|
1073
1073
|
doc: Doc,
|
|
1074
1074
|
modifiers: Seq[Modifier.Reflect],
|
|
1075
1075
|
defaultValue: Option[DynamicValue],
|
|
@@ -1124,7 +1124,7 @@ object DeriveGen extends Deriver[Gen] {
|
|
|
1124
1124
|
override def deriveWrapper[F[_, _], A, B](
|
|
1125
1125
|
wrapped: Reflect[F, B],
|
|
1126
1126
|
typeId: TypeId[A],
|
|
1127
|
-
binding: Binding
|
|
1127
|
+
binding: Binding.Wrapper[A, B],
|
|
1128
1128
|
doc: Doc,
|
|
1129
1129
|
modifiers: Seq[Modifier.Reflect],
|
|
1130
1130
|
defaultValue: Option[A],
|
|
@@ -1153,7 +1153,7 @@ The `derivePrimitive` method is responsible for deriving a `Gen` instance for pr
|
|
|
1153
1153
|
def derivePrimitive[A](
|
|
1154
1154
|
primitiveType: PrimitiveType[A],
|
|
1155
1155
|
typeId: TypeId[A],
|
|
1156
|
-
binding: Binding
|
|
1156
|
+
binding: Binding.Primitive[A],
|
|
1157
1157
|
doc: Doc,
|
|
1158
1158
|
modifiers: Seq[Modifier.Reflect],
|
|
1159
1159
|
defaultValue: Option[A],
|
|
@@ -1187,7 +1187,7 @@ The `deriveRecord` method is responsible for deriving a `Gen` instance for recor
|
|
|
1187
1187
|
def deriveRecord[F[_, _], A](
|
|
1188
1188
|
fields: IndexedSeq[Term[F, A, ?]],
|
|
1189
1189
|
typeId: TypeId[A],
|
|
1190
|
-
binding: Binding
|
|
1190
|
+
binding: Binding.Record[A],
|
|
1191
1191
|
doc: Doc,
|
|
1192
1192
|
modifiers: Seq[Modifier.Reflect],
|
|
1193
1193
|
defaultValue: Option[A],
|
|
@@ -1233,7 +1233,7 @@ The `deriveVariant` method is responsible for deriving a `Gen` instance for vari
|
|
|
1233
1233
|
def deriveVariant[F[_, _], A](
|
|
1234
1234
|
cases: IndexedSeq[Term[F, A, ?]],
|
|
1235
1235
|
typeId: TypeId[A],
|
|
1236
|
-
binding: Binding
|
|
1236
|
+
binding: Binding.Variant[A],
|
|
1237
1237
|
doc: Doc,
|
|
1238
1238
|
modifiers: Seq[Modifier.Reflect],
|
|
1239
1239
|
defaultValue: Option[A],
|
|
@@ -1268,7 +1268,7 @@ The `deriveSequence` method is responsible for deriving a `Gen` instance for seq
|
|
|
1268
1268
|
def deriveSequence[F[_, _], C[_], A](
|
|
1269
1269
|
element: Reflect[F, A],
|
|
1270
1270
|
typeId: TypeId[C[A]],
|
|
1271
|
-
binding: Binding
|
|
1271
|
+
binding: Binding.Seq[C, A],
|
|
1272
1272
|
doc: Doc,
|
|
1273
1273
|
modifiers: Seq[Modifier.Reflect],
|
|
1274
1274
|
defaultValue: Option[C[A]],
|
|
@@ -1313,7 +1313,7 @@ def deriveMap[F[_, _], M[_, _], K, V](
|
|
|
1313
1313
|
key: Reflect[F, K],
|
|
1314
1314
|
value: Reflect[F, V],
|
|
1315
1315
|
typeId: TypeId[M[K, V]],
|
|
1316
|
-
binding: Binding
|
|
1316
|
+
binding: Binding.Map[M, K, V],
|
|
1317
1317
|
doc: Doc,
|
|
1318
1318
|
modifiers: Seq[Modifier.Reflect],
|
|
1319
1319
|
defaultValue: Option[M[K, V]],
|
|
@@ -1352,7 +1352,7 @@ The `deriveDynamic` method is responsible for deriving a `Gen` instance for dyna
|
|
|
1352
1352
|
|
|
1353
1353
|
```scala
|
|
1354
1354
|
def deriveDynamic[F[_, _]](
|
|
1355
|
-
binding: Binding
|
|
1355
|
+
binding: Binding.Dynamic,
|
|
1356
1356
|
doc: Doc,
|
|
1357
1357
|
modifiers: Seq[Modifier.Reflect],
|
|
1358
1358
|
defaultValue: Option[DynamicValue],
|
|
@@ -1415,7 +1415,7 @@ The `deriveWrapper` method is responsible for deriving a `Gen` instance for wrap
|
|
|
1415
1415
|
def deriveWrapper[F[_, _], A, B](
|
|
1416
1416
|
wrapped: Reflect[F, B],
|
|
1417
1417
|
typeId: TypeId[A],
|
|
1418
|
-
binding: Binding
|
|
1418
|
+
binding: Binding.Wrapper[A, B],
|
|
1419
1419
|
doc: Doc,
|
|
1420
1420
|
modifiers: Seq[Modifier.Reflect],
|
|
1421
1421
|
defaultValue: Option[A],
|
|
@@ -1456,7 +1456,7 @@ Now we can use the derived `Gen[Person]` instance to generate random `Person` va
|
|
|
1456
1456
|
|
|
1457
1457
|
```scala
|
|
1458
1458
|
val random = new Random(42) // Seeded for reproducible output
|
|
1459
|
-
// random: Random = scala.util.Random@
|
|
1459
|
+
// random: Random = scala.util.Random@1d1d8934
|
|
1460
1460
|
|
|
1461
1461
|
Person.gen.generate(random)
|
|
1462
1462
|
// res14: Person = Person(name = "p", age = -1360544799)
|
package/ringbuffer.md
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: ringbuffer
|
|
3
|
+
title: "Ring Buffer"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ZIO Blocks — Ring Buffer (`zio.blocks.ringbuffer`)
|
|
7
|
+
|
|
8
|
+
`zio.blocks.ringbuffer` is a family of **high-performance, bounded ring buffers** for the JVM (and Scala.js). Each variant is optimized for a specific producer/consumer threading pattern—pick the one that matches your use case and get the fastest possible inter-thread communication with zero dependencies.
|
|
9
|
+
|
|
10
|
+
All variants expose `offer` (returns `false` if full) and `take` (returns `null` if empty)—both non-blocking. Capacity must be a **power of two** (enables bitwise masking instead of modulo). Elements must be **non-null reference types** (`A <: AnyRef`).
|
|
11
|
+
|
|
12
|
+
## Ring buffer variants
|
|
13
|
+
|
|
14
|
+
| Type | Producers | Consumers | Algorithm |
|
|
15
|
+
|------|-----------|-----------|-----------|
|
|
16
|
+
| `SpscRingBuffer` | 1 | 1 | FastFlow (null/non-null signaling) |
|
|
17
|
+
| `SpmcRingBuffer` | 1 | Many | Index-based capacity + CAS consumers |
|
|
18
|
+
| `MpscRingBuffer` | Many | 1 | CAS producers + relaxed poll |
|
|
19
|
+
| `MpmcRingBuffer` | Many | Many | Vyukov/Dmitry sequence buffer |
|
|
20
|
+
|
|
21
|
+
**Naming convention:** `S` = single, `M` = multi, `p` = producer, `c` = consumer.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```scala
|
|
28
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-ringbuffer" % "0.0.29"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Quick start
|
|
34
|
+
|
|
35
|
+
```scala
|
|
36
|
+
import zio.blocks.ringbuffer.SpscRingBuffer
|
|
37
|
+
|
|
38
|
+
val buf = SpscRingBuffer[String](1024) // capacity must be a power of 2
|
|
39
|
+
|
|
40
|
+
// Producer thread
|
|
41
|
+
buf.offer("hello") // true if inserted, false if full
|
|
42
|
+
|
|
43
|
+
// Consumer thread
|
|
44
|
+
val msg: String = buf.take() // element or null if empty
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## API
|
|
50
|
+
|
|
51
|
+
Every ring buffer provides:
|
|
52
|
+
|
|
53
|
+
```scala
|
|
54
|
+
def offer(a: A): Boolean // insert; returns false if full
|
|
55
|
+
def take(): A // remove; returns null if empty
|
|
56
|
+
def size: Int // approximate element count
|
|
57
|
+
def isEmpty: Boolean // approximate emptiness check
|
|
58
|
+
def isFull: Boolean // approximate fullness check
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`SpscRingBuffer` additionally provides batch operations:
|
|
62
|
+
|
|
63
|
+
```scala
|
|
64
|
+
def drain(consumer: A => Unit, limit: Int): Int // drain up to limit elements
|
|
65
|
+
def fill(supplier: () => A, limit: Int): Int // fill up to limit slots
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
All `size`/`isEmpty`/`isFull` values are **approximate** under concurrency—they are snapshots that may be stale by the time the caller acts on them.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Choosing a variant
|
|
73
|
+
|
|
74
|
+
Use the most constrained variant that fits your threading model:
|
|
75
|
+
|
|
76
|
+
| Scenario | Recommended |
|
|
77
|
+
|----------|-------------|
|
|
78
|
+
| Dedicated pipeline: one writer thread, one reader thread | `SpscRingBuffer` |
|
|
79
|
+
| Fan-in: many writers, one reader (e.g., logging, event aggregation) | `MpscRingBuffer` |
|
|
80
|
+
| Fan-out: one writer, many readers (e.g., work distribution) | `SpmcRingBuffer` |
|
|
81
|
+
| General purpose: any number of writers and readers | `MpmcRingBuffer` |
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Usage examples
|
|
86
|
+
|
|
87
|
+
### SPSC with drain/fill
|
|
88
|
+
|
|
89
|
+
```scala
|
|
90
|
+
import zio.blocks.ringbuffer.SpscRingBuffer
|
|
91
|
+
|
|
92
|
+
val buf = SpscRingBuffer[java.lang.Integer](64)
|
|
93
|
+
|
|
94
|
+
// Producer: batch-fill from a data source
|
|
95
|
+
var seq = 0
|
|
96
|
+
val filled = buf.fill(() => { seq += 1; Integer.valueOf(seq) }, 32)
|
|
97
|
+
println(s"Filled $filled elements")
|
|
98
|
+
|
|
99
|
+
// Consumer: batch-drain into a processor
|
|
100
|
+
val drained = buf.drain(e => println(s"Processing $e"), 32)
|
|
101
|
+
println(s"Drained $drained elements")
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### MPSC fan-in (multiple producers, single consumer)
|
|
105
|
+
|
|
106
|
+
```scala
|
|
107
|
+
import zio.blocks.ringbuffer.MpscRingBuffer
|
|
108
|
+
|
|
109
|
+
val buf = MpscRingBuffer[String](256)
|
|
110
|
+
|
|
111
|
+
// Multiple producer threads
|
|
112
|
+
for (i <- 0 until 4) {
|
|
113
|
+
new Thread(() => {
|
|
114
|
+
for (j <- 0 until 100)
|
|
115
|
+
buf.offer(s"producer-$i: message-$j")
|
|
116
|
+
}).start()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Single consumer thread
|
|
120
|
+
new Thread(() => {
|
|
121
|
+
var msg = buf.take()
|
|
122
|
+
while (msg != null) {
|
|
123
|
+
println(msg)
|
|
124
|
+
msg = buf.take()
|
|
125
|
+
}
|
|
126
|
+
}).start()
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### SPMC fan-out (single producer, multiple consumers)
|
|
130
|
+
|
|
131
|
+
```scala
|
|
132
|
+
import zio.blocks.ringbuffer.SpmcRingBuffer
|
|
133
|
+
|
|
134
|
+
val buf = SpmcRingBuffer[String](256)
|
|
135
|
+
|
|
136
|
+
// Single producer thread
|
|
137
|
+
new Thread(() => {
|
|
138
|
+
for (i <- 0 until 400)
|
|
139
|
+
while (!buf.offer(s"task-$i")) {} // retry if full
|
|
140
|
+
}).start()
|
|
141
|
+
|
|
142
|
+
// Multiple consumer (worker) threads
|
|
143
|
+
for (w <- 0 until 4) {
|
|
144
|
+
new Thread(() => {
|
|
145
|
+
var msg = buf.take()
|
|
146
|
+
while (msg != null) {
|
|
147
|
+
println(s"worker-$w: $msg")
|
|
148
|
+
msg = buf.take()
|
|
149
|
+
}
|
|
150
|
+
}).start()
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Non-blocking try-once with fallback
|
|
155
|
+
|
|
156
|
+
```scala
|
|
157
|
+
import zio.blocks.ringbuffer.MpmcRingBuffer
|
|
158
|
+
|
|
159
|
+
val buf = MpmcRingBuffer[String](64)
|
|
160
|
+
|
|
161
|
+
// Try to offer without blocking; handle backpressure yourself
|
|
162
|
+
if (!buf.offer("data")) {
|
|
163
|
+
// buffer is full — drop, log, or retry later
|
|
164
|
+
println("Buffer full, applying backpressure")
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Try to take without blocking
|
|
168
|
+
val result = buf.take()
|
|
169
|
+
if (result != null) {
|
|
170
|
+
println(s"Got: $result")
|
|
171
|
+
} else {
|
|
172
|
+
// buffer is empty — do other work
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Design notes
|
|
179
|
+
|
|
180
|
+
### FastFlow pattern (SPSC)
|
|
181
|
+
|
|
182
|
+
`SpscRingBuffer` uses the FastFlow algorithm: the **null/non-null state of an array slot** is the synchronization signal. The producer never reads `consumerIndex`; the consumer never reads `producerIndex`. This minimizes cross-core cache traffic to a single cache line per operation.
|
|
183
|
+
|
|
184
|
+
A **look-ahead step** (`capacity/4`, capped at 4096) lets the producer batch-check multiple future slots at once, further reducing the frequency of slow-path reads.
|
|
185
|
+
|
|
186
|
+
### Vyukov/Dmitry sequence buffer (MPMC)
|
|
187
|
+
|
|
188
|
+
`MpmcRingBuffer` uses a parallel `Long` sequence buffer alongside the data array. Each slot carries a stamp indicating whether it is available for writing or reading. Both `producerIndex` and `consumerIndex` are advanced via CAS, allowing any number of threads on both sides. The minimum capacity is 2 (the algorithm requires at least 2 slots to distinguish written from consumed).
|
|
189
|
+
|
|
190
|
+
### CAS-based producers (MPSC)
|
|
191
|
+
|
|
192
|
+
`MpscRingBuffer` follows the JCTools `MpscArrayQueue` design: producers claim a slot via CAS on `producerIndex`, then write the element with release semantics. A cached `producerLimit` avoids reading `consumerIndex` on every offer, reducing cross-core traffic. The consumer side uses relaxed poll semantics—a `null` slot means either empty or a producer mid-write.
|
|
193
|
+
|
|
194
|
+
### Index-based SPMC
|
|
195
|
+
|
|
196
|
+
`SpmcRingBuffer` uses index-based capacity checking on the producer side (no CAS needed for a single producer). Consumers use a CAS loop on `consumerIndex`. Consumers read the element *before* the CAS to avoid a race with the producer overwriting the slot. Consumers do not null array slots after reading—the producer overwrites them on the next lap.
|
|
197
|
+
|
|
198
|
+
### Cache-line padding
|
|
199
|
+
|
|
200
|
+
All ring buffers use **128-byte padding regions** (16 `Long` fields) between producer and consumer fields. This prevents false sharing on all architectures, including Apple Silicon which uses 128-byte cache lines (most x86 CPUs use 64-byte lines).
|
|
201
|
+
|
|
202
|
+
The padding is implemented via a class hierarchy:
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
Pad0 → ProducerFields → Pad1 → ConsumerFields → Pad2
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### VarHandle for memory ordering
|
|
209
|
+
|
|
210
|
+
All JVM implementations use `java.lang.invoke.VarHandle` (Java 9+) for acquire/release semantics instead of `sun.misc.Unsafe`. This is the recommended modern approach for lock-free data structures on the JVM.
|
|
211
|
+
|
|
212
|
+
### Power-of-two masking
|
|
213
|
+
|
|
214
|
+
Capacity must be a power of two. This allows `index & (capacity - 1)` instead of `index % capacity`, which is significantly faster because bitwise AND compiles to a single CPU instruction.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Thread-safety contract
|
|
219
|
+
|
|
220
|
+
Violating the threading contract (e.g., calling `take` from multiple threads on an `SpscRingBuffer`) results in **undefined behavior**. No runtime check is performed—this is enforced by contract for maximum performance.
|
|
221
|
+
|
|
222
|
+
| Type | `offer` | `take` |
|
|
223
|
+
|------|---------|--------|
|
|
224
|
+
| `SpscRingBuffer` | Single producer thread only | Single consumer thread only |
|
|
225
|
+
| `SpmcRingBuffer` | Single producer thread only | Any number of consumer threads |
|
|
226
|
+
| `MpscRingBuffer` | Any number of producer threads | Single consumer thread only |
|
|
227
|
+
| `MpmcRingBuffer` | Any number of producer threads | Any number of consumer threads |
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Performance characteristics
|
|
232
|
+
|
|
233
|
+
| Operation | Complexity |
|
|
234
|
+
|-----------|-----------|
|
|
235
|
+
| `offer` | Lock-free (SPSC/SPMC: wait-free) |
|
|
236
|
+
| `take` | Lock-free (SPSC/MPSC: wait-free) |
|
|
237
|
+
|
|
238
|
+
**SPSC** is the fastest: no CAS, no locks, minimal cache-line traffic. Use it whenever your threading model allows a dedicated producer-consumer pair.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Cross-platform support
|
|
243
|
+
|
|
244
|
+
On **Scala.js**, all ring buffer types compile and provide the same API surface. Since Scala.js is single-threaded, the JS implementations use plain reads and writes with no memory ordering primitives.
|
|
245
|
+
|
|
246
|
+
| Platform | Support |
|
|
247
|
+
|----------|---------|
|
|
248
|
+
| JVM | Full concurrency support |
|
|
249
|
+
| Scala.js | Sequential (same API) |
|
package/sidebars.js
CHANGED
|
@@ -13,7 +13,6 @@ const sidebars = {
|
|
|
13
13
|
"reference/binding-resolver",
|
|
14
14
|
"reference/registers",
|
|
15
15
|
"reference/typeid",
|
|
16
|
-
"reference/allows",
|
|
17
16
|
"reference/modifier",
|
|
18
17
|
"reference/dynamic-value",
|
|
19
18
|
"reference/dynamic-schema",
|
|
@@ -40,14 +39,27 @@ const sidebars = {
|
|
|
40
39
|
]
|
|
41
40
|
},
|
|
42
41
|
"reference/context",
|
|
43
|
-
|
|
42
|
+
{
|
|
43
|
+
type: "category",
|
|
44
|
+
label: "Resource Management & DI",
|
|
45
|
+
link: { type: "doc", id: "reference/resource-management-di/index" },
|
|
46
|
+
items: [
|
|
47
|
+
"reference/resource-management-di/resource",
|
|
48
|
+
"reference/resource-management-di/scope",
|
|
49
|
+
"reference/resource-management-di/wire",
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
"reference/combinators",
|
|
44
53
|
"reference/docs",
|
|
45
54
|
"reference/json",
|
|
46
55
|
"reference/json-patch",
|
|
56
|
+
"reference/json-differ",
|
|
47
57
|
"reference/json-schema",
|
|
48
58
|
"reference/xml",
|
|
49
59
|
"reference/syntax",
|
|
50
60
|
"reference/media-type",
|
|
61
|
+
"reference/http-model",
|
|
62
|
+
"ringbuffer",
|
|
51
63
|
]
|
|
52
64
|
},
|
|
53
65
|
{
|