Skip to content

Commit

Permalink
WIP. Fixes Dict's logic inside InsertInto(). Also changed the way Ove…
Browse files Browse the repository at this point in the history
…rflow Listener works (enum items). Got rid of unnecessary overflow listeners in some classes.
  • Loading branch information
nseam committed Aug 24, 2022
1 parent a2bdfe3 commit baa4942
Show file tree
Hide file tree
Showing 13 changed files with 287 additions and 282 deletions.
22 changes: 5 additions & 17 deletions Buffer/BufferCandle.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
// Includes.
#include "../BufferStruct.mqh"
#include "../Candle.struct.h"
#include "../SerializerConverter.mqh"
#include "../SerializerJson.mqh"

/**
* Class to store struct data.
Expand All @@ -41,7 +43,7 @@ class BufferCandle : public BufferStruct<CandleOCTOHLC<TV>> {
*
* Called on constructor.
*/
void Init() { SetOverflowListener(BufferCandleOverflowListener, 10); }
void Init() { SetOverflowListener(BufferStructOverflowListener, 10); }

public:
/* Constructors */
Expand All @@ -55,24 +57,10 @@ class BufferCandle : public BufferStruct<CandleOCTOHLC<TV>> {
Init();
}

/* Callback methods */

/**
* Function should return true if resize can be made, or false to overwrite current slot.
* Returns JSON representation of the buffer.
*/
static bool BufferCandleOverflowListener(ENUM_DICT_OVERFLOW_REASON _reason, int _size, int _num_conflicts) {
static int cache_limit = 86400;
switch (_reason) {
case DICT_OVERFLOW_REASON_FULL:
// We allow resize if dictionary size is less than 86400 slots.
return _size < cache_limit;
case DICT_OVERFLOW_REASON_TOO_MANY_CONFLICTS:
default:
// When there is too many conflicts, we just reject doing resize, so first conflicting slot will be reused.
break;
}
return false;
}
string ToJSON() { return SerializerConverter::FromObject(THIS_REF).ToString<SerializerJson>(); }
};

#endif // BUFFER_CANDLE_H
21 changes: 1 addition & 20 deletions Buffer/BufferTick.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ class BufferTick : public BufferStruct<TickAB<TV>> {
_vs_spread = NULL;
_vs_volume = NULL;
_vs_tick_volume = NULL;
SetOverflowListener(BufferTickOverflowListener, 10);
SetOverflowListener(BufferStructOverflowListener, 10);
}

public:
Expand Down Expand Up @@ -210,25 +210,6 @@ class BufferTick : public BufferStruct<TickAB<TV>> {
// Convert to OHLC in upper method
return NULL;
}

/* Callback methods */

/**
* Function should return true if resize can be made, or false to overwrite current slot.
*/
static bool BufferTickOverflowListener(ENUM_DICT_OVERFLOW_REASON _reason, int _size, int _num_conflicts) {
static int cache_limit = 86400;
switch (_reason) {
case DICT_OVERFLOW_REASON_FULL:
// We allow resize if dictionary size is less than 86400 slots.
return _size < cache_limit;
case DICT_OVERFLOW_REASON_TOO_MANY_CONFLICTS:
default:
// When there is too many conflicts, we just reject doing resize, so first conflicting slot will be reused.
break;
}
return false;
}
};

#endif // BUFFER_TICK_H
14 changes: 12 additions & 2 deletions BufferStruct.mqh
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,18 @@
* @see DictBase
*/
bool BufferStructOverflowListener(ENUM_DICT_OVERFLOW_REASON _reason, int _size, int _num_conflicts) {
// We allow resize if dictionary size is less than 10000 slots.
return _size < 10000;
static int cache_limit = 86400;
switch (_reason) {
case DICT_LISTENER_FULL_CAN_RESIZE:
case DICT_LISTENER_NOT_PERFORMANT_CAN_RESIZE:
// We allow resize if dictionary size is less than 86400 slots.
return _size < cache_limit;
case DICT_LISTENER_CONFLICTS_CAN_OVERWRITE:
// We start to overwrite slots when we can't make dict bigger and there is at least 10 consecutive conflicts while
// inserting new value.
return _size >= cache_limit && _num_conflicts >= 10;
}
return true;
}

