package assets_gen import os "core:os/os2" import "core:fmt" import "core:path/filepath" import "core:strings" COMPILED_DIR :: ".compiled-res/" HELP_STR :: `USAGE: %v [input dir] [output file].odin` Asset_Loader :: #type proc(string, ^os.File, ^os.File) file_content := `#+feature dynamic-literals package demonchime // DO NOT EDIT // // This file is autogenerated by tools/compile_assets // All resource types are defined in 'src/resources.odin'. import rl "vendor:raylib" Image_Id :: enum { } Animation_Id :: enum { } Map_Id :: enum { } Tileset_Id :: enum { } Resource_Id :: union { Image_Id, Animation_Id, Map_Id, Tileset_Id, } images: [Image_Id]Image_Resource animations: [Animation_Id]Animation_Resource maps: [Map_Id]Map_Resource tilesets: [Tileset_Id]Tileset_Resource path_to_id: map[string]Resource_Id load_resources :: proc() { load_images() load_anims() load_maps() load_tilesets() // Allow conversion from paths to a resource id, since it's a better way to // reference resources in other resources (JSON is a good example). } @(private="file") load_images :: proc() { } @(private="file") load_anims :: proc() { } @(private="file") load_maps :: proc() { } @(private="file") load_tilesets :: proc() { } ` images: map[string]string animations: map[string]string maps: map[string]string tilesets: map[string]string paths_to_res_type: map[string]string die :: proc(msg: string, args: ..any, exit_code := 1) { fmt.eprintfln(msg, ..args) os.exit(exit_code) } print_help :: proc(exit_code := 1) { die(HELP_STR, os.args[0], exit_code = exit_code) } recursive_read_directory :: proc( dir: string, allocator := context.allocator, ) -> ([]os.File_Info, os.Error) { context.allocator = allocator full_files: [dynamic]os.File_Info files, read_dir_err := os.read_all_directory_by_path( dir, allocator = allocator, ) if read_dir_err != nil { return nil, read_dir_err } defer delete(files) for file in files { fmt.printfln("Found %-15v '%v'", file.type, file.fullpath) #partial switch file.type { case .Directory: subdir_files, sub_err := recursive_read_directory(file.fullpath) if sub_err != nil { delete(full_files) return nil, sub_err } defer delete(subdir_files) append(&full_files, ..subdir_files) os.file_info_delete(file, allocator) case .Regular: append(&full_files, file) case: delete(full_files) die("Invalid file type %v", file.type) } } return full_files[:], nil } create_enum :: proc( content: string, replace: string, elements: map[string]string ) -> string { ids := "" for element in elements { ids = strings.concatenate({ ids, " ", strings.to_upper_snake_case(element, context.temp_allocator), ",\n" }, allocator = context.temp_allocator) } replaced, _ := strings.replace_all( content, replace, ids, allocator = context.temp_allocator, ) return replaced } create_loads :: proc( content: string, map_name: string, placeholder: string, elements: map[string]string ) -> string { load := "" for element in elements { load = strings.concatenate({ load, " ", map_name, "[.", strings.to_upper_snake_case(element, context.temp_allocator), "] = ", elements[element], "\n", }, allocator = context.temp_allocator) } return set_placeholder(content, placeholder, load) } set_placeholder :: proc( content: string, placeholder: string, with: string ) -> string { new_content, _ := strings.replace_all( content, placeholder, with, allocator = context.temp_allocator, ) return new_content } main :: proc() { if len(os.args) != 3 { print_help() } input_dir := os.args[1] output_file_path := os.args[2] output_file, open_err := os.open(output_file_path, {.Write, .Create, .Trunc}) if open_err != nil { die("Could not create output file '%v' (%v)", output_file_path, open_err) } defer os.close(output_file) dir_info, stat_err := os.stat(input_dir, context.temp_allocator) if stat_err != nil { die("Error reading directory %v", stat_err) } if dir_info.type != .Directory { die("Expected directory (Got %v)", dir_info.type) } files, dir_read_err := recursive_read_directory(input_dir) if dir_read_err != nil { die("Could not iterate directory %v", dir_read_err) } defer { for file in files { os.file_info_delete(file, context.allocator) } delete(files) } free_all(context.temp_allocator) loaders: map[string]Asset_Loader defer delete(loaders) loaders["tmj"] = load_map // loaders["world"] = load_json loaders["tsj"] = load_tileset loaders["qoi"] = load_qoi loaders["png"] = load_png loaders["ase"] = load_ase os.make_directory_all(COMPILED_DIR) fmt.println("Generating assets file...") for file in files { ext := filepath.ext(file.fullpath)[1:] loader, has := &loaders[ext] if !has { fmt.printfln( "%-25v Skipped\tNo loader for '%v'", file.name, ext, ) continue } f, open_err := os.open(file.fullpath) if open_err != nil { fmt.printfln("%-25v Skipped\tCould not load file", file.name) continue } defer os.close(f) fmt.printfln("%-25v Loading...", file.name) loader^(file.fullpath, f, output_file) fmt.printfln("%-25v Loaded", file.name) free_all(context.temp_allocator) } content := file_content images_to_enum: map[string]string defer delete(images_to_enum) content = create_enum(content, "", images) content = create_enum(content, "", animations) content = create_enum(content, "", maps) content = create_enum(content, "", tilesets) content = create_loads(content, "images", "", images) content = create_loads(content, "animations", "", animations) content = create_loads(content, "maps", "", maps) content = create_loads(content, "tilesets", "", tilesets) res_paths := "" cwd, _ := os.get_working_directory(context.temp_allocator) for file in files { res_type, is_res := paths_to_res_type[file.fullpath] if !is_res { continue } res_name := strings.to_upper_snake_case( filepath.stem(file.fullpath), context.temp_allocator, ) rel_path, _ := filepath.rel(cwd, file.fullpath, context.temp_allocator) res_paths = strings.concatenate({ res_paths, " path_to_id[\"", rel_path, "\"] = ", res_type, ".", res_name, "\n", }, allocator = context.temp_allocator) } content = set_placeholder(content, "", res_paths) os.write_string(output_file, content) for image in images { delete(images[image]) } for anim in animations { delete(animations[anim]) } for tmap in maps { delete(maps[tmap]) } free_all(context.temp_allocator) }