338 lines
11 KiB
Plaintext
338 lines
11 KiB
Plaintext
#import "Compiler";
|
|
#import "Basic";
|
|
#import "String";
|
|
#import "Sort";
|
|
#import "File";
|
|
|
|
build_release := false;
|
|
|
|
build :: (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!= 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 = 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
|
|
}
|
|
|
|
File.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 := 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
|
|
}
|
|
|
|
return e;
|
|
}
|
|
DONE
|