mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-04-05 20:48:12 -07:00
Create a new class that handles conversion worlds+embedded -> context data. Create a derived class that uses static_server_data+pony instead. There is also a not very efficient feature to deduplicate strings (may need perf testing). By moving code around, we can simplify a lot of the world loading. Where code lines were touched, some typing and some reformatting was added. The back compat for GetDataPackage without games was finally dropped. This was done as a cleanup because the refactoring touched those lines anyway. Also reworked the per-context dicts and the RoomInfo to hopefully be more efficient by ignoring unused games. (Generating the list of used games was required for the new code anyway.) Side effect of the MultiServer cache: we now load worlds lazily (but still all at once) and don't modify the games package in place. If needed we create copies. This almost gets us to the point where MultiServer doesn't need worlds - it still needs them for the forbidden items. There is a bonus optimization that deduplicates strings in name_groups that may have bad performance and may need some perf testing if we run into issues.
111 lines
4.6 KiB
Python
111 lines
4.6 KiB
Python
import typing as t
|
|
from weakref import WeakValueDictionary
|
|
|
|
from NetUtils import GamesPackage, DataPackage
|
|
|
|
GameAndChecksum = tuple[str, str | None]
|
|
ItemNameGroups = dict[str, list[str]]
|
|
LocationNameGroups = dict[str, list[str]]
|
|
|
|
|
|
K = t.TypeVar("K")
|
|
V = t.TypeVar("V")
|
|
|
|
|
|
class DictLike(dict[K, V]):
|
|
__slots__ = ("__weakref__",)
|
|
|
|
|
|
class GamesPackageCache:
|
|
# NOTE: this uses 3 separate collections because unpacking the get() result would end the container lifetime
|
|
_reduced_games_packages: WeakValueDictionary[GameAndChecksum, GamesPackage]
|
|
"""Does not include item_name_groups nor location_name_groups"""
|
|
_item_name_groups: WeakValueDictionary[GameAndChecksum, dict[str, list[str]]]
|
|
_location_name_groups: WeakValueDictionary[GameAndChecksum, dict[str, list[str]]]
|
|
|
|
def __init__(self) -> None:
|
|
self._reduced_games_packages = WeakValueDictionary()
|
|
self._item_name_groups = WeakValueDictionary()
|
|
self._location_name_groups = WeakValueDictionary()
|
|
|
|
def _cached_item_name(self, key: GameAndChecksum, item_name: str) -> str:
|
|
"""Returns a reference to an already-stored copy of item_name, or item_name"""
|
|
# TODO: there gotta be a better way, but maybe only in a C module?
|
|
for cached_item_name in self._reduced_games_packages[key].keys():
|
|
if cached_item_name == item_name:
|
|
return cached_item_name
|
|
return item_name
|
|
|
|
def _cached_location_name(self, key: GameAndChecksum, location_name: str) -> str:
|
|
"""Returns a reference to an already-stored copy of location_name, or location_name"""
|
|
# TODO: as above
|
|
for cached_item_name in self._reduced_games_packages[key].keys():
|
|
if cached_item_name == location_name:
|
|
return cached_item_name
|
|
return location_name
|
|
|
|
def _get(
|
|
self,
|
|
cache_key: GameAndChecksum,
|
|
) -> tuple[GamesPackage | None, ItemNameGroups | None, LocationNameGroups | None]:
|
|
if cache_key[1] is None:
|
|
return None, None, None
|
|
return (
|
|
self._reduced_games_packages.get(cache_key, None),
|
|
self._item_name_groups.get(cache_key, None),
|
|
self._location_name_groups.get(cache_key, None),
|
|
)
|
|
|
|
def get(
|
|
self,
|
|
game: str,
|
|
full_games_package: GamesPackage,
|
|
) -> tuple[GamesPackage, ItemNameGroups, LocationNameGroups]:
|
|
"""Loads and caches embedded data package provided by multidata"""
|
|
cache_key = (game, full_games_package.get("checksum", None))
|
|
cached_reduced_games_package, cached_item_name_groups, cached_location_name_groups = self._get(cache_key)
|
|
|
|
if cached_reduced_games_package is None:
|
|
cached_reduced_games_package = t.cast(
|
|
t.Any,
|
|
DictLike(
|
|
{
|
|
"item_name_to_id": full_games_package["item_name_to_id"],
|
|
"location_name_to_id": full_games_package["location_name_to_id"],
|
|
"checksum": full_games_package["checksum"],
|
|
}
|
|
),
|
|
)
|
|
if cache_key[1] is not None: # only cache if checksum is available
|
|
self._reduced_games_packages[cache_key] = cached_reduced_games_package
|
|
|
|
if cached_item_name_groups is None:
|
|
cached_item_name_groups = DictLike(
|
|
{
|
|
group_name: [self._cached_item_name(cache_key, item_name) for item_name in group_items]
|
|
for group_name, group_items in full_games_package["item_name_groups"].items()
|
|
}
|
|
)
|
|
if cache_key[1] is not None: # only cache if checksum is available
|
|
self._item_name_groups[cache_key] = cached_item_name_groups
|
|
|
|
if cached_location_name_groups is None:
|
|
cached_location_name_groups = DictLike(
|
|
{
|
|
group_name: [
|
|
self._cached_location_name(cache_key, location_name) for location_name in group_locations
|
|
]
|
|
for group_name, group_locations in full_games_package.get("location_name_groups", {}).items()
|
|
}
|
|
)
|
|
if cache_key[1] is not None: # only cache if checksum is available
|
|
self._location_name_groups[cache_key] = cached_location_name_groups
|
|
|
|
return cached_reduced_games_package, cached_item_name_groups, cached_location_name_groups
|
|
|
|
def get_static(self, game: str) -> tuple[GamesPackage, ItemNameGroups, LocationNameGroups]:
|
|
"""Loads legacy data package from installed worlds"""
|
|
import worlds
|
|
|
|
return self.get(game, worlds.network_data_package["games"][game])
|