Skip to content

Commit

Permalink
allow reading specific parts of an NBT structure as raw data to preve…
Browse files Browse the repository at this point in the history
…nt excessive memory usage from large numbers of small child tags
  • Loading branch information
KurtThiemann committed May 10, 2022
1 parent 3cb087c commit 859f4a6
Show file tree
Hide file tree
Showing 17 changed files with 454 additions and 15 deletions.
24 changes: 24 additions & 0 deletions src/Tag/ArrayValueTag.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use ArrayAccess;
use Aternos\Nbt\IO\Reader\Reader;
use Aternos\Nbt\IO\Writer\Writer;
use BadMethodCallException;
use Countable;
use Exception;
use Iterator;
Expand All @@ -28,13 +29,26 @@ public function writeContent(Writer $writer): static
return $this;
}

/**
* @inheritDoc
*/
protected function readContent(Reader $reader): static
{
$length = $reader->getDeserializer()->readLengthPrefix()->getValue();
$this->valueArray = $this->readValues($reader, $length);
return $this;
}

/**
* @inheritDoc
*/
protected static function readContentRaw(Reader $reader, TagOptions $options): string
{
$length = $reader->getDeserializer()->readLengthPrefix();
$valueData = static::readValuesRaw($reader, $length->getValue());
return $length->getRawData() . $valueData;
}

/**
* @param Writer $writer
* @return string
Expand All @@ -48,6 +62,16 @@ abstract protected function writeValues(Writer $writer): string;
*/
abstract protected function readValues(Reader $reader, int $length): array;

/**
* @param Reader $reader
* @param int $length
* @return string
*/
protected static function readValuesRaw(Reader $reader, int $length): string
{
throw new BadMethodCallException("Not implemented");
}

/**
* @param $value
* @return bool
Expand Down
12 changes: 12 additions & 0 deletions src/Tag/ByteArrayTag.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ protected function readValues(Reader $reader, int $length): array
return $values;
}

/**
* @inheritDoc
*/
protected static function readValuesRaw(Reader $reader, int $length): string
{
$result = "";
for ($i = 0; $i < $length; $i++) {
$result .= $reader->getDeserializer()->readByte()->getRawData();
}
return $result;
}

/**
* @inheritDoc
*/
Expand Down
8 changes: 8 additions & 0 deletions src/Tag/ByteTag.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,12 @@ protected function readContent(Reader $reader): static
$this->value = $reader->getDeserializer()->readByte()->getValue();
return $this;
}

/**
* @inheritDoc
*/
protected static function readContentRaw(Reader $reader, TagOptions $options): string
{
return $reader->getDeserializer()->readByte()->getRawData();
}
}
56 changes: 53 additions & 3 deletions src/Tag/CompoundTag.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,39 @@ class CompoundTag extends Tag implements Iterator, ArrayAccess, Countable
* @var Tag[]
*/
protected array $valueArray = [];
protected ?string $rawContent = null;
protected ?int $rawContentFormat = null;

/**
* @return bool
*/
public function isRaw(): bool
{
return $this->rawContent !== null;
}

/**
* @inheritDoc
* @throws Exception
*/
public function writeContent(Writer $writer): static
{
if($this->isRaw()) {
if($this->rawContentFormat !== $writer->getFormat()) {
throw new Exception("Cannot change format of raw compound tag");
}
$writer->write($this->rawContent);
return $this;
}

$writtenNames = [];
foreach ($this->valueArray as $value) {
if (in_array($value->getName(), $writtenNames)) {
throw new Exception("Duplicate key '" . $value->getName() . "' in compound tag");
}
$value->writeData($writer, true);
}
(new EndTag())->writeData($writer);
(new EndTag($this->options))->writeData($writer);
return $this;
}

