Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion test/general/test_implemented.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def test_slot_data(self):
"""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):
Expand Down
35 changes: 25 additions & 10 deletions worlds/zillion/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,16 +118,22 @@ def flush(self) -> None:
"""
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: Optional[GenData]
""" Finalized generation data needed by `generate_output` directly and by `fill_slot_data` indirectly. """
Mysteryem marked this conversation as resolved.
Show resolved Hide resolved
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: Union[ZillionLogicCache, None] = None

def __init__(self, world: MultiWorld, player: int):
super().__init__(world, player)
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)
Expand Down Expand Up @@ -308,6 +314,19 @@ def post_fill(self) -> None:

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
Expand Down Expand Up @@ -365,12 +384,7 @@ def finalize_item_locations(self) -> GenData:
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)

Expand All @@ -393,7 +407,8 @@ def fill_slot_data(self) -> ZillionSlotInfo: # json of WebHostLib.models.Slot
# TODO: tell client which canisters are keywords
# so it can open and get those when restoring doors

self.slot_data_ready.wait()
# `self.zz_system.get_game()` requires that item locations have been finalized.
self.finalize_item_locations_thread_safe()
assert self.zz_system.randomizer, "didn't get randomizer from generate_early"
game = self.zz_system.get_game()
Mysteryem marked this conversation as resolved.
Show resolved Hide resolved
return get_slot_info(game.regions, game.char_order[0], game.loc_name_2_pretty)
Expand Down
Loading