151 lines
6.0 KiB
C#
151 lines
6.0 KiB
C#
// This file is provided under The MIT License as part of RiptideNetworking.
|
|
// Copyright (c) Tom Weiland
|
|
// For additional information please see the included LICENSE.md file or view it on GitHub:
|
|
// https://github.com/RiptideNetworking/Riptide/blob/main/LICENSE.md
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace Riptide.Utils
|
|
{
|
|
/// <summary>Provides functionality for managing and manipulating a collection of bits.</summary>
|
|
internal class Bitfield
|
|
{
|
|
/// <summary>The first 8 bits stored in the bitfield.</summary>
|
|
internal byte First8 => (byte)segments[0];
|
|
/// <summary>The first 16 bits stored in the bitfield.</summary>
|
|
internal ushort First16 => (ushort)segments[0];
|
|
|
|
/// <summary>The number of bits which fit into a single segment.</summary>
|
|
private const int SegmentSize = sizeof(uint) * 8;
|
|
/// <summary>The segments of the bitfield.</summary>
|
|
private readonly List<uint> segments;
|
|
/// <summary>Whether or not the bitfield's capacity should dynamically adjust when shifting.</summary>
|
|
private readonly bool isDynamicCapacity;
|
|
/// <summary>The current number of bits being stored.</summary>
|
|
private int count;
|
|
/// <summary>The current capacity.</summary>
|
|
private int capacity;
|
|
|
|
/// <summary>Creates a bitfield.</summary>
|
|
/// <param name="isDynamicCapacity">Whether or not the bitfield's capacity should dynamically adjust when shifting.</param>
|
|
internal Bitfield(bool isDynamicCapacity = true)
|
|
{
|
|
segments = new List<uint>(4) { 0 };
|
|
capacity = segments.Count * SegmentSize;
|
|
this.isDynamicCapacity = isDynamicCapacity;
|
|
}
|
|
|
|
/// <summary>Checks if the bitfield has capacity for the given number of bits.</summary>
|
|
/// <param name="amount">The number of bits for which to check if there is capacity.</param>
|
|
/// <param name="overflow">The number of bits from <paramref name="amount"/> which there is no capacity for.</param>
|
|
/// <returns>Whether or not there is sufficient capacity.</returns>
|
|
internal bool HasCapacityFor(int amount, out int overflow)
|
|
{
|
|
overflow = count + amount - capacity;
|
|
return overflow < 0;
|
|
}
|
|
|
|
/// <summary>Shifts the bitfield by the given amount.</summary>
|
|
/// <param name="amount">How much to shift by.</param>
|
|
internal void ShiftBy(int amount)
|
|
{
|
|
int segmentShift = amount / SegmentSize; // How many WHOLE segments we have to shift by
|
|
int bitShift = amount % SegmentSize; // How many bits we have to shift by
|
|
|
|
if (!isDynamicCapacity)
|
|
count = Math.Min(count + amount, SegmentSize);
|
|
else if (!HasCapacityFor(amount, out int _))
|
|
{
|
|
Trim();
|
|
count += amount;
|
|
|
|
if (count > capacity)
|
|
{
|
|
int increaseBy = segmentShift + 1;
|
|
for (int i = 0; i < increaseBy; i++)
|
|
segments.Add(0);
|
|
|
|
capacity = segments.Count * SegmentSize;
|
|
}
|
|
}
|
|
else
|
|
count += amount;
|
|
|
|
int s = segments.Count - 1;
|
|
segments[s] <<= bitShift;
|
|
s -= 1 + segmentShift;
|
|
while (s > -1)
|
|
{
|
|
ulong shiftedBits = (ulong)segments[s] << bitShift;
|
|
segments[s] = (uint)shiftedBits;
|
|
|
|
segments[s + 1 + segmentShift] |= (uint)(shiftedBits >> SegmentSize);
|
|
s--;
|
|
}
|
|
}
|
|
|
|
/// <summary>Checks the last bit in the bitfield, and trims it if it is set to 1.</summary>
|
|
/// <param name="checkedPosition">The checked bit's position in the bitfield.</param>
|
|
/// <returns>Whether or not the checked bit was set.</returns>
|
|
internal bool CheckAndTrimLast(out int checkedPosition)
|
|
{
|
|
checkedPosition = count;
|
|
uint bitToCheck = (uint)(1 << ((count - 1) % SegmentSize));
|
|
bool isSet = (segments[segments.Count - 1] & bitToCheck) != 0;
|
|
count--;
|
|
return isSet;
|
|
}
|
|
|
|
/// <summary>Trims all bits from the end of the bitfield until an unset bit is encountered.</summary>
|
|
private void Trim()
|
|
{
|
|
while (count > 0 && IsSet(count))
|
|
count--;
|
|
}
|
|
|
|
/// <summary>Sets the given bit to 1.</summary>
|
|
/// <param name="bit">The bit to set.</param>
|
|
/// <exception cref="ArgumentOutOfRangeException"><paramref name="bit"/> is less than 1.</exception>
|
|
internal void Set(int bit)
|
|
{
|
|
if (bit < 1)
|
|
throw new ArgumentOutOfRangeException(nameof(bit), $"'{nameof(bit)}' must be greater than zero!");
|
|
|
|
bit--;
|
|
int s = bit / SegmentSize;
|
|
uint bitToSet = (uint)(1 << (bit % SegmentSize));
|
|
if (s < segments.Count)
|
|
segments[s] |= bitToSet;
|
|
}
|
|
|
|
/// <summary>Checks if the given bit is set to 1.</summary>
|
|
/// <param name="bit">The bit to check.</param>
|
|
/// <returns>Whether or not the bit is set.</returns>
|
|
/// <exception cref="ArgumentOutOfRangeException"><paramref name="bit"/> is less than 1.</exception>
|
|
internal bool IsSet(int bit)
|
|
{
|
|
if (bit > count)
|
|
return true;
|
|
|
|
if (bit < 1)
|
|
throw new ArgumentOutOfRangeException(nameof(bit), $"'{nameof(bit)}' must be greater than zero!");
|
|
|
|
bit--;
|
|
int s = bit / SegmentSize;
|
|
uint bitToCheck = (uint)(1 << (bit % SegmentSize));
|
|
if (s < segments.Count)
|
|
return (segments[s] & bitToCheck) != 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>Combines this bitfield with the given bits.</summary>
|
|
/// <param name="other">The bits to OR into the bitfield.</param>
|
|
internal void Combine(ushort other)
|
|
{
|
|
segments[0] |= other;
|
|
}
|
|
}
|
|
}
|