Hand-rolled GlobalAlloc wrapper around System that tracks allocs +
bytes + deallocs via two atomics. Each scenario runs 10k iterations
after a 1k warm-up; output is a markdown table with allocs/op,
bytes/op, deallocs/op.
Why hand-rolled (not dhat/criterion): R12 gates on a single number
("< 5 allocs/write"). dhat is heap-profiling-oriented (call-stack
attribution, JSON snapshots); criterion measures wall-clock latency
which is reported-but-not-gated per 60-roadmap.md:104. A 50-line
GlobalAlloc + atomic counters is the simplest thing that answers
the gate.
Run: `cargo bench -p mxaccess-codec`
Baseline numbers (release, Windows x64):
- Bool write: 1.00 allocs/op
- Int32 write: 2.00 allocs/op
- Float32 write: 2.00 allocs/op
- Float64 write: 2.00 allocs/op
- String write: 4.00 allocs/op (5-char string)
- Handle from_names: 2.00 allocs/op
- DataUpdate decode: 1.00 alloc/op
R12's < 5 allocs/write target is **already met** across the proven
matrix without any zero-copy work. The bench gates on this — any
write_message::encode scenario at >= 5 allocs/op exits the harness
with code 1.
Companion: `design/M6-bench-baseline.md` documents the numbers,
explains the per-scenario breakdown, and tightens F39's scope from
"hit the target" to "nice-to-have optimisations" (BytesMut output
buffer, name-signature cache, session-level scratch pool).
Workspace: 759 tests still pass; clippy --benches clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fix all 33 rustdoc warnings across the workspace:
- Unresolved intra-doc links: rewrite [`name`] → either backtick text
(when not actually a link) or fully-qualified `[Type::method]` /
`[crate::module::name]` form. Affected: mxaccess-codec
(asb_variant, item_control, metadata_query, observed_write_template,
reference_handle, write_message), mxaccess-rpc (pdu), mxaccess-nmx
(client), mxaccess-asb-nettcp (nmf), mxaccess-callback (exporter),
mxaccess (asb_session, session, lib).
- Bracket-text being interpreted as link refs (e.g. `body[17]` →
`` `body[17]` ``).
- Private-item references in public docs (CALLBACK_BROADCAST_CAPACITY,
recover_connection_core, mxvalue_to_writevalue) reduced to
backtick-text since they aren't part of the public API.
`RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps` now
exits clean. Workspace 759 tests pass; clippy clean.
Defers `#![warn(missing_docs)]` lint to a future pass — the cleanup
target is the broken-link warnings, which are signal; missing-docs
would surface hundreds of low-priority public-item gaps that are out
of scope for this F-number.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ports `Variant` (cs:1170-1241), `AsbStatus` (cs:1109-1167), `RuntimeValue`
(cs:741-791), `AsbVariantFactory.From*` (cs:1310-1429), and
`MxAsbDataClient.DecodeVariant` (cs:713-825) into `mxaccess-codec::asb_variant`.
Three layers per `docs/ASB-Variant-Wire-Format.md`:
1. `AsbVariant` — raw 2/4/4/payload header + bytes; round-trips byte-identical.
2. `DecodedVariant` — typed view with one variant per proven ASB scalar / array
(`Bool`, `Int32`, `Float`, `Double`, `String`, `DateTime`, `Duration` plus
array forms). Type ids outside the proven matrix surface as
`Unsupported { type_id, payload }` — same fallback as .NET's `_ => payload`.
3. `from_*` factories — mirror `AsbVariantFactory.FromX` exactly, setting
`length` to `payload.len()` per `cs:1431-1438`.
`AsbStatus` and `RuntimeValue` round-trip the wire layout verbatim.
Status-element walking (marker bit 7 = implicit zero, etc., per
`docs/ASB-Variant-Wire-Format.md:180-205`) is deferred to a follow-up; the
codec exposes the raw status payload bytes for now, matching .NET's
`AsbStatus.Payload = byte[]` shape.
The lib.rs `AsbVariant` / `AsbStatus` / `RuntimeValue` stubs are replaced by
the real types via `pub use`. 25 new unit tests cover the proven matrix:
scalar + array round-trip, byte layout (2/4/4/payload), `Unsupported`
fallback for declared-but-unproven types, short-frame rejection,
malformed `string[]` partial-decode preservation matching .NET behavior.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Last codec-side prerequisite before F13 (NmxClient high-level write
wrappers) can land. Two small additions, both wire-byte-direct ports
of the .NET reference's MxDataType → MxValueKind lookup logic.
mxaccess-codec
- MxValueKind::for_data_type(MxDataType, is_array) -> Option<MxValueKind>:
fuses NmxWriteMessage.cs:58-86 (TryGetValueKind's 12 base mappings
for data types 1..=6 scalar+array) with the two scalar fallbacks the
.NET GalaxyTagMetadata.ProjectWriteValue layers on top
(GalaxyRepositoryTagResolver.cs:65-69): ElapsedTime → Int32,
InternationalizedString → String. Returns None for any other
combination — including arrays of those two types and unsupported
scalars (ReferenceType, StatusType, Enum, etc.).
- 6 new tests covering the base table, both fallbacks, the array-of-
unsupported rejection, and the no-mapping branch for ReferenceType /
StatusType / Enum / DataQualityType / BigString / Unknown / NoData /
End sentinels.
mxaccess-galaxy
- GalaxyTagMetadata::resolve_write_kind() -> Result<MxValueKind,
UnsupportedDataType>: pure delegation to MxValueKind::for_data_type
+ a typed error carrying (mx_data_type, is_array) for diagnostics.
- GalaxyTagMetadata::is_writable() — Ok-side accessor for browse UIs.
- UnsupportedDataType public error type (re-exported from lib.rs).
- 7 new tests: Double scalar → Float64, Boolean array → BoolArray,
ElapsedTime scalar → Int32 (the fallback path), array-of-ElapsedTime
rejected, InternationalizedString → String, ReferenceType rejected,
Unknown sentinel rejected.
Test count delta: 446 -> 459 (+13; codec 215 -> 221, galaxy 49 -> 56).
All four DoD gates green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>