Files
cs/Zap.Net.Runtime/SegmentAllocator.cs
zeekay 2aa8261f86 rebrand: complete capnp → ZAP across all C# source/build files
Finishes the in-flight capnp→ZAP rebrand on this branch. The prior commits
renamed identifiers/namespaces/files/projects (Capnp.Net.Runtime→Zap.Net.Runtime,
CapnpC→ZapC, .capnp→.zap) but left the human-readable brand name "Cap'n Proto"
throughout doc-comments, .csproj/.nuspec descriptions, README, MSBuild .xaml
rule display names, and the generated XML doc. This completes brand neutrality:

- Replace 154 "Cap'n Proto" / 4 "Cap'n-Proto" / 1 "Cap'n'p" prose occurrences
  with "ZAP" across 32 first-party files (runtime, generator, zapc-csharp,
  MSBuild integration, chocolatey nuspecs, README, docs, XML doc).
- Revert the LICENSE provenance URL to the real upstream fork
  (c80k/capnproto-dotnetcore) — upstream-fork license headers must stay.
- Canonicalize project/repo URLs to github.com/zap-proto/cs (were fabricated
  c80k/zap-dotnetcore) in csproj/nuspec/VERIFICATION/README badges.
- Canonicalize docs URLs zap-protocol(.github.io|/zap-cs|/zap-cpp-core)
  → zap-proto.dev / github.com/zap-proto/{cs,cpp-core}.
- Collapse redundant "zap \"ZAP\"" PackageTags to "zap".

Verified consistent (no toolchain on host): every .sln/.csproj ProjectReference
resolves; all namespaces are Zap*/ZapC*; MSBuild task chain
(GenerateZapFileCodeBehindTask, ZapFiles ItemGroup, _ZapcCsharp_TaskAssembly)
is uniformly renamed; props/targets/xaml/coverage/chocolatey carry no capnp.

Deliberately preserved as upstream-derived test data (not first-party source):
upstream Cap'n Proto schema fixtures (rpc.zap, rpc-twoparty.zap, json.zap,
rpc-csharp.zap), golden compiler-error outputs (errors.txt), and the
machine-generated DO-NOT-EDIT test.zap.{c++,h}. Their "Cap'n Proto" strings are
the literal output of the external schema compiler the compat test matches.
2026-06-19 23:46:57 -07:00

146 lines
5.2 KiB
C#

using System;
using System.Collections.Generic;
namespace Zap
{
/// <summary>
/// The segment allocator default implementation.
/// </summary>
public class SegmentAllocator : ISegmentAllocator
{
class Segment
{
public Segment(Memory<ulong> mem, uint id)
{
Mem = mem;
Id = id;
}
public uint Id { get; }
public Memory<ulong> Mem { get; }
public int FreeOffset { get; set; }
public bool TryAllocate(uint nwords, out int offset)
{
if (checked(FreeOffset + (int)nwords) <= Mem.Length)
{
offset = FreeOffset;
int count = (int)nwords;
FreeOffset += count;
return true;
}
else
{
offset = -1;
return false;
}
}
public bool IsFull => FreeOffset >= Mem.Length;
}
readonly int _defaultSegmentSize;
readonly List<Segment> _segments = new List<Segment>();
readonly List<Segment> _nonFullSegments = new List<Segment>();
/// <summary>
/// Constructs an instance.
/// </summary>
/// <param name="defaultSegmentSize">Default size (in words) of a newly allocated segment. If a single allocation requires
/// a bigger size, a bigger dedicated segment will be allocated. On the wire, segments will be truncated to their actual
/// occupancies.</param>
public SegmentAllocator(int defaultSegmentSize = 64)
{
_defaultSegmentSize = defaultSegmentSize;
}
/// <summary>
/// The list of currently allocated segments, each one truncated to its actual occupancy.
/// </summary>
public IReadOnlyList<Memory<ulong>> Segments => _segments.LazyListSelect(s => s.Mem.Slice(0, s.FreeOffset));
/// <summary>
/// Allocates memory.
/// </summary>
/// <param name="nwords">Number of words to allocate</param>
/// <param name="preferredSegmentIndex">Preferred segment index. If enough space is available,
/// memory will be allocated inside that segment. Otherwise, a different segment will be chosen, or
/// a new one will be allocated, or allocation will fail (depending on <paramref name="forcePreferredSegment"/>).</param>
/// <param name="result">The allocated memory slice in case of success (<code>default(SegmentSlice) otherwise)</code></param>
/// <param name="forcePreferredSegment">Whether using the preferred segment is mandatory. If it is and there is not
/// enough space available, allocation will fail.</param>
/// <returns>Whether allocation was successful.</returns>
public bool Allocate(uint nwords, uint preferredSegmentIndex, out SegmentSlice result, bool forcePreferredSegment)
{
result = default;
Segment segment;
if (preferredSegmentIndex < _segments.Count)
{
segment = _segments[(int)preferredSegmentIndex];
if (segment.TryAllocate(nwords, out result.Offset))
{
result.SegmentIndex = preferredSegmentIndex;
return true;
}
}
if (forcePreferredSegment)
{
return false;
}
for (int i = 0; i < _nonFullSegments.Count; i++)
{
segment = _nonFullSegments[i];
if (segment.TryAllocate(nwords, out result.Offset))
{
result.SegmentIndex = segment.Id;
if (segment.IsFull)
{
int n = _nonFullSegments.Count - 1;
var tmp = _nonFullSegments[i];
_nonFullSegments[i] = _nonFullSegments[n];
_nonFullSegments[n] = tmp;
_nonFullSegments.RemoveAt(n);
}
else if (i > 0)
{
var tmp = _nonFullSegments[i];
_nonFullSegments[i] = _nonFullSegments[0];
_nonFullSegments[0] = tmp;
}
return true;
}
}
int size = Math.Max((int)nwords, _defaultSegmentSize);
var storage = new ulong[size];
var mem = new Memory<ulong>(storage);
segment = new Segment(mem, (uint)_segments.Count);
_segments.Add(segment);
if (!segment.TryAllocate(nwords, out result.Offset))
throw new InvalidProgramException();
result.SegmentIndex = segment.Id;
if (!segment.IsFull)
{
_nonFullSegments.Add(segment);
int n = _nonFullSegments.Count - 1;
var tmp = _nonFullSegments[0];
_nonFullSegments[0] = _nonFullSegments[n];
_nonFullSegments[n] = tmp;
}
return true;
}
}
}