offset + $bits > $this->maxBits){ throw new \InvalidArgumentException("Bit buffer cannot be larger than $this->maxBits bits (already have $this->offset bits)"); } if(($value & (~0 << $bits)) !== 0){ throw new \InvalidArgumentException("Value $value does not fit into $bits bits"); } $this->value |= ($value << $this->offset); $this->offset += $bits; } public function int(int $bits, int &$value) : void{ $this->writeInt($bits, $value); } private function writeBoundedIntAuto(int $min, int $max, int $value) : void{ if($value < $min || $value > $max){ throw new \InvalidArgumentException("Value $value is outside the range $min - $max"); } $bits = ((int) log($max - $min, 2)) + 1; $this->writeInt($bits, $value - $min); } public function boundedIntAuto(int $min, int $max, int &$value) : void{ $this->writeBoundedIntAuto($min, $max, $value); } protected function writeBool(bool $value) : void{ $this->writeInt(1, $value ? 1 : 0); } public function bool(bool &$value) : void{ $this->writeBool($value); } /** * @param Facing[] $faces */ public function facingFlags(array &$faces) : void{ $uniqueFaces = []; foreach($faces as $face){ $uniqueFaces[$face->value] = true; } foreach(Facing::ALL as $facing){ $this->writeBool(isset($uniqueFaces[$facing->value])); } } /** * @param HorizontalFacingOption[] $faces */ public function horizontalFacingFlags(array &$faces) : void{ $uniqueFaces = []; foreach($faces as $face){ $uniqueFaces[$face->value] = true; } foreach(HorizontalFacingOption::cases() as $facing){ $this->writeBool(isset($uniqueFaces[$facing->value])); } } public function facingExcept(Facing &$facing, Facing $except) : void{ $this->enum($facing); } public function horizontalAxis(Axis &$axis) : void{ $this->writeInt(1, match($axis){ Axis::X => 0, Axis::Z => 1, default => throw new \InvalidArgumentException("Invalid horizontal axis $axis->name") }); } /** * @param WallConnectionType[] $connections * @phpstan-param array, WallConnectionType> $connections */ public function wallConnections(array &$connections) : void{ $packed = 0; $offset = 0; foreach(Facing::HORIZONTAL as $facing){ $packed += match($connections[$facing->value] ?? null){ null => 0, WallConnectionType::SHORT => 1, WallConnectionType::TALL => 2, } * (3 ** $offset); $offset++; } $this->writeBoundedIntAuto(0, (3 ** 4) - 1, $packed); } public function railShape(int &$railShape) : void{ $this->int(4, $railShape); } public function straightOnlyRailShape(int &$railShape) : void{ $this->int(3, $railShape); } public function enum(\UnitEnum &$case) : void{ $metadata = RuntimeEnumMetadata::from($case); $this->writeInt($metadata->bits, $metadata->enumToInt($case)); } public function enumSet(array &$set, array $allCases) : void{ foreach($allCases as $case){ $this->writeBool(isset($set[spl_object_id($case)])); } } public function getValue() : int{ return $this->value; } public function getOffset() : int{ return $this->offset; } }