Files
coven/metaprogram.jai
2025-03-22 17:33:10 +01:00

794 lines
26 KiB
Plaintext

#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, "\tImGui.ColorEdit4(tprint_c(\"%\"), *e.%.component);\n", new_path, 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);
}
}
}
}
}
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);
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, \":\");\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));
// 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);
}
}
}
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 {
for note: it.expression.notes {
if to_lower_copy(note.text,, allocator = temp) == "newentity" {
array_add(*new_entity_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 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);
}
{
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;
// 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");
first_line := split(lines[0], ":");
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
INIT_SCENE :: #string DONE
init_scene :: (scene: *Scene) {
%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 serialize_entity;
#if EDITOR {
#poke_name Coven editor_ui_entity_creation;
#poke_name Coven entity_ui;
#poke_name Coven duplicate_entity;
}
DONE