#import "Compiler"; #import "Basic"; #import "String"; #import "Sort"; #import "File"; build :: (build_release: bool, main_path: string, game_name: string, working_dir: string = #filepath) { set_working_directory(working_dir); make_directory_if_it_does_not_exist("../../bin"); 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"); array_add(*new_path, "modules/ufbx"); 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_string("#import \"Bucket_Array\";", 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.name == "Color" { return true; } 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 "Color"; #through; case "Quaternion"; { if member.name == { case "x"; #through; case "y"; #through; case "z"; #through; case "w"; return true; case; return false; } } case "Static_Array"; #through; case "PArray"; #through; case "Stack"; #through; case "Queue"; return false; } } for member.notes { if to_lower_copy(it,, allocator = temp) == "dontserialize" return false; } return true; } should_make_ui :: (type: *Type_Info_Struct, member: Type_Info_Struct_Member) -> bool { for member.notes { if to_lower_copy(it,, allocator = temp) == "hide" return false; } 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; } } case "Color"; return true; } } return true; } is_readonly :: (type: *Type_Info_Struct, member: Type_Info_Struct_Member) -> bool { for member.notes { if to_lower_copy(it,, allocator = temp) == "readonly" return true; } return false; } generate_member_ui :: (type: *Type_Info_Struct, builder: *String_Builder, path: string = "") { for type.members { readonly := is_readonly(type, it); if should_make_ui(type, it) && (should_serialize(type, it) || readonly) { new_path : string; if it.name != "entity" { if path.count == 0 { new_path = it.name; } else { new_path = tprint("%1.%2", path, it.name); } } tag : Type_Info_Tag; if it.type.type == .VARIANT { info_variant := cast(*Type_Info_Variant)it.type; tag = info_variant.variant_of.type; } else { tag = it.type.type; } if readonly { print_to_builder(builder, "\tui_field_label(\"%\", e.%);\n", new_path, new_path); } else { if tag == { case .STRUCT; { info_struct := cast(*Type_Info_Struct) it.type; if info_struct.name == "Transform" { print_to_builder(builder, TRANSFORM_UI); } else if info_struct.name == "Vector3" { print_to_builder(builder, "\tui_vector_field(tprint(\"%\"), *e.%);\n", new_path, new_path); } else { generate_member_ui(info_struct, builder, new_path); } } case .BOOL; { print_to_builder(builder, "\tui_checkbox_field(tprint(\"%\"), *e.%);\n", new_path, new_path); } case .STRING; { print_to_builder(builder, "\tui_textfield(tprint(\"%\"), *e.%);\n", new_path, new_path); //ui_textfield :: (label: string, text: *string, identifier: s64 = 0, loc := #caller_location) { } case .FLOAT; { print_to_builder(builder, "\tui_float_field(tprint(\"%\"), *e.%);\n", new_path, new_path); } //case .ENUM; #through; case .INTEGER; { print_to_builder(builder, "\t{val := cast(int)e.%;\n", new_path); print_to_builder(builder, "\tui_int_field(tprint(\"%\"), *val);\n", new_path); print_to_builder(builder, "\te.% = xx val;}\n", new_path); } } } } } } generate_member_ui_imgui :: (type: *Type_Info_Struct, builder: *String_Builder, path: string = "") { for type.members { readonly := is_readonly(type, it); if should_make_ui(type, it) && (should_serialize(type, it) || readonly) { new_path : string; if it.name != "entity" { if path.count == 0 { new_path = it.name; } else { new_path = tprint("%1.%2", path, it.name); } } tag : Type_Info_Tag; if it.type.type == .VARIANT { info_variant := cast(*Type_Info_Variant)it.type; tag = info_variant.variant_of.type; } else { tag = it.type.type; } if readonly { print_to_builder(builder, "\tImGui.Text(\"% \%\", e.%);\n", new_path, new_path); } else { if tag == { case .STRUCT; { info_struct := cast(*Type_Info_Struct) it.type; if info_struct.name == "Transform" { print_to_builder(builder, TRANSFORM_UI_IMGUI); } else if info_struct.name == "Vector3" { print_to_builder(builder, "\tImGui.DragFloat3(tprint_c(\"%\"), *e.%.component);\n", new_path, new_path); } else if info_struct.name == "Color" || info_struct.name == "Vector4" { print_to_builder(builder, "\tsrgb_color := linear_to_srgb(e.%);\n", new_path); print_to_builder(builder, "\tImGui.ColorEdit4(tprint_c(\"%\"), *srgb_color.component);\n", new_path); print_to_builder(builder, "\te.% = srgb_to_linear(srgb_color);\n", new_path); } else { generate_member_ui_imgui(info_struct, builder, new_path); } } case .BOOL; { print_to_builder(builder, "\tImGui.Checkbox(tprint_c(\"%\"), *e.%);\n", new_path, new_path); } case .STRING; { print_to_builder(builder, "\timgui_input_text(tprint(\"%\"), *e.%);\n", new_path, new_path); //ui_textfield :: (label: string, text: *string, identifier: s64 = 0, loc := #caller_location) { } case .FLOAT; { print_to_builder(builder, "\tImGui.DragFloat(tprint_c(\"%\"), *e.%);\n", new_path, new_path); } //case .ENUM; #through; case .INTEGER; { print_to_builder(builder, "\t{val := cast(int)e.%;\n", new_path); print_to_builder(builder, "\timgui_input_int(tprint(\"%\"), *val);\n", new_path); print_to_builder(builder, "\te.% = xx val;}\n", new_path); } } } } } } TRANSFORM_UI :: #string DONE updated := ui_vector_field("Position", *e.transform.position); euler_rotation := quaternion_to_euler_v3(e.transform.orientation); euler_rotation *= RADIANS_TO_DEGREES; updated |= ui_vector_field("Rotation", *euler_rotation); euler_rotation *= DEGREES_TO_RADIANS; e.transform.orientation = euler_to_quaternion(euler_rotation); updated |= ui_vector_field("Scale", *e.transform.scale); if updated { update_matrix(*e.transform); } DONE TRANSFORM_UI_IMGUI :: #string DONE updated := ImGui.DragFloat3("Position", *e.transform.position.component); euler_rotation := quaternion_to_euler_v3(e.transform.orientation); euler_rotation *= RADIANS_TO_DEGREES; updated |= ImGui.DragFloat3("Rotation", *euler_rotation.component); euler_rotation *= DEGREES_TO_RADIANS; e.transform.orientation = euler_to_quaternion(euler_rotation); updated |= ImGui.DragFloat3("Scale", *e.transform.scale.component); if updated { update_matrix(*e.transform); } DONE generate_member_serialization :: (struct_type: *Type_Info_Struct, builder: *String_Builder, path: string = "") { for struct_type.members { if should_serialize(struct_type, it) { new_path : string; if path.count == 0 { new_path = it.name; } else { new_path = tprint("%1.%2", path, it.name); } type : Type_Info_Tag; if it.type.type == .VARIANT { info_variant := cast(*Type_Info_Variant)it.type; type = info_variant.variant_of.type; } else { type = it.type.type; } if type == { case .STRUCT; { info_struct := cast(*Type_Info_Struct) it.type; generate_member_serialization(info_struct, builder, new_path); } case .STRING; { print_to_builder(builder, "\tprint_to_builder(builder, \"%: \%\\n\", e.%);\n", new_path, 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 :: (struct_type: *Type_Info_Struct, builder: *String_Builder, path: string = "") { for struct_type.members { if should_serialize(struct_type, it) { new_path : string; if path.count == 0 { new_path = it.name; } else { new_path = tprint("%1.%2", path, it.name); } type : Type_Info_Tag; if it.type.type == .VARIANT { info_variant := cast(*Type_Info_Variant)it.type; type = info_variant.variant_of.type; } else { type = it.type.type; } if type == { case .STRUCT; { info_struct := cast(*Type_Info_Struct) it.type; generate_member_deserialization(info_struct, builder, new_path); } case .STRING; { print_to_builder(builder, "\t\t\t\tcase \"%\";\n", new_path); print_to_builder(builder, "\t\t\t\tif values[1].count > 0 { e.%1 = copy_string(trim(values[1])); }\n", new_path); } case .BOOL; { print_to_builder(builder, "\t\t\t\tcase \"%\";\n", new_path); print_to_builder(builder, "\t\t\t\t\tscan2(trim(values[1]), \"\%\", *e.%);\n", new_path); } case .FLOAT; #through; case .ENUM; { 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); } case .INTEGER; { print_to_builder(builder, "\t\t\t\tcase \"%\";\n", new_path); print_to_builder(builder, "\t\t\t\t\tscan2(values[1], \"\%\", cast(*int)*e.%);\n", new_path); } } } } } generate_member_copy :: (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); } type : Type_Info_Tag; if it.type.type == .VARIANT { info_variant := cast(*Type_Info_Variant)it.type; type = info_variant.variant_of.type; } else { type = it.type.type; } if type == { case .STRUCT; { info_struct := cast(*Type_Info_Struct) it.type; generate_member_copy(info_struct, builder, new_path); } case .BOOL; #through; case .FLOAT; #through; case .ENUM; #through; case .INTEGER; { print_to_builder(builder, "\tcopy.% = e.%;\n", new_path, new_path); } case .STRING; { print_to_builder(builder, "\tcopy.% = copy_string(e.%,,allocator=e.scene.allocator);\n", new_path, new_path); } } } } } generate_ui_procedure_for_entity :: (code_struct: *Code_Struct) { name := code_struct.defined_type.name; // Serialize ui : String_Builder; print_to_builder(*ui, "#if EDITOR {"); print_to_builder(*ui, "entity_ui_proc :: (e: *%) {\n", name); generate_member_ui(code_struct.defined_type, *ui); print_to_builder(*ui, "}\n"); print_to_builder(*ui, "}"); array_add(*entity_serialize_proc_string, builder_to_string(*ui)); } generate_ui_procedure_for_entity_imgui :: (code_struct: *Code_Struct) { name := code_struct.defined_type.name; // Serialize ui : String_Builder; print_to_builder(*ui, "#if EDITOR {"); print_to_builder(*ui, "entity_ui_proc_imgui :: (e: *%) {\n", name); generate_member_ui_imgui(code_struct.defined_type, *ui); if wants_custom_ui(code_struct) { print_to_builder(*ui, "custom_ui(e);\n"); } print_to_builder(*ui, "}\n"); print_to_builder(*ui, "}"); array_add(*entity_serialize_proc_string, builder_to_string(*ui)); } 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, \":\",, temp);\n"); print_to_builder(*deserialize, "\t\tif values.count == 2 {\n"); print_to_builder(*deserialize, "\t\t\tif trim(values[0], \" \",, temp) == {\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)); // Duplicate duplicate : String_Builder; print_to_builder(*duplicate, "duplicate_entity :: (e: *%) -> *% {\n", name, name); print_to_builder(*duplicate, "\tcopy := new_%(init=false);\n", to_lower_copy(name,,allocator=temp)); generate_member_copy(code_struct.defined_type, *duplicate); print_to_builder(*duplicate, "\tinit_entity(copy);\n"); print_to_builder(*duplicate, "\treturn copy;\n"); print_to_builder(*duplicate, "}\n"); array_add(*entity_serialize_proc_string, builder_to_string(*duplicate)); } 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); generate_ui_procedure_for_entity_imgui(code_struct); } } } wants_custom_ui :: (code_struct: *Code_Struct) -> bool { for note: code_struct.defined_type.notes { if to_lower_copy(note,, allocator = temp) == "customui" { return true; } } return false; } message: *Message_Import; 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 .IMPORT; { import := cast(*Message_Import)message; if import.module_name == "Coven" message = import; } case .TYPECHECKED; typechecked := cast(*Message_Typechecked) message; for typechecked.structs { note_struct(it.expression); } for typechecked.procedure_headers { if it.expression.name == "deinit_entity" { if it.expression.arguments.count > 0 { //struct_type := cast(*Type_Info_Struct)it.expression.arguments[0].type_inst.pointer_to; struct_type := cast(*Type_Info_Struct)it.expression.arguments[0].type_inst.pointer_to.result; array_add(*deinit_entity_procs, struct_type.name); } } else { for note: it.expression.notes { if to_lower_copy(note.text,, temp) == { case "newentity"; { array_add(*new_entity_procs, it.expression.name); } case "engineinit"; { array_add(*engine_init_procs, it.expression.name); } } } } } 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, "scene.by_type._%1.allocator = scene.allocator;", it); } build_string := sprint(INIT_SCENE, builder_to_string(*builder)); add_build_string(build_string, w, message); } { builder: String_Builder; for engine_init_procs { print_to_builder(*builder, "%();", it); } build_string := sprint(RUN_ENGINE_INIT, builder_to_string(*builder)); add_build_string(build_string, w, message); } { builder: String_Builder; for entity_type_names { lower := to_lower_copy (it,, allocator=temp); print_to_builder(*builder, "if ImGui.Button(\"New %1\") return new_%2();", it, lower); } for new_entity_procs { print_to_builder(*builder, "if ImGui.Button(\"%1\") return %1();", it); } build_string := sprint(EDITOR_UI_ENTITY_CREATION, builder_to_string(*builder)); add_build_string(build_string, w); } { builder: String_Builder; for entity_type_names { print_to_builder(*builder, "\tcase %1; bucket_array_remove(*e.scene.by_type._%1, e._locator);", it); } build_string := sprint(DELETE_ENTITY, builder_to_string(*builder)); add_build_string(build_string, w); } { 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; return duplicate_entity(cast(*%1)e);\n", it); } build_string := sprint(DUPLICATE_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, id); deserialize_entity(scene, lines, cast(*%1)e); init_entity(p); 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, "\tcase %1; entity_ui_proc_imgui(cast(*%1)e);\n", it); } build_string := sprint(ENTITY_UI, builder_to_string(*builder)); add_build_string(build_string, w); } { if deinit_entity_procs.count > 0 { builder: String_Builder; for deinit_entity_procs { print_to_builder(*builder, "\tcase %1; deinit_entity(cast(*%1)e);\n", it); } build_string := sprint(DEINIT_ENTITY, builder_to_string(*builder)); add_build_string(build_string, w); } else { add_build_string("call_correct_deinit_entity :: (e: *Entity) {}", w); add_build_string("deinit_entity :: () {}", w); } } { builder: String_Builder; for entity_type_names { print_to_builder(*builder, "new_%1 :: (scene: *Scene = null, init: bool = true) -> *%2 { _scene := scene;\nif _scene == null { \n_scene = engine.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(); if init { init_entity(p); } return p; }\n", to_lower_copy(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); add_build_string(PLACEHOLDER, 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; new_entity_procs: [..] string; engine_init_procs: [..] string; deinit_entity_procs: [..] 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 DELETE_ENTITY :: #string,\% DONE delete_entity :: (e: *Entity) { // Delete the file too path := tprint("../assets/scenes/\%/\%.ent", e.scene.name, e.id); File.file_delete(path); destroy_entity(e); if e.type == { %1 } } //#import "File"; DONE SERIALIZE_ENTITY :: #string,\% DONE serialize_entity :: (e: *Entity, path: string) { builder: String_Builder; builder.allocator = temp; if e.type == { %1 } File.write_entire_file(tprint("\%/\%.ent", path, e.id), builder_to_string(*builder)); } DONE DESERIALIZE_ENTITY :: #string DONE deserialize_entity :: (scene: *Scene, id: Entity_Id, path: string) -> *Entity { content := File.read_entire_file(path); if content.count == 0 return null; lines := split(content, "\n",, temp); first_line := split(lines[0], ":",, temp); if first_line.count != 2 return null; e: *Entity; type := trim(first_line[1], " \n\r"); if type == { %1 } free(content); return e; } DONE DUPLICATE_ENTITY :: #string DONE duplicate_entity :: (e: *Entity) -> *Entity { if e.type == { %1 } return e; } DONE ENTITY_UI :: #string DONE #if EDITOR { entity_ui :: (e: *Entity) { if e.type == { %1 } } } DONE DEINIT_ENTITY :: #string DONE #if EDITOR { call_correct_deinit_entity :: (e: *Entity) { if e.type == { %1 } } } DONE INIT_SCENE :: #string DONE init_scene :: (scene: *Scene) { %1 } DONE RUN_ENGINE_INIT :: #string DONE run_engine_inits :: () { %1 } DONE EDITOR_UI_ENTITY_CREATION :: #string DONE #if EDITOR { editor_ui_entity_creation :: () -> *Entity { %1 return null; } } DONE PLACEHOLDER :: #string DONE #poke_name Coven init_scene; #poke_name Coven Entity_Storage; #poke_name Coven delete_entity; #poke_name Coven deserialize_entity; #poke_name Coven deinit_entity; #poke_name Coven serialize_entity; #poke_name Coven new_mesh_entity; #poke_name Coven call_correct_deinit_entity; #poke_name Coven run_engine_inits; #if EDITOR { #poke_name Coven editor_ui_entity_creation; #poke_name Coven entity_ui; #poke_name Coven duplicate_entity; } DONE