forked from SilverStudioGames/WTS
Gouvernathor
36582d0f9c
some uses of str.format remain, but converting them would be more trouble than it's worth (cherry picked from commit f17cffa3ec5329988a58c76f8fa4f3fe4846a6fc)
406 lines
14 KiB
Plaintext
406 lines
14 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(f"Cache limit reached, purging cache... ({cache_size}/{cache_limit})\n{renpy.get_filename_line()}")
|
|
|
|
cache.clear()
|
|
|
|
if renpy.game.interface is not None:
|
|
if config.developer:
|
|
print(f"Statements limit reached, cleaning textures... ({self.limit})\n{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 callbacks for label calls and jumps, we use them for the event class
|
|
# in order to figure out if the event was completed or if it's just a call,
|
|
# and also for debugging.
|
|
# 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))
|
|
|
|
# class _JumpException(Exception):
|
|
|
|
# def __init__(self, label):
|
|
# for i in renpy.config._label_callbacks:
|
|
# i(label)
|
|
|
|
# class _JumpOutException(Exception):
|
|
|
|
# def __init__(self, label):
|
|
# for i in renpy.config._label_callbacks:
|
|
# i(label)
|
|
|
|
# renpy.game.CONTROL_EXCEPTIONS = tuple(list(renpy.game.CONTROL_EXCEPTIONS) + [_CallException, _JumpException, _JumpOutException])
|
|
# renpy.game.CallException = _CallException
|
|
# renpy.game.JumpException = _JumpException
|
|
# renpy.game.JumpOutException = _JumpOutException
|
|
|
|
# class _Call(renpy.ast.Call):
|
|
# def execute(self):
|
|
# statement_name("call")
|
|
|
|
# label = self.label
|
|
# if self.expression:
|
|
# label = renpy.python.py_eval(label)
|
|
|
|
# rv = renpy.game.context().call(label, return_site=self.next.name)
|
|
# next_node(rv)
|
|
# renpy.game.context().abnormal = True
|
|
|
|
# if self.arguments:
|
|
# args, kwargs = self.arguments.evaluate()
|
|
# renpy.store._args = args
|
|
# renpy.store._kwargs = kwargs
|
|
# else:
|
|
# renpy.store._args = None
|
|
# renpy.store._kwargs = None
|
|
|
|
# setattr(store, "_last_label_call", label)
|
|
|
|
# if config.developer:
|
|
# last_label = getattr(store, "_last_label_jump", None)
|
|
# caller_id = renpy.get_filename_line()
|
|
# print(f"Called '{stdcol.PURPLE}{label}{stdcol.END}' from '{stdcol.PURPLE}{last_label}{stdcol.END}' with ARGS:'{stdcol.YELLOW}{args}{stdcol.END}' KWARGS:'{stdcol.YELLOW}{kwargs}{stdcol.END}' caller '{stdcol.BLUE}{caller_id}{stdcol.END}'...")
|
|
|
|
# class _Jump(renpy.ast.Jump):
|
|
# def execute(self):
|
|
|
|
# statement_name("jump")
|
|
|
|
# target = self.target
|
|
# if self.expression:
|
|
# target = renpy.python.py_eval(target)
|
|
|
|
# rv = renpy.game.script.lookup(target)
|
|
# renpy.game.context().abnormal = True
|
|
|
|
# next_node(rv)
|
|
|
|
# setattr(store, "_last_label_jump", target)
|
|
|
|
# if config.developer:
|
|
# last_label = getattr(store, "_last_label_jump", None)
|
|
# caller_id = renpy.get_filename_line()
|
|
# print(f"Jumped '{stdcol.PURPLE}{target}{stdcol.END}' from '{stdcol.PURPLE}{last_label}{stdcol.END}' with ARGS:'{stdcol.YELLOW}{args}{stdcol.END}' KWARGS:'{stdcol.YELLOW}{kwargs}{stdcol.END}' caller '{stdcol.BLUE}{caller_id}{stdcol.END}'...")
|
|
|
|
# renpy.ast.Call = _Call
|
|
# renpy.ast.Jump = _Jump
|
|
|
|
default _last_label = None
|
|
|
|
python early:
|
|
def catch_label_call(label, abnormal):
|
|
if config.developer:
|
|
# last_label = renpy.game.context().come_from_label <- Doesn't work as expected, other methods are just as unreliable.
|
|
# from '{stdcol.PURPLE}{last_label}{stdcol.END}'
|
|
caller_id = renpy.get_filename_line()
|
|
print(f"Reached '{stdcol.PURPLE}{label}{stdcol.END}' caller '{stdcol.BLUE}{caller_id}{stdcol.END}'...")
|
|
|
|
setattr(store, "_last_label", label)
|
|
|
|
renpy.config.label_callbacks.append(catch_label_call)
|
|
|
|
# def catch_label_call(label, args, kwargs):
|
|
# # Used in event queue system to differentiate jumps from calls.
|
|
# if config.developer:
|
|
# last_label = getattr(store, "_last_label_jump", None)
|
|
# caller_id = renpy.get_filename_line()
|
|
# print(f"Called '{stdcol.PURPLE}{label}{stdcol.END}' from '{stdcol.PURPLE}{last_label}{stdcol.END}' with ARGS:'{stdcol.YELLOW}{args}{stdcol.END}' KWARGS:'{stdcol.YELLOW}{kwargs}{stdcol.END}' caller '{stdcol.BLUE}{caller_id}{stdcol.END}'...")
|
|
|
|
# setattr(store, "_last_label_call", label)
|
|
|
|
# def catch_label_jump(label):
|
|
# # if getattr(store, "_last_label_call", None) == label:
|
|
# # return
|
|
|
|
# if config.developer:
|
|
# last_label = getattr(store, "_last_label_jump", None)
|
|
# caller_id = renpy.get_filename_line()
|
|
# print(f"Jumped '{stdcol.PURPLE}{label}{stdcol.END}' from '{stdcol.PURPLE}{last_label}{stdcol.END}' caller '{stdcol.BLUE}{caller_id}{stdcol.END}'...")
|
|
|
|
# setattr(store, "_last_label_jump", label)
|
|
|
|
# renpy.config.call_callbacks = [catch_label_call] # CallException callback
|
|
# renpy.config._label_callbacks = [catch_label_jump] # JumpException and JumpOutException callbacks; Please note this is not the same as `config.label_callbacks`
|
|
|
|
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
|
|
|
|
# Adds support for nested stores for replay scope
|
|
def _call_replay(label, scope={}):
|
|
renpy.display.focus.clear_focus()
|
|
|
|
renpy.game.log.complete()
|
|
|
|
old_log = renpy.game.log
|
|
renpy.game.log = renpy.python.RollbackLog()
|
|
|
|
sb = renpy.python.StoreBackup()
|
|
renpy.python.clean_stores()
|
|
|
|
context = renpy.execution.Context(True)
|
|
renpy.game.contexts.append(context)
|
|
|
|
if renpy.display.interface is not None:
|
|
renpy.display.interface.enter_context()
|
|
|
|
# This has to be here, to ensure the scope stuff works.
|
|
renpy.exports.execute_default_statement()
|
|
|
|
for k, v in renpy.config.replay_scope.items():
|
|
stores = k.split(".")
|
|
current_obj = renpy.store
|
|
|
|
for store in stores[:-1]:
|
|
current_obj = getattr(current_obj, store)
|
|
|
|
setattr(current_obj, stores[-1], v)
|
|
|
|
for k, v in scope.items():
|
|
stores = k.split(".")
|
|
current_obj = renpy.store
|
|
|
|
for store in stores[:-1]:
|
|
current_obj = getattr(current_obj, store)
|
|
|
|
setattr(current_obj, stores[-1], v)
|
|
|
|
renpy.store._in_replay = label
|
|
|
|
try:
|
|
|
|
context.goto_label("_start_replay")
|
|
renpy.execution.run_context(False)
|
|
|
|
except renpy.game.EndReplay:
|
|
pass
|
|
|
|
finally:
|
|
|
|
context.pop_all_dynamic()
|
|
|
|
renpy.game.contexts.pop()
|
|
renpy.game.log = old_log
|
|
sb.restore()
|
|
|
|
if renpy.game.interface and renpy.game.interface.restart_interaction and renpy.game.contexts:
|
|
renpy.game.contexts[-1].scene_lists.focused = None
|
|
|
|
renpy.config.skipping = None
|
|
|
|
if renpy.config.after_replay_callback:
|
|
renpy.config.after_replay_callback()
|
|
|
|
renpy.call_replay = _call_replay
|