38 private int $offset = 0;
40 public function __construct(
45 public function readInt(
int $bits) :
int{
46 $bitsLeft = $this->maxBits - $this->offset;
47 if($bits > $bitsLeft){
48 throw new \InvalidArgumentException(
"No bits left in buffer (need $bits, have $bitsLeft");
51 $value = ($this->value >> $this->offset) & ~(~0 << $bits);
52 $this->offset += $bits;
56 public function int(
int $bits,
int &$value) :
void{
57 $value = $this->readInt($bits);
63 public function boundedInt(
int $bits,
int $min,
int $max,
int &$value) : void{
64 $offset = $this->offset;
66 $actualBits = $this->offset - $offset;
67 if($this->offset !== $offset + $bits){
68 throw new \InvalidArgumentException(
"Bits should be $actualBits for the given bounds, but received $bits. Use boundedIntAuto() for automatic bits calculation.");
72 private function readBoundedIntAuto(
int $min,
int $max) : int{
73 $bits = ((int) log($max - $min, 2)) + 1;
74 $result = $this->readInt($bits) + $min;
75 if($result < $min || $result > $max){
76 throw new InvalidSerializedRuntimeDataException(
"Value is outside the range $min - $max");
82 $value = $this->readBoundedIntAuto($min, $max);
85 protected function readBool() : bool{
86 return $this->readInt(1) === 1;
89 public function bool(
bool &$value) : void{
90 $value = $this->readBool();
93 public function horizontalFacing(
int &$facing) : void{
94 $facing = match($this->readInt(2)){
108 foreach(Facing::ALL as $facing){
109 if($this->readBool()){
110 $result[$facing] = $facing;
122 foreach(Facing::HORIZONTAL as $facing){
123 if($this->readBool()){
124 $result[$facing] = $facing;
131 public function facing(
int &$facing) : void{
132 $facing = match($this->readInt(3)){
139 default =>
throw new InvalidSerializedRuntimeDataException(
"Invalid facing value")
143 public function facingExcept(
int &$facing,
int $except) : void{
145 $this->facing($result);
146 if($result === $except){
147 throw new InvalidSerializedRuntimeDataException(
"Illegal facing value");
153 public function axis(
int &$axis) : void{
154 $axis = match($this->readInt(2)){
158 default =>
throw new InvalidSerializedRuntimeDataException(
"Invalid axis value")
162 public function horizontalAxis(
int &$axis) : void{
163 $axis = match($this->readInt(1)){
166 default =>
throw new AssumptionFailedError(
"Unreachable")
177 $packed = $this->readBoundedIntAuto(0, (3 ** 4) - 1);
178 foreach(Facing::HORIZONTAL as $facing){
179 $type = intdiv($packed, (3 ** $offset)) % 3;
181 $result[$facing] = match($type){
182 1 => WallConnectionType::SHORT,
183 2 => WallConnectionType::TALL,
190 $connections = $result;
200 $this->enumSet($slots, BrewingStandSlot::cases());
203 public function railShape(
int &$railShape) : void{
204 $result = $this->readInt(4);
205 if(!isset(RailConnectionInfo::CONNECTIONS[$result]) && !isset(RailConnectionInfo::CURVE_CONNECTIONS[$result])){
209 $railShape = $result;
212 public function straightOnlyRailShape(
int &$railShape) : void{
213 $result = $this->readInt(3);
214 if(!isset(RailConnectionInfo::CONNECTIONS[$result])){
215 throw new InvalidSerializedRuntimeDataException(
"No rail shape matches meta $result");
218 $railShape = $result;
221 public function enum(\UnitEnum &$case) :
void{
222 $metadata = RuntimeEnumMetadata::from($case);
223 $raw = $this->readInt($metadata->bits);
224 $result = $metadata->intToEnum($raw);
225 if($result ===
null){
232 public function enumSet(array &$set, array $allCases) : void{
234 foreach($allCases as $case){
235 if($this->readBool()){
236 $result[spl_object_id($case)] = $case;
242 public function getOffset() : int{ return $this->offset; }