Make Optional<T> into readonly struct to avoid heap allocations

This commit is contained in:
Daniel Cronqvist 2024-12-02 22:45:14 +01:00
parent de41fb5508
commit 92702a512d
35 changed files with 177 additions and 205 deletions

View file

@ -178,7 +178,7 @@ For enum definitions, the <xref:System.FlagsAttribute> can be used to indicate t
> Tiled supports enums which can store their values as either strings or integers, and depending on the storage type you have specified in Tiled, you must make sure to have the same storage type in your <xref:DotTiled.CustomEnumDefinition>. This can be done by setting the `StorageType` property to either `CustomEnumStorageType.String` or `CustomEnumStorageType.Int` when creating the definition, or by passing the storage type as an argument to the <xref:DotTiled.CustomEnumDefinition.FromEnum``1(DotTiled.CustomEnumStorageType)> method. To be consistent with Tiled, <xref:DotTiled.CustomEnumDefinition.FromEnum``1(DotTiled.CustomEnumStorageType)> will default to `CustomEnumStorageType.String` for the storage type parameter. > Tiled supports enums which can store their values as either strings or integers, and depending on the storage type you have specified in Tiled, you must make sure to have the same storage type in your <xref:DotTiled.CustomEnumDefinition>. This can be done by setting the `StorageType` property to either `CustomEnumStorageType.String` or `CustomEnumStorageType.Int` when creating the definition, or by passing the storage type as an argument to the <xref:DotTiled.CustomEnumDefinition.FromEnum``1(DotTiled.CustomEnumStorageType)> method. To be consistent with Tiled, <xref:DotTiled.CustomEnumDefinition.FromEnum``1(DotTiled.CustomEnumStorageType)> will default to `CustomEnumStorageType.String` for the storage type parameter.
> [!WARNING] > [!WARNING]
> If you have a custom enum type in Tiled, but do not define it in DotTiled, you must be aware that the type of the parsed property will be either <xref:DotTiled.StringProperty> or <xref:IntProperty>. It is not possible to determine the correct way to parse the enum property without the custom enum definition, which is why you will instead be given a property of type `string` or `int` when accessing the property in DotTiled. This can lead to inconsistencies between the map in Tiled and the loaded map with DotTiled. It is therefore recommended to define your custom enum types in DotTiled if you want to access the properties as <xref:EnumProperty> instances. > If you have a custom enum type in Tiled, but do not define it in DotTiled, you must be aware that the type of the parsed property will be either <xref:DotTiled.StringProperty> or <xref:DotTiled.IntProperty>. It is not possible to determine the correct way to parse the enum property without the custom enum definition, which is why you will instead be given a property of type `string` or `int` when accessing the property in DotTiled. This can lead to inconsistencies between the map in Tiled and the loaded map with DotTiled. It is therefore recommended to define your custom enum types in DotTiled if you want to access the properties as <xref:DotTiled.EnumProperty> instances.
## Mapping properties to C# classes or enums ## Mapping properties to C# classes or enums

View file

@ -79,6 +79,6 @@ public class Program
[ [
new CustomClassDefinition() { Name = "a" }, new CustomClassDefinition() { Name = "a" },
]; ];
return allDefinedTypes.FirstOrDefault(ctd => ctd.Name == name) is ICustomTypeDefinition ctd ? new Optional<ICustomTypeDefinition>(ctd) : Optional<ICustomTypeDefinition>.Empty; return allDefinedTypes.FirstOrDefault(ctd => ctd.Name == name) is ICustomTypeDefinition ctd ? new Optional<ICustomTypeDefinition>(ctd) : Optional.Empty;
} }
} }

View file

@ -42,10 +42,10 @@ public partial class MapParser : Node2D
private Tileset ResolveTileset(string source) private Tileset ResolveTileset(string source)
{ {
string tilesetString = FileAccess.Open($"res://{source}", FileAccess.ModeFlags.Read).GetAsText(); string tilesetString = FileAccess.Open($"res://{source}", FileAccess.ModeFlags.Read).GetAsText();
using TilesetReader tilesetReader = using TilesetReader tilesetReader =
new TilesetReader(tilesetString, ResolveTileset, ResolveTemplate, ResolveCustomType); new TilesetReader(tilesetString, ResolveTileset, ResolveTemplate, ResolveCustomType);
return tilesetReader.ReadTileset(); return tilesetReader.ReadTileset();
} }
private Template ResolveTemplate(string source) private Template ResolveTemplate(string source)
@ -62,6 +62,6 @@ public partial class MapParser : Node2D
[ [
new CustomClassDefinition() { Name = "a" }, new CustomClassDefinition() { Name = "a" },
]; ];
return allDefinedTypes.FirstOrDefault(ctd => ctd.Name == name) is ICustomTypeDefinition ctd ? new Optional<ICustomTypeDefinition>(ctd) : Optional<ICustomTypeDefinition>.Empty; return allDefinedTypes.FirstOrDefault(ctd => ctd.Name == name) is ICustomTypeDefinition ctd ? new Optional<ICustomTypeDefinition>(ctd) : Optional.Empty;
} }
} }

View file

@ -35,8 +35,7 @@ public static partial class DotTiledAssert
AssertEqual(expected.X, actual.X, nameof(TileLayer.X)); AssertEqual(expected.X, actual.X, nameof(TileLayer.X));
AssertEqual(expected.Y, actual.Y, nameof(TileLayer.Y)); AssertEqual(expected.Y, actual.Y, nameof(TileLayer.Y));
Assert.NotNull(actual.Data); AssertOptionalsEqual(expected.Data, actual.Data, nameof(TileLayer.Data), AssertData);
AssertData(expected.Data, actual.Data);
} }
private static void AssertLayer(ObjectLayer expected, ObjectLayer actual) private static void AssertLayer(ObjectLayer expected, ObjectLayer actual)
@ -60,8 +59,7 @@ public static partial class DotTiledAssert
AssertEqual(expected.X, actual.X, nameof(ImageLayer.X)); AssertEqual(expected.X, actual.X, nameof(ImageLayer.X));
AssertEqual(expected.Y, actual.Y, nameof(ImageLayer.Y)); AssertEqual(expected.Y, actual.Y, nameof(ImageLayer.Y));
Assert.NotNull(actual.Image); AssertOptionalsEqual(expected.Image, actual.Image, nameof(ImageLayer.Image), AssertImage);
AssertImage(expected.Image, actual.Image);
} }
private static void AssertLayer(Group expected, Group actual) private static void AssertLayer(Group expected, Group actual)

View file

