163 lines
6.1 KiB
Plaintext
163 lines
6.1 KiB
Plaintext
// Packs are built from a throwaway copy of the resource trees, not from the
|
|
// trees themselves:
|
|
//
|
|
// wipe .build/generated -> copy the trees in -> process the copy -> pack it
|
|
//
|
|
// Processing (currently just the Aseprite export) writes its output into the
|
|
// copy, so the real resource trees only ever hold sources. Rebuilding the copy
|
|
// from scratch every time is what keeps this simple: there is no stale output to
|
|
// detect and nothing to prune, because a deleted source is simply not there.
|
|
//
|
|
// Used by both the build (meta/pack.jai) and the runtime hot-reloader
|
|
// (pack_hotreload.jai), so they process resources identically.
|
|
|
|
#import "Basic";
|
|
#import "String";
|
|
#import "Process";
|
|
#import "File";
|
|
#import "File_Utilities";
|
|
|
|
// Not ".build/generated/resources": pack.jai derives the pack name by splitting
|
|
// on the *first* "/resources/", so a "resources" segment here would shadow the
|
|
// real one and yield pack "game" instead of "game_core".
|
|
MIRROR_ROOT :: ".build/generated";
|
|
|
|
ASEPRITE_SOURCE_EXTENSIONS :: string.["aseprite", "ase"];
|
|
|
|
is_aseprite_source :: (short_name: string) -> bool {
|
|
ok, base, extension := split_from_right(short_name, #char ".");
|
|
if !ok return false;
|
|
for ASEPRITE_SOURCE_EXTENSIONS if extension == it return true;
|
|
return false;
|
|
}
|
|
|
|
// Wipes and rebuilds the mirror, runs the processing steps over it, and returns
|
|
// the mirrored resource roots to pack in place of the real ones.
|
|
prepare_resource_mirror :: (use_test_game: bool) -> [] string {
|
|
run_command("rm", "-rf", MIRROR_ROOT);
|
|
|
|
game_tree := ifx use_test_game then "test_game" else "game";
|
|
|
|
roots: [..] string;
|
|
roots.allocator = temp;
|
|
array_add(*roots, copy_tree_into("./resources", MIRROR_ROOT));
|
|
array_add(*roots, copy_tree_into(tprint("./%/resources", game_tree), tprint("%/%", MIRROR_ROOT, game_tree)));
|
|
|
|
export_aseprite_sheets(roots);
|
|
|
|
return roots;
|
|
}
|
|
|
|
// "cp -r ./game/resources .build/generated/game" -> ".build/generated/game/resources"
|
|
copy_tree_into :: (source: string, destination_parent: string) -> string {
|
|
make_directory_if_it_does_not_exist(destination_parent, recursive = true);
|
|
run_command("cp", "-r", source, destination_parent);
|
|
|
|
ok, parent, name := split_from_right(source, #char "/");
|
|
return tprint("%/%", destination_parent, name);
|
|
}
|
|
|
|
// Aseprite's CLI, reproducing byte for byte what its "Export Sprite Sheet"
|
|
// dialog was producing for the sheets that used to be checked in.
|
|
// --format json-array and --list-tags are load-bearing: asset_manager.jai parses
|
|
// `frames` as an array and drives Animation off `meta.frameTags`.
|
|
ASEPRITE_EXPORT_FLAGS :: string.[
|
|
"--format", "json-array",
|
|
"--list-tags",
|
|
"--list-layers",
|
|
"--list-slices",
|
|
];
|
|
|
|
// "player.aseprite" -> player.sheet.png / player.sheet.json
|
|
// "anim.sheet.aseprite" -> anim.sheet.png / anim.sheet.json
|
|
//
|
|
// The doubled-up ".sheet" is dropped because asset_manager.jai keys sheets on
|
|
// everything before the *first* dot, so "anim.sheet.sheet.png" would load as a
|
|
// sheet named "anim" with extension "sheet.sheet.png" and be rejected.
|
|
sheet_output_paths :: (source: string) -> (png: string, json: string) {
|
|
ok, stem := split_from_right(source, #char ".");
|
|
if ends_with(stem, ".sheet") {
|
|
stem.count -= ".sheet".count;
|
|
}
|
|
return tprint("%.sheet.png", stem), tprint("%.sheet.json", stem);
|
|
}
|
|
|
|
find_aseprite_binary :: () -> (path: string, found: bool) {
|
|
candidates: [..] string;
|
|
candidates.allocator = temp;
|
|
|
|
#if OS == .LINUX || OS == .MACOS {
|
|
POSIX :: #import "POSIX";
|
|
read_env :: (name: string) -> string {
|
|
value := POSIX.getenv(name.data);
|
|
if !value return "";
|
|
return to_string(value);
|
|
}
|
|
|
|
// ASEPRITE points the build at a different install without editing this.
|
|
override := read_env("ASEPRITE");
|
|
if override array_add(*candidates, override);
|
|
|
|
home := read_env("HOME");
|
|
if home array_add(*candidates, tprint("%/bin/aseprite", home));
|
|
}
|
|
|
|
#if OS == .MACOS {
|
|
array_add(*candidates, "/Applications/Aseprite.app/Contents/MacOS/aseprite");
|
|
}
|
|
|
|
array_add(*candidates, "aseprite"); // whatever is on PATH
|
|
|
|
for candidates {
|
|
// Probing by running it also catches a path that exists but isn't executable.
|
|
result := run_command(it, "--version", capture_and_return_output = true);
|
|
if result.type == .EXITED && result.exit_code == 0 return it, true;
|
|
}
|
|
return "", false;
|
|
}
|
|
|
|
collect_aseprite_sources :: (root: string, sources: *[..] string) {
|
|
if !is_directory(root) return;
|
|
|
|
visit_files(root, true, sources, (info: *File_Visit_Info, sources: *[..] string) {
|
|
if info.is_directory return;
|
|
if !is_aseprite_source(info.short_name) return;
|
|
array_add(sources, copy_string(info.full_name));
|
|
});
|
|
}
|
|
|
|
// Exports every Aseprite file in the mirror to a sheet pair beside it. pack.jai
|
|
// skips the sources themselves, so only the exports end up in the pack.
|
|
export_aseprite_sheets :: (roots: [] string) {
|
|
sources: [..] string;
|
|
sources.allocator = temp;
|
|
for roots collect_aseprite_sources(it, *sources);
|
|
if !sources return;
|
|
|
|
aseprite, found := find_aseprite_binary();
|
|
if !found {
|
|
log_error("Aseprite export: % source file(s) to export but no aseprite binary was found.", sources.count);
|
|
log_error("Looked for $ASEPRITE, $HOME/bin/aseprite, and 'aseprite' on PATH.");
|
|
exit(1);
|
|
}
|
|
|
|
for source: sources {
|
|
png, json := sheet_output_paths(source);
|
|
|
|
args: [..] string;
|
|
args.allocator = temp;
|
|
array_add(*args, aseprite, "-b", source, "--sheet", png, "--data", json);
|
|
for flag: ASEPRITE_EXPORT_FLAGS array_add(*args, flag);
|
|
|
|
result, output, error := run_command(..args, capture_and_return_output = true);
|
|
if result.type != .EXITED || result.exit_code != 0 {
|
|
log_error("Aseprite export failed for %", source);
|
|
if output log_error(output,, logger = runtime_support_default_logger);
|
|
if error log_error(error,, logger = runtime_support_default_logger);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
print("Aseprite: exported % sheet(s)\n", sources.count);
|
|
}
|