OBOR is a binary object format to encode and decode objects in the Odin Programming language. It supports two ways of encoding and decoding:
schemaless and schemaful. With schemaful encoding, a type registry is built using RTTI and written to the start of the output bytes.
This allows for decoding to a different type by diffing two schemas and figuring out migration policies. For example, you can encode a struct {bar: []Bar, a: f32, b: string, } and decode the resulting bytes into a struct {b: string, foo: Foo, a: f32 }. OBOR generates a policy table that makes sure the a: f32 and b: string fields are decoded to the right offsets in the decoded struct and the bar: []Bar field is skipped in the encoded bytes.
Here is how it works: Let's say I have this type Inventory:
Inventory :: struct {
max_weight: u32,
items: [dynamic]Item,
}
Item :: struct {
name: string,
weight: u32,
is_magic: bool,
}
The constructed schema is like a lightweight version of the TypeInfo of all involved types:
0 -> size: 48, align: 48, is_copy: false, hash: 861660765213436421
Kind: StructTy{fields = [StructField{ty_idx = 1, offset = 0, name = "max_weight"}, StructField{ty_idx = 2, offset = 8, name = "items"}]}
1 -> size: 4, align: 4, is_copy: true, hash: 2029349188714305344
Kind: Int
2 -> size: 40, align: 40, is_copy: false, hash: 7409838414103185760
Kind: SeqTy{elem_ty_idx = 3, is_dyn_arr = true}
3 -> size: 24, align: 24, is_copy: false, hash: 14661186651083965018
Kind: StructTy{fields = [StructField{ty_idx = 4, offset = 0, name = "name"}, StructField{ty_idx = 1, offset = 16, name = "weight"}, StructField{ty_idx = 5, offset = 20, name = "is_magic"}]}
4 -> size: 16, align: 16, is_copy: false, hash: 3687799326438297182
Kind: StringTy{is_cstring = false}
5 -> size: 1, align: 1, is_copy: true, hash: 15449794709565471098
Kind: Bool
It is encoded to the output buffer first, to derive how the rest of the bytes are encoded. Imagine we have this example value:
Inventory {
max_weight = 230,
items = {
Item{"sword", 8, false},
Item{"apple", 3, false},
Item{"magic stick", 4, true}
},
}
Knowing the schema allows it to be pretty consicely encoded as these bytes:
[230, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 115, 119, 111, 114, 100, 8, 0, 0, 0, 0, 5, 0, 0, 0, 97, 112, 112, 108, 101, 1, 0, 0, 0, 0, 11, 0, 0, 0, 109, 97, 103, 105, 99, 32, 115, 116, 105, 99, 107, 4, 0, 0, 0, 1]
Here is a breakdown of the bytes:
max_weight: 230, 0, 0, 0
len(items): 3, 0, 0, 0, 0, 0, 0, 0
len("Sword"): 5, 0, 0, 0,
"Sword": 115, 119, 111, 114, 100,
weight: 8, 0, 0, 0
is_magic: 0
len("apple"): 5, 0, 0, 0,
"apple": 97, 112, 112, 108, 101,
weight: 1, 0, 0, 0
is_magic: 0
len("magic stick"): 11, 0, 0, 0,
"magic stick": 109, 97, 103, 105, 99, 32, 115, 116, 105, 99, 107,
weight: 4, 0, 0, 0
is_magic: 1