Use this file to discover all available pages before exploring further.
[FixedEchoStructure] tells Prowl.Echo that a type’s field order is stable and will never change. Instead of writing a named compound (dictionary-like) with field name strings, Echo serializes the fields positionally into a List — no names, just values in order. This produces smaller output and eliminates string key encoding during both write and read.
Vector2, Vector3, Quaternion, Color — types where the fields are fixed by definition and you own the type completely
Network packets
Message structs where the sender and receiver are always compiled from the same code and fields never drift
If you add, remove, or reorder fields on a type marked [FixedEchoStructure], all existing serialized data becomes unreadable. Only apply it to types you control completely and whose shape will not change.
When both [GenerateSerializer] and [FixedEchoStructure] are applied, the generator emits a different serialize/deserialize pattern than the default named-compound path.Serialize — builds a list and assigns it to compound:
public void Serialize(ref EchoObject compound, SerializationContext ctx){ var list = EchoObject.NewList(); list.ListAdd(new EchoObject(X)); list.ListAdd(new EchoObject(Y)); list.ListAdd(new EchoObject(Z)); compound = list;}
Deserialize — reads by position, validates the EchoType is List, and throws on field count mismatch:
public void Deserialize(EchoObject value, SerializationContext ctx){ if (value.TagType != EchoType.List) throw new System.InvalidOperationException("Expected list for fixed structure deserialization"); var listValue = (System.Collections.Generic.List<EchoObject>)value.Value!; if (listValue.Count != 3) throw new System.InvalidOperationException($"Field count mismatch. Expected 3 but got {listValue.Count}"); X = listValue[0].FloatValue; Y = listValue[1].FloatValue; Z = listValue[2].FloatValue;}
Any mismatch in field count throws immediately, which makes schema violations easy to detect.
[FixedEchoStructure] also works without source generation. The FixedStructureFormat formatter handles it via reflection, reading and writing fields in declaration order (sorted by MetadataToken). This is slower than source-generated code but still produces the positional List format:
// Works without [GenerateSerializer], but uses reflection[FixedEchoStructure]public struct Color{ public float R; public float G; public float B; public float A;}
Add [GenerateSerializer] alongside [FixedEchoStructure] whenever possible. The source-generated path avoids reflection entirely and is the fastest option available.
For the absolute smallest serialized size, combine fixed structure with the binary format’s Size encoding mode:
var options = new BinarySerializationOptions{ EncodingMode = BinaryEncodingMode.Size // LEB128 integer encoding};var v = new Vector3 { X = 1.0f, Y = 2.5f, Z = -0.5f };EchoObject echo = Serializer.Serialize(v);using var stream = new MemoryStream();using var writer = new BinaryWriter(stream);echo.WriteToBinary(writer, options);
This combination — positional list + LEB128 encoding — produces the most compact representation Echo can generate.
Because [FixedEchoStructure] serializes by position rather than by name, schema evolution requires special care:
[FormerlySerializedAs] has no effect — there are no names to remap
Adding a field in the middle of the list breaks deserialization of all existing data
Only appending fields to the end is safe if you also update the reader
For types that need to evolve over time, use the default compound serialization instead. Reserve [FixedEchoStructure] for types that are truly immutable in structure.