/**
Expand Down
17 changes: 17 additions & 0 deletions Candle.struct.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,9 @@ struct CandleOCTOHLC : CandleOHLC<T> {
// Returns timestamp of close price.
long GetCloseTimestamp() { return close_timestamp; }

// Serializers.
SerializerNodeType Serialize(Serializer &s);

// Returns text representation of candle.
string ToString() {
return StringFormat("%.5f %.5f %.5f %.5f [%s] @ %s - %s", open, high, low, close,
Expand Down Expand Up @@ -309,3 +312,17 @@ SerializerNodeType CandleTOHLC::Serialize(Serializer &s) {
s.Pass(THIS_REF, "close", close, SERIALIZER_FIELD_FLAG_DYNAMIC);
return SerializerNodeObject;
}

/* Method to serialize CandleEntry structure. */
template <typename T>
SerializerNodeType CandleOCTOHLC::Serialize(Serializer &s) {
s.Pass(THIS_REF, "is_complete", is_complete, SERIALIZER_FIELD_FLAG_DYNAMIC);
s.Pass(THIS_REF, "open_timestamp", open_timestamp, SERIALIZER_FIELD_FLAG_DYNAMIC);
s.Pass(THIS_REF, "close_timestamp", close_timestamp, SERIALIZER_FIELD_FLAG_DYNAMIC);
s.Pass(THIS_REF, "open", open, SERIALIZER_FIELD_FLAG_DYNAMIC);
s.Pass(THIS_REF, "high", high, SERIALIZER_FIELD_FLAG_DYNAMIC);
s.Pass(THIS_REF, "low", low, SERIALIZER_FIELD_FLAG_DYNAMIC);
s.Pass(THIS_REF, "close", close, SERIALIZER_FIELD_FLAG_DYNAMIC);
s.Pass(THIS_REF, "volume", volume, SERIALIZER_FIELD_FLAG_DYNAMIC);
return SerializerNodeObject;
}
6 changes: 4 additions & 2 deletions Dict.enum.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ enum DictMode { DictModeUnknown, DictModeDict, DictModeList };
* Reason of call to overflow listener.
*/
enum ENUM_DICT_OVERFLOW_REASON {
DICT_OVERFLOW_REASON_FULL,
DICT_OVERFLOW_REASON_TOO_MANY_CONFLICTS,
DICT_LISTENER_FULL_CAN_RESIZE, // Dict is full. Can we grow up the dict?
DICT_LISTENER_NOT_PERFORMANT_CAN_RESIZE, // Dict is not performant (too many average number of conflicts). Can we
// grow up the dict?
DICT_LISTENER_CONFLICTS_CAN_OVERWRITE // Conflict(s) when inserting new slot. Can we overwrite random used slot?
};

