mirror of
https://github.com/dcronqvist/DotTiled.git
synced 2025-05-09 06:36:02 +03:00
Make Optional<T> into readonly struct to avoid heap allocations
This commit is contained in:
parent
21470cb056
commit
9aa2c63e0b
35 changed files with 176 additions and 203 deletions
|
@ -79,6 +79,6 @@ public class Program
|
|||
[
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,10 +42,10 @@ public partial class MapParser : Node2D
|
|||
|
||||
private Tileset ResolveTileset(string source)
|
||||
{
|
||||
string tilesetString = FileAccess.Open($"res://{source}", FileAccess.ModeFlags.Read).GetAsText();
|
||||
using TilesetReader tilesetReader =
|
||||
new TilesetReader(tilesetString, ResolveTileset, ResolveTemplate, ResolveCustomType);
|
||||
return tilesetReader.ReadTileset();
|
||||
string tilesetString = FileAccess.Open($"res://{source}", FileAccess.ModeFlags.Read).GetAsText();
|
||||
using TilesetReader tilesetReader =
|
||||
new TilesetReader(tilesetString, ResolveTileset, ResolveTemplate, ResolveCustomType);
|
||||
return tilesetReader.ReadTileset();
|
||||
}
|
||||
|
||||
private Template ResolveTemplate(string source)
|
||||
|
@ -62,6 +62,6 @@ public partial class MapParser : Node2D
|
|||
[
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,8 +35,7 @@ public static partial class DotTiledAssert
|
|||
AssertEqual(expected.X, actual.X, nameof(TileLayer.X));
|
||||
AssertEqual(expected.Y, actual.Y, nameof(TileLayer.Y));
|
||||
|
||||
Assert.NotNull(actual.Data);
|
||||
AssertData(expected.Data, actual.Data);
|
||||
AssertOptionalsEqual(expected.Data, actual.Data, nameof(TileLayer.Data), AssertData);
|
||||
}
|
||||
|
||||
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.Y, actual.Y, nameof(ImageLayer.Y));
|
||||
|
||||
Assert.NotNull(actual.Image);
|
||||
AssertImage(expected.Image, actual.Image);
|
||||
AssertOptionalsEqual(expected.Image, actual.Image, nameof(ImageLayer.Image), AssertImage);
|
||||
}
|
||||
|
||||
private static void AssertLayer(Group expected, Group actual)
|
||||
|
|
|
@ -33,12 +33,6 @@ public static partial class DotTiledAssert
|
|||
string nameof,
|
||||
Action<T, T> assertEqual)
|
||||
{
|
||||
if (expected is null)
|
||||
{
|
||||
Assert.Null(actual);
|
||||
return;
|
||||
}
|
||||
|
||||
if (expected.HasValue)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (expected is null)
|
||||
{
|
||||
Assert.Null(actual);
|
||||
return;
|
||||
}
|
||||
|
||||
if (expected.HasValue)
|
||||
{
|
||||
Assert.True(actual.HasValue, $"Expected {nameof} to have a value");
|
||||
|
|
|
@ -58,7 +58,7 @@ public partial class TestData
|
|||
new IntProperty { Name = "intprop", Value = 8 },
|
||||
new ObjectProperty { Name = "objectprop", Value = 5 },
|
||||
new StringProperty { Name = "stringprop", Value = "This is a string, hello world!" },
|
||||
new ColorProperty { Name = "unsetcolorprop", Value = Optional<TiledColor>.Empty }
|
||||
new ColorProperty { Name = "unsetcolorprop", Value = Optional.Empty }
|
||||
]
|
||||
};
|
||||
}
|
||||
|
|
|
@ -59,30 +59,6 @@ public class OptionalTests
|
|||
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
|
||||
|
||||
[Fact]
|
||||
|
@ -237,18 +213,6 @@ public class OptionalTests
|
|||
_ = 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]
|
||||
public void EmptyOptionalEquality_ShouldReturnTrue()
|
||||
{
|
||||
|
|
|
@ -71,8 +71,8 @@ public class LoaderTests
|
|||
""");
|
||||
|
||||
var resourceCache = Substitute.For<IResourceCache>();
|
||||
resourceCache.GetTileset(Arg.Any<string>()).Returns(Optional<Tileset>.Empty);
|
||||
resourceCache.GetTemplate(Arg.Any<string>()).Returns(Optional<Template>.Empty);
|
||||
resourceCache.GetTileset(Arg.Any<string>()).Returns(Optional.Empty);
|
||||
resourceCache.GetTemplate(Arg.Any<string>()).Returns(Optional.Empty);
|
||||
|
||||
var customTypeDefinitions = Enumerable.Empty<ICustomTypeDefinition>();
|
||||
var loader = new Loader(resourceReader, resourceCache, customTypeDefinitions);
|
||||
|
@ -134,8 +134,8 @@ public class LoaderTests
|
|||
""");
|
||||
|
||||
var resourceCache = Substitute.For<IResourceCache>();
|
||||
resourceCache.GetTileset(Arg.Any<string>()).Returns(Optional<Tileset>.Empty);
|
||||
resourceCache.GetTemplate(Arg.Any<string>()).Returns(Optional<Template>.Empty);
|
||||
resourceCache.GetTileset(Arg.Any<string>()).Returns(Optional.Empty);
|
||||
resourceCache.GetTemplate(Arg.Any<string>()).Returns(Optional.Empty);
|
||||
|
||||
var customTypeDefinitions = Enumerable.Empty<ICustomTypeDefinition>();
|
||||
var loader = new Loader(resourceReader, resourceCache, customTypeDefinitions);
|
||||
|
@ -219,7 +219,7 @@ public class LoaderTests
|
|||
""");
|
||||
|
||||
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
|
||||
{
|
||||
Object = new PolygonObject
|
||||
|
|
|
@ -39,7 +39,7 @@ public partial class MapReaderTests
|
|||
return new Optional<ICustomTypeDefinition>(ctd);
|
||||
}
|
||||
|
||||
return Optional<ICustomTypeDefinition>.Empty;
|
||||
return Optional.Empty;
|
||||
}
|
||||
using var mapReader = new MapReader(mapString, ResolveTileset, ResolveTemplate, ResolveCustomType);
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ public partial class TmjMapReaderTests
|
|||
return new Optional<ICustomTypeDefinition>(ctd);
|
||||
}
|
||||
|
||||
return Optional<ICustomTypeDefinition>.Empty;
|
||||
return Optional.Empty;
|
||||
}
|
||||
using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate, ResolveCustomType);
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ public partial class TmxMapReaderTests
|
|||
return new Optional<ICustomTypeDefinition>(ctd);
|
||||
}
|
||||
|
||||
return Optional<ICustomTypeDefinition>.Empty;
|
||||
return Optional.Empty;
|
||||
}
|
||||
using var mapReader = new TmxMapReader(reader, ResolveTileset, ResolveTemplate, ResolveCustomType);
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ public abstract class BaseLayer : HasPropertiesBase
|
|||
/// <summary>
|
||||
/// A tint color that is multiplied with any tiles drawn by this layer.
|
||||
/// </summary>
|
||||
public Optional<TiledColor> TintColor { get; set; } = Optional<TiledColor>.Empty;
|
||||
public Optional<TiledColor> TintColor { get; set; } = Optional.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Horizontal offset for this layer in pixels.
|
||||
|
|
|
@ -118,27 +118,27 @@ public class Data
|
|||
/// <summary>
|
||||
/// The encoding used to encode the tile layer data.
|
||||
/// </summary>
|
||||
public Optional<DataEncoding> Encoding { get; set; } = Optional<DataEncoding>.Empty;
|
||||
public Optional<DataEncoding> Encoding { get; set; } = Optional.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The compression method used to compress the tile layer data.
|
||||
/// </summary>
|
||||
public Optional<DataCompression> Compression { get; set; } = Optional<DataCompression>.Empty;
|
||||
public Optional<DataCompression> Compression { get; set; } = Optional.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// <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>
|
||||
public Optional<uint[]> GlobalTileIDs { get; set; } = Optional<uint[]>.Empty;
|
||||
public Optional<uint[]> GlobalTileIDs { get; set; } = Optional.Empty;
|
||||
|
||||
/// <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"/>.
|
||||
/// </summary>
|
||||
public Optional<FlippingFlags[]> FlippingFlags { get; set; } = Optional<FlippingFlags[]>.Empty;
|
||||
public Optional<FlippingFlags[]> FlippingFlags { get; set; } = Optional.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// If the map is infinite, it will instead contain a list of chunks.
|
||||
/// </summary>
|
||||
public Optional<Chunk[]> Chunks { get; set; } = Optional<Chunk[]>.Empty;
|
||||
public Optional<Chunk[]> Chunks { get; set; } = Optional.Empty;
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ public class ObjectLayer : BaseLayer
|
|||
/// <summary>
|
||||
/// A color that is multiplied with any tile objects drawn by this layer.
|
||||
/// </summary>
|
||||
public Optional<TiledColor> Color { get; set; } = Optional<TiledColor>.Empty;
|
||||
public Optional<TiledColor> Color { get; set; } = Optional.Empty;
|
||||
|
||||
/// <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"/>).
|
||||
|
|
|
@ -10,7 +10,7 @@ public abstract class Object : HasPropertiesBase
|
|||
/// <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.
|
||||
/// </summary>
|
||||
public Optional<uint> ID { get; set; } = Optional<uint>.Empty;
|
||||
public Optional<uint> ID { get; set; } = Optional.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the object. An arbitrary string.
|
||||
|
@ -55,7 +55,7 @@ public abstract class Object : HasPropertiesBase
|
|||
/// <summary>
|
||||
/// A reference to a template file.
|
||||
/// </summary>
|
||||
public Optional<string> Template { get; set; } = Optional<string>.Empty;
|
||||
public Optional<string> Template { get; set; } = Optional.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Object properties.
|
||||
|
|
|
@ -30,7 +30,7 @@ public class TileLayer : BaseLayer
|
|||
/// <summary>
|
||||
/// The tile layer data.
|
||||
/// </summary>
|
||||
public Optional<Data> Data { get; set; } = Optional<Data>.Empty;
|
||||
public Optional<Data> Data { get; set; } = Optional.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to retrieve the Global Tile ID at a given coordinate in the layer.
|
||||
|
|
|
@ -101,7 +101,7 @@ public class Map : HasPropertiesBase
|
|||
/// <summary>
|
||||
/// The Tiled version used to save the file.
|
||||
/// </summary>
|
||||
public Optional<string> TiledVersion { get; set; } = Optional<string>.Empty;
|
||||
public Optional<string> TiledVersion { get; set; } = Optional.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The class of this map.
|
||||
|
@ -147,17 +147,17 @@ public class Map : HasPropertiesBase
|
|||
/// <summary>
|
||||
/// Only for hexagonal maps. Determines the width or height (depending on the staggered axis) of the tile's edge, in pixels.
|
||||
/// </summary>
|
||||
public Optional<int> HexSideLength { get; set; } = Optional<int>.Empty;
|
||||
public Optional<int> HexSideLength { get; set; } = Optional.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// For staggered and hexagonal maps, determines which axis (X or Y) is staggered.
|
||||
/// </summary>
|
||||
public Optional<StaggerAxis> StaggerAxis { get; set; } = Optional<StaggerAxis>.Empty;
|
||||
public Optional<StaggerAxis> StaggerAxis { get; set; } = Optional.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// For staggered and hexagonal maps, determines whether the "even" or "odd" indexes along the staggered axis are shifted.
|
||||
/// </summary>
|
||||
public Optional<StaggerIndex> StaggerIndex { get; set; } = Optional<StaggerIndex>.Empty;
|
||||
public Optional<StaggerIndex> StaggerIndex { get; set; } = Optional.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// X coordinate of the parallax origin in pixels.
|
||||
|
|
|
@ -2,62 +2,94 @@ using System;
|
|||
|
||||
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>
|
||||
/// Represents a value that may or may not be present.
|
||||
/// </summary>
|
||||
/// <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;
|
||||
|
||||
/// <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>
|
||||
public bool HasValue { get; }
|
||||
/// <returns></returns>
|
||||
public bool HasValue => _hasValue;
|
||||
#pragma warning restore IDE0032 // Use auto property
|
||||
|
||||
/// <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>
|
||||
/// <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");
|
||||
|
||||
/// <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>
|
||||
/// <param name="value">The value to be set.</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>
|
||||
/// <param name="value"></param>
|
||||
public static implicit operator Optional<T>(T value)
|
||||
{
|
||||
if (value is null)
|
||||
return new();
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return new(value);
|
||||
return new Optional<T>(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="Optional{T}"/> object to a value.
|
||||
/// Creates a new object initialized to an empty value.
|
||||
/// </summary>
|
||||
/// <param name="optional">The <see cref="Optional{T}"/> object to be converted.</param>
|
||||
public static implicit operator T(Optional<T> optional)
|
||||
/// <param name="_"></param>
|
||||
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>
|
||||
|
@ -71,17 +103,6 @@ public class Optional<T>
|
|||
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>
|
||||
/// Returns the value of the current <see cref="Optional{T}"/> object if it has been set; otherwise, returns the specified default value.
|
||||
/// </summary>
|
||||
|
@ -96,9 +117,6 @@ public class Optional<T>
|
|||
/// <returns></returns>
|
||||
public Optional<T> GetValueOrOptional(Optional<T> defaultValue) => HasValue ? this : defaultValue;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => HasValue ? _value.ToString() : "Empty";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
|
@ -129,9 +147,13 @@ public class Optional<T>
|
|||
public override int GetHashCode() => HasValue ? _value!.GetHashCode() : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an empty <see cref="Optional{T}"/> object.
|
||||
/// Determines whether the specified <see cref="Optional{T}"/> objects are not equal.
|
||||
/// </summary>
|
||||
#pragma warning disable CA1000 // Do not declare static members on generic types
|
||||
public static Optional<T> Empty => new();
|
||||
#pragma warning restore CA1000 // Do not declare static members on generic types
|
||||
/// <param name="left"></param>
|
||||
/// <param name="right"></param>
|
||||
/// <returns></returns>
|
||||
public static bool operator !=(Optional<T> left, Optional<T> right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ public class DefaultResourceCache : IResourceCache
|
|||
if (_templates.TryGetValue(path, out var template))
|
||||
return new Optional<Template>(template);
|
||||
|
||||
return Optional<Template>.Empty;
|
||||
return Optional.Empty;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -25,7 +25,7 @@ public class DefaultResourceCache : IResourceCache
|
|||
if (_tilesets.TryGetValue(path, out var tileset))
|
||||
return new Optional<Tileset>(tileset);
|
||||
|
||||
return Optional<Tileset>.Empty;
|
||||
return Optional.Empty;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
|
@ -91,7 +91,7 @@ internal static partial class Helpers
|
|||
if (string.IsNullOrWhiteSpace(className))
|
||||
return null;
|
||||
|
||||
var customType = customTypeResolver(className) ?? throw new InvalidOperationException($"Could not resolve custom type '{className}'.");
|
||||
var customType = customTypeResolver(className);
|
||||
if (!customType.HasValue)
|
||||
return null;
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ public interface IResourceCache
|
|||
/// Retrieves a tileset from the cache with the given <paramref name="path"/>.
|
||||
/// </summary>
|
||||
/// <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);
|
||||
|
||||
/// <summary>
|
||||
|
@ -30,6 +30,6 @@ public interface IResourceCache
|
|||
/// Retrieves a template from the cache with the given <paramref name="path"/>.
|
||||
/// </summary>
|
||||
/// <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);
|
||||
}
|
||||
|
|
|
@ -119,6 +119,6 @@ public class Loader
|
|||
if (_customTypeDefinitions.TryGetValue(name, out var customTypeDefinition))
|
||||
return new Optional<ICustomTypeDefinition>(customTypeDefinition);
|
||||
|
||||
return Optional<ICustomTypeDefinition>.Empty;
|
||||
return Optional.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,10 @@ internal static class ExtensionsJsonElement
|
|||
internal static Optional<T> GetOptionalProperty<T>(this JsonElement element, string propertyName)
|
||||
{
|
||||
if (!element.TryGetProperty(propertyName, out var property))
|
||||
return Optional<T>.Empty;
|
||||
return Optional.Empty;
|
||||
|
||||
if (property.ValueKind == JsonValueKind.Null)
|
||||
return Optional<T>.Empty;
|
||||
return Optional.Empty;
|
||||
|
||||
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>
|
||||
{
|
||||
if (!element.TryGetProperty(propertyName, out var property))
|
||||
return Optional<T>.Empty;
|
||||
return Optional.Empty;
|
||||
|
||||
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))
|
||||
return Optional<T>.Empty;
|
||||
return Optional.Empty;
|
||||
|
||||
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)
|
||||
{
|
||||
if (!element.TryGetProperty(propertyName, out var property))
|
||||
return Optional<T>.Empty;
|
||||
return Optional.Empty;
|
||||
|
||||
return parser(property);
|
||||
}
|
||||
|
|
|
@ -32,8 +32,8 @@ public abstract partial class TmjReaderBase
|
|||
Y = y,
|
||||
Width = width,
|
||||
Height = height,
|
||||
GlobalTileIDs = data.GlobalTileIDs,
|
||||
FlippingFlags = data.FlippingFlags
|
||||
GlobalTileIDs = data.GlobalTileIDs.Value,
|
||||
FlippingFlags = data.FlippingFlags.Value
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ public abstract partial class TmjReaderBase
|
|||
|
||||
internal DotTiled.Object ReadObject(JsonElement element)
|
||||
{
|
||||
Optional<uint> idDefault = Optional<uint>.Empty;
|
||||
Optional<uint> idDefault = Optional.Empty;
|
||||
string nameDefault = "";
|
||||
string typeDefault = "";
|
||||
float xDefault = 0f;
|
||||
|
@ -80,7 +80,7 @@ public abstract partial class TmjReaderBase
|
|||
var template = element.GetOptionalProperty<string>("template");
|
||||
if (template.HasValue)
|
||||
{
|
||||
var resolvedTemplate = _externalTemplateResolver(template);
|
||||
var resolvedTemplate = _externalTemplateResolver(template.Value);
|
||||
var templObj = resolvedTemplate.Object;
|
||||
|
||||
idDefault = templObj.ID;
|
||||
|
@ -225,7 +225,7 @@ public abstract partial class TmjReaderBase
|
|||
text.Value.Visible = visible;
|
||||
text.Value.Template = template;
|
||||
text.Value.Properties = properties;
|
||||
return text;
|
||||
return text.Value;
|
||||
}
|
||||
|
||||
return new RectangleObject
|
||||
|
|
|
@ -16,7 +16,7 @@ public abstract partial class TmjReaderBase
|
|||
{
|
||||
"zlib" => DataCompression.ZLib,
|
||||
"gzip" => DataCompression.GZip,
|
||||
"" => Optional<DataCompression>.Empty,
|
||||
"" => Optional.Empty,
|
||||
_ => throw new JsonException($"Unsupported compression '{s}'.")
|
||||
});
|
||||
var chunks = element.GetOptionalPropertyCustom<Data>("chunks", e => ReadDataAsChunks(e, compression, encoding));
|
||||
|
@ -62,7 +62,7 @@ public abstract partial class TmjReaderBase
|
|||
Y = y,
|
||||
Width = width,
|
||||
Height = height,
|
||||
Data = data ?? chunks
|
||||
Data = data.GetValueOrOptional(chunks)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,25 +7,35 @@ public abstract partial class TmjReaderBase
|
|||
{
|
||||
internal Tileset ReadTileset(
|
||||
JsonElement element,
|
||||
Optional<string> parentVersion = null,
|
||||
Optional<string> parentTiledVersion = null)
|
||||
Optional<string> parentVersion = default,
|
||||
Optional<string> parentTiledVersion = default)
|
||||
{
|
||||
var source = element.GetOptionalProperty<string>("source");
|
||||
var firstGID = element.GetOptionalProperty<uint>("firstgid");
|
||||
|
||||
if (source.HasValue)
|
||||
{
|
||||
var resolvedTileset = _externalTilesetResolver(source.Value);
|
||||
resolvedTileset.FirstGID = firstGID;
|
||||
resolvedTileset.Source = source;
|
||||
return resolvedTileset;
|
||||
}
|
||||
|
||||
var backgroundColor = element.GetOptionalPropertyParseable<TiledColor>("backgroundcolor");
|
||||
var @class = element.GetOptionalProperty<string>("class").GetValueOr("");
|
||||
var columns = element.GetOptionalProperty<int>("columns");
|
||||
var columns = element.GetRequiredProperty<int>("columns");
|
||||
var fillMode = element.GetOptionalPropertyParseable<FillMode>("fillmode", s => s switch
|
||||
{
|
||||
"stretch" => FillMode.Stretch,
|
||||
"preserve-aspect-fit" => FillMode.PreserveAspectFit,
|
||||
_ => throw new JsonException($"Unknown fill mode '{s}'")
|
||||
}).GetValueOr(FillMode.Stretch);
|
||||
var firstGID = element.GetOptionalProperty<uint>("firstgid");
|
||||
var grid = element.GetOptionalPropertyCustom<Grid>("grid", ReadGrid);
|
||||
var image = element.GetOptionalProperty<string>("image");
|
||||
var imageHeight = element.GetOptionalProperty<int>("imageheight");
|
||||
var imageWidth = element.GetOptionalProperty<int>("imagewidth");
|
||||
var margin = element.GetOptionalProperty<int>("margin");
|
||||
var name = element.GetOptionalProperty<string>("name");
|
||||
var margin = element.GetRequiredProperty<int>("margin");
|
||||
var name = element.GetRequiredProperty<string>("name");
|
||||
var objectAlignment = element.GetOptionalPropertyParseable<ObjectAlignment>("objectalignment", s => s switch
|
||||
{
|
||||
"unspecified" => ObjectAlignment.Unspecified,
|
||||
|
@ -41,11 +51,10 @@ public abstract partial class TmjReaderBase
|
|||
_ => throw new JsonException($"Unknown object alignment '{s}'")
|
||||
}).GetValueOr(ObjectAlignment.Unspecified);
|
||||
var properties = ResolveAndMergeProperties(@class, element.GetOptionalPropertyCustom("properties", ReadProperties).GetValueOr([]));
|
||||
var source = element.GetOptionalProperty<string>("source");
|
||||
var spacing = element.GetOptionalProperty<int>("spacing");
|
||||
var tileCount = element.GetOptionalProperty<int>("tilecount");
|
||||
var spacing = element.GetRequiredProperty<int>("spacing");
|
||||
var tileCount = element.GetRequiredProperty<int>("tilecount");
|
||||
var tiledVersion = element.GetOptionalProperty<string>("tiledversion").GetValueOrOptional(parentTiledVersion);
|
||||
var tileHeight = element.GetOptionalProperty<int>("tileheight");
|
||||
var tileHeight = element.GetRequiredProperty<int>("tileheight");
|
||||
var tileOffset = element.GetOptionalPropertyCustom<TileOffset>("tileoffset", ReadTileOffset);
|
||||
var tileRenderSize = element.GetOptionalPropertyParseable<TileRenderSize>("tilerendersize", s => s switch
|
||||
{
|
||||
|
@ -54,28 +63,20 @@ public abstract partial class TmjReaderBase
|
|||
_ => throw new JsonException($"Unknown tile render size '{s}'")
|
||||
}).GetValueOr(TileRenderSize.Tile);
|
||||
var tiles = element.GetOptionalPropertyCustom<List<Tile>>("tiles", ReadTiles).GetValueOr([]);
|
||||
var tileWidth = element.GetOptionalProperty<int>("tilewidth");
|
||||
var tileWidth = element.GetRequiredProperty<int>("tilewidth");
|
||||
var transparentColor = element.GetOptionalPropertyParseable<TiledColor>("transparentcolor");
|
||||
var version = element.GetOptionalProperty<string>("version").GetValueOrOptional(parentVersion);
|
||||
var transformations = element.GetOptionalPropertyCustom<Transformations>("transformations", ReadTransformations);
|
||||
var wangsets = element.GetOptionalPropertyCustom<List<Wangset>>("wangsets", el => el.GetValueAsList<Wangset>(e => ReadWangset(e))).GetValueOr([]);
|
||||
|
||||
if (source.HasValue)
|
||||
{
|
||||
var resolvedTileset = _externalTilesetResolver(source);
|
||||
resolvedTileset.FirstGID = firstGID;
|
||||
resolvedTileset.Source = source;
|
||||
return resolvedTileset;
|
||||
}
|
||||
|
||||
Optional<Image> imageModel = image.HasValue ? new Image
|
||||
{
|
||||
Format = Helpers.ParseImageFormatFromSource(image),
|
||||
Format = Helpers.ParseImageFormatFromSource(image.Value),
|
||||
Source = image,
|
||||
Height = imageHeight,
|
||||
Width = imageWidth,
|
||||
TransparentColor = transparentColor
|
||||
} : Optional<Image>.Empty;
|
||||
} : Optional.Empty;
|
||||
|
||||
return new Tileset
|
||||
{
|
||||
|
@ -170,11 +171,11 @@ public abstract partial class TmjReaderBase
|
|||
|
||||
Optional<Image> imageModel = image.HasValue ? new Image
|
||||
{
|
||||
Format = Helpers.ParseImageFormatFromSource(image),
|
||||
Format = Helpers.ParseImageFormatFromSource(image.Value),
|
||||
Source = image,
|
||||
Height = imageHeight ?? 0,
|
||||
Width = imageWidth ?? 0
|
||||
} : Optional<Image>.Empty;
|
||||
Height = imageHeight.GetValueOr(0),
|
||||
Width = imageWidth.GetValueOr(0)
|
||||
} : Optional.Empty;
|
||||
|
||||
return new Tile
|
||||
{
|
||||
|
|
|
@ -64,10 +64,10 @@ public abstract partial class TmxReaderBase
|
|||
internal static uint[] ReadTileChildrenInWrapper(string wrapper, XmlReader reader) =>
|
||||
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();
|
||||
if (encoding == DataEncoding.Csv)
|
||||
if (encoding.Value == DataEncoding.Csv)
|
||||
return ParseCsvData(data);
|
||||
|
||||
using var bytes = new MemoryStream(Convert.FromBase64String(data));
|
||||
|
|
|
@ -74,7 +74,7 @@ public abstract partial class TmxReaderBase
|
|||
var template = _reader.GetOptionalAttribute("template");
|
||||
DotTiled.Object obj = null;
|
||||
if (template.HasValue)
|
||||
obj = _externalTemplateResolver(template).Object;
|
||||
obj = _externalTemplateResolver(template.Value).Object;
|
||||
|
||||
uint idDefault = obj?.ID.GetValueOr(0) ?? 0;
|
||||
string nameDefault = obj?.Name ?? "";
|
||||
|
@ -84,7 +84,7 @@ public abstract partial class TmxReaderBase
|
|||
float widthDefault = obj?.Width ?? 0f;
|
||||
float heightDefault = obj?.Height ?? 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;
|
||||
List<IProperty> propertiesDefault = obj?.Properties ?? null;
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ public abstract partial class TmxReaderBase
|
|||
OffsetY = offsetY,
|
||||
ParallaxX = parallaxX,
|
||||
ParallaxY = parallaxY,
|
||||
Data = data ?? Optional<Data>.Empty,
|
||||
Data = data is null ? Optional.Empty : new Optional<Data>(data),
|
||||
Properties = properties ?? []
|
||||
};
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ namespace DotTiled.Serialization.Tmx;
|
|||
public abstract partial class TmxReaderBase
|
||||
{
|
||||
internal Tileset ReadTileset(
|
||||
Optional<string> parentVersion = null,
|
||||
Optional<string> parentTiledVersion = null)
|
||||
Optional<string> parentVersion = default,
|
||||
Optional<string> parentTiledVersion = default)
|
||||
{
|
||||
var firstGID = _reader.GetOptionalAttributeParseable<uint>("firstgid");
|
||||
var source = _reader.GetOptionalAttribute("source");
|
||||
|
@ -18,7 +18,7 @@ public abstract partial class TmxReaderBase
|
|||
if (source.HasValue && firstGID.HasValue)
|
||||
{
|
||||
// Is external tileset
|
||||
var externalTileset = _externalTilesetResolver(source);
|
||||
var externalTileset = _externalTilesetResolver(source.Value);
|
||||
externalTileset.FirstGID = firstGID;
|
||||
externalTileset.Source = source;
|
||||
|
||||
|
@ -136,7 +136,7 @@ public abstract partial class TmxReaderBase
|
|||
});
|
||||
|
||||
if (!format.HasValue && source.HasValue)
|
||||
format = Helpers.ParseImageFormatFromSource(source);
|
||||
format = Helpers.ParseImageFormatFromSource(source.Value);
|
||||
|
||||
return new Image
|
||||
{
|
||||
|
@ -225,11 +225,11 @@ public abstract partial class TmxReaderBase
|
|||
Probability = probability,
|
||||
X = x,
|
||||
Y = y,
|
||||
Width = width.HasValue ? width : image?.Width ?? 0,
|
||||
Height = height.HasValue ? height : image?.Height ?? 0,
|
||||
Width = width.HasValue ? width.Value : image?.Width.GetValueOr(0) ?? 0,
|
||||
Height = height.HasValue ? height.Value : image?.Height.GetValueOr(0) ?? 0,
|
||||
Properties = properties ?? [],
|
||||
Image = image is null ? Optional<Image>.Empty : image,
|
||||
ObjectLayer = objectLayer is null ? Optional<ObjectLayer>.Empty : objectLayer,
|
||||
Image = image is null ? Optional.Empty : image,
|
||||
ObjectLayer = objectLayer is null ? Optional.Empty : objectLayer,
|
||||
Animation = animation ?? []
|
||||
};
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ public class Template
|
|||
/// <summary>
|
||||
/// If the template represents a tile object, this property will contain the tileset that the tile belongs to.
|
||||
/// </summary>
|
||||
public Optional<Tileset> Tileset { get; set; } = Optional<Tileset>.Empty;
|
||||
public Optional<Tileset> Tileset { get; set; } = Optional.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The object that this template represents.
|
||||
|
|
|
@ -34,25 +34,25 @@ public class Image
|
|||
/// <summary>
|
||||
/// The format of the image.
|
||||
/// </summary>
|
||||
public Optional<ImageFormat> Format { get; set; } = Optional<ImageFormat>.Empty;
|
||||
public Optional<ImageFormat> Format { get; set; } = Optional.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The reference to the image file.
|
||||
/// </summary>
|
||||
public Optional<string> Source { get; set; } = Optional<string>.Empty;
|
||||
public Optional<string> Source { get; set; } = Optional.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a specific color that is treated as transparent.
|
||||
/// </summary>
|
||||
public Optional<TiledColor> TransparentColor { get; set; } = Optional<TiledColor>.Empty;
|
||||
public Optional<TiledColor> TransparentColor { get; set; } = Optional.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The image width in pixels, used for tile index correction when the image changes.
|
||||
/// </summary>
|
||||
public Optional<int> Width { get; set; } = Optional<int>.Empty;
|
||||
public Optional<int> Width { get; set; } = Optional.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The image height in pixels, used for tile index correction when the image changes.
|
||||
/// </summary>
|
||||
public Optional<int> Height { get; set; } = Optional<int>.Empty;
|
||||
public Optional<int> Height { get; set; } = Optional.Empty;
|
||||
}
|
||||
|
|
|
@ -54,12 +54,12 @@ public class Tile : HasPropertiesBase
|
|||
/// <summary>
|
||||
/// The image representing this tile. Only used for tilesets that composed of a collection of images.
|
||||
/// </summary>
|
||||
public Optional<Image> Image { get; set; } = Optional<Image>.Empty;
|
||||
public Optional<Image> Image { get; set; } = Optional.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Used when the tile contains e.g. collision information.
|
||||
/// </summary>
|
||||
public Optional<ObjectLayer> ObjectLayer { get; set; } = Optional<ObjectLayer>.Empty;
|
||||
public Optional<ObjectLayer> ObjectLayer { get; set; } = Optional.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The animation frames for this tile.
|
||||
|
|
|
@ -131,17 +131,17 @@ public class Tileset : HasPropertiesBase
|
|||
/// <summary>
|
||||
/// The Tiled version used to save the file in case it was loaded from an external tileset file.
|
||||
/// </summary>
|
||||
public Optional<string> TiledVersion { get; set; } = Optional<string>.Empty;
|
||||
public Optional<string> TiledVersion { get; set; } = Optional.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The first global tile ID of this tileset (this global ID maps to the first tile in this tileset).
|
||||
/// </summary>
|
||||
public Optional<uint> FirstGID { get; set; } = Optional<uint>.Empty;
|
||||
public Optional<uint> FirstGID { get; set; } = Optional.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// If this tileset is stored in an external TSX (Tile Set XML) file, this attribute refers to that file.
|
||||
/// </summary>
|
||||
public Optional<string> Source { get; set; } = Optional<string>.Empty;
|
||||
public Optional<string> Source { get; set; } = Optional.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The name of this tileset.
|
||||
|
@ -201,17 +201,17 @@ public class Tileset : HasPropertiesBase
|
|||
/// <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.
|
||||
/// </summary>
|
||||
public Optional<Image> Image { get; set; } = Optional<Image>.Empty;
|
||||
public Optional<Image> Image { get; set; } = Optional.Empty;
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
public Optional<TileOffset> TileOffset { get; set; } = Optional<TileOffset>.Empty;
|
||||
public Optional<TileOffset> TileOffset { get; set; } = Optional.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Ths is only used in case of isometric orientation, and determines how tile overlays for terrain and collision information are rendered.
|
||||
/// </summary>
|
||||
public Optional<Grid> Grid { get; set; } = Optional<Grid>.Empty;
|
||||
public Optional<Grid> Grid { get; set; } = Optional.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Tileset properties.
|
||||
|
@ -231,7 +231,7 @@ public class Tileset : HasPropertiesBase
|
|||
/// <summary>
|
||||
/// Used to describe which transformations can be applied to the tiles (e.g. to extend a Wang set by transforming existing tiles).
|
||||
/// </summary>
|
||||
public Optional<Transformations> Transformations { get; set; } = Optional<Transformations>.Empty;
|
||||
public Optional<Transformations> Transformations { get; set; } = Optional.Empty;
|
||||
|
||||
/// <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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue