using System;
using System.Collections.Generic;
namespace Capnp
{
///
/// The segment allocator default implementation.
///
public class SegmentAllocator : ISegmentAllocator
{
class Segment
{
public Segment(Memory mem, uint id)
{
Mem = mem;
Id = id;
}
public uint Id { get; }
public Memory 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 _segments = new List();
readonly List _nonFullSegments = new List();
///
/// Constructs an instance.
///
/// 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.
public SegmentAllocator(int defaultSegmentSize = 64)
{
_defaultSegmentSize = defaultSegmentSize;
}
///
/// The list of currently allocated segments, each one truncated to its actual occupancy.
///
public IReadOnlyList> Segments => _segments.LazyListSelect(s => s.Mem.Slice(0, s.FreeOffset));
///
/// Allocates memory.
///
/// Number of words to allocate
/// 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 ).
/// The allocated memory slice in case of success (default(SegmentSlice) otherwise)
/// Whether using the preferred segment is mandatory. If it is and there is not
/// enough space available, allocation will fail.
/// Whether allocation was successful.
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(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;
}
}
}