@ -33,12 +33,6 @@ public static partial class DotTiledAssert
string nameof, string nameof,
Action<T, T> assertEqual) Action<T, T> assertEqual)
{ {
if (expected is null)
{
Assert.Null(actual);
return;
}
if (expected.HasValue) if (expected.HasValue)
{ {
Assert.True(actual.HasValue, $"Expected {nameof} to have a value"); Assert.True(actual.HasValue, $"Expected {nameof} to have a value");
@ -51,12 +45,6 @@ public static partial class DotTiledAssert
internal static void AssertEqual<T>(Optional<T> expected, Optional<T> actual, string nameof) internal static void AssertEqual<T>(Optional<T> expected, Optional<T> actual, string nameof)
{ {
if (expected is null)
{
Assert.Null(actual);
return;
}
if (expected.HasValue) if (expected.HasValue)
{ {
Assert.True(actual.HasValue, $"Expected {nameof} to have a value"); Assert.True(actual.HasValue, $"Expected {nameof} to have a value");

View file

@ -58,7 +58,7 @@ public partial class TestData
new IntProperty { Name = "intprop", Value = 8 }, new IntProperty { Name = "intprop", Value = 8 },
new ObjectProperty { Name = "objectprop", Value = 5 }, new ObjectProperty { Name = "objectprop", Value = 5 },
new StringProperty { Name = "stringprop", Value = "This is a string, hello world!" }, new StringProperty { Name = "stringprop", Value = "This is a string, hello world!" },
new ColorProperty { Name = "unsetcolorprop", Value = Optional<Color>.Empty } new ColorProperty { Name = "unsetcolorprop", Value = Optional.Empty }
] ]
}; };
} }

View file

@ -59,30 +59,6 @@ public class OptionalTests
Assert.Equal(expectedValue, optional.Value); Assert.Equal(expectedValue, optional.Value);
} }
[Fact]
public void ImplicitConversion_FromOptionalToValue_ShouldReturnValue_WhenHasValueIsTrue()
{
// Arrange
int expectedValue = 10;
var optional = new Optional<int>(expectedValue);
// Act
int value = optional;
// Assert
Assert.Equal(expectedValue, value);
}
[Fact]
public void ImplicitConversion_FromOptionalToValue_ShouldThrowException_WhenHasValueIsFalse()
{
// Arrange
var optional = new Optional<int>();
// Act & Assert
_ = Assert.Throws<InvalidOperationException>(() => { int value = optional; });
}
// ToString Method Tests // ToString Method Tests
[Fact] [Fact]
@ -237,18 +213,6 @@ public class OptionalTests
_ = Assert.Throws<InvalidOperationException>(() => optional.Value); _ = Assert.Throws<InvalidOperationException>(() => optional.Value);
} }
[Fact]
public void ImplicitConversion_WhenHasValueIsFalse_ShouldThrowInvalidOperationException()
{
// Arrange
var optional = new Optional<int>();
// Act & Assert
_ = Assert.Throws<InvalidOperationException>(() => { int value = optional; });
}
// Edge Cases
[Fact] [Fact]
public void EmptyOptionalEquality_ShouldReturnTrue() public void EmptyOptionalEquality_ShouldReturnTrue()
{ {

View file

@ -71,8 +71,8 @@ public class LoaderTests
"""); """);
var resourceCache = Substitute.For<IResourceCache>(); var resourceCache = Substitute.For<IResourceCache>();
resourceCache.GetTileset(Arg.Any<string>()).Returns(Optional<Tileset>.Empty); resourceCache.GetTileset(Arg.Any<string>()).Returns(Optional.Empty);
resourceCache.GetTemplate(Arg.Any<string>()).Returns(Optional<Template>.Empty); resourceCache.GetTemplate(Arg.Any<string>()).Returns(Optional.Empty);
var customTypeDefinitions = Enumerable.Empty<ICustomTypeDefinition>(); var customTypeDefinitions = Enumerable.Empty<ICustomTypeDefinition>();
var loader = new Loader(resourceReader, resourceCache, customTypeDefinitions); var loader = new Loader(resourceReader, resourceCache, customTypeDefinitions);
@ -134,8 +134,8 @@ public class LoaderTests
"""); """);
var resourceCache = Substitute.For<IResourceCache>(); var resourceCache = Substitute.For<IResourceCache>();
resourceCache.GetTileset(Arg.Any<string>()).Returns(Optional<Tileset>.Empty); resourceCache.GetTileset(Arg.Any<string>()).Returns(Optional.Empty);
resourceCache.GetTemplate(Arg.Any<string>()).Returns(Optional<Template>.Empty); resourceCache.GetTemplate(Arg.Any<string>()).Returns(Optional.Empty);
var customTypeDefinitions = Enumerable.Empty<ICustomTypeDefinition>(); var customTypeDefinitions = Enumerable.Empty<ICustomTypeDefinition>();
var loader = new Loader(resourceReader, resourceCache, customTypeDefinitions); var loader = new Loader(resourceReader, resourceCache, customTypeDefinitions);
@ -219,7 +219,7 @@ public class LoaderTests
"""); """);
var resourceCache = Substitute.For<IResourceCache>(); var resourceCache = Substitute.For<IResourceCache>();
resourceCache.GetTileset(Arg.Any<string>()).Returns(Optional<Tileset>.Empty); resourceCache.GetTileset(Arg.Any<string>()).Returns(Optional.Empty);
resourceCache.GetTemplate("template.tx").Returns(new Optional<Template>(new Template resourceCache.GetTemplate("template.tx").Returns(new Optional<Template>(new Template
{ {
Object = new PolygonObject Object = new PolygonObject

View file

@ -39,7 +39,7 @@ public partial class MapReaderTests
return new Optional<ICustomTypeDefinition>(ctd); return new Optional<ICustomTypeDefinition>(ctd);
} }
return Optional<ICustomTypeDefinition>.Empty; return Optional.Empty;
} }
using var mapReader = new MapReader(mapString, ResolveTileset, ResolveTemplate, ResolveCustomType); using var mapReader = new MapReader(mapString, ResolveTileset, ResolveTemplate, ResolveCustomType);

View file

@ -35,7 +35,7 @@ public partial class TmjMapReaderTests
return new Optional<ICustomTypeDefinition>(ctd); return new Optional<ICustomTypeDefinition>(ctd);
} }
return Optional<ICustomTypeDefinition>.Empty; return Optional.Empty;
} }
using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate, ResolveCustomType); using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate, ResolveCustomType);

View file

