Zillion: Finalize item locations in either generate_output or fill_slot_data (#4121)

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
This commit is contained in:
Mysteryem 2025-01-19 23:20:45 +00:00 committed by GitHub
parent cbf4bbbca8
commit 9443861849
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 25 additions and 13 deletions

View File

@ -39,7 +39,7 @@ class TestImplemented(unittest.TestCase):
"""Tests that if a world creates slot data, it's json serializable."""
for game_name, world_type in AutoWorldRegister.world_types.items():
# has an await for generate_output which isn't being called
if game_name in {"Ocarina of Time", "Zillion"}:
if game_name in {"Ocarina of Time"}:
continue
multiworld = setup_solo_multiworld(world_type)
with self.subTest(game=game_name, seed=multiworld.seed):

View File

@ -119,8 +119,13 @@ class ZillionWorld(World):
"""
my_locations: list[ZillionLocation] = []
""" This is kind of a cache to avoid iterating through all the multiworld locations in logic. """
slot_data_ready: threading.Event
""" This event is set in `generate_output` when the data is ready for `fill_slot_data` """
finalized_gen_data: GenData | None
""" Finalized generation data needed by `generate_output` and by `fill_slot_data`. """
item_locations_finalization_lock: threading.Lock
"""
This lock is used in `generate_output` and `fill_slot_data` to ensure synchronized access to `finalized_gen_data`,
so that whichever is run first can finalize the item locations while the other waits.
"""
logic_cache: ZillionLogicCache | None = None
def __init__(self, world: MultiWorld, player: int) -> None:
@ -128,7 +133,8 @@ class ZillionWorld(World):
self.logger = logging.getLogger("Zillion")
self.lsi = ZillionWorld.LogStreamInterface(self.logger)
self.zz_system = System()
self.slot_data_ready = threading.Event()
self.finalized_gen_data = None
self.item_locations_finalization_lock = threading.Lock()
def _make_item_maps(self, start_char: Chars) -> None:
_id_to_name, _id_to_zz_id, id_to_zz_item = make_id_to_others(start_char)
@ -305,6 +311,19 @@ class ZillionWorld(World):
self.zz_system.post_fill()
def finalize_item_locations_thread_safe(self) -> GenData:
"""
Call self.finalize_item_locations() and cache the result in a thread-safe manner so that either
`generate_output` or `fill_slot_data` can finalize item locations without concern for which of the two functions
is called first.
"""
# The lock is acquired when entering the context manager and released when exiting the context manager.
with self.item_locations_finalization_lock:
# If generation data has yet to be finalized, finalize it.
if self.finalized_gen_data is None:
self.finalized_gen_data = self.finalize_item_locations()
return self.finalized_gen_data
def finalize_item_locations(self) -> GenData:
"""
sync zilliandomizer item locations with AP item locations
@ -363,12 +382,7 @@ class ZillionWorld(World):
def generate_output(self, output_directory: str) -> None:
"""This method gets called from a threadpool, do not use multiworld.random here.
If you need any last-second randomization, use self.random instead."""
try:
gen_data = self.finalize_item_locations()
except BaseException:
raise
finally:
self.slot_data_ready.set()
gen_data = self.finalize_item_locations_thread_safe()
out_file_base = self.multiworld.get_out_file_name_base(self.player)
@ -392,9 +406,7 @@ class ZillionWorld(World):
# TODO: tell client which canisters are keywords
# so it can open and get those when restoring doors
self.slot_data_ready.wait()
assert self.zz_system.randomizer, "didn't get randomizer from generate_early"
game = self.zz_system.get_game()
game = self.finalize_item_locations_thread_safe().zz_game
return get_slot_info(game.regions, game.char_order[0], game.loc_name_2_pretty)
# end of ordered Main.py calls