Skip to content

Commit 6f6aa8e

Browse files
Refactor vector structure implementation
1 parent 3254553 commit 6f6aa8e

File tree

1 file changed

+42
-29
lines changed

1 file changed

+42
-29
lines changed

src/protocol/v6/structures/Vector.php

Lines changed: 42 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -27,97 +27,100 @@ public function __toString(): string
2727
return json_encode([(string)$this->type_marker, (string)$this->data]);
2828
}
2929

30-
private static array $formats = ['s', 'l', 'q'];
30+
private static array $endianesFormats = ['s', 'l', 'q'];
3131

3232
/**
3333
* Encode array as vector structure
34+
* This is a helper method to create Vector structure from array of numbers
3435
* @param int[]|float[] $data
36+
* @param TypeMarker|null $type Optional type to force specific data type .. null = auto decide
3537
* @return self
3638
* @throws \InvalidArgumentException
3739
*/
38-
public static function encode(array $data): self
40+
public static function encode(array $data, ?TypeMarker $type = null): self
3941
{
40-
if (count($data) === 0) {
41-
throw new \InvalidArgumentException('Vector cannot be empty');
42-
}
43-
if (count($data) > 4096) {
44-
throw new \InvalidArgumentException('Vector cannot have more than 4096 elements');
42+
$anyFloat = false;
43+
foreach ($data as $entry) {
44+
if (!is_int($entry) && !is_float($entry)) {
45+
throw new \InvalidArgumentException('Vector can only contain numeric values');
46+
}
47+
if (!$anyFloat && is_float($entry)) {
48+
$anyFloat = true;
49+
}
4550
}
4651

47-
$anyFloat = in_array(true, array_map('is_float', $data));
48-
$minValue = min($data);
49-
$maxValue = max($data);
50-
$marker = 0;
52+
$minValue = count($data) ? min($data) : 0;
53+
$maxValue = count($data) ? max($data) : 0;
5154
$packFormat = '';
5255

5356
if ($anyFloat) {
5457
if ($minValue >= 1.4e-45 && $maxValue <= 3.4028235e+38) { // Single precision float (FLOAT_32)
55-
$marker = 0xC6;
58+
if ($type === null) $type = TypeMarker::FLOAT_32;
5659
$packFormat = 'G';
5760
} else { // Double precision float (FLOAT_64)
58-
$marker = 0xC1;
61+
if ($type === null) $type = TypeMarker::FLOAT_64;
5962
$packFormat = 'E';
6063
}
6164
} else {
6265
if ($minValue >= -128 && $maxValue <= 127) { // INT_8
63-
$marker = 0xC8;
66+
if ($type === null) $type = TypeMarker::INT_8;
6467
$packFormat = 'c';
6568
} elseif ($minValue >= -32768 && $maxValue <= 32767) { // INT_16
66-
$marker = 0xC9;
69+
if ($type === null) $type = TypeMarker::INT_16;
6770
$packFormat = 's';
6871
} elseif ($minValue >= -2147483648 && $maxValue <= 2147483647) { // INT_32
69-
$marker = 0xCA;
72+
if ($type === null) $type = TypeMarker::INT_32;
7073
$packFormat = 'l';
7174
} else { // INT_64
72-
$marker = 0xCB;
75+
if ($type === null) $type = TypeMarker::INT_64;
7376
$packFormat = 'q';
7477
}
7578
}
7679

77-
if ($marker === 0) {
80+
if ($type === null) {
7881
throw new \InvalidArgumentException('Unsupported data type for vector');
7982
}
8083

8184
// Pack the data
8285
$packed = [];
8386
$littleEndian = unpack('S', "\x01\x00")[1] === 1;
8487
foreach ($data as $entry) {
85-
$value = pack($packFormat, $entry);
86-
$packed[] = in_array($packFormat, self::$formats) && $littleEndian ? strrev($value) : $value;
88+
$value = pack($packFormat, $anyFloat ? (float)$entry : (int)$entry);
89+
$packed[] = in_array($packFormat, self::$endianesFormats) && $littleEndian ? strrev($value) : $value;
8790
}
8891

89-
return new self(new Bytes([chr($marker)]), new Bytes($packed));
92+
return new self(new Bytes([chr($type->value)]), new Bytes($packed));
9093
}
9194

9295
/**
93-
* Decode vector structure .. returns binary $this->data as array
96+
* Decode vector structure .. returns binary $this->data as array of numbers
9497
* @return int[]|float[]
9598
* @throws \InvalidArgumentException
9699
*/
97100
public function decode(): array
98101
{
99102
switch (ord($this->type_marker[0])) {
100-
case 0xC8: // INT_8
103+
case TypeMarker::INT_8->value: // INT_8
101104
$size = 1;
102105
$unpackFormat = 'c';
103106
break;
104-
case 0xC9: // INT_16
107+
case TypeMarker::INT_16->value: // INT_16
105108
$size = 2;
106109
$unpackFormat = 's';
107110
break;
108-
case 0xCA: // INT_32
111+
case TypeMarker::INT_32->value: // INT_32
109112
$size = 4;
110113
$unpackFormat = 'l';
111114
break;
112-
case 0xCB: // INT_64
115+
case TypeMarker::INT_64->value: // INT_64
113116
$size = 8;
114117
$unpackFormat = 'q';
115118
break;
116-
case 0xC6: // FLOAT_32
119+
case TypeMarker::FLOAT_32->value: // FLOAT_32
117120
$size = 4;
118121
$unpackFormat = 'G';
119122
break;
120-
case 0xC1: // FLOAT_64
123+
case TypeMarker::FLOAT_64->value: // FLOAT_64
121124
$size = 8;
122125
$unpackFormat = 'E';
123126
break;
@@ -128,9 +131,19 @@ public function decode(): array
128131
$output = [];
129132
$littleEndian = unpack('S', "\x01\x00")[1] === 1;
130133
foreach(mb_str_split((string)$this->data, $size, '8bit') as $value) {
131-
$output[] = unpack($unpackFormat, in_array($unpackFormat, self::$formats) && $littleEndian ? strrev($value) : $value)[1];
134+
$output[] = unpack($unpackFormat, in_array($unpackFormat, self::$endianesFormats) && $littleEndian ? strrev($value) : $value)[1];
132135
}
133136

134137
return $output;
135138
}
136139
}
140+
141+
enum TypeMarker: int
142+
{
143+
case INT_8 = 0xC8;
144+
case INT_16 = 0xC9;
145+
case INT_32 = 0xCA;
146+
case INT_64 = 0xCB;
147+
case FLOAT_32 = 0xC6;
148+
case FLOAT_64 = 0xC1;
149+
}

0 commit comments

Comments
 (0)