38 use LegacyRuntimeEnumDescriberTrait;
40 private int $offset = 0;
42 public function __construct(
47 public function readInt(
int $bits) :
int{
48 $bitsLeft = $this->maxBits - $this->offset;
49 if($bits > $bitsLeft){
50 throw new \InvalidArgumentException(
"No bits left in buffer (need $bits, have $bitsLeft");
53 $value = ($this->value >> $this->offset) & ~(~0 << $bits);
54 $this->offset += $bits;
58 public function int(
int $bits,
int &$value) :
void{
59 $value = $this->readInt($bits);
65 public function boundedInt(
int $bits,
int $min,
int $max,
int &$value) : void{
66 $offset = $this->offset;
68 $actualBits = $this->offset - $offset;
69 if($this->offset !== $offset + $bits){
70 throw new \InvalidArgumentException(
"Bits should be $actualBits for the given bounds, but received $bits. Use boundedIntAuto() for automatic bits calculation.");
74 private function readBoundedIntAuto(
int $min,
int $max) : int{
75 $bits = ((int) log($max - $min, 2)) + 1;
76 $result = $this->readInt($bits) + $min;
77 if($result < $min || $result > $max){
78 throw new InvalidSerializedRuntimeDataException(
"Value is outside the range $min - $max");
84 $value = $this->readBoundedIntAuto($min, $max);
87 protected function readBool() : bool{
88 return $this->readInt(1) === 1;
91 public function bool(
bool &$value) : void{
92 $value = $this->readBool();
95 public function horizontalFacing(
int &$facing) : void{
96 $facing = match($this->readInt(2)){
110 foreach(Facing::ALL as $facing){
111 if($this->readBool()){
112 $result[$facing] = $facing;
124 foreach(Facing::HORIZONTAL as $facing){
125 if($this->readBool()){
126 $result[$facing] = $facing;
133 public function facing(
int &$facing) : void{
134 $facing = match($this->readInt(3)){
141 default =>
throw new InvalidSerializedRuntimeDataException(
"Invalid facing value")
145 public function facingExcept(
int &$facing,
int $except) : void{
147 $this->facing($result);
148 if($result === $except){
149 throw new InvalidSerializedRuntimeDataException(
"Illegal facing value");
155 public function axis(
int &$axis) : void{
156 $axis = match($this->readInt(2)){
160 default =>
throw new InvalidSerializedRuntimeDataException(
"Invalid axis value")
164 public function horizontalAxis(
int &$axis) : void{
165 $axis = match($this->readInt(1)){
168 default =>
throw new AssumptionFailedError(
"Unreachable")
179 $packed = $this->readBoundedIntAuto(0, (3 ** 4) - 1);
180 foreach(Facing::HORIZONTAL as $facing){
181 $type = intdiv($packed, (3 ** $offset)) % 3;
183 $result[$facing] = match($type){
184 1 => WallConnectionType::SHORT,
185 2 => WallConnectionType::TALL,
192 $connections = $result;
202 $this->enumSet($slots, BrewingStandSlot::cases());
205 public function railShape(
int &$railShape) : void{
206 $result = $this->readInt(4);
207 if(!isset(RailConnectionInfo::CONNECTIONS[$result]) && !isset(RailConnectionInfo::CURVE_CONNECTIONS[$result])){
211 $railShape = $result;
214 public function straightOnlyRailShape(
int &$railShape) : void{
215 $result = $this->readInt(3);
216 if(!isset(RailConnectionInfo::CONNECTIONS[$result])){
217 throw new InvalidSerializedRuntimeDataException(
"No rail shape matches meta $result");
220 $railShape = $result;
223 public function enum(\UnitEnum &$case) :
void{
224 $metadata = RuntimeEnumMetadata::from($case);
225 $raw = $this->readInt($metadata->bits);
226 $result = $metadata->intToEnum($raw);
227 if($result ===
null){
234 public function enumSet(array &$set, array $allCases) : void{
236 foreach($allCases as $case){
237 if($this->readBool()){
238 $result[spl_object_id($case)] = $case;
244 public function getOffset() : int{ return $this->offset; }