diff --git a/src/Common.h b/src/Common.h index 21c5dfe..9563a0a 100644 --- a/src/Common.h +++ b/src/Common.h @@ -1,5 +1,7 @@ #pragma once +#include "util/Math.h" + #include "logging/Logger.h" constexpr unsigned long long operator"" KB(unsigned long long l) diff --git a/src/NetworkMessage.h b/src/NetworkMessage.h index 610c1fd..5bb8a33 100644 --- a/src/NetworkMessage.h +++ b/src/NetworkMessage.h @@ -1,6 +1,7 @@ #pragma once #include "util/ByteUtil.h" +#include "Types.h" #include #include @@ -71,6 +72,11 @@ namespace Feather WriteString(string.c_str(), string.length()); } + inline void WritePosition(BlockPos pos) + { + Write(pos.Encode()); + } + inline void Finalize() { PrependVarIntSize(); diff --git a/src/Types.h b/src/Types.h index 7977e97..0e7f809 100644 --- a/src/Types.h +++ b/src/Types.h @@ -1,5 +1,6 @@ #pragma once +#include "Common.h" #include "util/ByteUtil.h" #include @@ -19,4 +20,36 @@ namespace Feather .data = { ReverseBytes(uuid.data[0]), ReverseBytes(uuid.data[1]) } }; } + + struct BlockPos + { + int32_t x, y, z; + + BlockPos(int32_t x, int32_t y, int32_t z) : x(x), y(y), z(z) {} + + inline int64_t Encode() + { + int64_t l = 0; + l |= ((long)x & PACKED_X_MASK) << X_OFFSET; + l |= ((long)y & PACKED_Y_MASK) << Y_OFFSET; + l |= ((long)z & PACKED_Z_MASK) << Z_OFFSET; + + return l; + } + private: + + // X: 26 bits, Z: 26 bits, Y: 12 bits + static const int PACKED_X_LENGTH = 1 + Math::CeilLog2(Math::RoundToPowerOf2(30000000)); // should be 26 + static const int PACKED_Z_LENGTH = PACKED_X_LENGTH; // same as X + static const int PACKED_Y_LENGTH = 64 - PACKED_X_LENGTH - PACKED_Z_LENGTH; // should be 12 + + static const int64_t PACKED_X_MASK = (1 << PACKED_X_LENGTH) - 1; + static const int64_t PACKED_Y_MASK = (1 << PACKED_Y_LENGTH) - 1; + static const int64_t PACKED_Z_MASK = (1 << PACKED_Z_LENGTH) - 1; + + // order is X Z Y + static const int Y_OFFSET = 0; + static const int Z_OFFSET = PACKED_Y_LENGTH; + static const int X_OFFSET = PACKED_Y_LENGTH + PACKED_Z_LENGTH; + }; } \ No newline at end of file diff --git a/src/protocol/generate_protocol.py b/src/protocol/generate_protocol.py index 542e4c7..279c84e 100644 --- a/src/protocol/generate_protocol.py +++ b/src/protocol/generate_protocol.py @@ -45,9 +45,10 @@ def get_type_size(type): count = extract_array_count(type) type = extract_array_type(type) + # TODO: encode type sizes in Hjson data if type == 'varint': return varint_max_size - elif type == 'int64' or type == 'uint64' or type == 'double': + elif type == 'int64' or type == 'uint64' or type == 'double' or type == 'position': return 8 elif type == 'int32' or type == 'uint32' or type == 'float': return 4 @@ -81,6 +82,8 @@ def get_rw_func(primitiveType, aliasedType, read): return '{}VarInt<{}>'.format(prefix, primitiveType) elif aliasedType == 'string': return '{}String'.format(prefix) + elif aliasedType == 'position': + return '{}Position'.format(prefix) else: return '{}<{}>'.format(prefix, primitiveType) diff --git a/src/protocol/protocol.hjson b/src/protocol/protocol.hjson index 1f929bb..31c68b4 100644 --- a/src/protocol/protocol.hjson +++ b/src/protocol/protocol.hjson @@ -11,6 +11,7 @@ varint : int32_t string : std::string uuid : MinecraftUUID + position: BlockPos uint64 : uint64_t int64 : int64_t diff --git a/src/util/Math.h b/src/util/Math.h new file mode 100644 index 0000000..d9cea79 --- /dev/null +++ b/src/util/Math.h @@ -0,0 +1,47 @@ +#pragma once + +#include + +namespace Feather::Math +{ + // de Bruijn Sequence: B(2, 5) = 0000 0111 0111 1100 1011 0101 0011 0001. Contains all 32 distinct 5-bit strings. + constexpr uint32_t DE_BRUIJN_SEQ = 0x077CB531U; + + constexpr int MULTIPLY_DE_BRUIJN_BIT_POSITION[32] = {0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9}; + + // Is x a power of two? + inline constexpr bool IsPowerOf2(int x) + { + return x != 0 && (x & (x - 1)) == 0; + } + + // Smallest number 2^z that is greater than or equal to x + // Precondition: x > 0 + inline constexpr int RoundToPowerOf2(int x) + { + x--; + + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return x + 1; + } + + inline constexpr int CeilLog2(int x) + { + // Ensure x is power of 2 + x = IsPowerOf2(x) ? x : RoundToPowerOf2(x); + + // The de Bruijn sequence contains all 32 distinct 5-bit strings + // 27 is 32 - 5 and 0x1F (31) is the lower 5 bits 0001 1111 + return MULTIPLY_DE_BRUIJN_BIT_POSITION[(int)((int64_t)x * DE_BRUIJN_SEQ >> 27) & 0x1F]; + } + + // log base 2 + inline constexpr int Log2(int x) + { + return CeilLog2(x) - (IsPowerOf2(x) ? 0 : 1); + } +} \ No newline at end of file