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): def __copy__(self):
return SMBool(self.bool, self.difficulty, self._knows, self._items) 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): def json(self):
# as we have slots instead of dict # as we have slots instead of dict
return {'bool': self.bool, 'difficulty': self.difficulty, 'knows': self.knows, 'items': self.items} 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.objectives import Objectives
from ..utils.parameters import Knows, isKnows from ..utils.parameters import Knows, isKnows
import logging import logging
from copy import deepcopy
import sys import sys
class SMBoolManager(object): class SMBoolManager(object):
@@ -34,6 +35,46 @@ class SMBoolManager(object):
self.createFacadeFunctions() self.createFacadeFunctions()
self.createKnowsFunctions(player) self.createKnowsFunctions(player)
self.resetItems() 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): def computeItemsPositions(self):
# compute index in cache key for each items # compute index in cache key for each items
@@ -245,6 +286,9 @@ class SMBoolManagerPlando(SMBoolManager):
def __init__(self): def __init__(self):
super(SMBoolManagerPlando, self).__init__() super(SMBoolManagerPlando, self).__init__()
def __deepcopy__(self, memodict):
return super().__deepcopy__(memodict)
def addItem(self, item): def addItem(self, item):
# a new item is available # a new item is available
already = self.haveItem(item) already = self.haveItem(item)