@zio.dev/zio-blocks 0.0.33 → 0.0.51
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/compile-time-resource-safety-with-scope.md +16 -17
- package/guides/getting-started-with-mux.md +1507 -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 +246 -165
- package/guides/telemetry-guide.md +1069 -0
- package/guides/zio-schema-migration.md +29 -22
- package/index.md +292 -50
- 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 +651 -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.md +158 -0
- package/reference/context.md +4 -4
- package/reference/datastar.md +346 -0
- package/reference/docs.md +1461 -345
- package/reference/endpoint/auth-type.md +146 -0
- package/reference/endpoint/endpoint.md +297 -0
- package/reference/endpoint/http-codec.md +249 -0
- package/reference/endpoint/index.md +825 -0
- package/reference/endpoint/path-codec.md +237 -0
- package/reference/endpoint/route-pattern.md +196 -0
- package/reference/endpoint/route-tree.md +111 -0
- package/reference/endpoint/segment-codec.md +212 -0
- package/reference/html.md +1120 -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 +855 -0
- package/reference/http-model/index.md +47 -0
- package/reference/http-model/model.md +1481 -0
- package/reference/http-model/schema.md +747 -0
- package/reference/maybe.md +826 -0
- package/reference/media-type.md +2 -2
- package/reference/mux.mdx +823 -0
- package/reference/openapi.md +1351 -0
- package/reference/resource-management/defer-handle.md +1 -1
- package/reference/resource-management/resource.md +31 -2
- package/reference/resource-management/scope.md +28 -12
- package/reference/resource-management/wire.md +3 -7
- 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 +151 -0
- package/reference/ringbuffer/mpsc.mdx +132 -0
- package/reference/ringbuffer/spmc.mdx +108 -0
- package/reference/ringbuffer/spsc.mdx +344 -0
- package/reference/{allows.md → schema/allows.md} +4 -4
- package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
- package/reference/{binding.md → schema/binding.md} +2 -3
- package/reference/schema/built-in-codecs/avro.md +451 -0
- package/reference/schema/built-in-codecs/bson.md +480 -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} +10 -10
- package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +151 -5
- package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
- package/reference/schema/format.md +92 -0
- package/reference/schema/index.md +50 -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} +165 -72
- 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.md → schema/schema.md} +12 -0
- 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 +533 -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 +146 -0
- package/reference/sql/db-tx.md +82 -0
- package/reference/sql/db-value.md +41 -0
- package/reference/sql/ddl.md +85 -0
- package/reference/sql/frag.md +254 -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 +353 -0
- package/reference/sql-zio.md +112 -0
- package/reference/streams/concurrent-operators.md +106 -0
- package/reference/streams/index.md +653 -0
- package/reference/streams/pipeline.md +718 -0
- package/reference/streams/reader.md +1284 -0
- package/reference/streams/scala-2-compatibility.md +55 -0
- package/reference/streams/sink.md +1426 -0
- package/reference/streams/stream.md +2526 -0
- package/reference/streams/writer.md +1045 -0
- package/reference/streams/zero-boxing.md +275 -0
- package/reference/telemetry.md +693 -0
- package/reference/typeid.md +5 -19
- package/sidebars.js +238 -43
- 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,680 @@
|
|
|
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
|
+
stale: "#C0BDB4",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function initState() {
|
|
15
|
+
return {
|
|
16
|
+
buf: Array(CAP).fill(null), // actual slot contents — never cleared by consumers
|
|
17
|
+
live: Array(CAP).fill(false), // is slot logically occupied (cIdx ≤ slot < pIdx)?
|
|
18
|
+
pIdx: 0,
|
|
19
|
+
cIdx: 0,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ── Prose step summary ────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
function stepDescription(calc) {
|
|
26
|
+
if (!calc) return null;
|
|
27
|
+
|
|
28
|
+
if (calc.op === "offer") {
|
|
29
|
+
if (calc.size >= CAP) {
|
|
30
|
+
return {
|
|
31
|
+
color: COLOR.fail,
|
|
32
|
+
paragraphs: [
|
|
33
|
+
`The producer reads pIdx = ${calc.pIdx} with a plain load — no CAS is needed ` +
|
|
34
|
+
`since there is exactly one producer. It reads cIdx = ${calc.cIdx} to check capacity. ` +
|
|
35
|
+
`The occupancy pIdx − cIdx = ${calc.size} equals the buffer capacity (${CAP}), ` +
|
|
36
|
+
`so every slot holds an unconsumed element.`,
|
|
37
|
+
|
|
38
|
+
`offer() returns false immediately. No slot is written and pIdx stays at ${calc.pIdx}.`,
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
} else {
|
|
42
|
+
return {
|
|
43
|
+
color: COLOR.write,
|
|
44
|
+
paragraphs: [
|
|
45
|
+
`The producer reads pIdx = ${calc.pIdx} with a plain load — no CAS is needed ` +
|
|
46
|
+
`since there is exactly one producer. The occupancy pIdx − cIdx = ${calc.size} ` +
|
|
47
|
+
`is less than the capacity (${CAP}), so there is at least one free slot. ` +
|
|
48
|
+
`The target slot is computed as pIdx & mask = ${calc.pIdx} & ${MASK} = ${calc.slot}.`,
|
|
49
|
+
|
|
50
|
+
`The producer first writes the element into buf[${calc.slot}] with a release store, ` +
|
|
51
|
+
`then advances pIdx from ${calc.pIdx} to ${calc.pIdx + 1} with a second release store. ` +
|
|
52
|
+
`The ordering matters: pIdx is advanced last. A consumer that reads pIdx = ${calc.pIdx + 1} ` +
|
|
53
|
+
`with an acquire load is guaranteed to also see the element already sitting in buf[${calc.slot}].`,
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
if (calc.size === 0) {
|
|
59
|
+
return {
|
|
60
|
+
color: COLOR.fail,
|
|
61
|
+
paragraphs: [
|
|
62
|
+
`A consumer reads cIdx = ${calc.cIdx} (volatile) and pIdx = ${calc.pIdx} (acquire). ` +
|
|
63
|
+
`Since pIdx equals cIdx, the buffer contains no elements. ` +
|
|
64
|
+
`take() returns null immediately — no CAS is attempted and cIdx stays at ${calc.cIdx}.`,
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
} else {
|
|
68
|
+
return {
|
|
69
|
+
color: COLOR.read,
|
|
70
|
+
paragraphs: [
|
|
71
|
+
`A consumer reads cIdx = ${calc.cIdx} (volatile) and pIdx = ${calc.pIdx} (acquire). ` +
|
|
72
|
+
`Since pIdx (${calc.pIdx}) > cIdx (${calc.cIdx}), the buffer is not empty. ` +
|
|
73
|
+
`The slot is computed as cIdx & mask = ${calc.cIdx} & ${MASK} = ${calc.slot}.`,
|
|
74
|
+
|
|
75
|
+
`The consumer reads buf[${calc.slot}] = "${calc.element}" BEFORE attempting the CAS. ` +
|
|
76
|
+
`This ordering is critical: once the CAS advances cIdx to ${calc.cIdx + 1}, ` +
|
|
77
|
+
`the producer is permitted to overwrite slot ${calc.slot} on its next lap. ` +
|
|
78
|
+
`Reading first ensures the consumer captures the value while it is still exclusively theirs.`,
|
|
79
|
+
|
|
80
|
+
`The CAS on cIdx from ${calc.cIdx} to ${calc.cIdx + 1} succeeds. ` +
|
|
81
|
+
`Unlike MPSC, buf[${calc.slot}] is NOT cleared — the slot retains "${calc.element}" ` +
|
|
82
|
+
`until the producer wraps around and overwrites it. ` +
|
|
83
|
+
`Element validity is determined by comparing pIdx and cIdx, never by null-checking slots.`,
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function StepDescription({ calc }) {
|
|
91
|
+
const desc = stepDescription(calc);
|
|
92
|
+
|
|
93
|
+
if (!desc) return (
|
|
94
|
+
<div style={{
|
|
95
|
+
margin: "6px 0 4px", borderRadius: 8,
|
|
96
|
+
border: "1px solid #e8e6df", background: "#f5f4f0",
|
|
97
|
+
padding: "10px 14px", fontSize: 12,
|
|
98
|
+
color: "#ccc", textAlign: "center", fontStyle: "italic",
|
|
99
|
+
}}>
|
|
100
|
+
step summary will appear here after each operation
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<div style={{
|
|
106
|
+
margin: "6px 0 4px", borderRadius: 8,
|
|
107
|
+
border: `1px solid ${desc.color}22`,
|
|
108
|
+
background: `${desc.color}08`,
|
|
109
|
+
padding: "10px 14px",
|
|
110
|
+
}}>
|
|
111
|
+
{desc.paragraphs.map((p, i) => (
|
|
112
|
+
<p key={i} style={{
|
|
113
|
+
margin: i < desc.paragraphs.length - 1 ? "0 0 6px" : "0",
|
|
114
|
+
fontSize: 12.5, lineHeight: 1.65, color: "#444",
|
|
115
|
+
}}>
|
|
116
|
+
{p}
|
|
117
|
+
</p>
|
|
118
|
+
))}
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── Algorithm trace panel ─────────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
function CalcPanel({ calc }) {
|
|
126
|
+
if (!calc) return (
|
|
127
|
+
<div style={{
|
|
128
|
+
margin: "6px 0 0", borderRadius: 8,
|
|
129
|
+
border: "1px solid #e8e6df", background: "#f5f4f0",
|
|
130
|
+
padding: "10px 14px", fontSize: 11,
|
|
131
|
+
fontFamily: "monospace", color: "#ccc", textAlign: "center",
|
|
132
|
+
}}>
|
|
133
|
+
algorithm trace will appear here after each operation
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const isOffer = calc.op === "offer";
|
|
138
|
+
const opColor = isOffer ? COLOR.write : COLOR.read;
|
|
139
|
+
|
|
140
|
+
let rows;
|
|
141
|
+
let decisionColor;
|
|
142
|
+
let decisionText;
|
|
143
|
+
|
|
144
|
+
if (isOffer) {
|
|
145
|
+
const full = calc.size >= CAP;
|
|
146
|
+
rows = [
|
|
147
|
+
{
|
|
148
|
+
label: "pIdx",
|
|
149
|
+
expr: "pIdx",
|
|
150
|
+
val: String(calc.pIdx),
|
|
151
|
+
note: "plain read — single producer, no CAS needed",
|
|
152
|
+
hi: false,
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
label: "cIdx",
|
|
156
|
+
expr: "cIdx",
|
|
157
|
+
val: String(calc.cIdx),
|
|
158
|
+
note: "read to check capacity",
|
|
159
|
+
hi: false,
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
label: "size",
|
|
163
|
+
expr: "pIdx − cIdx",
|
|
164
|
+
val: String(calc.size),
|
|
165
|
+
note: `${calc.pIdx} − ${calc.cIdx} = ${calc.size} (current occupancy)`,
|
|
166
|
+
hi: true,
|
|
167
|
+
},
|
|
168
|
+
...(!full ? [{
|
|
169
|
+
label: "slot",
|
|
170
|
+
expr: "pIdx & mask",
|
|
171
|
+
val: String(calc.slot),
|
|
172
|
+
note: `${calc.pIdx} & ${MASK} = ${calc.slot} (bitmask replaces mod)`,
|
|
173
|
+
hi: false,
|
|
174
|
+
}] : []),
|
|
175
|
+
];
|
|
176
|
+
decisionColor = full ? COLOR.fail : COLOR.write;
|
|
177
|
+
decisionText = full
|
|
178
|
+
? `✗ size == cap → buffer FULL → return false`
|
|
179
|
+
: `✓ size < cap → write buf[${calc.slot}] (release), then pIdx ${calc.pIdx}→${calc.pIdx + 1} (release) → return true`;
|
|
180
|
+
} else {
|
|
181
|
+
const empty = calc.size === 0;
|
|
182
|
+
rows = [
|
|
183
|
+
{
|
|
184
|
+
label: "cIdx",
|
|
185
|
+
expr: "cIdx",
|
|
186
|
+
val: String(calc.cIdx),
|
|
187
|
+
note: "volatile read — any consumer thread may hold this",
|
|
188
|
+
hi: false,
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
label: "pIdx",
|
|
192
|
+
expr: "pIdx",
|
|
193
|
+
val: String(calc.pIdx),
|
|
194
|
+
note: "acquire read — sees all writes up to this pIdx",
|
|
195
|
+
hi: false,
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
label: "size",
|
|
199
|
+
expr: "pIdx − cIdx",
|
|
200
|
+
val: String(calc.size),
|
|
201
|
+
note: `${calc.pIdx} − ${calc.cIdx} = ${calc.size} (${empty ? "== 0 → empty" : "> 0 → not empty"})`,
|
|
202
|
+
hi: true,
|
|
203
|
+
},
|
|
204
|
+
...(!empty ? [
|
|
205
|
+
{
|
|
206
|
+
label: "slot",
|
|
207
|
+
expr: "cIdx & mask",
|
|
208
|
+
val: String(calc.slot),
|
|
209
|
+
note: `${calc.cIdx} & ${MASK} = ${calc.slot} (bitmask replaces mod)`,
|
|
210
|
+
hi: false,
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
label: "element",
|
|
214
|
+
expr: "buf[slot]",
|
|
215
|
+
val: `"${calc.element}"`,
|
|
216
|
+
note: `read BEFORE CAS — once cIdx advances, producer may overwrite slot ${calc.slot}`,
|
|
217
|
+
hi: true,
|
|
218
|
+
},
|
|
219
|
+
] : []),
|
|
220
|
+
];
|
|
221
|
+
decisionColor = empty ? COLOR.fail : COLOR.read;
|
|
222
|
+
decisionText = empty
|
|
223
|
+
? `✗ pIdx == cIdx → buffer EMPTY → return null`
|
|
224
|
+
: `✓ element captured → CAS cIdx ${calc.cIdx}→${calc.cIdx + 1} → return "${calc.element}" (buf[${calc.slot}] not cleared)`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<div style={{
|
|
229
|
+
margin: "6px 0 0", borderRadius: 8,
|
|
230
|
+
border: `1px solid ${opColor}30`,
|
|
231
|
+
overflow: "hidden", fontSize: 12,
|
|
232
|
+
}}>
|
|
233
|
+
<div style={{
|
|
234
|
+
background: `${opColor}14`, padding: "5px 12px",
|
|
235
|
+
display: "flex", alignItems: "center", gap: 8,
|
|
236
|
+
borderBottom: `1px solid ${opColor}20`,
|
|
237
|
+
}}>
|
|
238
|
+
<span style={{ color: opColor, fontFamily: "monospace", fontWeight: 700, fontSize: 12 }}>
|
|
239
|
+
{isOffer ? "offer()" : "take()"}
|
|
240
|
+
</span>
|
|
241
|
+
<span style={{ color: "#bbb", fontSize: 11 }}>— algorithm trace</span>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
{rows.map((r, i) => (
|
|
245
|
+
<div key={i} style={{
|
|
246
|
+
display: "grid",
|
|
247
|
+
gridTemplateColumns: "58px 140px 52px 1fr",
|
|
248
|
+
alignItems: "center",
|
|
249
|
+
padding: "4px 12px",
|
|
250
|
+
borderBottom: i < rows.length - 1 ? "1px solid #f0ede6" : "none",
|
|
251
|
+
background: r.hi ? `${opColor}0c` : "transparent",
|
|
252
|
+
}}>
|
|
253
|
+
<span style={{
|
|
254
|
+
fontFamily: "monospace", fontSize: 11,
|
|
255
|
+
fontWeight: r.hi ? 700 : 400,
|
|
256
|
+
color: r.hi ? opColor : "#aaa",
|
|
257
|
+
}}>
|
|
258
|
+
{r.label}
|
|
259
|
+
</span>
|
|
260
|
+
<span style={{ fontFamily: "monospace", fontSize: 11, color: "#ccc" }}>
|
|
261
|
+
= {r.expr}
|
|
262
|
+
</span>
|
|
263
|
+
<span style={{
|
|
264
|
+
fontFamily: "monospace", fontSize: 14, fontWeight: 700,
|
|
265
|
+
color: r.hi ? decisionColor : "#555",
|
|
266
|
+
textAlign: "right", paddingRight: 10,
|
|
267
|
+
}}>
|
|
268
|
+
{r.val}
|
|
269
|
+
</span>
|
|
270
|
+
<span style={{
|
|
271
|
+
fontFamily: "monospace", fontSize: 10,
|
|
272
|
+
color: r.hi ? decisionColor + "aa" : "#d8d5cc",
|
|
273
|
+
}}>
|
|
274
|
+
{r.note}
|
|
275
|
+
</span>
|
|
276
|
+
</div>
|
|
277
|
+
))}
|
|
278
|
+
|
|
279
|
+
<div style={{
|
|
280
|
+
padding: "6px 12px",
|
|
281
|
+
background: `${decisionColor}10`,
|
|
282
|
+
borderTop: `1px solid ${decisionColor}28`,
|
|
283
|
+
fontFamily: "monospace", fontSize: 11,
|
|
284
|
+
fontWeight: 600, color: decisionColor,
|
|
285
|
+
}}>
|
|
286
|
+
{decisionText}
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ── SVG ring diagram ──────────────────────────────────────────────────────────
|
|
293
|
+
|
|
294
|
+
function RingDiagram({ buf, live, pIdx, cIdx, hiSlots, hiColor }) {
|
|
295
|
+
const size = pIdx - cIdx;
|
|
296
|
+
const pSlot = pIdx & MASK;
|
|
297
|
+
const cSlot = cIdx & MASK;
|
|
298
|
+
const same = pSlot === cSlot;
|
|
299
|
+
|
|
300
|
+
const SX = [56, 186, 316, 446];
|
|
301
|
+
const SW = 106, SH = 78, SY = 30;
|
|
302
|
+
const bot = SY + SH;
|
|
303
|
+
|
|
304
|
+
const pCX = SX[pSlot] + SW / 2;
|
|
305
|
+
const cCX = SX[cSlot] + SW / 2;
|
|
306
|
+
const pX = same ? pCX - 28 : pCX;
|
|
307
|
+
const cX = same ? cCX + 28 : cCX;
|
|
308
|
+
|
|
309
|
+
const szColor = size === CAP ? COLOR.fail : COLOR.neutral;
|
|
310
|
+
|
|
311
|
+
const statItems = [
|
|
312
|
+
{ label: "size", val: `${size} / ${CAP}`, col: szColor },
|
|
313
|
+
{ label: "pIdx", val: `${pIdx}`, col: COLOR.write },
|
|
314
|
+
{ label: "cIdx", val: `${cIdx}`, col: COLOR.read },
|
|
315
|
+
];
|
|
316
|
+
|
|
317
|
+
return (
|
|
318
|
+
<svg viewBox="0 0 608 210" style={{ width: "100%", display: "block" }}>
|
|
319
|
+
<defs>
|
|
320
|
+
<marker id="arr-spmc" viewBox="0 0 10 10" refX="8" refY="5"
|
|
321
|
+
markerWidth="5" markerHeight="5" orient="auto-start-reverse">
|
|
322
|
+
<path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke"
|
|
323
|
+
strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
324
|
+
</marker>
|
|
325
|
+
</defs>
|
|
326
|
+
|
|
327
|
+
{SX.map((x, i) => {
|
|
328
|
+
const hi = hiSlots?.includes(i);
|
|
329
|
+
const val = buf[i];
|
|
330
|
+
const isLive = live[i];
|
|
331
|
+
const isStale = !isLive && val !== null;
|
|
332
|
+
const col = hi ? hiColor : undefined;
|
|
333
|
+
|
|
334
|
+
const valText = val ?? "—";
|
|
335
|
+
const valColor = hi
|
|
336
|
+
? hiColor
|
|
337
|
+
: isLive
|
|
338
|
+
? COLOR.write
|
|
339
|
+
: isStale
|
|
340
|
+
? COLOR.stale
|
|
341
|
+
: "#ccc";
|
|
342
|
+
|
|
343
|
+
const borderColor = hi ? col : isLive ? "#aaa" : "#d4d2c8";
|
|
344
|
+
const borderWidth = hi ? 1.5 : 0.5;
|
|
345
|
+
const fillColor = hi
|
|
346
|
+
? col + "18"
|
|
347
|
+
: isStale
|
|
348
|
+
? "#f8f7f4"
|
|
349
|
+
: "var(--slot-bg, #f5f5f3)";
|
|
350
|
+
|
|
351
|
+
return (
|
|
352
|
+
<g key={i}>
|
|
353
|
+
<rect x={x} y={SY} width={SW} height={SH} rx="8"
|
|
354
|
+
fill={fillColor}
|
|
355
|
+
stroke={borderColor}
|
|
356
|
+
strokeWidth={borderWidth}
|
|
357
|
+
strokeDasharray={isStale && !hi ? "4 3" : undefined} />
|
|
358
|
+
|
|
359
|
+
{/* Slot label */}
|
|
360
|
+
<text x={x + SW / 2} y={SY + 11} textAnchor="middle"
|
|
361
|
+
dominantBaseline="central"
|
|
362
|
+
fontSize="10" fill="#888" fontFamily="monospace">
|
|
363
|
+
slot {i}
|
|
364
|
+
</text>
|
|
365
|
+
|
|
366
|
+
{/* Value */}
|
|
367
|
+
<text x={x + SW / 2} y={SY + SH / 2 + (isStale ? 0 : 4)} textAnchor="middle"
|
|
368
|
+
dominantBaseline="central"
|
|
369
|
+
fontSize={val ? 20 : 13} fontWeight={isLive ? "600" : "400"}
|
|
370
|
+
fontFamily={val ? "monospace" : "sans-serif"}
|
|
371
|
+
fill={valColor}>
|
|
372
|
+
{valText}
|
|
373
|
+
</text>
|
|
374
|
+
|
|
375
|
+
{/* "stale" badge for consumed-but-not-overwritten slots */}
|
|
376
|
+
{isStale && !hi && (
|
|
377
|
+
<text x={x + SW / 2} y={SY + SH - 10} textAnchor="middle"
|
|
378
|
+
dominantBaseline="central"
|
|
379
|
+
fontSize="9" fill={COLOR.stale} fontFamily="monospace">
|
|
380
|
+
stale
|
|
381
|
+
</text>
|
|
382
|
+
)}
|
|
383
|
+
</g>
|
|
384
|
+
);
|
|
385
|
+
})}
|
|
386
|
+
|
|
387
|
+
{/* pIdx arrow */}
|
|
388
|
+
<line x1={pX} y1={bot + 2} x2={pX} y2={bot + 12}
|
|
389
|
+
stroke={COLOR.write} strokeWidth="1.5" markerEnd="url(#arr-spmc)" />
|
|
390
|
+
<text x={pX} y={bot + 24} textAnchor="middle" dominantBaseline="central"
|
|
391
|
+
fontSize="11" fontFamily="monospace" fill={COLOR.write}>
|
|
392
|
+
pIdx={pIdx}
|
|
393
|
+
</text>
|
|
394
|
+
|
|
395
|
+
{/* cIdx arrow */}
|
|
396
|
+
<line x1={cX} y1={bot + 2} x2={cX} y2={bot + 12}
|
|
397
|
+
stroke={COLOR.read} strokeWidth="1.5" markerEnd="url(#arr-spmc)" />
|
|
398
|
+
<text x={cX} y={bot + 24} textAnchor="middle" dominantBaseline="central"
|
|
399
|
+
fontSize="11" fontFamily="monospace" fill={COLOR.read}>
|
|
400
|
+
cIdx={cIdx}
|
|
401
|
+
</text>
|
|
402
|
+
|
|
403
|
+
{/* Stats */}
|
|
404
|
+
{statItems.map((s, n) => {
|
|
405
|
+
const sx = 100 + n * 200;
|
|
406
|
+
return (
|
|
407
|
+
<g key={n}>
|
|
408
|
+
<text x={sx} y={166} textAnchor="middle" dominantBaseline="central"
|
|
409
|
+
fontSize="9" fill="#aaa" fontFamily="sans-serif">
|
|
410
|
+
{s.label}
|
|
411
|
+
</text>
|
|
412
|
+
<text x={sx} y={182} textAnchor="middle" dominantBaseline="central"
|
|
413
|
+
fontSize="14" fontWeight="500" fontFamily="monospace" fill={s.col}>
|
|
414
|
+
{s.val}
|
|
415
|
+
</text>
|
|
416
|
+
</g>
|
|
417
|
+
);
|
|
418
|
+
})}
|
|
419
|
+
|
|
420
|
+
{/* Legend */}
|
|
421
|
+
{[
|
|
422
|
+
[COLOR.write, "pIdx — single producer"],
|
|
423
|
+
[COLOR.read, "cIdx — consumers (CAS)"],
|
|
424
|
+
[COLOR.stale, "stale — consumed, not yet overwritten"],
|
|
425
|
+
].map(([c, label], n) => (
|
|
426
|
+
<g key={n}>
|
|
427
|
+
<circle cx={34 + n * 192} cy={203} r="4" fill={c} />
|
|
428
|
+
<text x={44 + n * 192} y={203} dominantBaseline="central"
|
|
429
|
+
fontSize="10" fill="#999" fontFamily="sans-serif">
|
|
430
|
+
{label}
|
|
431
|
+
</text>
|
|
432
|
+
</g>
|
|
433
|
+
))}
|
|
434
|
+
</svg>
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ── History log ───────────────────────────────────────────────────────────────
|
|
439
|
+
|
|
440
|
+
function HistoryLog({ entries, currentIndex }) {
|
|
441
|
+
const bottomRef = useRef(null);
|
|
442
|
+
|
|
443
|
+
return (
|
|
444
|
+
<div style={{
|
|
445
|
+
maxHeight: 110, overflowY: "auto",
|
|
446
|
+
border: "1px solid #e0ded6", borderRadius: 8,
|
|
447
|
+
fontSize: 11, fontFamily: "monospace",
|
|
448
|
+
}}>
|
|
449
|
+
{entries.map((e, i) => {
|
|
450
|
+
const border = { write: COLOR.write, read: COLOR.read, fail: COLOR.fail }[e.kind] ?? "#ccc";
|
|
451
|
+
const isSelected = currentIndex === i;
|
|
452
|
+
return (
|
|
453
|
+
<div key={i} style={{
|
|
454
|
+
display: "flex", gap: 10, padding: "4px 10px",
|
|
455
|
+
borderLeft: `3px solid ${border}`,
|
|
456
|
+
borderBottom: i < entries.length - 1 ? "1px solid #f0ede6" : "none",
|
|
457
|
+
color: border,
|
|
458
|
+
background: isSelected ? `${border}0a` : "transparent",
|
|
459
|
+
fontWeight: isSelected ? 600 : 400,
|
|
460
|
+
}}>
|
|
461
|
+
<span style={{ color: "#bbb", minWidth: 32, fontSize: 10 }}>#{i + 1}</span>
|
|
462
|
+
<span style={{ flex: 1 }}>{e.msg}</span>
|
|
463
|
+
</div>
|
|
464
|
+
);
|
|
465
|
+
})}
|
|
466
|
+
<div ref={bottomRef} />
|
|
467
|
+
</div>
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// ── Main component ────────────────────────────────────────────────────────────
|
|
472
|
+
|
|
473
|
+
export default function SpmcRingBuffer() {
|
|
474
|
+
const [state, setState] = useState(initState);
|
|
475
|
+
const [input, setInput] = useState("A");
|
|
476
|
+
const [history, setHistory] = useState([{
|
|
477
|
+
msg: "initial state", kind: "neutral",
|
|
478
|
+
stateSnapshot: initState(), calcSnapshot: null,
|
|
479
|
+
}]);
|
|
480
|
+
const [hi, setHi] = useState({ slots: null, color: null });
|
|
481
|
+
const [calc, setCalc] = useState(null);
|
|
482
|
+
const [historyIndex, setHistoryIndex] = useState(-1);
|
|
483
|
+
|
|
484
|
+
function addLog(msg, kind, stateSnapshot, calcSnapshot) {
|
|
485
|
+
setHistory(h => [...h, { msg, kind, stateSnapshot, calcSnapshot }]);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const displayedState = historyIndex >= 0 ? history[historyIndex].stateSnapshot : state;
|
|
489
|
+
|
|
490
|
+
const goBack = useCallback(() => {
|
|
491
|
+
setHistoryIndex(i => {
|
|
492
|
+
const next = i === -1 ? history.length - 1 : Math.max(0, i - 1);
|
|
493
|
+
setCalc(history[next].calcSnapshot);
|
|
494
|
+
return next;
|
|
495
|
+
});
|
|
496
|
+
}, [history]);
|
|
497
|
+
|
|
498
|
+
const goForward = useCallback(() => {
|
|
499
|
+
setHistoryIndex(i => {
|
|
500
|
+
const next = i === history.length - 1 ? -1 : Math.min(history.length - 1, i + 1);
|
|
501
|
+
if (next >= 0) setCalc(history[next].calcSnapshot);
|
|
502
|
+
else setCalc(null);
|
|
503
|
+
return next;
|
|
504
|
+
});
|
|
505
|
+
}, [history]);
|
|
506
|
+
|
|
507
|
+
const doOffer = useCallback(() => {
|
|
508
|
+
const val = input.trim();
|
|
509
|
+
if (!val) return;
|
|
510
|
+
|
|
511
|
+
setState(prev => {
|
|
512
|
+
const { buf, live, pIdx, cIdx } = prev;
|
|
513
|
+
const size = pIdx - cIdx;
|
|
514
|
+
const slot = pIdx & MASK;
|
|
515
|
+
const snap = { op: "offer", pIdx, cIdx, size, slot };
|
|
516
|
+
setCalc(snap);
|
|
517
|
+
|
|
518
|
+
if (size >= CAP) {
|
|
519
|
+
addLog(`offer("${val}") → false [size=${size} FULL]`, "fail", prev, snap);
|
|
520
|
+
setHi({ slots: [slot], color: COLOR.fail });
|
|
521
|
+
setHistoryIndex(-1);
|
|
522
|
+
return prev;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Producer: write buf[slot], then release pIdx (ordering matters)
|
|
526
|
+
const newBuf = [...buf]; newBuf[slot] = val;
|
|
527
|
+
const newLive = [...live]; newLive[slot] = true;
|
|
528
|
+
const newState = { buf: newBuf, live: newLive, pIdx: pIdx + 1, cIdx };
|
|
529
|
+
addLog(
|
|
530
|
+
`offer("${val}") → true [slot ${slot} pIdx: ${pIdx}→${pIdx + 1}]`,
|
|
531
|
+
"write", newState, snap,
|
|
532
|
+
);
|
|
533
|
+
setHi({ slots: [slot], color: COLOR.write });
|
|
534
|
+
if (val.length === 1 && val >= "A" && val < "Z")
|
|
535
|
+
setInput(String.fromCharCode(val.charCodeAt(0) + 1));
|
|
536
|
+
setHistoryIndex(-1);
|
|
537
|
+
return newState;
|
|
538
|
+
});
|
|
539
|
+
}, [input]);
|
|
540
|
+
|
|
541
|
+
const doTake = useCallback(() => {
|
|
542
|
+
setState(prev => {
|
|
543
|
+
const { buf, live, pIdx, cIdx } = prev;
|
|
544
|
+
const size = pIdx - cIdx;
|
|
545
|
+
const slot = cIdx & MASK;
|
|
546
|
+
const element = buf[slot]; // read BEFORE CAS
|
|
547
|
+
|
|
548
|
+
const snap = { op: "take", cIdx, pIdx, size, slot, element };
|
|
549
|
+
setCalc(snap);
|
|
550
|
+
|
|
551
|
+
if (size === 0) {
|
|
552
|
+
addLog(`take() → null [pIdx=${pIdx} cIdx=${cIdx} EMPTY]`, "fail", prev, snap);
|
|
553
|
+
setHi({ slots: [slot], color: COLOR.fail });
|
|
554
|
+
setHistoryIndex(-1);
|
|
555
|
+
return prev;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// CAS succeeds (single-threaded sim). buf[slot] is NOT cleared — mark stale only.
|
|
559
|
+
const newLive = [...live]; newLive[slot] = false;
|
|
560
|
+
const newState = { buf, live: newLive, pIdx, cIdx: cIdx + 1 }; // buf unchanged!
|
|
561
|
+
addLog(
|
|
562
|
+
`take() → "${element}" [slot ${slot} cIdx: ${cIdx}→${cIdx + 1} buf[${slot}] kept]`,
|
|
563
|
+
"read", newState, snap,
|
|
564
|
+
);
|
|
565
|
+
setHi({ slots: [slot], color: COLOR.read });
|
|
566
|
+
setHistoryIndex(-1);
|
|
567
|
+
return newState;
|
|
568
|
+
});
|
|
569
|
+
}, []);
|
|
570
|
+
|
|
571
|
+
const doReset = useCallback(() => {
|
|
572
|
+
setState(initState());
|
|
573
|
+
setInput("A");
|
|
574
|
+
setHistory([{ msg: "initial state", kind: "neutral", stateSnapshot: initState(), calcSnapshot: null }]);
|
|
575
|
+
setHi({ slots: null, color: null });
|
|
576
|
+
setCalc(null);
|
|
577
|
+
setHistoryIndex(-1);
|
|
578
|
+
}, []);
|
|
579
|
+
|
|
580
|
+
const s = {
|
|
581
|
+
wrap: {
|
|
582
|
+
fontFamily: "sans-serif",
|
|
583
|
+
border: "1px solid #e0ded6", borderRadius: 12,
|
|
584
|
+
padding: "16px 16px 12px", background: "#fafaf8",
|
|
585
|
+
maxWidth: 680, margin: "1.5rem auto",
|
|
586
|
+
},
|
|
587
|
+
heading: {
|
|
588
|
+
fontSize: 13, fontWeight: 600, letterSpacing: "0.04em",
|
|
589
|
+
textTransform: "uppercase", color: "#888",
|
|
590
|
+
marginBottom: 12, textAlign: "center",
|
|
591
|
+
},
|
|
592
|
+
controls: {
|
|
593
|
+
display: "flex", alignItems: "center", gap: 8,
|
|
594
|
+
justifyContent: "center", flexWrap: "wrap", marginBottom: 6,
|
|
595
|
+
},
|
|
596
|
+
input: {
|
|
597
|
+
padding: "6px 10px", borderRadius: 8,
|
|
598
|
+
border: "1px solid #d4d2c8", background: "#fff",
|
|
599
|
+
fontSize: 13, fontFamily: "monospace", width: 110,
|
|
600
|
+
outline: "none", color: "#333",
|
|
601
|
+
},
|
|
602
|
+
btnOffer: {
|
|
603
|
+
padding: "6px 18px", borderRadius: 8, border: "none",
|
|
604
|
+
background: COLOR.write, color: "#fff",
|
|
605
|
+
fontSize: 13, fontWeight: 600, cursor: "pointer",
|
|
606
|
+
},
|
|
607
|
+
btnTake: {
|
|
608
|
+
padding: "6px 18px", borderRadius: 8, border: "none",
|
|
609
|
+
background: COLOR.read, color: "#fff",
|
|
610
|
+
fontSize: 13, fontWeight: 600, cursor: "pointer",
|
|
611
|
+
},
|
|
612
|
+
btnReset: {
|
|
613
|
+
padding: "6px 14px", borderRadius: 8,
|
|
614
|
+
border: "1px solid #d4d2c8", background: "transparent",
|
|
615
|
+
color: "#888", fontSize: 13, cursor: "pointer",
|
|
616
|
+
},
|
|
617
|
+
btnNav: (disabled) => ({
|
|
618
|
+
padding: "6px 12px", borderRadius: 8,
|
|
619
|
+
border: `1px solid ${disabled ? "#e8e6df" : "#d4d2c8"}`, background: "transparent",
|
|
620
|
+
color: disabled ? "#ccc" : "#888", fontSize: 13,
|
|
621
|
+
cursor: disabled ? "default" : "pointer", opacity: disabled ? 0.5 : 1,
|
|
622
|
+
}),
|
|
623
|
+
sectionLabel: { fontSize: 11, color: "#bbb", marginBottom: 4, paddingLeft: 4 },
|
|
624
|
+
hint: {
|
|
625
|
+
fontSize: 11, textAlign: "center", color: "#aaa",
|
|
626
|
+
marginTop: 8, lineHeight: 1.6,
|
|
627
|
+
},
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
return (
|
|
631
|
+
<div style={s.wrap}>
|
|
632
|
+
<div style={s.heading}>SPMC Ring Buffer · Capacity 4</div>
|
|
633
|
+
|
|
634
|
+
<div style={s.controls}>
|
|
635
|
+
<input
|
|
636
|
+
style={s.input} value={input} maxLength={6} placeholder='e.g. "A"'
|
|
637
|
+
onChange={e => setInput(e.target.value)}
|
|
638
|
+
onKeyDown={e => e.key === "Enter" && doOffer()}
|
|
639
|
+
/>
|
|
640
|
+
<button style={s.btnOffer} onClick={doOffer}>Offer →</button>
|
|
641
|
+
<button style={s.btnTake} onClick={doTake}>← Take</button>
|
|
642
|
+
<button style={s.btnReset} onClick={doReset}>Reset</button>
|
|
643
|
+
<button style={s.btnNav(historyIndex === 0)} onClick={goBack} disabled={historyIndex === 0}>← Back</button>
|
|
644
|
+
<button style={s.btnNav(historyIndex === -1)} onClick={goForward} disabled={historyIndex === -1}>Forward →</button>
|
|
645
|
+
</div>
|
|
646
|
+
|
|
647
|
+
<RingDiagram
|
|
648
|
+
buf={displayedState.buf}
|
|
649
|
+
live={displayedState.live}
|
|
650
|
+
pIdx={displayedState.pIdx}
|
|
651
|
+
cIdx={displayedState.cIdx}
|
|
652
|
+
hiSlots={hi.slots}
|
|
653
|
+
hiColor={hi.color}
|
|
654
|
+
/>
|
|
655
|
+
|
|
656
|
+
<div style={{ marginTop: 8 }}>
|
|
657
|
+
<div style={s.sectionLabel}>Step summary</div>
|
|
658
|
+
<StepDescription calc={calc} />
|
|
659
|
+
</div>
|
|
660
|
+
|
|
661
|
+
<div style={{ marginTop: 8 }}>
|
|
662
|
+
<div style={s.sectionLabel}>Algorithm trace</div>
|
|
663
|
+
<CalcPanel calc={calc} />
|
|
664
|
+
</div>
|
|
665
|
+
|
|
666
|
+
<div style={{ marginTop: 8 }}>
|
|
667
|
+
<div style={s.sectionLabel}>Operation history</div>
|
|
668
|
+
<HistoryLog entries={history} currentIndex={historyIndex} />
|
|
669
|
+
</div>
|
|
670
|
+
|
|
671
|
+
<div style={s.hint}>
|
|
672
|
+
Type any label ·{" "}
|
|
673
|
+
<strong style={{ color: COLOR.write }}>Offer</strong> to enqueue (single producer) ·{" "}
|
|
674
|
+
<strong style={{ color: COLOR.read }}>Take</strong> to dequeue (any consumer, CAS) ·{" "}
|
|
675
|
+
<strong style={{ color: COLOR.stale }}>stale</strong> slots show why consumers read before CAS ·
|
|
676
|
+
use Back / Forward to replay any step
|
|
677
|
+
</div>
|
|
678
|
+
</div>
|
|
679
|
+
);
|
|
680
|
+
}
|