// 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
{
/// Provides functionality for managing and manipulating a collection of bits.
internal class Bitfield
{
/// The first 8 bits stored in the bitfield.
internal byte First8 => (byte)segments[0];
/// The first 16 bits stored in the bitfield.
internal ushort First16 => (ushort)segments[0];
/// The number of bits which fit into a single segment.
private const int SegmentSize = sizeof(uint) * 8;
/// The segments of the bitfield.
private readonly List segments;
/// Whether or not the bitfield's capacity should dynamically adjust when shifting.
private readonly bool isDynamicCapacity;
/// The current number of bits being stored.
private int count;
/// The current capacity.
private int capacity;
/// Creates a bitfield.
/// Whether or not the bitfield's capacity should dynamically adjust when shifting.
internal Bitfield(bool isDynamicCapacity = true)
{
segments = new List(4) { 0 };
capacity = segments.Count * SegmentSize;
this.isDynamicCapacity = isDynamicCapacity;
}
/// Checks if the bitfield has capacity for the given number of bits.
/// The number of bits for which to check if there is capacity.
/// The number of bits from which there is no capacity for.
/// Whether or not there is sufficient capacity.
internal bool HasCapacityFor(int amount, out int overflow)
{
overflow = count + amount - capacity;
return overflow < 0;
}
/// Shifts the bitfield by the given amount.
/// How much to shift by.
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--;
}
}
/// Checks the last bit in the bitfield, and trims it if it is set to 1.
/// The checked bit's position in the bitfield.
/// Whether or not the checked bit was set.
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;
}
/// Trims all bits from the end of the bitfield until an unset bit is encountered.
private void Trim()
{
while (count > 0 && IsSet(count))
count--;
}
/// Sets the given bit to 1.
/// The bit to set.
/// is less than 1.
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;
}
/// Checks if the given bit is set to 1.
/// The bit to check.
/// Whether or not the bit is set.
/// is less than 1.
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;
}
/// Combines this bitfield with the given bits.
/// The bits to OR into the bitfield.
internal void Combine(ushort other)
{
segments[0] |= other;
}
}
}