mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Refactor Templates to Layouts, Review and Cleanup
- Fix tgogenerator - Fix UI for ForceGroup and Layouts - Fix ammo depot handling - Split bigger files in smaller meaningful files (TGO, layouts, forces) - Renamed Template to Layout - Renamed GroundGroup to TheaterGroup and GroundUnit to TheaterUnit - Reorganize Layouts and UnitGroups to a ArmedForces class and ForceGroup similar to the AirWing and Squadron - Reworded the UnitClass, GroupRole, GroupTask (adopted to PEP8) and reworked the connection from Role and Task - added comments - added missing unit classes - added temp workaround for missing classes - add repariable property to TheaterUnit - Review and Cleanup Added serialization for loaded templates Loading the templates from the .miz files takes a lot of computation time and in the future there will be more templates added to the system. Therefore a local pickle serialization for the loaded templates was re-added: - The pickle will be created the first time the TemplateLoader will be accessed - Pickle is stored in Liberation SaveDir - Added UI option to (re-)import templates
This commit is contained in:
203
game/layout/layoutloader.py
Normal file
203
game/layout/layoutloader.py
Normal file
@@ -0,0 +1,203 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
import logging
|
||||
import pickle
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from pathlib import Path
|
||||
from typing import Iterator
|
||||
|
||||
import dcs
|
||||
import yaml
|
||||
from dcs import Point
|
||||
from dcs.unitgroup import StaticGroup
|
||||
|
||||
from game import persistency
|
||||
from game.data.groups import GroupRole, GroupTask
|
||||
from game.layout.layout import (
|
||||
TheaterLayout,
|
||||
GroupLayout,
|
||||
LayoutUnit,
|
||||
AntiAirLayout,
|
||||
BuildingLayout,
|
||||
NavalLayout,
|
||||
GroundForceLayout,
|
||||
DefensesLayout,
|
||||
)
|
||||
from game.layout.layoutmapping import GroupLayoutMapping, LayoutMapping
|
||||
from game.profiling import logged_duration
|
||||
from game.version import VERSION
|
||||
|
||||
TEMPLATE_DIR = "resources/layouts/"
|
||||
TEMPLATE_DUMP = "Liberation/layouts.p"
|
||||
|
||||
TEMPLATE_TYPES = {
|
||||
GroupRole.AIR_DEFENSE: AntiAirLayout,
|
||||
GroupRole.BUILDING: BuildingLayout,
|
||||
GroupRole.NAVAL: NavalLayout,
|
||||
GroupRole.GROUND_FORCE: GroundForceLayout,
|
||||
GroupRole.DEFENSES: DefensesLayout,
|
||||
}
|
||||
|
||||
|
||||
class LayoutLoader:
|
||||
# list of layouts per category. e.g. AA or similar
|
||||
_templates: dict[str, TheaterLayout] = {}
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._templates = {}
|
||||
|
||||
def initialize(self) -> None:
|
||||
if not self._templates:
|
||||
with logged_duration("Loading layouts"):
|
||||
self.load_templates()
|
||||
|
||||
@property
|
||||
def layouts(self) -> Iterator[TheaterLayout]:
|
||||
self.initialize()
|
||||
yield from self._templates.values()
|
||||
|
||||
def load_templates(self) -> None:
|
||||
"""This will load all pre-loaded layouts from a pickle file.
|
||||
If pickle can not be loaded it will import and dump the layouts"""
|
||||
# We use a pickle for performance reasons. Importing takes many seconds
|
||||
file = Path(persistency.base_path()) / TEMPLATE_DUMP
|
||||
if file.is_file():
|
||||
# Load from pickle if existing
|
||||
with file.open("rb") as f:
|
||||
try:
|
||||
version, self._templates = pickle.load(f)
|
||||
# Check if the game version of the dump is identical to the current
|
||||
if version == VERSION:
|
||||
return
|
||||
except Exception as e:
|
||||
logging.error(f"Error {e} reading layouts dump. Recreating.")
|
||||
# If no dump is available or game version is different create a new dump
|
||||
self.import_templates()
|
||||
|
||||
def import_templates(self) -> None:
|
||||
"""This will import all layouts from the template folder
|
||||
and dumps them to a pickle"""
|
||||
mappings: dict[str, list[LayoutMapping]] = {}
|
||||
with logged_duration("Parsing mapping yamls"):
|
||||
for file in Path(TEMPLATE_DIR).rglob("*.yaml"):
|
||||
if not file.is_file():
|
||||
continue
|
||||
with file.open("r", encoding="utf-8") as f:
|
||||
mapping_dict = yaml.safe_load(f)
|
||||
|
||||
template_map = LayoutMapping.from_dict(mapping_dict, f.name)
|
||||
|
||||
if template_map.layout_file in mappings:
|
||||
mappings[template_map.layout_file].append(template_map)
|
||||
else:
|
||||
mappings[template_map.layout_file] = [template_map]
|
||||
|
||||
with logged_duration(f"Parsing all layout miz multithreaded"):
|
||||
with ThreadPoolExecutor() as exe:
|
||||
for miz, maps in mappings.items():
|
||||
exe.submit(self._load_from_miz, miz, maps)
|
||||
|
||||
logging.info(f"Imported {len(self._templates)} layouts")
|
||||
self._dump_templates()
|
||||
|
||||
def _dump_templates(self) -> None:
|
||||
file = Path(persistency.base_path()) / TEMPLATE_DUMP
|
||||
dump = (VERSION, self._templates)
|
||||
with file.open("wb") as fdata:
|
||||
pickle.dump(dump, fdata)
|
||||
|
||||
@staticmethod
|
||||
def mapping_for_group(
|
||||
mappings: list[LayoutMapping], group_name: str
|
||||
) -> tuple[LayoutMapping, int, GroupLayoutMapping]:
|
||||
for mapping in mappings:
|
||||
for g_id, group_mapping in enumerate(mapping.groups):
|
||||
if (
|
||||
group_mapping.name == group_name
|
||||
or group_name in group_mapping.statics
|
||||
):
|
||||
return mapping, g_id, group_mapping
|
||||
raise KeyError
|
||||
|
||||
def _load_from_miz(self, miz: str, mappings: list[LayoutMapping]) -> None:
|
||||
template_position: dict[str, Point] = {}
|
||||
temp_mis = dcs.Mission()
|
||||
with logged_duration(f"Parsing {miz}"):
|
||||
# The load_file takes a lot of time to compute. That's why the layouts
|
||||
# are written to a pickle and can be reloaded from the ui
|
||||
# Example the whole routine: 0:00:00.934417,
|
||||
# the .load_file() method: 0:00:00.920409
|
||||
temp_mis.load_file(miz)
|
||||
|
||||
for country in itertools.chain(
|
||||
temp_mis.coalition["red"].countries.values(),
|
||||
temp_mis.coalition["blue"].countries.values(),
|
||||
):
|
||||
for dcs_group in itertools.chain(
|
||||
temp_mis.country(country.name).vehicle_group,
|
||||
temp_mis.country(country.name).ship_group,
|
||||
temp_mis.country(country.name).static_group,
|
||||
):
|
||||
try:
|
||||
mapping, group_id, group_mapping = self.mapping_for_group(
|
||||
mappings, dcs_group.name
|
||||
)
|
||||
except KeyError:
|
||||
logging.warning(f"No mapping for dcs group {dcs_group.name}")
|
||||
continue
|
||||
|
||||
template = self._templates.get(mapping.name, None)
|
||||
if template is None:
|
||||
# Create a new template
|
||||
template = TEMPLATE_TYPES[mapping.role](
|
||||
mapping.name, mapping.role, mapping.description
|
||||
)
|
||||
template.generic = mapping.generic
|
||||
template.tasks = mapping.tasks
|
||||
self._templates[template.name] = template
|
||||
|
||||
for i, unit in enumerate(dcs_group.units):
|
||||
group_template = None
|
||||
for group in template.groups:
|
||||
if group.name == group_mapping.name:
|
||||
# We already have a layoutgroup for this dcs_group
|
||||
group_template = group
|
||||
if not group_template:
|
||||
group_template = GroupLayout(
|
||||
group_mapping.name,
|
||||
[],
|
||||
group_mapping.group,
|
||||
group_mapping.unit_count,
|
||||
group_mapping.unit_types,
|
||||
group_mapping.unit_classes,
|
||||
group_mapping.alternative_classes,
|
||||
)
|
||||
group_template.optional = group_mapping.optional
|
||||
# Add the group at the correct position
|
||||
template.add_group(group_template, group_id)
|
||||
unit_template = LayoutUnit.from_unit(unit)
|
||||
if i == 0 and template.name not in template_position:
|
||||
template_position[template.name] = unit.position
|
||||
unit_template.position = (
|
||||
unit_template.position - template_position[template.name]
|
||||
)
|
||||
group_template.units.append(unit_template)
|
||||
|
||||
def by_name(self, template_name: str) -> Iterator[TheaterLayout]:
|
||||
for template in self.layouts:
|
||||
if template.name == template_name:
|
||||
yield template
|
||||
|
||||
def by_task(self, group_task: GroupTask) -> Iterator[TheaterLayout]:
|
||||
for template in self.layouts:
|
||||
if not group_task or group_task in template.tasks:
|
||||
yield template
|
||||
|
||||
def by_tasks(self, group_tasks: list[GroupTask]) -> Iterator[TheaterLayout]:
|
||||
unique_templates = []
|
||||
for group_task in group_tasks:
|
||||
for template in self.by_task(group_task):
|
||||
if template not in unique_templates:
|
||||
unique_templates.append(template)
|
||||
yield from unique_templates
|
||||
Reference in New Issue
Block a user