@ -35,7 +35,7 @@ public partial class TmxMapReaderTests
return new Optional<ICustomTypeDefinition>(ctd); return new Optional<ICustomTypeDefinition>(ctd);
} }
return Optional<ICustomTypeDefinition>.Empty; return Optional.Empty;
} }
using var mapReader = new TmxMapReader(reader, ResolveTileset, ResolveTemplate, ResolveCustomType); using var mapReader = new TmxMapReader(reader, ResolveTileset, ResolveTemplate, ResolveCustomType);

View file

@ -37,7 +37,7 @@ public abstract class BaseLayer : HasPropertiesBase
/// <summary> /// <summary>
/// A tint color that is multiplied with any tiles drawn by this layer. /// A tint color that is multiplied with any tiles drawn by this layer.
/// </summary> /// </summary>
public Optional<Color> TintColor { get; set; } = Optional<Color>.Empty; public Optional<Color> TintColor { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// Horizontal offset for this layer in pixels. /// Horizontal offset for this layer in pixels.

View file

@ -118,27 +118,27 @@ public class Data
/// <summary> /// <summary>
/// The encoding used to encode the tile layer data. /// The encoding used to encode the tile layer data.
/// </summary> /// </summary>
public Optional<DataEncoding> Encoding { get; set; } = Optional<DataEncoding>.Empty; public Optional<DataEncoding> Encoding { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// The compression method used to compress the tile layer data. /// The compression method used to compress the tile layer data.
/// </summary> /// </summary>
public Optional<DataCompression> Compression { get; set; } = Optional<DataCompression>.Empty; public Optional<DataCompression> Compression { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// The parsed tile layer data, as a list of tile GIDs. /// The parsed tile layer data, as a list of tile GIDs.
/// To get an actual tile ID, you map it to a local tile ID using the correct tileset. Please refer to /// To get an actual tile ID, you map it to a local tile ID using the correct tileset. Please refer to
/// <see href="https://doc.mapeditor.org/en/stable/reference/global-tile-ids/#mapping-a-gid-to-a-local-tile-id">the documentation on how to do this</see>. /// <see href="https://doc.mapeditor.org/en/stable/reference/global-tile-ids/#mapping-a-gid-to-a-local-tile-id">the documentation on how to do this</see>.
/// </summary> /// </summary>
public Optional<uint[]> GlobalTileIDs { get; set; } = Optional<uint[]>.Empty; public Optional<uint[]> GlobalTileIDs { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// The parsed flipping flags for each tile in the layer. Appear in the same order as the tiles in the layer in <see cref="GlobalTileIDs"/>. /// The parsed flipping flags for each tile in the layer. Appear in the same order as the tiles in the layer in <see cref="GlobalTileIDs"/>.
/// </summary> /// </summary>
public Optional<FlippingFlags[]> FlippingFlags { get; set; } = Optional<FlippingFlags[]>.Empty; public Optional<FlippingFlags[]> FlippingFlags { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// If the map is infinite, it will instead contain a list of chunks. /// If the map is infinite, it will instead contain a list of chunks.
/// </summary> /// </summary>
public Optional<Chunk[]> Chunks { get; set; } = Optional<Chunk[]>.Empty; public Optional<Chunk[]> Chunks { get; set; } = Optional.Empty;
} }

View file

@ -46,7 +46,7 @@ public class ObjectLayer : BaseLayer
/// <summary> /// <summary>
/// A color that is multiplied with any tile objects drawn by this layer. /// A color that is multiplied with any tile objects drawn by this layer.
/// </summary> /// </summary>
public Optional<Color> Color { get; set; } = Optional<Color>.Empty; public Optional<Color> Color { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// Whether the objects are drawn according to the order of appearance (<see cref="DrawOrder.Index"/>) or sorted by their Y coordinate (<see cref="DrawOrder.TopDown"/>). /// Whether the objects are drawn according to the order of appearance (<see cref="DrawOrder.Index"/>) or sorted by their Y coordinate (<see cref="DrawOrder.TopDown"/>).

View file

@ -10,7 +10,7 @@ public abstract class Object : HasPropertiesBase
/// <summary> /// <summary>
/// Unique ID of the objects. Each object that is placed on a map gets a unique ID. Even if an object was deleted, no object gets the same ID. /// Unique ID of the objects. Each object that is placed on a map gets a unique ID. Even if an object was deleted, no object gets the same ID.
/// </summary> /// </summary>
public Optional<uint> ID { get; set; } = Optional<uint>.Empty; public Optional<uint> ID { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// The name of the object. An arbitrary string. /// The name of the object. An arbitrary string.
@ -55,7 +55,7 @@ public abstract class Object : HasPropertiesBase
/// <summary> /// <summary>
/// A reference to a template file. /// A reference to a template file.
/// </summary> /// </summary>
public Optional<string> Template { get; set; } = Optional<string>.Empty; public Optional<string> Template { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// Object properties. /// Object properties.

View file

@ -28,5 +28,5 @@ public class TileLayer : BaseLayer
/// <summary> /// <summary>
/// The tile layer data. /// The tile layer data.
/// </summary> /// </summary>
public Optional<Data> Data { get; set; } = Optional<Data>.Empty; public Optional<Data> Data { get; set; } = Optional.Empty;
} }

View file

@ -100,7 +100,7 @@ public class Map : HasPropertiesBase
/// <summary> /// <summary>
/// The Tiled version used to save the file. /// The Tiled version used to save the file.
/// </summary> /// </summary>
public Optional<string> TiledVersion { get; set; } = Optional<string>.Empty; public Optional<string> TiledVersion { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// The class of this map. /// The class of this map.
@ -146,17 +146,17 @@ public class Map : HasPropertiesBase
/// <summary> /// <summary>
/// Only for hexagonal maps. Determines the width or height (depending on the staggered axis) of the tile's edge, in pixels. /// Only for hexagonal maps. Determines the width or height (depending on the staggered axis) of the tile's edge, in pixels.
/// </summary> /// </summary>
public Optional<uint> HexSideLength { get; set; } = Optional<uint>.Empty; public Optional<uint> HexSideLength { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// For staggered and hexagonal maps, determines which axis (X or Y) is staggered. /// For staggered and hexagonal maps, determines which axis (X or Y) is staggered.
/// </summary> /// </summary>
public Optional<StaggerAxis> StaggerAxis { get; set; } = Optional<StaggerAxis>.Empty; public Optional<StaggerAxis> StaggerAxis { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// For staggered and hexagonal maps, determines whether the "even" or "odd" indexes along the staggered axis are shifted. /// For staggered and hexagonal maps, determines whether the "even" or "odd" indexes along the staggered axis are shifted.
/// </summary> /// </summary>
public Optional<StaggerIndex> StaggerIndex { get; set; } = Optional<StaggerIndex>.Empty; public Optional<StaggerIndex> StaggerIndex { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// X coordinate of the parallax origin in pixels. /// X coordinate of the parallax origin in pixels.

View file

@ -2,62 +2,94 @@ using System;
namespace DotTiled; namespace DotTiled;
/// <summary>
/// Represents an empty value.
/// </summary>
public readonly struct OptionalEmpty;
/// <summary>
/// Represents an empty <see cref="Optional{T}"/> object.
/// </summary>
public static class Optional
{
/// <summary>
/// Represents an empty <see cref="Optional{T}"/> object.
/// </summary>
public static readonly OptionalEmpty Empty = default;
}
/// <summary> /// <summary>
/// Represents a value that may or may not be present. /// Represents a value that may or may not be present.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the optionally present value.</typeparam> /// <typeparam name="T">The type of the optionally present value.</typeparam>
public class Optional<T> public readonly struct Optional<T>
{ {
#pragma warning disable IDE0032 // Use auto property
private readonly bool _hasValue;
private readonly T _value; private readonly T _value;
/// <summary> /// <summary>
/// Gets a value indicating whether the current <see cref="Optional{T}"/> object has a value. /// Returns <see langword="true"/> if the <see cref="Value"/> will return a meaningful value.
/// </summary> /// </summary>
public bool HasValue { get; } /// <returns></returns>
public bool HasValue => _hasValue;
#pragma warning restore IDE0032 // Use auto property
/// <summary> /// <summary>
/// Gets the value of the current <see cref="Optional{T}"/> object if it has been set; otherwise, throws an exception. /// Constructs an <see cref="Optional{T}"/> with a meaningful value.
/// </summary> /// </summary>
/// <param name="value"></param>
public Optional(T value)
{
_hasValue = true;
_value = value;
}
/// <summary>
/// Gets the value of the current object. Not meaningful unless <see cref="HasValue"/> returns <see langword="true"/>.
/// </summary>
/// <remarks>
/// <para>Unlike <see cref="Nullable{T}.Value"/>, this property does not throw an exception when
/// <see cref="HasValue"/> is <see langword="false"/>.</para>
/// </remarks>
/// <returns>
/// <para>The value if <see cref="HasValue"/> is <see langword="true"/>; otherwise, the default value for type
/// <typeparamref name="T"/>.</para>
/// </returns>
public T Value => HasValue ? _value : throw new InvalidOperationException("Value is not set"); public T Value => HasValue ? _value : throw new InvalidOperationException("Value is not set");
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Optional{T}"/> class with the specified value. /// Creates a new object initialized to a meaningful value.
/// </summary> /// </summary>
/// <param name="value">The value to be set.</param> /// <param name="value"></param>
public Optional(T value)
{
_value = value;
HasValue = true;
}
/// <summary>
/// Initializes a new instance of the <see cref="Optional{T}"/> class with no value.
/// </summary>
public Optional()
{
_value = default!;
HasValue = false;
}
/// <summary>
/// Implicitly converts a value to an <see cref="Optional{T}"/> object.
/// </summary>
/// <param name="value">The value to be converted.</param>
public static implicit operator Optional<T>(T value) public static implicit operator Optional<T>(T value)
{ {
if (value is null) if (value is null)
return new(); {
return default;
}
return new(value); return new Optional<T>(value);
} }
/// <summary> /// <summary>
/// Implicitly converts an <see cref="Optional{T}"/> object to a value. /// Creates a new object initialized to an empty value.
/// </summary> /// </summary>
/// <param name="optional">The <see cref="Optional{T}"/> object to be converted.</param> /// <param name="_"></param>
public static implicit operator T(Optional<T> optional) public static implicit operator Optional<T>(OptionalEmpty _)
{ {
return optional.Value; return default;
}
/// <summary>
/// Returns a string representation of this object.
/// </summary>
public override string ToString()
{
// Note: For nullable types, it's possible to have _hasValue true and _value null.
return _hasValue
? _value?.ToString() ?? "null"
: "Empty";
} }
/// <summary> /// <summary>
@ -71,17 +103,6 @@ public class Optional<T>
return left.Equals(right); return left.Equals(right);
} }
/// <summary>
/// Determines whether the specified <see cref="Optional{T}"/> objects are not equal.
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <returns></returns>
public static bool operator !=(Optional<T> left, Optional<T> right)
{
return !left.Equals(right);
}
/// <summary> /// <summary>
/// Returns the value of the current <see cref="Optional{T}"/> object if it has been set; otherwise, returns the specified default value. /// Returns the value of the current <see cref="Optional{T}"/> object if it has been set; otherwise, returns the specified default value.
/// </summary> /// </summary>
@ -96,9 +117,6 @@ public class Optional<T>
/// <returns></returns> /// <returns></returns>
public Optional<T> GetValueOrOptional(Optional<T> defaultValue) => HasValue ? this : defaultValue; public Optional<T> GetValueOrOptional(Optional<T> defaultValue) => HasValue ? this : defaultValue;
/// <inheritdoc />
public override string ToString() => HasValue ? _value.ToString() : "Empty";
/// <inheritdoc /> /// <inheritdoc />
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
@ -129,9 +147,13 @@ public class Optional<T>
public override int GetHashCode() => HasValue ? _value!.GetHashCode() : 0; public override int GetHashCode() => HasValue ? _value!.GetHashCode() : 0;
/// <summary> /// <summary>
/// Represents an empty <see cref="Optional{T}"/> object. /// Determines whether the specified <see cref="Optional{T}"/> objects are not equal.
/// </summary> /// </summary>
#pragma warning disable CA1000 // Do not declare static members on generic types /// <param name="left"></param>
public static Optional<T> Empty => new(); /// <param name="right"></param>
#pragma warning restore CA1000 // Do not declare static members on generic types /// <returns></returns>
public static bool operator !=(Optional<T> left, Optional<T> right)
{
return !left.Equals(right);
}
} }

View file

@ -16,7 +16,7 @@ public class DefaultResourceCache : IResourceCache
if (_templates.TryGetValue(path, out var template)) if (_templates.TryGetValue(path, out var template))
return new Optional<Template>(template); return new Optional<Template>(template);
return Optional<Template>.Empty; return Optional.Empty;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -25,7 +25,7 @@ public class DefaultResourceCache : IResourceCache
if (_tilesets.TryGetValue(path, out var tileset)) if (_tilesets.TryGetValue(path, out var tileset))
return new Optional<Tileset>(tileset); return new Optional<Tileset>(tileset);
return Optional<Tileset>.Empty; return Optional.Empty;
} }
/// <inheritdoc/> /// <inheritdoc/>

View file

@ -91,7 +91,7 @@ internal static partial class Helpers
if (string.IsNullOrWhiteSpace(className)) if (string.IsNullOrWhiteSpace(className))
return null; return null;
var customType = customTypeResolver(className) ?? throw new InvalidOperationException($"Could not resolve custom type '{className}'."); var customType = customTypeResolver(className);
if (!customType.HasValue) if (!customType.HasValue)
return null; return null;

View file

@ -16,7 +16,7 @@ public interface IResourceCache
/// Retrieves a tileset from the cache with the given <paramref name="path"/>. /// Retrieves a tileset from the cache with the given <paramref name="path"/>.
/// </summary> /// </summary>
/// <param name="path">The path to the tileset file.</param> /// <param name="path">The path to the tileset file.</param>
/// <returns>The tileset if it exists in the cache; otherwise, <see cref="Optional{Tileset}.Empty"/>.</returns> /// <returns>The tileset if it exists in the cache; otherwise, <see cref="Optional.Empty"/>.</returns>
Optional<Tileset> GetTileset(string path); Optional<Tileset> GetTileset(string path);
/// <summary> /// <summary>
@ -30,6 +30,6 @@ public interface IResourceCache
/// Retrieves a template from the cache with the given <paramref name="path"/>. /// Retrieves a template from the cache with the given <paramref name="path"/>.
/// </summary> /// </summary>
/// <param name="path">The path to the template file.</param> /// <param name="path">The path to the template file.</param>
/// <returns>The template if it exists in the cache; otherwise, <see cref="Optional{Template}.Empty"/>.</returns> /// <returns>The template if it exists in the cache; otherwise, <see cref="Optional.Empty"/>.</returns>
Optional<Template> GetTemplate(string path); Optional<Template> GetTemplate(string path);
} }

View file

@ -119,6 +119,6 @@ public class Loader
if (_customTypeDefinitions.TryGetValue(name, out var customTypeDefinition)) if (_customTypeDefinitions.TryGetValue(name, out var customTypeDefinition))
return new Optional<ICustomTypeDefinition>(customTypeDefinition); return new Optional<ICustomTypeDefinition>(customTypeDefinition);
return Optional<ICustomTypeDefinition>.Empty; return Optional.Empty;
} }
} }

View file

@ -18,10 +18,10 @@ internal static class ExtensionsJsonElement
internal static Optional<T> GetOptionalProperty<T>(this JsonElement element, string propertyName) internal static Optional<T> GetOptionalProperty<T>(this JsonElement element, string propertyName)
{ {
if (!element.TryGetProperty(propertyName, out var property)) if (!element.TryGetProperty(propertyName, out var property))
return Optional<T>.Empty; return Optional.Empty;
if (property.ValueKind == JsonValueKind.Null) if (property.ValueKind == JsonValueKind.Null)
return Optional<T>.Empty; return Optional.Empty;
return property.GetValueAs<T>(); return property.GetValueAs<T>();
} }
@ -67,15 +67,15 @@ internal static class ExtensionsJsonElement
internal static Optional<T> GetOptionalPropertyParseable<T>(this JsonElement element, string propertyName) where T : IParsable<T> internal static Optional<T> GetOptionalPropertyParseable<T>(this JsonElement element, string propertyName) where T : IParsable<T>
{ {
if (!element.TryGetProperty(propertyName, out var property)) if (!element.TryGetProperty(propertyName, out var property))
return Optional<T>.Empty; return Optional.Empty;
return T.Parse(property.GetString()!, CultureInfo.InvariantCulture); return T.Parse(property.GetString()!, CultureInfo.InvariantCulture);
} }
internal static Optional<T> GetOptionalPropertyParseable<T>(this JsonElement element, string propertyName, Func<string, T> parser) internal static Optional<T> GetOptionalPropertyParseable<T>(this JsonElement element, string propertyName, Func<string, Optional<T>> parser)
{ {
if (!element.TryGetProperty(propertyName, out var property)) if (!element.TryGetProperty(propertyName, out var property))
return Optional<T>.Empty; return Optional.Empty;
return parser(property.GetString()!); return parser(property.GetString()!);
} }
@ -91,7 +91,7 @@ internal static class ExtensionsJsonElement
internal static Optional<T> GetOptionalPropertyCustom<T>(this JsonElement element, string propertyName, Func<JsonElement, T> parser) internal static Optional<T> GetOptionalPropertyCustom<T>(this JsonElement element, string propertyName, Func<JsonElement, T> parser)
{ {
if (!element.TryGetProperty(propertyName, out var property)) if (!element.TryGetProperty(propertyName, out var property))
return Optional<T>.Empty; return Optional.Empty;
return parser(property); return parser(property);
} }

