This adds support for writing to a StreamInterface, including a MappedBlockStream which might be discontiguous.
No code actually uses this yet, but I've added a bunch of unit tests to illustrate the idea and demonstrate that it works.
There were two unexpected complexities that arose with this patch.
First, due to the nature of how we need to allocate from a pool when a read breaks a stream boundary, things get complicated when you go and start writing. If you just write to the underlying stream (which could be discontiguous at the point you write), a person holding onto a pool pointer will now have a stale view of the contents.
Second, we previously assumed that any two reads from the same stream offset would have the same size (due to the nature of how streams are laid out as records that don't often change). This is a very inconvenient assumption when trying to write unit tests, where you may want to write something, read it back out, then write over it, then read it back out again. So I removed this assumption.
The result is somewhat convoluted, but the idea is this:
read: 1. try to read contiguously. If so, return it. 2. Otherwise check if there is a cached pool allocation that is the same size or bigger than the request. If so, return it. 3. Otherwise make a new pool allocation for the given offset and size, copy the data into it, and add it to the list of allocations for this offset. write: 1. Write all the data across the discontiguous sequence of blocks. 2. Iterate every cached allocation, looking for any allocation which overlaps this request. 3. For those which overlap, memcpy the overlapped portion of the cached allocation with the corresponding data from the new write
While this is O(n) in number of cached allocations, this is again most
common in tests, and will not be a frequent occurrence in real world
situations, so I think it's ok.