Expand All @@ -41,12 +59,31 @@ public function writeContent(Writer $writer): static
*/
protected function readContent(Reader $reader): static
{
while (!(($tag = Tag::load($reader)) instanceof EndTag)) {
$this->valueArray[] = $tag;
if($this->options->shouldBeReadRaw($this)) {
$this->rawContentFormat = $reader->getFormat();
$this->rawContent = static::readContentRaw($reader, $this->options);
return $this;
}
while (!(($tag = Tag::load($reader, $this->options, $this)) instanceof EndTag)) {
$this->valueArray[] = $tag->setParentTag($this);
}
return $this;
}

/**
* @inheritDoc
* @throws Exception
*/
protected static function readContentRaw(Reader $reader, TagOptions $options): string
{
$result = "";
do {
$tag = Tag::loadRaw($reader, $options);
$result .= $tag->getData();
} while ($tag->getTagType() !== TagType::TAG_End);
return $result;
}

/**
* @param Tag $value
* @inheritDoc
Expand All @@ -60,6 +97,9 @@ public function offsetSet($offset, $value): void
if (!is_string($offset) && !is_null($offset)) {
throw new Exception("Invalid CompoundTag key");
}
if($this->isRaw()) {
throw new Exception("Raw compound tags cannot be modified");
}
if (is_null($offset) && is_null($value->getName())) {
throw new Exception("Tags inside a CompoundTag must be named.");
}
Expand All @@ -68,6 +108,7 @@ public function offsetSet($offset, $value): void
} else {
$offset = $value->getName();
}
$value->setParentTag($this);
$this->offsetUnset($offset);
$this->valueArray[] = $value;
}
Expand Down Expand Up @@ -140,11 +181,16 @@ public function offsetGet($offset)

/**
* @inheritDoc
* @throws Exception
*/
public function offsetUnset($offset)
{
if($this->isRaw()) {
throw new Exception("Raw compound tags cannot be modified");
}
foreach ($this->valueArray as $i => $val) {
if ($val->getName() === $offset) {
$val->setParentTag(null);
unset($this->valueArray[$i]);
break;
}
Expand All @@ -164,6 +210,9 @@ public function count(): int
*/
protected function getValueString(): string
{
if($this->isRaw()) {
return strlen($this->rawContent) . " bytes";
}
return $this->count() . " entr" . ($this->count() === 1 ? "y" : "ies") . "\n{\n" .
$this->indent(implode(", \n", array_map("strval", array_values($this->valueArray)))) .
"\n}";
Expand Down Expand Up @@ -199,6 +248,7 @@ public function set(?string $name, Tag $tag): CompoundTag
/**
* @param string $name
* @return $this
* @throws Exception
*/
public function delete(string $name): CompoundTag
{
Expand Down
8 changes: 8 additions & 0 deletions src/Tag/DoubleTag.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ protected function readContent(Reader $reader): static
return $this;
}

/**
* @inheritDoc
*/
protected static function readContentRaw(Reader $reader, TagOptions $options): string
{
return $reader->getDeserializer()->readDouble()->getRawData();
}

/**
* @inheritDoc
*/
Expand Down
11 changes: 9 additions & 2 deletions src/Tag/EndTag.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace Aternos\Nbt\Tag;

use Aternos\Nbt\IO\Reader\Reader;
use Aternos\Nbt\Deserializer\NbtDeserializer;
use Aternos\Nbt\IO\Writer\Writer;

class EndTag extends Tag
Expand All @@ -29,7 +28,15 @@ protected function readContent(Reader $reader): static
/**
* @inheritDoc
*/
public function canBeNamed(): bool
protected static function readContentRaw(Reader $reader, TagOptions $options): string
{
return "";
}

/**
* @inheritDoc
*/
public static function canBeNamed(): bool
{
return false;
}
Expand Down
8 changes: 8 additions & 0 deletions src/Tag/FloatTag.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ protected function readContent(Reader $reader): static
return $this;
}

/**
* @inheritDoc
*/
protected static function readContentRaw(Reader $reader, TagOptions $options): string
{
return $reader->getDeserializer()->readFloat()->getRawData();
}

/**
* @inheritDoc
*/
Expand Down
12 changes: 12 additions & 0 deletions src/Tag/IntArrayTag.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ protected function readValues(Reader $reader, int $length): array
return $values;
}

/**
* @inheritDoc
*/
protected static function readValuesRaw(Reader $reader, int $length): string
{
$result = "";
for ($i = 0; $i < $length; $i++) {
$result .= $reader->getDeserializer()->readInt()->getRawData();
}
return $result;
}

/**
* @inheritDoc
*/
Expand Down
8 changes: 8 additions & 0 deletions src/Tag/IntTag.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,12 @@ protected function readContent(Reader $reader): static
$this->value = $reader->getDeserializer()->readInt()->getValue();
return $this;
}

/**
* @inheritDoc
*/
protected static function readContentRaw(Reader $reader, TagOptions $options): string
{
return $reader->getDeserializer()->readInt()->getRawData();
}
}
51 changes: 50 additions & 1 deletion src/Tag/ListTag.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ protected function readValues(Reader $reader, int $length): array
throw new Exception("Unknown ListTag content type " . $this->contentTagType);
}
for ($i = 0; $i < $length; $i++) {
$values[] = (new $tagClass())->read($reader, false);
$values[] = (new $tagClass($this->options))->setParentTag($this)->read($reader, false);
}
return $values;
}
Expand All @@ -91,12 +91,38 @@ protected function checkArrayValue($value): bool
return $value::TYPE === $this->contentTagType;
}

