Generate asset enum programmatically
This commit is contained in:
parent
6fb4f2b3f1
commit
31c28a7009
|
@ -0,0 +1,210 @@
|
|||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import yaml
|
||||
import re
|
||||
import os
|
||||
from typing import Dict, List
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def build_asset_map(assets_file: str) -> Dict[str, str]:
|
||||
file_types_to_skip = (
|
||||
'LevelSetup', # 0x071D - 0x07B5
|
||||
'DemoInput', # 0x09A2 - 0x0A0A
|
||||
'QuizQuestion', # 0x1213 - 0x13D5
|
||||
'GruntyQuestion', # 0x1407 - 0x1424
|
||||
'Midi' # 0x1516 - 0x15C2
|
||||
)
|
||||
|
||||
file_type_to_asset_type = {
|
||||
'Animation': 'ANIM',
|
||||
'Model': 'MODEL',
|
||||
'Sprite_I4': 'SPRITE',
|
||||
'Sprite_I8': 'SPRITE',
|
||||
'Sprite_CI4': 'SPRITE',
|
||||
'Sprite_CI8': 'SPRITE',
|
||||
'Sprite_RGBA16': 'SPRITE',
|
||||
'Sprite_RGBA32': 'SPRITE',
|
||||
'Sprite_UNKNOWN(256)': 'SPRITE',
|
||||
'Dialog': 'DIALOG',
|
||||
}
|
||||
|
||||
asset_map = {}
|
||||
with open(assets_file, 'r') as stream:
|
||||
try:
|
||||
data = yaml.safe_load(stream)
|
||||
if "files" not in data:
|
||||
raise Exception("Expected to find 'files' key in assets file")
|
||||
files = data["files"]
|
||||
for file in files:
|
||||
file_type = file["type"]
|
||||
if file_type in file_types_to_skip:
|
||||
continue
|
||||
|
||||
if file_type not in file_type_to_asset_type:
|
||||
continue
|
||||
|
||||
asset_type = file_type_to_asset_type[file_type]
|
||||
address = file["uid"]
|
||||
asset_map[f"{address:X}"] = f"ASSET_{address:X}_{asset_type}_UNKNOWN"
|
||||
except yaml.YAMLError as exc:
|
||||
print(exc)
|
||||
|
||||
return asset_map
|
||||
|
||||
|
||||
def get_existing_asset_enums(assets_enum_file: str) -> Dict[str, str]:
|
||||
with open(assets_enum_file, "r") as enums_stream:
|
||||
existing_asset_match = re.compile(r"^\s*(ASSET_([A-F0-9]+)_\w+)", re.IGNORECASE)
|
||||
existing_asset_mappings = {}
|
||||
for line in enums_stream:
|
||||
asset_line = existing_asset_match.match(line)
|
||||
if asset_line is not None:
|
||||
asset_address = asset_line.group(2).upper()
|
||||
existing_asset_mappings[asset_address] = asset_line.group(1)
|
||||
|
||||
return existing_asset_mappings
|
||||
|
||||
|
||||
def build_asset_enum(asset_map: Dict[str, str], existing_assets: Dict[str, str]) -> List[str]:
|
||||
asset_enum_lines = []
|
||||
address_already_defined = False
|
||||
for i in range(1, int(0x1516)):
|
||||
address = f"{i:X}"
|
||||
if address in existing_assets:
|
||||
if address_already_defined is False:
|
||||
asset_enum_lines.append(
|
||||
f"{existing_assets[address]} = 0x{address},")
|
||||
address_already_defined = True
|
||||
else:
|
||||
asset_enum_lines.append(f"{existing_assets[address]},")
|
||||
elif address in asset_map:
|
||||
if address_already_defined is False:
|
||||
asset_enum_lines.append(f"{asset_map[address]} = 0x{address},")
|
||||
address_already_defined = True
|
||||
else:
|
||||
asset_enum_lines.append(f"{asset_map[address]},")
|
||||
else:
|
||||
asset_enum_lines.append(f"// {address} unused")
|
||||
address_already_defined = False
|
||||
|
||||
asset_enum_lines = remove_unnecessary_unused_asset_lines(asset_enum_lines)
|
||||
|
||||
# Remove trailing comma in last asset line
|
||||
last_line: str = asset_enum_lines[-1]
|
||||
if last_line.endswith(","):
|
||||
last_line = last_line.replace(",", "")
|
||||
asset_enum_lines[-1] = last_line
|
||||
|
||||
return map(lambda x: f" {x}", asset_enum_lines)
|
||||
|
||||
|
||||
def remove_unnecessary_unused_asset_lines(lines: List[str]) -> List[str]:
|
||||
prev_unused = False
|
||||
unused_asset_line_indexes = []
|
||||
# Create list of lines that contain "// xx is unused"
|
||||
for idx, line in enumerate(lines):
|
||||
if line.endswith('unused') is False:
|
||||
continue
|
||||
|
||||
if idx + 1 >= len(lines):
|
||||
continue
|
||||
|
||||
prev: str | None = lines[idx - 1]
|
||||
prev_unused = prev is not None and prev.endswith('unused')
|
||||
|
||||
next: str | None = lines[idx + 1]
|
||||
next_unused = next is not None and next.endswith('unused')
|
||||
|
||||
if prev_unused and next_unused:
|
||||
unused_asset_line_indexes.append(idx)
|
||||
|
||||
# "Group" lines where multiple "// xx is unused" lines occur in a row
|
||||
groups = []
|
||||
group = []
|
||||
target = None
|
||||
for idx, line_index in enumerate(unused_asset_line_indexes):
|
||||
if target is not None and target > idx:
|
||||
continue
|
||||
else:
|
||||
target = None
|
||||
|
||||
for next in range(0, len(unused_asset_line_indexes) - idx):
|
||||
if unused_asset_line_indexes[next + idx] == line_index + next:
|
||||
group.append(next + idx)
|
||||
else:
|
||||
break
|
||||
|
||||
if len(group) == 0:
|
||||
continue
|
||||
|
||||
combine_from_index = group[0]
|
||||
combine_from = unused_asset_line_indexes[combine_from_index]
|
||||
combine_to_index = group[len(group)-1]
|
||||
combine_to = unused_asset_line_indexes[combine_to_index]
|
||||
groups.append((combine_from - 1, combine_to + 1))
|
||||
group = []
|
||||
target = next + idx
|
||||
|
||||
# Replace the "unnecessary" lines with a single line that lists the range that is unused
|
||||
offset = 0
|
||||
address_regex = re.compile(r"^// (\w+) unused$")
|
||||
for group in groups:
|
||||
(start, end) = group
|
||||
start -= offset
|
||||
start_line = lines[start]
|
||||
start_match = address_regex.match(start_line)
|
||||
|
||||
end -= offset
|
||||
end_line = lines[end]
|
||||
end_match = address_regex.match(end_line)
|
||||
|
||||
if start_match is not None and end_match is not None:
|
||||
lines[start:end+1] = [
|
||||
f"// 0x{start_match.group(1)} - 0x{end_match.group(1)} unused"
|
||||
]
|
||||
|
||||
offset += end - start
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def write_enum_file(asset_lines: List[str], out_file: str):
|
||||
with open(out_file, "w") as enums_out:
|
||||
lines = [
|
||||
"enum asset_e",
|
||||
"{",
|
||||
*asset_lines,
|
||||
"};"
|
||||
]
|
||||
enums_out.write("\n".join(lines))
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Generate asset_e enum from assets/assets.yaml and include/enums.h files',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||
)
|
||||
parser.add_argument(
|
||||
'basedir',
|
||||
type=str,
|
||||
help="base directory (containing src/)"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
asset_map = build_asset_map(
|
||||
assets_file=f"{args.basedir}{os.sep}assets{os.sep}assets.yaml"
|
||||
)
|
||||
existing_assets = get_existing_asset_enums(
|
||||
assets_enum_file=f"{args.basedir}{os.sep}include{os.sep}enums.h"
|
||||
)
|
||||
|
||||
asset_enum_lines = build_asset_enum(asset_map, existing_assets)
|
||||
write_enum_file(
|
||||
asset_enum_lines,
|
||||
out_file=f"{args.basedir}{os.sep}assets.h"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue