1144 lines
95 KiB
MQL5
1144 lines
95 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| CBitBuffer.mqh |
|
|
//| Copyright © 2025, Amr Ali |
|
|
//| https://www.mql5.com/en/users/amrali |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Amr Ali"
|
|
#property link "https://www.mql5.com/en/users/amrali"
|
|
#property version "1.02"
|
|
|
|
// Updates:
|
|
// 2025.07.21 - v.1.01 : The CBitBuffer class actively prevents illegal attempts to write after a read operation has begun,
|
|
// : which helps in maintaining data integrity. Users receive a specific error (BIT_BUFFER_MIXED_OPERATION_ERROR)
|
|
// : when they attempt to mix operations.
|
|
// 2025.07.22 - v.1.02 : Changed design of the class to allow the mixed read/write operations, by correct handling of partial
|
|
// : flushes/refills, which helps in maintaining data integrity when switching between read and write modes.
|
|
// : Removed the variable (m_operationMode) and the error code (BIT_BUFFER_MIXED_OPERATION_ERROR).
|
|
// : Cleaned the code comments for better clarity of intent.
|
|
// : Updated examples in "CBitBuffer_Test.mq5" to cover more test cases.
|
|
|
|
//--- Bit size constants for MQL5 data types ---
|
|
enum ENUM_BIT_SIZES
|
|
{
|
|
BIT_SIZE_BOOL = 1,
|
|
BIT_SIZE_CHAR = 8,
|
|
BIT_SIZE_UCHAR = 8,
|
|
BIT_SIZE_SHORT = 16,
|
|
BIT_SIZE_USHORT = 16,
|
|
BIT_SIZE_INT = 32,
|
|
BIT_SIZE_UINT = 32,
|
|
BIT_SIZE_LONG = 64,
|
|
BIT_SIZE_ULONG = 64,
|
|
BIT_SIZE_DATETIME = 64,
|
|
BIT_SIZE_FLOAT = 32,
|
|
BIT_SIZE_DOUBLE = 64
|
|
};
|
|
|
|
//--- Error codes for CBitBuffer operations ---
|
|
enum ENUM_BIT_BUFFER_ERROR
|
|
{
|
|
BIT_BUFFER_NO_ERROR = 0, // No error
|
|
BIT_BUFFER_INVALID_BIT_COUNT = 1, // Invalid number of bits specified for read/write
|
|
BIT_BUFFER_MEMORY_ALLOCATION_FAILED = 2, // Memory allocation failure
|
|
BIT_BUFFER_READ_OUT_OF_BOUNDS = 3, // Attempt to read beyond available data
|
|
BIT_BUFFER_WRITE_FLUSH_FAILED = 4, // Write operation failed during internal buffer flush
|
|
BIT_BUFFER_INVALID_READ_POSITION = 5, // Attempt to set read position out of valid bounds
|
|
BIT_BUFFER_DATA_TOO_LARGE = 6, // Data length exceeds maximum allowed capacity
|
|
BIT_BUFFER_READ_DATA_INCOMPLETE = 7, // Incomplete data found during read
|
|
BIT_BUFFER_RAW_OPERATION_FAILED = 8, // Failed to copy raw buffer data
|
|
BIT_BUFFER_FILE_SAVE_FAILED = 9, // File save failed
|
|
BIT_BUFFER_FILE_LOAD_FAILED = 10 // File load failed
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CBitBuffer Class |
|
|
//| Reads/writes individual bits or bit sequences to ulong[] buffer. |
|
|
//| Supports efficient bit manipulation and mixed read/write ops. |
|
|
//+------------------------------------------------------------------+
|
|
class CBitBuffer
|
|
{
|
|
private:
|
|
ulong m_mainBuffer[]; // Main storage for bits (ulong = 64 bits)
|
|
long m_totalWrittenBits; // Total bits written into buffer
|
|
long m_currentReadBitPosition;// Current bit position for reading
|
|
ulong m_writeBufferInternal; // Internal buffer for accumulating bits before writing (up to 64 bits)
|
|
int m_writeBufferBitCounter; // Bits in m_writeBufferInternal
|
|
ulong m_readBufferInternal; // Internal buffer for holding bits read from mainBuffer (up to 64 bits)
|
|
int m_readBufferBitCounter; // Bits in m_readBufferInternal
|
|
ENUM_BIT_BUFFER_ERROR m_lastError; // Stores the last error code
|
|
|
|
private: // Constants for variable-length encoding (VLQ)
|
|
enum ENUM_VLQ_CONSTANTS
|
|
{
|
|
VLQ_CONTINUATION_BIT = 0x80, // MSB indicates more bytes follow
|
|
VLQ_DATA_BITS = 0x7F // Lower 7 bits for data
|
|
};
|
|
|
|
private: // Helper methods
|
|
// Dynamically allocates memory for m_mainBuffer with exponential growth.
|
|
bool EnsureBufferCapacity(long requiredSize);
|
|
// Flushes any pending writes from the write cache to the main buffer.
|
|
bool FlushWriteCache();
|
|
// Refills the internal read buffer efficiently from the main buffer.
|
|
bool RefillReadBuffer();
|
|
// Creates a 64-bit bitmask for the rightmost 'n' bits.
|
|
ulong Bitmask64(int n) const { return (n == 64) ? 0xFFFFFFFFFFFFFFFF : ((1ULL << n) - 1); }
|
|
|
|
public: // Constructor and Destructor
|
|
CBitBuffer();
|
|
CBitBuffer(int initialCapacityBytes);
|
|
~CBitBuffer();
|
|
|
|
public: // Core bit operations
|
|
bool WriteBit(bool bit); // Writes a single bit
|
|
bool WriteBits(ulong value, int numberOfBits); // Writes N bits from a ulong
|
|
bool ReadBit(); // Reads a single bit
|
|
ulong ReadBits(int numberOfBits); // Reads N bits as a ulong
|
|
ulong PeekBits(int numberOfBits); // Reads N bits without advancing position
|
|
|
|
public: // Position and size management
|
|
bool SetReadPosition(long bitPosition); // Sets read position in bits
|
|
long GetReadPosition() const { return m_currentReadBitPosition; }
|
|
bool ResetReadPosition() { return SetReadPosition(0); } // Resets read position to 0
|
|
bool SkipBits(long bitsToSkip); // Skips N bits from current read position
|
|
long GetTotalWrittenBits() const { return m_totalWrittenBits + m_writeBufferBitCounter; } // Total bits written
|
|
long GetTotalBytesWritten() const { return (GetTotalWrittenBits() + 7) / 8; } // Total bytes written
|
|
long GetTotalBytesAllocated() const { return ArraySize(m_mainBuffer) * sizeof(ulong); } // Total allocated bytes
|
|
long GetRemainingReadBits() const { return GetTotalWrittenBits() - GetReadPosition(); } // Remaining bits to read
|
|
|
|
public: // Data type specific read/write operations
|
|
bool WriteBool(bool value) { return WriteBit(value); }
|
|
bool WriteChar(char value) { return WriteBits(value, BIT_SIZE_CHAR); }
|
|
bool WriteUChar(uchar value) { return WriteBits(value, BIT_SIZE_UCHAR); }
|
|
bool WriteShort(short value) { return WriteBits(value, BIT_SIZE_SHORT); }
|
|
bool WriteUShort(ushort value) { return WriteBits(value, BIT_SIZE_USHORT); }
|
|
bool WriteInt(int value) { return WriteBits(value, BIT_SIZE_INT); }
|
|
bool WriteUInt(uint value) { return WriteBits(value, BIT_SIZE_UINT); }
|
|
bool WriteLong(long value) { return WriteBits(value, BIT_SIZE_LONG); }
|
|
bool WriteULong(ulong value) { return WriteBits(value, BIT_SIZE_ULONG); }
|
|
bool WriteDatetime(datetime value) { return WriteBits(value, BIT_SIZE_DATETIME); }
|
|
bool WriteFloat(float value); // Writes a 32-bit float
|
|
bool WriteDouble(double value); // Writes a 64-bit double
|
|
bool WriteString(string value); // Writes string with length prefix
|
|
template<typename T>
|
|
bool WriteStruct(T &struct_object); // Writes struct with length prefix
|
|
|
|
bool ReadBool() { return ReadBit(); }
|
|
char ReadChar() { return (char)ReadBits(BIT_SIZE_CHAR); }
|
|
uchar ReadUChar() { return (uchar)ReadBits(BIT_SIZE_UCHAR); }
|
|
short ReadShort() { return (short)ReadBits(BIT_SIZE_SHORT); }
|
|
ushort ReadUShort() { return (ushort)ReadBits(BIT_SIZE_USHORT); }
|
|
int ReadInt() { return (int)ReadBits(BIT_SIZE_INT); }
|
|
uint ReadUInt() { return (uint)ReadBits(BIT_SIZE_UINT); }
|
|
long ReadLong() { return (long)ReadBits(BIT_SIZE_LONG); }
|
|
ulong ReadULong() { return ReadBits(BIT_SIZE_ULONG); }
|
|
datetime ReadDatetime() { return (datetime)ReadBits(BIT_SIZE_DATETIME); }
|
|
float ReadFloat(); // Reads a 32-bit float
|
|
double ReadDouble(); // Reads a 64-bit double
|
|
string ReadString(); // Reads string with length prefix
|
|
template<typename T>
|
|
T ReadStruct(); // Reads struct with length prefix
|
|
|
|
public: // Variable-length encoding for integers (VLQ)
|
|
bool WriteVarInt(int value) { ClearLastError(); return WriteVarULong((uint)((value << 1) ^ (value >> 31))); } // ZigZag + VLQ
|
|
bool WriteVarUInt(uint value) { ClearLastError(); return WriteVarULong(value); } // VLQ
|
|
bool WriteVarLong(long value) { ClearLastError(); return WriteVarULong((ulong)((value << 1) ^ (value >> 63))); } // ZigZag + VLQ
|
|
bool WriteVarULong(ulong value); // Writes unsigned long using VLQ
|
|
int ReadVarInt(); // Reads signed int using ZigZag + VLQ
|
|
uint ReadVarUInt(); // Reads unsigned int using VLQ
|
|
long ReadVarLong(); // Reads signed long using ZigZag + VLQ
|
|
ulong ReadVarULong(); // Reads unsigned long using VLQ
|
|
|
|
public: // Buffer management
|
|
void Clear(); // Clears buffer content and resets state
|
|
bool GetFinalizedBuffer(ulong &destinationArray[]); // Copies buffer content to array
|
|
bool SetRawBuffer(const ulong &sourceBuffer[], long numBits = -1); // Sets buffer content from array
|
|
bool Save(string filename, bool common = false); // Saves buffer to file
|
|
bool Load(string filename, bool common = false); // Loads buffer from file
|
|
|
|
public: // Error handling
|
|
ENUM_BIT_BUFFER_ERROR GetLastError() const { return m_lastError; } // Returns last error code
|
|
string GetLastErrorString() const; // Returns error description string
|
|
void ClearLastError() { m_lastError = BIT_BUFFER_NO_ERROR; } // Clears last error
|
|
|
|
void PrintHex(int start = 0, int count = WHOLE_ARRAY) const; // Prints main buffer in hex (debugging)
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Default Constructor |
|
|
//| Initializes the CBitBuffer instance. |
|
|
//+------------------------------------------------------------------+
|
|
CBitBuffer::CBitBuffer()
|
|
{
|
|
ArrayResize(m_mainBuffer, 1, 1024); // Start with a minimum of one ulong.
|
|
m_totalWrittenBits = 0;
|
|
m_currentReadBitPosition = 0;
|
|
m_writeBufferInternal = 0;
|
|
m_writeBufferBitCounter = 0;
|
|
m_readBufferInternal = 0;
|
|
m_readBufferBitCounter = 0;
|
|
m_lastError = BIT_BUFFER_NO_ERROR;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor with Initial Capacity |
|
|
//| Initializes with specified byte capacity. |
|
|
//+------------------------------------------------------------------+
|
|
CBitBuffer::CBitBuffer(int initialCapacityBytes)
|
|
{
|
|
// Calculate required ulongs based on initial capacity in bytes.
|
|
long initialUlongs = (initialCapacityBytes * 8 + 63) / 64;
|
|
if (ArrayResize(m_mainBuffer, (int)initialUlongs) == -1)
|
|
{
|
|
m_lastError = BIT_BUFFER_MEMORY_ALLOCATION_FAILED;
|
|
PrintFormat("Error %d: Failed to resize main buffer to %d bytes.", m_lastError, initialCapacityBytes);
|
|
}
|
|
m_totalWrittenBits = 0;
|
|
m_currentReadBitPosition = 0;
|
|
m_writeBufferInternal = 0;
|
|
m_writeBufferBitCounter = 0;
|
|
m_readBufferInternal = 0;
|
|
m_readBufferBitCounter = 0;
|
|
m_lastError = BIT_BUFFER_NO_ERROR;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Destructor: Releases the memory allocated for the main buffer. |
|
|
//+------------------------------------------------------------------+
|
|
CBitBuffer::~CBitBuffer()
|
|
{
|
|
ArrayFree(m_mainBuffer);
|
|
}
|
|
|
|
//---
|
|
//| PRIVATE HELPER METHODS
|
|
//---
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Ensures buffer has enough capacity (2x growth, min increment). |
|
|
//+------------------------------------------------------------------+
|
|
bool CBitBuffer::EnsureBufferCapacity(long requiredSize)
|
|
{
|
|
if (requiredSize <= ArraySize(m_mainBuffer)) return true;
|
|
|
|
long currentCapacity = ArraySize(m_mainBuffer);
|
|
long newCapacity = MathMax(requiredSize, currentCapacity * 2);
|
|
newCapacity = MathMax(newCapacity, currentCapacity + 256); // Grow by at least 256 ulongs
|
|
|
|
if (ArrayResize(m_mainBuffer, (int)newCapacity, 1024) == -1)
|
|
{
|
|
m_lastError = BIT_BUFFER_MEMORY_ALLOCATION_FAILED;
|
|
PrintFormat("Error %d: Failed to resize main buffer to %d ulongs. Current: %d, Required: %d",
|
|
m_lastError, newCapacity, currentCapacity, requiredSize);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Flushes accumulated bits from write buffer to main buffer. |
|
|
//| Handles merging into partially filled ulongs. |
|
|
//+------------------------------------------------------------------+
|
|
bool CBitBuffer::FlushWriteCache()
|
|
{
|
|
if (m_writeBufferBitCounter == 0) return true;
|
|
|
|
long targetArrIndex = m_totalWrittenBits / 64;
|
|
int bitOffsetInUlong = (int)(m_totalWrittenBits % 64);
|
|
|
|
// Ensure capacity for the target ulong.
|
|
if (!EnsureBufferCapacity(targetArrIndex + 1))
|
|
{
|
|
m_lastError = BIT_BUFFER_MEMORY_ALLOCATION_FAILED;
|
|
PrintFormat("Error %d: Failed to resize main buffer during flush.", m_lastError);
|
|
return false;
|
|
}
|
|
|
|
// If the target ulong is partially filled, merge new bits. Otherwise, assign directly.
|
|
if (bitOffsetInUlong != 0)
|
|
{
|
|
// Shift new bits into position and OR with existing bits.
|
|
m_mainBuffer[targetArrIndex] |= (m_writeBufferInternal << bitOffsetInUlong);
|
|
}
|
|
else
|
|
{
|
|
// Target ulong is empty/aligned, just assign.
|
|
m_mainBuffer[targetArrIndex] = m_writeBufferInternal;
|
|
}
|
|
|
|
m_totalWrittenBits += m_writeBufferBitCounter;
|
|
m_writeBufferInternal = 0;
|
|
m_writeBufferBitCounter = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Refills internal read buffer from main buffer. |
|
|
//| Handles unaligned read positions by shifting. |
|
|
//+------------------------------------------------------------------+
|
|
bool CBitBuffer::RefillReadBuffer()
|
|
{
|
|
long targetArrIndex = m_currentReadBitPosition / 64;
|
|
int bitOffsetInUlong = (int)(m_currentReadBitPosition % 64);
|
|
|
|
// Check for read out of bounds.
|
|
if (targetArrIndex >= ArraySize(m_mainBuffer))
|
|
{
|
|
m_lastError = BIT_BUFFER_READ_OUT_OF_BOUNDS;
|
|
PrintFormat("Error %d: Attempt to read beyond main buffer size during RefillReadBuffer.", m_lastError);
|
|
return false;
|
|
}
|
|
|
|
// Load the full ulong from the main buffer.
|
|
m_readBufferInternal = m_mainBuffer[targetArrIndex];
|
|
|
|
// If not 64-bit aligned, shift out already-read bits.
|
|
if (bitOffsetInUlong != 0)
|
|
{
|
|
m_readBufferInternal >>= bitOffsetInUlong;
|
|
}
|
|
|
|
m_readBufferBitCounter = 64 - bitOffsetInUlong; // Update available bits in internal buffer.
|
|
|
|
return true;
|
|
}
|
|
|
|
//---
|
|
//| PUBLIC CORE WRITE METHODS
|
|
//---
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Writes a single boolean bit. |
|
|
//+------------------------------------------------------------------+
|
|
bool CBitBuffer::WriteBit(bool bitValue)
|
|
{
|
|
return WriteBits(bitValue ? 1 : 0, BIT_SIZE_BOOL);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Writes a specified number of bits from a ulong value. |
|
|
//| Handles splitting writes across internal and main buffer. |
|
|
//+------------------------------------------------------------------+
|
|
bool CBitBuffer::WriteBits(ulong value, int numberOfBits)
|
|
{
|
|
ClearLastError();
|
|
if (numberOfBits <= 0 || numberOfBits > 64)
|
|
{
|
|
m_lastError = BIT_BUFFER_INVALID_BIT_COUNT;
|
|
PrintFormat("Error %d: WriteBits called with invalid numberOfBits (%d), must be 1-64.", m_lastError, numberOfBits);
|
|
return false;
|
|
}
|
|
|
|
// Invalidate internal read buffer as we are writing.
|
|
if (m_readBufferBitCounter > 0)
|
|
{
|
|
m_readBufferInternal = 0;
|
|
m_readBufferBitCounter = 0;
|
|
}
|
|
|
|
// Mask value to only keep relevant bits for N < 64.
|
|
if (numberOfBits < 64)
|
|
{
|
|
value &= Bitmask64(numberOfBits);
|
|
}
|
|
|
|
// Calculate the effective capacity remaining in the current ulong block
|
|
// in the main buffer, considering m_totalWrittenBits alignment.
|
|
int partialUlongBitsFlushed = (int)(m_totalWrittenBits % 64);
|
|
int effectiveWriteBufferCapacity = 64 - partialUlongBitsFlushed;
|
|
|
|
// Calculate how many bits can fit into the current internal write buffer
|
|
// (which is implicitly filling the current ulong block).
|
|
int spaceLeft = effectiveWriteBufferCapacity - m_writeBufferBitCounter;
|
|
|
|
// Fast path: The new bits fit entirely into the current internal write buffer.
|
|
if (numberOfBits <= spaceLeft)
|
|
{
|
|
// Shift the new bits into the internal buffer.
|
|
// Bits are accumulated from the LSB (rightmost) side.
|
|
m_writeBufferInternal |= value << m_writeBufferBitCounter;
|
|
m_writeBufferBitCounter += numberOfBits;
|
|
|
|
// If the internal buffer is now full (meaning the current ulong block is complete),
|
|
// flush it to the main buffer.
|
|
if (m_writeBufferBitCounter == effectiveWriteBufferCapacity)
|
|
{
|
|
if (!FlushWriteCache())
|
|
{
|
|
m_lastError = BIT_BUFFER_WRITE_FLUSH_FAILED;
|
|
PrintFormat("Error %d: Flush failed after full buffer.", m_lastError);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
// Slow path: The new bits do not fit entirely, split across internal buffer and main buffer.
|
|
else
|
|
{
|
|
// Fill the current internal buffer completely with the first part of 'value'.
|
|
ulong maskForFirstPart = Bitmask64(spaceLeft);
|
|
m_writeBufferInternal |= (value & maskForFirstPart) << m_writeBufferBitCounter;
|
|
m_writeBufferBitCounter = effectiveWriteBufferCapacity; // Current ulong block is now full.
|
|
|
|
// Flush the full (capacity) internal buffer to the main buffer.
|
|
if (!FlushWriteCache())
|
|
{
|
|
m_lastError = BIT_BUFFER_WRITE_FLUSH_FAILED;
|
|
PrintFormat("Error %d: Flush failed during split write (first part).", m_lastError);
|
|
return false;
|
|
}
|
|
|
|
// Prepare remaining bits from the original 'value' for the next write.
|
|
value >>= spaceLeft; // Shift out the bits that were just written.
|
|
numberOfBits -= spaceLeft; // Reduce the count of bits remaining to write.
|
|
|
|
// Shift remaining bits into the now-empty internal buffer (guaranteed to fit now).
|
|
m_writeBufferInternal = value;
|
|
m_writeBufferBitCounter = numberOfBits;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//---
|
|
//| PUBLIC CORE READ METHODS
|
|
//---
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Reads a single bit. |
|
|
//+------------------------------------------------------------------+
|
|
bool CBitBuffer::ReadBit()
|
|
{
|
|
return ReadBits(BIT_SIZE_BOOL) != 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Reads a specified number of bits as a ulong. |
|
|
//| Flushes write cache and refills read cache as needed. |
|
|
//+------------------------------------------------------------------+
|
|
ulong CBitBuffer::ReadBits(int numberOfBits)
|
|
{
|
|
ClearLastError();
|
|
if (numberOfBits <= 0 || numberOfBits > 64)
|
|
{
|
|
m_lastError = BIT_BUFFER_INVALID_BIT_COUNT;
|
|
PrintFormat("Error %d: ReadBits called with invalid numberOfBits (%d), must be 1-64.", m_lastError, numberOfBits);
|
|
return 0;
|
|
}
|
|
|
|
// Ensure pending writes are flushed to see latest data.
|
|
if (m_writeBufferBitCounter > 0)
|
|
{
|
|
if (!FlushWriteCache())
|
|
{
|
|
m_lastError = BIT_BUFFER_WRITE_FLUSH_FAILED;
|
|
PrintFormat("Error %d: Failed to flush write buffer before reading.", m_lastError);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Check for reading beyond written data.
|
|
if (m_currentReadBitPosition + numberOfBits > m_totalWrittenBits)
|
|
{
|
|
m_lastError = BIT_BUFFER_READ_OUT_OF_BOUNDS;
|
|
PrintFormat("Error %d: Attempt to read beyond written data. ReadPos:%d, Requested bits:%d, Remaining bits:%d",
|
|
m_lastError, m_currentReadBitPosition, numberOfBits, m_totalWrittenBits - m_currentReadBitPosition);
|
|
return 0;
|
|
}
|
|
|
|
ulong readValue = 0;
|
|
int bitsReadIntoResult = 0;
|
|
|
|
while (bitsReadIntoResult < numberOfBits)
|
|
{
|
|
// Refill internal read buffer if empty.
|
|
if (m_readBufferBitCounter == 0)
|
|
{
|
|
if (!RefillReadBuffer())
|
|
{
|
|
// RefillReadBuffer sets its own specific errors.
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Copy available bits from internal read buffer.
|
|
int bitsToTake = MathMin(numberOfBits - bitsReadIntoResult, m_readBufferBitCounter);
|
|
ulong mask = Bitmask64(bitsToTake);
|
|
ulong bits = m_readBufferInternal & mask;
|
|
readValue |= (bits << bitsReadIntoResult);
|
|
|
|
// Advance internal buffer and main read position.
|
|
m_readBufferInternal >>= bitsToTake;
|
|
m_readBufferBitCounter -= bitsToTake;
|
|
m_currentReadBitPosition += bitsToTake;
|
|
|
|
bitsReadIntoResult += bitsToTake;
|
|
}
|
|
|
|
return readValue;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Reads N bits without advancing read position. |
|
|
//+------------------------------------------------------------------+
|
|
ulong CBitBuffer::PeekBits(int numberOfBits)
|
|
{
|
|
// Save current read state.
|
|
ulong tempReadBuffer = m_readBufferInternal;
|
|
int tempReadCounter = m_readBufferBitCounter;
|
|
long tempReadPosition = m_currentReadBitPosition;
|
|
ENUM_BIT_BUFFER_ERROR tempLastError = m_lastError;
|
|
|
|
// Perform read using ReadBits (handles flushes/refills).
|
|
ulong peekedResult = ReadBits(numberOfBits);
|
|
|
|
// Restore original read state.
|
|
m_readBufferInternal = tempReadBuffer;
|
|
m_readBufferBitCounter = tempReadCounter;
|
|
m_currentReadBitPosition = tempReadPosition;
|
|
m_lastError = tempLastError;
|
|
|
|
return peekedResult;
|
|
}
|
|
|
|
//---
|
|
//| PUBLIC TYPE-SPECIFIC READ/WRITE METHODS
|
|
//---
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Writes a 32-bit float by converting to its uint bit pattern. |
|
|
//+------------------------------------------------------------------+
|
|
bool CBitBuffer::WriteFloat(float value)
|
|
{
|
|
union _f {float value; uint bits;} flt;
|
|
flt.value = value;
|
|
|
|
// Write the 32-bit representation of the float.
|
|
return WriteBits(flt.bits, BIT_SIZE_FLOAT);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Reads a 32-bit float by converting uint bit pattern to float. |
|
|
//+------------------------------------------------------------------+
|
|
float CBitBuffer::ReadFloat()
|
|
{
|
|
ClearLastError();
|
|
// Read the 32-bit representation of float.
|
|
uint bits = (uint)ReadBits(BIT_SIZE_FLOAT);
|
|
if (m_lastError != BIT_BUFFER_NO_ERROR) return 0.0;
|
|
|
|
// Union to interpret unsigned integer bits as a float.
|
|
union _f {float value; uint bits;} flt;
|
|
flt.bits = bits;
|
|
return flt.value;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Writes a 64-bit double by converting to its ulong bit pattern. |
|
|
//+------------------------------------------------------------------+
|
|
bool CBitBuffer::WriteDouble(double value)
|
|
{
|
|
union _d {double value; ulong bits;} dbl;
|
|
dbl.value = value;
|
|
|
|
// Write the 64-bit representation of the double.
|
|
return WriteBits(dbl.bits, BIT_SIZE_DOUBLE);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Reads a 64-bit double by converting ulong bit pattern to double. |
|
|
//+------------------------------------------------------------------+
|
|
double CBitBuffer::ReadDouble()
|
|
{
|
|
ClearLastError();
|
|
// Read the 64-bit representation of double.
|
|
ulong bits = ReadBits(BIT_SIZE_DOUBLE);
|
|
if (m_lastError != BIT_BUFFER_NO_ERROR) return 0.0;
|
|
|
|
// Union to interpret unsigned long bits as a double.
|
|
union _d {double value; ulong bits;} dbl;
|
|
dbl.bits = bits;
|
|
return dbl.value;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Writes a string, prefixed by its UTF-8 byte length (ushort). |
|
|
//+------------------------------------------------------------------+
|
|
bool CBitBuffer::WriteString(string value)
|
|
{
|
|
ClearLastError();
|
|
uchar utfBytes[];
|
|
StringToCharArray(value, utfBytes, 0, -1, CP_UTF8);
|
|
|
|
int lengthBytes = ArraySize(utfBytes) - 1; // Exclude null terminator
|
|
if (lengthBytes > USHORT_MAX)
|
|
{
|
|
m_lastError = BIT_BUFFER_DATA_TOO_LARGE;
|
|
PrintFormat("Error %d: String length %d exceeds max ushort (%d).", m_lastError, lengthBytes, USHORT_MAX);
|
|
return false;
|
|
}
|
|
|
|
if (!WriteUShort((ushort)lengthBytes)) return false; // Write length
|
|
|
|
for (int i = 0; i < lengthBytes; i++) // Write each byte
|
|
{
|
|
if (!WriteUChar(utfBytes[i])) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Reads a string prefixed by its length. Decodes UTF-8 bytes. |
|
|
//+------------------------------------------------------------------+
|
|
string CBitBuffer::ReadString()
|
|
{
|
|
ClearLastError();
|
|
ushort lengthChars = ReadUShort();
|
|
if (GetLastError() != BIT_BUFFER_NO_ERROR) return "";
|
|
|
|
if (lengthChars == 0xFFFF) return NULL; // Null string
|
|
if (lengthChars == 0) return ""; // Empty string
|
|
|
|
long requiredBitsForString = (long)lengthChars * BIT_SIZE_UCHAR;
|
|
if (requiredBitsForString > GetRemainingReadBits())
|
|
{
|
|
m_lastError = BIT_BUFFER_READ_DATA_INCOMPLETE;
|
|
PrintFormat("Error %d: Not enough data for string (%d bits). ReadPos:%d, Remaining:%d",
|
|
m_lastError, requiredBitsForString, GetReadPosition(), GetRemainingReadBits());
|
|
return "";
|
|
}
|
|
|
|
uchar utfBytes[];
|
|
ArrayResize(utfBytes, lengthChars);
|
|
|
|
for (int i = 0; i < lengthChars; i++)
|
|
{
|
|
utfBytes[i] = ReadUChar();
|
|
if (GetLastError() != BIT_BUFFER_NO_ERROR) return "";
|
|
}
|
|
return CharArrayToString(utfBytes, 0, -1, CP_UTF8);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Writes a struct, prefixed by its byte length (ushort). |
|
|
//+------------------------------------------------------------------+
|
|
template<typename T>
|
|
bool CBitBuffer::WriteStruct(T &struct_object)
|
|
{
|
|
ClearLastError();
|
|
uchar bytes_arr[];
|
|
::ResetLastError();
|
|
if(!StructToCharArray(struct_object, bytes_arr))
|
|
{
|
|
m_lastError = BIT_BUFFER_RAW_OPERATION_FAILED;
|
|
PrintFormat("Error %d: StructToCharArray() failed. MQL5 Error: %d", m_lastError, ::GetLastError());
|
|
return false;
|
|
}
|
|
|
|
int lengthBytes = ArraySize(bytes_arr);
|
|
if (lengthBytes > USHORT_MAX)
|
|
{
|
|
m_lastError = BIT_BUFFER_DATA_TOO_LARGE;
|
|
PrintFormat("Error %d: Struct length %d exceeds max ushort (%d).", m_lastError, lengthBytes, USHORT_MAX);
|
|
return false;
|
|
}
|
|
|
|
if (!WriteUShort((ushort)lengthBytes)) return false; // Write length
|
|
|
|
for (int i = 0; i < lengthBytes; i++) // Write each byte
|
|
{
|
|
if (!WriteUChar(bytes_arr[i])) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Reads a struct prefixed by its byte length. |
|
|
//+------------------------------------------------------------------+
|
|
template<typename T>
|
|
T CBitBuffer::ReadStruct()
|
|
{
|
|
ClearLastError();
|
|
T struct_result = {};
|
|
ushort lengthBytes = ReadUShort();
|
|
if (GetLastError() != BIT_BUFFER_NO_ERROR) return struct_result;
|
|
|
|
long requiredBitsForStruct = (long)lengthBytes * BIT_SIZE_UCHAR;
|
|
if (requiredBitsForStruct > GetRemainingReadBits())
|
|
{
|
|
m_lastError = BIT_BUFFER_READ_DATA_INCOMPLETE;
|
|
PrintFormat("Error %d: Not enough data for struct (%d bits). ReadPos:%d, Remaining:%d",
|
|
m_lastError, requiredBitsForStruct, GetReadPosition(), GetRemainingReadBits());
|
|
return struct_result;
|
|
}
|
|
|
|
uchar bytes_arr[];
|
|
ArrayResize(bytes_arr, lengthBytes);
|
|
|
|
for (int i = 0; i < lengthBytes; i++)
|
|
{
|
|
bytes_arr[i] = ReadUChar();
|
|
if (GetLastError() != BIT_BUFFER_NO_ERROR) return struct_result;
|
|
}
|
|
|
|
::ResetLastError();
|
|
if(!CharArrayToStruct(struct_result, bytes_arr))
|
|
{
|
|
m_lastError = BIT_BUFFER_RAW_OPERATION_FAILED;
|
|
PrintFormat("Error %d: CharArrayToStruct() failed. MQL5 Error: %d", m_lastError, ::GetLastError());
|
|
return struct_result;
|
|
}
|
|
return struct_result;
|
|
}
|
|
|
|
//---
|
|
//| PUBLIC VLQ ENCODING METHODS
|
|
//---
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Writes an unsigned long using Variable-Length Quantity (VLQ) |
|
|
//| encoding. Smaller values use fewer bits. |
|
|
//+------------------------------------------------------------------+
|
|
bool CBitBuffer::WriteVarULong(ulong value)
|
|
{
|
|
ClearLastError();
|
|
do
|
|
{
|
|
uchar byte = (uchar)(value & VLQ_DATA_BITS);
|
|
value >>= 7;
|
|
if (value > 0) byte |= VLQ_CONTINUATION_BIT; // Set MSB if more bytes follow
|
|
if (!WriteUChar(byte)) return false;
|
|
}
|
|
while(value > 0);
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Reads an unsigned long using VLQ decoding. |
|
|
//+------------------------------------------------------------------+
|
|
ulong CBitBuffer::ReadVarULong()
|
|
{
|
|
ClearLastError();
|
|
ulong value = 0;
|
|
long shift = 0;
|
|
uchar byte;
|
|
|
|
do
|
|
{
|
|
byte = ReadUChar();
|
|
if (GetLastError() != BIT_BUFFER_NO_ERROR) return 0;
|
|
|
|
value |= (ulong)(byte & VLQ_DATA_BITS) << shift;
|
|
if (shift >= 64) // Prevent overflow
|
|
{
|
|
m_lastError = BIT_BUFFER_DATA_TOO_LARGE;
|
|
PrintFormat("Error %d: VLQ sequence malformed or too long.", m_lastError);
|
|
return 0;
|
|
}
|
|
shift += 7;
|
|
}
|
|
while ((byte & VLQ_CONTINUATION_BIT) != 0); // Continue if MSB is set
|
|
return value;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Reads an unsigned int using VLQ decoding. |
|
|
//+------------------------------------------------------------------+
|
|
uint CBitBuffer::ReadVarUInt()
|
|
{
|
|
ClearLastError();
|
|
uint value = 0;
|
|
int shift = 0;
|
|
uchar byte;
|
|
|
|
do
|
|
{
|
|
byte = ReadUChar();
|
|
if (GetLastError() != BIT_BUFFER_NO_ERROR) return 0;
|
|
|
|
value |= (uint)(byte & VLQ_DATA_BITS) << shift;
|
|
if (shift >= 32) // Prevent overflow
|
|
{
|
|
m_lastError = BIT_BUFFER_DATA_TOO_LARGE;
|
|
PrintFormat("Error %d: VLQ sequence malformed or too long (uint).", m_lastError);
|
|
return 0;
|
|
}
|
|
shift += 7;
|
|
}
|
|
while ((byte & VLQ_CONTINUATION_BIT) != 0);
|
|
return value;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Reads a signed long using ZigZag decoding + VLQ. |
|
|
//| Maps signed values to unsigned for efficient VLQ packing. |
|
|
//+------------------------------------------------------------------+
|
|
long CBitBuffer::ReadVarLong()
|
|
{
|
|
// Read the VLQ encoded unsigned long.
|
|
ulong unsignedValue = ReadVarULong();
|
|
if (GetLastError() != BIT_BUFFER_NO_ERROR) return 0;
|
|
|
|
// Perform ZigZag decoding:
|
|
// For even unsigned: (unsigned >> 1)
|
|
// For odd unsigned: ~(unsigned >> 1)
|
|
return (long)((unsignedValue >> 1) ^ (-(long)(unsignedValue & 1)));
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Reads a signed int using ZigZag decoding + VLQ. |
|
|
//+------------------------------------------------------------------+
|
|
int CBitBuffer::ReadVarInt()
|
|
{
|
|
// Read the VLQ encoded unsigned int.
|
|
uint unsignedValue = ReadVarUInt();
|
|
if (GetLastError() != BIT_BUFFER_NO_ERROR) return 0;
|
|
|
|
// Perform ZigZag decoding for int.
|
|
return (int)((unsignedValue >> 1) ^ (-(int)(unsignedValue & 1)));
|
|
}
|
|
|
|
//---
|
|
//| PUBLIC POSITION MANAGEMENT METHODS
|
|
//---
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Sets the absolute read position within the buffer. |
|
|
//+------------------------------------------------------------------+
|
|
bool CBitBuffer::SetReadPosition(long bitPosition)
|
|
{
|
|
ClearLastError();
|
|
if (bitPosition < 0 || bitPosition > m_totalWrittenBits)
|
|
{
|
|
m_lastError = BIT_BUFFER_INVALID_READ_POSITION;
|
|
PrintFormat("Error %d: Attempt to set read position out of bounds. Position: %d, TotalWrittenBits: %d",
|
|
m_lastError, bitPosition, m_totalWrittenBits);
|
|
return false;
|
|
}
|
|
|
|
m_currentReadBitPosition = bitPosition;
|
|
// Invalidate internal read buffer as its contents might not match the new position.
|
|
m_readBufferInternal = 0;
|
|
m_readBufferBitCounter = 0;
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Advances the read position by a specified number of bits. |
|
|
//+------------------------------------------------------------------+
|
|
bool CBitBuffer::SkipBits(long bitsToSkip)
|
|
{
|
|
ClearLastError();
|
|
if (bitsToSkip < 0)
|
|
{
|
|
m_lastError = BIT_BUFFER_INVALID_BIT_COUNT;
|
|
PrintFormat("Error %d: SkipBits called with negative bitsToSkip (%d).", m_lastError, bitsToSkip);
|
|
return false;
|
|
}
|
|
if (bitsToSkip == 0) return true;
|
|
|
|
long newReadPos = m_currentReadBitPosition + bitsToSkip;
|
|
if (newReadPos > m_totalWrittenBits)
|
|
{
|
|
// If the skip would go beyond the total written bits, cap it at the end.
|
|
newReadPos = m_totalWrittenBits;
|
|
m_lastError = BIT_BUFFER_READ_OUT_OF_BOUNDS;
|
|
PrintFormat("Warning %d: Tried to skip beyond written data. Adjusted skip amount to prevent out-of-bounds.", m_lastError);
|
|
return SetReadPosition(newReadPos);
|
|
}
|
|
|
|
return SetReadPosition(newReadPos); // Set the adjusted position
|
|
}
|
|
|
|
//---
|
|
//| PUBLIC BUFFER MANAGEMENT METHODS
|
|
//---
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Clears buffer, keeping allocated memory for potential reuse. |
|
|
//+------------------------------------------------------------------+
|
|
void CBitBuffer::Clear()
|
|
{
|
|
ZeroMemory(m_mainBuffer);
|
|
m_totalWrittenBits = 0;
|
|
m_currentReadBitPosition = 0;
|
|
m_writeBufferInternal = 0;
|
|
m_writeBufferBitCounter = 0;
|
|
m_readBufferInternal = 0;
|
|
m_readBufferBitCounter = 0;
|
|
m_lastError = BIT_BUFFER_NO_ERROR;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Copies finalized buffer content to a destination ulong array. |
|
|
//| Flushes any pending writes first. |
|
|
//+------------------------------------------------------------------+
|
|
bool CBitBuffer::GetFinalizedBuffer(ulong &destinationArray[])
|
|
{
|
|
ClearLastError();
|
|
if (m_writeBufferBitCounter > 0)
|
|
{
|
|
if (!FlushWriteCache())
|
|
{
|
|
m_lastError = BIT_BUFFER_WRITE_FLUSH_FAILED;
|
|
PrintFormat("Error %d: Failed to flush write buffer before getting finalized buffer.", m_lastError);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
long totalUlongsWritten = (m_totalWrittenBits + 63) / 64;
|
|
if (totalUlongsWritten == 0)
|
|
{
|
|
ArrayFree(destinationArray);
|
|
return true;
|
|
}
|
|
|
|
// Resize the destination array to hold the written data.
|
|
if (ArrayResize(destinationArray, (int)totalUlongsWritten) == -1)
|
|
{
|
|
m_lastError = BIT_BUFFER_MEMORY_ALLOCATION_FAILED;
|
|
PrintFormat("Error %d: Failed to resize destination array to %d ulongs.", m_lastError, totalUlongsWritten);
|
|
return false;
|
|
}
|
|
|
|
// Copy data from the internal main buffer to the destination array.
|
|
ArrayCopy(destinationArray, m_mainBuffer, 0, 0, (int)totalUlongsWritten);
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Sets internal buffer content from a source ulong array. |
|
|
//| Resets state and copies data. |
|
|
//+------------------------------------------------------------------+
|
|
bool CBitBuffer::SetRawBuffer(const ulong &sourceBuffer[], long numBits = -1)
|
|
{
|
|
Clear();
|
|
|
|
long sourceUlongCount = ArraySize(sourceBuffer);
|
|
long effectiveNumBits = (numBits == -1) ? sourceUlongCount * 64 : numBits;
|
|
|
|
if (effectiveNumBits < 0)
|
|
{
|
|
m_lastError = BIT_BUFFER_INVALID_BIT_COUNT;
|
|
PrintFormat("Error %d: SetRawBuffer called with negative numBits (%d).", m_lastError, numBits);
|
|
return false;
|
|
}
|
|
|
|
long requiredUlongs = (effectiveNumBits + 63) / 64;
|
|
if (requiredUlongs > sourceUlongCount)
|
|
{
|
|
m_lastError = BIT_BUFFER_READ_DATA_INCOMPLETE;
|
|
PrintFormat("Error %d: Source buffer too small for specified numBits. Source ulongs: %d, Required ulongs: %d.",
|
|
m_lastError, sourceUlongCount, requiredUlongs);
|
|
return false;
|
|
}
|
|
|
|
// Ensure the main buffer has enough capacity.
|
|
if (!EnsureBufferCapacity(requiredUlongs))
|
|
{
|
|
m_lastError = BIT_BUFFER_MEMORY_ALLOCATION_FAILED;
|
|
PrintFormat("Error %d: Failed to allocate memory for SetRawBuffer.", m_lastError);
|
|
return false;
|
|
}
|
|
|
|
// Copy the raw data from the source buffer.
|
|
ArrayCopy(m_mainBuffer, sourceBuffer, 0, 0, (int)requiredUlongs);
|
|
m_totalWrittenBits = effectiveNumBits; // Set total written bits to the effective number of bits.
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Saves buffer content to a file as raw binary data. |
|
|
//| The file is prefixed by the total number of written bits. |
|
|
//+------------------------------------------------------------------+
|
|
bool CBitBuffer::Save(string filename, bool common = false)
|
|
{
|
|
ClearLastError();
|
|
// Ensure all pending writes are flushed before saving.
|
|
if (m_writeBufferBitCounter > 0)
|
|
{
|
|
if (!FlushWriteCache())
|
|
{
|
|
m_lastError = BIT_BUFFER_WRITE_FLUSH_FAILED;
|
|
PrintFormat("Error %d: Failed to flush write buffer before saving file.", m_lastError);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int fileHandle = FileOpen(filename, FILE_WRITE | FILE_BIN | FILE_SHARE_READ | (common ? FILE_COMMON : 0));
|
|
if (fileHandle == INVALID_HANDLE)
|
|
{
|
|
m_lastError = BIT_BUFFER_FILE_SAVE_FAILED;
|
|
PrintFormat("Error %d: Failed to open file '%s' for writing. MQL5 Error: %d", m_lastError, filename, ::GetLastError());
|
|
return false;
|
|
}
|
|
|
|
long ulongsToWrite = (m_totalWrittenBits + 63) / 64; // Round up to nearest ulong.
|
|
if (ulongsToWrite == 0)
|
|
{
|
|
// If no bits were written, just close the file (it will be empty or 0-byte size).
|
|
FileClose(fileHandle);
|
|
return true;
|
|
}
|
|
|
|
// Write total bits first for correct loading.
|
|
if (FileWriteLong(fileHandle, m_totalWrittenBits) != sizeof(long))
|
|
{
|
|
m_lastError = BIT_BUFFER_FILE_SAVE_FAILED;
|
|
PrintFormat("Error %d: Failed to write totalWrittenBits to file '%s'.", m_lastError, filename);
|
|
FileClose(fileHandle);
|
|
return false;
|
|
}
|
|
|
|
// Write the main buffer content.
|
|
if (FileWriteArray(fileHandle, m_mainBuffer, 0, (int)ulongsToWrite) != ulongsToWrite)
|
|
{
|
|
m_lastError = BIT_BUFFER_FILE_SAVE_FAILED;
|
|
PrintFormat("Error %d: Failed to write buffer data to file '%s'. MQL5 Error: %d", m_lastError, filename, ::GetLastError());
|
|
FileClose(fileHandle);
|
|
return false;
|
|
}
|
|
|
|
FileClose(fileHandle);
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Loads buffer content from a file, expecting the total number |
|
|
//| of bits as a prefix. |
|
|
//+------------------------------------------------------------------+
|
|
bool CBitBuffer::Load(string filename, bool common = false)
|
|
{
|
|
Clear();
|
|
|
|
int fileHandle = FileOpen(filename, FILE_READ | FILE_BIN | FILE_SHARE_READ | (common ? FILE_COMMON : 0));
|
|
if (fileHandle == INVALID_HANDLE)
|
|
{
|
|
m_lastError = BIT_BUFFER_FILE_LOAD_FAILED;
|
|
PrintFormat("Error %d: Failed to open file '%s' for reading. MQL5 Error: %d", m_lastError, filename, ::GetLastError());
|
|
return false;
|
|
}
|
|
|
|
// Read the total number of bits first.
|
|
long loadedTotalBits = FileReadLong(fileHandle);
|
|
if (loadedTotalBits == 0 && FileIsEnding(fileHandle)) // Empty file case.
|
|
{
|
|
FileClose(fileHandle);
|
|
return true;
|
|
}
|
|
|
|
if (loadedTotalBits < 0 || FileIsEnding(fileHandle)) // Error or unexpected end of file.
|
|
{
|
|
m_lastError = BIT_BUFFER_FILE_LOAD_FAILED;
|
|
PrintFormat("Error %d: Failed to read totalWrittenBits from file '%s' or file is empty/corrupt.", m_lastError, filename);
|
|
FileClose(fileHandle);
|
|
return false;
|
|
}
|
|
|
|
long requiredUlongs = (loadedTotalBits + 63) / 64;
|
|
|
|
// Ensure buffer capacity for the incoming data.
|
|
if (!EnsureBufferCapacity(requiredUlongs))
|
|
{
|
|
m_lastError = BIT_BUFFER_MEMORY_ALLOCATION_FAILED;
|
|
PrintFormat("Error %d: Failed to allocate memory for loading file '%s'.", m_lastError, filename);
|
|
FileClose(fileHandle);
|
|
return false;
|
|
}
|
|
|
|
// Read the main buffer content.
|
|
if (FileReadArray(fileHandle, m_mainBuffer, 0, (int)requiredUlongs) != requiredUlongs)
|
|
{
|
|
m_lastError = BIT_BUFFER_FILE_LOAD_FAILED;
|
|
PrintFormat("Error %d: Failed to read buffer data from file '%s'. MQL5 Error: %d", m_lastError, filename, ::GetLastError());
|
|
FileClose(fileHandle);
|
|
return false;
|
|
}
|
|
|
|
FileClose(fileHandle);
|
|
m_totalWrittenBits = loadedTotalBits; // Set total written bits after successful load.
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Returns a string description for the given error code. |
|
|
//+------------------------------------------------------------------+
|
|
string CBitBuffer::GetLastErrorString() const
|
|
{
|
|
switch (m_lastError)
|
|
{
|
|
case BIT_BUFFER_NO_ERROR: return "No error.";
|
|
case BIT_BUFFER_INVALID_BIT_COUNT: return "Invalid bit count specified (must be 1-64).";
|
|
case BIT_BUFFER_MEMORY_ALLOCATION_FAILED: return "Memory allocation failed for the internal buffer.";
|
|
case BIT_BUFFER_READ_OUT_OF_BOUNDS: return "Attempt to read beyond available data.";
|
|
case BIT_BUFFER_WRITE_FLUSH_FAILED: return "Write operation failed during internal buffer flush.";
|
|
case BIT_BUFFER_INVALID_READ_POSITION: return "Attempt to set read position out of valid bounds (negative or beyond total written bits).";
|
|
case BIT_BUFFER_DATA_TOO_LARGE: return "Data length exceeds maximum allowed capacity (e.g., string/struct too large for ushort length prefix).";
|
|
case BIT_BUFFER_READ_DATA_INCOMPLETE: return "Incomplete data found during read, or declared length exceeds available data.";
|
|
case BIT_BUFFER_RAW_OPERATION_FAILED: return "Failed to copy raw buffer data to/from the internal buffer.";
|
|
case BIT_BUFFER_FILE_SAVE_FAILED: return "FileSave failed for the main buffer during save operation.";
|
|
case BIT_BUFFER_FILE_LOAD_FAILED: return "FileLoad failed for the main buffer during load operation.";
|
|
default: return "Unknown error code.";
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Prints internal main buffer in hexadecimal format (debugging). |
|
|
//+------------------------------------------------------------------+
|
|
void CBitBuffer::PrintHex(int start = 0, int count = WHOLE_ARRAY) const
|
|
{
|
|
PrintFormat("Write Cache (Hex): 0x%.16llX (Bits: %d)", m_writeBufferInternal, m_writeBufferBitCounter);
|
|
|
|
uchar b[];
|
|
uint pos = 0;
|
|
// Serialize the main buffer (m_mainBuffer) to bytes for printing.
|
|
long ulongsToPrint = (m_totalWrittenBits + 63) / 64; // Round up to nearest ulong.
|
|
long bytes = ulongsToPrint * 8;
|
|
ArrayResize(b, (int)bytes);
|
|
for (long i = 0; i < ulongsToPrint; i++)
|
|
{
|
|
ulong x = m_mainBuffer[i];
|
|
// Extract bytes from ulong (LSB first for typical byte representation)
|
|
b[pos++] = (uchar)((x >> 0) & 0xff);
|
|
b[pos++] = (uchar)((x >> 8) & 0xff);
|
|
b[pos++] = (uchar)((x >> 16) & 0xff);
|
|
b[pos++] = (uchar)((x >> 24) & 0xff);
|
|
b[pos++] = (uchar)((x >> 32) & 0xff);
|
|
b[pos++] = (uchar)((x >> 40) & 0xff);
|
|
b[pos++] = (uchar)((x >> 48) & 0xff);
|
|
b[pos++] = (uchar)((x >> 56) & 0xff);
|
|
}
|
|
PrintFormat("Raw Buffer (Hex): (Bits: %d)", m_totalWrittenBits);
|
|
// Helper function to print byte array in hex.
|
|
ArrayPrintHex(b, start, count, 32); // Assuming ArrayPrintHex is defined globally or in a common include.
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Global Helper Function: Converts an array to a hex string for printing.|
|
|
//| This function is assumed to be defined externally or in a common |
|
|
//| include file, as it's not part of the CBitBuffer class. |
|
|
//+------------------------------------------------------------------+
|
|
template <typename T>
|
|
void ArrayPrintHex(const T &array[], int start = 0, int count = WHOLE_ARRAY, int wrap = 16)
|
|
{
|
|
string result = "";
|
|
int size = ArraySize(array);
|
|
if(start < 0)
|
|
start = 0;
|
|
if(count == WHOLE_ARRAY)
|
|
count = size;
|
|
size = MathMin((start + count), size);
|
|
for(int i = 0; i < size-start; i++)
|
|
{
|
|
// insert a newline after each 'wrap' items
|
|
if (i > 0 && (i % wrap) == 0)
|
|
result += "\r\n";
|
|
|
|
if(sizeof(T) < 8)
|
|
result += StringFormat("%." + (string)(sizeof(T) * 2) + "X ", array[start+i]);
|
|
else
|
|
result += StringFormat("%.16llX ", array[start+i]);
|
|
}
|
|
//return(result);
|
|
Print(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|