Syroot.Worms.Armageddon 4.0.1: Optimize WA v3 schemes to store only non-default extended options.

- Adds SchemeVersionExtensions.GetWeaponCount(), returning how many weapons are stored by the corresponding SchemeVersion.
- Adds ExtendedOptions.AsSpan() to return the unmanaged representation of the extended settings.
- Optimize usage of .NET Standard 2.0 Span backports.
This commit is contained in:
Ray Koopa 2020-07-02 05:28:59 +02:00
parent 08703b6c12
commit 5b69f82a35
6 changed files with 48 additions and 38 deletions

View File

@ -72,6 +72,21 @@ namespace Syroot.Worms.Armageddon
Version3 = 3 Version3 = 3
} }
/// <summary>
/// Represents extension methods for <see cref="SchemeVersion"/> instances.
/// </summary>
public static class SchemeVersionExtensions
{
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <summary>
/// Returns how many <see cref="SchemeWeapon"/> instances are stored in the <paramref name="version"/>.
/// </summary>
/// <param name="version">The <see cref="SchemeVersion"/>.</param>
/// <returns>The number of weapons stored for this version.</returns>
public static int GetWeaponCount(this SchemeVersion version) => version == SchemeVersion.Version1 ? 45 : 64;
}
/// <summary> /// <summary>
/// Represents the gravity simulation used in a game (as originating from RubberWorm). /// Represents the gravity simulation used in a game (as originating from RubberWorm).
/// </summary> /// </summary>

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Syroot.Worms.Armageddon namespace Syroot.Worms.Armageddon
@ -1102,7 +1103,14 @@ namespace Syroot.Worms.Armageddon
return hash.ToHashCode(); return hash.ToHashCode();
} }
private float FixedPointToSingle(int value) => value / 65536f; /// <summary>
/// Returns this instance as raw data.
/// </summary>
/// <returns>A <see cref="Span{Byte}"/> storing the raw data of the options.</returns>
internal unsafe Span<byte> AsSpan()
=> new Span<byte>(Unsafe.AsPointer(ref this), Unsafe.SizeOf<ExtendedOptions>());
private static float FixedPointToSingle(int value) => value / 65536f;
private static byte NullBoolToTriState(bool? value) => value switch private static byte NullBoolToTriState(bool? value) => value switch
{ {
@ -1117,7 +1125,7 @@ namespace Syroot.Worms.Armageddon
_ => value.Value _ => value.Value
}; };
private int SingleToFixedPoint(float value) => (int)(value * 65536); private static int SingleToFixedPoint(float value) => (int)(value * 65536);
private static bool? TriStateToNullBool(byte value) => value switch private static bool? TriStateToNullBool(byte value) => value switch
{ {

View File

@ -3,7 +3,6 @@ using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using Syroot.BinaryData; using Syroot.BinaryData;
using Syroot.Worms.Core; using Syroot.Worms.Core;
@ -1067,7 +1066,7 @@ namespace Syroot.Worms.Armageddon
// Read the weapons. // Read the weapons.
Weapons = new WeaponList(); Weapons = new WeaponList();
reader.ReadStructs(Weapons.AsSpan(GetWeaponCount(Version))); reader.ReadStructs(Weapons.AsSpan(Version.GetWeaponCount()));
// Read available extended settings or deserialize them from RubberWorm settings encoded in probabilities. // Read available extended settings or deserialize them from RubberWorm settings encoded in probabilities.
switch (Version) switch (Version)
@ -1201,13 +1200,13 @@ namespace Syroot.Worms.Armageddon
SaveRubberWorm(); SaveRubberWorm();
// Write the weapons. // Write the weapons.
writer.WriteStructs<SchemeWeapon>(Weapons.AsSpan(GetWeaponCount(version))); writer.WriteStructs<SchemeWeapon>(Weapons.AsSpan(version.GetWeaponCount()));
// Clear RubberWorm probabilities again or write available extended settings. // Clear RubberWorm probabilities again or write available extended settings.
switch (version) switch (version)
{ {
case SchemeVersion.Version2: ClearRubberWorm(); break; case SchemeVersion.Version2: ClearRubberWorm(); break;
case SchemeVersion.Version3: SaveExtendedOptions(writer); break; case SchemeVersion.Version3: SaveExtendedOptions(writer, Attachment.Length == 0); break;
} }
// Write any trailing bytes (either unknown extended scheme options or unknown WWP trash). // Write any trailing bytes (either unknown extended scheme options or unknown WWP trash).
@ -1216,8 +1215,6 @@ namespace Syroot.Worms.Armageddon
// ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- // ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private static int GetWeaponCount(SchemeVersion version) => version == SchemeVersion.Version1 ? 45 : 64;
private void ClearRubberWorm() private void ClearRubberWorm()
{ {
// Reset all super weapon probabilities to remove any settings made by RubberWorm. // Reset all super weapon probabilities to remove any settings made by RubberWorm.
@ -1229,23 +1226,10 @@ namespace Syroot.Worms.Armageddon
private unsafe void LoadExtendedOptions(BinaryStream reader) private unsafe void LoadExtendedOptions(BinaryStream reader)
{ {
if (reader.EndOfStream) // Create a copy of default options overwritten by available extended data.
{ Span<byte> bytes = _extended.AsSpan();
ClearExtendedOptions(); ExtendedOptions.Default.AsSpan().CopyTo(bytes);
}
else
{
Span<byte> bytes = new Span<byte>(
Unsafe.AsPointer(ref _extended), Unsafe.SizeOf<ExtendedOptions>());
#if NETSTANDARD2_0
// Cannot prevent copy in .NET Standard 2.0.
byte[] buffer = new byte[(int)Math.Min(bytes.Length, reader.Length - reader.Position)];
reader.Read(buffer, 0, buffer.Length);
buffer.AsSpan().CopyTo(bytes);
#else
reader.Read(bytes); reader.Read(bytes);
#endif
}
} }
private void LoadRubberWorm() private void LoadRubberWorm()
@ -1362,16 +1346,19 @@ namespace Syroot.Worms.Armageddon
ClearRubberWorm(); ClearRubberWorm();
} }
private unsafe void SaveExtendedOptions(BinaryStream writer) private unsafe void SaveExtendedOptions(BinaryStream writer, bool trim)
{ {
ReadOnlySpan<byte> bytes = new ReadOnlySpan<byte>( ReadOnlySpan<byte> bytes = _extended.AsSpan();
Unsafe.AsPointer(ref _extended), Unsafe.SizeOf<ExtendedOptions>());
#if NETSTANDARD2_0 // Trim away default bytes.
// Cannot prevent copy in .NET Standard 2.0. int length = bytes.Length;
writer.Write(bytes.ToArray()); if (trim)
#else {
writer.Write(bytes); ReadOnlySpan<byte> defaultBytes = ExtendedOptions.Default.AsSpan();
#endif for (; length > 0 && bytes[length - 1] == defaultBytes[length - 1]; length--) ;
}
writer.Write(bytes.Slice(0, length));
} }
private void SaveRubberWorm() private void SaveRubberWorm()

View File

@ -7,7 +7,7 @@
<Description>.NET library for loading and modifying files of Team17's Worms Armageddon.</Description> <Description>.NET library for loading and modifying files of Team17's Worms Armageddon.</Description>
<PackageReleaseNotes>Overhaul implementation and documentation. Implement W:A V3 scheme format.</PackageReleaseNotes> <PackageReleaseNotes>Overhaul implementation and documentation. Implement W:A V3 scheme format.</PackageReleaseNotes>
<PackageTags>$(PackageTags);worms armageddon</PackageTags> <PackageTags>$(PackageTags);worms armageddon</PackageTags>
<Version>4.0.0</Version> <Version>4.0.1</Version>
</PropertyGroup> </PropertyGroup>
<!-- References --> <!-- References -->

View File

@ -84,8 +84,8 @@ namespace Syroot.Worms.IO
/// <param name="value">The instance to write into the current stream.</param> /// <param name="value">The instance to write into the current stream.</param>
public static void Save<T>(this Stream stream, T value) where T : ISaveable => value.Save(stream); public static void Save<T>(this Stream stream, T value) where T : ISaveable => value.Save(stream);
// ---- Backports ----
#if NETSTANDARD2_0 #if NETSTANDARD2_0
// ---- Backports ----
/// <summary> /// <summary>
/// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the /// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the

View File

@ -19,13 +19,13 @@ namespace Syroot.Worms.Test.Armageddon
/// Tests all files found in the test directory. /// Tests all files found in the test directory.
/// </summary> /// </summary>
[TestMethod] [TestMethod]
public void TestFiles() => Tools.TestFiles<Scheme>("*.wsc"); public void TestSchemes() => Tools.TestFiles<Scheme>("*.wsc");
/// <summary> /// <summary>
/// Tests all schemes from a Worms Scheme Database CSV dump (ask owner Byte for updated dumps). /// Tests all schemes from a Worms Scheme Database CSV dump (ask owner Byte for updated dumps).
/// </summary> /// </summary>
[TestMethod] [TestMethod]
public void TestWsdb() public void TestSchemesWsdb()
{ {
// Reuse buffer for all schemes, including the SCHM header stripped in the dump. // Reuse buffer for all schemes, including the SCHM header stripped in the dump.
byte[] buffer = new byte[407]; byte[] buffer = new byte[407];