aboutsummaryrefslogtreecommitdiff
path: root/tools/compile_assets/main.odin
diff options
context:
space:
mode:
authorXander Swan <[hidden email]>2026-01-07 23:12:22 -0500
committerXander Swan <[hidden email]>2026-01-07 23:12:22 -0500
commit0988ab832bfc7a1b1c851125b6172cf68c6d9cb9 (patch)
tree460bc2d9f0bce463af273d6b2b2c20faa880ac29 /tools/compile_assets/main.odin
parentade0dc4d257d053b7064184f193f8168c496e308 (diff)
doesn't compile but i'm commiting anywya
Diffstat (limited to 'tools/compile_assets/main.odin')
-rw-r--r--tools/compile_assets/main.odin328
1 files changed, 328 insertions, 0 deletions
diff --git a/tools/compile_assets/main.odin b/tools/compile_assets/main.odin
new file mode 100644
index 0000000..60c8a8c
--- /dev/null
+++ b/tools/compile_assets/main.odin
@@ -0,0 +1,328 @@
+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 {
+<image-enum>}
+
+Animation_Id :: enum {
+<anim-enum>}
+
+Map_Id :: enum {
+<map-enum>}
+
+Tileset_Id :: enum {
+<tileset-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).
+<resource-paths>}
+
+@(private="file")
+load_images :: proc() {
+<image-load>}
+
+@(private="file")
+load_anims :: proc() {
+<anim-load>}
+
+@(private="file")
+load_maps :: proc() {
+<maps-load>}
+
+@(private="file")
+load_tilesets :: proc() {
+<tileset-load>}
+`
+
+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, "<image-enum>", images)
+ content = create_enum(content, "<anim-enum>", animations)
+ content = create_enum(content, "<map-enum>", maps)
+ content = create_enum(content, "<tileset-enum>", tilesets)
+
+ content = create_loads(content, "images", "<image-load>", images)
+ content = create_loads(content, "animations", "<anim-load>", animations)
+ content = create_loads(content, "maps", "<maps-load>", maps)
+ content = create_loads(content, "tilesets", "<tileset-load>", 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, "<resource-paths>", 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)
+}
+