/**
* @inheritDoc
*/
protected function readContent(Reader $reader): static
{
$this->contentTagType = $reader->getDeserializer()->readByte()->getValue();
return parent::readContent($reader);
}

/**
* @inheritDoc
* @throws Exception
*/
protected static function readContentRaw(Reader $reader, TagOptions $options): string
{
$contentTagType = $reader->getDeserializer()->readByte();
$length = $reader->getDeserializer()->readLengthPrefix();
$valueData = "";

/** @var Tag $tagClass */
$tagClass = Tag::getTagClass($contentTagType->getValue());
if (is_null($tagClass)) {
throw new Exception("Unknown ListTag content type " . $contentTagType->getValue());
}
$lengthVal = $length->getValue();
for ($i = 0; $i < $lengthVal; $i++) {
$valueData .= $tagClass::readRaw($reader, $options, false);
}

return $contentTagType->getRawData() . $length->getRawData() . $valueData;
}

/**
* @inheritDoc
*/
Expand All @@ -113,6 +139,29 @@ protected function getTagTypeString(): string
return parent::getTagTypeString() . "<" . TagType::NAMES[$this->contentTagType] . ">";
}

/**
* @inheritDoc
*/
public function offsetSet($offset, $value)
{
/** @var Tag $previousValue */
$previousValue = $this->valueArray[$offset] ?? null;
parent::offsetSet($offset, $value);
$value->setParentTag($this);
$previousValue?->setParentTag(null);
}

/**
* @inheritDoc
*/
public function offsetUnset($offset)
{
/** @var Tag $previousValue */
$previousValue = $this->valueArray[$offset] ?? null;
$previousValue?->setParentTag(null);
parent::offsetUnset($offset);
}

/**
* @inheritDoc
*/
Expand Down
12 changes: 12 additions & 0 deletions src/Tag/LongArrayTag.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ protected function readValues(Reader $reader, int $length): array
return $values;
}

/**
* @inheritDoc
*/
protected static function readValuesRaw(Reader $reader, int $length): string
{
$raw = "";
for ($i = 0; $i < $length; $i++) {
$raw = $reader->getDeserializer()->readLong()->getRawData();
}
return $raw;
}

/**
* @inheritDoc
*/
Expand Down
Loading

0 comments on commit 859f4a6

Please sign in to comment.