Core: log fill progress (#2382)
* Core: log fill progress * Add names to common fill steps * Update Fill.py Co-authored-by: el-u <109771707+el-u@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: el-u <109771707+el-u@users.noreply.github.com> * cleanup default name --------- Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
This commit is contained in:
parent
d5745d4051
commit
f81e72686a
45
Fill.py
45
Fill.py
|
@ -15,6 +15,10 @@ class FillError(RuntimeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _log_fill_progress(name: str, placed: int, total_items: int) -> None:
|
||||||
|
logging.info(f"Current fill step ({name}) at {placed}/{total_items} items placed.")
|
||||||
|
|
||||||
|
|
||||||
def sweep_from_pool(base_state: CollectionState, itempool: typing.Sequence[Item] = tuple()) -> CollectionState:
|
def sweep_from_pool(base_state: CollectionState, itempool: typing.Sequence[Item] = tuple()) -> CollectionState:
|
||||||
new_state = base_state.copy()
|
new_state = base_state.copy()
|
||||||
for item in itempool:
|
for item in itempool:
|
||||||
|
@ -26,7 +30,7 @@ def sweep_from_pool(base_state: CollectionState, itempool: typing.Sequence[Item]
|
||||||
def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations: typing.List[Location],
|
def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations: typing.List[Location],
|
||||||
item_pool: typing.List[Item], single_player_placement: bool = False, lock: bool = False,
|
item_pool: typing.List[Item], single_player_placement: bool = False, lock: bool = False,
|
||||||
swap: bool = True, on_place: typing.Optional[typing.Callable[[Location], None]] = None,
|
swap: bool = True, on_place: typing.Optional[typing.Callable[[Location], None]] = None,
|
||||||
allow_partial: bool = False, allow_excluded: bool = False) -> None:
|
allow_partial: bool = False, allow_excluded: bool = False, name: str = "Unknown") -> None:
|
||||||
"""
|
"""
|
||||||
:param world: Multiworld to be filled.
|
:param world: Multiworld to be filled.
|
||||||
:param base_state: State assumed before fill.
|
:param base_state: State assumed before fill.
|
||||||
|
@ -38,16 +42,20 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations:
|
||||||
:param on_place: callback that is called when a placement happens
|
:param on_place: callback that is called when a placement happens
|
||||||
:param allow_partial: only place what is possible. Remaining items will be in the item_pool list.
|
:param allow_partial: only place what is possible. Remaining items will be in the item_pool list.
|
||||||
:param allow_excluded: if true and placement fails, it is re-attempted while ignoring excluded on Locations
|
:param allow_excluded: if true and placement fails, it is re-attempted while ignoring excluded on Locations
|
||||||
|
:param name: name of this fill step for progress logging purposes
|
||||||
"""
|
"""
|
||||||
unplaced_items: typing.List[Item] = []
|
unplaced_items: typing.List[Item] = []
|
||||||
placements: typing.List[Location] = []
|
placements: typing.List[Location] = []
|
||||||
cleanup_required = False
|
cleanup_required = False
|
||||||
|
|
||||||
swapped_items: typing.Counter[typing.Tuple[int, str, bool]] = Counter()
|
swapped_items: typing.Counter[typing.Tuple[int, str, bool]] = Counter()
|
||||||
reachable_items: typing.Dict[int, typing.Deque[Item]] = {}
|
reachable_items: typing.Dict[int, typing.Deque[Item]] = {}
|
||||||
for item in item_pool:
|
for item in item_pool:
|
||||||
reachable_items.setdefault(item.player, deque()).append(item)
|
reachable_items.setdefault(item.player, deque()).append(item)
|
||||||
|
|
||||||
|
# for progress logging
|
||||||
|
total = min(len(item_pool), len(locations))
|
||||||
|
placed = 0
|
||||||
|
|
||||||
while any(reachable_items.values()) and locations:
|
while any(reachable_items.values()) and locations:
|
||||||
# grab one item per player
|
# grab one item per player
|
||||||
items_to_place = [items.pop()
|
items_to_place = [items.pop()
|
||||||
|
@ -152,9 +160,15 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations:
|
||||||
spot_to_fill.locked = lock
|
spot_to_fill.locked = lock
|
||||||
placements.append(spot_to_fill)
|
placements.append(spot_to_fill)
|
||||||
spot_to_fill.event = item_to_place.advancement
|
spot_to_fill.event = item_to_place.advancement
|
||||||
|
placed += 1
|
||||||
|
if not placed % 1000:
|
||||||
|
_log_fill_progress(name, placed, total)
|
||||||
if on_place:
|
if on_place:
|
||||||
on_place(spot_to_fill)
|
on_place(spot_to_fill)
|
||||||
|
|
||||||
|
if total > 1000:
|
||||||
|
_log_fill_progress(name, placed, total)
|
||||||
|
|
||||||
if cleanup_required:
|
if cleanup_required:
|
||||||
# validate all placements and remove invalid ones
|
# validate all placements and remove invalid ones
|
||||||
state = sweep_from_pool(base_state, [])
|
state = sweep_from_pool(base_state, [])
|
||||||
|
@ -198,6 +212,8 @@ def remaining_fill(world: MultiWorld,
|
||||||
unplaced_items: typing.List[Item] = []
|
unplaced_items: typing.List[Item] = []
|
||||||
placements: typing.List[Location] = []
|
placements: typing.List[Location] = []
|
||||||
swapped_items: typing.Counter[typing.Tuple[int, str]] = Counter()
|
swapped_items: typing.Counter[typing.Tuple[int, str]] = Counter()
|
||||||
|
total = min(len(itempool), len(locations))
|
||||||
|
placed = 0
|
||||||
while locations and itempool:
|
while locations and itempool:
|
||||||
item_to_place = itempool.pop()
|
item_to_place = itempool.pop()
|
||||||
spot_to_fill: typing.Optional[Location] = None
|
spot_to_fill: typing.Optional[Location] = None
|
||||||
|
@ -247,6 +263,12 @@ def remaining_fill(world: MultiWorld,
|
||||||
|
|
||||||
world.push_item(spot_to_fill, item_to_place, False)
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
placements.append(spot_to_fill)
|
placements.append(spot_to_fill)
|
||||||
|
placed += 1
|
||||||
|
if not placed % 1000:
|
||||||
|
_log_fill_progress("Remaining", placed, total)
|
||||||
|
|
||||||
|
if total > 1000:
|
||||||
|
_log_fill_progress("Remaining", placed, total)
|
||||||
|
|
||||||
if unplaced_items and locations:
|
if unplaced_items and locations:
|
||||||
# There are leftover unplaceable items and locations that won't accept them
|
# There are leftover unplaceable items and locations that won't accept them
|
||||||
|
@ -282,7 +304,7 @@ def accessibility_corrections(world: MultiWorld, state: CollectionState, locatio
|
||||||
locations.append(location)
|
locations.append(location)
|
||||||
if pool and locations:
|
if pool and locations:
|
||||||
locations.sort(key=lambda loc: loc.progress_type != LocationProgressType.PRIORITY)
|
locations.sort(key=lambda loc: loc.progress_type != LocationProgressType.PRIORITY)
|
||||||
fill_restrictive(world, state, locations, pool)
|
fill_restrictive(world, state, locations, pool, name="Accessibility Corrections")
|
||||||
|
|
||||||
|
|
||||||
def inaccessible_location_rules(world: MultiWorld, state: CollectionState, locations):
|
def inaccessible_location_rules(world: MultiWorld, state: CollectionState, locations):
|
||||||
|
@ -352,23 +374,25 @@ def distribute_early_items(world: MultiWorld,
|
||||||
player_local = early_local_rest_items[player]
|
player_local = early_local_rest_items[player]
|
||||||
fill_restrictive(world, base_state,
|
fill_restrictive(world, base_state,
|
||||||
[loc for loc in early_locations if loc.player == player],
|
[loc for loc in early_locations if loc.player == player],
|
||||||
player_local, lock=True, allow_partial=True)
|
player_local, lock=True, allow_partial=True, name=f"Local Early Items P{player}")
|
||||||
if player_local:
|
if player_local:
|
||||||
logging.warning(f"Could not fulfill rules of early items: {player_local}")
|
logging.warning(f"Could not fulfill rules of early items: {player_local}")
|
||||||
early_rest_items.extend(early_local_rest_items[player])
|
early_rest_items.extend(early_local_rest_items[player])
|
||||||
early_locations = [loc for loc in early_locations if not loc.item]
|
early_locations = [loc for loc in early_locations if not loc.item]
|
||||||
fill_restrictive(world, base_state, early_locations, early_rest_items, lock=True, allow_partial=True)
|
fill_restrictive(world, base_state, early_locations, early_rest_items, lock=True, allow_partial=True,
|
||||||
|
name="Early Items")
|
||||||
early_locations += early_priority_locations
|
early_locations += early_priority_locations
|
||||||
for player in world.player_ids:
|
for player in world.player_ids:
|
||||||
player_local = early_local_prog_items[player]
|
player_local = early_local_prog_items[player]
|
||||||
fill_restrictive(world, base_state,
|
fill_restrictive(world, base_state,
|
||||||
[loc for loc in early_locations if loc.player == player],
|
[loc for loc in early_locations if loc.player == player],
|
||||||
player_local, lock=True, allow_partial=True)
|
player_local, lock=True, allow_partial=True, name=f"Local Early Progression P{player}")
|
||||||
if player_local:
|
if player_local:
|
||||||
logging.warning(f"Could not fulfill rules of early items: {player_local}")
|
logging.warning(f"Could not fulfill rules of early items: {player_local}")
|
||||||
early_prog_items.extend(player_local)
|
early_prog_items.extend(player_local)
|
||||||
early_locations = [loc for loc in early_locations if not loc.item]
|
early_locations = [loc for loc in early_locations if not loc.item]
|
||||||
fill_restrictive(world, base_state, early_locations, early_prog_items, lock=True, allow_partial=True)
|
fill_restrictive(world, base_state, early_locations, early_prog_items, lock=True, allow_partial=True,
|
||||||
|
name="Early Progression")
|
||||||
unplaced_early_items = early_rest_items + early_prog_items
|
unplaced_early_items = early_rest_items + early_prog_items
|
||||||
if unplaced_early_items:
|
if unplaced_early_items:
|
||||||
logging.warning("Ran out of early locations for early items. Failed to place "
|
logging.warning("Ran out of early locations for early items. Failed to place "
|
||||||
|
@ -422,13 +446,14 @@ def distribute_items_restrictive(world: MultiWorld) -> None:
|
||||||
|
|
||||||
if prioritylocations:
|
if prioritylocations:
|
||||||
# "priority fill"
|
# "priority fill"
|
||||||
fill_restrictive(world, world.state, prioritylocations, progitempool, swap=False, on_place=mark_for_locking)
|
fill_restrictive(world, world.state, prioritylocations, progitempool, swap=False, on_place=mark_for_locking,
|
||||||
|
name="Priority")
|
||||||
accessibility_corrections(world, world.state, prioritylocations, progitempool)
|
accessibility_corrections(world, world.state, prioritylocations, progitempool)
|
||||||
defaultlocations = prioritylocations + defaultlocations
|
defaultlocations = prioritylocations + defaultlocations
|
||||||
|
|
||||||
if progitempool:
|
if progitempool:
|
||||||
# "progression fill"
|
# "advancement/progression fill"
|
||||||
fill_restrictive(world, world.state, defaultlocations, progitempool)
|
fill_restrictive(world, world.state, defaultlocations, progitempool, name="Progression")
|
||||||
if progitempool:
|
if progitempool:
|
||||||
raise FillError(
|
raise FillError(
|
||||||
f'Not enough locations for progress items. There are {len(progitempool)} more items than locations')
|
f'Not enough locations for progress items. There are {len(progitempool)} more items than locations')
|
||||||
|
|
|
@ -264,7 +264,8 @@ def fill_dungeons_restrictive(multiworld: MultiWorld):
|
||||||
|
|
||||||
if loc in all_state_base.events:
|
if loc in all_state_base.events:
|
||||||
all_state_base.events.remove(loc)
|
all_state_base.events.remove(loc)
|
||||||
fill_restrictive(multiworld, all_state_base, locations, in_dungeon_items, True, True)
|
fill_restrictive(multiworld, all_state_base, locations, in_dungeon_items, True, True,
|
||||||
|
name="LttP Dungeon Items")
|
||||||
|
|
||||||
|
|
||||||
dungeon_music_addresses = {'Eastern Palace - Prize': [0x1559A],
|
dungeon_music_addresses = {'Eastern Palace - Prize': [0x1559A],
|
||||||
|
|
|
@ -470,7 +470,8 @@ class ALTTPWorld(World):
|
||||||
prizepool = unplaced_prizes.copy()
|
prizepool = unplaced_prizes.copy()
|
||||||
prize_locs = empty_crystal_locations.copy()
|
prize_locs = empty_crystal_locations.copy()
|
||||||
world.random.shuffle(prize_locs)
|
world.random.shuffle(prize_locs)
|
||||||
fill_restrictive(world, all_state, prize_locs, prizepool, True, lock=True)
|
fill_restrictive(world, all_state, prize_locs, prizepool, True, lock=True,
|
||||||
|
name="LttP Dungeon Prizes")
|
||||||
except FillError as e:
|
except FillError as e:
|
||||||
lttp_logger.exception("Failed to place dungeon prizes (%s). Will retry %s more times", e,
|
lttp_logger.exception("Failed to place dungeon prizes (%s). Will retry %s more times", e,
|
||||||
attempts - attempt)
|
attempts - attempt)
|
||||||
|
|
Loading…
Reference in New Issue