37 private int $offset = 0;
39 public function __construct(
44 public function readInt(
int $bits) :
int{
45 $bitsLeft = $this->maxBits - $this->offset;
46 if($bits > $bitsLeft){
47 throw new \InvalidArgumentException(
"No bits left in buffer (need $bits, have $bitsLeft");
50 $value = ($this->value >> $this->offset) & ~(~0 << $bits);
51 $this->offset += $bits;
55 public function int(
int $bits,
int &$value) :
void{
56 $value = $this->readInt($bits);
59 private function readBoundedIntAuto(
int $min,
int $max) :
int{
60 $bits = ((int) log($max - $min, 2)) + 1;
61 $result = $this->readInt($bits) + $min;
62 if($result < $min || $result > $max){
69 $value = $this->readBoundedIntAuto($min, $max);
72 protected function readBool() : bool{
73 return $this->readInt(1) === 1;
76 public function bool(
bool &$value) : void{
77 $value = $this->readBool();
80 public function horizontalFacing(
int &$facing) : void{
81 $facing = match($this->readInt(2)){
95 foreach(Facing::ALL as $facing){
96 if($this->readBool()){
97 $result[$facing] = $facing;
109 foreach(Facing::HORIZONTAL as $facing){
110 if($this->readBool()){
111 $result[$facing] = $facing;
118 public function facing(
int &$facing) : void{
119 $facing = match($this->readInt(3)){
126 default =>
throw new InvalidSerializedRuntimeDataException(
"Invalid facing value")
130 public function facingExcept(
int &$facing,
int $except) : void{
132 $this->facing($result);
133 if($result === $except){
134 throw new InvalidSerializedRuntimeDataException(
"Illegal facing value");
140 public function axis(
int &$axis) : void{
141 $axis = match($this->readInt(2)){
145 default =>
throw new InvalidSerializedRuntimeDataException(
"Invalid axis value")
149 public function horizontalAxis(
int &$axis) : void{
150 $axis = match($this->readInt(1)){
153 default =>
throw new AssumptionFailedError(
"Unreachable")
164 $packed = $this->readBoundedIntAuto(0, (3 ** 4) - 1);
165 foreach(Facing::HORIZONTAL as $facing){
166 $type = intdiv($packed, (3 ** $offset)) % 3;
168 $result[$facing] = match($type){
169 1 => WallConnectionType::SHORT,
170 2 => WallConnectionType::TALL,
177 $connections = $result;
180 public function railShape(
int &$railShape) : void{
181 $result = $this->readInt(4);
182 if(!isset(RailConnectionInfo::CONNECTIONS[$result]) && !isset(RailConnectionInfo::CURVE_CONNECTIONS[$result])){
183 throw new InvalidSerializedRuntimeDataException(
"Invalid rail shape $result");
186 $railShape = $result;
189 public function straightOnlyRailShape(
int &$railShape) : void{
190 $result = $this->readInt(3);
191 if(!isset(RailConnectionInfo::CONNECTIONS[$result])){
192 throw new InvalidSerializedRuntimeDataException(
"No rail shape matches meta $result");
195 $railShape = $result;
198 public function enum(\UnitEnum &$case) :
void{
199 $metadata = RuntimeEnumMetadata::from($case);
200 $raw = $this->readInt($metadata->bits);
201 $result = $metadata->intToEnum($raw);
202 if($result ===
null){
209 public function enumSet(array &$set, array $allCases) : void{
211 foreach($allCases as $case){
212 if($this->readBool()){
213 $result[spl_object_id($case)] = $case;
219 public function getOffset() : int{ return $this->offset; }