diff --git a/game/scripts/doll/body.rpy b/game/scripts/doll/body.rpy index 74c41883..0a47b670 100644 --- a/game/scripts/doll/body.rpy +++ b/game/scripts/doll/body.rpy @@ -8,6 +8,8 @@ init python: "zorder": None, } + __slots__ = ("char", "hue", "zorder", "_hash") + def __init__(self, obj): self.char = obj self.hue = HueMatrix(0) diff --git a/game/scripts/doll/clothes.rpy b/game/scripts/doll/clothes.rpy index ae9e6cb6..bf25a849 100644 --- a/game/scripts/doll/clothes.rpy +++ b/game/scripts/doll/clothes.rpy @@ -1,5 +1,5 @@ init python: - class DollCloth(DollMethods, SlottedNoRollback): + class DollCloth(DollMethods): layer_types = { "mask": "-1", "skin": 10, @@ -15,6 +15,8 @@ init python: "zorder": None, } + __slots__ = ("name", "categories", "type", "id", "color", "unlocked", "level", "blacklist", "modpath", "parent", "char", "color_default", "zorder", "seen", "_hash") + def __init__(self, name, categories, type, id, color, zorder=None, unlocked=False, level=0, blacklist=[], modpath=None, parent=None): self.name = name self.categories = categories diff --git a/game/scripts/doll/common.rpy b/game/scripts/doll/common.rpy index 256dba11..23e8e255 100644 --- a/game/scripts/doll/common.rpy +++ b/game/scripts/doll/common.rpy @@ -48,8 +48,9 @@ init -1 python: def visit(self): return [] - class DollMethods(object): + class DollMethods(SlottedObject): """Container class for commonly used methods and attributes""" + _image = Null() _image_cached = False blacklist_toggles = ("hair", "glasses", "pubes", "piercing", "makeup", "tattoo", "earrings") diff --git a/game/scripts/doll/cum.rpy b/game/scripts/doll/cum.rpy index 2e44e435..aa987de6 100644 --- a/game/scripts/doll/cum.rpy +++ b/game/scripts/doll/cum.rpy @@ -12,6 +12,8 @@ init python: "zorder": None, } + __slots__ = ("char", "_cum", "_hash") + def __init__(self, obj): self.char = obj self._cum = {k: None for k in (self.char.clothing_layers | self.char.face_layers).keys()} diff --git a/game/scripts/doll/face.rpy b/game/scripts/doll/face.rpy index feb1d619..7b585121 100644 --- a/game/scripts/doll/face.rpy +++ b/game/scripts/doll/face.rpy @@ -10,6 +10,8 @@ init python: "zorder": None, } + __slots__ = ("char", "_cum", "_hash") + def __init__(self, obj): self.char = obj self._face = {k: None for k in self.char.face_layers.keys()} diff --git a/game/scripts/doll/main.rpy b/game/scripts/doll/main.rpy index 114245b3..c69c82ea 100644 --- a/game/scripts/doll/main.rpy +++ b/game/scripts/doll/main.rpy @@ -44,6 +44,9 @@ init python: "headgear": 281 } + __slots__ = ("wardrobe", "wardrobe_list", "blacklist", "outfits", "name", "states", "face", "body", "cum", \ + "pose", "emote", "_hash", "zorder", "layer", "animation", "tag", "pos", "zoom", "xzoom", "align", "modpath") + def __init__(self, name, modpath=None): self.wardrobe = {} self.wardrobe_list = [] @@ -84,7 +87,7 @@ init python: return hash(salt) def show(self): - if renpy.get_screen(("wardrobe", "animatedCG", "studio")): + if renpy.get_screen(("wardrobe", "animatedCG", "studio")) or renpy.showing("cg"): return base_transform = doll_transform(self.pos, self.zoom, self.xzoom) diff --git a/game/scripts/doll/outfits.rpy b/game/scripts/doll/outfits.rpy index a3f1aad8..28f6d8ba 100644 --- a/game/scripts/doll/outfits.rpy +++ b/game/scripts/doll/outfits.rpy @@ -1,7 +1,9 @@ init python: - class DollOutfit(DollMethods, SlottedNoRollback): + class DollOutfit(DollMethods): default_schedule = {"day": False, "night": False, "cloudy": False, "rainy": False, "snowy": False} + __slots__ = ("group", "name", "desc", "price", "char", "unlocked", "schedule", "temp", "hidden", "addons", "_hash") + def __init__(self, group, unlocked=False, name="", desc="", price=0, temp=False, schedule={}, hidden=False, addons=[]): self.group = [x.clone() if not x.parent else x for x in group] self.name = name diff --git a/game/scripts/utility/engine.rpy b/game/scripts/utility/engine.rpy index afc2f821..d284ad24 100644 --- a/game/scripts/utility/engine.rpy +++ b/game/scripts/utility/engine.rpy @@ -123,3 +123,82 @@ python early hide: renpy.display.focus.set_focused = set_focused +init -100 python: + # Due to the sheer number of Doll-type objects we create and keep per each character, + # we have to use each and every optimization technique we can get our hands on. + # The below code implements revertable __slots__ support, which reduces memory, + # without losing any functionality relevant to the objects that are using it. + # + # The code is taken from a PR proposed by Andykl: + # https://github.com/renpy/renpy/pull/3282 + + class _RevertableObject(_object): + # Reimplementation of the Ren'py class, + # but with slots support. + def __new__(cls, *args, **kwargs): + self = super(_RevertableObject, cls).__new__(cls) + + log = renpy.game.log + if log is not None: + log.mutated[id(self)] = None + + return self + + def __init__(self, *args, **kwargs): + if (args or kwargs) and renpy.config.developer: + raise TypeError("object() takes no parameters.") + + __setattr__ = renpy.revertable.mutator(object.__setattr__) # type: ignore + __delattr__ = renpy.revertable.mutator(object.__delattr__) # type: ignore + + def _clean(self): + return None + + def _compress(self, clean): + return clean + + def _rollback(self, compressed): + pass + + class SlottedObject(_RevertableObject): + __slots__ = () + + def __init_subclass__(cls): + if renpy.config.developer: + if hasattr(cls, "__getstate__"): + raise TypeError("slotted_object subclasses can't have " + "__getstate__ method. Use __reduce__ instead.") + + for slot in cls.__dict__.get("__slots__", ()): + if slot.startswith("__") and not slot.endswith("__"): + raise ValueError("slotted_object __slots__ can not be mangled. " + "If you need it, mangle it by yourself.") + + def _clean(self): + rv = object.__reduce_ex__(self, 2)[2] + # We need to make a copy of __dict__ to avoid its futher mutations. + + # No attributes are set + if rv is None: + rv = { } + + # Only __dict__ have attributes + elif isinstance(rv, dict): + rv = rv.copy() + + # Only __slots__ have attributes + elif rv[0] is None: + rv = rv[1] + + # Otherwise it is (__dict__, __slots__), so merge it together + else: + rv = dict(rv[0], **rv[1]) + + return rv + + def _rollback(self, compressed): + if hasattr(self, "__dict__"): + self.__dict__.clear() + + for k, v in compressed.items(): + setattr(self, k, v)