package assets_gen import os "core:os/os2" import "core:fmt" import "core:path/filepath" import "core:strings" import "core:sort" 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'. Image_Id :: enum { } Animation_Id :: enum { } Room_Id :: enum { } Tileset_Id :: enum { } Object_Type :: enum { } images: [Image_Id]Image_Resource = { } animations: [Animation_Id]Animation_Resource = { } rooms: [Room_Id]Room_Resource = { } tilesets: [Tileset_Id]Tileset_Resource = { } tiles: []Tile_Resource = { } world: []Room_Position_Resource = { } Resource_Id :: union { Image_Id, Animation_Id, Room_Id, Tileset_Id, } path_to_resource_id: map[string]Resource_Id = { } ` images: map[string]string animations: map[string]string rooms: map[string]string tiles: [dynamic]string tilesets: map[string]string world: [dynamic]string // Don't want to add types multiple times, so it's a set object_type_names: map[string]struct{} output_dir: 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 } sort_keys :: proc( m: map[string]$V, allocator := context.allocator, ) -> []string { arr := make([]string, len(m), allocator) i := 0 for k, _ in m { arr[i] = k i += 1 } sort.merge_sort(arr) return arr } create_enum :: proc( content: string, replace: string, elements: map[string]string ) -> string { ids := "" keys := sort_keys(elements, context.temp_allocator) for key in keys { ids = strings.concatenate({ ids, " ", strings.to_ada_case(key, 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 := "" keys := sort_keys(elements, context.temp_allocator) for key in keys { load = strings.concatenate({ load, " .", strings.to_ada_case(key, context.temp_allocator), " = ", elements[key], ",\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() } // add dummy append(&tiles, "") // Need to copy, cause you can't just free executable memory, you fool no_anim_line := "{frame_count=1, frame_durations={100}, tags={}}" no_anim_line_owned := make([]u8, len(no_anim_line)) copy_from_string(no_anim_line_owned, no_anim_line) animations["NONE"] = string(no_anim_line_owned) input_dir := os.args[1] output_file_path := os.args[2] abs_output_path, found_abs := filepath.abs( output_file_path, context.temp_allocator ) if !found_abs { die("Could not find absolute path to output file") } output_dir = filepath.dir(abs_output_path) defer delete(output_dir) 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_room loaders["world"] = load_world 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) 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, "", rooms) content = create_enum(content, "", tilesets) content = create_loads(content, "images", "", images) content = create_loads(content, "animations", "", animations) content = create_loads(content, "rooms", "", rooms) content = create_loads(content, "tilesets", "", tilesets) tile_loads := "" for tile, i in tiles { if i == 0 { continue } tile_loads = strings.concatenate({ tile_loads, " ", tile, ",\n", }, allocator = context.temp_allocator) } content = set_placeholder(content, "", tile_loads) room_pos_loads := "" for room_pos, i in world { room_pos_loads = strings.concatenate({ room_pos_loads, " ", room_pos, ",\n", }, allocator = context.temp_allocator) } content = set_placeholder(content, "", room_pos_loads) 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_ada_case( filepath.stem(file.fullpath), context.temp_allocator, ) rel_path, _ := filepath.rel(cwd, file.fullpath, context.temp_allocator) res_paths = strings.concatenate({ res_paths, " \"", rel_path, "\" = ", res_type, ".", res_name, ",\n", }, allocator = context.temp_allocator) } content = set_placeholder(content, "", res_paths) object_type_enum := "" for object_type in sort_keys( object_type_names, allocator = context.temp_allocator, ) { object_type_enum = strings.concatenate({ object_type_enum, " ", object_type, ",\n" }, allocator = context.temp_allocator) delete(object_type) } content, _ = strings.replace_all( content, "", object_type_enum, allocator = context.temp_allocator, ) os.write_string(output_file, content) for image in images { delete(images[image]) } for anim in animations { delete(animations[anim]) } for room in rooms { delete(rooms[room]) } free_all(context.temp_allocator) }