Added
- Add
WallMath.Median,Percentile,MeanandStandardDeviationoverIReadOnlyList<float>,<double>,<int>and<long>, sorting pooled copies so the caller's list is never reordered. See Descriptive Statistics (#742). - Add
SpriteHelpers.RotateTexture90,RotateTexture180andExtractSpriteRect, which produce new textures and leave the source untouched, returning null with a logged reason instead of throwing on unreadable or write-protected textures. See Helper Utilities (#742). - Add
Objects.Sha256Hexfor text (UTF-8) and byte payloads, andObjects.TrySha256HexOfFile, which reports a missing or unreadable file withfalseinstead of throwing. The sprite sheet tooling's private file hash now calls it. See SHA-256 Digests (#742). - Add
EditorUtilities.IsInvokedByTestRunner(), so an editor window can skip fixture-consumingOnEnablework during a test run instead of re-deriving it. Answersfalserather than throwing where the command line is unreadable, and replaces seven private copies. See Editor Utilities (#742). - Fix
ColorExtensionsaveraging (GetAverageColor, dominant and weighted variants) reading channel-swapped pixels fromARGB32andBGRA32textures through the raw fast path; those formats now take the sameGetPixels32path as every other format (#749). - Enforce one member ordering across the codebase: const, events, delegates, static properties, static fields, properties, fields, constructors, static methods, methods, nested types last, each tier public → protected → internal → private including const.
npm run lint:nested-type-placementenforces it (#672). - Add a JesseSort adaptation with linear monotone-input handling and contiguous pile storage, credited to Jesse Lew. See JesseSort (#747).
- Add runtime-length exclusion arrays to
NextEnumExceptfor interface and concrete generators. See Random Generators (#742). - Add
BitOps, a public home for popcount, trailing zero count, floor log2, highest-bit isolation, power-of-two detection and power-of-two ceiling; the private copies inBitSet,ImmutableBitSet, the sorters and the sprite/enum tools now call it. See Helper Utilities (#742). - Add reusable protobuf read limits for encoded bytes, length-delimited fields, tag counts, nesting and cumulative packed-element counts across nested messages. See Serialization (#647).
- Add reusable JSON read limits for nesting, per-container element counts and string or property-name lengths, with a parameterless constructor for generic factories. New
Serializer.JsonDeserializeoverloads refuse an over-budget document before decoding it. See Serialization (#647). - Add
WUH017warnings for a same-objectGetComponentcompared against null, whereTryGetComponentanswers the same question without Unity's Editor allocation. See Analyzers (#741). - Add opt-in exact sprite-art colliders, reusable alpha-mask reading and pixel-boundary tracing, with alpha and minimum-area controls. See Sprite Colliders (#715).
- Add editor-only sprite keyframe times and threshold-based last-motion detection, with renderer-path filtering and selectable bounds edges. See Sprite Animation Motion (#717).
- Add cross-assembly WallstopProto subtypes with static dispatch, inherited private-member support, and collision checks at compilation, project assembly and startup (#612).
- Add malformed-state checks to WallstopProto raw remainder reads; failed reads leave an empty span and preserve the reader position (#647).
- Add unbounded retention, custom key comparers and opt-in removal ownership transfer to
Cache<TKey, TValue>. Shared cache limits now resize existing entries immediately (#703). - Add
WUH015warnings for invalid Unity callback signatures andWUH016warnings for hidden inherited callbacks, using resolved types and inheritance (#654). - Add
Helpers.ClearTagCache(), which drops every cachedHelpers.Find(tag)lookup. A miss costs oneGameObject.FindGameObjectWithTag(#643). - Add
Buffers.ComparerPoolMaxDistinctEntries, which bounds how many distinct comparers the set and dictionary pool caches retain; the least recently used is evicted past it. See Comparer-Keyed Pools (#689). - Add
StringWrapper.MaxCachedWrappersandStringWrapper.CachedCount, which bound and report how many distinct strings the wrapper cache retains; the least recently requested is evicted past it (#694). - Add
SerializableTypeCatalog.MaxCachedFilterResults, which bounds how many distinct type-picker search terms are cached; the least recently used is evicted past it (#694). - Add
PoolTypeResolver.MaxCachedTypeNames, which bounds how many distinct type-name spellings the pool type resolver retains (#643). - Add
PoolStatistics.ApproximatelyEquals(other, tolerance), matching the overloadPoolFrequencyStatisticsalready carries now thatEqualsis exact (#639). - Add
RestorableGlobal<T>, which lends a global for ausingblock and takes it back exactly once, at any nesting depth and in any disposal order, so a copied scope can no longer restore a stale value.WUH014reports the disposable structs that still assign inDispose. See Restorable Globals (#627). - Add Tools > Wallstop Studios > Unity Helpers > Run EditMode/PlayMode Tests With Summary, which starts a test run and writes a pollable summary file to
Temp/for a script driving the editor from outside. See Test Run Reporter (#625). - Ship four validation rules with the package -- unfilled
[WNotNull]slots, broken serializable dictionaries, empty animation keyframes, and script files that misname what they bind -- so the Asset Validation window and CI batch mode have something to check out of the box. See Asset Validation (#675). - Extend
WUH003tois nullandis not nullon aUnityEngine.Object. Both are CLR null tests, so a destroyed object reads as alive exactly as it does through?.. A type pattern is not reported, because it is not a null test at all. See Analyzers (#621). - Add Tools > Wallstop Studios > Unity Helpers > Authored Assets, reports for a type nothing can author, an unfilled
[WNotNull]slot, aSerializableDictionarythat lost its pairing, and an empty keyframe. Reading is never destructive. A file a scan cannot read is named. See Authored Asset Validation (#618, #619, #620, #624, #631, #670). - Add Report Stale Serialized Keys and Repair Stale Serialized Keys In Selection, for keys an asset still records that no field claims. The repair rewrites one asset at a time, undoes any rewrite that loses objects or throws, and names which. See Authored Asset Validation (#617, #679).
- Add
WUH012, which warns when a walk of a serializedList<T>orT[]of references dereferences a row without testing it. Deleting the asset a row names leaves the row behind, empty. Compaction counts as the guard. See Analyzers (#628). - Add
WUH011, which warns whenSerializedStringComparer.compareModechanges after the comparer has been handed to a collection; freeze the comparer to keep existing keys reachable. See Performance Analyzers (#663). - Add
SerializedStringComparer.Freeze(), which pins the comparison rule so a later write tocompareModecannot re-bucket a dictionary already built with it -- keys sorted under the old rule become unreachable under the new one, with nothing thrown (#646). - Add
RTree2D.GetElementsWithCentersInBoundsandRTree3D.GetElementsWithCentersInBounds, which return only elements whoseBounds.centeris inside the query box, so a sweep over a tiling visits each element once. See Spatial Tree Semantics (#658). - Add
Objects.StableHash32V1(bytes, seed), an FNV-1a hash whose answer depends on nothing but its arguments, so it is safe to persist or compare across machines. See Stable Hashing (#639). - Add
ApproximatelyEquals(other, tolerance)toCircle,Sphere,Line2D,Line3DandPoolFrequencyStatistics, so a computed shape can still be compared loosely now thatEqualsis exact. The tolerance is the whole permitted per-component difference at every magnitude (#639). - A negative, infinite or NaN tolerance makes
ApproximatelyEqualsreturn false, and a non-finite component compares exactly, so a shape with an infinite radius is still approximately equal to itself (#639). - Add
FastVector2Int.HasSameXYandFastVector3Int.HasSameXY, which say what the cross-dimensionalEqualsoverloads did. Those overloads are now[Obsolete]and go away in 4.0 (#639). - Add
Helpers.NameHashCode, the hash code that agrees withHelpers.NameEquals(#639). - Add
TryNextUint,TryNextUlong,TryNextDoubleandTryNextGaussiantoAbstractRandom, reporting exhausted sampling, including nested integer rejection, withfalseand a default output. Existing methods retain degraded sampling (#638). - Add
SpatialHash2D.TryInsertandSpatialHash3D.TryInsert, which report whether a position was stored.Insertdrops a non-finite position silently: one that wentNaNin physics is data, not a call the caller got wrong, so it never aborts the frame. See Query Contract (#642). - Add
WUH010, which warns when a dictionary is read through its key indexer -- it throws for an absent key whereTryGetValuereports, andmatch.Groups["name"]quietly hands back a group that never matched. Off by default, unlike the rest of the family. See Analyzers (#652). - Add
WUH003andWUH004, which warn when?.,?[],??or??=is applied to aUnityEngine.Object-- those operators test CLR null, so a destroyed object walks straight through the guard -- and when an NUnit null assertion passes over one. See Analyzers (#621). - Add
WUH005, which warns onUnityEngine.Random, whose state no test can set or read without disturbing every other caller. It points atPRNG.Instanceand the seedable generators behindIRandom. See Analyzers (#622). - Add
WUH006andWUH007, which warn when anEffectHandleor a coroutine handle is discarded -- each is the only thing that can undo the call it came from. The coroutine rule matches the return type, so this package's delay helpers count too. See Analyzers (#623, #626). - Add
WUH008, which warns when aTryXxxoutvalue is read on a path where the call's result was never tested. Only the BCL promises to writedefaulton a miss. See Analyzers (#629). - Add
WUH009, which warns when a teardown override callsbasebefore statements that still need what it released. Setup chains base-first; teardown chains base-last. See Analyzers (#630). - Add
Span<T>overloads of theIListhelpers -- shuffling,Shift/rotate,TrySwap,Fill, predicate search,TryFindAllandTryPartition-- so astackallocbuffer reaches them with no allocation. They share theIListbodies, so seeded output is unchanged. See Span Operations (#632, #651). - Add
WPROTO047, a warning when a type inherits its WallstopProto contract and declares[WProtoMember]of its own without carrying[WProtoContract]. The code works; the attribute is what tells a reader those members are on the wire (#613). - Add a time-sliced validation engine with per-profile change/save triggers, build gates and JSON/JUnit reports. Failed, cancelled and incomplete runs retain the last complete result. See Asset Validation (#288, #648, #669).
- Add the themed Sentinel workspace with rules, profiles, safe fixes, suppressions and editor status. Preferences can disable all Sentinel editor work and UI. See Asset Validation (#288, #634, #655, #669).
- Add
[WProtoSubtype(typeof(Base))], so a subtype joins a WallstopProto hierarchy without picking a field number. The editor assigns and commits the number on the next reload, and Assign WallstopProto Subtype Tags retires a removed one -- hand-numbered or not -- so it is never reused. See Polymorphism (#587, #601, #606). - Add
Sfc64Random, the Small Fast Chaotic generator: a published-pedigree 64-bit generator with a very small hot path that answersNextUlongin one state advance. See Random Generators (#516). - Add
[WProtoReserved], which records a field number or name a removed[WProtoMember]held -- or, on an enum, a removed member's value. Taking one again is a build error, not a save that reads back as the wrong thing, and the exported schema carries the proto3reservedlines. See Retiring a member (#608, #609). - Subclass a
[WProtoContract]and it serializes -- no attribute needed, the editor commits its field number. Write[WProtoNotSerialized]on one that never reaches the serializer. See Polymorphism (#613). - Add a proto schema exporter: Tools > Wallstop Studios > Unity Helpers > Proto Schema Exporter writes
proto3for your[WProtoContract]types, so anything downstream can read your saves. Search and tick the exact types, name a package, and write one file or one per assembly, namespace or type (#424, #595). - Reading a repeated WallstopProto field allocates from the elements a payload delivers, not the count it states, so splitting one across many runs cannot amplify (#647).
- Add strict UTF-8 validation to WallstopProto strings and Uri: wire bytes that are not valid UTF-8 refuse the payload as malformed instead of decoding to replacement characters, as proto3 requires (#580).
- Add
IntMap<TValue>, an int-keyed open-addressing map measured at 1.26x–2.19xDictionary<int,int>on hit-heavy lookups, with no comparer indirection on the lookup path. See Data Structures (#578). - Add
charandUrisupport to WallstopProto, byte-compatible with both protobuf-net majors, so links and code units inside a save no longer need a surrogate.DateTimeOffset,IntPtr,UIntPtrandTypestay refused, with the reasons in the serialization guide (#399). - Add
stackTrace: falsetoLog,LogWarnandLogError, for a diagnostic that repeats once per object or once per frame. Unity captures a stack trace for every log by default, measured at 178.4 us against 13.3 us without one. See Logging Extensions (#564). - Add
RandomGeneratorMetadata.Period, so every generator states its period where a caller can read it, and the Random Generators table now carries a Period column that cannot drift from it. A published period is quoted; where none exists the value states the measured live state width instead (#516, #285). - Add
SerializedMemberNames, which converts between a property's source name and the<Name>k__BackingFieldUnity serializes it under, for anyone writing a drawer that resolves a member by name (#550). - Add
WUH002, which warns when a Unity-serialized field resolves onto a collection of collections -- Unity drops every inner value and reports nothing. Covers public fields of nested[Serializable]types too. A warning at most, and switchable off. See Performance Analyzers (#548). - Add a non-throwing sibling for all eight ranged draws,
NextIntInRangethroughNextDoubleInRange. They answer the low bound where the strict overloads reject an empty range, which is what an authored min/max pair set equal produces. See Ranges a Designer Authored (#546). - Add
WUH001, which warns when a lookup factory is passed as a method group -- C# rebuilds that delegate on every call, hits included. CoversConcurrentDictionary,ConditionalWeakTableand this package'sIDictionaryextensions. A warning at most, and switchable off. See Performance Analyzers (#538). - Add
Serializer.ProtobufSurrogatesReady(), which reports whether protobuf will write the byte layout this package documents, and names the types it will not. A game can check it before its first save instead of reading a startup log. See Checking the surrogates took effect (#531). - Add
ReflectionHelpers.CreateTypedArray(), which builds an array whose element type is known only at run time about twice as fast asArray.CreateInstanceplusArray.SetValue(#529). - Add
string.Slugify(), which produces a URL- and filename-safe key: lowercase ASCII, single hyphens, accents folded so"Café"is"cafe".ToKebabCasekeeps punctuation and accents and is not a substitute. See Slugs (#386). - Add
TrackedObjectPool<T>, a pool forUnityEngine.Objectitems whose lifetime ends in a callback rather than a scope.Disposedestroys what is still checked out instead of stranding it in the scene, and a destroyed item is never pooled or handed out. See Pooling Unity Objects (#523). - Add
VisualElement.IsShown(),IsShownResolved(),IsWithin(),FocusedElement()andTryFocus().Focus()is silent when it does nothing, and Unity's ownContainsexcludes the element itself, so "did focus move" and "is the focused element mine" both had to be written by hand. See UI Toolkit Extensions (#513). - Add
Xoshiro128StarStarandXoshiro256StarStar, the first generators here whose every output bit is strong enough forNextBooland power-of-two masks.Xoshiro256StarStaralso answersNextUlongin one state advance instead of two. See Random Generators (#510, #285). - Add release notes to the Unity Package Manager: the package now carries its changelog section and a Changelog link, so Version History shows what changed instead of nothing (#421).
- Add AOT protobuf support for
ValueTuple:Serializer.ProtoSerialize((7, 1.5f))threwExecutionEngineExceptionon an IL2CPP player and now goes through a generated formatter. JSON of a tuple stays editor-only. DefineWALLSTOP_DISABLE_VALUE_TUPLE_SERIALIZATIONto opt out. See Tuples serialize on IL2CPP (#289). - Add
SerializableValueTuple<T1, T2>andSerializableValueTuple<T1, T2, T3>, so a tuple survives Unity serialization -- a(int, float)field is silently dropped, taking any authored contents with it. Byte-identical toValueTuplein protobuf and JSON. See SerializableValueTuple (#289). - Add a comparer constructor to the
Dictionary<TKey, TValue>nested inSerializableDictionaryBase, sobase(new Dictionary<string, T>(StringComparer.OrdinalIgnoreCase))-- the documented way to seed a comparer -- compiles inside a subclass, where that type shadows the framework one (#476). - Add
long,floatanddoubleoverloads ofWrappedAdd, and alongWrappedIncrement, so an angle or a normalized phase wraps with the same call a ring-buffer cursor does. See Numeric Helpers. - Add
WDoomRandom, an index-into-array generator inspired by id's original DOOM technique: one array read per draw, and one index of state, so a save file records the whole generator. It repeats every 1024 draws -- for retro feel and replays, never sampling. See Random Generators (#281, #516, #524). - Add
SortAlgorithm.YamandIList<T>.YamSort(), a stable sort that approaches O(n) on sequential and reverse-sequential data. Adapted from YamSort by Gary Gende. See IList Sorting Performance (#461). - Add
[SingletonCreation(SingletonCreationPolicy.NeverCreate)], which stopsRuntimeSingleton<T>.Instancecreating a bare stand-in when no instance exists. It returnsnulland names the type instead. See Controlling on-demand creation (#321). - Add
AnimationClip.GetSpriteFramesFromClip(), which pairs every sprite a clip references with the binding that supplies it, aGetSpritesFromClip(path, propertyName, type)overload that filters on that binding, andUnityExtensions.SpriteBindingProperty. Editor-only. See Sprites from an AnimationClip (#451). - Add
AssetChangeDetectionUtility.Enabled,ResetEnabledToDefault()andEnabledScope(bool)to turn the[DetectAssetChanged]watcher on and off. The returnedAssetChangeDetectionEnabledScoperestores the previous setting on dispose (#327). - Add
SingleThreadedThreadPool.DrainAsync(), which closes the pool and waits for queued work to finish instead of dropping it, plusIsAcceptingWork(#318). - Add
SerializableList<T>, a list that survives Unity serialization inside another serialized collection (#314). - Add the Inspector drawer for
SerializableDictionary<TKey, TValue, TValueCache>, which previously had none (#314). - Add
WGuid.TryCreate(Guid, out WGuid), which wraps an existing GUID without throwing when it is not version 4 (#437). - Add
Enum.TryConvertToUInt64()andTryConvertToInt64(), which convert any enum value to its 64-bit bit pattern without overflowing on negative or very large members. - Add
ColorQuantization, the one place an 8-bit channel becomes a normalized float and back.ToNormalizeddivides by 255, the same bits Unity'sColor32conversion gives;ToByterounds to the nearest channel;ToThresholdBytebounds a float cutoff. See 8-bit Channels and Normalized Floats (#466, #565). - Add
ColorQuantization.AreSameColor(), the one definition of "the same color" in the package: two colors match when they encode to the same 8-bit channels, which is the only rule an equality comparer can hash (#472). - Add
ColorContrast, which answers whether text is readable on a background the way WCAG defines it:RelativeLuminance,ContrastRatioandReadableTextColor. See Readable Text on Any Background (#471). - Add
TextureResampling, the one place a resampler picks a source pixel and mixes colors of unequal opacity:BilinearSourceCoordinate,NearestSourceIndex,PremultiplyandUnpremultiply. See Resampling a Texture (#470). - Add
SemaphoreSlim.Acquire(),TryAcquire()andAcquireAsync(), so a permit can be taken withusinginstead of afinally. The lease releases its permit exactly once, however many copies of it exist. See Semaphore Leases (#358). - Add
DurableFile, whose writes cannot leave a truncated file behind:TryWriteAllText,TryWriteAllBytes,TryAppendAllText,TryCopy,TryDeleteand text/copy async equivalents. See Durable Writes for Player Data (#319). - Add
WallstopProto(preview), an IL2CPP-safe, protobuf-net-compatible serializer. Hand-written formatters must consume valid complete payloads; enum registrations must match the actual enum type, width and signedness. See WallstopProto (#343, #647, #722). - Add steady-state allocation-free
IndentLevelScopeand internal label-width scopes that remain copy-idempotent and restore the newest active editor-global value even when nested copies are disposed out of order (#627). - Add facade-owned nested-message size plans to WallstopProto. Measured prefix widths avoid repeated large-payload moves, direct writers keep canonical back-patching, and a formatter that underwrites its measurement is refused (#383, #504).
- Add the WallstopProto source generator (preview): annotate a type with
[WProtoContract]and its formatter is generated into your assembly and registered for you. A contract it cannot serialize is a build error naming the fix. See The generator (#343). - Add
WProtoFormatterProvider, which resolvesIWProtoFormatter<T>without reflection. Built-ins coverFastVector2Int,FastVector3Int,WGuid,RandomState,DateTime,TimeSpan,Guidanddecimal; later registrations replace them (#343, #379, #399). - Add support for a
[WProtoMember]that is another[WProtoContract], including the same type again, so a tree or linked list serializes without a hand-written formatter. A sub-message merges into the value your constructor gave the member. See Contracts that hold other contracts (#380, #475, #485). - Add WallstopProto support for arrays and any
ICollection<T>with a public parameterless constructor andAdd, includingstructcollections, which are not boxed on the write path.OverwriteList = truereplaces the constructor's collection instead of appending. See Collections (#343). - Add WallstopProto support for
LinkedList<T>,Queue<T>,Stack<T>,ReadOnlyCollection<T>,ReadOnlyDictionary<K,V>and members declared as the matching interfaces. AStack<T>round-trips in its original order. See Collections (#395). - Add WallstopProto support for nested and jagged collections —
int[][],List<List<int>>,Dictionary<string, List<int>>and deeper — which protobuf-net refuses. See Collections (#399). - Add WallstopProto support for rectangular arrays —
int[,],int[,,], and one anywhere a collection can go — which protobuf-net refuses. Dimensions travel with the elements, sonew int[0, 5]keeps its shape. See Collections (#434). - Add WallstopProto maps: a
Dictionary<,>,SortedDictionary<,>or your ownIDictionary<,>is written as a protobuf map, byte-compatible with protobuf-net. See Maps (#387). - Add WallstopProto polymorphism:
[WProtoInclude(tag, typeof(Subtype))]round-trips a member typed as a base as its concrete subtype, with no reflection. An unrecognized include tag is skipped, so saves from newer builds still load. See Polymorphism (#390). - Add
[assembly: WProtoSurrogate(typeof(Real), typeof(Surrogate))]for wire shapes on types you cannot annotate, plus open generic pairs that cover each closed construction. Package pairs serve two- and three-componentValueTuplemembers and map keys. See Surrogates (#391, #399). - Add
WPROTO038, a build error when an open surrogate pair has mismatched arity, openness, or stricter generic constraints than its real type (#399). - Add
[assembly: WProtoRootMarshal(typeof(Real), typeof(Formatter))], which gives a type a different wire shape as the root of a serialization than as a member of a contract. See Root marshals (#402). - Add
[assembly: WProtoDeclaredRoot(typeof(IRandom), typeof(AbstractRandom))], which names the contract serving a value held as an interface. It applies at the root only. See Declared roots (#403). - Add generic
[WProtoContract]support: each closed construction gets its own encoding and is registered automatically, so your ownBox<YourStruct>needs no manual registration. A member typed as the contract's own parameter merges a sub-message a payload carries twice. See Generic contracts (#385, #484). - Add
Tools > Wallstop Studios > Unity Helpers > Validate Serialized Fields In Selection, which reports apublicor[SerializeField]field Unity silently drops -- aDictionary, a(int, float), aNullable<T>-- and names the package stand-in to use instead. See Serialized Field Validator (#497). - Add generated JSON converters, so
Deque<int>andSerializableDictionary<string, int>serialize in an IL2CPP player instead of throwingExecutionEngineException. DefineWALLSTOP_DISABLE_GENERATED_JSON_CONVERTERSto opt out. See Generic containers get their converters generated (#501). - Add bounded pooled scratch lists to generated JSON array converters, so a warm read only allocates its returned array and an untrusted large array is not retained by the pool (#504).
- Add a metamorphic battery over WallstopProto: payloads rewritten into other legal spellings -- fields reordered, an undeclared field injected, a sub-message split into two that merge -- must decode unchanged (#462).
- Add a differential fuzz over WallstopProto: random valid values for repeated, map and polymorphic contracts, checked in both directions against protobuf-net 2.4.9 and 3.2.56 (#437).
- Add
WProtoMemberAttribute.DataFormat, which selects a signed integer member's encoding.WProtoDataFormat.ZigZagis protobuf'ssint32/sint64, where a small negative value costs one byte instead of ten. See DataFormat (#527). - Add
WPROTO037, a build error forDataFormat = ZigZagon a member with no such encoding. Onlysbyte,short,intandlonghave one, so anywhere else the annotation would have been dropped and the member written as theint32it declined (#527). - Add
WPROTO035andWPROTO036, warnings for an[assembly: WJsonConverter]pairing that cannot be closed, and for two pairings claiming one type (#501). - Add
WPROTO034, a warning for a lifecycle hook declared on a subtype of a[WProtoInclude]chain. protobuf-net 3 never runs one, and protobuf-net 2 runs it in the opposite order to WallstopProto, so declare it on the root instead. See The generator (#500). - Add
WPROTO033, a warning for aSkipConstructorcontract holding a field that is initialized where it is declared and is not a[WProtoMember]. That instance is allocated uninitialized, so the field arrives at its type's default. See The generator (#494). - Add WallstopProto support for
readonlyfields and get-only properties, previously a build error. The generator emits a private constructor into yourpartialtype, keeps the parameterless one it would otherwise have had, and a member the payload omits keeps the value your constructor gave it (#394, #491). - Add
WProtoContractAttribute.SkipConstructor, which reads a contract without running any constructor its author wrote, matching protobuf-net and still working under IL2CPP. A collection member holds exactly the elements the payload carried, unless a parent's constructor supplied the instance (#394). - Add
WProtoContractAttribute.IgnoreListHandling, which writes a contract that also implementsICollection<T>as a message. Without it such a member is a build error naming both readings (#343). - Add
IWProtoScalarFormatter<T>andWProtoGeneric<T>, which make a value's wire type expressible so a generic contract can encode a member whose type it cannot see.IWProtoFormatter<T>is unchanged. - Add
IWProtoPolymorphicFormatter, which lets a formatter report the runtime types its[WProtoInclude]chain writes, soSerializercan serve a value held as its base (#403). - Add
IWProtoConditionalFormatter, so a formatter reports whether it can encode a closure before anything is written. An element WallstopProto cannot encode now falls back to protobuf-net instead of throwing mid-serialization (#402, #416). - Add
WProtoFacade.TryDeserializeAs(), the read that names a concrete type.Serializer.ProtoDeserialize<T>(byte[], Type)routes through it (#403). - Add
SerializationCapacityLimits, the bound a deserializer applies to a capacity a payload claims, withMaximumRestoredCapacity(default 1,048,576) for games whose saves are legitimately larger. An empty rectangular array whose axis exceeds it does not deserialize; one carrying elements is unaffected (#437). - Add
WProtoReader.CountPackedElements(),WProtoArrayBuilder<T>andWProtoRepeated.Reserve(), so a hand-written formatter can size a repeated field once instead of growing it. Generated formatters already size theirs from the packed run (#398). - Add
WProtoReader.TryReadPackedRun(), which reads a packed repeated field's payload as its own reader without spending a nesting level. - Add
WProtoMessageAccumulatorand the matchingWProtoReader.TryReadMessage(payload, formatter, out value)overload, so a hand-written formatter can merge a sub-message field a payload carries more than once (#475). - Add
IWProtoMergeFormatter<T>and aWProtoReader.TryReadMessage(payload, formatter, seed, out value)overload, so a hand-written formatter can decode into a value the caller already holds.IWProtoFormatter<T>is unchanged; a formatter that skips this reads exactly as before (#485). - Add
WProtoReader.MaxNestingDepth(64), the deepest message nesting a payload may use, so a few kilobytes cannot ask a formatter for thousands of stack frames. A hand-written formatter descends withTryReadMessage(formatter, out value)ornew WProtoReader(payload, in parent), which carry the depth (#343, #377). - Add
WProtoGeneric<T>.CanEncode, which reports whether a closed type argument can be encoded at all,IsMessage, which reports whether it is encoded as a sub-message, and aTryReadValue(ref reader, payload, out value)overload that decodes accumulated occurrences (#484). - Add
WProtoRectangular, the shape check and refusal message a hand-written rectangular-array formatter needs. - Add
WProtoRepeated.NullElement()andNullNestedElement(), which build the exceptions a generated formatter throws for anullrepeated element or inner collection. - Add
WProtoFormatterProvider.UnexpectedSubtype(), which builds the exception thrown for a value whose runtime type its contract does not declare. - Add
WPROTO018, a build error when a[WProtoContract]'s base is one too but is not declared with[WProtoInclude](#394). - Add
WPROTO028, a warning when a closed construction cannot be registered because it closes over aprivatenested type. It is skipped rather than failing the build (#414). - Add
WPROTO030, an informational diagnostic when a protobuf-net contract has no[WProtoContract], so migration is an opt-in worklist. It reads[ProtoContract], a vendored protobuf-net under a renamed namespace, and a[DataContract]with ordered[DataMember]s, and names which matched (#407, #597). - Add
WPROTO031, a warning when assemblies declare different roots for the same type (#419). - Add
WPROTO032, a build error when a member's collections nest more than 64 deep, which is deeper than the reader can read back (#399).
Security
- Ship the runtime assembly with
allowUnsafeCodeoff. Two BMI2 pointer branches guarded by framework symbols no Unity player defines were the only reason it was on, so nothing that ever ran has changed (#637). - Clamp or refuse a capacity a payload claims in
Deque,SparseSet,BitSetandImmutableBitSet. Six bytes claimingint.MaxValuepreviously allocated 8-16 GB and crashed the player. RaiseSerializationCapacityLimits.MaximumRestoredCapacityif your saves exceed 1,048,576 elements (#429).
Changed
- Change Editor
MatchColliderToSprite.OnValidateto preserve collider geometry; programmatic callers now useRebuildCollider(). Inspector edits and the match button still rebuild immediately with Undo support. See Sprite Colliders. - Reduce script compilation work for generic protobuf and JSON contracts (#706).
- Change Unity Method Analyzer to read compiler diagnostics, with assembly coverage, explicit recompilation, and navigation to resolved source locations. See Unity Method Analyzer (#654).
- Pool purge sizing now averages every rental sample inside
RollingWindowSecondsrather than the most recent ten thousand, so spike detection reads a longer history on a busy pool (#693). - Restoring a
CyclicBufferfrom JSON whose stated capacity is below the items it carries now keeps every item, matching what both binary paths already did (#637). StringWrapper.Disposeno longer removes the wrapper from the cache, and is[Obsolete]. The wrapper is shared with every other holder of the same string, so one borrower'susingblock evicted an entry the rest were still reading. UseStringWrapper.RemoveorClear(#646).- Restrict
Equals(object)onAttribute,WGuid,FastVector2Int,FastVector3Int,SerializableType,SerializableNullable<T>and bothSerializableValueTuplearities to its own type. A boxedfloat,Guid,Type,Vector2IntorValueTuplenever answered true in return; the typed overloads are unchanged (#639). - Change
Attributeto hash itsCurrentValue, so an attribute already sitting in aDictionaryorHashSetbecomes unreachable the moment a modification changes that value. Re-add it after applying one (#639). - Restrict
CompareTo(object)onFastVector2IntandFastVector3Intto its own type, so it can no longer answer 0 for a pairEqualsrefuses. The cross-dimensionalCompareTooverloads are now[Obsolete]and go away in 4.0 (#639). - Say what
Objects.HashCodeactually promises: the mixing is fixed, but each argument contributes an ordinaryGetHashCode, which is process-local for strings, Unity objects and your own types. It was documented as deterministic and offered for save files (#639). - Change
PRNG.Instanceto returnXoshiro256StarStar: anExcellent-rated generator with a published 2^256-1 period and reference, measured even with the previous default. Streams drawn fromPRNG.Instancediffer; construct a generator directly to keep one (#516). - An attribute with only additive modifications recalculates 2.85x faster: 0.4563 us before, 0.1600 us now, measured on
6000.4.6f1. Addition, Multiplication and Override each got a full pass over every modification whether or not any carried that action (#529). - A relational field that finds nothing is ~15x cheaper to assign: 366-431 us before, 25.1 us now, measured on
6000.4.6f1against a control that did not move. Unity captured a stack trace for the error log on every assignment, and for a collection field finding nothing is a normal state (#564, #529). RomuDuonow implements published romuDuo, so a given seed produces a different sequence than in 3.5.1. No saved seed orRandomStatecarries a sequence across this change; pin a generator whose stream is unchanged if you need one to (#509).DisjointSet.TryGetAllSets()is 2.4x-3.2x faster and allocates no temporary lists. It gathered every element into a per-root scratch list and then copied all of them a second time; elements now go straight into their result list, found through a dense index rather than a hash lookup (#309).Serializer.ProtoSerialize(value, ref buffer)no longer allocates a second full payload for theSerializablecollection types. The overload exists so a per-frame serialize allocates nothing (#504).- Serializing a
SerializableDictionaryorSerializableHashSetno longer resolves its protobuf wrapper type by reflection on every call; the type and its constructor are resolved once per element type (#504). - Writing a
WGuidto JSON no longer allocates a 36-character string per value, and reading anAnimationCurvekeyframe no longer boxes itsweightedMode(#504). - Relational fields typed as an interface no longer fetch every component on the object and type-test each one. Unity's own query resolves an interface, so the query is 1.35x-2.49x faster depending on how many components the object carries -- and the child shape pays it once per descendant (#529).
- Relational collection fields typed as a base component --
Collider2D[],Renderer[]-- no longer fetch every component on the object and type-test each one. Unity's own query already resolves a base class, so assignment of a component with three such fields is 9% faster (#529). Animator.ResetTriggers()no longer allocates. It readAnimator.parameters, which builds a new array and new element objects on every read -- 53.5 bytes per call for a three-parameter controller. The trigger hashes are now read once per controller (#549).GameObject.IsDontDestroyOnLoad()no longer allocates. It readScene.name, which marshals a fresh managed string every call, and the signature reads cheap enough to end up inUpdate. The answer now comes from the scene's handle (#549).WGroupandWGroupEndnow accept fields only. They advertised properties, which nothing lays out, so the attribute compiled and drew nothing. Use[field: WGroup(...)]on an auto-property (#550).WShowIfnow reads a condition that names a[field: SerializeField]auto-property from the serialized state rather than the live object, so a pending Inspector edit is what it reacts to (#550).- Draw 64-bit values 2.49x faster from
BlastCircuitRandom,RomuDuo,SplitMix64,WyRandomandXoshiro256StarStar-- one state advance instead of two.NextUlong,NextLongandNextDoublereturn a different sequence for a given seed; 32-bit draws are unchanged. See Seeded streams that moved (#509). - Return the strong half of
XoroShiroRandom, raising it fromFairtoGood: itsNextBool()was predictable from 128 draws, and PractRand now runs clean through 8GB where it used to fail at 16MB. Every draw changes for a given seed (#509, #285). - Stop the serializer allocating a delegate on every collection serialize. Four cache lookups built their factory per call rather than reusing one, costing 106-116 bytes each time, cache hit included (#504).
- Assign a hierarchy 30% faster when most of its components have no relational fields:
AssignHierarchytook a lock per component just to answer "does this type have any", costing 59.7 ns per component against 41.5 ns now (#529). - Deserialize a
SerializableDictionary,SerializableSortedDictionary,SerializableHashSetorSerializableSortedSetfrom JSON without reflection: the converters looked their backing fields up on every read, costing 1.5 us of a 17.6 us 16-entry read (#504). - Stop
[ChildComponent]and[ParentComponent]collection fields allocating aComponent[]on every assignment, so a scene full of components no longer builds garbage duringAwake(#534, #529). - Assign relational array fields faster: a
[SiblingComponent]array field costs 22% less, and sibling collection fields no longer allocate per call.ListandHashSetfields are unchanged (#529). - Write
FastVector2IntandFastVector3Intcomponents assint32, so a negative coordinate costs one byte instead of ten. A 1,000-cell tilemap centred on the origin falls from 14,690 to 3,870 bytes. Payloads written by 3.5.1 still read (#527). - Stop
FastVector2IntandFastVector3Intwriting their cached hash, which every reader already recomputed: a 1,000-cell tilemap falls from 14,167 to 5,870 bytes. Payloads written by 3.5.1 still read; a 3.5.1 build cannot read new ones (#519). - Remove an item from a
SpatialHash2D/SpatialHash3Dbucket by swapping the last entry into its place rather than shifting the tail. Query results were never ordered and are not now; the order two items in one cell come back in can differ from before. - Say what has actually been measured about
IllusionFlow,StormDropRandom,PhotonSpinRandom,FlurryBurstRandomandBlastCircuitRandom. All five are now verified clean through 8GB of PractRand 0.95 here, rather than described by an author's claim on a repository that is offline. Ratings are unchanged (#286, #516). - Take every
Bounds,BoundsInt,RectandColorextension receiver byin. Calling one no longer copies the struct at your call site, whichErrorProne.NET.Structsreported as anEPS06warning you could not fix without giving up extension syntax (#512). - Make
PooledArray<T>andPooledResource<T>readonly struct, so readingarray,lengthorresourceand callingDispose()no longer copies the wrapper first (#512). - Lower
XoroShiroRandomtoFairandRomuDuotoGood. Bit 0 ofXoroShiroRandomfollows a linear recurrence of order 128, so itsNextBool()is predictable from 128 draws; preferXoshiro128StarStarwhere single bits matter (#509, #286). - Lower
SquirrelRandomtoFair. It fails PractRand at 1GB, reproducibly across four seeds; it stays a good fit for the table lookups it was designed for (#286). - Read cancellation-aware JSON files through a pooled scratch buffer and deserialize the pooled stream's valid segment directly. Large save files no longer allocate an extra full-payload copy before decoding (#504).
- Speed up every pooled-buffer operation on blittable elements -- the sorts,
Shuffle,Filland the geometry helpers -- by clearing a returned array only when its element type can hold a reference. A reference element is still never left rooted in the pool (#482). - Speed up
ProtoSerialize,ProtoDeserialize,ProtoEqualsandNextEnumby resolving each one's type questions once per closed generic instead of on every call. Measured at 8.5x for that check alone on reference types (#346). - Every
IList<T>sort now runs over an array rather than the list's indexer, so sorting aT[]is 2.5x to 5.4x faster and needs no copy. Any other list is copied through a pooled buffer and back. See Where the Time Actually Goes (#463). - Speed up
IList<T>.Reverse(26x-37x),Shift/RotateLeft/RotateRight(2.7x-36x),Fill(3.6x-38x),Shuffle(1.5x-2x) and predicateIndexOf/LastIndexOf(2.4x-3.1x) with bulk array operations. See Bulk Operations on a List (#480). - Enable WallstopProto by default for the runtime assembly: every
Serializer.ProtoSerializeandProtoDeserializeoverload, includingref bufferandforceRuntimeType: true, uses generated formatters when available and falls back to protobuf-net (#343, #403). - Serialize thirty of this package's own contracts through WallstopProto when
WALLSTOP_PROTOis defined, includingAbstractRandomand all seventeen generators. Saved data is unchanged (#394). - Serialize
SerializableHashSet,SerializableSortedSet,SerializableDictionary,SerializableSortedDictionary,Deque,CyclicBufferandSparseSetthrough WallstopProto whenWALLSTOP_PROTOis defined. Saved data is unchanged (#402). - Serialize the fourteen surrogated Unity and package structs --
Vector2,Bounds,Resolution,Parabola,ImmutableBitSetand the rest -- through WallstopProto when one is the root of aProtoSerialize. An IL2CPP player threw or silently returned a default. Existing saves still read (#696). - Allow a
structbacking set inSerializableSetBase<T, TSet>: thewhere TSet : classconstraint is gone (#388). - Report a read of the write-only
GameObjectandTouchJSON converters asNotSupportedExceptioninstead ofNotImplementedException(#437). - Ship the bundled
System.Text.Jsonand friends only on editors that do not provide them; Unity supplies its own from 6000.5. See Bundled Assembly Conflicts (#331).
Fixed
- Fix package compiler and analyzer warnings across supported Unity versions, including the 2021.3 serialization attribute conflict and Unity 6 obsolete build-target calls (#766, #770).
- Fix the seven UI Toolkit progress controls failing to compile on Unity 6000.6. Their UXML tags and attributes remain available on Unity 2021.3 and newer (#759).
- Fix pooled serialization writes accepting negative or oversized advances and overflowing capacity calculations (#760).
- Fix caught exceptions losing their type, inner exceptions, and stack trace in diagnostic logs (#762).
- Fix global budget enforcement running pool purge and disposal callbacks while holding the registry lock (#752).
- Fix a disposed pooled serialization writer hanging when code tries to use it again (#753).
- Fix the protobuf byte-writer pool abandoning its rented buffer on purge: writers now return their
ArrayPool<byte>rental when they leave the pool, and the pooling guide documents when a pool needsonDisposal(#749). - Fix collection-inspector button textures leaking across script reloads. See Property Drawers (#648).
- Fix unbounded sprite preview retention in Animation Creator and Animation Event Editor while preserving full-resolution previews. See Animation Tools (#648).
- Fix
WyRandom.Copy()losing pending Gaussian, bool, and byte values, so a copied generator continues the same mixed draw sequence (#638). - Fix malformed
SparseSetbinary saves silently losing invalid element IDs or enlarging their universe for duplicates. Both binary readers now refuse invalid elements before restoring the set (#647). - Fix explicit-type protobuf collection reads bypassing wrappers, preserving stored elements and capacity rules (#647).
- Fix infinite-bound random draws returning NaN or excluded endpoints, and stop exhausted sources from multiplying retry budgets (#638).
- Fix exact relational component matching when
AllowInterfacesis disabled, including concrete types that are not sealed (#733). - Fix editor windows and collection drawers retaining owned serialized state and copied previews after refresh, close or failed initialization; shared Unity previews remain available (#734).
- Fix cleared runtime singletons being returned again before frame-end destruction. Immediate lookup selects a live replacement or respects
NeverCreate(#729). - Fix finite extreme coordinates disappearing from spatial queries or choosing farther nearest neighbors. Immutable trees preserve accepted finite entries across overflowing bounds and distance calculations (#720).
- Fix runtime and ScriptableObject singleton reset callbacks interrupting cleanup or recursively invoking themselves. Reset completes after callback failures and rejects worker-thread access before changing state (#723).
- Fix stuck progress bars after failed prefab checks, sprite and texture batches, atlas operations, reference replacement, and animation copy or delete cleanup (#648).
- Fix infinite stored geometry hiding finite spatial-tree entries. Constructors exclude non-finite positions and bounds edges while preserving source identities; infinite query radii remain supported (#718).
- Fix NaN guards in random sampling, sprite extraction, parabola math, spatial queries and cache timing. Nonfinite coroutine intervals use the existing one-frame delay. See Numeric Guards (#716).
- Fix package stylesheet and asset paths for local checkouts outside the Unity project. Cached packages resolve through their package identity, and similarly named sibling projects no longer count as project content (#655).
- Fix child-component binding to select nearer matches first for single fields and capped collections (#709).
- Fix typed reflection invokers for inherited receivers and struct interface methods. Receiver-specific caching and exact return-type validation prevent incompatible delegates (#644).
- Fix
Range<T>.Overlapsfor coincident open intervals, excluded touching endpoints, and empty or inverted ranges (#707). - Fix SLRU cache segment accounting during eviction and preserve protected-segment limits after resizing or clearing (#703).
- Fix Image Blur batches to restore source texture import settings, release temporary textures, and show one completion message per batch (#648).
- Fix
FluxSortoverflowing the stack on a list sorted by a shared key. 100,000 equal values recursed 47,572 frames deep; aStackOverflowExceptionis caught by nothing (#645). - Lower a deserialized
BitSet's capacity to the words the payload actually delivered, so corrupt saves fail reads safely and later writes still grow. An index too large to represent now returns false instead of throwing (#647). - Fix inspector button colours leaking a texture per intermediate shade: dragging a
[WButton]palette colour picker minted a 1x1 texture the editor never released (#701). - Bound the drawer caches that grew once per inspected object -- foldout animations, measured property widths and built foldout keys -- so a long editor session no longer retains every object it ever showed (#701).
- Fix
StringWrapper.Getkeeping every string it was ever given alive for the process, so wrapping a value built from gameplay grew without bound. A wrapper re-created after an eviction still equals the one it replaced (#694). - Fix
SerializableTypeCatalog.GetFilteredDescriptorsretaining a filtered copy of every project type for each distinct search term, so typing a name one character at a time kept an array per prefix (#694). - Fix
TagHandler.RemoveTagthrowing when a tag-removed handler removed another tag through the same buffer, which left the first tag raised. It also returns the handles it removed rather than the nested call's (#640). - Fix a cosmetic component destroyed by a sibling's callback aborting the whole effect, so an instant effect left its tags raised and applied no modifications (#640).
- Fix
AttributesComponent.ForceApplyAttributeModificationsandForceRemoveAttributeModificationsthrowingNullReferenceExceptionfor a handle whose effect is missing (#640). - Fix
PoolStatistics.Equalscomparing three rates within a tolerance, so a value did not equal itself when a rate wasNaNand equality was not transitive. It is exact now, withApproximatelyEquals(other, tolerance)for a loose compare (#639). - Fix
AttributeEffect.EqualsandCosmeticEffectData.GetHashCodethrowingMissingReferenceExceptionfor a destroyed asset, which could throw out of a dictionary lookup (#639). - Fix
AnimatedSpriteLayer.EqualsthrowingNullReferenceExceptionwhen either side was a default value, so comparing two entries of a fresh array threw (#639). - Fix a default
ImmutableBitSetcomparing unequal to an empty one, so a default value did not equal its own round trip (#639). - Fix
SplitMix64comparing by reference throughEquals(object)while hashing by value, so two generators with the same seed landed in twoHashSetentries (#639). - Fix one element whose position or bounds went non-finite hiding every finite element from
QuadTree2D,RTree2DandRTree3D, andRTree3Dthrowing when no element was finite. A non-finite element is skipped (#642). - Fix a query radius above about 1.8e19 returning elements outside it, on all eight trees and both spatial hashes: squaring it saturated to infinity and disabled the exact distance test (#642).
- Fix
OctTree3Dthrowing andQuadTree2Dsilently answering empty for an unusableboundary. Both now compute bounds from the elements, as passing no boundary does (#642). - Fix
ReflectionHelpers.TryResolveTypecaching names no assembly declares, so a save file naming a renamed type grew the cache for the process. A name that only becomes resolvable after a later assembly load now resolves (#643). - Fix eight editor drawer caches growing for the life of the editor, several holding a
SerializedPropertyor dropdown option and so keeping an inspected asset or scene object alive (#643, #694). - Fix
Helpers.Find(tag)keeping an unloaded scene's objects alive: an entry whose object the unload destroyed is dropped, where before only the next lookup of that same tag would notice.Helpers.ClearTagCache()drops them all (#643). - Fix every pooled rental allocating on a growth boundary for its pool's first ten thousand rents, and each pool then retaining 131 KB of usage samples for the process. Rentals are allocation-free once a pool exists (#693, #643).
- Fix
TextureScale.BilinearandPointreturning a partly scaled texture with no error when a worker slice failed. The failure now reaches the caller, as it already did on a single-core machine (#691). - Fix the
[SiblingComponent],[ChildComponent],[ParentComponent]and[ValidateAssignment]field caches being plain dictionaries, so assigning components from more than one thread could corrupt them (#644). - Fix an
ImmutableBitSetwhose serialized capacity exceeded the words stored beside it throwingIndexOutOfRangeExceptionfromTryGetandAll. The capacity is bounded by the words delivered (#637). - Fix a
[SiblingComponent],[ChildComponent]or[ParentComponent]field declaredprivateon a base class never being assigned, and never logging that it was not. Inherited[WNotNull],[ValidateAssignment]andAttributefields are found now too (#688). - Fix
TextureScale.BilinearandPointreturning their pooled buffers while worker threads were still writing into them, so a scaled texture could carry another caller's pixels (#643). - Fix a dropdown drawer keeping its choices list after returning it to the pool, so another editor could be handed the list it was still displaying (#643).
- Fix five pooled rentals being dropped rather than returned when the code between the rent and the release threw (#643).
- Bound the comparer-keyed set and dictionary pool caches, which kept every comparer a game ever handed them -- and any scene object one captured -- alive for the process.
Buffers.ComparerPoolMaxDistinctEntriessets the bound (#689). - Fix
Partitiontaking two pooled leases per partition and releasing one, so a consumer that did not dispose each partition leaked a lease slot per partition (#689). - Fix a
ScriptableObjectSingletonwhose first load threw staying broken for the rest of the session: the failure is now logged andInstanceanswers null, and clearing the instance recovers (#644). - Fix
Instancehanding back aScriptableObjectSingletonasset destroyed by a reimport, whichHasInstancealready reported as absent. It reloads instead (#644). - Fix
[AutoLoadSingleton]running only in the first play session when Enter Play Mode Options skips domain reload (#644). - Fix an effect that a subscriber removes from inside an apply callback leaving an attribute changed, or a tag raised, with no active effect and no way to undo it (#640).
- Fix removing an effect clearing a tag another active effect still owns, when the first effect only got part-way through applying its own (#640).
- Fix a shared
CosmeticEffectDatabeing applied twice and removed once when an effect is re-applied, which is what the defaultRefreshstacking mode does (#640). - Fix an attribute callback that adds or destroys a sibling
AttributesComponentaborting the rest of the effect's removal, leaving its tags raised and its cosmetics orphaned (#640). - Fix
NextSubsetreturning empty or another caller's values for a source that is not a list. Arrays and lists were never affected (#643). - Fix
PolygonCollider2D.Invertlosing its pooled buffers when Unity rejects a path (#643). - Fix a single-valued
[ChildComponent]field binding a disabled component that the same attribute on a collection field excluded (#644). - Fix every relational field in a scene being left unassigned, silently, when an injected
AttributeMetadataCachehad been destroyed (#644). - Fix a singleton first touched from inside another singleton's
OnInstanceClearedaborting startup cleanup for every singleton after it (#644). - Fix a
defaultNativePcgRandom-- an unassigned field, an array element -- being a stuck generator rather than a seeded one: every draw returned the same value, in range and so not obviously wrong. Explicitly seeded streams are unchanged (#638). - Fix editor work that only ran on a tick an unattended or CI editor may never pump: a failing run exported nothing, drawer caches survived a domain reload, and old selector history was never pruned. All three now run as soon as the editor can answer (#684).
- Fix
IList<T>.Sort, which documented "No allocations": any list that is not aT[]is copied through a pooled buffer. Its docs now state that, and everySortAlgorithmmember says whether it is stable. See Sorting Performance (#645). - Fix
RemoveAtSwapBackmutating single-item, out-of-range and fixed-size lists before throwing. Failed removals now leave the list untouched (#645). - Fix
WallstopGenericPool<T>returning an item into a pool that itsonReleasecallback just disposed. The item is retired exactly once instead of being resurrected inside a dead pool, in both thread-safe andSINGLE_THREADEDbuilds (#643). - Fix
RuntimeSingleton<T>accepting a sibling component that inherited the wrong closed generic base. The mismatch is refused and reported instead (#637). - Fix runtime and ScriptableObject singletons creating or loading a new instance while the application is quitting. An instance already found remains available during teardown (#644).
- Fix races in the package logger. Its global flag, per-object disable set, metadata cache and scratch dictionary were unsynchronized, so a worker could miss a toggle, throw from inside a diagnostic, or print one object's fields under another's name (#646).
- Fix
ToCachedNameandToDisplayNamegrowing a process-lifetime cache for undefined and composite enum values. Only declared members are cached now; anything else is formatted fresh (#646). - Fix
UnityMainThreadDispatcher.RunAsyncnaming the caller's token on every cancellation. It now reports the token that actually cancelled the work, so a caller racing its own timeout against an unrelated one can tell them apart. See Which Token Cancelled (#641). - Fix
SceneHelperscene disposal hanging forever when the dispatcher queue was full or the unload failed. Disposal now faults with the reason, and disposing a scope twice unloads the scene once (#641). - Fix eight editor windows -- Image Blur, Sprite Settings Applier, Texture Settings Applier, Animation Copier, Sprite Cropper, Animation Creator, Sprite Pivot Adjuster and Sprite Sheet Extractor -- leaking a
SerializedObjecton every close. Each now releases it and rebinds when reopened (#641). - Fix
RTree3D.GetElementsInBoundsdropping an element that straddles the query box: it filtered by the element's center whereRTree2Dreturns anything the box touches, so a system ported from 2D to 3D silently stopped seeing them. Both now mean "the element's box touches the query box" (#658). - Fix spatial hash queries that never returned: a huge or infinite radius, or a rect spanning the float range, walked every cell in the volume. Each query now walks the cheaper of the cells and the occupied buckets. See Query Contract (#642).
- Fix nearest-neighbor queries dropping equal-valued elements: all six spatial trees staged results in a value-keyed set, so 64 identical points answered one neighbor. They now return
min(count, n), ordered by distance then insertion index (#642). - Fix negative,
NaNand non-finite spatial query inputs returning an arbitrary subset. Every query clears its destination once and returns empty,RTree2Dzero-range returns only elements the point touches, andRTree3Dbounds queries include the max face like their siblings, zero-size elements too (#642). - Fix
RTree2DandRTree3Drange queries measuring with an epsilon.RTree3Ddropped an element sitting exactly on the query radius along an axis, and both admitted one up to 1e-3 world units outside a small radius. Distances are compared exactly now (#642). - Fix a spatial hash's
Disposedestroying the process-wide buffer pool every other consumer of that element type shares; it now releases only its own buckets. ANaNor infinite cell size is also rejected instead of collapsing the grid (#642). - Fix
Circle,Sphere,Line2D,Line3DandPoolFrequencyStatisticscomparing equal while hashing apart, so a value could vanish from the set it had just been added to. Equality is exact now; useApproximatelyEqualsfor a tolerance (#639). - Fix
Attributehashing by reference identity whileEqualscomparedCurrentValue, which put two equal attributes in different dictionary buckets. It hashesCurrentValuenow (#639). - Fix
AttributeEffect.EqualsignoringperiodicEffects,behaviorsand all four stacking fields, and its hash ignoring the name it compares. Equality reads every authored field now, and the hash reads the managed ones (#639). - Fix
CosmeticEffectDatahashing its component count whileEqualscompared a deduplicated type set, so one copy of a component and two compared equal and hashed apart (#639). - Fix an effect's stack key becoming unreachable once the effect asset was destroyed. The key compared managed identity and hashed through Unity's own
GetHashCode, which collapses on destruction (#639). - Fix
default(RandomState)hashing zero while comparing equal tonew RandomState(0). The hash comes from the membersEqualscompares, never from the value a payload carried, and skips a gaussian the state says it does not hold (#639). - Fix
AnimationEventEqualityComparer.GetHashCode(null)throwing while itsEqualsreported two nulls equal, which madeHashSet<AnimationEvent>.Add(null)fail. Null hashes to zero (#639). - Fix
SerializableType.Equals(null)andSerializableNullable<T>.Equals(null)answering true. A boxed value type is a real object, soObject.Equalsrequires false; test emptiness withIsEmptyorHasValue(#639). - Fix effect teardown that ran your callbacks before the handle was detached: one that removed, re-applied or queried its effect saw it still active, recursed, corrupted a pooled buffer, or threw and stranded the tags after it.
ApplyEffectrefuses aStackpastmaximumStacks. See Removal Is Two-Phase (#640). - Fix
IRandom.Next()andNextLong()returningint.MaxValueandlong.MaxValue, one value outside their documented[0, max)range. Values from those two methods change for a given seed; rawNextUint/NextUlongstreams and saved generator state do not (#638). - Fix guards a destroyed object walked straight through:
?.and??compare against CLR null, soTagHandler,EffectHandler,SingletonAutoLoader, two property drawers and the settings asset acted on effects, windows and assets that were already gone (#621). - Fix
RuntimeSingleton<T>.Instancehanding back a live-but-inertGameObjectwhen the singleton component could not be attached. The half-built object is destroyed andInstancereturns null instead. (#629). - Fix relational component assignment falling back to reflection after its metadata cache was destroyed: a destroyed
_metadataCachefield skipped the liveAttributeMetadataCache.Instanceinstead of using it. (#629). - Fix the Animation Creator treating a frame index too large for an
intas index0, where it collided with a real frame0. Such a name is no longer read as an indexed frame. (#629). - Fix reads of
TryXxxoutvalues whose call was never tested, including aRollingHighWaterMarkcleanup that could subtract a sample nobody wrote and then loop forever (#629). - Fix two documentation examples that could not work as printed: the enum display-name sample imported
Core.Attributerather thanCore.Attributes, and thelink.xmlsample preserved an assembly name that does not exist, which strips silently (#441). - Fix fifteen broken documentation links on
AssetDatabaseBatchScope: its<see cref>references toAssetDatabase.Refresh,CreateAssetandImportAssetnamed an ambiguous overload or an unresolvable type, so an IDE linked the wrong overload or nothing (#594). - Fix the IntelliSense tooltip on ten public
ReflectionHelpersdelegate factories, which carried two<summary>tags and showed the vaguer one (#441). - Fix zero-valued
ValueTuplecomponents and fixed-width map keys being omitted by WallstopProto where protobuf-net writes them explicitly, including enum tuple map keys (#399). - Fix
string.FromBase64()inventing replacement-character text when the decoded bytes are not valid UTF-8; corrupt payloads now return an empty string instead (#580). - Fix entering Play Mode destroying scene-authored
RuntimeSingletoncomponents before their scene starts (#582). - Fix double allocation when JSON-deserializing arrays: array growth rents from the shared pool and collection property names match without a throwaway string (#504).
- An
AttributeEffectauthoring mistake --Instantwith periodic or behaviour data, or an unassigned cosmetic entry -- is now reported once per effect and in the Inspector, not on every application. Each report rendered the whole effect to JSON, measured at 20.5 us (#567). - Fix
Attribute.CurrentValuereporting the wrong number in the editor: outside play mode it discarded the cached value, so an attribute deserialized while buffed lost the buff, and in play mode an Inspector edit to the base value left the cache stale (#569). - Fix a single
[SiblingComponent]or[ChildComponent]field binding a disabled component whenIncludeInactive = false. With two candidates on one object and the first disabled, the disabled one was assigned instead of the enabled one behind it (#529). - Fix a second
awaitof the sameAsyncOperationstopping the first one resuming. Two coroutines or tasks awaiting oneSceneManager.LoadSceneAsynchandle now both continue; previously only the last to register did. - Fix one refused protobuf surrogate registration disabling every registration after it, so
Vector3,ColorandBoundssilently encoded with different bytes. Each is now independent and names the type it could not register. - Fix
UnityRandomsaves and copies losing engine position or pending Gaussian draws. Snapshots preserve sampling caches; restoring also restores Unity's shared random state. See Random Generators (#521, #728). - Fix protobuf-net writing a different payload than WallstopProto for a zero-initialized
FastVector2IntorFastVector3Int. The surrogate mirrored the cached hash throughGetHashCode()rather than the stored field, so the two encoders disagreed on the origin (#309). - Fix
default(FastVector2Int)anddefault(FastVector3Int)comparing unequal to the origin they describe. An array element, an unset field or a dictionary miss did not matchnew FastVector2Int(0, 0), so a set held the origin cell twice. Wire format is unchanged (#309). - Fix
default(CacheStatistics)anddefault(PoolStatistics)comparing unequal to an all-zero snapshot, for the same reason (#309). - Fix
PoolStatisticshashing the three rates it compares with a tolerance, so two snapshots that were equal could hash differently and a set held both (#309). - Fix generator metadata that named the wrong algorithm:
LinearCongruentialGeneratoris the Numerical Recipesranqd1LCG, not Park-Miller;RomuDuomatches neither published ROMU duo variant;IllusionFlowis not a PCG or xorshift hybrid (#509). - Fix the license recorded for the ROMU algorithm behind
RomuDuo: it is Apache 2.0, not CC0 (#509). - Fix
SortByName()andScriptableObjectSingleton<T>.Instancethrowing on a name whose trailing digits do not fit anint, such as a timestamp, or are not ASCII digits. Such names now order correctly, and a suffix of any length is compared without being parsed (#386). - Fix
string.Reverse()destroying emoji and other non-BMP characters: it split every surrogate pair, so the result encoded as replacement characters. Reversing twice now returns the original (#386). - Fix
string.Truncate()returning a result longer than the limit it was given when the ellipsis did not fit, and cutting characters in half. The result now always fits and is always valid text (#386). - Fix
SerializedStringComparerthrowing fromEqualsandGetHashCodewhen its serialized mode held an unrecognized value. It falls back to ordinal comparison, and hashes null instead of throwing (#386). - Fix fourteen comparables ordering null last instead of first, disagreeing with every
IComparablein the framework and with each other.StringWrapperalso ordered by hash code rather than by its string (#386). - Fix
XoroShiroRandombeing documented as xoshiro128**. It is xoroshiro128+, whose lowest bits are the weak ones and the half this returns, so its rating is now Good (#285). - Fix
NextGuid()andNextWGuid()throwing on any generator restored from a protobuf payload read by protobuf-net. Twelve generators were affected, so the first GUID drawn after loading a save crashed (#492). - Fix
IllusionFlow,PcgRandom,RomuDuo,XorShiftRandomandXoroShiroRandomreplaying a different sequence than the one saved when their whole serialized state was at its default (#492). - Fix dead or crashing streams when restored shared reservoir, PCG, StormDrop, PhotonSpin or SystemRandom state is invalid. Repair and PhotonSpin warmup now run after construction or deserialization, not on every draw (#492, #503).
- Fix
Helpers.EnumeratePrefabsandEnumerateScriptableObjectssearching folders the caller never named when the asset paths were passed as anything other than astring[](#482). - Fix
[WShowIf]never matching a member of aulong-backed enum abovelong.MaxValue, which hid the field it was meant to reveal (#346). - Fix a chosen text colour on a
[WButton]or[WEnumToggleButtons]palette entry being overwritten whenever its red, green and blue were all zero, so opaque black was replaced by the auto-computed colour and any transparency was discarded. Existing entries keep the colour they were showing (#476). - Fix settings color keys being stored case-sensitively while every reader matched them without regard to case, so
"Save"and"save"were two palette entries on disk and one entry to every reader (#476). - Fix
PositiveModreturning a negative result -- the one thing it promises never to do -- for any maximum above 2^30 (int) or 2^62 (long), and roundingfloatanddoublevalues that were already inside the range.WrappedAddnow wraps the real sum when adding overflows. - Fix
floatanddoublePositiveModreturning0for every input when the maximum is 1, which made the normalized-phase case it documents useless:5.5f.PositiveMod(1f)is0.5f. It could also return the maximum itself when the remainder was too small to survive being added to it. - Fix
SerializableSortedDictionaryskipping the rebuild of its serialized arrays when two stored keys compare equal under its own comparer, which could leave a newly added key unsaved (#476). - Fix
SerializableDictionary,SerializableSortedDictionaryandSerializableHashSetwriting back entries their own comparer holds as one, so a case-insensitive dictionary could save two entries that merged into one on the next load (#476). - Fix
SortAlgorithm.PowerandSortAlgorithm.PowerPlusreordering elements that compare equal, which both are documented as never doing. Any list whose equal elements sit inside a descending stretch was affected (#461). - Fix
SerializableDictionaryandSerializableSortedDictionarydiscarding a comparer they were constructed with, so seeding one withStringComparer.OrdinalIgnoreCase— the documented way to make it case-insensitive — did nothing (#472). - Fix the settings window treating any color within about two and a half 8-bit steps of the factory default as untouched, so a color you had deliberately changed could be overwritten by the suggested palette (#472).
- Fix the inspector repainting on every settings check once a custom color held a
NaNchannel, and missing the removal of a color key present in any snapshot but the first (#472). - Fix a log format naming a color Unity cannot parse —
$"{value:#notacolor}"— emitting a rich text tag the console shows as literal markup instead of the value. The value is now logged undecorated (#473). - Fix
LayeredImagediscarding one alpha level more than itspixelCutoffasks for at some cutoffs and not others, so it kept different pixels than the sprite tools at the same cutoff (#473). - Fix the multi-file selector's list background being white at 15% opacity rather than the opaque dark panel it was written as, and the sprite animation creator's selected-thumbnail border coming back out of gamut and more opaque than its fill.
Color * floatscales alpha too (#473). - Fix
[WButton]and the serialized collection drawers labelling buttons with the less readable of black and white on 22.9% of colors. The shipped green "Add" button was at 2.84:1, below the 3:1 large-text floor; it is now 7.40:1 (#471). - Fix a dark
[WButton]showing no hover or press feedback: darkening a color already near black clamped all three states to the same color (#471). - Fix
Serializer.ProtoDeserialize()refusing to read back whatProtoSerialize()wrote for a value whose fields are all at their defaults —Vector3.zero,Color.clear,Quaternion(0,0,0,0)and any such contract encode to zero bytes, which was rejected as empty input. Anullpayload is still refused (#474). - Fix
TextureScale.Bilinear()andPoint()shifting a scaled texture half a texel toward the origin: an upscale never reached the source's brightest pixel and a symmetric image downscaled asymmetrically (#470). - Fix
TextureScale.Bilinear()and the Image Blur tool pulling a fully transparent pixel's color into its visible neighbors, so red beside transparent green produced a yellow edge. Opaque textures are unchanged (#470). - Fix the sprite sheet extractor's preview thumbnails sampling half a texel toward the origin (#470).
- Fix the
WButtonandWEnumToggleButtonsstyle and texture caches growing without bound as inspector colours change: two colours the caches called equal were stored under different keys, so every repaint could add another entry (#466). - Fix a
WButtonhover or pressed colour derived from an out-of-gamut palette colour coming back with a negative channel (#466). - Fix
IRandom.NextColorInRange()returning the varied colour fully opaque, discarding the alpha of the base colour (#466). - Fix
IRandom.NextColorInRange()throwingArgumentExceptionfor a variance of zero or a negative variance, and returning black for one that is not a finite number (#466). - Fix
Color.ChangeColorBrightness()returning a colour whose channels are allNaNwhen the correction factor isNaN(#466). - Fix a save holding an unset
WGuidfailing to load: JSON wrote the empty GUID and then refused to read it back (#437). - Fix a JSON payload with
mingreater thanmax, or withmaxmissing, crashing aRange<T>load withArgumentExceptioninstead of reporting corrupt data (#437). - Fix a
Gradientwith more than eight colour or alpha keys silently losing the extras and filling the player log with errors. The payload is now refused (#437). - Fix
Serializer.JsonStringifyandJsonSerializefailing on aType, at the root of a graph or behind anobjectmember, despite the shippedTypeconverter (#437). - Fix an unattributed field joining the wrong group when a type declares more than one
[WGroup], and a bare[WGroupEnd]closing a group other than the one it follows. Auto-include now targets the most recently declared group, and a bare end closes every open group (#455). - Fix a
SerializableDictionary's "Add entry" Value field being drawn 8.5px left of its Key field wherever the inspector indents it (#284). - Fix a watcher on a non-
GameObjecttype loading every imported prefab, which is whereSendMessage cannot be called during Awake, CheckConsistency, or OnValidatecame from. A sub-asset nested inside a.prefabno longer matches a watcher on its type (#280). - Fix the asset-change watcher deserializing assets during import to decide whether a path holds a watched type, which produced the same warning. The decision is now made from asset metadata (#439).
- Fix
[DetectAssetChanged]crashing headless editors; it no longer initializes in batch mode (#327). - Fix
SerializableDictionary<TKey, List<TValue>>and its sorted counterpart saving their keys and none of their values. Dictionaries with other value types keep exactly the same saved data (#314, #348). - Report
SerializableHashSet<List<TValue>>in the Inspector instead of drawing a column that persists nothing: a list compares by reference, so such a set already treats equal contents as distinct elements (#314, #354). - Fix the Inspector drawing an empty value column instead of an error for dictionary values no wrapper repairs, such as
List<List<T>>and jagged arrays. Sorted dictionaries are covered too (#357). - Fix
StartFunctionAsCoroutine()andExecuteOverTime()stopping forever the first time their action threw. Both now report the failure against the owning object and keep running (#359). - Fix disabled logging still evaluating its receiver and building its message, so
MySingleton.Instance.Log($"…")created the singleton and allocated in a release build. DefineENABLE_UBERLOGGINGto keep logging — see Logging Extensions (#350). - Fix
GetRandomPointInCircle()andGetRandomPointInSphere()returning points outside the shape far from the origin: at world coordinate 1,000,000 with radius 0.05, half of them were outside. - Fix
NativePcgRandomproducing worse randomness than every other generator:NextFloat()could return exactly 1,NextLong()could return a negative,NextUint(0)threw, and most seeds built a shortened-period stream. Sequences for a given seed have changed (#282). - Fix
ToCachedName()andToDisplayName()falling back to a dictionary for any signed enum with a negative member, andsbyteenums allocating a 256-entry cache (#339). - Fix
EditorCacheHelper.GetEnumDisplayName()rebuilding the enum's value array on every call. Display names are unchanged. - Fix
[WEnumToggleButtons]throwingOverflowExceptionon an enum with a negative member and taking the Inspector down. The Odin drawer rendered such members as "None" and wrote that back (#339). - Fix an interrupted
Serializer.WriteToJsonFile()destroying the previous save. Every JSON file write now stages and swaps, and creates missing directories (#319). - Fix a copied
PooledResource<T>orPooledArray<T>returning the same instance to the pool twice, so two live rentals shared one buffer. Every disposable scope in the package is now disposed at most once, at no allocation cost (#358). - Fix a double-disposed
AssetDatabaseBatchScopeending an outer scope's batch early, leaving the rest of its asset writes unbatched (#358). - Fix
WallstopArrayPool<T>andWallstopFastArrayPool<T>allocating 32 bytes on every rent (#367). - Fix
Color.ToHex()truncating each channel instead of rounding it, so it disagreed with Unity's ownColorUtility.ToHtmlStringRGBA()on half of all colors and could not returnFFfor any channel below exactly 1 (#466). - Fix the sprite cropper and pivot adjuster selecting different pixels than the sheet extractor at the same alpha cutoff, and two transparency scorers disagreeing with their twenty siblings by one alpha level (#466).
- Fix the Inspector's solid-texture cache holding two entries for one color: colors a hair either side of a channel boundary compared equal and hashed apart (#466).
- Fix
GetAverageColor(ColorAveragingMethod.Dominant)returning a channel above 1 for a saturated color - a dominant white came back as 1.0039 (#466). - Fix
GetAverageColor(ColorAveragingMethod.Weighted)returning a fully transparent color for opaque black pixels, which weigh nothing under luminance weighting (#466).
Install
- Import the attached
.unitypackageinto a Unity project, or - install
com.wallstop-studios.unity-helpers@3.6.0from npm / OpenUPM through Unity Package Manager.
The release includes the npm tarball and the .unitypackage, each with a .sha256 checksum.