View file

@ -32,8 +32,8 @@ public abstract partial class TmjReaderBase
Y = y, Y = y,
Width = width, Width = width,
Height = height, Height = height,
GlobalTileIDs = data.GlobalTileIDs, GlobalTileIDs = data.GlobalTileIDs.Value,
FlippingFlags = data.FlippingFlags FlippingFlags = data.FlippingFlags.Value
}; };
} }

View file

@ -61,7 +61,7 @@ public abstract partial class TmjReaderBase
internal DotTiled.Object ReadObject(JsonElement element) internal DotTiled.Object ReadObject(JsonElement element)
{ {
Optional<uint> idDefault = Optional<uint>.Empty; Optional<uint> idDefault = Optional.Empty;
string nameDefault = ""; string nameDefault = "";
string typeDefault = ""; string typeDefault = "";
float xDefault = 0f; float xDefault = 0f;
@ -80,7 +80,7 @@ public abstract partial class TmjReaderBase
var template = element.GetOptionalProperty<string>("template"); var template = element.GetOptionalProperty<string>("template");
if (template.HasValue) if (template.HasValue)
{ {
var resolvedTemplate = _externalTemplateResolver(template); var resolvedTemplate = _externalTemplateResolver(template.Value);
var templObj = resolvedTemplate.Object; var templObj = resolvedTemplate.Object;
idDefault = templObj.ID; idDefault = templObj.ID;
@ -225,7 +225,7 @@ public abstract partial class TmjReaderBase
text.Value.Visible = visible; text.Value.Visible = visible;
text.Value.Template = template; text.Value.Template = template;
text.Value.Properties = properties; text.Value.Properties = properties;
return text; return text.Value;
} }
return new RectangleObject return new RectangleObject

View file

@ -16,7 +16,7 @@ public abstract partial class TmjReaderBase
{ {
"zlib" => DataCompression.ZLib, "zlib" => DataCompression.ZLib,
"gzip" => DataCompression.GZip, "gzip" => DataCompression.GZip,
"" => Optional<DataCompression>.Empty, "" => Optional.Empty,
_ => throw new JsonException($"Unsupported compression '{s}'.") _ => throw new JsonException($"Unsupported compression '{s}'.")
}); });
var chunks = element.GetOptionalPropertyCustom<Data>("chunks", e => ReadDataAsChunks(e, compression, encoding)); var chunks = element.GetOptionalPropertyCustom<Data>("chunks", e => ReadDataAsChunks(e, compression, encoding));
@ -62,7 +62,7 @@ public abstract partial class TmjReaderBase
Y = y, Y = y,
Width = width, Width = width,
Height = height, Height = height,
Data = data ?? chunks Data = data.GetValueOrOptional(chunks)
}; };
} }
} }

