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

Docs: Make an actual LogicMixin spec & explanation #3975

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Changes from 11 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a08fbc6
Docs: Make an actual LogicMixin spec & explanation
NewSoupVi Sep 21, 2024
4b6ad12
Update world api.md
NewSoupVi Sep 21, 2024
f9108f4
Update world api.md
NewSoupVi Sep 21, 2024
98d808b
Update world api.md
NewSoupVi Sep 21, 2024
259d010
Update world api.md
NewSoupVi Sep 21, 2024
584dd8f
Update world api.md
NewSoupVi Sep 21, 2024
910d4b9
Update world api.md
NewSoupVi Sep 21, 2024
f7d6f68
Update world api.md
NewSoupVi Sep 21, 2024
8aed3ef
Update world api.md
NewSoupVi Sep 21, 2024
db577ad
Update world api.md
NewSoupVi Sep 21, 2024
4eeac94
Update world api.md
NewSoupVi Sep 21, 2024
7b097c9
Update docs/world api.md
NewSoupVi Sep 21, 2024
555b721
Update docs/world api.md
NewSoupVi Sep 21, 2024
88de34d
Update world api.md
NewSoupVi Sep 22, 2024
54ab181
Code corrections / actually follow own spec
NewSoupVi Sep 22, 2024
c21c10a
Update docs/world api.md
NewSoupVi Sep 25, 2024
e694804
Update world api.md
NewSoupVi Sep 26, 2024
c0cd03f
Update world api.md
NewSoupVi Sep 26, 2024
819710f
Reorganize / Rewrite the parts about optimisations a bit
NewSoupVi Oct 28, 2024
fe366e2
Update world api.md
NewSoupVi Oct 28, 2024
1460910
Write a big motivation paragraph
NewSoupVi Oct 28, 2024
798556f
Update world api.md
NewSoupVi Oct 28, 2024
245a668
Update world api.md
NewSoupVi Oct 28, 2024
f78f906
line break issues
NewSoupVi Oct 28, 2024
da72d40
Update docs/world api.md
NewSoupVi Oct 28, 2024
d0a555a
Update docs/world api.md
NewSoupVi Oct 28, 2024
febcd84
Update docs/world api.md
NewSoupVi Oct 28, 2024
8d0f1be
Update world api.md
NewSoupVi Oct 28, 2024
80e89db
Update docs/world api.md
NewSoupVi Oct 28, 2024
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
70 changes: 67 additions & 3 deletions docs/world api.md
Original file line number Diff line number Diff line change
Expand Up @@ -696,9 +696,73 @@ When importing a file that defines a class that inherits from `worlds.AutoWorld.
is automatically extended by the mixin's members. These members should be prefixed with the name of the implementing
world since the namespace is shared with all other logic mixins.

Some uses could be to add additional variables to the state object, or to have a custom state machine that gets modified
with the state.
Please do this with caution and only when necessary.
In general, a LogicMixin class should have at least one mutable variable that is tracking some custom state per player,
as well as an `init_mixin` and a `copy_mixin` function so that this variable gets initialized and copied correctly when
NewSoupVi marked this conversation as resolved.
Show resolved Hide resolved
`CollectionState()` and `CollectionState.copy()` are called respectively.

```python
from BaseClasses import CollectionState, MultiWorld
from worlds.AutoWorld import LogicMixin

class MyGameState(LogicMixin):
defeatable_enemies: Dict[int, Set[str]] # per player

def init_mixin(self, multiworld: MultiWorld) -> None:
self.defeatable_enemies = {}

def copy_mixin(self, new_state: CollectionState) -> CollectionState:
# Be careful to make a "deep enough" copy here!
new_state.defeatable_enemies = {player: enemies.copy() for player, enemies in self.defeatable_enemies.items()}
```

After doing this, you can now access `state.defeatable_enemies[player]` from your access rules.

Usually, doing this coincides with an override of `World.collect` and `World.remove`, where any time a relevant item is
collected or removed, the custom state variable gets recalculated.
NewSoupVi marked this conversation as resolved.
Show resolved Hide resolved

```python
# __init__.py

def collect(self, state: CollectionState, item: Item) -> bool:
change = super().collect(state, item)
if change and item in COMBAT_ITEMS:
state.defeatable_enemies |= get_newly_unlocked_enemies(state)
return change

def remove(self, state: CollectionState, item: Item) -> bool:
change = super().remove(state, item)
if change and item in COMBAT_ITEMS:
state.defeatable_enemies -= get_newly_locked_enemies(state)
return change
```

Using LogicMixin can slow down your code by a lot if you don't use it intelligently.
For example, you can make use of the fact that `collect` should only unlock things, and `remove` should only
lock things.
In our example, we have two different functions: `get_newly_unlocked_enemies` and `get_newly_locked_enemies`.
`get_newly_unlocked_enemies` should only consider enemies that are *not already in the set*
and check whether they were **unlocked**.
`get_newly_locked_enemies` should only consider enemies that are *already in the set*
and check whether they **became locked**.

There are a multitude of other ways you can optimise LogicMixin. Some games use a `mygame_state_is_stale`
variable that they simply set to True in `collect`, `remove`, and `init_mixin`.
The calls to the actual recalculating functions are then moved to the start of the relevant access rules like this:

```python
def can_defeat_enemy(state: CollectionState, player: int, enemy: str) -> bool:
if state.custom_state_is_stale[player]:
state.defeatable_enemies = recalculate_defeatable_enemies(state)
state.custom_state_is_stale[player] = False

return enemy in state.defeatable_enemies
```

This can help quite significantly because it is possible for 0 local access rules to be called between two calls to
NewSoupVi marked this conversation as resolved.
Show resolved Hide resolved
`collect`, so recalculating on every `collect` is very slow.

Only use LogicMixin if necessary. There are often other ways to achieve what it does, like making clever use of
`state.prog_items`, or using event items, pseudo-regions etc.
NewSoupVi marked this conversation as resolved.
Show resolved Hide resolved

#### pre_fill

Expand Down