BitArray32 and BitArray64
C# already has two bit array types, but both are lacking. BitArray
is a class
so it requires heap allocation and GC. BitVector32
is a struct
, but it’s usage is bizzare, it’s implemented inefficiently, it’s not enumerable, and there’s no 64-bit version. Today we’ll create a new, simple type to remedy these issues and add a new tool to our toolbox!
Goals
Our goal is to create a better version of BitArray
and BitVector32
. We want a type with 32- and 64-bit versions, doesn’t require heap allocation, uses assertions for bounds checks, supports foreach
, doesn’t trigger the GC, supports Unity jobs, and allows for direct access to the bits it holds.
We’ll call our types BitArray32
and BitArray64
. The following table illustrates how these new types compare with the .NET types:
Type | Length | Allocation | Bounds Checks | Foreach | GC | Job Support | Public Bits |
---|---|---|---|---|---|---|---|
BitArray | Arbitrary | Heap | Exceptions | Yes | Yes | No | No |
BitVector32 | 32 | Stack or Heap | Exceptions | No | No | Yes | No |
BitArray32 | 32 | Stack or Heap | Assertions | Yes if C# 7+ | No | Yes | Yes |
BitArray64 | 64 | Stack or Heap | Assertions | Yes if C# 7+ | No | Yes | Yes |
If we can achieve this goal, we’ll have two excellent new types in our toolbox.
BitVector32
As a class
, there’s really no hope for BitArray
. On the other hand, BitVector32
is a struct
and therefore might be a good option. Unfortunately, it suffers from some nasty issues that we’ll explore now.
First, and most prominently, is how its indexer works. Most users would expect an indexer to index into something. This is not how BitVector32
works. Instead, its indexer takes a mask parameter rather than an index. This leads to the following bizarre behavior:
// Initially all zeroes BitVector32 vec = new BitVector32(0); // This is unexpectedly true! Here's why: // return (bits & mask) == mask; // return (0 & 0) == 0; // return 0 == 0; // return true; Debug.Log(vec[0]); // False as expected // return (bits & mask) == mask; // return (0 & 1) == 1; // return 0 == 1; // return false; Debug.Log(vec[1]); // False as expected // return (bits & mask) == mask; // return (0 & 2) == 2; // return 0 == 2; // return false; Debug.Log(vec[2]); // Doesn't set the bit at index 7 // Sets the mask of 7: 00000000000000000000000000000111 // So the low three bits are now unexpectedly set // bits |= mask; // bits = 0 | 7; // bits = 7; vec[7] = true; // Unexpectedly true // return (bits & mask) == mask; // return (7 & 0) == 0; // return 0 == 0; // return true; Debug.Log(vec[0]); // Unexpectedly true // return (bits & mask) == mask; // return (7 & 1) == 1; // return 1 == 1; // return true; Debug.Log(vec[1]); // Unexpectedly true // return (bits & mask) == mask; // return (7 & 2) == 2; // return 2 == 2; // return true; Debug.Log(vec[2]); // This is true, but not because the bit at index 7 is set to 1 // Only true because the bits at index 0, 1, and 2 are set to 1 // return (bits & mask) == mask; // return (7 & 7) == 7; // return 7 == 7; // return true; Debug.Log(vec[7]); // Unexpectedly set the bit at index 0 to 0: // bits &= ~mask; // bits = 7 & ~1; // bits = 7 & 0b11111111111111111111111111111110; // bits = 6; vec[1] = false; // This is now false because one of the bits at index 0, 1, and 2 is now 0: // return (bits & mask) == mask; // return (6 & 7) == 7; // return 6 == 7; // return false; Debug.Log(vec[7]);
Other than that, we have the following issues with BitVector32
:
- No
BitVector64
version for 64-bit long arrays - Bounds checks use exceptions, which don’t work in Burst-compiled jobs and aren’t stripped out of release builds
- Doesn’t implement
IEquatable<T>
, soEquals
always performs expensive type checks - Doesn’t have a
GetEnumerator
, so we can’t useforeach
to iterate over it ToString
usesStringBuilder
which creates an extra GC allocation. There’s no initial capacity passed to it, soStringBuilder
has to do dynamic resizing as characters are added.- The bits isn’t directly accessible, which means they can’t be used as
ref
,out
, orin
parameters
Implementation
The basics of what we need are very simple. First, we start with a struct
that simply wraps a uint
for 32-bits or ulong
for 64-bits and provides an indexer into its bits. Here’s how that looks for the 32-bit version:
public struct BitArray32 { public uint Bits; public bool this[int index] { get { uint mask = 1u << index; return (Bits & mask) == mask; } set { uint mask = 1u << index; if (value) { Bits |= mask; } else { Bits &= ~mask; } } } }
Now we can start adding on to this core. First, we need assertion-based bounds checks so let’s add a method for that:
[BurstDiscard] public void RequireIndexInBounds(int index) { Assert.IsTrue( index >= 0 && index < 32, "Index out of bounds: " + index); }
Normally we couldn’t use this method in Burst-compiled jobs, but adding [BurstDiscard]
allows us to circumvent that restriction.
Now we can call this from the indexer:
public bool this[int index] { get { RequireIndexInBounds(index); uint mask = 1u << index; return (Bits & mask) == mask; } set { RequireIndexInBounds(index); uint mask = 1u << index; if (value) { Bits |= mask; } else { Bits &= ~mask; } } }
The next expansion is to provide a way to avoid that if
in the set
block of the indexer for when the value to set is known at compile time. Let’s create two methods: one to set to 1
and one to unset to 0
.
public void SetBit(int index) { RequireIndexInBounds(index); uint mask = 1u << index; Bits |= mask; } public void UnsetBit(int index) { RequireIndexInBounds(index); uint mask = 1u << index; Bits &= ~mask; }
Next, we’ll add methods to get and set multiple bits at once. These avoid the need to perform any bounds checking and to call the indexer over and over.
public uint GetBits(uint mask) { return Bits & mask; } public void SetBits(uint mask) { Bits |= mask; } public void UnsetBits(uint mask) { Bits &= ~mask; }
By default, the array is made up of all zeroes because the default value of uint
and ulong
is 0
. Let’s provide a constructor to change that default:
public BitArray32(uint bits) { Bits = bits; }
Next, we’ll add a Length
property to mimick the interface of C#’s managed arrays:
public int Length { get { return 32; } }
Now we can override object.Equals
and provide a more efficient Equals
using the IEquatable<BitArray32>
interface. Both of them just compare the Bits
field, but the object.Equals
version also has to perform a type check and cast.
public struct BitArray32 : IEquatable<BitArray32> { public override bool Equals(object obj) { return obj is BitArray32 && Bits == ((BitArray32)obj).Bits; } public bool Equals(BitArray32 arr) { return Bits == arr.Bits; } }
We’ll also need a GetHashCode
that just returns the result of GetHashCode
on the underlying Bits
:
public override int GetHashCode() { return Bits.GetHashCode(); }
And the last piece of boilerplate is to provide a ToString
that prints out the bits as 0
or 1
characters. Rather than using a StringBuilder
like in BitVector32
, we’ll simply using a char[]
since we know exactly how long it’ll be:
public override string ToString() { const string header = "BitArray32{"; const int headerLen = 11; // must be header.Length char[] chars = new char[headerLen + 32 + 1]; int i = 0; for (; i < headerLen; ++i) { chars[i] = header[i]; } for (uint num = 1u << 31; num > 0; num >>= 1, ++i) { chars[i] = (Bits & num) != 0 ? '1' : '0'; } chars[i] = '}'; return new string(chars); }
Finally, let’s add support for foreach
loops. To do so, let’s create a nested Enumerator
type with the required MoveNext
method and Current
property. This is a ref struct
since it includes a pointer directly to the Bits
field of the array and we don’t want that pointer to be invalidated by having the array go out of scope before the Enumerator
does. Since ref struct
is only available in C# 7 and later, we wrap this with #if CSHARP_7_OR_LATER
to avoid compiler errors in older versions of Unity.
#if CSHARP_7_OR_LATER public unsafe ref struct Enumerator { private readonly uint* m_Bits; private int m_Index; public Enumerator(uint* bits) { m_Bits = bits; m_Index = -1; } public bool MoveNext() { m_Index++; return m_Index < 32; } public bool Current { get { RequireIndexInBounds(); uint mask = 1u << m_Index; return (*m_Bits & mask) == mask; } } [BurstDiscard] public void RequireIndexInBounds() { Assert.IsTrue( m_Index >= 0 && m_Index < 32, "Index out of bounds: " + m_Index); } } #endif
The counterpart to this is to add a GetEnumerator
method to BitArray32
:
#if CSHARP_7_OR_LATER public unsafe Enumerator GetEnumerator() { // Safe because Enumerator is a 'ref struct' fixed (uint* bits = &Bits) { return new Enumerator(bits); } } #endif
Source
Here’s the full source for BitArray32
, complete with thorough xml-doc comments:
using System; using Unity.Burst; using UnityEngine.Assertions; /// <summary> /// Array of 32 bits. Fully unmanaged. Defaults to zeroes. Enumerable in C# 7. /// </summary> /// /// <author> /// Jackson Dunstan, https://JacksonDunstan.com/articles/5172 /// </author> /// /// <license> /// MIT /// </license> public struct BitArray32 : IEquatable<BitArray32> { #if CSHARP_7_OR_LATER /// <summary> /// Enumerates the bits of the array from least-significant to /// most-signficant. It's OK to change the array while enumerating. /// </summary> public unsafe ref struct Enumerator { /// <summary> /// Pointer to the bits /// </summary> private readonly uint* m_Bits; /// <summary> /// Index into the bits /// </summary> private int m_Index; /// <summary> /// Create the enumerator with index at -1 /// </summary> /// /// <param name="bits"> /// Bits to enumerate /// </param> public Enumerator(uint* bits) { m_Bits = bits; m_Index = -1; } /// <summary> /// Move to the next bit /// </summary> /// /// <returns> /// If a bit is available via <see cref="Current"/>. If not, enumeration /// is done. /// </returns> public bool MoveNext() { m_Index++; return m_Index < 32; } /// <summary> /// Get the current bit. If <see cref="MoveNext"/> has not been called /// or the last call of <see cref="MoveNext"/> returned false, this /// function asserts. /// </summary> /// /// <value> /// The current bit /// </value> public bool Current { get { RequireIndexInBounds(); uint mask = 1u << m_Index; return (*m_Bits & mask) == mask; } } /// <summary> /// Assert if <see cref="m_Index"/> isn't in bounds /// </summary> [BurstDiscard] public void RequireIndexInBounds() { Assert.IsTrue( m_Index >= 0 && m_Index < 32, "Index out of bounds: " + m_Index); } } #endif /// <summary> /// Integer whose bits make up the array /// </summary> public uint Bits; /// <summary> /// Create the array with the given bits /// </summary> /// /// <param name="bits"> /// Bits to make up the array /// </param> public BitArray32(uint bits) { Bits = bits; } /// <summary> /// Get or set the bit at the given index. For faster getting of multiple /// bits, use <see cref="GetBits(uint)"/>. For faster setting of single /// bits, use <see cref="SetBit(int)"/> or <see cref="UnsetBit(int)"/>. For /// faster setting of multiple bits, use <see cref="SetBits(uint)"/> or /// <see cref="UnsetBits(uint)"/>. /// </summary> /// /// <param name="index"> /// Index of the bit to get or set /// </param> public bool this[int index] { get { RequireIndexInBounds(index); uint mask = 1u << index; return (Bits & mask) == mask; } set { RequireIndexInBounds(index); uint mask = 1u << index; if (value) { Bits |= mask; } else { Bits &= ~mask; } } } /// <summary> /// Get the length of the array /// </summary> /// /// <value> /// The length of the array. Always 32. /// </value> public int Length { get { return 32; } } /// <summary> /// Set a single bit to 1 /// </summary> /// /// <param name="index"> /// Index of the bit to set. Asserts if not on [0:31]. /// </param> public void SetBit(int index) { RequireIndexInBounds(index); uint mask = 1u << index; Bits |= mask; } /// <summary> /// Set a single bit to 0 /// </summary> /// /// <param name="index"> /// Index of the bit to unset. Asserts if not on [0:31]. /// </param> public void UnsetBit(int index) { RequireIndexInBounds(index); uint mask = 1u << index; Bits &= ~mask; } /// <summary> /// Get all the bits that match a mask /// </summary> /// /// <param name="mask"> /// Mask of bits to get /// </param> /// /// <returns> /// The bits that match the given mask /// </returns> public uint GetBits(uint mask) { return Bits & mask; } /// <summary> /// Set all the bits that match a mask to 1 /// </summary> /// /// <param name="mask"> /// Mask of bits to set /// </param> public void SetBits(uint mask) { Bits |= mask; } /// <summary> /// Set all the bits that match a mask to 0 /// </summary> /// /// <param name="mask"> /// Mask of bits to unset /// </param> public void UnsetBits(uint mask) { Bits &= ~mask; } /// <summary> /// Check if this array equals an object /// </summary> /// /// <param name="obj"> /// Object to check. May be null. /// </param> /// /// <returns> /// If the given object is a BitArray32 and its bits are the same as this /// array's bits /// </returns> public override bool Equals(object obj) { return obj is BitArray32 && Bits == ((BitArray32)obj).Bits; } /// <summary> /// Check if this array equals another array /// </summary> /// /// <param name="arr"> /// Array to check /// </param> /// /// <returns> /// If the given array's bits are the same as this array's bits /// </returns> public bool Equals(BitArray32 arr) { return Bits == arr.Bits; } /// <summary> /// Get the hash code of this array /// </summary> /// /// <returns> /// The hash code of this array, which is the same as /// the hash code of <see cref="Bits"/>. /// </returns> public override int GetHashCode() { return Bits.GetHashCode(); } /// <summary> /// Get a string representation of the array /// </summary> /// /// <returns> /// A newly-allocated string representing the bits of the array. /// </returns> public override string ToString() { const string header = "BitArray32{"; const int headerLen = 11; // must be header.Length char[] chars = new char[headerLen + 32 + 1]; int i = 0; for (; i < headerLen; ++i) { chars[i] = header[i]; } for (uint num = 1u << 31; num > 0; num >>= 1, ++i) { chars[i] = (Bits & num) != 0 ? '1' : '0'; } chars[i] = '}'; return new string(chars); } /// <summary> /// Assert if the given index isn't in bounds /// </summary> /// /// <param name="index"> /// Index to check /// </param> [BurstDiscard] public void RequireIndexInBounds(int index) { Assert.IsTrue( index >= 0 && index < 32, "Index out of bounds: " + index); } #if CSHARP_7_OR_LATER /// <summary> /// Get an enumerator for this array's bits /// </summary> /// /// <returns> /// An enumerator for this array's bits /// </returns> public unsafe Enumerator GetEnumerator() { // Safe because Enumerator is a 'ref struct' fixed (uint* bits = &Bits) { return new Enumerator(bits); } } #endif }
And the full source for BitArray64
:
using System; using Unity.Burst; using UnityEngine.Assertions; /// <summary> /// Array of 64 bits. Fully unmanaged. Defaults to zeroes. Enumerable in C# 7. /// </summary> /// /// <author> /// Jackson Dunstan, https://JacksonDunstan.com/articles/5172 /// </author> /// /// <license> /// MIT /// </license> public struct BitArray64 : IEquatable<BitArray64> { #if CSHARP_7_OR_LATER /// <summary> /// Enumerates the bits of the array from least-significant to /// most-signficant. It's OK to change the array while enumerating. /// </summary> public unsafe ref struct Enumerator { /// <summary> /// Pointer to the bits /// </summary> private readonly ulong* m_Bits; /// <summary> /// Index into the bits /// </summary> private int m_Index; /// <summary> /// Create the enumerator with index at -1 /// </summary> /// /// <param name="bits"> /// Bits to enumerate /// </param> public Enumerator(ulong* bits) { m_Bits = bits; m_Index = -1; } /// <summary> /// Move to the next bit /// </summary> /// /// <returns> /// If a bit is available via <see cref="Current"/>. If not, enumeration /// is done. /// </returns> public bool MoveNext() { m_Index++; return m_Index < 64; } /// <summary> /// Get the current bit. If <see cref="MoveNext"/> has not been called /// or the last call of <see cref="MoveNext"/> returned false, this /// function asserts. /// </summary> /// /// <value> /// The current bit /// </value> public bool Current { get { RequireIndexInBounds(); ulong mask = 1ul << m_Index; return (*m_Bits & mask) == mask; } } /// <summary> /// Assert if <see cref="m_Index"/> isn't in bounds /// </summary> [BurstDiscard] public void RequireIndexInBounds() { Assert.IsTrue( m_Index >= 0 && m_Index < 64, "Index out of bounds: " + m_Index); } } #endif /// <summary> /// Integer whose bits make up the array /// </summary> public ulong Bits; /// <summary> /// Create the array with the given bits /// </summary> /// /// <param name="bits"> /// Bits to make up the array /// </param> public BitArray64(ulong bits) { Bits = bits; } /// <summary> /// Get or set the bit at the given index. For faster getting of multiple /// bits, use <see cref="GetBits(ulong)"/>. For faster setting of single /// bits, use <see cref="SetBit(int)"/> or <see cref="UnsetBit(int)"/>. For /// faster setting of multiple bits, use <see cref="SetBits(ulong)"/> or /// <see cref="UnsetBits(ulong)"/>. /// </summary> /// /// <param name="index"> /// Index of the bit to get or set /// </param> public bool this[int index] { get { RequireIndexInBounds(index); ulong mask = 1ul << index; return (Bits & mask) == mask; } set { RequireIndexInBounds(index); ulong mask = 1ul << index; if (value) { Bits |= mask; } else { Bits &= ~mask; } } } /// <summary> /// Get the length of the array /// </summary> /// /// <value> /// The length of the array. Always 64. /// </value> public int Length { get { return 64; } } /// <summary> /// Set a single bit to 1 /// </summary> /// /// <param name="index"> /// Index of the bit to set. Asserts if not on [0:31]. /// </param> public void SetBit(int index) { RequireIndexInBounds(index); ulong mask = 1ul << index; Bits |= mask; } /// <summary> /// Set a single bit to 0 /// </summary> /// /// <param name="index"> /// Index of the bit to unset. Asserts if not on [0:31]. /// </param> public void UnsetBit(int index) { RequireIndexInBounds(index); ulong mask = 1ul << index; Bits &= ~mask; } /// <summary> /// Get all the bits that match a mask /// </summary> /// /// <param name="mask"> /// Mask of bits to get /// </param> /// /// <returns> /// The bits that match the given mask /// </returns> public ulong GetBits(ulong mask) { return Bits & mask; } /// <summary> /// Set all the bits that match a mask to 1 /// </summary> /// /// <param name="mask"> /// Mask of bits to set /// </param> public void SetBits(ulong mask) { Bits |= mask; } /// <summary> /// Set all the bits that match a mask to 0 /// </summary> /// /// <param name="mask"> /// Mask of bits to unset /// </param> public void UnsetBits(ulong mask) { Bits &= ~mask; } /// <summary> /// Check if this array equals an object /// </summary> /// /// <param name="obj"> /// Object to check. May be null. /// </param> /// /// <returns> /// If the given object is a BitArray64 and its bits are the same as this /// array's bits /// </returns> public override bool Equals(object obj) { return obj is BitArray64 && Bits == ((BitArray64)obj).Bits; } /// <summary> /// Check if this array equals another array /// </summary> /// /// <param name="arr"> /// Array to check /// </param> /// /// <returns> /// If the given array's bits are the same as this array's bits /// </returns> public bool Equals(BitArray64 arr) { return Bits == arr.Bits; } /// <summary> /// Get the hash code of this array /// </summary> /// /// <returns> /// The hash code of this array, which is the same as /// the hash code of <see cref="Bits"/>. /// </returns> public override int GetHashCode() { return Bits.GetHashCode(); } /// <summary> /// Get a string representation of the array /// </summary> /// /// <returns> /// A newly-allocated string representing the bits of the array. /// </returns> public override string ToString() { const string header = "BitArray64{"; const int headerLen = 11; // must be header.Length char[] chars = new char[headerLen + 64 + 1]; int i = 0; for (; i < headerLen; ++i) { chars[i] = header[i]; } for (ulong num = 1ul << 63; num > 0; num >>= 1, ++i) { chars[i] = (Bits & num) != 0 ? '1' : '0'; } chars[i] = '}'; return new string(chars); } /// <summary> /// Assert if the given index isn't in bounds /// </summary> /// /// <param name="index"> /// Index to check /// </param> [BurstDiscard] public void RequireIndexInBounds(int index) { Assert.IsTrue( index >= 0 && index < 64, "Index out of bounds: " + index); } #if CSHARP_7_OR_LATER /// <summary> /// Get an enumerator for this array's bits /// </summary> /// /// <returns> /// An enumerator for this array's bits /// </returns> public unsafe Enumerator GetEnumerator() { // Safe because Enumerator is a 'ref struct' fixed (ulong* bits = &Bits) { return new Enumerator(bits); } } #endif }
Usage
Now that we have these types, let’s look at how to use them. In this example we’ll see how to use a BitArray32
in a Unity job. This isn’t possible with BitArray
or BitVector32
, so it’s a clear win for our newly-created type. In doing so we’ll also see a foreach
loop, which isn’t supported by BitVector32
. At no point in this example do we ever allocate managed memory for the GC to later collect.
// A Burst-compiled job that counts the number of bits set [BurstCompile] struct CountBitsJob : IJob { // Bits to count [ReadOnly] public BitArray32 BitArray; // The count is written to the first element [WriteOnly] public NativeArray<int> NumBitsSet; public void Execute() { // Clear count NumBitsSet[0] = 0; // Loop over the bits in the array // Possible because we have GetEnumerator() and Enumerator // Burst unrolls this to 32 copies of the loop body foreach (bool bit in BitArray) { // Count the bit if it's set // Burst replaces this branch with an increment of 0 or 1 if (bit) { NumBitsSet[0]++; } } } } // Create the array with the low bits set to 101 BitArray32 arr = new BitArray32(5); // Create the output for the count NativeArray<int> num = new NativeArray<int>(1, Allocator.TempJob); // Create and run the job CountBitsJob job = new CountBitsJob { BitArray = arr, NumBitsSet = num }; job.Execute(); // Get the result int count = num[0]; // Dispose the count output num.Dispose();
Hopefully the usage of BitArray32
here is clear and simple, unlike in BitVector32
with its masking indexer. Still, if we make a mistake then the bounds-check assertions will immediately alert us to the problem in debug builds. They’re stripped out of release builds, so we incur zero overhead in the shipping version of our game.
Conclusion
BitArray32
and BitArray64
are nice new additions to our toolbox. There are surely times where their usage doesn’t make sense, such as when we want more than 64 bits in the array or a dynamic number of bits, but in many common cases they can be quite handy. By supporting Unity jobs and foreach
without the clumbsy interfaces and GC of .NET alternatives, they usually provide a better option than BitArray
and BitVector32
. Hopefully you’ll find them useful!
#1 by Mike on March 11th, 2019 ·
Good stuff as always! I’m curious why the ToString method was done like that. Wouldn’t something like
Convert.ToString( self, 2 ).PadLeft( 32, '0' )
do the same with a lot less code?#2 by jackson on March 11th, 2019 ·
ToString
was implemented like that primarily to save on managed (a.k.a. GC) allocations.Convert.ToString
is implemented by creating abyte[]
,StringBuilder
(which internally creates aString[]
), and finally aString
.String.PadLeft
is implemented by creating anotherString
. In the article, I create only achar[]
and aString
which is a significant savings. I could have gone one step further and usedstackalloc
to remove thestring[]
allocation, but that would have required theunsafe
keyword. Given that theEnumerator
already requires that, it might be a good option for further improvement.#3 by Mike on March 13th, 2019 ·
Gotcha! Makes sense. I usually leave my ToString implementations ugly and non-performant but it sounds like you are more diligent than I! Keep up the great work on the blog posts.
#4 by Chris Ochs on March 12th, 2019 ·
There is a trick for working with BitVector32, depending on the use case. Which is to create your masks up front then index through those. Not as functional as your version but I’ll post it here just for reference.
https://gist.github.com/gamemachine/fa08afc83c8376cf9ca8fe52e1f19a94
#5 by Nate Aschenbach on October 29th, 2019 ·
Hi, thanks for these amazing articles!
I was a little confused about the use of foreach within an IJob.Execute… my understanding from the documentation (and my own messing around) is that foreach isn’t currently supported for Burst?
Is this somehow made possible by the way you’ve implemented GetEnumerator() and Enumerator?
Thanks!
#6 by jackson on October 31st, 2019 ·
I’m glad you’re enjoying the articles!
You’re right that foreach isn’t allowed in a Burst-compiled job, but this code does compile with Unity 2019.2.9f1 and Burst 1.1.2. I’m not entirely sure why, but it could have to do with the
Enumerator
type being aref struct
and therefore not implementingIDisposable
.