Skip to content

Commit 89984a0

Browse files
committed
Core: don't start threads for 'pass'
Core: print output progress every 10 files (OoT output may take a while, so let's give some user feedback on progress) Subnautica: remove empty output method
1 parent 2e2ca16 commit 89984a0

File tree

2 files changed

+158
-159
lines changed

2 files changed

+158
-159
lines changed

Main.py

+156-159
Original file line numberDiff line numberDiff line change
@@ -185,169 +185,166 @@ def main(args, seed=None):
185185
logger.info(f'Beginning output...')
186186
outfilebase = 'AP_' + world.seed_name
187187

188-
pool = concurrent.futures.ThreadPoolExecutor()
189-
190188
output = tempfile.TemporaryDirectory()
191189
with output as temp_dir:
192-
check_accessibility_task = pool.submit(world.fulfills_accessibility)
193-
194-
output_file_futures = []
190+
with concurrent.futures.ThreadPoolExecutor() as pool:
191+
check_accessibility_task = pool.submit(world.fulfills_accessibility)
192+
193+
output_file_futures = []
194+
195+
for player in world.player_ids:
196+
# skip starting a thread for methods that say "pass".
197+
if AutoWorld.World.generate_output.__code__ is not world.worlds[player].generate_output.__code__:
198+
output_file_futures.append(pool.submit(AutoWorld.call_single, world, "generate_output", player, temp_dir))
199+
output_file_futures.append(pool.submit(AutoWorld.call_stage, world, "generate_output", temp_dir))
200+
201+
def get_entrance_to_region(region: Region):
202+
for entrance in region.entrances:
203+
if entrance.parent_region.type in (RegionType.DarkWorld, RegionType.LightWorld, RegionType.Generic):
204+
return entrance
205+
for entrance in region.entrances: # BFS might be better here, trying DFS for now.
206+
return get_entrance_to_region(entrance.parent_region)
207+
208+
# collect ER hint info
209+
er_hint_data = {player: {} for player in world.get_game_players("A Link to the Past") if
210+
world.shuffle[player] != "vanilla" or world.retro[player]}
211+
212+
for region in world.regions:
213+
if region.player in er_hint_data and region.locations:
214+
main_entrance = get_entrance_to_region(region)
215+
for location in region.locations:
216+
if type(location.address) == int: # skips events and crystals
217+
if lookup_vanilla_location_to_entrance[location.address] != main_entrance.name:
218+
er_hint_data[region.player][location.address] = main_entrance.name
219+
220+
ordered_areas = ('Light World', 'Dark World', 'Hyrule Castle', 'Agahnims Tower', 'Eastern Palace', 'Desert Palace',
221+
'Tower of Hera', 'Palace of Darkness', 'Swamp Palace', 'Skull Woods', 'Thieves Town', 'Ice Palace',
222+
'Misery Mire', 'Turtle Rock', 'Ganons Tower', "Total")
223+
224+
checks_in_area = {player: {area: list() for area in ordered_areas}
225+
for player in range(1, world.players + 1)}
226+
227+
for player in range(1, world.players + 1):
228+
checks_in_area[player]["Total"] = 0
195229

196-
for player in world.player_ids:
197-
# skip starting a thread for methods that say "pass".
198-
if AutoWorld.World.generate_output.__code__ is not world.worlds[player].generate_output.__code__:
199-
output_file_futures.append(pool.submit(AutoWorld.call_single, world, "generate_output", player, temp_dir))
200-
output_file_futures.append(pool.submit(AutoWorld.call_stage, world, "generate_output", temp_dir))
201-
202-
def get_entrance_to_region(region: Region):
203-
for entrance in region.entrances:
204-
if entrance.parent_region.type in (RegionType.DarkWorld, RegionType.LightWorld, RegionType.Generic):
205-
return entrance
206-
for entrance in region.entrances: # BFS might be better here, trying DFS for now.
207-
return get_entrance_to_region(entrance.parent_region)
208-
209-
# collect ER hint info
210-
er_hint_data = {player: {} for player in world.get_game_players("A Link to the Past") if
211-
world.shuffle[player] != "vanilla" or world.retro[player]}
212-
213-
for region in world.regions:
214-
if region.player in er_hint_data and region.locations:
215-
main_entrance = get_entrance_to_region(region)
216-
for location in region.locations:
217-
if type(location.address) == int: # skips events and crystals
218-
if lookup_vanilla_location_to_entrance[location.address] != main_entrance.name:
219-
er_hint_data[region.player][location.address] = main_entrance.name
220-
221-
ordered_areas = ('Light World', 'Dark World', 'Hyrule Castle', 'Agahnims Tower', 'Eastern Palace', 'Desert Palace',
222-
'Tower of Hera', 'Palace of Darkness', 'Swamp Palace', 'Skull Woods', 'Thieves Town', 'Ice Palace',
223-
'Misery Mire', 'Turtle Rock', 'Ganons Tower', "Total")
224-
225-
checks_in_area = {player: {area: list() for area in ordered_areas}
226-
for player in range(1, world.players + 1)}
227-
228-
for player in range(1, world.players + 1):
229-
checks_in_area[player]["Total"] = 0
230-
231-
for location in world.get_filled_locations():
232-
if type(location.address) is int:
233-
main_entrance = get_entrance_to_region(location.parent_region)
234-
if location.game != "A Link to the Past":
235-
checks_in_area[location.player]["Light World"].append(location.address)
236-
elif location.parent_region.dungeon:
237-
dungeonname = {'Inverted Agahnims Tower': 'Agahnims Tower',
238-
'Inverted Ganons Tower': 'Ganons Tower'} \
239-
.get(location.parent_region.dungeon.name, location.parent_region.dungeon.name)
240-
checks_in_area[location.player][dungeonname].append(location.address)
241-
elif main_entrance.parent_region.type == RegionType.LightWorld:
242-
checks_in_area[location.player]["Light World"].append(location.address)
243-
elif main_entrance.parent_region.type == RegionType.DarkWorld:
244-
checks_in_area[location.player]["Dark World"].append(location.address)
245-
checks_in_area[location.player]["Total"] += 1
246-
247-
oldmancaves = []
248-
takeanyregions = ["Old Man Sword Cave", "Take-Any #1", "Take-Any #2", "Take-Any #3", "Take-Any #4"]
249-
for index, take_any in enumerate(takeanyregions):
250-
for region in [world.get_region(take_any, player) for player in range(1, world.players + 1) if
251-
world.retro[player]]:
252-
item = world.create_item(region.shop.inventory[(0 if take_any == "Old Man Sword Cave" else 1)]['item'],
253-
region.player)
254-
player = region.player
255-
location_id = SHOP_ID_START + total_shop_slots + index
256-
257-
main_entrance = get_entrance_to_region(region)
258-
if main_entrance.parent_region.type == RegionType.LightWorld:
259-
checks_in_area[player]["Light World"].append(location_id)
260-
else:
261-
checks_in_area[player]["Dark World"].append(location_id)
262-
checks_in_area[player]["Total"] += 1
263-
264-
er_hint_data[player][location_id] = main_entrance.name
265-
oldmancaves.append(((location_id, player), (item.code, player)))
266-
267-
FillDisabledShopSlots(world)
268-
269-
def write_multidata():
270-
import NetUtils
271-
slot_data = {}
272-
client_versions = {}
273-
minimum_versions = {"server": (0, 1, 1), "clients": client_versions}
274-
games = {}
275-
for slot in world.player_ids:
276-
client_versions[slot] = world.worlds[slot].get_required_client_version()
277-
games[slot] = world.game[slot]
278-
precollected_items = {player: [] for player in range(1, world.players + 1)}
279-
for item in world.precollected_items:
280-
precollected_items[item.player].append(item.code)
281-
precollected_hints = {player: set() for player in range(1, world.players + 1)}
282-
# for now special case Factorio tech_tree_information
283-
sending_visible_players = set()
284-
for player in world.get_game_players("Factorio"):
285-
if world.tech_tree_information[player].value == 2:
286-
sending_visible_players.add(player)
287-
288-
for slot in world.player_ids:
289-
slot_data[slot] = world.worlds[slot].fill_slot_data()
290-
291-
locations_data: Dict[int, Dict[int, Tuple[int, int]]] = {player: {} for player in world.player_ids}
292230
for location in world.get_filled_locations():
293-
if type(location.address) == int:
294-
# item code None should be event, location.address should then also be None
295-
assert location.item.code is not None
296-
locations_data[location.player][location.address] = location.item.code, location.item.player
297-
if location.player in sending_visible_players and location.item.player != location.player:
298-
hint = NetUtils.Hint(location.item.player, location.player, location.address,
299-
location.item.code, False)
300-
precollected_hints[location.player].add(hint)
301-
precollected_hints[location.item.player].add(hint)
302-
elif location.item.name in args.start_hints[location.item.player]:
303-
hint = NetUtils.Hint(location.item.player, location.player, location.address,
304-
location.item.code, False,
305-
er_hint_data.get(location.player, {}).get(location.address, ""))
306-
precollected_hints[location.player].add(hint)
307-
precollected_hints[location.item.player].add(hint)
308-
309-
multidata = {
310-
"slot_data": slot_data,
311-
"games": games,
312-
"names": [[name for player, name in sorted(world.player_name.items())]],
313-
"connect_names": {name: (0, player) for player, name in world.player_name.items()},
314-
"remote_items": {player for player in world.player_ids if
315-
world.worlds[player].remote_items},
316-
"locations": locations_data,
317-
"checks_in_area": checks_in_area,
318-
"server_options": get_options()["server_options"],
319-
"er_hint_data": er_hint_data,
320-
"precollected_items": precollected_items,
321-
"precollected_hints": precollected_hints,
322-
"version": tuple(version_tuple),
323-
"tags": ["AP"],
324-
"minimum_versions": minimum_versions,
325-
"seed_name": world.seed_name
326-
}
327-
AutoWorld.call_all(world, "modify_multidata", multidata)
328-
329-
multidata = zlib.compress(pickle.dumps(multidata), 9)
330-
331-
with open(os.path.join(temp_dir, f'{outfilebase}.archipelago'), 'wb') as f:
332-
f.write(bytes([1])) # version of format
333-
f.write(multidata)
334-
335-
multidata_task = pool.submit(write_multidata)
336-
if not check_accessibility_task.result():
337-
if not world.can_beat_game():
338-
raise Exception("Game appears as unbeatable. Aborting.")
339-
else:
340-
logger.warning("Location Accessibility requirements not fulfilled.")
341-
342-
# retrieve exceptions via .result() if they occured.
343-
if multidata_task:
344-
multidata_task.result()
345-
for i, future in enumerate(concurrent.futures.as_completed(output_file_futures)):
346-
if i % 10 == 0:
347-
logger.info(f'Generating output files ({i}/{len(output_file_futures)}).')
348-
future.result()
349-
350-
pool.shutdown() # wait for all queued tasks to complete
231+
if type(location.address) is int:
232+
main_entrance = get_entrance_to_region(location.parent_region)
233+
if location.game != "A Link to the Past":
234+
checks_in_area[location.player]["Light World"].append(location.address)
235+
elif location.parent_region.dungeon:
236+
dungeonname = {'Inverted Agahnims Tower': 'Agahnims Tower',
237+
'Inverted Ganons Tower': 'Ganons Tower'} \
238+
.get(location.parent_region.dungeon.name, location.parent_region.dungeon.name)
239+
checks_in_area[location.player][dungeonname].append(location.address)
240+
elif main_entrance.parent_region.type == RegionType.LightWorld:
241+
checks_in_area[location.player]["Light World"].append(location.address)
242+
elif main_entrance.parent_region.type == RegionType.DarkWorld:
243+
checks_in_area[location.player]["Dark World"].append(location.address)
244+
checks_in_area[location.player]["Total"] += 1
245+
246+
oldmancaves = []
247+
takeanyregions = ["Old Man Sword Cave", "Take-Any #1", "Take-Any #2", "Take-Any #3", "Take-Any #4"]
248+
for index, take_any in enumerate(takeanyregions):
249+
for region in [world.get_region(take_any, player) for player in range(1, world.players + 1) if
250+
world.retro[player]]:
251+
item = world.create_item(region.shop.inventory[(0 if take_any == "Old Man Sword Cave" else 1)]['item'],
252+
region.player)
253+
player = region.player
254+
location_id = SHOP_ID_START + total_shop_slots + index
255+
256+
main_entrance = get_entrance_to_region(region)
257+
if main_entrance.parent_region.type == RegionType.LightWorld:
258+
checks_in_area[player]["Light World"].append(location_id)
259+
else:
260+
checks_in_area[player]["Dark World"].append(location_id)
261+
checks_in_area[player]["Total"] += 1
262+
263+
er_hint_data[player][location_id] = main_entrance.name
264+
oldmancaves.append(((location_id, player), (item.code, player)))
265+
266+
FillDisabledShopSlots(world)
267+
268+
def write_multidata():
269+
import NetUtils
270+
slot_data = {}
271+
client_versions = {}
272+
minimum_versions = {"server": (0, 1, 1), "clients": client_versions}
273+
games = {}
274+
for slot in world.player_ids:
275+
client_versions[slot] = world.worlds[slot].get_required_client_version()
276+
games[slot] = world.game[slot]
277+
precollected_items = {player: [] for player in range(1, world.players + 1)}
278+
for item in world.precollected_items:
279+
precollected_items[item.player].append(item.code)
280+
precollected_hints = {player: set() for player in range(1, world.players + 1)}
281+
# for now special case Factorio tech_tree_information
282+
sending_visible_players = set()
283+
for player in world.get_game_players("Factorio"):
284+
if world.tech_tree_information[player].value == 2:
285+
sending_visible_players.add(player)
286+
287+
for slot in world.player_ids:
288+
slot_data[slot] = world.worlds[slot].fill_slot_data()
289+
290+
locations_data: Dict[int, Dict[int, Tuple[int, int]]] = {player: {} for player in world.player_ids}
291+
for location in world.get_filled_locations():
292+
if type(location.address) == int:
293+
# item code None should be event, location.address should then also be None
294+
assert location.item.code is not None
295+
locations_data[location.player][location.address] = location.item.code, location.item.player
296+
if location.player in sending_visible_players and location.item.player != location.player:
297+
hint = NetUtils.Hint(location.item.player, location.player, location.address,
298+
location.item.code, False)
299+
precollected_hints[location.player].add(hint)
300+
precollected_hints[location.item.player].add(hint)
301+
elif location.item.name in args.start_hints[location.item.player]:
302+
hint = NetUtils.Hint(location.item.player, location.player, location.address,
303+
location.item.code, False,
304+
er_hint_data.get(location.player, {}).get(location.address, ""))
305+
precollected_hints[location.player].add(hint)
306+
precollected_hints[location.item.player].add(hint)
307+
308+
multidata = {
309+
"slot_data": slot_data,
310+
"games": games,
311+
"names": [[name for player, name in sorted(world.player_name.items())]],
312+
"connect_names": {name: (0, player) for player, name in world.player_name.items()},
313+
"remote_items": {player for player in world.player_ids if
314+
world.worlds[player].remote_items},
315+
"locations": locations_data,
316+
"checks_in_area": checks_in_area,
317+
"server_options": get_options()["server_options"],
318+
"er_hint_data": er_hint_data,
319+
"precollected_items": precollected_items,
320+
"precollected_hints": precollected_hints,
321+
"version": tuple(version_tuple),
322+
"tags": ["AP"],
323+
"minimum_versions": minimum_versions,
324+
"seed_name": world.seed_name
325+
}
326+
AutoWorld.call_all(world, "modify_multidata", multidata)
327+
328+
multidata = zlib.compress(pickle.dumps(multidata), 9)
329+
330+
with open(os.path.join(temp_dir, f'{outfilebase}.archipelago'), 'wb') as f:
331+
f.write(bytes([1])) # version of format
332+
f.write(multidata)
333+
334+
multidata_task = pool.submit(write_multidata)
335+
if not check_accessibility_task.result():
336+
if not world.can_beat_game():
337+
raise Exception("Game appears as unbeatable. Aborting.")
338+
else:
339+
logger.warning("Location Accessibility requirements not fulfilled.")
340+
341+
# retrieve exceptions via .result() if they occured.
342+
if multidata_task:
343+
multidata_task.result()
344+
for i, future in enumerate(concurrent.futures.as_completed(output_file_futures)):
345+
if i % 10 == 0:
346+
logger.info(f'Generating output files ({i}/{len(output_file_futures)}).')
347+
future.result()
351348

352349
if not args.skip_playthrough:
353350
logger.info('Calculating playthrough.')

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Currently, the following games are supported:
99
* Subnautica
1010
* Slay the Spire
1111
* Risk of Rain 2
12+
* The Legend of Zelda: Ocarina of Time
1213

1314
For setup and instructions check out our [tutorials page](http://archipelago.gg:48484/tutorial).
1415
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled
@@ -39,6 +40,7 @@ This project makes use of multiple other projects. We wouldn't be here without t
3940

4041
* [z3randomizer](https://github.com/CaitSith2/z3randomizer)
4142
* [Enemizer](https://github.com/Ijwu/Enemizer)
43+
* [Ocarina of Time Randomizer](https://github.com/TestRunnerSRL/OoT-Randomizer)
4244

4345
## Contributing
4446
Contributions are welcome. We have a few asks of any new contributors.

0 commit comments

Comments
 (0)