View file

@ -7,8 +7,8 @@ public abstract partial class TmjReaderBase
{ {
internal Tileset ReadTileset( internal Tileset ReadTileset(
JsonElement element, JsonElement element,
Optional<string> parentVersion = null, Optional<string> parentVersion = default,
Optional<string> parentTiledVersion = null) Optional<string> parentTiledVersion = default)
{ {
var backgroundColor = element.GetOptionalPropertyParseable<Color>("backgroundcolor"); var backgroundColor = element.GetOptionalPropertyParseable<Color>("backgroundcolor");
var @class = element.GetOptionalProperty<string>("class").GetValueOr(""); var @class = element.GetOptionalProperty<string>("class").GetValueOr("");
@ -63,7 +63,7 @@ public abstract partial class TmjReaderBase
if (source.HasValue) if (source.HasValue)
{ {
var resolvedTileset = _externalTilesetResolver(source); var resolvedTileset = _externalTilesetResolver(source.Value);
resolvedTileset.FirstGID = firstGID; resolvedTileset.FirstGID = firstGID;
resolvedTileset.Source = source; resolvedTileset.Source = source;
return resolvedTileset; return resolvedTileset;
@ -71,34 +71,34 @@ public abstract partial class TmjReaderBase
Optional<Image> imageModel = image.HasValue ? new Image Optional<Image> imageModel = image.HasValue ? new Image
{ {
Format = Helpers.ParseImageFormatFromSource(image), Format = Helpers.ParseImageFormatFromSource(image.Value),
Source = image, Source = image,
Height = imageHeight, Height = imageHeight,
Width = imageWidth, Width = imageWidth,
TransparentColor = transparentColor TransparentColor = transparentColor
} : Optional<Image>.Empty; } : Optional.Empty;
return new Tileset return new Tileset
{ {
Class = @class, Class = @class,
Columns = columns, Columns = columns.Value,
FillMode = fillMode, FillMode = fillMode,
FirstGID = firstGID, FirstGID = firstGID,
Grid = grid, Grid = grid,
Image = imageModel, Image = imageModel,
Margin = margin, Margin = margin.Value,
Name = name, Name = name.Value,
ObjectAlignment = objectAlignment, ObjectAlignment = objectAlignment,
Properties = properties, Properties = properties,
Source = source, Source = source,
Spacing = spacing, Spacing = spacing.Value,
TileCount = tileCount, TileCount = tileCount.Value,
TiledVersion = tiledVersion, TiledVersion = tiledVersion,
TileHeight = tileHeight, TileHeight = tileHeight.Value,
TileOffset = tileOffset, TileOffset = tileOffset,
RenderSize = tileRenderSize, RenderSize = tileRenderSize,
Tiles = tiles, Tiles = tiles,
TileWidth = tileWidth, TileWidth = tileWidth.Value,
Version = version, Version = version,
Wangsets = wangsets, Wangsets = wangsets,
Transformations = transformations Transformations = transformations
@ -171,11 +171,11 @@ public abstract partial class TmjReaderBase
Optional<Image> imageModel = image.HasValue ? new Image Optional<Image> imageModel = image.HasValue ? new Image
{ {
Format = Helpers.ParseImageFormatFromSource(image), Format = Helpers.ParseImageFormatFromSource(image.Value),
Source = image, Source = image,
Height = imageHeight ?? 0, Height = imageHeight.GetValueOr(0),
Width = imageWidth ?? 0 Width = imageWidth.GetValueOr(0)
} : Optional<Image>.Empty; } : Optional.Empty;
return new Tile return new Tile
{ {

View file

@ -64,10 +64,10 @@ public abstract partial class TmxReaderBase
internal static uint[] ReadTileChildrenInWrapper(string wrapper, XmlReader reader) => internal static uint[] ReadTileChildrenInWrapper(string wrapper, XmlReader reader) =>
reader.ReadList(wrapper, "tile", (r) => r.GetOptionalAttributeParseable<uint>("gid").GetValueOr(0)).ToArray(); reader.ReadList(wrapper, "tile", (r) => r.GetOptionalAttributeParseable<uint>("gid").GetValueOr(0)).ToArray();
internal static uint[] ReadRawData(XmlReader reader, DataEncoding encoding, Optional<DataCompression> compression) internal static uint[] ReadRawData(XmlReader reader, Optional<DataEncoding> encoding, Optional<DataCompression> compression)
{ {
var data = reader.ReadElementContentAsString(); var data = reader.ReadElementContentAsString();
if (encoding == DataEncoding.Csv) if (encoding.Value == DataEncoding.Csv)
return ParseCsvData(data); return ParseCsvData(data);
using var bytes = new MemoryStream(Convert.FromBase64String(data)); using var bytes = new MemoryStream(Convert.FromBase64String(data));

View file

@ -74,7 +74,7 @@ public abstract partial class TmxReaderBase
var template = _reader.GetOptionalAttribute("template"); var template = _reader.GetOptionalAttribute("template");
DotTiled.Object obj = null; DotTiled.Object obj = null;
if (template.HasValue) if (template.HasValue)
obj = _externalTemplateResolver(template).Object; obj = _externalTemplateResolver(template.Value).Object;
uint idDefault = obj?.ID.GetValueOr(0) ?? 0; uint idDefault = obj?.ID.GetValueOr(0) ?? 0;
string nameDefault = obj?.Name ?? ""; string nameDefault = obj?.Name ?? "";
@ -84,7 +84,7 @@ public abstract partial class TmxReaderBase
float widthDefault = obj?.Width ?? 0f; float widthDefault = obj?.Width ?? 0f;
float heightDefault = obj?.Height ?? 0f; float heightDefault = obj?.Height ?? 0f;
float rotationDefault = obj?.Rotation ?? 0f; float rotationDefault = obj?.Rotation ?? 0f;
Optional<uint> gidDefault = obj is TileObject tileObj ? tileObj.GID : Optional<uint>.Empty; Optional<uint> gidDefault = obj is TileObject tileObj ? tileObj.GID : Optional.Empty;
bool visibleDefault = obj?.Visible ?? true; bool visibleDefault = obj?.Visible ?? true;
List<IProperty> propertiesDefault = obj?.Properties ?? null; List<IProperty> propertiesDefault = obj?.Properties ?? null;
@ -242,15 +242,15 @@ public abstract partial class TmxReaderBase
internal TextObject ReadTextObject() internal TextObject ReadTextObject()
{ {
// Attributes // Attributes
var fontFamily = _reader.GetOptionalAttribute("fontfamily") ?? "sans-serif"; var fontFamily = _reader.GetOptionalAttribute("fontfamily").GetValueOr("sans-serif");
var pixelSize = _reader.GetOptionalAttributeParseable<int>("pixelsize") ?? 16; var pixelSize = _reader.GetOptionalAttributeParseable<int>("pixelsize").GetValueOr(16);
var wrap = _reader.GetOptionalAttributeParseable<bool>("wrap") ?? false; var wrap = _reader.GetOptionalAttributeParseable<bool>("wrap").GetValueOr(false);
var color = _reader.GetOptionalAttributeClass<Color>("color") ?? Color.Parse("#000000", CultureInfo.InvariantCulture); var color = _reader.GetOptionalAttributeClass<Color>("color").GetValueOr(Color.Parse("#000000", CultureInfo.InvariantCulture));
var bold = _reader.GetOptionalAttributeParseable<bool>("bold") ?? false; var bold = _reader.GetOptionalAttributeParseable<bool>("bold").GetValueOr(false);
var italic = _reader.GetOptionalAttributeParseable<bool>("italic") ?? false; var italic = _reader.GetOptionalAttributeParseable<bool>("italic").GetValueOr(false);
var underline = _reader.GetOptionalAttributeParseable<bool>("underline") ?? false; var underline = _reader.GetOptionalAttributeParseable<bool>("underline").GetValueOr(false);
var strikeout = _reader.GetOptionalAttributeParseable<bool>("strikeout") ?? false; var strikeout = _reader.GetOptionalAttributeParseable<bool>("strikeout").GetValueOr(false);
var kerning = _reader.GetOptionalAttributeParseable<bool>("kerning") ?? true; var kerning = _reader.GetOptionalAttributeParseable<bool>("kerning").GetValueOr(true);
var hAlign = _reader.GetOptionalAttributeEnum<TextHorizontalAlignment>("halign", s => s switch var hAlign = _reader.GetOptionalAttributeEnum<TextHorizontalAlignment>("halign", s => s switch
{ {
"left" => TextHorizontalAlignment.Left, "left" => TextHorizontalAlignment.Left,
@ -258,14 +258,14 @@ public abstract partial class TmxReaderBase
"right" => TextHorizontalAlignment.Right, "right" => TextHorizontalAlignment.Right,
"justify" => TextHorizontalAlignment.Justify, "justify" => TextHorizontalAlignment.Justify,
_ => throw new InvalidOperationException($"Unknown horizontal alignment '{s}'") _ => throw new InvalidOperationException($"Unknown horizontal alignment '{s}'")
}) ?? TextHorizontalAlignment.Left; }).GetValueOr(TextHorizontalAlignment.Left);
var vAlign = _reader.GetOptionalAttributeEnum<TextVerticalAlignment>("valign", s => s switch var vAlign = _reader.GetOptionalAttributeEnum<TextVerticalAlignment>("valign", s => s switch
{ {
"top" => TextVerticalAlignment.Top, "top" => TextVerticalAlignment.Top,
"center" => TextVerticalAlignment.Center, "center" => TextVerticalAlignment.Center,
"bottom" => TextVerticalAlignment.Bottom, "bottom" => TextVerticalAlignment.Bottom,
_ => throw new InvalidOperationException($"Unknown vertical alignment '{s}'") _ => throw new InvalidOperationException($"Unknown vertical alignment '{s}'")
}) ?? TextVerticalAlignment.Top; }).GetValueOr(TextVerticalAlignment.Top);
// Elements // Elements
var text = _reader.ReadElementContentAsString("text", ""); var text = _reader.ReadElementContentAsString("text", "");

View file

@ -49,7 +49,7 @@ public abstract partial class TmxReaderBase
OffsetY = offsetY, OffsetY = offsetY,
ParallaxX = parallaxX, ParallaxX = parallaxX,
ParallaxY = parallaxY, ParallaxY = parallaxY,
Data = data ?? Optional<Data>.Empty, Data = data is null ? Optional.Empty : new Optional<Data>(data),
Properties = properties ?? [] Properties = properties ?? []
}; };
} }

View file

@ -8,8 +8,8 @@ namespace DotTiled.Serialization.Tmx;
public abstract partial class TmxReaderBase public abstract partial class TmxReaderBase
{ {
internal Tileset ReadTileset( internal Tileset ReadTileset(
Optional<string> parentVersion = null, Optional<string> parentVersion = default,
Optional<string> parentTiledVersion = null) Optional<string> parentTiledVersion = default)
{ {
var firstGID = _reader.GetOptionalAttributeParseable<uint>("firstgid"); var firstGID = _reader.GetOptionalAttributeParseable<uint>("firstgid");
var source = _reader.GetOptionalAttribute("source"); var source = _reader.GetOptionalAttribute("source");
@ -18,7 +18,7 @@ public abstract partial class TmxReaderBase
if (source.HasValue && firstGID.HasValue) if (source.HasValue && firstGID.HasValue)
{ {
// Is external tileset // Is external tileset
var externalTileset = _externalTilesetResolver(source); var externalTileset = _externalTilesetResolver(source.Value);
externalTileset.FirstGID = firstGID; externalTileset.FirstGID = firstGID;
externalTileset.Source = source; externalTileset.Source = source;
@ -136,7 +136,7 @@ public abstract partial class TmxReaderBase
}); });
if (!format.HasValue && source.HasValue) if (!format.HasValue && source.HasValue)
format = Helpers.ParseImageFormatFromSource(source); format = Helpers.ParseImageFormatFromSource(source.Value);
return new Image return new Image
{ {
@ -225,11 +225,11 @@ public abstract partial class TmxReaderBase
Probability = probability, Probability = probability,
X = x, X = x,
Y = y, Y = y,
Width = width.HasValue ? width : image?.Width ?? 0, Width = width.HasValue ? width.Value : image?.Width.GetValueOr(0) ?? 0,
Height = height.HasValue ? height : image?.Height ?? 0, Height = height.HasValue ? height.Value : image?.Height.GetValueOr(0) ?? 0,
Properties = properties ?? [], Properties = properties ?? [],
Image = image is null ? Optional<Image>.Empty : image, Image = image is null ? Optional.Empty : image,
ObjectLayer = objectLayer is null ? Optional<ObjectLayer>.Empty : objectLayer, ObjectLayer = objectLayer is null ? Optional.Empty : objectLayer,
Animation = animation ?? [] Animation = animation ?? []
}; };
} }

View file

@ -8,7 +8,7 @@ public class Template
/// <summary> /// <summary>
/// If the template represents a tile object, this property will contain the tileset that the tile belongs to. /// If the template represents a tile object, this property will contain the tileset that the tile belongs to.
/// </summary> /// </summary>
public Optional<Tileset> Tileset { get; set; } = Optional<Tileset>.Empty; public Optional<Tileset> Tileset { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// The object that this template represents. /// The object that this template represents.

View file

@ -34,25 +34,25 @@ public class Image
/// <summary> /// <summary>
/// The format of the image. /// The format of the image.
/// </summary> /// </summary>
public Optional<ImageFormat> Format { get; set; } = Optional<ImageFormat>.Empty; public Optional<ImageFormat> Format { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// The reference to the image file. /// The reference to the image file.
/// </summary> /// </summary>
public Optional<string> Source { get; set; } = Optional<string>.Empty; public Optional<string> Source { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// Defines a specific color that is treated as transparent. /// Defines a specific color that is treated as transparent.
/// </summary> /// </summary>
public Optional<Color> TransparentColor { get; set; } = Optional<Color>.Empty; public Optional<Color> TransparentColor { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// The image width in pixels, used for tile index correction when the image changes. /// The image width in pixels, used for tile index correction when the image changes.
/// </summary> /// </summary>
public Optional<uint> Width { get; set; } = Optional<uint>.Empty; public Optional<uint> Width { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// The image height in pixels, used for tile index correction when the image changes. /// The image height in pixels, used for tile index correction when the image changes.
/// </summary> /// </summary>
public Optional<uint> Height { get; set; } = Optional<uint>.Empty; public Optional<uint> Height { get; set; } = Optional.Empty;
} }

View file

@ -54,12 +54,12 @@ public class Tile : HasPropertiesBase
/// <summary> /// <summary>
/// The image representing this tile. Only used for tilesets that composed of a collection of images. /// The image representing this tile. Only used for tilesets that composed of a collection of images.
/// </summary> /// </summary>
public Optional<Image> Image { get; set; } = Optional<Image>.Empty; public Optional<Image> Image { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// Used when the tile contains e.g. collision information. /// Used when the tile contains e.g. collision information.
/// </summary> /// </summary>
public Optional<ObjectLayer> ObjectLayer { get; set; } = Optional<ObjectLayer>.Empty; public Optional<ObjectLayer> ObjectLayer { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// The animation frames for this tile. /// The animation frames for this tile.

View file

@ -103,17 +103,17 @@ public class Tileset : HasPropertiesBase
/// <summary> /// <summary>
/// The Tiled version used to save the file in case it was loaded from an external tileset file. /// The Tiled version used to save the file in case it was loaded from an external tileset file.
/// </summary> /// </summary>
public Optional<string> TiledVersion { get; set; } = Optional<string>.Empty; public Optional<string> TiledVersion { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// The first global tile ID of this tileset (this global ID maps to the first tile in this tileset). /// The first global tile ID of this tileset (this global ID maps to the first tile in this tileset).
/// </summary> /// </summary>
public Optional<uint> FirstGID { get; set; } = Optional<uint>.Empty; public Optional<uint> FirstGID { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// If this tileset is stored in an external TSX (Tile Set XML) file, this attribute refers to that file. /// If this tileset is stored in an external TSX (Tile Set XML) file, this attribute refers to that file.
/// </summary> /// </summary>
public Optional<string> Source { get; set; } = Optional<string>.Empty; public Optional<string> Source { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// The name of this tileset. /// The name of this tileset.
@ -173,17 +173,17 @@ public class Tileset : HasPropertiesBase
/// <summary> /// <summary>
/// If the tileset is based on a single image, which is cut into tiles based on the given attributes of the tileset, then this is that image. /// If the tileset is based on a single image, which is cut into tiles based on the given attributes of the tileset, then this is that image.
/// </summary> /// </summary>
public Optional<Image> Image { get; set; } = Optional<Image>.Empty; public Optional<Image> Image { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// This is used to specify an offset in pixels, to be applied when drawing a tile from the related tileset. When not present, no offset is applied. /// This is used to specify an offset in pixels, to be applied when drawing a tile from the related tileset. When not present, no offset is applied.
/// </summary> /// </summary>
public Optional<TileOffset> TileOffset { get; set; } = Optional<TileOffset>.Empty; public Optional<TileOffset> TileOffset { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// Ths is only used in case of isometric orientation, and determines how tile overlays for terrain and collision information are rendered. /// Ths is only used in case of isometric orientation, and determines how tile overlays for terrain and collision information are rendered.
/// </summary> /// </summary>
public Optional<Grid> Grid { get; set; } = Optional<Grid>.Empty; public Optional<Grid> Grid { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// Tileset properties. /// Tileset properties.
@ -203,7 +203,7 @@ public class Tileset : HasPropertiesBase
/// <summary> /// <summary>
/// Used to describe which transformations can be applied to the tiles (e.g. to extend a Wang set by transforming existing tiles). /// Used to describe which transformations can be applied to the tiles (e.g. to extend a Wang set by transforming existing tiles).
/// </summary> /// </summary>
public Optional<Transformations> Transformations { get; set; } = Optional<Transformations>.Empty; public Optional<Transformations> Transformations { get; set; } = Optional.Empty;
/// <summary> /// <summary>
/// If this tileset is based on a collection of images, then this list of tiles will contain the individual images that make up the tileset. /// If this tileset is based on a collection of images, then this list of tiles will contain the individual images that make up the tileset.