Skip to content

Instantly share code, notes, and snippets.

@cjddmut
Last active May 23, 2020 19:50
Show Gist options
  • Save cjddmut/cb43af3ee191af78363f41a3188c0f7b to your computer and use it in GitHub Desktop.
Save cjddmut/cb43af3ee191af78363f41a3188c0f7b to your computer and use it in GitHub Desktop.
C# struct based lists that can be created, passed around, and released without references or garbage.
/*
* Created by Galvanic Games (http://galvanicgames.com)
*
* The MIT License (MIT)
*
* Copyright (c) 2019
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
*
* ============= Description =============
*
* Create struct lists that contain unmanaged (NO REFERENCES) types that are passed around as value types. Behaves like
* any other struct and will not generate garbage when created and unscoped. Since a value type, they can be passed
* around without fear of a called function changing values unexpectedly.
*
* Uses unsafe pointer math so this file needs to be compiled using the /unsafe compiler flag. If working in Unity 3D
* then enable unsafe code in 'Player Settings > allow 'unsafe' code'.
*
* Also requires at least the .NET 4.x runtime which is available in Unity 2018.3
*
* There are four lists created below, each of different sizes (4, 8, 16, and 32). If a custom size is desired
* then duplicate one of the structs and update the number of value fields, the MAX_SIZE constant, and the struct name
* (including constructors and Equals method). The actual implementation will still work and should be left unchanged.
*
* Define DISABLE_VTL_BOUNDS_CHK to remove bounds checking for the list. Performance would be better but it WILL
* write over other memory if you overstep the bounds.
*
* Each list implements IList so the methods defined in that interface are available to be used with these struct lists.
*
* EXAMPLE:
*
* public struct MyStruct
* {
* public int intValue;
*
* ...
* }
*
* public class MyClass
* {
* public void MyFunction()
* {
* ValueTypeList4<MyStruct> list = new ValueTypeList4<MyStruct>();
* list.Add(new MyStruct(1));
* list.Add(new MyStruct(2));
* list.Add(new MyStruct(3));
* list.Add(new MyStruct(4));
*
* Print(list[0]) // 1
* Print(list[2]) // 3
*
* list.RemoveAt(2);
* Print(list[2]) // 4
*
*
* MyOtherFunction(list); // If this function modifies doesn't matter cause list is a value type and is copied
*
* } // Won't generate garbage when is out of scope cause it's allocated on the stack
* }
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public unsafe struct ValueTypeList4<T> : IEquatable<ValueTypeList4<T>>, IList<T> where T : unmanaged
{
// There are two routes I could take as far as I can tell. I can create a fixed byte array and fit as many
// items in as I can. Or just lay out a fixed number side by side. The former is cleaner code, the latter makes
// better use of space and will be more intuitive to work with. So went with latter. If there's a way to get a fixed
// array of items that's on the stack then that's the best of both worlds
//
// Unfortunately below doesn't work since sizeof(T) isn't known at compile time
// private fixed byte _buffer[MAX_SIZE * sizeof(T)];
//
// And this isn't allowed in C# for unmanaged types, just primitives
// private fixed T _items[MAX_SIZE];
private T _value0;
private T _value1;
private T _value2;
private T _value3;
public const int MAX_SIZE = 4;
#region Implementation
private int _count;
// Generated value by JetBrains
private const int HASHCODE_MULTIPLIER = 397;
private const int NO_INDEX = -1;
public T this[int index]
{
get
{
#if !DISABLE_VTL_BOUNDS_CHK
if (index < 0 || index >= _count)
{
throw new IndexOutOfRangeException();
}
#endif
fixed (T* pFirst = &_value0)
{
return *(pFirst + index);
}
}
set
{
#if !DISABLE_VTL_BOUNDS_CHK
if (index < 0 || index >= _count)
{
throw new IndexOutOfRangeException();
}
#endif
fixed (T* pFirst = &_value0)
{
*(pFirst + index) = value;
}
}
}
public int Count => _count;
public bool IsReadOnly => false;
public ValueTypeList4(T[] arr) : this()
{
AddRange(arr);
}
public ValueTypeList4(List<T> list) : this()
{
AddRange(list);
}
public IEnumerator<T> GetEnumerator()
{
for (int i = 0; i < _count; i++)
{
yield return this[i];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(T item)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (_count == MAX_SIZE)
{
throw new ValueTypeListFull();
}
#endif
fixed (T* pFirst = &_value0)
{
*(pFirst + _count) = item;
}
_count++;
}
public void AddRange(T[] arr)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (_count + arr.Length > MAX_SIZE)
{
throw new ValueTypeListFull();
}
#endif
fixed (T* pMe = &_value0, pThem = arr)
{
long copySizeInBytes = arr.Length * sizeof(T);
Buffer.MemoryCopy(pThem, pMe + _count, copySizeInBytes, copySizeInBytes);
}
_count += arr.Length;
}
public void AddRange(List<T> list)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (_count + list.Count > MAX_SIZE)
{
throw new ValueTypeListFull();
}
#endif
fixed (T* pFirst = &_value0)
{
int listCount = list.Count;
for (int i = 0; i < listCount; i++)
{
*(pFirst + i + _count) = list[i];
}
}
}
public void Clear()
{
_count = 0;
}
public bool Contains(T item)
{
return IndexOf(item) != NO_INDEX;
}
public void CopyTo(T[] array, int arrayIndex)
{
fixed (T* pMe = &_value0, pThem = array)
{
long sizeInBytes = _count * sizeof(T);
Buffer.MemoryCopy(
pMe,
pThem + arrayIndex,
sizeInBytes,
sizeInBytes);
}
}
public bool Remove(T item)
{
int index = IndexOf(item);
if (index != NO_INDEX)
{
RemoveAt(index);
return true;
}
return false;
}
public int IndexOf(T item)
{
fixed (T* pFirst = &_value0)
{
int size = sizeof(T);
for (int i = 0; i < _count; i++)
{
// Similarly, if we later force T to implement IEquatable<T> then this should be replaced with
// (pFirst + i)->Equals(item)
if (ValueTypeListUtil.MemoryCompare(pFirst + i, &item, size))
{
return i;
}
}
}
return NO_INDEX;
}
public void Insert(int index, T item)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (_count == MAX_SIZE)
{
throw new ValueTypeListFull();
}
#endif
fixed (T* pFirst = &_value0)
{
_count++;
for (int i = _count - 1; i >= index + 1; i--)
{
*(pFirst + i) = *(pFirst + i - 1);
}
*(pFirst + index) = item;
}
}
public void RemoveAt(int index)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (index < 0 || index >= _count)
{
throw new IndexOutOfRangeException();
}
#endif
fixed (T* pFirst = &_value0)
{
T* pItem = pFirst + index;
long copyAmountBytes = sizeof(T) * (_count - (index + 1));
Buffer.MemoryCopy(
pItem + 1,
pItem,
copyAmountBytes,
copyAmountBytes);
}
_count--;
}
public void UnstableRemoveAt(int index)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (index < 0 || index >= _count)
{
throw new IndexOutOfRangeException();
}
#endif
fixed (T* pFirst = &_value0)
{
*(pFirst + index) = *(pFirst + _count - 1);
}
_count--;
}
public bool Equals(ValueTypeList4<T> other)
{
if (_count != other._count)
{
return false;
}
// We do a memory compare since T may not implement IEquatable<T>, if that is too rigid then change
// to force T to implement IEquatable<T> and just call (pFirst + i)->Equals(item).
fixed (T* pFirst = &_value0)
{
// Below errors out with "You cannot use the fixed statement to take the address of an already fixed expression"
// So I interpret that as I'm already safe? But I don't fully follow or understand so research is needed.
// fixed(T* pFirstOther = &other._value0) {}
return ValueTypeListUtil.MemoryCompare(pFirst, &other._value0, _count * sizeof(T));
}
}
public override int GetHashCode()
{
// If T doesn't implement IEquatable<T> this will generate garbage
int hashCode = 0;
unchecked
{
fixed (T* pFirst = &_value0)
{
for (int i = 0; i < _count; i++)
{
hashCode = (hashCode * HASHCODE_MULTIPLIER) ^ ((pFirst + i)->GetHashCode());
}
}
}
return hashCode;
}
#endregion
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct ValueTypeList8<T> : IEquatable<ValueTypeList8<T>>, IList<T> where T : unmanaged
{
private T _value0;
private T _value1;
private T _value2;
private T _value3;
private T _value4;
private T _value5;
private T _value6;
private T _value7;
public const int MAX_SIZE = 8;
#region Implementation
private int _count;
// Generated value by JetBrains
private const int HASHCODE_MULTIPLIER = 397;
private const int NO_INDEX = -1;
public T this[int index]
{
get
{
#if !DISABLE_VTL_BOUNDS_CHK
if (index < 0 || index >= _count)
{
throw new IndexOutOfRangeException();
}
#endif
fixed (T* pFirst = &_value0)
{
return *(pFirst + index);
}
}
set
{
#if !DISABLE_VTL_BOUNDS_CHK
if (index < 0 || index >= _count)
{
throw new IndexOutOfRangeException();
}
#endif
fixed (T* pFirst = &_value0)
{
*(pFirst + index) = value;
}
}
}
public int Count => _count;
public bool IsReadOnly => false;
public ValueTypeList8(T[] arr) : this()
{
AddRange(arr);
}
public ValueTypeList8(List<T> list) : this()
{
AddRange(list);
}
public IEnumerator<T> GetEnumerator()
{
for (int i = 0; i < _count; i++)
{
yield return this[i];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(T item)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (_count == MAX_SIZE)
{
throw new ValueTypeListFull();
}
#endif
fixed (T* pFirst = &_value0)
{
*(pFirst + _count) = item;
}
_count++;
}
public void AddRange(T[] arr)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (_count + arr.Length > MAX_SIZE)
{
throw new ValueTypeListFull();
}
#endif
fixed (T* pMe = &_value0, pThem = arr)
{
long copySizeInBytes = arr.Length * sizeof(T);
Buffer.MemoryCopy(pThem, pMe + _count, copySizeInBytes, copySizeInBytes);
}
_count += arr.Length;
}
public void AddRange(List<T> list)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (_count + list.Count > MAX_SIZE)
{
throw new ValueTypeListFull();
}
#endif
fixed (T* pFirst = &_value0)
{
int listCount = list.Count;
for (int i = 0; i < listCount; i++)
{
*(pFirst + i + _count) = list[i];
}
}
}
public void Clear()
{
_count = 0;
}
public bool Contains(T item)
{
return IndexOf(item) != NO_INDEX;
}
public void CopyTo(T[] array, int arrayIndex)
{
fixed (T* pMe = &_value0, pThem = array)
{
long sizeInBytes = _count * sizeof(T);
Buffer.MemoryCopy(
pMe,
pThem + arrayIndex,
sizeInBytes,
sizeInBytes);
}
}
public bool Remove(T item)
{
int index = IndexOf(item);
if (index != NO_INDEX)
{
RemoveAt(index);
return true;
}
return false;
}
public int IndexOf(T item)
{
fixed (T* pFirst = &_value0)
{
int size = sizeof(T);
for (int i = 0; i < _count; i++)
{
// Similarly, if we later force T to implement IEquatable<T> then this should be replaced with
// (pFirst + i)->Equals(item)
if (ValueTypeListUtil.MemoryCompare(pFirst + i, &item, size))
{
return i;
}
}
}
return NO_INDEX;
}
public void Insert(int index, T item)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (_count == MAX_SIZE)
{
throw new ValueTypeListFull();
}
#endif
fixed (T* pFirst = &_value0)
{
_count++;
for (int i = _count - 1; i >= index + 1; i--)
{
*(pFirst + i) = *(pFirst + i - 1);
}
*(pFirst + index) = item;
}
}
public void RemoveAt(int index)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (index < 0 || index >= _count)
{
throw new IndexOutOfRangeException();
}
#endif
fixed (T* pFirst = &_value0)
{
T* pItem = pFirst + index;
long copyAmountBytes = sizeof(T) * (_count - (index + 1));
Buffer.MemoryCopy(
pItem + 1,
pItem,
copyAmountBytes,
copyAmountBytes);
}
_count--;
}
public void UnstableRemoveAt(int index)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (index < 0 || index >= _count)
{
throw new IndexOutOfRangeException();
}
#endif
fixed (T* pFirst = &_value0)
{
*(pFirst + index) = *(pFirst + _count - 1);
}
_count--;
}
public bool Equals(ValueTypeList8<T> other)
{
if (_count != other._count)
{
return false;
}
// We do a memory compare since T may not implement IEquatable<T>, if that is too rigid then change
// to force T to implement IEquatable<T> and just call (pFirst + i)->Equals(item).
fixed (T* pFirst = &_value0)
{
// Below errors out with "You cannot use the fixed statement to take the address of an already fixed expression"
// So I interpret that as I'm already safe? But I don't fully follow or understand so research is needed.
// fixed(T* pFirstOther = &other._value0) {}
return ValueTypeListUtil.MemoryCompare(pFirst, &other._value0, _count * sizeof(T));
}
}
public override int GetHashCode()
{
// If T doesn't implement IEquatable<T> this will generate garbage
int hashCode = 0;
unchecked
{
fixed (T* pFirst = &_value0)
{
for (int i = 0; i < _count; i++)
{
hashCode = (hashCode * HASHCODE_MULTIPLIER) ^ ((pFirst + i)->GetHashCode());
}
}
}
return hashCode;
}
#endregion
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct ValueTypeList16<T> : IEquatable<ValueTypeList16<T>>, IList<T> where T : unmanaged
{
private T _value0;
private T _value1;
private T _value2;
private T _value3;
private T _value4;
private T _value5;
private T _value6;
private T _value7;
private T _value8;
private T _value9;
private T _value10;
private T _value11;
private T _value12;
private T _value13;
private T _value14;
private T _value15;
public const int MAX_SIZE = 16;
#region Implementation
private int _count;
// Generated value by JetBrains
private const int HASHCODE_MULTIPLIER = 397;
private const int NO_INDEX = -1;
public T this[int index]
{
get
{
#if !DISABLE_VTL_BOUNDS_CHK
if (index < 0 || index >= _count)
{
throw new IndexOutOfRangeException();
}
#endif
fixed (T* pFirst = &_value0)
{
return *(pFirst + index);
}
}
set
{
#if !DISABLE_VTL_BOUNDS_CHK
if (index < 0 || index >= _count)
{
throw new IndexOutOfRangeException();
}
#endif
fixed (T* pFirst = &_value0)
{
*(pFirst + index) = value;
}
}
}
public int Count => _count;
public bool IsReadOnly => false;
public ValueTypeList16(T[] arr) : this()
{
AddRange(arr);
}
public ValueTypeList16(List<T> list) : this()
{
AddRange(list);
}
public IEnumerator<T> GetEnumerator()
{
for (int i = 0; i < _count; i++)
{
yield return this[i];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(T item)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (_count == MAX_SIZE)
{
throw new ValueTypeListFull();
}
#endif
fixed (T* pFirst = &_value0)
{
*(pFirst + _count) = item;
}
_count++;
}
public void AddRange(T[] arr)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (_count + arr.Length > MAX_SIZE)
{
throw new ValueTypeListFull();
}
#endif
fixed (T* pMe = &_value0, pThem = arr)
{
long copySizeInBytes = arr.Length * sizeof(T);
Buffer.MemoryCopy(pThem, pMe + _count, copySizeInBytes, copySizeInBytes);
}
_count += arr.Length;
}
public void AddRange(List<T> list)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (_count + list.Count > MAX_SIZE)
{
throw new ValueTypeListFull();
}
#endif
fixed (T* pFirst = &_value0)
{
int listCount = list.Count;
for (int i = 0; i < listCount; i++)
{
*(pFirst + i + _count) = list[i];
}
}
}
public void Clear()
{
_count = 0;
}
public bool Contains(T item)
{
return IndexOf(item) != NO_INDEX;
}
public void CopyTo(T[] array, int arrayIndex)
{
fixed (T* pMe = &_value0, pThem = array)
{
long sizeInBytes = _count * sizeof(T);
Buffer.MemoryCopy(
pMe,
pThem + arrayIndex,
sizeInBytes,
sizeInBytes);
}
}
public bool Remove(T item)
{
int index = IndexOf(item);
if (index != NO_INDEX)
{
RemoveAt(index);
return true;
}
return false;
}
public int IndexOf(T item)
{
fixed (T* pFirst = &_value0)
{
int size = sizeof(T);
for (int i = 0; i < _count; i++)
{
// Similarly, if we later force T to implement IEquatable<T> then this should be replaced with
// (pFirst + i)->Equals(item)
if (ValueTypeListUtil.MemoryCompare(pFirst + i, &item, size))
{
return i;
}
}
}
return NO_INDEX;
}
public void Insert(int index, T item)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (_count == MAX_SIZE)
{
throw new ValueTypeListFull();
}
#endif
fixed (T* pFirst = &_value0)
{
_count++;
for (int i = _count - 1; i >= index + 1; i--)
{
*(pFirst + i) = *(pFirst + i - 1);
}
*(pFirst + index) = item;
}
}
public void RemoveAt(int index)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (index < 0 || index >= _count)
{
throw new IndexOutOfRangeException();
}
#endif
fixed (T* pFirst = &_value0)
{
T* pItem = pFirst + index;
long copyAmountBytes = sizeof(T) * (_count - (index + 1));
Buffer.MemoryCopy(
pItem + 1,
pItem,
copyAmountBytes,
copyAmountBytes);
}
_count--;
}
public void UnstableRemoveAt(int index)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (index < 0 || index >= _count)
{
throw new IndexOutOfRangeException();
}
#endif
fixed (T* pFirst = &_value0)
{
*(pFirst + index) = *(pFirst + _count - 1);
}
_count--;
}
public bool Equals(ValueTypeList16<T> other)
{
if (_count != other._count)
{
return false;
}
// We do a memory compare since T may not implement IEquatable<T>, if that is too rigid then change
// to force T to implement IEquatable<T> and just call (pFirst + i)->Equals(item).
fixed (T* pFirst = &_value0)
{
// Below errors out with "You cannot use the fixed statement to take the address of an already fixed expression"
// So I interpret that as I'm already safe? But I don't fully follow or understand so research is needed.
// fixed(T* pFirstOther = &other._value0) {}
return ValueTypeListUtil.MemoryCompare(pFirst, &other._value0, _count * sizeof(T));
}
}
public override int GetHashCode()
{
// If T doesn't implement IEquatable<T> this will generate garbage
int hashCode = 0;
unchecked
{
fixed (T* pFirst = &_value0)
{
for (int i = 0; i < _count; i++)
{
hashCode = (hashCode * HASHCODE_MULTIPLIER) ^ ((pFirst + i)->GetHashCode());
}
}
}
return hashCode;
}
#endregion
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct ValueTypeList32<T> : IEquatable<ValueTypeList32<T>>, IList<T> where T : unmanaged
{
private T _value0;
private T _value1;
private T _value2;
private T _value3;
private T _value4;
private T _value5;
private T _value6;
private T _value7;
private T _value8;
private T _value9;
private T _value10;
private T _value11;
private T _value12;
private T _value13;
private T _value14;
private T _value15;
private T _value16;
private T _value17;
private T _value18;
private T _value19;
private T _value20;
private T _value21;
private T _value22;
private T _value23;
private T _value24;
private T _value25;
private T _value26;
private T _value27;
private T _value28;
private T _value29;
private T _value30;
private T _value31;
public const int MAX_SIZE = 32;
#region Implementation
private int _count;
// Generated value by JetBrains
private const int HASHCODE_MULTIPLIER = 397;
private const int NO_INDEX = -1;
public T this[int index]
{
get
{
#if !DISABLE_VTL_BOUNDS_CHK
if (index < 0 || index >= _count)
{
throw new IndexOutOfRangeException();
}
#endif
fixed (T* pFirst = &_value0)
{
return *(pFirst + index);
}
}
set
{
#if !DISABLE_VTL_BOUNDS_CHK
if (index < 0 || index >= _count)
{
throw new IndexOutOfRangeException();
}
#endif
fixed (T* pFirst = &_value0)
{
*(pFirst + index) = value;
}
}
}
public int Count => _count;
public bool IsReadOnly => false;
public ValueTypeList32(T[] arr) : this()
{
AddRange(arr);
}
public ValueTypeList32(List<T> list) : this()
{
AddRange(list);
}
public IEnumerator<T> GetEnumerator()
{
for (int i = 0; i < _count; i++)
{
yield return this[i];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(T item)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (_count == MAX_SIZE)
{
throw new ValueTypeListFull();
}
#endif
fixed (T* pFirst = &_value0)
{
*(pFirst + _count) = item;
}
_count++;
}
public void AddRange(T[] arr)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (_count + arr.Length > MAX_SIZE)
{
throw new ValueTypeListFull();
}
#endif
fixed (T* pMe = &_value0, pThem = arr)
{
long copySizeInBytes = arr.Length * sizeof(T);
Buffer.MemoryCopy(pThem, pMe + _count, copySizeInBytes, copySizeInBytes);
}
_count += arr.Length;
}
public void AddRange(List<T> list)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (_count + list.Count > MAX_SIZE)
{
throw new ValueTypeListFull();
}
#endif
fixed (T* pFirst = &_value0)
{
int listCount = list.Count;
for (int i = 0; i < listCount; i++)
{
*(pFirst + i + _count) = list[i];
}
}
}
public void Clear()
{
_count = 0;
}
public bool Contains(T item)
{
return IndexOf(item) != NO_INDEX;
}
public void CopyTo(T[] array, int arrayIndex)
{
fixed (T* pMe = &_value0, pThem = array)
{
long sizeInBytes = _count * sizeof(T);
Buffer.MemoryCopy(
pMe,
pThem + arrayIndex,
sizeInBytes,
sizeInBytes);
}
}
public bool Remove(T item)
{
int index = IndexOf(item);
if (index != NO_INDEX)
{
RemoveAt(index);
return true;
}
return false;
}
public int IndexOf(T item)
{
fixed (T* pFirst = &_value0)
{
int size = sizeof(T);
for (int i = 0; i < _count; i++)
{
// Similarly, if we later force T to implement IEquatable<T> then this should be replaced with
// (pFirst + i)->Equals(item)
if (ValueTypeListUtil.MemoryCompare(pFirst + i, &item, size))
{
return i;
}
}
}
return NO_INDEX;
}
public void Insert(int index, T item)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (_count == MAX_SIZE)
{
throw new ValueTypeListFull();
}
#endif
fixed (T* pFirst = &_value0)
{
_count++;
for (int i = _count - 1; i >= index + 1; i--)
{
*(pFirst + i) = *(pFirst + i - 1);
}
*(pFirst + index) = item;
}
}
public void RemoveAt(int index)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (index < 0 || index >= _count)
{
throw new IndexOutOfRangeException();
}
#endif
fixed (T* pFirst = &_value0)
{
T* pItem = pFirst + index;
long copyAmountBytes = sizeof(T) * (_count - (index + 1));
Buffer.MemoryCopy(
pItem + 1,
pItem,
copyAmountBytes,
copyAmountBytes);
}
_count--;
}
public void UnstableRemoveAt(int index)
{
#if !DISABLE_VTL_BOUNDS_CHK
if (index < 0 || index >= _count)
{
throw new IndexOutOfRangeException();
}
#endif
fixed (T* pFirst = &_value0)
{
*(pFirst + index) = *(pFirst + _count - 1);
}
_count--;
}
public bool Equals(ValueTypeList32<T> other)
{
if (_count != other._count)
{
return false;
}
// We do a memory compare since T may not implement IEquatable<T>, if that is too rigid then change
// to force T to implement IEquatable<T> and just call (pFirst + i)->Equals(item).
fixed (T* pFirst = &_value0)
{
// Below errors out with "You cannot use the fixed statement to take the address of an already fixed expression"
// So I interpret that as I'm already safe? But I don't fully follow or understand so research is needed.
// fixed(T* pFirstOther = &other._value0) {}
return ValueTypeListUtil.MemoryCompare(pFirst, &other._value0, _count * sizeof(T));
}
}
public override int GetHashCode()
{
// If T doesn't implement IEquatable<T> this will generate garbage
int hashCode = 0;
unchecked
{
fixed (T* pFirst = &_value0)
{
for (int i = 0; i < _count; i++)
{
hashCode = (hashCode * HASHCODE_MULTIPLIER) ^ ((pFirst + i)->GetHashCode());
}
}
}
return hashCode;
}
#endregion
}
public class ValueTypeListFull : Exception
{
}
public static unsafe class ValueTypeListUtil
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool MemoryCompare(void* p1, void* p2, int sizeInBytes)
{
byte* pByte1 = (byte*) p1;
byte* pByte2 = (byte*) p2;
// There are some interesting solutions here, possibly faster ones?
// https://stackoverflow.com/questions/43289/comparing-two-byte-arrays-in-net
for (int i = 0; i < sizeInBytes; i++)
{
if (*pByte1 != *pByte2)
{
return false;
}
pByte1++;
pByte2++;
}
return true;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment