<<
path:
root/public/blog.git/html/modules/osor_serialization/module.jai
blob: 982a9b3b11d58b99d24d1e868b9969ce770063eb
[raw]
[clear marker]
5#module_parameters(CLEAN_TABLES_UP := true);
9serialize :: (input : *$T) -> []u8
11 ALIGNED_INPUT_SIZE :: #run align_for_serialization(size_of(T));
13 dynamic_memory_required := cast(s64)handle_dynamic_memory(input, null, .COUNT);
16 buffer.count = ALIGNED_INPUT_SIZE + dynamic_memory_required;
17 buffer.data = alloc(buffer.count);
18 assert(buffer.data == align_for_serialization(buffer.data));
19 memset(buffer.data, 0, buffer.count);
21 dynamic_memory : []u8;
22 dynamic_memory.count = dynamic_memory_required;
23 dynamic_memory.data = buffer.data + ALIGNED_INPUT_SIZE;
25 output := cast(*T)buffer.data;
26 memcpy(output, input, size_of(type_of(input.*)));
28 dynamic_memory_pointer := handle_dynamic_memory(output, dynamic_memory.data, .COPY_AND_ENCODE);
29 assert(dynamic_memory_pointer == dynamic_memory.data + dynamic_memory.count);
36deserialize :: (bytes : []u8, $Output_Type : Type) -> *Output_Type
38 info := type_info(Output_Type);
39 result := cast(*Output_Type) bytes.data;
40 inline decode_dynamic_memory(result);
53 cleanup_for_serialization :: (table : *Table)
55 DEFAULT_VALUE :: table.Entry.{};
57 if it.hash < FIRST_VALID_HASH
58 memcpy(it, *DEFAULT_VALUE, size_of(type_of(DEFAULT_VALUE)));
64handle_dynamic_memory :: (element : *$T,
65 dynamic_memory_pointer : *u8,
66 $mode : enum { COUNT; COPY_AND_ENCODE; },
67 $allow_pointer := false) -> next_dynamic_memory_pointer : *u8
69 #insert #run () -> string
71 quick_string_builder();
72 p("// Handling dynamic memory for serializing %\n", T);
73 info := cast(*Type_Info)type_info(T);
77 #if CLEAN_TABLES_UP if same_polymorph_source(info, type_info(Table(int,int)))
80 cleanup_for_serialization(element);
85 if #complete info.type ==
88 #if mode == .COPY_AND_ENCODE
91 memcpy(dynamic_memory_pointer, element.data, element.count); // copy original data
92 element.data = xx (cast(u64)dynamic_memory_pointer - cast(u64)element); // make pointer, point to the dynamic buffer (encoded)
96 dynamic_memory_pointer = align_for_serialization(dynamic_memory_pointer + element.count); // commit the dynamic allocation
102 array_info := cast(*Type_Info_Array)info;
103 element_size := array_info.element_type.runtime_size;
104 if #complete array_info.array_type ==
107 #if mode == .COPY_AND_ENCODE
110 element.allocated = element.count;
111 element.allocator = Serialized_Data_Allocator;
119 array_data_size := element.count * size_of(type_of(element.data.*));
121 #if mode == .COPY_AND_ENCODE
124 memcpy(dynamic_memory_pointer, element.data, array_data_size); // copy original array elements
125 element.data = xx (cast(u64)dynamic_memory_pointer - cast(u64)element); // encode the data pointer
126 data_start := cast(type_of(element.data)) dynamic_memory_pointer; // get a pointer to the data to iterate
132 data_start := element.data; // if we're not copying we just iterate throught the original data
136 dynamic_memory_pointer = align_for_serialization(dynamic_memory_pointer + array_data_size); // commit the dynamic allocation
137 for 0..element.count-1
139 dynamic_memory_pointer = handle_dynamic_memory(data_start + it, dynamic_memory_pointer, mode);
146 for 0..element.count-1
148 dynamic_memory_pointer = handle_dynamic_memory(element.data + it, dynamic_memory_pointer, mode);
156 struct_info := cast(*Type_Info_Struct)info;
158 replace := needs_serialization_replacement(struct_info);
162 replaced_element := element.serialization_replacement(element);
163 #assert(size_of(T) == size_of(*element.Replacement_Type));
168 pointer_to_replaced_element := *replaced_element;
169 dynamic_memory_pointer = handle_dynamic_memory(*pointer_to_replaced_element, dynamic_memory_pointer, mode, allow_pointer = true);
175 (cast(**element.Replacement_Type)element).* = *replaced_element;
176 dynamic_memory_pointer = handle_dynamic_memory(cast(**element.Replacement_Type)element, dynamic_memory_pointer, mode, allow_pointer = true);
182 for member : struct_info.members
184 if !(member.flags & .CONSTANT)
186 allow_pointer := false;
187 for member.notes if it == "allow_single_element_pointer_serialization" then allow_pointer = true;
189 dynamic_memory_pointer = handle_dynamic_memory(cast(*type_of(T.%1))((cast(*u8)element) + %2),
190 dynamic_memory_pointer,
193 DONE, member.name, member.offset_in_bytes, allow_pointer);
201 if info != type_info(Allocator_Proc)
203 compiler_report(tprint("Only Allocator procedures can be in serialized structs, and % isn't one", get_type(info)));
216 element_size := size_of(type_of((element.*).*));
218 #if mode == .COPY_AND_ENCODE
221 memcpy(dynamic_memory_pointer, element.*, element_size); // copy original data
222 (cast(*u64)element).* = (cast(u64)dynamic_memory_pointer - cast(u64)element); // make pointer, point to the dynamic buffer (encoded)
223 element_data := cast(type_of(element.*)) dynamic_memory_pointer;
229 element_data := element.*;
233 dynamic_memory_pointer = align_for_serialization(dynamic_memory_pointer + element_size); // commit the dynamic allocation
234 dynamic_memory_pointer = handle_dynamic_memory(element_data, dynamic_memory_pointer, mode);
240 log_error("We only allow null-pointers to be serialized, and % wasn't!", T);
245 } // if element.* != null
252 INTERNAL_TYPE :: #run find_variant_contained_type(type_of(element.*));
253 dynamic_memory_pointer = handle_dynamic_memory(cast(*INTERNAL_TYPE)element, dynamic_memory_pointer, mode, allow_pointer);
262 case .POLYMORPHIC_VARIABLE;
264 // Nothing to do for these, they will just be memcpy-ed
268 case .TYPE; #through;
269 case .CODE; #through;
270 case .UNTYPED_ENUM; #through;
271 case .UNTYPED_LITERAL; #through;
272 case .OVERLOAD_SET; #through;
274 compiler_report(tprint("Type % cannot be serialized cause it's a %", T, info.type));
276 return builder_to_string(b);
279 return dynamic_memory_pointer;
284decode_dynamic_memory :: (element : *$T)
286 #insert #run () -> string
288 quick_string_builder();
289 p("// Decoding dynamic memory for deserializing %\n", T);
290 info := cast(*Type_Info)type_info(T);
295 element.data = cast(*u8)(cast(u64)element + cast(u64)element.data);
301 array_info := cast(*Type_Info_Array)info;
302 element_type := array_info.element_type;
303 do_decode := !(element_type.type == .INTEGER || element_type.type == .FLOAT || element_type.type == .BOOL);
304 if #complete array_info.array_type ==
308 element.allocated = element.count;
309 element.allocator = Serialized_Data_Allocator;
316 element.data = xx (cast(u64)element + cast(u64)element.data);
325 for 0..element.count-1
327 inline decode_dynamic_memory(element.data + it);
336 struct_info := cast(*Type_Info_Struct)info;
337 replace := needs_serialization_replacement(struct_info);
341 #assert(size_of(T) == size_of(*element.Replacement_Type));
342 pointer_to_replaced_element : **element.Replacement_Type = cast(**element.Replacement_Type)element;
343 inline decode_dynamic_memory(pointer_to_replaced_element);
344 element.* = element.deserialization_replacement(pointer_to_replaced_element.*);
347 for member : struct_info.members
349 if !(member.flags & .CONSTANT)
352 inline decode_dynamic_memory(cast(*type_of(T.%1))((cast(*u8)element) + %2));
353 DONE, member.name, member.offset_in_bytes);
360 if info == type_info(Allocator_Proc)
362 // @@NOTE: We could catch Allocator structs on the .STRUCT codepath
363 // instead, but just in case someone does an allocator procedure by itself
364 // maybe this is better and simpler.
366 element.* = Serialized_Data_Allocator.proc;
374 offset := (cast(*u64)element).*;
377 element.* = cast(T)(cast(u64)element + (cast(*u64)element).*);
378 inline decode_dynamic_memory(element.*);
386 INTERNAL_TYPE :: #run find_variant_contained_type(type_of(element.*));
387 decode_dynamic_memory(cast(*INTERNAL_TYPE)element);
393 return builder_to_string(b);
399Serialized_Data_Allocator :: Allocator.{
400 proc = (mode: Allocator_Mode, size: s64, old_size: s64, old_memory_pointer: *void, allocator_data: *void) -> *void
402 log_error("Can't allocate data on a serialized data structure!");
411needs_serialization_replacement :: (struct_info : *Type_Info_Struct) -> bool
414 found_serialization_replacement := false;
415 found_deserialization_replacement := false;
416 for member : struct_info.members
418 if member.flags & .CONSTANT
420 if member.name == "Replacement_Type" && member.type.type == .TYPE
424 if member.name == "serialization_replacement" && member.type.type == .PROCEDURE
426 found_serialization_replacement = true;
428 if member.name == "deserialization_replacement" && member.type.type == .PROCEDURE
430 found_deserialization_replacement = true;
434 return found_type && found_serialization_replacement && found_deserialization_replacement;
440// @@NOTE: We'd like to handle the alignment of the serialized elements
441// explicitly, so if we're gonna copy an instance of some type into
442// dynamic memory, we'd first align the address via something like
443// an `alignment_of(T)`. However there's no way to do this currently.
445// Also, when we allocate a buffer we also would have to align the allocation
446// and give the user back the unaligned addresse to free it, similar to how
447// `NewArray` works at the time of writing, which kind of sucks as an interface.
448// Otherwise if we just alloc and don't align we'd be further aligning as we
449// encode the dynamic memory and leave things out of whack.
451// So for now, since most defaults allocators align to 8 bytes, we're also gonna
452// align to 8 bytes here. All basic data types and pointers would just work. It'd
453// fail for SIMD registers that require larger alignments, but oh well, we don't have
454// real control of this here, unless we just aligned everything to 512 bits / 64 bytes
455// to account for 512-wide SIMD ops (I believe the largest reasonable used one at the
456// time of writing). But that would also be a waste of space in the types that don't
459// TLDR: We'd love an `alignment_of(T)` or an alignment field in `Type_Info` as well
460// as a way to `alloc(bytes, alignment)`.
462// - Ruben Osorio, 30/08/2025
464align_for_serialization :: (value : $T) -> T
466 return (value + 7) & (~7);
471quick_string_builder :: () #expand
473 `_builder : String_Builder;
474 _builder.allocator = temporary_allocator;
475 `defer reset(*_builder);
477 `p :: (format_string : string, args: ..Any) #expand
479 print_to_builder(*_builder, format_string, ..args);
481 `a :: (the_string : string) #expand
483 append(*_builder, the_string);
489find_variant_contained_type :: ($variant_type : Type) -> Type
491 info := type_info(variant_type);
492 assert(info.type == .VARIANT);
493 variant_info := cast(*Type_Info_Variant)info;
494 return get_type(variant_info.variant_of);
499same_polymorph_source :: (info_a : *Type_Info, info_b : *Type_Info) -> bool
501 find_top_polymorph_source :: (info : *Type_Info) -> *Type_Info
505 if info.type == .STRUCT
507 struct_info := cast(*Type_Info_Struct)info;
508 if struct_info.polymorph_source_struct
510 info = struct_info.polymorph_source_struct;
518 return find_top_polymorph_source(info_a) == find_top_polymorph_source(info_b);