Collection schemas
FateWeaver encodes three shapes of collection: arrays, maps, and enums.
Arrays, lists and sets
ArraySchema<T> writes a 4-byte element count followed by each element encoded with the element
schema. List<T>, Set<T> and Collection<T> fields use the same wire format, so a List<Double>
field is byte-identical to a Double[] field.
Sets are written in their own iteration order. That order is stable for a LinkedHashSet and
arbitrary for a HashSet. Either way, it means nothing in the log.
Maps
MapSchema<K, V> writes a 4-byte entry count followed by each key/value pair. Keys and values are
encoded by their own nested schemas, and keys don't have to be strings.
Logging a collection without a schema
In Kotlin you can skip the schema. log is a reified extension, so it reads the static type at the
call site, type arguments included. A Map<String, Double> is just a LinkedHashMap at runtime, so
without the static type the key and value types would be gone.
Two cases still fail.
If the static type is Any, or anything else without type arguments, inference falls back to the
runtime class. So val v: Any = mapOf(...) is rejected. The key and value types are already gone,
and guessing them from the first entry would lock one sample's shape into the path.
A path's schema is also fixed by its first log. Log a Map<String, Double> and then a
Map<String, Int> to the same name, and the second call prints a warning and is skipped. The
warning names both types.
FateLogWriter has the same API outside FTC. Import its extension from gay.zharel.fateweaver.log.
Nulls
A nullable type infers an OptionalSchema, encoded as a presence byte followed by the value when
there is one. There is nothing to turn on. Declare the property nullable and you are done.
An absent value costs one byte. A present one costs one byte more than it would unwrapped. Non-null fields are unchanged, so your existing logs don't get any bigger.
A null in a field that isn't declared nullable is still an error that names the field. You will see this with Java fields and Kotlin platform types, where the declaration says non-null and the value disagrees.
Decoder compatibility
Optional values use schema tag 9. A decoder that predates the tag cannot skip past it. It stops reading at the first channel that uses one.
Requirements
The format has a fixed shape, so collections must be homogeneous and fully typed:
Map<String, Any>,List<*>and raw types are rejected when the schema is built. The error names the field and type.- Nullable element, key or value types such as
List<String?>andMap<String, Double?>are fine. They infer anOptionalSchema, which costs one byte per element. - Type arguments must be concrete. A generic holder like
class Box<T>(val items: List<T>)can't be inferred. Register a schema for the concrete type instead.
For a heterogeneous map, or a type you don't control, inference won't work. Build the schema by hand
with CustomStructSchema, or register one for the class with FateSchema.registerSchema. A
registered schema always beats inference.
Decoder compatibility
MapSchema uses schema tag 8, which was added after the first release of the format. Log entries
are not length-prefixed, so a decoder that predates the tag cannot skip it and stops reading
there. Update external tooling before you produce logs that contain maps.