/**
Expand Down
136 changes: 75 additions & 61 deletions Dict.mqh
Original file line number Diff line number Diff line change
Expand Up @@ -206,94 +206,108 @@ class Dict : public DictBase<K, V> {
* Inserts value into given array of DictSlots.
*/
bool InsertInto(DictSlotsRef<K, V>& dictSlotsRef, const K key, V value, bool allow_resize) {
if (_mode == DictModeUnknown)
_mode = DictModeDict;
else if (_mode != DictModeDict) {
if (THIS_ATTR _mode == DictModeUnknown)
THIS_ATTR _mode = DictModeDict;
else if (THIS_ATTR _mode != DictModeDict) {
Alert("Warning: Dict already operates as a list, not a dictionary!");
return false;
}

unsigned int position;
DictSlot<K, V>* keySlot = GetSlotByKey(dictSlotsRef, key, position);
DictSlot<K, V>* _slot = THIS_ATTR GetSlotByKey(dictSlotsRef, key, position);

if (keySlot == NULL && !IsGrowUpAllowed()) {
// Resize is prohibited, so we will just overwrite some slot.
allow_resize = false;
// If we have a slot then we can overwrite it.
if (_slot != NULL) {
WriteSlot(_slot, key, value, DICT_SLOT_HAS_KEY | DICT_SLOT_IS_USED | DICT_SLOT_WAS_USED);
// We're done, we don't have to increment number of slots used.
return true;
}

if (allow_resize) {
// Will resize dict if there were performance problems before or there is no slots.
if (IsGrowUpAllowed() && !dictSlotsRef.IsPerformant()) {
// If we don't have a slot then we should consider growing up number of slots or overwrite some existing slot.

bool _is_performant = dictSlotsRef.IsPerformant(); // Whether there is no performance problems.
bool _is_full =
dictSlotsRef._num_used == ArraySize(dictSlotsRef.DictSlots); // Whether we don't have empty slots to use.

if ((_is_full || !_is_performant) && allow_resize) {
// We have to resize the dict as it is either full or have perfomance problems due to massive number of conflicts
// when inserting new values.
if (overflow_listener == NULL) {
// There is no overflow listener so we can freely grow up the dict.
if (!GrowUp()) {
// Can't resize the dict. Error happened.
return false;
}
// We now have new positions of slots, so we have to take the corrent slot again.
keySlot = GetSlotByKey(dictSlotsRef, key, position);
}

if (keySlot == NULL && dictSlotsRef._num_used == ArraySize(dictSlotsRef.DictSlots)) {
// No DictSlotsRef.DictSlots available.
if (overflow_listener != NULL) {
if (!overflow_listener(DICT_OVERFLOW_REASON_FULL, dictSlotsRef._num_used, 0)) {
// Overwriting slot pointed exactly by key's position in the hash table (we don't check for possible
// conflicts).
keySlot = &dictSlotsRef.DictSlots[Hash(key) % ArraySize(dictSlotsRef.DictSlots)];
} else {
// Overflow listener will decide if we can grow up the dict.
if (overflow_listener(_is_full ? DICT_LISTENER_FULL_CAN_RESIZE : DICT_LISTENER_NOT_PERFORMANT_CAN_RESIZE,
dictSlotsRef._num_used, 0)) {
// We can freely grow up the dict.
if (!GrowUp()) {
// Can't resize the dict. Error happened.
return false;
}
}

if (keySlot == NULL) {
// We need to expand array of DictSlotsRef.DictSlots (by 25% by default).
if (!GrowUp()) return false;
}
}
}

if (keySlot == NULL) {
position = Hash(key) % ArraySize(dictSlotsRef.DictSlots);

unsigned int _starting_position = position;
int _num_conflicts = 0;
bool _overwrite_slot = false;

// Searching for empty DictSlot<K, V> or used one with the matching key. It skips used, hashless DictSlots.
while (dictSlotsRef.DictSlots[position].IsUsed() &&
(!dictSlotsRef.DictSlots[position].HasKey() || dictSlotsRef.DictSlots[position].key != key)) {
if (overflow_listener_max_conflicts != 0 && ++_num_conflicts == overflow_listener_max_conflicts) {
if (overflow_listener != NULL) {
if (!overflow_listener(DICT_OVERFLOW_REASON_TOO_MANY_CONFLICTS, dictSlotsRef._num_used, _num_conflicts)) {
// Overflow listener returned false so we won't search for further empty slot.
_overwrite_slot = true;
break;
}
} else {
// Even if there is no overflow listener function, we stop searching for further empty slot as maximum
// number of conflicts has been reached.
_overwrite_slot = true;
break;
}
}
// At this point we have at least one free slot and we won't be doing any dict's grow up in the loop where we search
// for an empty slot.

// Position we will start from in order to search free slot.
position = THIS_ATTR Hash(key) % ArraySize(dictSlotsRef.DictSlots);

// Position may overflow, so we will start from the beginning.
position = (position + 1) % ArraySize(dictSlotsRef.DictSlots);
// Saving position for further, possible overwrite.
unsigned int _starting_position = position;

// How many times we had to skip slot as it was already occupied.
unsigned int _num_conflicts = 0;

// Searching for empty DictSlot<K, V> or used one with the matching key. It skips used, hashless DictSlots.
while (dictSlotsRef.DictSlots[position].IsUsed() &&
(!dictSlotsRef.DictSlots[position].HasKey() || dictSlotsRef.DictSlots[position].key != key)) {
++_num_conflicts;

if (overflow_listener == NULL) {
// There is no overflow listener, so we can't overwrite a slot. We will be looping until we find empty slot.
continue;
}

if (_overwrite_slot) {
// Overwriting starting position for faster further lookup.
// We had to skip slot as it is already occupied. Now we are checking if
// there is too many conflicts/skips and thus we can overwrite slot in
// the starting position.
if (overflow_listener(DICT_LISTENER_CONFLICTS_CAN_OVERWRITE, dictSlotsRef._num_used, _num_conflicts)) {
// Looks like dict is working as buffer and we can overwrite slot in the starting position.
position = _starting_position;
} else if (!dictSlotsRef.DictSlots[position].IsUsed()) {
// If slot isn't already used then we increment number of used slots.
++dictSlotsRef._num_used;
break;
}

dictSlotsRef.AddConflicts(_num_conflicts);
// Position may overflow, so we will start from the beginning.
position = (position + 1) % ArraySize(dictSlotsRef.DictSlots);
}

dictSlotsRef.DictSlots[position].key = key;
dictSlotsRef.DictSlots[position].value = value;
dictSlotsRef.DictSlots[position].SetFlags(DICT_SLOT_HAS_KEY | DICT_SLOT_IS_USED | DICT_SLOT_WAS_USED);
// Acknowledging slots array about number of conflicts as it calculates average number of conflicts per insert.
dictSlotsRef.AddConflicts(_num_conflicts);

// Incrementing number of slots used only if we're writing into empty slot.
if (!dictSlotsRef.DictSlots[position].IsUsed()) {
++dictSlotsRef._num_used;
}

// Writing slot in the position of empty slot or, when overwriting, in starting position.
WriteSlot(dictSlotsRef.DictSlots[position], key, value, DICT_SLOT_HAS_KEY | DICT_SLOT_IS_USED | DICT_SLOT_WAS_USED);
return true;
}

/***
* Writes slot with given key, value and flags.
*/
void WriteSlot(DictSlot<K, V>& _slot, const K _key, V _value, unsigned char _slot_flags) {
_slot.key = _key;
_slot.value = _value;
_slot.SetFlags(_slot_flags);
}

/**
* Inserts hashless value into given array of DictSlots.
*/
Expand Down
12 changes: 0 additions & 12 deletions DictBase.mqh
Original file line number Diff line number Diff line change
Expand Up @@ -236,18 +236,6 @@ class DictBase {
// No key found.
}

/**
* Checks whether overflow listener allows dict to grow up.
*/
bool IsGrowUpAllowed() {
if (overflow_listener == NULL) {
return true;
}

// Checking if overflow listener allows resize from current to higher number of slots.
return overflow_listener(DICT_OVERFLOW_REASON_FULL, Size(), 0);
}

/**
* Moves last slot to given one to fill the hole after removing the value.
*/
Expand Down
Loading

0 comments on commit baa4942

Please sign in to comment.