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