SM: Speed up deepcopy in copy_mixin (#4228)

This commit is contained in:
Mysteryem
2025-07-30 03:10:36 +01:00
committed by GitHub
parent 2a0ed7faa2
commit 1d8a0b2940
2 changed files with 52 additions and 0 deletions

View File

@@ -66,6 +66,14 @@ class SMBool:
def __copy__(self):
return SMBool(self.bool, self.difficulty, self._knows, self._items)
def __deepcopy__(self, memodict):
# `bool` and `difficulty` are a `bool` and `int`, so do not need to be copied.
# The `_knows` list is never mutated, so does not need to be copied.
# The `_items` list is a `list[str | list[str]]` (copied to a flat `list[str]` when accessed through the `items`
# property) that is mutated by code in helpers.py, so needs to be copied. Because there could be lists within
# the list, it is copied using the `flatten()` helper function.
return SMBool(self.bool, self.difficulty, self._knows, flatten(self._items))
def json(self):
# as we have slots instead of dict
return {'bool': self.bool, 'difficulty': self.difficulty, 'knows': self.knows, 'items': self.items}

View File

@@ -8,6 +8,7 @@ from ..utils.doorsmanager import DoorsManager
from ..utils.objectives import Objectives
from ..utils.parameters import Knows, isKnows
import logging
from copy import deepcopy
import sys
class SMBoolManager(object):
@@ -34,6 +35,46 @@ class SMBoolManager(object):
self.createFacadeFunctions()
self.createKnowsFunctions(player)
self.resetItems()
self.itemsPositions = {}
def __deepcopy__(self, memodict):
# Use __new__ to avoid calling __init__ like copy.deepcopy without __deepcopy__ implemented.
new = object.__new__(type(self))
# Copy everything over in the same order as __init__, ensuring that mutable attributes are deeply copied.
# SMBool instances contain mutable lists, so must be deep-copied.
new._items = {i: deepcopy(v, memodict) for i, v in self._items.items()}
# `_counts` is a dict[str, int], so the dict can be copied because its keys and values are immutable.
new._counts = self._counts.copy()
# `player` is an int.
new.player = self.player
# `maxDiff` is an int.
new.maxDiff = self.maxDiff
# `onlyBossLeft` is a bool.
new.onlyBossLeft = self.onlyBossLeft
# The HelpersGraph keeps reference to the instance, so a new HelpersGraph is required.
new.helpers = Logic.HelpersGraph(new)
# DoorsManager is stateless, so the same instance can be used.
new.doorsManager = self.doorsManager
# Objectives are cached by self.player, so will be the same instance for the copy.
new.objectives = self.objectives
# Copy the facade functions from new.helpers into new.__dict__.
new.createFacadeFunctions()
# Copying the existing 'knows' functions from `self` to `new` is faster than re-creating all the lambdas with
# `new.createKnowsFunctions(player)`.
for key in Knows.__dict__.keys():
if isKnows(key):
attribute_name = "knows"+key
knows_func = getattr(self, attribute_name)
setattr(new, attribute_name, knows_func)
# There is no need to call `new.resetItems()` because `_items` and `_counts` have been copied over.
# new.resetItems()
# itemsPositions is a `dict[str, tuple[int, int]]`, so the dict can be copied because the keys and values are
# immutable.
new.itemsPositions = self.itemsPositions.copy()
return new
def computeItemsPositions(self):
# compute index in cache key for each items
@@ -245,6 +286,9 @@ class SMBoolManagerPlando(SMBoolManager):
def __init__(self):
super(SMBoolManagerPlando, self).__init__()
def __deepcopy__(self, memodict):
return super().__deepcopy__(memodict)
def addItem(self, item):
# a new item is available
already = self.haveItem(item)