Skip to content

Commit

Permalink
Merge pull request #3 from cyotek/v3
Browse files Browse the repository at this point in the history
Version 3
  • Loading branch information
cyotek committed Jan 1, 2017
2 parents 787a0b0 + 3660d15 commit 95a9af2
Show file tree
Hide file tree
Showing 155 changed files with 28,680 additions and 6,320 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -12,3 +12,5 @@ testresults
*.suo

push.cmd

BenchmarkDotNet.Artifacts/
56 changes: 55 additions & 1 deletion CHANGELOG.md
@@ -1,8 +1,62 @@
Cyotek.Data.Nbt Change Log
==========================

[3.0.0-alpha] - 2016-12-27
------------------------

This release concentrates on cleaning up the code by removing unused or rarely used features, solving boxing issues, removing some hacks, generally reworking the API and increasing the testing coverage. Some minor code changes may be required due to the level of breaking change this release introduces.

A fair chunk of both the library code and test code are now generated via T4 templates.

### Added
* With the base `Tag` class no longer offering a `Value` property, `GetValue` and `SetValue` methods are available. However, note that these methods will box value types
* The `TagWriter` class can now be used to directly write NBT data to a stream without having to first create concrete `Tag` classes (e.g. similar to using `XmlWriter` over `XmlDocument`)
* The base `Tag` class now implements `IEquatable<Tag>` (boxing)
* Each concrete tag class now implements `IEquatable<>` (non-boxing)
* Added indexers to `TagCompound`
* Added `TagCompound.Count`
* Added Benchmarks project testing the different serialization methods. Unsurprisingly, XML is many times slower than binary, and writing NBT documents without constructing `Tag` objects is faster than creating and then saving a `NbtDocument`
* Added new `TagDictionary.AddRange` overloads
* All library code is now covered by tests

### Fixed
* All tags created internally by the library use `TagFactory` and avoid all of the boxing issues present in previous versions
* `XmlTagReader` crashed if empty byte or int array values were present
* Calling `XmlTagReader.IsNbtDocument` would return `true` if a `type` attribute was found, regardless of if the value was `TagCompound` or not
* `TagDictionary.Add(name, object)` didn't support `TagDictionary` or `TagCollection` values
* `TagCollection.Add(object)` now correctly supports `TagDictionary` and `TagCollection` values
* `TagCollection.Add` will now correctly throw exceptions if named tags are added
* Resolved an infinite loop reading empty `TagCompound` or `TagList` data stored in XML files

### Changed
* Tag names should now be empty when not set rather than `null`
* `TagReader.ReadCollection` and `ReadDictionary` renamed to `ReadList` and `ReadCompound` to match their NBT types.
* `TagFactory.Create` methods have had their parameters shuffled so that `name` comes first, mirroring `TagDictionary` and other `TagFactory` methods
* `TagCollection` contents will automatically set the `ListType` based on the first value added when no explicit type is defined
* `TagDictionary.Add` methods now return the appropriate concrete `Tag` instance for the value's data type
* `TagCompound.GetBoolValue` renamed to `GetBooleanValue`
* `TagCompound.Query` should no longer throw exceptions when a match cannot be found, but instead returns `null`

### Removed
* Removed the `WriteTagOptions` and `ReadTagOptions` enumerations, plus removed any overloaded method supplying these options. Each reader and writer now maintains its own state to know when it should or should not be doing things without having to be told
* Removed the `ITag` interface as it was a pointless level of abstraction and there is already an abstract `Tag` class
* Similarly, `ITagReader` and `ITagWriter` have also been removed
* Removed the default constructors from tag readers and writers
* Removed the default `Tag.Value` implementation. Each `Tag` implementation has a strongly typed `Value` property without boxing.
* Removed all events from the `Tag` object as they added overhead without being used in most use cases
* Removed the `TagException` class as it was unused
* Removed the `TagEventArgs` class as it was unused
* Removed `Tag.ToString(string)` overloads
* Removed `Tag.CanRemove` and `Tag.Remove()`, as they are too situational
* `Tag` properties are no longer `virtual`
* Removed the `CompressionOptions` enum and related support from `NbtDocument`. When saving to a file, XML will be uncompressed, binary will be gzipped. When saving to a `Stream`, you can pass in your own `GZipStream`, `DeflateStream` or equivalent
* All `TagCollection.Add` method overloads that accepted a `name` argument have been removed
* Removed secondary data type helpers from `TagCollection`
* Removed `TagCompound.GetEnumValue`


