Class AbilityObserverBroadcastSerializers
Hand written wire format for AbilityActivatedBroadcast.
public static class AbilityObserverBroadcastSerializers
- Inheritance
-
AbilityObserverBroadcastSerializers
- Inherited Members
Remarks
A cast broadcast goes to every observer of the caster, and a channelled ability sends one per tick it is held, so its size is paid far more often than its field count suggests. The generated serializer wrote all eleven fields unconditionally — three world vectors, a quaternion and two full ticks — for every mode, about 65-73 bytes. Most of that is dead weight in any given mode.
Two observations shape this format:
- A Camera spawn pose is a pure function of the aim origin, the aim direction and the ability's range — all of which the observer either receives or reads off the template. So Camera carries aim (12 + up to 5 bytes) and omits the pose; every other mode carries the pose (12 + 8 bytes) and omits the aim, because those poses come off transforms the observer holds interpolated and cannot reproduce.
- SpawnTick and ServerTick are close together in practice — identical for a server driven NPC, and a fraction of a second apart for a player, whose replicate tick runs a buffer ahead of the server's. The spawn tick therefore travels as a signed 16-bit offset, with a full width fallback for the case where the two domains are genuinely far apart (a client that has just resynchronised, or an unset tick).
The mode and the two shape flags share one byte. Object ids and ticks travel through FishNet's packed encoding, which is genuinely variable width for them. The FULL-ENTROPY fields do not: the packed 32-bit form is a seven-bit-per-byte varint, so a value that uses the whole range costs FIVE bytes where unpacked costs four. The seed and the packed aim word are therefore written unpacked, each noted at the point it happens.
Discovered by FishNet's codegen through the Write*/Read* naming convention and
applied across assemblies because the struct carries [UseGlobalCustomSerializer].
Fields
MAX_LEARNED_EVENTS
Hard cap on the crafted event ids carried by one AbilityLearnedObserverBroadcast.
public const int MAX_LEARNED_EVENTS = 64
Field Value
Remarks
An ability's event count is bounded by its template's AdditionalEventSlots plus
the events baked into the template, which is a handful. The cap exists so the count
fits one byte and so a malformed or hostile message cannot make the reader allocate an
arbitrarily large array before the stream runs out.
Methods
ReadAbilityActivatedBroadcast(Reader)
Reads an AbilityActivatedBroadcast written by the method above.
public static AbilityActivatedBroadcast ReadAbilityActivatedBroadcast(this Reader reader)
Parameters
readerReader
Returns
Remarks
Fields that a given mode does not carry come back at their defaults — Vector3.zero,
Quaternion.identity, and -1 for an absent target — which is exactly what the
receiving side expects: the handler branches on SpawnMode
and only reads the fields that mode carries.
ReadAbilityLearnedObserverBroadcast(Reader)
Reads an AbilityLearnedObserverBroadcast written by the method above.
public static AbilityLearnedObserverBroadcast ReadAbilityLearnedObserverBroadcast(this Reader reader)
Parameters
readerReader
Returns
Remarks
An over-large count is DISCARDED, not clamped. Clamping used to read
MAX_LEARNED_EVENTS ids and leave the rest of the declared ones in the
stream — and FishNet does not reposition the reader after a broadcast handler runs
(ClientManager.ParseBroadcast uses the length prefix only to SKIP a key it has no
handler for), so every byte left behind misaligned whatever shared the datagram. See
CharacterBuffsBroadcastSerializer.ReadCharacterBuffsBroadcast, which spells the
same trap out at length. The discard is returned with an unresolvable caster id so the
handler drops it outright rather than filing an events-less ability.
WriteAbilityActivatedBroadcast(Writer, AbilityActivatedBroadcast)
Writes an AbilityActivatedBroadcast in its mode-shaped form.
public static void WriteAbilityActivatedBroadcast(this Writer writer, AbilityActivatedBroadcast value)
Parameters
writerWritervalueAbilityActivatedBroadcast
WriteAbilityLearnedObserverBroadcast(Writer, AbilityLearnedObserverBroadcast)
Writes an AbilityLearnedObserverBroadcast.
public static void WriteAbilityLearnedObserverBroadcast(this Writer writer, AbilityLearnedObserverBroadcast value)
Parameters
writerWritervalueAbilityLearnedObserverBroadcast
Remarks
Hand written for two things the generated array serializer cannot do: bound the count against a malformed message before an array is allocated for it, and treat a null list as an empty one rather than spending a sentinel on it.
It is not written for the length prefix, and a remark here used to claim it was —
that FishNet's generated array serializer "writes a four-byte length". It does not:
Writer.WriteArray writes the count with WriteSignedPackedWhole, which is one
byte for any count this message can carry. The single byte below matches that; the reasons
above are what earn the hand-written form.
The template id and every event id are unpacked. Both are
(typeName + assetName).GetDeterministicHashCode() values that occupy the whole
32-bit range, so FishNet's signed-packed form spends five bytes where four suffice — the
same trade WriteTo(Writer) makes.