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
}
/// <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>
/// Represents the gravity simulation used in a game (as originating from RubberWorm).
/// </summary>

View File

@ -1,4 +1,5 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Syroot.Worms.Armageddon
@ -1102,7 +1103,14 @@ namespace Syroot.Worms.Armageddon
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
{
@ -1117,7 +1125,7 @@ namespace Syroot.Worms.Armageddon
_ => 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
{

View File

@ -3,7 +3,6 @@ using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core;
@ -1067,7 +1066,7 @@ namespace Syroot.Worms.Armageddon
// Read the weapons.
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.
switch (Version)
@ -1201,13 +1200,13 @@ namespace Syroot.Worms.Armageddon
SaveRubberWorm();
// 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.
switch (version)
{
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).
@ -1216,8 +1215,6 @@ namespace Syroot.Worms.Armageddon
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private static int GetWeaponCount(SchemeVersion version) => version == SchemeVersion.Version1 ? 45 : 64;
private void ClearRubberWorm()
{
// 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)
{
if (reader.EndOfStream)
{
ClearExtendedOptions();
}
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);
#endif
}
// Create a copy of default options overwritten by available extended data.
Span<byte> bytes = _extended.AsSpan();
ExtendedOptions.Default.AsSpan().CopyTo(bytes);
reader.Read(bytes);
}
private void LoadRubberWorm()
@ -1362,16 +1346,19 @@ namespace Syroot.Worms.Armageddon
ClearRubberWorm();
}
private unsafe void SaveExtendedOptions(BinaryStream writer)
private unsafe void SaveExtendedOptions(BinaryStream writer, bool trim)
{
ReadOnlySpan<byte> bytes = new ReadOnlySpan<byte>(
Unsafe.AsPointer(ref _extended), Unsafe.SizeOf<ExtendedOptions>());
#if NETSTANDARD2_0
// Cannot prevent copy in .NET Standard 2.0.
writer.Write(bytes.ToArray());
#else
writer.Write(bytes);
#endif
ReadOnlySpan<byte> bytes = _extended.AsSpan();
// Trim away default bytes.
int length = bytes.Length;
if (trim)
{
ReadOnlySpan<byte> defaultBytes = ExtendedOptions.Default.AsSpan();
for (; length > 0 && bytes[length - 1] == defaultBytes[length - 1]; length--) ;
}
writer.Write(bytes.Slice(0, length));
}
private void SaveRubberWorm()

View File

@ -7,7 +7,7 @@
<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>
<PackageTags>$(PackageTags);worms armageddon</PackageTags>
<Version>4.0.0</Version>
<Version>4.0.1</Version>
</PropertyGroup>
<!-- References -->

View File

@ -84,8 +84,8 @@ namespace Syroot.Worms.IO
/// <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);
// ---- Backports ----
#if NETSTANDARD2_0
// ---- Backports ----
/// <summary>
/// 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.
/// </summary>
[TestMethod]
public void TestFiles() => Tools.TestFiles<Scheme>("*.wsc");
public void TestSchemes() => Tools.TestFiles<Scheme>("*.wsc");
/// <summary>
/// Tests all schemes from a Worms Scheme Database CSV dump (ask owner Byte for updated dumps).
/// </summary>
[TestMethod]
public void TestWsdb()
public void TestSchemesWsdb()
{
// Reuse buffer for all schemes, including the SCHM header stripped in the dump.
byte[] buffer = new byte[407];