[2.1.0] - 2016-02-24
------------------
--------------------

### Added
* Added new constructor to `XmlTagWriter` allowing you to specify a `XmlWriter`. Useful for when calling `Write*` methods directly, without first using `WriteDocument`
Expand Down
13 changes: 0 additions & 13 deletions Cyotek.Data.Nbt.ncrunchsolution

This file was deleted.

18 changes: 17 additions & 1 deletion Cyotek.Data.Nbt.sln
@@ -1,20 +1,24 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.24720.0
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cyotek.Data.Nbt", "src\Cyotek.Data.Nbt\Cyotek.Data.Nbt.csproj", "{14287AD3-9576-46E6-9DE3-B7731A496DA3}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Files", "Solution Files", "{3D33379A-2DDB-40E1-A507-F49B6C131CA8}"
ProjectSection(SolutionItems) = preProject
build.cmd = build.cmd
CHANGELOG.md = CHANGELOG.md
COPYING.txt = COPYING.txt
push.cmd = push.cmd
README.md = README.md
SPECIFICATION.md = SPECIFICATION.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cyotek.Data.Nbt.Tests", "src\Cyotek.Data.Nbt.Tests\Cyotek.Data.Nbt.Tests.csproj", "{97730548-7E5A-4AD4-AFC1-6EE1A656C520}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarks", "src\Benchmarks\Benchmarks.csproj", "{29762725-63B1-44E5-A3ED-B34F2C2E5E41}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -45,6 +49,18 @@ Global
{97730548-7E5A-4AD4-AFC1-6EE1A656C520}.Release|Mixed Platforms.Build.0 = Release|x86
{97730548-7E5A-4AD4-AFC1-6EE1A656C520}.Release|x86.ActiveCfg = Release|x86
{97730548-7E5A-4AD4-AFC1-6EE1A656C520}.Release|x86.Build.0 = Release|x86
{29762725-63B1-44E5-A3ED-B34F2C2E5E41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{29762725-63B1-44E5-A3ED-B34F2C2E5E41}.Debug|Any CPU.Build.0 = Debug|Any CPU
{29762725-63B1-44E5-A3ED-B34F2C2E5E41}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{29762725-63B1-44E5-A3ED-B34F2C2E5E41}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{29762725-63B1-44E5-A3ED-B34F2C2E5E41}.Debug|x86.ActiveCfg = Debug|Any CPU
{29762725-63B1-44E5-A3ED-B34F2C2E5E41}.Debug|x86.Build.0 = Debug|Any CPU
{29762725-63B1-44E5-A3ED-B34F2C2E5E41}.Release|Any CPU.ActiveCfg = Release|Any CPU
{29762725-63B1-44E5-A3ED-B34F2C2E5E41}.Release|Any CPU.Build.0 = Release|Any CPU
{29762725-63B1-44E5-A3ED-B34F2C2E5E41}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{29762725-63B1-44E5-A3ED-B34F2C2E5E41}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{29762725-63B1-44E5-A3ED-B34F2C2E5E41}.Release|x86.ActiveCfg = Release|Any CPU
{29762725-63B1-44E5-A3ED-B34F2C2E5E41}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
140 changes: 138 additions & 2 deletions README.md
@@ -1,9 +1,11 @@
Cyotek.Data.Nbt
===============

[![Build status](https://ci.appveyor.com/api/projects/status/d2l6xj7mbv5rkc92?svg=true)](https://ci.appveyor.com/project/cyotek/cyotek-data-nbt)

NBT (Named Binary Tag) is a tag based binary format designed to carry large amounts of binary data with smaller amounts of additional data. This is currently the format that Minecraft uses for player and region data.

Cyotek.Data.Nbt is a library for reading and writing NBT format files used by Minecraft. However, the format is versatile enough to use for many other applications.
Cyotek.Data.Nbt is a library for reading and writing NBT format files used by Minecraft. However, the format is versatile enough to use for many other applications and purposes.

It was originally based on [LibNBT](http://libnbt.codeplex.com/) found on CodePlex, but I've made a lot of changes to it. The API has substantially changed, although it should be easier to use than the original.

Expand All @@ -14,11 +16,145 @@ Features

* Support for all tags in the specification
* Reads and writes binary files compatible with existing NBT libraries and tools
* Supports reading and writing to XML based files for human readable output
* Supports reading and writing to XML based files for human readable output (deprecated)
* Ability to provide custom readers/writers if required
* Extended API for working with NBT documents
* Query support

Saving a document
-----------------

Similar to `XmlDocument` and `XmlWriter`, you can either directly write NBT documents, or you can construct a document then save it. The former approach is the fastest, the latter approach may be simpler.

### Using the BinaryTagWriter to create a document

using (TagWriter writer = new BinaryTagWriter(stream))
{
writer.WriteStartDocument();
writer.WriteStartTag("Level", TagType.Compound);
writer.WriteTag("longTest", 9223372036854775807);
writer.WriteTag("shortTest", (short)32767);
writer.WriteTag("stringTest", "HELLO WORLD THIS IS A TEST STRING ÅÄÖ!");
writer.WriteTag("floatTest", (float)0.498231471);
writer.WriteTag("intTest", 2147483647);
writer.WriteStartTag("nested compound test", TagType.Compound);
writer.WriteStartTag("ham", TagType.Compound);
writer.WriteTag("name", "Hampus");
writer.WriteTag("value", 0.75F);
writer.WriteEndTag();
writer.WriteStartTag("egg", TagType.Compound);
writer.WriteTag("name", "Eggbert");
writer.WriteTag("value", 0.5F);
writer.WriteEndTag();
writer.WriteEndTag();
writer.WriteStartTag("listTest (long)", TagType.List, TagType.Long, 5);
writer.WriteTag((long)11);
writer.WriteTag((long)12);
writer.WriteTag((long)13);
writer.WriteTag((long)14);
writer.WriteTag((long)15);
writer.WriteEndTag();
writer.WriteStartTag("listTest (compound)", TagType.List, TagType.Compound, 2);
writer.WriteStartTag(TagType.Compound);
writer.WriteTag("name", "Compound tag #0");
writer.WriteTag("created-on", 1264099775885);
writer.WriteEndTag();
writer.WriteStartTag(TagType.Compound);
writer.WriteTag("name", "Compound tag #1");
writer.WriteTag("created-on", 1264099775885);
writer.WriteEndTag();
writer.WriteEndTag();
writer.WriteTag("byteTest", (byte)127);
writer.WriteTag("byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))", SampleByteArray);
writer.WriteTag("doubleTest", 0.49312871321823148);
writer.WriteEndTag();
writer.WriteEndDocument();
}

### Using NbtDocument to construct and save a document

NbtDocument document;
TagCompound root;
TagCompound compound;
TagCompound child;
TagList list;

document = new NbtDocument();

root = document.DocumentRoot;
root.Name = "Level";
root.Value.Add("longTest", 9223372036854775807);
root.Value.Add("shortTest", (short)32767);
root.Value.Add("stringTest", "HELLO WORLD THIS IS A TEST STRING ÅÄÖ!");
root.Value.Add("floatTest", (float)0.498231471);
root.Value.Add("intTest", 2147483647);

compound = (TagCompound)root.Value.Add("nested compound test", TagType.Compound);
child = (TagCompound)compound.Value.Add("ham", TagType.Compound);
child.Value.Add("name", "Hampus");
child.Value.Add("value", (float)0.75);
child = (TagCompound)compound.Value.Add("egg", TagType.Compound);
child.Value.Add("name", "Eggbert");
child.Value.Add("value", (float)0.5);

list = (TagList)root.Value.Add("listTest (long)", TagType.List, TagType.Long);
list.Value.Add((long)11);
list.Value.Add((long)12);
list.Value.Add((long)13);
list.Value.Add((long)14);
list.Value.Add((long)15);

list = (TagList)root.Value.Add("listTest (compound)", TagType.List, TagType.Compound);
child = (TagCompound)list.Value.Add(TagType.Compound);
child.Value.Add("name", "Compound tag #0");
child.Value.Add("created-on", 1264099775885);
child = (TagCompound)list.Value.Add(TagType.Compound);
child.Value.Add("name", "Compound tag #1");
child.Value.Add("created-on", 1264099775885);

root.Value.Add("byteTest", (byte)127);
root.Value.Add("byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))", SampleByteArray);
root.Value.Add("doubleTest", 0.49312871321823148);

Once you have a document or root `TagCompound`, you can either use the `NbtDocument` class to save the document, or use a `TagWriter` class directly.

#### Using a TagWriter class

using (BinaryTagWriter writer = new BinaryTagWriter(stream)) // Or XmlTagWriter
{
writer.WriteStartDocument();
writer.WriteTag(document.DocumentRoot);
writer.WriteEndDocument();
}

#### Using NbtDocument

document.Save(stream);

See the Benchmarks project, or the test suite for examples of the different ways of serializing document.s

Using Binary or XML Formats
---------------------------

The library supports both the use of the binary NBT format, and a variant that uses XML. While the XML version is much more readable, it is also much slower to serialize and isn't really recommended for production use.

The `Load` methods of `NbtDocument` class will automatically detect if the source is binary or XML, allowing seamless use. However, if you use serialization classes directly you will need to perform your own detection and construct an `XmlTagReader` or `BinaryTagReader` object accordingly. The static `NbtDocument.GetDocumentFormat` method can help with format detection.

The following table was generated by running the write benchmarks using [BenchmarkDotNet ](http://benchmarkdotnet.org/) and clearly show the difference between writing XML and writing binary.

| Method | Mean | StdErr | StdDev | Median | Gen 0 | Allocated |
| ---------------------------------- |------------ |---------- |----------- |------------ |-------- |---------- |
| WriteBinaryDirect | 8.4448 us | 0.0836 us | 0.5733 us | 8.1153 us | 3.4424 | 6.67 kB |
| WriteBinaryDocument | 16.6214 us | 0.1641 us | 0.7337 us | 16.1428 us | 5.4867 | 10.12 kB |
| WriteBinaryDocumentViaNbtDocument | 16.8629 us | 0.1667 us | 1.2696 us | 16.0460 us | 5.6095 | 10.14 kB |
| WritePredefinedBinaryDocument | 9.3312 us | 0.0808 us | 0.3129 us | 9.1901 us | 3.6070 | 6.86 kB |
| WritePredefinedXmlDocument | 180.8340 us | 1.8036 us | 10.8217 us | 177.5857 us | 23.9909 | 50.56 kB |
| WriteXmlDirect | 172.9066 us | 1.8421 us | 10.2566 us | 167.6837 us | 24.0885 | 50.37 kB |
| WriteXmlDocument | 180.4061 us | 0.6515 us | 2.2568 us | 179.7206 us | 29.5038 | 53.8 kB |
| WriteXmlDocumentViaNbtDocument | 187.5790 us | 1.8653 us | 12.7877 us | 182.9234 us | 28.5127 | 53.82 kB |

In closing, XML support will probably be removed in the next major version of the library and is deprecated in the current.

License
-------

Expand Down

0 comments on commit 95a9af2

Please sign in to comment.