Files
K-C-Multiplayer/Riptide/Utils/Bitfield.cs
2025-12-14 21:08:19 +01:00

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;
}
}
}