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

Core: optimize "fast_fill" #6

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 21 additions & 16 deletions Fill.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,36 +136,34 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations:
itempool.extend(unplaced_items)


def fast_fill(world: MultiWorld, locations: typing.List[Location],
itempool: typing.List[Item], single_player_placement: bool = False) -> None:
def remaining_fill(world: MultiWorld,
locations: typing.List[Location],
itempool: typing.List[Item]) -> None:
unplaced_items: typing.List[Item] = []
placements: typing.List[Location] = []
swap = 0
swapped_items: typing.Counter[typing.Tuple[int, str]] = Counter()
while locations and itempool:
item_to_place = itempool.pop()
spot_to_fill: typing.Optional[Location] = None

for i, location in enumerate(locations):
if location.item_rule(item_to_place) and (not single_player_placement or
location.player == item_to_place.player):
if location.item_rule(item_to_place):
# popping by index is faster than removing by content,
spot_to_fill = locations.pop(i)
# skipping a scan for the element
break

else:
swap += 1
# we filled all reachable spots.
# try swapping this item with previously placed items

for (i, location) in enumerate(placements):
placed_item = location.item
# Unplaceable items can sometimes be swapped infinitely. Limit the
# number of times we will swap an individual item to prevent this
swap_count = swapped_items[placed_item.player,
placed_item.name]
if swap_count > 1 or placed_item.advancement:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to ensure it does not swap out advancement items so they do not get placed in this routine which disregards access rules

Copy link
Author

@Berserker66 Berserker66 Sep 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But location.item comes from placements and placements is filled within this function from locations only, which are all empty at the beginning of this call, so all contents should never be advancement

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right. I knew that. I was just seeing if you knew


if swapped_items[placed_item.player,
placed_item.name] > 1:
continue

location.item = None
Expand All @@ -175,9 +173,8 @@ def fast_fill(world: MultiWorld, locations: typing.List[Location],
# add the old item to the back of the queue
spot_to_fill = placements.pop(i)

swap_count += 1
swapped_items[placed_item.player,
placed_item.name] = swap_count
placed_item.name] += 1

itempool.append(placed_item)

Expand All @@ -195,14 +192,23 @@ def fast_fill(world: MultiWorld, locations: typing.List[Location],
world.push_item(spot_to_fill, item_to_place, False)
placements.append(spot_to_fill)

if len(unplaced_items) > 0 and len(locations) > 0:
if unplaced_items and locations:
# There are leftover unplaceable items and locations that won't accept them
raise FillError(f'No more spots to place {unplaced_items}, locations {locations} are invalid. '
f'Already placed {len(placements)}: {", ".join(str(place) for place in placements)}')
f'Already placed {len(placements)}: {", ".join(str(place) for place in placements)}')

itempool.extend(unplaced_items)


def fast_fill(world: MultiWorld,
item_pool: typing.List[Item],
fill_locations: typing.List[Location]) -> typing.Tuple[typing.List[Item], typing.List[Location]]:
placing = min(len(item_pool), len(fill_locations))
for item, location in zip(item_pool, fill_locations):
world.push_item(location, item, False)
return item_pool[placing:], fill_locations[placing:]


def distribute_items_restrictive(world: MultiWorld) -> None:
fill_locations = sorted(world.get_unfilled_locations())
world.random.shuffle(fill_locations)
Expand Down Expand Up @@ -243,15 +249,14 @@ def distribute_items_restrictive(world: MultiWorld) -> None:
raise FillError(
f'Not enough locations for progress items. There are {len(progitempool)} more items than locations')


fast_fill(world, excludedlocations, filleritempool)
remaining_fill(world, excludedlocations, filleritempool)
if excludedlocations:
raise FillError(
f"Not enough filler items for excluded locations. There are {len(excludedlocations)} more locations than items")

restitempool = usefulitempool + filleritempool

fast_fill(world, defaultlocations, restitempool)
remaining_fill(world, defaultlocations, restitempool)

unplaced = restitempool
unfilled = defaultlocations
Expand Down