Files

455 lines
22 KiB
Markdown

# MessagePack.pbi
PureBasic include for creating/reading MessagePack data.
**When integrating, do not read any `.pbi` files to save on tokens, use the API below.** \
When working on the include itself, see [Working on this repository](#working-on-this-repository).
## APIs
### Behaviour
* All the procedures take a `*MsgPackData` from `MsgPackCreate`, `MsgPackWrapMsgBuffer` or `MsgPackExamine`.
* Errors are reported through the internal error code, see `MsgPackLastError`.
* Writes and other procedures returning a `.MsgPack_ErrorCode` also return it directly.
* Reads returning a value give `0`, `""`, `#False`, `#Null` or `#MsgPack_InvalidSize` on errors,
which can also be valid values, so check `MsgPackLastError` after them.
* A successful read or write resets it to `#MsgPack_Error_Success`.
* Failed reads and writes don't move the internal offset, and write nothing.
* The internal offset is auto-incremented on all complete reads and writes.
* Does not apply to `MsgPackGetFormatCode`, `MsgPackGetExtensionType`, `MsgPackGetEntryCount` and `MsgPackGetFieldFullSize`.
* Can be disabled with the internal `#MsgPack_DataFlags_MoveOffsetAfterRead/Write` flags, honored by every read and write.
* The internal buffer is automatically grown as needed, unless it is owned by the caller.
* Invalid or truncated data never raises a `DebuggerError`, only programming errors do. (e.g.: `#Null` pointers)
* Views from `MsgPackExamine` are read-only, writes return `#MsgPack_Error_BufferIsReadOnly`.
### Type aliases/macros
* `.MsgPack_FormatCode` \
Represents the low-level format code from the specification. \
Fix* types are returned without the value/size bits they embed, e.g. `$A0` for any FixStr and `0` for positive fixints.
* `#MsgPack_FormatCode_Null`
* `#MsgPack_FormatCode_False`
* `#MsgPack_FormatCode_True`
* `#MsgPack_FormatCode_FixIntPositive`
* `#MsgPack_FormatCode_FixIntNegative`
* `#MsgPack_FormatCode_UInt8`
* `#MsgPack_FormatCode_UInt16`
* `#MsgPack_FormatCode_UInt32`
* `#MsgPack_FormatCode_UInt64`
* `#MsgPack_FormatCode_Int8`
* `#MsgPack_FormatCode_Int16`
* `#MsgPack_FormatCode_Int32`
* `#MsgPack_FormatCode_Int64`
* `#MsgPack_FormatCode_Float32`
* `#MsgPack_FormatCode_Float64`
* `#MsgPack_FormatCode_FixStr`
* `#MsgPack_FormatCode_Str8`
* `#MsgPack_FormatCode_Str16`
* `#MsgPack_FormatCode_Str32`
* `#MsgPack_FormatCode_Bin8`
* `#MsgPack_FormatCode_Bin16`
* `#MsgPack_FormatCode_Bin32`
* `#MsgPack_FormatCode_FixArray`
* `#MsgPack_FormatCode_Array16`
* `#MsgPack_FormatCode_Array32`
* `#MsgPack_FormatCode_FixMap`
* `#MsgPack_FormatCode_Map16`
* `#MsgPack_FormatCode_Map32`
* `#MsgPack_FormatCode_FixExt1`
* `#MsgPack_FormatCode_FixExt2`
* `#MsgPack_FormatCode_FixExt4`
* `#MsgPack_FormatCode_FixExt8`
* `#MsgPack_FormatCode_FixExt16`
* `#MsgPack_FormatCode_Ext8`
* `#MsgPack_FormatCode_Ext16`
* `#MsgPack_FormatCode_Ext32`
* `#MsgPack_FormatCode_Invalid`
* `.MsgPack_DataType` \
High-level representation of a format code. \
Should be used when iterating on complex objects to detect the members' general type.
* `#MsgPack_DataTypes_Unknown` - For `#MsgPack_FormatCode_Invalid`.
* `#MsgPack_DataTypes_Null`
* `#MsgPack_DataTypes_Boolean`
* `#MsgPack_DataTypes_Integer`
* `#MsgPack_DataTypes_Float`
* `#MsgPack_DataTypes_String`
* `#MsgPack_DataTypes_Binary`
* `#MsgPack_DataTypes_Array`
* `#MsgPack_DataTypes_Map`
* `#MsgPack_DataTypes_Extension`
* `.MsgPack_ErrorCode` \
* `#MsgPack_Error_Success = 0`
* `#MsgPack_Error_NullPointerGiven`
* `#MsgPack_Error_BufferTooSmall`
* `#MsgPack_Error_InvalidValue`
* `#MsgPack_Error_ValueOverflow`
* `#MsgPack_Error_FailedToGrowBuffer`
* `#MsgPack_Error_AtOrPastTheEndOfBuffer`
* `#MsgPack_Error_ReadPastEndOfBuffer`
* `#MsgPack_Error_InvalidFormatCode`
* `#MsgPack_Error_FailedToAllocateBuffer`
* `#MsgPack_Error_BinaryDataSizeIsNegative`
* `#MsgPack_Error_BinaryDataSizeIsTooBig`
* `#MsgPack_Error_OutputBufferTooSmall`
* `#MsgPack_Error_UnsupportedStringEncoding`
* `#MsgPack_Error_InvalidStringSize`
* `#MsgPack_Error_TooManyEntries`
* `#MsgPack_Error_OddMapFieldCount`
* `#MsgPack_Error_MaxDepthExceeded`
* `#MsgPack_Error_CannotGrowBufferDueToConfig`
* `#MsgPack_Error_BufferIsReadOnly`
* `.MsgPack_DataFlag` \
Internal flags, do not touch.
* `.MsgPack_ExaminationOption` \
Options for `MsgPackExamine`.
* `#MsgPack_ExaminationOptions_Unsafe`
### Constants & compiler options
* `#MsgPack_InvalidSize = -1` \
Returned by procedures that return a size, on errors.
* `#MsgPack_MaxDepth` \
Maximum nesting depth of arrays and maps, a top-level array or map being at depth 1. \
Defaults to `128`, can be defined before the inclusion, from `1` to `4096`. \
Deeper data is rejected with `#MsgPack_Error_MaxDepthExceeded`.
* `#MsgPack_DisableNullChecks` \
Disables the `#Null` pointer checks when defined as `#True` before the inclusion. \
Defaults to `#False`.
### Errors & format codes
* `MsgPackLastError.MsgPack_ErrorCode(*MsgPackData.MsgPackData)` \
Returns the error code set by the last read, write or navigation on `*MsgPackData`, `#MsgPack_Error_Success` if it succeeded. \
Returns `#MsgPack_Error_NullPointerGiven` if `*MsgPackData` is `#Null`.
* `MsgPackGetFormatCode.MsgPack_FormatCode(*MsgPackData.MsgPackData)` \
Returns the format code of the field at the current internal offset, without moving it or setting the internal error code. \
Returns `#MsgPack_FormatCode_Invalid` at the end of the buffer, or for the `0xC1` byte.
* `MsgPackFormatCodeToDataType.MsgPack_DataType(FormatCode.MsgPack_FormatCode)` \
Returns the data type of a format code from `MsgPackGetFormatCode`. \
Returns `#MsgPack_DataTypes_Unknown` for `#MsgPack_FormatCode_Invalid`.
### Allocation & ownership
* `MsgPackCreate.i(InitialSize.i, GrowthIncrement.i = 1)` \
Prepares an internal structure for a buffer of a given size `InitialSize.i`. \
The buffer grows in multiples of `GrowthIncrement` bytes, `1` growing it by the exact size needed by each write. \
Returns `#Null` on errors.
* `MsgPackWrapMsgBuffer.i(*Buffer, BufferSize.i, GiveOwnership.b = #False, GrowthIncrement.i = 1)` \
Wraps a given buffer `*Buffer` of a given size `BufferSize.i` into an internal structure, without copying it. \
If `GiveOwnership` is `#False`, the buffer stays owned by the caller, and is never grown or freed. \
If `GiveOwnership` is `#True`, `*Buffer` must come from `AllocateMemory()`, it can be grown, and freed with `MsgPackFree`. \
Returns `#Null` on errors.
* `MsgPackGrow.MsgPack_ErrorCode(*MsgPackData.MsgPackData, DesiredSizeIncrease.q)` \
Grows the buffer by `DesiredSizeIncrease` bytes, rounded up to a multiple of its `GrowthIncrement`. \
Called by the writes as needed, there's no need to call it beforehand. \
Returns `#MsgPack_Error_Success` on success, something else otherwise. (e.g.: `#MsgPack_Error_CannotGrowBufferDueToConfig` for buffers owned by the caller)
* `MsgPackFree.b(*MsgPackData.MsgPackData, FreeDataBuffer.b)` \
Frees a given internal structure `*MsgPackData` and its associated buffer if `FreeDataBuffer` is `#True`. \
`FreeDataBuffer` is mandatory, use `#False` for buffers wrapped with `MsgPackWrapMsgBuffer` that the caller still owns. \
Returns `#True` if freed properly, `#False` otherwise.
* `MsgPackFinalize.i(*MsgPackData.MsgPackData)` \
Frees a given internal structure `*MsgPackData` and returns its buffer, shrunk to the number of bytes written. \
The caller takes ownership of the buffer, gets its size with `MemorySize()` and frees it with `FreeMemory()`. \
Fails if the buffer is owned by the caller or nothing was written. \
Returns `#Null` on errors and leaves `*MsgPackData` untouched. \
See: `MsgPackLastError`
### Literals
* `MsgPackReadBoolean.b(*MsgPackData.MsgPackData)` \
Attempts to read the boolean at the current internal offset. \
Returns `#True` or `#False`, `#False` on errors too. \
See: `MsgPackLastError`
* `MsgPackWriteNull.MsgPack_ErrorCode(*MsgPackData.MsgPackData)` \
Attempts to write a nil at the current internal offset. \
Returns `#MsgPack_Error_Success` on success, something else otherwise.
* `MsgPackWriteTrue.MsgPack_ErrorCode(*MsgPackData.MsgPackData)` \
Attempts to write `#True` at the current internal offset. \
Returns `#MsgPack_Error_Success` on success, something else otherwise.
* `MsgPackWriteFalse.MsgPack_ErrorCode(*MsgPackData.MsgPackData)` \
Attempts to write `#False` at the current internal offset. \
Returns `#MsgPack_Error_Success` on success, something else otherwise.
* `MsgPackWriteBoolean.MsgPack_ErrorCode(*MsgPackData.MsgPackData, Boolean.b)` \
Writes `#True` or `#False` depending on `Boolean`. \
Returns `#MsgPack_Error_Success` on success, something else otherwise.
* `MsgPackReadNull.MsgPack_ErrorCode(*MsgPackData.MsgPackData)` \
Attempts to read a nil at the current internal offset. \
Returns `#MsgPack_Error_Success` on success, something else otherwise, in which case the offset isn't moved.
### Integers & Floats
* `MsgPackWriteInteger.MsgPack_ErrorCode(*MsgPackData.MsgPackData, Value.q)` \
Writes `Value` using the smallest int format code that fits, fixints included. \
Positive values always use the unsigned format codes. \
UInt64 values above `(2^63)-1` must be written with `MsgPackWriteUInt64`.
* `MsgPackWriteFixInt.MsgPack_ErrorCode(*MsgPackData.MsgPackData, Value.q)` \
Writes `Value` as a fixint, from `-32` to `127`. \
Returns `#MsgPack_Error_ValueOverflow` if it doesn't fit.
* `MsgPackWrite/Read[U]Int8/16/32/64`, `MsgPackWrite/ReadFloat32/64` \
Force the given format code. Aliases: `MsgPackWrite/ReadU8`, `I8`, ..., `F64`. \
`UInt32` and `UInt64` use `.l` and `.q`, so their values above the signed max come back negative.
* `MsgPackReadInteger.q(*MsgPackData.MsgPackData)` \
Reads any int field at the current internal offset, fixints included. \
Returns `0` on errors and sets the internal error code. \
UInt64 values above `(2^63)-1` fail with `#MsgPack_Error_ValueOverflow` without moving the offset, so they can be read with `MsgPackReadUInt64`. \
See: `MsgPackLastError`
* `MsgPackReadFloat.d(*MsgPackData.MsgPackData)` \
Reads any float field at the current internal offset. \
Returns `0` on errors and sets the internal error code. \
See: `MsgPackLastError`
### Binary
* `MsgPackWriteBinary.MsgPack_ErrorCode(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)` \
Writes `BufferSize` bytes from `*Buffer` using the smallest bin format code that fits. \
Returns `#MsgPack_Error_Success` on success, something else otherwise.
* `MsgPackWriteBin8/16/32.MsgPack_ErrorCode(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)` \
Same as `MsgPackWriteBinary`, but forces the given format code, even if a smaller one fits.
* `MsgPackReadBinary.q(*MsgPackData.MsgPackData, *OutputBuffer, OutputBufferSize.q)` \
Copies the bin field at the current internal offset into `*OutputBuffer`. \
Returns the number of bytes copied, or `#MsgPack_InvalidSize` on errors. \
The offset isn't moved on errors, so it can be retried with a bigger buffer. \
See: `MsgPackLastError`
* `MsgPackCopyBinary.i(*MsgPackData.MsgPackData)` \
Copies the bin field at the current internal offset into a new buffer that must be freed with `FreeMemory()`. \
Returns `#Null` on errors, or for empty bin fields with `#MsgPack_Error_Success` as the last error. \
See: `MsgPackLastError`
### Strings
Supported encodings: `#PB_UTF8` (default, per spec), `#PB_Ascii`, `#PB_Unicode` (UTF-16 LE).
* `MsgPackWriteString.MsgPack_ErrorCode(*MsgPackData.MsgPackData, String.s, Encoding.i = #PB_UTF8)` \
Writes `String` using the smallest str format code that fits its encoded byte size. \
Returns `#MsgPack_Error_Success` on success, something else otherwise.
* `MsgPackWriteFixStr/Str8/Str16/Str32.MsgPack_ErrorCode(*MsgPackData.MsgPackData, String.s, Encoding.i = #PB_UTF8)` \
Same as `MsgPackWriteString`, but forces the given format code.
* `MsgPackReadString.s(*MsgPackData.MsgPackData, Encoding.i = #PB_UTF8)` \
Reads any str field at the current internal offset. \
Returns `""` on errors and sets the internal error code. \
See: `MsgPackLastError`
### Extensions
`ExtType.b` is the signed ext type, `-128` to `-1` being reserved by the spec but not rejected. \
Size errors reuse the `#MsgPack_Error_BinaryDataSize*` codes.
* `MsgPackWriteExtension.MsgPack_ErrorCode(*MsgPackData.MsgPackData, ExtType.b, *Buffer, BufferSize.q)` \
Writes `BufferSize` bytes from `*Buffer` using fixext for 1/2/4/8/16 bytes, or the smallest ext format code otherwise. \
Returns `#MsgPack_Error_Success` on success, something else otherwise.
* `MsgPackWriteExt8/16/32.MsgPack_ErrorCode(*MsgPackData.MsgPackData, ExtType.b, *Buffer, BufferSize.q)` \
Same as `MsgPackWriteExtension`, but forces the given format code, even if a smaller one fits.
* `MsgPackWriteFixExt1/2/4/8/16.MsgPack_ErrorCode(*MsgPackData.MsgPackData, ExtType.b, *Buffer)` \
Writes the 1/2/4/8/16 bytes at `*Buffer` as the given fixext.
* `MsgPackGetExtensionType.b(*MsgPackData.MsgPackData)` \
Returns the type of the ext field at the current internal offset, without moving it. \
Returns `0` on errors and sets the internal error code. \
See: `MsgPackLastError`
* `MsgPackReadExtension.q(*MsgPackData.MsgPackData, *OutputBuffer, OutputBufferSize.q)` \
Same as `MsgPackReadBinary`, for the data of ext fields.
* `MsgPackCopyExtension.i(*MsgPackData.MsgPackData)` \
Same as `MsgPackCopyBinary`, for the data of ext fields.
### Navigation
Arrays and maps are handled iteratively, so deep nesting can't overflow the stack.
* `MsgPackGetFieldFullSize.q(*MsgPackData.MsgPackData)` \
Returns the full size in bytes of the field at the current internal offset, without moving it. \
Arrays and maps include all of their contained fields, recursively. \
Returns `#MsgPack_InvalidSize` on errors and sets the internal error code. \
See: `MsgPackLastError`
* `MsgPackGetEntryCount.q(*MsgPackData.MsgPackData)` \
Returns the number of entries in the array or map at the current internal offset, without moving it. \
A map entry is a key and a value, and counts as one. \
Returns `#MsgPack_InvalidSize` on errors and sets the internal error code.
* `MsgPackSkip.MsgPack_ErrorCode(*MsgPackData.MsgPackData, FieldCount.q = 1)` \
Moves the internal offset past the next `FieldCount` fields, the ones contained in arrays and maps included. \
Map keys and values count as separate fields. \
Returns `#MsgPack_Error_Success` on success, something else otherwise, in which case the offset isn't moved.
### Arrays & Maps
* `MsgPackExamine.i(*MsgPackData.MsgPackData, ExaminationOptions.MsgPack_ExaminationOption = 0)` \
Returns a view over the array or map at the current internal offset, to iterate on with `MsgPackNextEntry`. \
The view is a regular `MsgPackData` limited to the array/map's content, so all the regular read functions work on it, and can't read past it. \
It reads straight from `*MsgPackData`'s buffer, which must not be grown or freed while the view is used. \
The whole array/map is checked beforehand, and the offset of `*MsgPackData` is moved past it. \
Returns `#Null` on errors and sets the internal error code. \
See: `MsgPackLastError`
* `#MsgPack_ExaminationOptions_Unsafe` - **Not recommended !** \
Skips the content check, which is also what finds the array/map's end. \
The view can read past the array/map until `MsgPackNextEntry` reaches its end, invalid content is only
detected while iterating, and the offset of `*MsgPackData` isn't moved, use `MsgPackSkip` afterward.
* `MsgPackNextEntry.b(*Iterator.MsgPackData)` \
Moves the view's offset to its next entry, the first one on the first call. \
Whatever wasn't read from the current entry is skipped, nested arrays/maps included. \
Map entries are read key first, then value. \
Returns `#True` if an entry is available, `#False` at the end or on errors, with the internal error code set accordingly.
* `MsgPackFinish(*Iterator.MsgPackData)` \
Frees a view from `MsgPackExamine`, without touching the underlying buffer.
```purebasic
Define *Iterator.MsgPackData = MsgPackExamine(*MsgPack)
If *Iterator
While MsgPackNextEntry(*Iterator)
Key.s = MsgPackReadString(*Iterator)
If MsgPackFormatCodeToDataType(MsgPackGetFormatCode(*Iterator)) = #MsgPack_DataTypes_Map
*Nested = MsgPackExamine(*Iterator) ; Or leave it, it'll be skipped
; ...
MsgPackFinish(*Nested)
EndIf
Wend
MsgPackFinish(*Iterator)
EndIf
```
* `MsgPackWriteArray.MsgPack_ErrorCode(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)` \
Writes an array whose entries are the fields in the `BufferSize` bytes of MessagePack data at `*Buffer`, using the smallest array format code that fits. \
The data is fully checked and its fields are counted, nested arrays/maps counting as one. \
`*Buffer` can come from `MsgPackFinalize`, with `MemorySize()` as its size, but not from `*MsgPackData`'s own buffer. \
Returns `#MsgPack_Error_Success` on success, something else otherwise, with nothing written.
* `MsgPackWriteMap.MsgPack_ErrorCode(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)` \
Same as `MsgPackWriteArray`, for maps. \
The data must hold an even number of fields, alternating keys and values, or `#MsgPack_Error_OddMapFieldCount` is returned.
* `MsgPackWriteArrayFrom/MapFrom.MsgPack_ErrorCode(*MsgPackData.MsgPackData, *Source.MsgPackData)` \
Same as `MsgPackWriteArray/Map`, with the data written so far in `*Source`, which is left untouched.
* `MsgPackWriteFixArray/Array16/Array32/FixMap/Map16/Map32.MsgPack_ErrorCode(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)` \
Same as `MsgPackWriteArray/Map`, but forces the given format code. \
Returns `#MsgPack_Error_TooManyEntries` if the entries don't fit in it.
## Usage patterns
### Writing, exporting and reading
```purebasic
Define *Writer = MsgPackCreate(64)
If MsgPackWriteInteger(*Writer, 42) <> #MsgPack_Error_Success
; ...
EndIf
; Frees `*Writer`, the caller now owns `*Buffer`.
Define *Buffer = MsgPackFinalize(*Writer)
; Gives `*Buffer` back to a structure, freed along with it.
Define *Reader = MsgPackWrapMsgBuffer(*Buffer, MemorySize(*Buffer), #True)
Define Value.q = MsgPackReadInteger(*Reader)
If MsgPackLastError(*Reader) <> #MsgPack_Error_Success
; ...
EndIf
MsgPackFree(*Reader, #True)
```
### Building arrays & maps
Their content is written in a separate structure, then copied in with its fields counted.
```purebasic
Define *Fields = MsgPackCreate(32)
MsgPackWriteString(*Fields, "Key")
MsgPackWriteInteger(*Fields, 1)
MsgPackWriteMapFrom(*Writer, *Fields)
MsgPackFree(*Fields, #True)
```
Forcing their format code requires the content as a buffer, e.g. from `MsgPackFinalize`, for `MsgPackWriteFixArray/Array16/...`.
### Iterating over a buffer
The end of a buffer can't be queried, so either wrap the data in a top-level array/map,
or subtract each top-level field's `MsgPackGetFieldFullSize` from the buffer's size until it reaches `0`.
### Missing or asymmetric procedures
* Floats have no implicitly typed setter, use `MsgPackWriteFloat32/64`.
* Strings have no explicitly typed getters, `MsgPackReadString` reads all str format codes.
* Fixints have no explicitly typed getter, `MsgPackReadInteger` reads them.
## Remarks
* Never touch the `MsgPackData` structure unless you **absolutely** have to.
## Support
- Both the ASM and C backends
- x86, x64, arm64
## Working on this repository
### Layout
* `Includes/MessagePack.pbi` - Main file, only includes the others.
* `Includes/_*.pbi` - One file per family. (Allocators, Literals, Primitives, Strings, ...)
* `Includes/PB-Endianness/` - Git submodule, don't edit it from here.
* `UnitTests/` - One folder per family, numbered like the includes' order.
* `Examples/` - One folder per example, with a `.pb` of the same name.
* `PerfTests/` - Benchmarks, one folder per workload, each with a `RunPerfTests.ps1`.
* `SecurityTests/` - Private, git-ignored, leave it alone unless asked.
### Conventions
* `.pb`/`.pbi` files use CRLF line endings and ASCII only, `Chr()` for anything else.
* Includes use tabs, examples use 4 spaces.
* Private procedures and macros are prefixed with `_MsgPack`, public ones with `MsgPack`.
* Every public procedure is documented above its definition, and in this file.
* `#Null` pointers raise a `DebuggerError` behind `CompilerIf Not #MsgPack_DisableNullChecks`, invalid data only uses `Debug`.
### Unit tests
* Run with `RunTests.ps1`, which builds each test without the debugger, so `Debug` & co are compiled out.
* Each test includes `../_UnitTesting.pbi`, uses its `Assert*` procedures, and ends with `End 1` if `FailedUnitTests` isn't `0`.
* `*FatalErrors.pb` tests call `DebuggerError` on purpose, and only pass without the debugger.
* No PureBasic compiler is available in the cloud sandbox, ask the user to run the tests.
### Performance tests
* Run with the `RunPerfTests.ps1` of each folder, Windows only.
* They dot-source `PerfTests/_PerfTesting.ps1`, which builds the folder's `.pb` files without the debugger, with `/OPTIMIZER` on the C backend,
runs `PerfPython.py` and `DotNet/` if present, and saves the results in `PerfTests/Results/<Folder>_<Version>_<Architecture>-<Backend>.csv`.
* PureBasic 6.41 and 6.21 are configured in `$Compilers`, 6.21 being kept to compare the string handling changes of 6.40.
* Each implementation times its scenarios with the shared `_PerfTesting.pbi`/`.py`/`.cs`: one warm-up run, then the median of 5.
* Each program prints `Implementation,Scenario,MedianMs,NsPerOp,Check` lines on stdout, errors go to stderr.
* `Check` must be identical for a scenario across implementations, the scripts report mismatches.
* Each folder's workload is described in its `_Workload.pbi`, keep its `PerfPython.py` and `DotNet/Program.cs` in sync with it.
* Python packages are pinned in `PerfTests/requirements.txt`, MessagePack-CSharp in each `DotNetPerf.csproj`.
* The results tables are in `PerfTests/readme.md`, the conclusions in the main readme's "Performances" section, keep both in sync with new results.
* Open performance questions and their leads are in `PerfTests/Investigations.md`, update it when one is looked into.
### Examples
* Start with a `;- Notes` section, then `;- Example`.
* Check every result, and use a different `End` code for each failure.
* Link new examples in the readme's "Examples" section.