#import "Compiler"; #import "Basic"; #import "String"; #import "Sort"; build_release := false; build :: (main_path: string, game_name: string) { set_working_directory(#filepath); w := compiler_create_workspace("Game"); opts := get_build_options(w); opts.output_type = .EXECUTABLE; opts.output_executable_name = game_name; opts.output_path = "bin/"; new_path: [..] string; array_add(*new_path, ..opts.import_path); array_add(*new_path, "modules"); opts.import_path = new_path; if build_release { set_optimization(*opts, .VERY_OPTIMIZED); } compiler_begin_intercept(w); set_build_options(opts, w); add_build_string(tprint("GAME_NAME :: \"%\";", game_name), w); add_build_file(main_path, w); message_loop(); compiler_end_intercept(w); set_build_options_dc(.{do_output=false}); } // Contains all the auto-generated serialization procedures entity_serialize_proc_string: [..] string; should_serialize :: (type: *Type_Info_Struct, member: Type_Info_Struct_Member) -> bool { if type!= null { if type.name == { case "Vector2"; { if member.name == { case "x"; #through; case "y"; #through; case; return false; } } case "Vector3"; { if member.name == { case "x"; #through; case "y"; #through; case "z"; return true; case; return false; } } case "Vector4"; #through; case "Quaternion"; { if member.name == { case "x"; #through; case "y"; #through; case "z"; #through; case "w"; return true; case; return false; } } } } for member.notes { if to_lower_copy_new(it,, allocator = temp) == "dontserialize" return false; } return true; } generate_member_serialization :: (type: *Type_Info_Struct, builder: *String_Builder, path: string = "") { for type.members { if should_serialize(type, it) { new_path : string; if path.count == 0 { new_path = it.name; } else { new_path = tprint("%1.%2", path, it.name); } if it.type.type == { case .STRUCT; { info_struct := cast(*Type_Info_Struct) it.type; generate_member_serialization(info_struct, builder, new_path); } case .BOOL; #through; case .FLOAT; #through; case .ENUM; #through; case .INTEGER; { print_to_builder(builder, "\tprint_to_builder(builder, \"%: \%\\n\", e.%);\n", new_path, new_path); } } } } } generate_member_deserialization :: (type: *Type_Info_Struct, builder: *String_Builder, path: string = "") { for type.members { if should_serialize(type, it) { new_path : string; if path.count == 0 { new_path = it.name; } else { new_path = tprint("%1.%2", path, it.name); } if it.type.type == { case .STRUCT; { info_struct := cast(*Type_Info_Struct) it.type; generate_member_deserialization(info_struct, builder, new_path); } case .BOOL; #through; case .FLOAT; #through; case .INTEGER; { print_to_builder(builder, "\t\t\t\tcase \"%\";\n", new_path); print_to_builder(builder, "\t\t\t\t\tscan2(values[1], \"\%\", *e.%);\n", new_path); } } } } } generate_serialize_procedure_for_entity :: (code_struct: *Code_Struct) { name := code_struct.defined_type.name; // Serialize serialize : String_Builder; print_to_builder(*serialize, "serialize_entity :: (e: *%, builder: *String_Builder) {\n", name); print_to_builder(*serialize, "\tprint_to_builder(builder, \"type: %\\n\");\n", name); generate_member_serialization(code_struct.defined_type, *serialize); print_to_builder(*serialize, "}\n"); array_add(*entity_serialize_proc_string, builder_to_string(*serialize)); // Deserialize deserialize : String_Builder; print_to_builder(*deserialize, "deserialize_entity :: (scene: *Scene, lines: [] string, e: *%) {\n", name); print_to_builder(*deserialize, "\tfor line: lines {\n"); print_to_builder(*deserialize, "\t\tvalues := split(line, \":\");\n"); print_to_builder(*deserialize, "\t\tif values.count == 2 {\n"); print_to_builder(*deserialize, "\t\t\tif trim(values[0], \" \") == {\n"); generate_member_deserialization(code_struct.defined_type, *deserialize); print_to_builder(*deserialize, "\t\t\t}\n"); print_to_builder(*deserialize, "\t\t}\n"); print_to_builder(*deserialize, "\t}\n"); print_to_builder(*deserialize, "}\n"); array_add(*entity_serialize_proc_string, builder_to_string(*deserialize)); } note_struct :: (code_struct: *Code_Struct) { if is_subclass_of(code_struct.defined_type, "Entity") { name := code_struct.defined_type.name; added := array_add_if_unique(*entity_type_names, name); if added { print("Detected entity '%'.\n", name); generate_serialize_procedure_for_entity(code_struct); } } } message_loop :: () { while true { message := compiler_wait_for_message(); // We ignore most message types. We mainly want to know about code that has been typechecked, // so that we can look for the data structures we care about; or, when all the code is done // being typechecked, we want to generate our new code. if message.kind == { case .TYPECHECKED; typechecked := cast(*Message_Typechecked) message; for typechecked.structs note_struct(it.expression); case .PHASE; phase := cast(*Message_Phase) message; if phase.phase == .TYPECHECKED_ALL_WE_CAN { if !generated_code { generate_code(message.workspace); generated_code = true; } } case .COMPLETE; break; } } } generate_code :: (w: Workspace) { // Let's sort the entity_type_names alphabetically, which is nice, but also // gives us a reproducible build order, since our metaprogram may see these // entities in any order: quick_sort(entity_type_names, compare_strings); // A piece of the code we want to generate is a comma-separated string of entity type // names, so, let's make that using join(): type_names := join(..entity_type_names, ", "); // We will also want to generate entity_storage_string, which contains a bunch of Bucket_Arrays // for allocating each entity type. We might be able to do this with join() again, but // we'll use String_Builder here, since that is a good example of how to do arbitrarily complex things: entity_storage_string: string; { builder: String_Builder; for entity_type_names { // Let's say it's always 20 items per bucket, // unless it's Player, in which case we want 64: items_per_bucket := 20; if it == "Player" items_per_bucket = 64; print_to_builder(*builder, " _%1: Bucket_Array(%1, %2, true);\n", it, items_per_bucket); } entity_storage_string = builder_to_string(*builder); } { builder: String_Builder; for entity_type_names { print_to_builder(*builder, "\tcase %1; serialize_entity(cast(*%1)e, *builder);\n", it); } build_string := sprint(SERIALIZE_ENTITY, builder_to_string(*builder)); add_build_string(build_string, w); } { builder: String_Builder; for entity_type_names { print_to_builder(*builder, "\tcase \"%1\"; p, locator := find_and_occupy_empty_slot(*scene.by_type._%1); p._locator = locator; e = p; register_entity(scene, p); init_entity(p); deserialize_entity(scene, lines, cast(*%1)e); update_matrix(*e.transform);\n", it); } build_string := sprint(DESERIALIZE_ENTITY, builder_to_string(*builder)); add_build_string(build_string, w); } { builder: String_Builder; for entity_type_names { print_to_builder(*builder, "new_%1 :: (scene: *Scene = null) -> *%2 { _scene := scene;\nif _scene == null { \n_scene = game_state.current_scene; }\np, locator := find_and_occupy_empty_slot(*_scene.by_type._%2); p._locator = locator; register_entity(_scene, p); p.transform = create_identity_transform(); init_entity(p); return p; }\n", to_lower_copy_new(it,, allocator=temp), it); } add_build_string(builder_to_string(*builder), w); } for entity_serialize_proc_string { add_build_string(it, w); } // We fill out INSERTION_STRING to create the code we want to insert: build_string := sprint(INSERTION_STRING, entity_type_names.count, type_names, entity_storage_string); // Add this string to the target program: add_build_string(build_string, w); // We'll print out the added code just to show at compile-time what we are doing: print("Adding build string:\n%\n", build_string); } generated_code := false; entity_type_names: [..] string; // INSERTION_STRING represents the code we want to add to the target program. // We'll use print to insert useful things where the % markers are. INSERTION_STRING :: #string DONE // NUM_ENTITY_TYPES tells the target program how many entity types there are. NUM_ENTITY_TYPES :: %1; // entity_types is an array containing all the entity types. entity_types : [%1] Type : .[ %2 ]; Entity_Storage :: struct { %3 } DONE SERIALIZE_ENTITY :: #string DONE serialize_entity :: (e: *Entity, path: string) { builder: String_Builder; builder.allocator = temp; if e.type == { %1 } write_entire_file(tprint("%%/%%.ent", path, e.id), builder_to_string(*builder)); } DONE DESERIALIZE_ENTITY :: #string DONE deserialize_entity :: (scene: *Scene, path: string) -> *Entity { content := read_entire_file(path); if content.count == 0 return null; lines := split(content, "\n"); first_line := split(lines[0], ":"); if first_line.count != 2 return null; e: *Entity; type := trim(first_line[1], " \n\r"); if type == { %1 } return e; } DONE