FixAntenna/NetCore/Message/FieldIndex.cs (586 lines of code) (raw):
// Copyright (c) 2021 EPAM Systems
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
namespace Epam.FixAntenna.NetCore.Message
{
internal class FieldIndex : IFieldIndexData
{
public const int Notfound = -1;
internal const int ItemsPerField = 4;
private const int Tag = 0;
private const int Offset = 1;
private const int Length = 2;
private const int Flags = 3;
internal const int FlagMaxAvailInplaceMax = 0x7fff;
internal const int FlagMaxAvailInplaceMask = 0x7fff0000;
internal const int FlagMaxAvailInplaceShift = 16;
internal const int FlagOrigbufStorage = 1;
internal const int FlagArenaStorage = 2;
internal const int FlagPerfieldStorage = 4;
internal const int FlagMaxAvailInplace = 8;
internal const int FlagPreparedTag = 16;
internal const int ItemsInHashentry = 2;
//offset for tag id in hashtbl's block.
internal const int HashtblTagEntry = 0;
//offset for value in index array in hashtbl's block.
internal const int HashtblIndexEntry = 1;
//Must be power of two
internal const int HashtableRedundancy = 2;
internal static readonly int FlagAllStorageBits =
FlagOrigbufStorage | FlagArenaStorage | FlagPerfieldStorage;
//Count of blocks in index array
private int _fieldCount;
/// <summary>
/// Each blocks in index array consists from 4 ints:
/// 1 - tag id
/// 2 - offset of value
/// 3 - length of value
/// 4 - bit set of flags:
/// 1 - value in original buffer
/// 2 - value in arena storage
/// 3 - value in per_field storage
/// 4 - FLAG_MAX_AVAIL_INPLACE (TODO: add description)
/// </summary>
private int[] _index = new int[AbstractFixMessage.InitMaxFields * ItemsPerField];
/// <summary>
/// Each block of hashtbl array consists from 2 ints:
/// 1 - tag id
/// 2 - position in index array
/// </summary>
internal int[] Hashtbl =
new int[AbstractFixMessage.InitMaxFields * ItemsInHashentry * HashtableRedundancy];
//Count of blocks in hashtbl array
internal int HashtblElems;
public virtual int GetTag(int tagIndex)
{
return _index[tagIndex * ItemsPerField + Tag];
}
public virtual int GetOffset(int tagIndex)
{
return _index[tagIndex * ItemsPerField + Offset];
}
public virtual int GetLength(int tagIndex)
{
return _index[tagIndex * ItemsPerField + Length];
}
public virtual int Count => _fieldCount;
public virtual int GetIndexCapacity()
{
return _index.Length / ItemsPerField;
}
public virtual void EnlargeIndex(int ratio)
{
if (_fieldCount * ItemsPerField == _index.Length)
{
var newArray = new int[_index.Length * ratio];
Array.Copy(_index, 0, newArray, 0, _index.Length);
_index = newArray;
}
}
public virtual void EnlargeHastable(int ratio)
{
Hashtbl = EnlargeHashtable(Hashtbl, ratio);
}
private int[] EnlargeHashtable(int[] tbl, int ratio)
{
int[] newTbl;
if (ratio == 1)
{
ClearHashTbl(tbl);
newTbl = tbl;
}
else
{
newTbl = new int[tbl.Length * ratio];
}
// rehash the table
for (var i = 0; i < _fieldCount * ItemsPerField; i += ItemsPerField)
{
var tag = _index[i + Tag];
AddToHashTbl(newTbl, tag, i, 0);
}
//returnObj( tbl );
return newTbl;
}
private void AddToHashTbl(int tag, int index)
{
HashtblElems = AddToHashTbl(Hashtbl, tag, index, HashtblElems);
}
private int AddToHashTbl(int[] tbl, int tag, int index, int elems)
{
var i = FindElementInHashTbl(tag, tbl);
if (i != IndexedStorage.NotFound)
{
return elems;
}
var hashcode = Hash(tbl.Length, tag);
for (i = hashcode * ItemsInHashentry; i < tbl.Length; i += ItemsInHashentry)
{
if (tbl[i + HashtblTagEntry] == 0)
{
tbl[i + HashtblTagEntry] = tag;
tbl[i + HashtblIndexEntry] = index;
elems++;
return elems;
}
}
// wrap around and go from start up to this element
for (i = 0; i < hashcode * ItemsInHashentry; i += ItemsInHashentry)
{
if (tbl[i + HashtblTagEntry] == 0)
{
tbl[i + HashtblTagEntry] = tag;
tbl[i + HashtblIndexEntry] = index;
elems++;
return elems;
}
}
//TBD! fix error message
throw new Exception("internal error: hashtable full?");
}
private void ClearHashTbl()
{
if (HashtblElems > 0)
{
for (var i = 0; i < Hashtbl.Length; i += ItemsInHashentry)
{
Hashtbl[i + HashtblTagEntry] = 0;
}
}
HashtblElems = 0;
}
private void ClearHashTbl(int[] tbl)
{
//WHY: hashtblElems? may be need another param for size?
if (HashtblElems > 0)
{
for (var i = 0; i < tbl.Length; i += ItemsInHashentry)
{
tbl[i + HashtblTagEntry] = 0;
}
}
}
public virtual bool IsNeedToEnlarge()
{
return _fieldCount * ItemsPerField == _index.Length;
}
/// <param name="requiredSize"> </param>
/// <returns> ratio for enlarging </returns>
public virtual int IsNeedToEnlarge(int requiredSize)
{
if (requiredSize > _index.Length)
{
return requiredSize / _index.Length + 1;
}
return 0;
}
private int FindElementInHashTbl(int tag)
{
return FindElementInHashTbl(tag, Hashtbl);
}
private int FindElementInHashTbl(int tag, int[] hashtbl)
{
var hashcode = Hash(hashtbl.Length, tag);
for (var i = hashcode * ItemsInHashentry; i < hashtbl.Length; i += ItemsInHashentry)
{
var item = hashtbl[i + HashtblTagEntry];
if (item == tag)
{
return i;
}
if (item == 0)
{
break;
}
}
//let search from index start
for (var i = 0; i < hashcode * ItemsInHashentry; i += ItemsInHashentry)
{
var item = hashtbl[i + HashtblTagEntry];
if (item == tag)
{
return i;
}
if (item == 0)
{
break;
}
}
return IndexedStorage.NotFound;
}
public virtual int FindIndexEntryInHashTbl(int tag)
{
return FindIndexEntryInHashTbl(Hashtbl, tag);
}
private int FindIndexEntryInHashTbl(int[] tbl, int tag)
{
var startPos = Hash(tbl.Length, tag) * ItemsInHashentry;
for (var i = startPos; i < tbl.Length; i += ItemsInHashentry)
{
var item = tbl[i + HashtblTagEntry];
if (item == tag)
{
return tbl[i + HashtblIndexEntry];
}
if (item == 0)
{
break;
}
}
//let search from index start
for (var i = 0; i < startPos; i += ItemsInHashentry)
{
var item = tbl[i + HashtblTagEntry];
if (item == tag)
{
return tbl[i + HashtblIndexEntry];
}
if (item == 0)
{
break;
}
}
return IndexedStorage.NotFound;
}
private int Hash(int tableLength, int tag)
{
return ((tableLength - 1) / ItemsInHashentry) & tag;
}
public virtual void ShiftBuffer(int shift)
{
var tagOffset = 0;
for (var i = 0; i < _fieldCount; i++)
{
if (IsOriginalMessageStorage(i))
{
_index[tagOffset + Offset] += shift;
}
tagOffset += ItemsPerField;
}
}
public virtual void ShiftBufferAndChangeStorage(int shift, int storageType)
{
var tagOffset = 0;
for (var i = 0; i < _fieldCount; i++)
{
if (IsOriginalMessageStorage(i))
{
_index[tagOffset + Offset] += shift;
var flags = _index[tagOffset + Flags];
_index[tagOffset + Flags] = (flags & ~FlagAllStorageBits) | storageType;
}
tagOffset += ItemsPerField;
}
}
private int GetFlags(int tagIndex)
{
return _index[tagIndex * ItemsPerField + Flags];
}
public virtual int GetStorageType(int tagIndex)
{
return GetFlags(tagIndex) & FlagAllStorageBits;
}
public virtual bool IsArenaStorage(int i)
{
return GetStorageType(i) == FlagArenaStorage;
}
public virtual bool IsPerFieldStorage(int i)
{
return GetStorageType(i) == FlagPerfieldStorage;
}
public virtual bool IsOriginalMessageStorage(int i)
{
return GetStorageType(i) == FlagOrigbufStorage;
}
public virtual bool IsPreparedOriginalStorage(int i)
{
return (GetFlags(i) & FlagPreparedTag) == FlagPreparedTag;
}
public virtual int Add(int tag, int offset, int length, int flag)
{
var i = _fieldCount * ItemsPerField;
_index[i + Tag] = tag;
_index[i + Offset] = offset;
_index[i + Length] = length;
_index[i + Flags] = flag;
AddToHashTbl(tag, i);
return _fieldCount++;
}
//TBD! make sure that checkTagExistsAtIndex always called with right index
public virtual void CheckTagExistsAtIndex(int tagIndex)
{
if (tagIndex < 0 || _fieldCount <= tagIndex)
{
throw new IndexOutOfRangeException("No tag at tagIndex " + tagIndex);
}
}
public virtual void AddAtIndex(int addAtIndex, int tag, int length)
{
if (_fieldCount != addAtIndex)
{
//isn't new element at the end - check tag index
CheckTagExistsAtIndex(addAtIndex);
}
var tagDataStartPos = addAtIndex * ItemsPerField;
var tagDataEndPos = tagDataStartPos + ItemsPerField;
var insertNew = true;
var reinsert = false;
var foundPosInHash = FindElementInHashTbl(tag);
if (foundPosInHash != IndexedStorage.NotFound)
{
insertNew = false;
if (Hashtbl[foundPosInHash + HashtblIndexEntry] >= tagDataStartPos)
{
reinsert = true;
}
}
_fieldCount++;
if (addAtIndex < _fieldCount - 1)
{
Array.Copy(_index, tagDataStartPos, _index, tagDataEndPos,
(_fieldCount - addAtIndex - 1) * ItemsPerField);
}
for (var i = tagDataStartPos; i < tagDataEndPos; i++)
{
_index[i] = 0;
}
_index[tagDataStartPos + Tag] = tag;
_index[tagDataStartPos + Length] = length;
// adjust mapping into index array for the index elements greater than the added
for (var k = 0; k < Hashtbl.Length; k += ItemsInHashentry)
{
if (Hashtbl[k + HashtblTagEntry] != 0 && Hashtbl[k + HashtblIndexEntry] >= tagDataStartPos)
{
Hashtbl[k + HashtblIndexEntry] += ItemsPerField;
}
}
if (insertNew)
{
AddToHashTbl(tag, tagDataStartPos);
}
else if (reinsert)
{
//remove element
Hashtbl[foundPosInHash + HashtblTagEntry] = 0;
AddToHashTbl(tag, tagDataStartPos);
}
}
public virtual void UpdateStorageData(int tagIndex, int storageTypeFlag, int offset, int length)
{
var posInIndex = tagIndex * ItemsPerField;
var flags = _index[posInIndex + Flags];
if ((flags & storageTypeFlag) == 0)
{
flags = (flags & ~FlagAllStorageBits) | storageTypeFlag;
_index[posInIndex + Flags] = flags;
}
_index[posInIndex + Offset] = offset;
if (length > FlagMaxAvailInplaceMax)
{
length = FlagMaxAvailInplaceMax;
}
flags = (flags & ~FlagMaxAvailInplaceMask) | FlagMaxAvailInplace |
(length << FlagMaxAvailInplaceShift);
_index[posInIndex + Flags] = flags;
}
public virtual int GetMaxAvailableInPlace(int index)
{
var flags = GetFlags(index);
var maxAvail = 0;
if ((flags & FlagMaxAvailInplace) != 0)
{
maxAvail = (flags & FlagMaxAvailInplaceMask) >> FlagMaxAvailInplaceShift;
}
return maxAvail;
}
public virtual int UpdateLength(int tagIndex, int length)
{
var posInIndex = tagIndex * ItemsPerField;
var oldLen = _index[posInIndex + Length];
_index[posInIndex + Length] = length;
return oldLen;
}
public virtual void Clear()
{
ClearHashTbl();
_fieldCount = 0;
HashtblElems = 0;
}
public virtual int GetTagIndex(int tagId)
{
var indexEntryInHashTbl = FindIndexEntryInHashTbl(tagId);
if (indexEntryInHashTbl == IndexedStorage.NotFound)
{
return IndexedStorage.NotFound;
}
return indexEntryInHashTbl / ItemsPerField;
}
public int GetTagIndex(int tag, int fromIndex, int toIndex)
{
for (var i = fromIndex * ItemsPerField; i < toIndex * ItemsPerField; i += ItemsPerField)
{
if (_index[i + Tag] == tag)
{
return i / ItemsPerField;
}
}
return IndexedStorage.NotFound;
}
public int GetTagOccurrenceIndex(int tag, int occurrence)
{
var first = GetTagIndex(tag);
if (occurrence <= 1 || first == IndexedStorage.NotFound)
{
return first;
}
var counter = 0;
for (var i = first * ItemsPerField; i < _fieldCount * ItemsPerField; i += ItemsPerField)
{
if (_index[i + Tag] == tag)
{
counter++;
if (counter >= occurrence)
{
return i / ItemsPerField;
}
}
}
return -1;
}
public int GetTagOccurrenceCount(int tag)
{
var i = GetTagIndex(tag);
if (i == IndexedStorage.NotFound)
{
return 0;
}
var count = 0;
for (; i < _fieldCount * ItemsPerField; i += ItemsPerField)
{
if (_index[i + Tag] == tag)
{
count++;
}
}
return count;
}
public virtual bool CheckTagsStorageType(int storageType)
{
for (var i = 0; i < _fieldCount * ItemsPerField; i += ItemsPerField)
{
if ((_index[i + Flags] & storageType) == 0)
{
return false;
}
}
return true;
}
public virtual void RemoveElementFromIndex(int tagIndex)
{
if (tagIndex < _fieldCount - 1)
{
Array.Copy(_index, (tagIndex + 1) * ItemsPerField, _index, tagIndex * ItemsPerField,
(_fieldCount - tagIndex - 1) * ItemsPerField);
}
_fieldCount--;
}
public virtual void RemoveFromHashtbl(int tagIndex)
{
var indexOffset = tagIndex * ItemsPerField;
var tagId = _index[indexOffset + Tag];
var hashIndex = FindElementInHashTbl(tagId);
if (hashIndex != -1)
{
RemoveFromHashtblInternal(tagId, indexOffset);
}
}
//TODO!
private void RemoveFromHashtblInternal(int tagId, int indexOffset)
{
var secondOccuranceIndex = GetTagOccurrenceIndex(tagId, 2);
var hashIndex = FindElementInHashTbl(tagId);
var needToRehash = false;
if (secondOccuranceIndex != IndexedStorage.NotFound)
{
Hashtbl[hashIndex + HashtblIndexEntry] = secondOccuranceIndex * ItemsPerField;
// adjust mapping into index array for the index elements greater than the removed
for (var j = 0; j < Hashtbl.Length; j += ItemsInHashentry)
{
if (Hashtbl[j + HashtblTagEntry] != 0 && Hashtbl[j + HashtblIndexEntry] > indexOffset)
{
Hashtbl[j + HashtblIndexEntry] -= ItemsPerField;
}
}
return;
}
HashtblElems--;
if (!needToRehash)
{
// check next element - if it have same hashCode - we need rehash
var size = Hashtbl.Length / ItemsInHashentry;
var deleted = hashIndex / ItemsInHashentry;
var next = deleted + 1;
if (next >= size)
{
next = 0;
}
if (Hashtbl[next * ItemsInHashentry] != 0)
{
needToRehash = true;
}
// // see if this created a gap that we potentially need to close
// // consider the sequence of elements: 0, x1, x2, [gap: deleted element], x3, x4, 0
// // gap is created if both x2 and x3 were present (not 0)
//
// int size = hashtbl.length / ITEMS_IN_HASHENTRY;
// int deleted = hashIndex / ITEMS_IN_HASHENTRY;
// int x2 = mod((deleted - 1), size);
// int x3 = mod((deleted + 1), size);
//
// if (hashtbl[x2 * ITEMS_IN_HASHENTRY] != 0 && hashtbl[x3 * ITEMS_IN_HASHENTRY] != 0) {
// // yes, we created a gap
//
// // TBD!
// // An optimal solution would be: to make sure the element addressing is correct in the range from x1 to x4
// // and re-hash elements in the range from x4 to x1 (starting from x4 and going to x1)
//
// // ... for now we're going just re-hash the whole table, which is less optimal
//
// needToRehash = true;
// }
}
if (needToRehash)
{
ClearHashTbl(Hashtbl);
// rehash the table
for (var i = 0; i < _fieldCount * ItemsPerField; i += ItemsPerField)
{
var tag = _index[i + Tag];
// skip tag that we removing
if (tag != tagId)
{
AddToHashTbl(Hashtbl, tag, i, 0);
}
}
}
else
{
// remove a single element
// erase the element
Hashtbl[hashIndex + HashtblTagEntry] = 0;
}
// adjust mapping into index array for the index elements greater than the removed
for (var j = 0; j < Hashtbl.Length; j += ItemsInHashentry)
{
if (Hashtbl[j + HashtblTagEntry] != 0 && Hashtbl[j + HashtblIndexEntry] > indexOffset)
{
Hashtbl[j + HashtblIndexEntry] -= ItemsPerField;
}
}
}
internal virtual void RemoveFromHashtbl2(int i, int indexOffset, bool tagNotUnique)
{
var needToRehash = tagNotUnique;
HashtblElems--;
if (!needToRehash)
{
// see if this created a gap that we potentially need to close
// consider the sequence of elements: 0, x1, x2, [gap: deleted element], x3, x4, 0
// gap is created if both x2 and x3 were present (not 0)
var size = Hashtbl.Length / ItemsInHashentry;
var deleted = i / ItemsInHashentry;
var x2 = Mod(deleted - 1, size);
var x3 = Mod(deleted + 1, size);
if (Hashtbl[x2 * ItemsInHashentry] != 0 && Hashtbl[x3 * ItemsInHashentry] != 0)
{
// yes, we created a gap
// TBD!
// An optimal solution would be: to make sure the element addressing is correct in the range from x1 to x4
// and re-hash elements in the range from x4 to x1 (starting from x4 and going to x1)
// ... for now we're going just re-hash the whole table, which is less optimal
needToRehash = true;
}
}
if (needToRehash)
{
Hashtbl = EnlargeHashtable(Hashtbl, 1);
return;
}
// remove a single element
// erase the element
Hashtbl[i + HashtblTagEntry] = 0;
// adjust mapping into index array for the index elements greater than the removed
for (var j = 0; j < Hashtbl.Length; j += ItemsInHashentry)
{
if (Hashtbl[j + HashtblTagEntry] != 0 && Hashtbl[j + HashtblIndexEntry] > indexOffset)
{
Hashtbl[j + HashtblIndexEntry] -= ItemsPerField;
}
}
}
private int Mod(int x, int size)
{
if (x >= 0)
{
return x;
}
return size + x;
}
public override bool Equals(object o)
{
return base.Equals(o);
}
public override int GetHashCode()
{
var result = 0;
for (var i = 0; i < Count; i++)
{
result = 31 * result + _index[i * ItemsPerField + Tag];
result = 31 * result + _index[i * ItemsPerField + Length];
}
return result;
}
public virtual void DeepCopyFrom(FieldIndex donor)
{
_fieldCount = donor._fieldCount;
HashtblElems = donor.HashtblElems;
var donorIndexSize = donor.Count * ItemsPerField;
var donorHashSize = donor.Hashtbl.Length;
if (_index.Length < donorIndexSize)
{
_index = new int[donorIndexSize];
}
if (Hashtbl.Length < donorHashSize)
{
Hashtbl = new int[donorHashSize];
}
Array.Copy(donor._index, 0, _index, 0, donorIndexSize);
Hashtbl = EnlargeHashtable(Hashtbl, 1);
}
}
}