@zio.dev/zio-blocks 0.0.33 → 0.0.55
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/adr/2026-07-18-data-migration.md +123 -0
- package/guides/async-getting-started.md +687 -0
- package/guides/compile-time-resource-safety-with-scope.md +21 -16
- package/guides/getting-started-with-mux.md +1395 -0
- package/guides/query-dsl-extending.md +161 -102
- package/guides/query-dsl-fluent-builder.md +217 -157
- package/guides/query-dsl-reified-optics.md +12 -10
- package/guides/query-dsl-sql.md +640 -165
- package/guides/sql-checked-interpolation.md +173 -0
- package/guides/sql-transactions.md +286 -0
- package/guides/telemetry-guide.md +1130 -0
- package/guides/zio-schema-migration.md +29 -22
- package/index.md +248 -389
- package/package.json +1 -1
- package/plans/config-follow-up-prs.md +188 -0
- package/plans/config-pr-assessment-roadmap.md +310 -0
- package/reference/MuxDataFlow.jsx +250 -0
- package/reference/async.md +1499 -0
- package/reference/chunk.md +3533 -308
- package/reference/codegen/case-class.md +436 -0
- package/reference/codegen/emitter-config.md +383 -0
- package/reference/codegen/examples.md +664 -0
- package/reference/codegen/field.md +316 -0
- package/reference/codegen/index.md +317 -0
- package/reference/codegen/scala-emitter.md +392 -0
- package/reference/codegen/scala-file.md +276 -0
- package/reference/codegen/sealed-trait.md +408 -0
- package/reference/codegen/type-definition.md +340 -0
- package/reference/codegen/type-ref.md +201 -0
- package/reference/combinators.md +347 -117
- package/reference/config/config-decoder.md +460 -0
- package/reference/config/config-source.md +489 -0
- package/reference/config/errors.md +278 -0
- package/reference/config/flags.md +369 -0
- package/reference/config/formats.md +314 -0
- package/reference/config/index.md +304 -0
- package/reference/config/rollout.md +336 -0
- package/reference/context.md +9 -52
- package/reference/data-migration.md +269 -0
- package/reference/datastar/attributes.md +302 -0
- package/reference/datastar/events.md +234 -0
- package/reference/datastar/index.md +256 -0
- package/reference/datastar/signals.md +230 -0
- package/reference/datastar/sse.md +295 -0
- package/reference/datastar.md +346 -0
- package/reference/docs.md +1461 -345
- package/reference/endpoint/auth-type.md +146 -0
- package/reference/endpoint/bulk-creation.md +96 -0
- package/reference/endpoint/endpoint.md +297 -0
- package/reference/endpoint/http-codec.md +249 -0
- package/reference/endpoint/index.md +745 -0
- package/reference/endpoint/path-codec.md +225 -0
- package/reference/endpoint/route-pattern.md +194 -0
- package/reference/endpoint/route-tree.md +111 -0
- package/reference/endpoint/segment-codec.md +199 -0
- package/reference/html.md +1424 -0
- package/reference/htmx/attribute-values.md +359 -0
- package/reference/htmx/hx-encoding.md +111 -0
- package/reference/htmx/hx-params.md +204 -0
- package/reference/htmx/hx-swap.md +276 -0
- package/reference/htmx/hx-sync.md +251 -0
- package/reference/htmx/hx-target.md +314 -0
- package/reference/htmx/hx-trigger.md +457 -0
- package/reference/htmx/hx-url-update.md +239 -0
- package/reference/htmx/index.md +807 -0
- package/reference/htmx/response-headers.md +240 -0
- package/reference/http-model/headers.md +735 -0
- package/reference/http-model/index.md +49 -0
- package/reference/http-model/model.md +1517 -0
- package/reference/http-model/schema-codecs.md +522 -0
- package/reference/http-model/schema.md +750 -0
- package/reference/http-model/server-sent-event.md +341 -0
- package/reference/jwt.md +195 -0
- package/reference/maybe.md +943 -0
- package/reference/media-type.md +2 -2
- package/reference/mux.md +254 -0
- package/reference/mux.mdx +828 -0
- package/reference/openapi.md +1351 -0
- package/reference/projection.md +654 -0
- package/reference/resource-management/defer-handle.md +1 -1
- package/reference/resource-management/resource.md +31 -98
- package/reference/resource-management/scope.md +28 -220
- package/reference/resource-management/wire.md +5 -55
- package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
- package/reference/ringbuffer/MpscDiagram.jsx +618 -0
- package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
- package/reference/ringbuffer/SpscDiagram.jsx +677 -0
- package/reference/ringbuffer/advanced.mdx +109 -0
- package/reference/ringbuffer/index.mdx +145 -0
- package/reference/ringbuffer/mpmc.mdx +185 -0
- package/reference/ringbuffer/mpsc.mdx +164 -0
- package/reference/ringbuffer/spmc.mdx +108 -0
- package/reference/ringbuffer/spsc.mdx +416 -0
- package/reference/{allows.md → schema/allows.md} +4 -100
- package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
- package/reference/{binding.md → schema/binding.md} +3 -4
- package/reference/schema/built-in-codecs/avro.md +451 -0
- package/reference/schema/built-in-codecs/bson.md +510 -0
- package/reference/schema/built-in-codecs/csv.md +564 -0
- package/reference/schema/built-in-codecs/index.md +77 -0
- package/reference/schema/built-in-codecs/json/index.md +295 -0
- package/reference/schema/built-in-codecs/json/json-config.md +217 -0
- package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
- package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
- package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
- package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
- package/reference/schema/built-in-codecs/messagepack.md +508 -0
- package/reference/schema/built-in-codecs/thrift.md +433 -0
- package/reference/schema/built-in-codecs/toon.md +1078 -0
- package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
- package/reference/schema/built-in-codecs/yaml.md +552 -0
- package/reference/{codec.md → schema/codec.md} +11 -11
- package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +196 -5
- package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
- package/reference/schema/format.md +92 -0
- package/reference/schema/index.md +52 -0
- package/reference/schema/migration.md +297 -0
- package/reference/{modifier.md → schema/modifier.md} +58 -7
- package/reference/{optics.md → schema/optics.md} +2 -2
- package/reference/{patch.md → schema/patch.md} +1 -1
- package/{path-interpolator.md → reference/schema/path-interpolator.md} +167 -72
- package/reference/schema/reflect-transformer.md +140 -0
- package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
- package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
- package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
- package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
- package/reference/schema/schema-search.md +263 -0
- package/reference/{schema.md → schema/schema.md} +22 -2
- package/reference/{structural-types.md → schema/structural-types.md} +1 -1
- package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
- package/reference/smithy.md +1032 -0
- package/reference/sql/db-codec-deriver.md +71 -0
- package/reference/sql/db-codec.md +687 -0
- package/reference/sql/db-con.md +271 -0
- package/reference/sql/db-connection.md +153 -0
- package/reference/sql/db-param-writer.md +77 -0
- package/reference/sql/db-param.md +66 -0
- package/reference/sql/db-result-reader.md +148 -0
- package/reference/sql/db-tx.md +114 -0
- package/reference/sql/db-value.md +41 -0
- package/reference/sql/ddl.md +85 -0
- package/reference/sql/frag.md +288 -0
- package/reference/sql/index.md +341 -0
- package/reference/sql/repo.md +600 -0
- package/reference/sql/sql-dialect.md +73 -0
- package/reference/sql/sql-logger.md +62 -0
- package/reference/sql/sql-name-mapper.md +70 -0
- package/reference/sql/table-metadata.md +134 -0
- package/reference/sql/table.md +448 -0
- package/reference/sql/transactor-zio.md +399 -0
- package/reference/sql/transactor.md +363 -0
- package/reference/sql-zio.md +112 -0
- package/reference/streams/core/index.md +32 -0
- package/reference/streams/core/pipeline.md +854 -0
- package/reference/streams/core/sink.md +1404 -0
- package/reference/streams/core/stream.md +3236 -0
- package/reference/streams/execution-and-compatibility/async-execution.md +822 -0
- package/reference/streams/execution-and-compatibility/index.md +35 -0
- package/reference/streams/execution-and-compatibility/platform-differences.md +297 -0
- package/reference/streams/execution-and-compatibility/scala-2-compatibility.md +88 -0
- package/reference/streams/execution-and-compatibility/zero-boxing.md +393 -0
- package/reference/streams/index.md +726 -0
- package/reference/streams/primitives/index.md +30 -0
- package/reference/streams/primitives/reader.md +1992 -0
- package/reference/streams/primitives/writer.md +1201 -0
- package/reference/telemetry/common/any-value.md +90 -0
- package/reference/telemetry/common/attribute-key.md +87 -0
- package/reference/telemetry/common/attributes.md +118 -0
- package/reference/telemetry/common/index.md +39 -0
- package/reference/telemetry/common/instrumentation-scope.md +24 -0
- package/reference/telemetry/common/resource.md +34 -0
- package/reference/telemetry/index.md +311 -0
- package/reference/telemetry/logging/index.md +197 -0
- package/reference/telemetry/logging/log-enrichment.md +72 -0
- package/reference/telemetry/logging/log-formatter.md +100 -0
- package/reference/telemetry/logging/log-record-processor.md +56 -0
- package/reference/telemetry/logging/log-record.md +44 -0
- package/reference/telemetry/logging/log-writer.md +64 -0
- package/reference/telemetry/logging/logger-provider.md +142 -0
- package/reference/telemetry/logging/logger.md +83 -0
- package/reference/telemetry/logging/severity.md +62 -0
- package/reference/telemetry/metrics/index.md +150 -0
- package/reference/telemetry/metrics/instruments.md +183 -0
- package/reference/telemetry/metrics/labeled-instruments.md +74 -0
- package/reference/telemetry/metrics/meter-provider.md +76 -0
- package/reference/telemetry/metrics/meter.md +98 -0
- package/reference/telemetry/metrics/metric-data.md +57 -0
- package/reference/telemetry/otel/custom-exporter.md +216 -0
- package/reference/telemetry/otel/index.md +212 -0
- package/reference/telemetry/tracing/index.md +155 -0
- package/reference/telemetry/tracing/sampler.md +89 -0
- package/reference/telemetry/tracing/span-builder.md +57 -0
- package/reference/telemetry/tracing/span-context.md +39 -0
- package/reference/telemetry/tracing/span-data.md +32 -0
- package/reference/telemetry/tracing/span-kind.md +55 -0
- package/reference/telemetry/tracing/span-processor.md +53 -0
- package/reference/telemetry/tracing/span-status.md +47 -0
- package/reference/telemetry/tracing/span.md +117 -0
- package/reference/telemetry/tracing/tracer-provider.md +91 -0
- package/reference/telemetry/tracing/tracer.md +52 -0
- package/reference/typeid.md +5 -83
- package/sidebars.js +376 -43
- package/undocumented-report.md +528 -270
- package/reference/formats.md +0 -694
- package/reference/http-model.md +0 -1716
- package/reference/streams.md +0 -989
- package/ringbuffer.md +0 -249
- /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
- /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
- /package/reference/{lazy.md → schema/lazy.md} +0 -0
- /package/reference/{reflect.md → schema/reflect.md} +0 -0
- /package/reference/{registers.md → schema/registers.md} +0 -0
- /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
- /package/reference/{syntax.md → schema/syntax.md} +0 -0
- /package/reference/{validation.md → schema/validation.md} +0 -0
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
import React, { useState, useCallback, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
const CAP = 4;
|
|
4
|
+
const MASK = CAP - 1;
|
|
5
|
+
|
|
6
|
+
const COLOR = {
|
|
7
|
+
write: "#1D9E75",
|
|
8
|
+
read: "#378ADD",
|
|
9
|
+
fail: "#E24B4A",
|
|
10
|
+
neutral: "#888780",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function initState() {
|
|
14
|
+
return {
|
|
15
|
+
buf: Array(CAP).fill(null),
|
|
16
|
+
pIdx: 0,
|
|
17
|
+
cIdx: 0,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ── Prose description ─────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
function stepDescription(calc) {
|
|
24
|
+
if (!calc) return null;
|
|
25
|
+
|
|
26
|
+
if (calc.op === "offer") {
|
|
27
|
+
const full = calc.size >= CAP;
|
|
28
|
+
|
|
29
|
+
if (full) {
|
|
30
|
+
return {
|
|
31
|
+
color: COLOR.fail,
|
|
32
|
+
paragraphs: [
|
|
33
|
+
`The producer reads pIdx = ${calc.pIdx} and cIdx = ${calc.cIdx}. ` +
|
|
34
|
+
`The difference pIdx − cIdx = ${calc.size} equals the capacity (${CAP}), ` +
|
|
35
|
+
`which means every slot in the buffer is currently occupied.`,
|
|
36
|
+
|
|
37
|
+
`Because the buffer is full, the producer cannot claim a slot. ` +
|
|
38
|
+
`offer() returns false immediately and the buffer state is unchanged.`,
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
} else {
|
|
42
|
+
return {
|
|
43
|
+
color: COLOR.write,
|
|
44
|
+
paragraphs: [
|
|
45
|
+
`The producer reads pIdx = ${calc.pIdx} and cIdx = ${calc.cIdx}. ` +
|
|
46
|
+
`The difference pIdx − cIdx = ${calc.size}, which is less than the capacity (${CAP}), ` +
|
|
47
|
+
`so there is at least one free slot.`,
|
|
48
|
+
|
|
49
|
+
`The target slot is computed as pIdx & mask = ${calc.pIdx} & ${MASK} = ${calc.slot}. ` +
|
|
50
|
+
`The producer performs a CAS to advance pIdx from ${calc.pIdx} to ${calc.pIdx + 1}, ` +
|
|
51
|
+
`claiming that slot exclusively. It then writes the element into buf[${calc.slot}] ` +
|
|
52
|
+
`with release semantics so the consumer can observe it. offer() returns true.`,
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
const empty = calc.value === null;
|
|
58
|
+
|
|
59
|
+
if (empty) {
|
|
60
|
+
return {
|
|
61
|
+
color: COLOR.fail,
|
|
62
|
+
paragraphs: [
|
|
63
|
+
`The consumer reads cIdx = ${calc.cIdx} with a plain load — no CAS is needed ` +
|
|
64
|
+
`because only one thread ever consumes. ` +
|
|
65
|
+
`The source slot is computed as cIdx & mask = ${calc.cIdx} & ${MASK} = ${calc.slot}.`,
|
|
66
|
+
|
|
67
|
+
`An acquire-read of buf[${calc.slot}] returns null. ` +
|
|
68
|
+
`A null slot means either the buffer is empty, or a producer has already ` +
|
|
69
|
+
`claimed the slot via CAS but has not yet written the element (mid-write). ` +
|
|
70
|
+
`In both cases the consumer cannot proceed — take() returns null and cIdx stays at ${calc.cIdx}.`,
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
} else {
|
|
74
|
+
return {
|
|
75
|
+
color: COLOR.read,
|
|
76
|
+
paragraphs: [
|
|
77
|
+
`The consumer reads cIdx = ${calc.cIdx} with a plain load — no CAS is needed ` +
|
|
78
|
+
`because only one thread ever consumes. ` +
|
|
79
|
+
`The source slot is computed as cIdx & mask = ${calc.cIdx} & ${MASK} = ${calc.slot}.`,
|
|
80
|
+
|
|
81
|
+
`An acquire-read of buf[${calc.slot}] returns "${calc.value}", confirming the slot ` +
|
|
82
|
+
`holds a fully written element. The consumer clears buf[${calc.slot}] to null ` +
|
|
83
|
+
`(releasing the slot for future producers) and advances cIdx from ${calc.cIdx} to ${calc.cIdx + 1} ` +
|
|
84
|
+
`with release semantics. take() returns "${calc.value}".`,
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function StepDescription({ calc }) {
|
|
92
|
+
const desc = stepDescription(calc);
|
|
93
|
+
|
|
94
|
+
if (!desc) return (
|
|
95
|
+
<div style={{
|
|
96
|
+
margin: "6px 0 4px", borderRadius: 8,
|
|
97
|
+
border: "1px solid #e8e6df", background: "#f5f4f0",
|
|
98
|
+
padding: "10px 14px", fontSize: 12,
|
|
99
|
+
color: "#ccc", textAlign: "center", fontStyle: "italic",
|
|
100
|
+
}}>
|
|
101
|
+
step summary will appear here after each operation
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div style={{
|
|
107
|
+
margin: "6px 0 4px", borderRadius: 8,
|
|
108
|
+
border: `1px solid ${desc.color}22`,
|
|
109
|
+
background: `${desc.color}08`,
|
|
110
|
+
padding: "10px 14px",
|
|
111
|
+
}}>
|
|
112
|
+
{desc.paragraphs.map((p, i) => (
|
|
113
|
+
<p key={i} style={{
|
|
114
|
+
margin: i === 0 ? "0 0 6px" : "0",
|
|
115
|
+
fontSize: 12.5, lineHeight: 1.65,
|
|
116
|
+
color: "#444",
|
|
117
|
+
}}>
|
|
118
|
+
{p}
|
|
119
|
+
</p>
|
|
120
|
+
))}
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── Algorithm trace panel ─────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
function CalcPanel({ calc }) {
|
|
128
|
+
if (!calc) return (
|
|
129
|
+
<div style={{
|
|
130
|
+
margin: "6px 0 4px", borderRadius: 8,
|
|
131
|
+
border: "1px solid #e8e6df", background: "#f5f4f0",
|
|
132
|
+
padding: "10px 14px", fontSize: 11,
|
|
133
|
+
fontFamily: "monospace", color: "#ccc", textAlign: "center",
|
|
134
|
+
}}>
|
|
135
|
+
algorithm trace will appear here after each operation
|
|
136
|
+
</div>
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const isOffer = calc.op === "offer";
|
|
140
|
+
const opColor = isOffer ? COLOR.write : COLOR.read;
|
|
141
|
+
|
|
142
|
+
let rows;
|
|
143
|
+
let decisionColor;
|
|
144
|
+
let decisionText;
|
|
145
|
+
|
|
146
|
+
if (isOffer) {
|
|
147
|
+
const full = calc.size >= CAP;
|
|
148
|
+
rows = [
|
|
149
|
+
{
|
|
150
|
+
label: "pIdx",
|
|
151
|
+
expr: "pIdx",
|
|
152
|
+
val: String(calc.pIdx),
|
|
153
|
+
note: "monotonic producer counter (never resets)",
|
|
154
|
+
hi: false,
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
label: "cIdx",
|
|
158
|
+
expr: "cIdx",
|
|
159
|
+
val: String(calc.cIdx),
|
|
160
|
+
note: "monotonic consumer counter",
|
|
161
|
+
hi: false,
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
label: "size",
|
|
165
|
+
expr: "pIdx − cIdx",
|
|
166
|
+
val: String(calc.size),
|
|
167
|
+
note: `${calc.pIdx} − ${calc.cIdx} = ${calc.size} (current occupancy)`,
|
|
168
|
+
hi: true,
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
label: "slot",
|
|
172
|
+
expr: "pIdx & mask",
|
|
173
|
+
val: String(calc.slot),
|
|
174
|
+
note: `${calc.pIdx} & ${MASK} = ${calc.slot} (bitmask replaces mod)`,
|
|
175
|
+
hi: false,
|
|
176
|
+
},
|
|
177
|
+
];
|
|
178
|
+
decisionColor = full ? COLOR.fail : COLOR.write;
|
|
179
|
+
decisionText = full
|
|
180
|
+
? `✗ size == cap → buffer FULL → return false`
|
|
181
|
+
: `✓ size < cap → CAS pIdx ${calc.pIdx}→${calc.pIdx + 1}, write buf[${calc.slot}] → return true`;
|
|
182
|
+
} else {
|
|
183
|
+
const empty = calc.value === null;
|
|
184
|
+
rows = [
|
|
185
|
+
{
|
|
186
|
+
label: "cIdx",
|
|
187
|
+
expr: "cIdx",
|
|
188
|
+
val: String(calc.cIdx),
|
|
189
|
+
note: "plain read — only one consumer thread",
|
|
190
|
+
hi: false,
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
label: "slot",
|
|
194
|
+
expr: "cIdx & mask",
|
|
195
|
+
val: String(calc.slot),
|
|
196
|
+
note: `${calc.cIdx} & ${MASK} = ${calc.slot} (bitmask replaces mod)`,
|
|
197
|
+
hi: false,
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
label: "value",
|
|
201
|
+
expr: "buf[slot]",
|
|
202
|
+
val: calc.value !== null ? `"${calc.value}"` : "null",
|
|
203
|
+
note: calc.value !== null
|
|
204
|
+
? `buf[${calc.slot}] = "${calc.value}" (acquire-read — element is ready)`
|
|
205
|
+
: `buf[${calc.slot}] = null (empty or producer mid-write)`,
|
|
206
|
+
hi: true,
|
|
207
|
+
},
|
|
208
|
+
];
|
|
209
|
+
decisionColor = empty ? COLOR.fail : COLOR.read;
|
|
210
|
+
decisionText = empty
|
|
211
|
+
? `✗ value == null → buffer EMPTY → return null`
|
|
212
|
+
: `✓ value ≠ null → clear buf[${calc.slot}], cIdx ${calc.cIdx}→${calc.cIdx + 1} → return "${calc.value}"`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<div style={{
|
|
217
|
+
margin: "6px 0 0", borderRadius: 8,
|
|
218
|
+
border: `1px solid ${opColor}30`,
|
|
219
|
+
overflow: "hidden", fontSize: 12,
|
|
220
|
+
}}>
|
|
221
|
+
{/* Header */}
|
|
222
|
+
<div style={{
|
|
223
|
+
background: `${opColor}14`, padding: "5px 12px",
|
|
224
|
+
display: "flex", alignItems: "center", gap: 8,
|
|
225
|
+
borderBottom: `1px solid ${opColor}20`,
|
|
226
|
+
}}>
|
|
227
|
+
<span style={{ color: opColor, fontFamily: "monospace", fontWeight: 700, fontSize: 12 }}>
|
|
228
|
+
{isOffer ? "offer()" : "take()"}
|
|
229
|
+
</span>
|
|
230
|
+
<span style={{ color: "#bbb", fontSize: 11 }}>— algorithm trace</span>
|
|
231
|
+
</div>
|
|
232
|
+
|
|
233
|
+
{/* Variable rows */}
|
|
234
|
+
{rows.map((r, i) => (
|
|
235
|
+
<div key={i} style={{
|
|
236
|
+
display: "grid",
|
|
237
|
+
gridTemplateColumns: "52px 130px 44px 1fr",
|
|
238
|
+
alignItems: "center",
|
|
239
|
+
padding: "4px 12px",
|
|
240
|
+
borderBottom: i < rows.length - 1 ? "1px solid #f0ede6" : "none",
|
|
241
|
+
background: r.hi ? `${opColor}0c` : "transparent",
|
|
242
|
+
}}>
|
|
243
|
+
<span style={{
|
|
244
|
+
fontFamily: "monospace", fontSize: 11,
|
|
245
|
+
fontWeight: r.hi ? 700 : 400,
|
|
246
|
+
color: r.hi ? opColor : "#aaa",
|
|
247
|
+
}}>
|
|
248
|
+
{r.label}
|
|
249
|
+
</span>
|
|
250
|
+
<span style={{ fontFamily: "monospace", fontSize: 11, color: "#ccc" }}>
|
|
251
|
+
= {r.expr}
|
|
252
|
+
</span>
|
|
253
|
+
<span style={{
|
|
254
|
+
fontFamily: "monospace", fontSize: 14, fontWeight: 700,
|
|
255
|
+
color: r.hi ? decisionColor : "#555",
|
|
256
|
+
textAlign: "right", paddingRight: 10,
|
|
257
|
+
}}>
|
|
258
|
+
{r.val}
|
|
259
|
+
</span>
|
|
260
|
+
<span style={{
|
|
261
|
+
fontFamily: "monospace", fontSize: 10,
|
|
262
|
+
color: r.hi ? decisionColor + "aa" : "#d8d5cc",
|
|
263
|
+
}}>
|
|
264
|
+
{r.note}
|
|
265
|
+
</span>
|
|
266
|
+
</div>
|
|
267
|
+
))}
|
|
268
|
+
|
|
269
|
+
{/* Decision bar */}
|
|
270
|
+
<div style={{
|
|
271
|
+
padding: "6px 12px",
|
|
272
|
+
background: `${decisionColor}10`,
|
|
273
|
+
borderTop: `1px solid ${decisionColor}28`,
|
|
274
|
+
fontFamily: "monospace", fontSize: 11,
|
|
275
|
+
fontWeight: 600, color: decisionColor,
|
|
276
|
+
}}>
|
|
277
|
+
{decisionText}
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ── SVG ring diagram ──────────────────────────────────────────────────────────
|
|
284
|
+
|
|
285
|
+
function RingDiagram({ buf, pIdx, cIdx, hiSlots, hiColor }) {
|
|
286
|
+
const size = pIdx - cIdx;
|
|
287
|
+
const pSlot = pIdx & MASK;
|
|
288
|
+
const cSlot = cIdx & MASK;
|
|
289
|
+
const same = pSlot === cSlot;
|
|
290
|
+
|
|
291
|
+
const SX = [56, 186, 316, 446];
|
|
292
|
+
const SW = 106, SH = 70, SY = 30;
|
|
293
|
+
const bot = SY + SH;
|
|
294
|
+
|
|
295
|
+
const pCX = SX[pSlot] + SW / 2;
|
|
296
|
+
const cCX = SX[cSlot] + SW / 2;
|
|
297
|
+
const pX = same ? pCX - 28 : pCX;
|
|
298
|
+
const cX = same ? cCX + 28 : cCX;
|
|
299
|
+
|
|
300
|
+
const szColor = size === CAP ? COLOR.fail : COLOR.neutral;
|
|
301
|
+
|
|
302
|
+
const statItems = [
|
|
303
|
+
{ label: "size", val: `${size} / ${CAP}`, col: szColor },
|
|
304
|
+
{ label: "pIdx", val: `${pIdx}`, col: COLOR.write },
|
|
305
|
+
{ label: "cIdx", val: `${cIdx}`, col: COLOR.read },
|
|
306
|
+
];
|
|
307
|
+
|
|
308
|
+
return (
|
|
309
|
+
<svg viewBox="0 0 608 192" style={{ width: "100%", display: "block" }}>
|
|
310
|
+
<defs>
|
|
311
|
+
<marker id="arr-mpsc" viewBox="0 0 10 10" refX="8" refY="5"
|
|
312
|
+
markerWidth="5" markerHeight="5" orient="auto-start-reverse">
|
|
313
|
+
<path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke"
|
|
314
|
+
strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
315
|
+
</marker>
|
|
316
|
+
</defs>
|
|
317
|
+
|
|
318
|
+
{SX.map((x, i) => {
|
|
319
|
+
const hi = hiSlots?.includes(i);
|
|
320
|
+
const val = buf[i];
|
|
321
|
+
const col = hi ? hiColor : undefined;
|
|
322
|
+
return (
|
|
323
|
+
<g key={i}>
|
|
324
|
+
<rect x={x} y={SY} width={SW} height={SH} rx="8"
|
|
325
|
+
fill={hi ? col + "18" : "var(--slot-bg, #f5f5f3)"}
|
|
326
|
+
stroke={hi ? col : val ? "#aaa" : "#d4d2c8"}
|
|
327
|
+
strokeWidth={hi ? 1.5 : 0.5} />
|
|
328
|
+
<text x={x + SW / 2} y={SY + 11} textAnchor="middle"
|
|
329
|
+
dominantBaseline="central"
|
|
330
|
+
fontSize="10" fill="#888" fontFamily="monospace">
|
|
331
|
+
slot {i}
|
|
332
|
+
</text>
|
|
333
|
+
<text x={x + SW / 2} y={SY + SH / 2 + 4} textAnchor="middle"
|
|
334
|
+
dominantBaseline="central"
|
|
335
|
+
fontSize={val ? 22 : 13} fontWeight={val ? "600" : "400"}
|
|
336
|
+
fontFamily={val ? "monospace" : "sans-serif"}
|
|
337
|
+
fill={val ? COLOR.write : "#ccc"}>
|
|
338
|
+
{val ?? "null"}
|
|
339
|
+
</text>
|
|
340
|
+
</g>
|
|
341
|
+
);
|
|
342
|
+
})}
|
|
343
|
+
|
|
344
|
+
<line x1={pX} y1={bot + 2} x2={pX} y2={bot + 12}
|
|
345
|
+
stroke={COLOR.write} strokeWidth="1.5" markerEnd="url(#arr-mpsc)" />
|
|
346
|
+
<text x={pX} y={bot + 24} textAnchor="middle" dominantBaseline="central"
|
|
347
|
+
fontSize="11" fontFamily="monospace" fill={COLOR.write}>
|
|
348
|
+
pIdx={pIdx}
|
|
349
|
+
</text>
|
|
350
|
+
|
|
351
|
+
<line x1={cX} y1={bot + 2} x2={cX} y2={bot + 12}
|
|
352
|
+
stroke={COLOR.read} strokeWidth="1.5" markerEnd="url(#arr-mpsc)" />
|
|
353
|
+
<text x={cX} y={bot + 24} textAnchor="middle" dominantBaseline="central"
|
|
354
|
+
fontSize="11" fontFamily="monospace" fill={COLOR.read}>
|
|
355
|
+
cIdx={cIdx}
|
|
356
|
+
</text>
|
|
357
|
+
|
|
358
|
+
{statItems.map((s, n) => {
|
|
359
|
+
const sx = 100 + n * 200;
|
|
360
|
+
return (
|
|
361
|
+
<g key={n}>
|
|
362
|
+
<text x={sx} y={156} textAnchor="middle" dominantBaseline="central"
|
|
363
|
+
fontSize="9" fill="#aaa" fontFamily="sans-serif">
|
|
364
|
+
{s.label}
|
|
365
|
+
</text>
|
|
366
|
+
<text x={sx} y={172} textAnchor="middle" dominantBaseline="central"
|
|
367
|
+
fontSize="14" fontWeight="500" fontFamily="monospace" fill={s.col}>
|
|
368
|
+
{s.val}
|
|
369
|
+
</text>
|
|
370
|
+
</g>
|
|
371
|
+
);
|
|
372
|
+
})}
|
|
373
|
+
|
|
374
|
+
{[[COLOR.write, "pIdx — producers (CAS)"], [COLOR.read, "cIdx — single consumer"]].map(
|
|
375
|
+
([c, label], n) => (
|
|
376
|
+
<g key={n}>
|
|
377
|
+
<circle cx={168 + n * 200} cy={188} r="4" fill={c} />
|
|
378
|
+
<text x={178 + n * 200} y={188} dominantBaseline="central"
|
|
379
|
+
fontSize="11" fill="#999" fontFamily="sans-serif">
|
|
380
|
+
{label}
|
|
381
|
+
</text>
|
|
382
|
+
</g>
|
|
383
|
+
)
|
|
384
|
+
)}
|
|
385
|
+
</svg>
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ── History log ───────────────────────────────────────────────────────────────
|
|
390
|
+
|
|
391
|
+
function HistoryLog({ entries, currentIndex }) {
|
|
392
|
+
const bottomRef = useRef(null);
|
|
393
|
+
|
|
394
|
+
return (
|
|
395
|
+
<div style={{
|
|
396
|
+
maxHeight: 110, overflowY: "auto",
|
|
397
|
+
border: "1px solid #e0ded6", borderRadius: 8,
|
|
398
|
+
fontSize: 11, fontFamily: "monospace",
|
|
399
|
+
}}>
|
|
400
|
+
{entries.map((e, i) => {
|
|
401
|
+
const border = { write: COLOR.write, read: COLOR.read, fail: COLOR.fail }[e.kind] ?? "#ccc";
|
|
402
|
+
const isSelected = currentIndex === i;
|
|
403
|
+
return (
|
|
404
|
+
<div key={i} style={{
|
|
405
|
+
display: "flex", gap: 10, padding: "4px 10px",
|
|
406
|
+
borderLeft: `3px solid ${border}`,
|
|
407
|
+
borderBottom: i < entries.length - 1 ? "1px solid #f0ede6" : "none",
|
|
408
|
+
color: border,
|
|
409
|
+
background: isSelected ? `${border}0a` : "transparent",
|
|
410
|
+
fontWeight: isSelected ? 600 : 400,
|
|
411
|
+
}}>
|
|
412
|
+
<span style={{ color: "#bbb", minWidth: 32, fontSize: 10 }}>#{i + 1}</span>
|
|
413
|
+
<span style={{ flex: 1 }}>{e.msg}</span>
|
|
414
|
+
</div>
|
|
415
|
+
);
|
|
416
|
+
})}
|
|
417
|
+
<div ref={bottomRef} />
|
|
418
|
+
</div>
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// ── Main component ────────────────────────────────────────────────────────────
|
|
423
|
+
|
|
424
|
+
export default function MpscRingBuffer() {
|
|
425
|
+
const [state, setState] = useState(initState);
|
|
426
|
+
const [input, setInput] = useState("A");
|
|
427
|
+
const [history, setHistory] = useState([{
|
|
428
|
+
msg: "initial state", kind: "neutral",
|
|
429
|
+
stateSnapshot: initState(), calcSnapshot: null,
|
|
430
|
+
}]);
|
|
431
|
+
const [hi, setHi] = useState({ slots: null, color: null });
|
|
432
|
+
const [calc, setCalc] = useState(null);
|
|
433
|
+
const [historyIndex, setHistoryIndex] = useState(-1);
|
|
434
|
+
|
|
435
|
+
function addLog(msg, kind, stateSnapshot, calcSnapshot) {
|
|
436
|
+
setHistory(h => [...h, { msg, kind, stateSnapshot, calcSnapshot }]);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const displayedState = historyIndex >= 0 ? history[historyIndex].stateSnapshot : state;
|
|
440
|
+
|
|
441
|
+
const goBack = useCallback(() => {
|
|
442
|
+
setHistoryIndex(i => {
|
|
443
|
+
const next = i === -1 ? history.length - 1 : Math.max(0, i - 1);
|
|
444
|
+
setCalc(history[next].calcSnapshot);
|
|
445
|
+
return next;
|
|
446
|
+
});
|
|
447
|
+
}, [history]);
|
|
448
|
+
|
|
449
|
+
const goForward = useCallback(() => {
|
|
450
|
+
setHistoryIndex(i => {
|
|
451
|
+
const next = i === history.length - 1 ? -1 : Math.min(history.length - 1, i + 1);
|
|
452
|
+
if (next >= 0) setCalc(history[next].calcSnapshot);
|
|
453
|
+
else setCalc(null);
|
|
454
|
+
return next;
|
|
455
|
+
});
|
|
456
|
+
}, [history]);
|
|
457
|
+
|
|
458
|
+
const doOffer = useCallback(() => {
|
|
459
|
+
const val = input.trim();
|
|
460
|
+
if (!val) return;
|
|
461
|
+
|
|
462
|
+
setState(prev => {
|
|
463
|
+
const { buf, pIdx, cIdx } = prev;
|
|
464
|
+
const size = pIdx - cIdx;
|
|
465
|
+
const slot = pIdx & MASK;
|
|
466
|
+
const snap = { op: "offer", pIdx, cIdx, size, slot };
|
|
467
|
+
setCalc(snap);
|
|
468
|
+
|
|
469
|
+
if (size >= CAP) {
|
|
470
|
+
addLog(`offer("${val}") → false [size=${size} FULL]`, "fail", prev, snap);
|
|
471
|
+
setHi({ slots: [slot], color: COLOR.fail });
|
|
472
|
+
setHistoryIndex(-1);
|
|
473
|
+
return prev;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const newBuf = [...buf]; newBuf[slot] = val;
|
|
477
|
+
const newState = { buf: newBuf, pIdx: pIdx + 1, cIdx };
|
|
478
|
+
addLog(`offer("${val}") → true [slot ${slot} pIdx: ${pIdx}→${pIdx + 1}]`, "write", newState, snap);
|
|
479
|
+
setHi({ slots: [slot], color: COLOR.write });
|
|
480
|
+
if (val.length === 1 && val >= "A" && val < "Z")
|
|
481
|
+
setInput(String.fromCharCode(val.charCodeAt(0) + 1));
|
|
482
|
+
setHistoryIndex(-1);
|
|
483
|
+
return newState;
|
|
484
|
+
});
|
|
485
|
+
}, [input]);
|
|
486
|
+
|
|
487
|
+
const doTake = useCallback(() => {
|
|
488
|
+
setState(prev => {
|
|
489
|
+
const { buf, pIdx, cIdx } = prev;
|
|
490
|
+
const slot = cIdx & MASK;
|
|
491
|
+
const value = buf[slot];
|
|
492
|
+
const snap = { op: "take", cIdx, slot, value };
|
|
493
|
+
setCalc(snap);
|
|
494
|
+
|
|
495
|
+
if (value === null) {
|
|
496
|
+
addLog(`take() → null [slot ${slot} buf[${slot}]=null EMPTY]`, "fail", prev, snap);
|
|
497
|
+
setHi({ slots: [slot], color: COLOR.fail });
|
|
498
|
+
setHistoryIndex(-1);
|
|
499
|
+
return prev;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const newBuf = [...buf]; newBuf[slot] = null;
|
|
503
|
+
const newState = { buf: newBuf, pIdx, cIdx: cIdx + 1 };
|
|
504
|
+
addLog(`take() → "${value}" [slot ${slot} cIdx: ${cIdx}→${cIdx + 1}]`, "read", newState, snap);
|
|
505
|
+
setHi({ slots: [slot], color: COLOR.read });
|
|
506
|
+
setHistoryIndex(-1);
|
|
507
|
+
return newState;
|
|
508
|
+
});
|
|
509
|
+
}, []);
|
|
510
|
+
|
|
511
|
+
const doReset = useCallback(() => {
|
|
512
|
+
setState(initState());
|
|
513
|
+
setInput("A");
|
|
514
|
+
setHistory([{ msg: "initial state", kind: "neutral", stateSnapshot: initState(), calcSnapshot: null }]);
|
|
515
|
+
setHi({ slots: null, color: null });
|
|
516
|
+
setCalc(null);
|
|
517
|
+
setHistoryIndex(-1);
|
|
518
|
+
}, []);
|
|
519
|
+
|
|
520
|
+
const s = {
|
|
521
|
+
wrap: {
|
|
522
|
+
fontFamily: "sans-serif",
|
|
523
|
+
border: "1px solid #e0ded6", borderRadius: 12,
|
|
524
|
+
padding: "16px 16px 12px", background: "#fafaf8",
|
|
525
|
+
maxWidth: 680, margin: "1.5rem auto",
|
|
526
|
+
},
|
|
527
|
+
heading: {
|
|
528
|
+
fontSize: 13, fontWeight: 600, letterSpacing: "0.04em",
|
|
529
|
+
textTransform: "uppercase", color: "#888",
|
|
530
|
+
marginBottom: 12, textAlign: "center",
|
|
531
|
+
},
|
|
532
|
+
controls: {
|
|
533
|
+
display: "flex", alignItems: "center", gap: 8,
|
|
534
|
+
justifyContent: "center", flexWrap: "wrap", marginBottom: 6,
|
|
535
|
+
},
|
|
536
|
+
input: {
|
|
537
|
+
padding: "6px 10px", borderRadius: 8,
|
|
538
|
+
border: "1px solid #d4d2c8", background: "#fff",
|
|
539
|
+
fontSize: 13, fontFamily: "monospace", width: 110,
|
|
540
|
+
outline: "none", color: "#333",
|
|
541
|
+
},
|
|
542
|
+
btnOffer: {
|
|
543
|
+
padding: "6px 18px", borderRadius: 8, border: "none",
|
|
544
|
+
background: COLOR.write, color: "#fff",
|
|
545
|
+
fontSize: 13, fontWeight: 600, cursor: "pointer",
|
|
546
|
+
},
|
|
547
|
+
btnTake: {
|
|
548
|
+
padding: "6px 18px", borderRadius: 8, border: "none",
|
|
549
|
+
background: COLOR.read, color: "#fff",
|
|
550
|
+
fontSize: 13, fontWeight: 600, cursor: "pointer",
|
|
551
|
+
},
|
|
552
|
+
btnReset: {
|
|
553
|
+
padding: "6px 14px", borderRadius: 8,
|
|
554
|
+
border: "1px solid #d4d2c8", background: "transparent",
|
|
555
|
+
color: "#888", fontSize: 13, cursor: "pointer",
|
|
556
|
+
},
|
|
557
|
+
btnNav: (disabled) => ({
|
|
558
|
+
padding: "6px 12px", borderRadius: 8,
|
|
559
|
+
border: `1px solid ${disabled ? "#e8e6df" : "#d4d2c8"}`, background: "transparent",
|
|
560
|
+
color: disabled ? "#ccc" : "#888", fontSize: 13,
|
|
561
|
+
cursor: disabled ? "default" : "pointer", opacity: disabled ? 0.5 : 1,
|
|
562
|
+
}),
|
|
563
|
+
hint: {
|
|
564
|
+
fontSize: 11, textAlign: "center", color: "#aaa",
|
|
565
|
+
marginTop: 8, lineHeight: 1.6,
|
|
566
|
+
},
|
|
567
|
+
sectionLabel: { fontSize: 11, color: "#bbb", marginBottom: 4, paddingLeft: 4 },
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
return (
|
|
571
|
+
<div style={s.wrap}>
|
|
572
|
+
<div style={s.heading}>MPSC Ring Buffer · Capacity 4</div>
|
|
573
|
+
|
|
574
|
+
<div style={s.controls}>
|
|
575
|
+
<input
|
|
576
|
+
style={s.input} value={input} maxLength={6} placeholder='e.g. "A"'
|
|
577
|
+
onChange={e => setInput(e.target.value)}
|
|
578
|
+
onKeyDown={e => e.key === "Enter" && doOffer()}
|
|
579
|
+
/>
|
|
580
|
+
<button style={s.btnOffer} onClick={doOffer}>Offer →</button>
|
|
581
|
+
<button style={s.btnTake} onClick={doTake}>← Take</button>
|
|
582
|
+
<button style={s.btnReset} onClick={doReset}>Reset</button>
|
|
583
|
+
<button style={s.btnNav(historyIndex === 0)} onClick={goBack} disabled={historyIndex === 0}>← Back</button>
|
|
584
|
+
<button style={s.btnNav(historyIndex === -1)} onClick={goForward} disabled={historyIndex === -1}>Forward →</button>
|
|
585
|
+
</div>
|
|
586
|
+
|
|
587
|
+
<RingDiagram
|
|
588
|
+
buf={displayedState.buf}
|
|
589
|
+
pIdx={displayedState.pIdx}
|
|
590
|
+
cIdx={displayedState.cIdx}
|
|
591
|
+
hiSlots={hi.slots}
|
|
592
|
+
hiColor={hi.color}
|
|
593
|
+
/>
|
|
594
|
+
|
|
595
|
+
<div style={{ marginTop: 8 }}>
|
|
596
|
+
<div style={s.sectionLabel}>Step summary</div>
|
|
597
|
+
<StepDescription calc={calc} />
|
|
598
|
+
</div>
|
|
599
|
+
|
|
600
|
+
<div style={{ marginTop: 8 }}>
|
|
601
|
+
<div style={s.sectionLabel}>Algorithm trace</div>
|
|
602
|
+
<CalcPanel calc={calc} />
|
|
603
|
+
</div>
|
|
604
|
+
|
|
605
|
+
<div style={{ marginTop: 8 }}>
|
|
606
|
+
<div style={s.sectionLabel}>Operation history</div>
|
|
607
|
+
<HistoryLog entries={history} currentIndex={historyIndex} />
|
|
608
|
+
</div>
|
|
609
|
+
|
|
610
|
+
<div style={s.hint}>
|
|
611
|
+
Type any label ·{" "}
|
|
612
|
+
<strong style={{ color: COLOR.write }}>Offer</strong> to enqueue ·{" "}
|
|
613
|
+
<strong style={{ color: COLOR.read }}>Take</strong> to dequeue ·
|
|
614
|
+
use Back / Forward to replay any step
|
|
615
|
+
</div>
|
|
616
|
+
</div>
|
|
617
|
+
);
|
|
618
|
+
}
|