Target a couple of hot paths:
- In Actor, cache the renderables enumerable by tracking the last used WorldRenderer. It's slightly uncouth but in practise the WorldRenderer will be the same for the lifetime of the Actor and outlive it anyway.
- In RenderSprites, cache the renderables enumerable. If we refresh the palettes directly, we don't need the WorldRenderer for anything else so can return a cached enumerable. This could result in iterating the animation list twice, so use a flag to only do it when required, which is rare.
- In AutoTarget, the compiler has an unfortunate behaviour where it allocates the helper class for a capturing lambda at the top of the loop, but it's on a rare path and so this allocation is usually unused. We can discourage the compiler from allocating until it's actually needed by wrapping the call in a local function.
Using OrderBy provides a stable sort, but internally allocates a buffer every time for storing the sort keys. We'd like to avoid this allocation, but we also can't directly use Array.Sort/Span.Sort as these are unstable sorts.
By calculating sort keys with the item index embedded, we can ensure a stable sorting result whilst being able to reuse the buffer for the sort keys across future calls.
Since these will live for the lifetime of the process, it is worth freezing them on creation to improve lookup performance. If the dictionaries were iterated, change to an immutable array instead to preserve the order.
As config is often meant to be loaded and then never modified, it is helpful to support collections that are read-only after creation. This allows various classes that load config from YAML and elsewhere to deserialize straight into read-only collections and enforce invariants around data mutation.
- Improve the AudReader and WavReader by performing fewer, larger stream reads to consume bytes more efficiently.
- Improve ImaAdpcmReader.LoadImaAdpcmSound by accepting an output buffer rather than allocating a new array.
- Improve StreamExts.ReadAllBytes by using MemoryStream.CopyTo. This internally rents a much larger buffer from the array pool, allowing for fewer, larger stream reads.
Painting a MultiBrush previously required it to be non-empty. When
using Replaceability.Any, this is unlikely to be the desire behavior,
as some utilities (such as the upcoming LatTiler and RampTiler) may
reasonably generate an empty MultiBrush as output.
This changes painting MultiBrushes using Replaceability.Any to be
effectively no-ops. The .Tile and .Actor cases still use checks, as
these still plausibly represent mistakes.
This reverts commit 86b9227577.
This commit relied on the relationship not changing, but when a player is defeated their relationship can change due to becoming a spectator. This can cause a crash if the shroud is not removed. Revert the commit to resolve this crash.
This introduces basic Path Tiler map editor tool functionality to TS
with some segmented MultiBrush definitions. The included MultiBrush
definitions cover both the Temperate and Snow tilesets's Beach, Cliff,
and WaterCliff tiles.
MultiBrush now incorporates Height and Ramp information. Some
associated Terraformer utilities are updated with the API surface
change.
There are some known limitations that are not addressed in this PR:
- The path laying UI doesn't account for the map's current heights.
- The preview does not use a correct height or Z-ordering.
The Symmetry.Mirror enum was previously documented as defined in terms
of the WPos system, which happens to align with CPos for Rectangular
grid types. However, the existing code gets confused when attempting
to support RectangularIsometric, due to a lack of appropriate
conversion logic.
This change makes Mirror agnostic to specific coordinate systems and
introduces a WMirror wrapper that provides getters for the correct
Mirror configuration given the coordinate system being used.
- If generating a RectangularIsometric map via lobby, make the height
twice the width so that the map is approximately world-square.
- Use equal top and bottom cordons for RectangularIsometric maps.
- Change some map bounds checking.
- Check that actors are at least partially inside map.Contains before
placing. (To avoid frozen actor crashes.)
- Fix editor generated map blitting for RectangularIsometric.