trueno/first.jai
2026-08-09 12:01:37 +03:00

282 lines
11 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#load "./src/meta/meta.jai";
Iprof :: #import "Iprof"(IMPORT_MODE = .METAPROGRAM);
#import "File_Utilities";
Trueno_Build_Options :: struct {
wasm_build : bool;
release_build : bool;
tacoma_enabled : bool;
iprof_enabled : bool;
demo_build : bool;
test_engine : bool;
test_game : bool;
test_exe_engine : bool;
test_exe_game : bool;
}
// Engine tests build against test_game/ so they work without the game/ directory;
// game tests build against the real game and its real resources. Kept in step with
// FLAG_USE_TEST_GAME in src/main.jai.
uses_test_game :: (opts: Trueno_Build_Options) -> bool {
return opts.test_engine || opts.test_exe_engine;
}
build_options_from_args :: (args: []string) -> Trueno_Build_Options {
opts : Trueno_Build_Options;
for arg : args {
if arg == {
case "wasm";
opts.wasm_build = true;
case "tacoma";
opts.tacoma_enabled = true;
case "release";
opts.release_build = true;
case "iprof";
opts.iprof_enabled = true;
case "demo";
opts.demo_build = true;
case "test";
opts.test_game = true;
opts.test_engine = true;
case "test_game";
opts.test_game = true;
case "test_engine";
opts.test_engine = true;
case "test_exe_game";
opts.test_exe_game = true;
case "test_exe_engine";
opts.test_exe_engine = true;
}
}
return opts;
}
// Scans `dir` recursively and returns the size of the largest file whose
// short name ends with `ext`. Returns `floor` when no matching file exists.
max_asset_file_size :: (dir: string, ext: string, floor: s64 = 0) -> s64 {
Ctx :: struct { ext: string; max: s64; }
ctx: Ctx;
ctx.ext = ext;
visit_files(dir, true, *ctx, (info: *File_Visit_Info, c: *Ctx) {
if info.is_directory return;
if !ends_with(info.short_name, c.ext) return;
f, ok := file_open(info.full_name);
defer if ok file_close(*f);
if !ok return;
size, ok2 := file_length(f);
if ok2 && size > c.max then c.max = size;
});
return max(floor, ctx.max);
}
add_asset_buffer_sizes_to_compiler_strings :: (trueno_opts: Trueno_Build_Options, w: Workspace) {
use_test_game := uses_test_game(trueno_opts);
pack_dir := ifx use_test_game then "./test_packs" else "./packs";
game_resources_dir := ifx use_test_game then "./test_game/resources" else "./game/resources";
sizes: [6]s64;
sizes[0] = max_asset_file_size(pack_dir, ".pack", 1 * 1024 * 1024);
sizes[1] = max(
max_asset_file_size(game_resources_dir, "world.json", 64 * 1024),
max_asset_file_size(game_resources_dir, "index.world", 64 * 1024)
);
sizes[2] = max_asset_file_size(game_resources_dir, "chunks.bin", 1 * 1024 * 1024);
sizes[3] = max_asset_file_size(game_resources_dir, ".shgrid", 1 * 1024 * 1024);
sizes[4] = max_asset_file_size(game_resources_dir, "rdm_atlas.rdm", 16 * 1024 * 1024);
sizes[5] = max_asset_file_size(game_resources_dir, "rdm_manifest.json", 256 * 1024);
names :: string.["MAX_PACK_SIZE", "MAX_WORLD_SIZE", "MAX_CHUNKS_SIZE",
"MAX_SHGRID_SIZE", "MAX_RDM_ATLAS_SIZE", "MAX_RDM_MANIFEST_SIZE"];
for i: 0..5 {
add_build_string(tprint("% :: %;\n", names[i], sizes[i]), w);
}
}
add_trueno_opts_to_compiler_strings :: (trueno_opts : Trueno_Build_Options, w: Workspace) {
#import "String";
bool_to_string :: (b: bool) -> string {
if b then return tprint("true"); else return tprint("false");
}
trueno_opts_info := type_info(Trueno_Build_Options);
for trueno_opts_info.members {
optPtr := (cast(*bool)(*trueno_opts)) + it.offset_in_bytes;
name := tprint("%", it.name);
to_upper_in_place(name);
add_build_string(tprint("FLAG_% :: %;\n", name, bool_to_string(optPtr.*)), w);
}
}
compile_shaders :: () {
process_result, output, error := run_command("bash", ifx OS == .MACOS then "./compile_shaders_mac.sh" else "./compile_shaders.sh", working_directory=tprint("%/src/shaders", #filepath));
if process_result.exit_code != 0 {
log_error("Shader compilation failed.");
if output {
log_error(output,, logger = runtime_support_default_logger);
}
exit(1);
}
}
wasm_copy_assets :: () {
if is_directory("./packs") {
run_command("cp", "-r", "./packs", "./dist/", working_directory=tprint("%", #filepath));
}
worlds_src := "./game/resources/worlds";
if is_directory(worlds_src) {
make_directory_if_it_does_not_exist("./dist/game/resources", recursive = true);
run_command("cp", "-r", worlds_src, "./dist/game/resources/", working_directory=tprint("%", #filepath));
}
}
metaprogramming_loop :: (trueno_opts: Trueno_Build_Options, w: *Workspace, iprof_plugin: *Iprof.My_Plugin = null) {
while true {
message := compiler_wait_for_message();
// Fixed iprof integration
if iprof_plugin then iprof_plugin.message(iprof_plugin, message);
custom_message_handler(message, w);
if message.kind == .COMPLETE then break;
}
compiler_end_intercept(w.*);
}
native_build :: (opts: Build_Options, trueno_opts: Trueno_Build_Options) {
set_build_options_dc(.{do_output=false});
current_w := get_current_workspace();
root_opts := get_build_options();
w := compiler_create_workspace("Target");
opts := get_build_options(w);
copy_commonly_propagated_fields(opts, *root_opts);
opts.cpu_target = root_opts.cpu_target;
opts.os_target = root_opts.os_target;
opts.backend = ifx (trueno_opts.release_build || OS == .MACOS) then .LLVM else .X64;
opts.output_executable_name = root_opts.output_executable_name;
set_build_options(opts, w);
iprof_plugin: *Iprof.My_Plugin;
intercept_flags: Intercept_Flags;
if trueno_opts.iprof_enabled {
iprof_plugin = cast(*Iprof.My_Plugin) Iprof.get_plugin();
iprof_plugin.workspace = w;
// Instrumenting all modules creates >65536 unique (parent,zone) hash entries,
// which trips Iprof's MAX_HASH_SIZE assert. Limit to our own files.
iprof_plugin.instrument_modules = false;
iprof_plugin.before_intercept(iprof_plugin, *intercept_flags);
}
compiler_begin_intercept(w, intercept_flags);
if trueno_opts.iprof_enabled {
iprof_plugin.add_source(iprof_plugin);
}
add_trueno_opts_to_compiler_strings(trueno_opts, w);
add_asset_buffer_sizes_to_compiler_strings(trueno_opts, w);
add_build_file("src/platform_specific/main_native.jai", w);
add_shaders_to_workspace(w);
metaprogramming_loop(trueno_opts, *w, iprof_plugin);
if trueno_opts.iprof_enabled {
iprof_plugin.finish(iprof_plugin);
iprof_plugin.shutdown(iprof_plugin);
}
}
wasm_build :: (opts: Build_Options, trueno_opts: Trueno_Build_Options) {
set_build_options_dc(.{do_output = false});
make_directory_if_it_does_not_exist("dist", recursive = true);
wasm_copy_assets();
w := compiler_create_workspace("Wasm");
options := get_build_options(w);
copy_commonly_propagated_fields(get_build_options(), *options);
options.output_type = .OBJECT_FILE;
options.backend = .LLVM;
options.os_target = .WASM;
options.cpu_target = .CUSTOM;
options.emit_debug_info = .DWARF;
options.backtrace_on_crash = .OFF;
options.output_path = "dist/";
options.output_executable_name = "main";
options.llvm_options.target_system_features = "+bulk-memory";
options.llvm_options.enable_split_modules = false;
options.llvm_options.function_sections = true;
import_paths: [..]string;
for options.import_path array_add(*import_paths, it);
options.import_path = import_paths;
// This was compiled from https://github.com/wingo/walloc via "clang -Oz --target=wasm64 -nostdlib -c -o walloc.o walloc.c".
// We should probably port this allocator to Jai instead…
// -rluba, 2023-11-15
walloc_object_file_path := "walloc.o";
STACK_SIZE :: 1024 * 1024 * 1024;
options.additional_linker_arguments = .["--stack-first", "-z", tprint("stack-size=%", STACK_SIZE), walloc_object_file_path];
set_build_options(options, w);
remap_import(w, "*", "Default_Allocator", "Walloc");
compiler_begin_intercept(w);
add_trueno_opts_to_compiler_strings(trueno_opts, w);
add_asset_buffer_sizes_to_compiler_strings(trueno_opts, w);
add_build_file("src/platform_specific/main_web.jai", w);
add_shaders_to_workspace(w);
metaprogramming_loop(trueno_opts, *w);
args := string.[
"emcc",
"src/platform_specific/main.c", "dist/main.o", "modules/sokol-jai/sokol/gfx/sokol_gfx_wasm_gl_release.a", "modules/sokol-jai/sokol/log/sokol_log_wasm_gl_release.a", "modules/sokol-jai/sokol/audio/sokol_audio_wasm_gl_release.a", "modules/sokol-jai/sokol/time/sokol_time_wasm_gl_release.a", "modules/sokol-jai/sokol/app/sokol_app_wasm_gl_release.a", "modules/sokol-jai/sokol/glue/sokol_glue_wasm_gl_release.a", "modules/sokol-jai/sokol/fetch/sokol_fetch_wasm_gl_release.a", "modules/sokol-jai/sokol/gl/sokol_gl_wasm_gl_release.a", "modules/sokol-jai/sokol/fontstash/sokol_fontstash_wasm_gl_release.a",
"modules/sokol-jai/sokol/stbi/stb_image.a",
"-o", "dist/index.html",
"-sSTACK_SIZE=10MB",
"-sALLOW_MEMORY_GROWTH",
"-sERROR_ON_UNDEFINED_SYMBOLS=1", "-sMEMORY64", "-sMAX_WEBGL_VERSION=2",
"--js-library=src/platform_specific/runtime.js",
"--shell-file=src/platform_specific/shell.html",
];
process_result, output, error := run_command(..args, capture_and_return_output = true);
if process_result.exit_code != 0 {
log_error("EMCC compilation failed.");
if error {
log_error(error,, logger = runtime_support_default_logger);
}
exit(1);
}
file_delete("dist/main.o");
}
#run {
opt := get_build_options();
trueno_opts := build_options_from_args(opt.compile_time_command_line);
compile_shaders();
use_test_game := uses_test_game(trueno_opts);
create_pack(use_test_game, ifx use_test_game then "./test_packs" else "./packs");
if trueno_opts.wasm_build {
wasm_build(opt, trueno_opts);
} else {
native_build(opt, trueno_opts);
}
}
#import "Basic";
#import "Compiler";
#import "Process";
#import "File";
#import "String";
#import "File_Utilities";