WTS/game/scripts/utility/engine.rpy
LoafyLemon 13a7017773 Convert Hermione's Personal Favours into the new Event system to address code logic bugs.
* Added internal counter for queues
* Simplified global vars for event completion checks.
* Temporarily disabled Hermione's statistics menu
* Replaced counter/points checks with status flags.
* Disabled debug profiling for images
2023-06-24 23:27:13 +01:00

242 lines
8.4 KiB
Plaintext

init python:
if renpy.android:
settings.default("crashdefendersetting", 0)
# Attempts at fixing the texture leak plaguing
# android devices, mainly running on Android11 & Android 12.
class Android11TextureLeakFix(NoRollback):
def __init__(self, limit=100):
self.statements = 0
self.set_mode(settings.get("crashdefendersetting"))
def set_mode(self, mode):
if mode == 3:
self.limit = 15
elif mode == 2:
self.limit = 25
elif mode == 1:
self.limit = 55
else:
self.limit = 0
def __call__(self, name):
if renpy.is_init_phase() or self.limit == 0:
return
self.statements += 1
if self.statements > self.limit:
self.statements = 0
# Big thanks to Andykl (https://github.com/Andykl)
# for finding the issue and inventing this workaround.
# https://github.com/renpy/renpy/issues/3643
cache = renpy.display.im.cache
cache_size = cache.get_total_size()
cache_limit = cache.cache_limit * 0.95
if cache_size >= cache_limit:
if config.developer:
print("Cache limit reached, purging cache... ({}/{})\n{}".format(cache_size, cache_limit, renpy.get_filename_line()))
cache.clear()
if renpy.game.interface is not None:
if config.developer:
print("Statements limit reached, cleaning textures... ({})\n{}".format(self.limit, renpy.get_filename_line()))
renpy.game.interface.full_redraw = True
renpy.game.interface.restart_interaction = True
if renpy.display.draw is not None:
renpy.display.draw.kill_textures()
renpy.display.render.free_memory()
crashdefender = Android11TextureLeakFix()
config.statement_callbacks.append(crashdefender)
python early hide:
import functools
if renpy.windows:
# On windows, Renpy does not support backslashes in some of its functions,
# but because the code needs to be platform-independent,
# we require to monkey patch those functions in order
# to remain compatible with all platforms without losing functionality.
@renpy.pure
def _loadable(filename):
filename = filename.replace("\\", "/")
return renpy.loader.loadable(filename)
renpy.loadable = _loadable
# renpy.list_files does not use cached results, let's fix that.
@functools.cache
def _list_files(common=False):
rv = [fn for dir, fn in renpy.loader.listdirfiles(common) if not fn.startswith("saves/")]
rv.sort()
return rv
renpy.list_files = _list_files
# Default focus behaviour restarts the interaction whenever
# any element that contains a tooltip is being hovered or unhovered.
# Restarting interactions in a short timespan causes massive lag spikes,
# in order to fix it, we'll refresh just the tooltip screen and skip the rest.
def set_focused(widget, arg, screen):
global _tooltip
renpy.display.focus.argument = arg
renpy.display.focus.screen_of_focused = screen
if screen is not None:
renpy.display.focus.screen_of_focused_names = { screen.screen_name[0], screen.tag }
else:
renpy.display.focus.screen_of_focused_names = set()
renpy.game.context().scene_lists.focused = widget
renpy.display.tts.displayable(widget)
# Figure out the tooltip.
_tooltip = widget._get_tooltip() if widget else None
# setattr(renpy.store, "widget", widget) # DEBUG
# if renpy.display.focus.tooltip != new_tooltip:
# renpy.display.focus.tooltip = new_tooltip
# renpy.display.focus.capture_focus("tooltip")
# renpy.exports.restart_interaction()
# if renpy.display.focus.tooltip is not None:
# renpy.display.focus.last_tooltip = renpy.display.focus.tooltip
# renpy.display.focus.screen_of_last_focused_names = renpy.display.focus.screen_of_focused_names
renpy.display.focus.set_focused = set_focused
# Add a callbacks for label calls, we use them for the event class
# in order to figure out if the event was completed or if it's just a call.
#
# Might not be required
#
# class _CallException(Exception):
# from_current = False
# def __init__(self, label, args, kwargs, from_current=False):
# Exception.__init__(self)
# self.label = label
# self.args = args
# self.kwargs = kwargs
# self.from_current = from_current
# for i in renpy.config.call_callbacks:
# i(label, args, kwargs)
# def __reduce__(self):
# return (_CallException, (self.label, self.args, self.kwargs, self.from_current))
# renpy.game.CONTROL_EXCEPTIONS = tuple(list(renpy.game.CONTROL_EXCEPTIONS) + [_CallException])
# renpy.game.CallException = _CallException
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)
# The original does not support nested actions.
@renpy.pure
def If(expression, true=None, false=None):
if isinstance(expression, Action):
expression = expression()
return true if expression else false
# python early:
# renpy.config.call_callbacks = [] # CallException callback