From fc794f969ad4985f0f2ad59b79feedb073a9673d Mon Sep 17 00:00:00 2001 From: LoafyLemon Date: Fri, 14 Jul 2023 23:52:27 +0100 Subject: [PATCH] Android Bug fixes * Added a UI lock mechanism to avoid render stalls * Fixed initialization issue due to python init offset for android devices * Fixed a hang caused by joining threads on android devices * Fixed a race condition when forcefully stopping threads --- game/scripts/doll/clothes.rpy | 10 +++----- game/scripts/doll/main.rpy | 39 ++++++++++++++++-------------- game/scripts/doll/outfits.rpy | 10 +++----- game/scripts/doll/threading.rpy | 24 +++++++++++------- game/scripts/options.rpy | 2 +- game/scripts/utility/settings.rpy | 2 +- game/scripts/wardrobe/wardrobe.rpy | 24 +++++++++++++++--- 7 files changed, 66 insertions(+), 45 deletions(-) diff --git a/game/scripts/doll/clothes.rpy b/game/scripts/doll/clothes.rpy index 2c3cf17f..a2485cbb 100644 --- a/game/scripts/doll/clothes.rpy +++ b/game/scripts/doll/clothes.rpy @@ -281,13 +281,11 @@ init python: thread = DollThread(target=_func, args=(self, self._hash)) thread.start() - if settings.get("multithreading"): - @property - def button(self): + @property + def button(self): + if settings.get("multithreading"): return self._button.get_with_default(self._loading) - else: - @property - def button(self): + else: return self._build_button(self._hash) def clear_button_cache(self): diff --git a/game/scripts/doll/main.rpy b/game/scripts/doll/main.rpy index 9e7d8c60..36d55be5 100644 --- a/game/scripts/doll/main.rpy +++ b/game/scripts/doll/main.rpy @@ -141,35 +141,38 @@ init python: return Fixed(*sprites, self.emote, fit_first=True) - if settings.get("multithreading"): - def build_image(self): + def build_image(self): - def _func(self, hash): - result = self._build_image(hash) - self._sprite.put(result) + def _func(self, hash): + result = self._build_image(hash) + self._sprite.put(result) - thread = DollThread(target=_func, args=(self, self._hash)) - thread.start() - else: - def build_image(self): - self._sprite.put(self._build_image(self._hash)) - renpy.restart_interaction() + thread = DollThread(target=_func, args=(self, self._hash)) + thread.start() def _image(self, st, at): return self._sprite.get_with_default(Null()), None @property def image(self): + if settings.get("multithreading"): - if not renpy.is_skipping() and self.is_stale(): - self.build_image() + if not renpy.is_skipping() and self.is_stale(): + self.build_image() - if renpy.showing(get_character_tag(self.name), layer=self.layer): - self.show() - elif renpy.in_rollback(): - self.build_image() + if renpy.showing(get_character_tag(self.name), layer=self.layer): + self.show() + # elif renpy.in_rollback(): + # self.build_image() - return DynamicDisplayable(self._image) + return DynamicDisplayable(self._image) + else: + if not renpy.is_skipping() and self.is_stale(): + + if renpy.showing(get_character_tag(self.name), layer=self.layer): + self.show() + + return self._build_image(self._hash) def equip(self, obj, remove_old=True): """Takes DollCloth or DollOutfit object to equip.""" diff --git a/game/scripts/doll/outfits.rpy b/game/scripts/doll/outfits.rpy index 16604274..5b3a1bb4 100644 --- a/game/scripts/doll/outfits.rpy +++ b/game/scripts/doll/outfits.rpy @@ -180,13 +180,11 @@ init python: thread = DollThread(target=_func, args=(self, self._hash, subcat)) thread.start() - if settings.get("multithreading"): - @property - def button(self): + @property + def button(self): + if settings.get("multithreading"): return self._button.get_with_default(self._loading) - else: - @property - def button(self): + else: global current_subcategory return self._build_button(self._hash, current_subcategory) diff --git a/game/scripts/doll/threading.rpy b/game/scripts/doll/threading.rpy index 638bb88b..2fc9d995 100644 --- a/game/scripts/doll/threading.rpy +++ b/game/scripts/doll/threading.rpy @@ -40,18 +40,24 @@ init python early: def stop(self): self._stop.set() - DollThread._instances.remove(self) - DollThread._count -= 1 + + if self in DollThread._instances: + DollThread._instances.remove(self) + DollThread._count -= 1 + + if DollThread._count <= 0: + renpy.restart_interaction() @classmethod def stop_all(cls): - with cls.__lock: - for thread in cls._instances: - thread.stop() - + for thread in cls._instances: # Allow threads to exit gracefully before forceful termination - for thread in cls._instances: - thread.join() + thread.stop() + + # if not renpy.android: + # # Then forcefully terminate the remainders (except on android because that stalls the renderer) + # for thread in cls._instances: + # thread.join() class DefaultQueue(queue.Queue, NoRollback): def __init__(self): @@ -89,4 +95,4 @@ init python early: return (DefaultQueue, ()) def __reduce_ex__(self, protocol): - return DefaultQueue, (), self.__getstate__(), None, iter([]) \ No newline at end of file + return DefaultQueue, (), self.__getstate__(), None, iter([]) diff --git a/game/scripts/options.rpy b/game/scripts/options.rpy index 7fa06fa3..d934fc34 100644 --- a/game/scripts/options.rpy +++ b/game/scripts/options.rpy @@ -10,7 +10,7 @@ default preferences.renderer = "auto" default preferences.gl_powersave = False default preferences.audio_when_minimized = False -init -5 python: +init python: settings.default("theme", "auto") settings.default("text_color_day", "#402313ff") settings.default("text_color_night", "#341c0fff") diff --git a/game/scripts/utility/settings.rpy b/game/scripts/utility/settings.rpy index 7b9e801c..3e022954 100644 --- a/game/scripts/utility/settings.rpy +++ b/game/scripts/utility/settings.rpy @@ -6,7 +6,7 @@ init offset = -10 default persistent.custom_settings = {} default persistent.custom_settings_default = {} -init -10 python in settings: +init python in settings: from store import persistent, Action, DictEquality not_set = object() diff --git a/game/scripts/wardrobe/wardrobe.rpy b/game/scripts/wardrobe/wardrobe.rpy index 7a565aa1..3499309d 100644 --- a/game/scripts/wardrobe/wardrobe.rpy +++ b/game/scripts/wardrobe/wardrobe.rpy @@ -35,10 +35,19 @@ init python: _lock = False def rebuild_wardrobe_icons(items, subcat): + if not settings.get("multithreading"): + return + + if subcat == "import": + return + for i in items.get(subcat, []): i.build_button(subcat) def lock_wardrobe_icon(icon): + if not settings.get("multithreading"): + return icon + lock = bool(DollThread._count) return gray_tint(icon) if lock else icon @@ -629,9 +638,10 @@ screen wardrobe_menu(xx, yy): button: focus_mask None xysize (72, 72) - background icon + background lock_wardrobe_icon(icon) activate_sound "sounds/scroll.ogg" tooltip category + sensitive (not bool(DollThread._count)) action Return(["category", category]) if current_category == category: xoffset icon_xoffset @@ -646,8 +656,9 @@ screen wardrobe_menu(xx, yy): button: focus_mask None xysize (72, 72) - background Fixed(icon_bg, Transform("interface/wardrobe/icons/categories/outfits.webp", zoom=0.45, anchor=(0.5, 0.5), align=(0.5, 0.5)), icon_frame) + background lock_wardrobe_icon(Fixed(icon_bg, Transform("interface/wardrobe/icons/categories/outfits.webp", zoom=0.45, anchor=(0.5, 0.5), align=(0.5, 0.5)), icon_frame)) tooltip "Outfits Manager" + sensitive (not bool(DollThread._count)) action Return(["category", "outfits"]) if current_category == "outfits": yoffset icon_yoffset @@ -758,7 +769,7 @@ screen wardrobe_menuitem(xx, yy): pos (12, 108) for subcategory in category_items.keys(): - $ icon = "interface/wardrobe/icons/{}.webp".format(subcategory) + $ icon = lock_wardrobe_icon("interface/wardrobe/icons/{}.webp".format(subcategory)) button: focus_mask None @@ -767,6 +778,7 @@ screen wardrobe_menuitem(xx, yy): selected_background Transform(icon, size=(72, 72), fit="contain", ) selected (subcategory == current_subcategory) tooltip subcategory + sensitive (not bool(DollThread._count)) action Return(["subcategory", subcategory]) # # Item icons @@ -810,7 +822,7 @@ screen wardrobe_outfit_menuitem(xx, yy): pos (8, 108) for subcategory in category_items.keys(): - $ icon = "interface/wardrobe/icons/{}.webp".format(subcategory) + $ icon = lock_wardrobe_icon("interface/wardrobe/icons/{}.webp".format(subcategory)) $ action = Return(["subcategory", subcategory]) if subcategory == "schedule" and not get_character_scheduling(states.active_girl): @@ -823,6 +835,7 @@ screen wardrobe_outfit_menuitem(xx, yy): background Transform(icon, alpha=0.65, xsize=72, fit="contain") selected_background Transform(icon, xsize=72, fit="contain") selected (subcategory == current_subcategory) + sensitive (not bool(DollThread._count)) tooltip subcategory action action @@ -846,8 +859,10 @@ screen wardrobe_outfit_menuitem(xx, yy): textbutton "Save": focus_mask None xysize icon_size + insensitive_background "#0000001A" idle_background "#00000033" text_align (0.5, 0.5) + sensitive (not bool(DollThread._count)) action Return(["addoutfit", None]) if current_subcategory == "import": @@ -895,6 +910,7 @@ style wardrobe_button is empty: style wardrobe_button_text: color "#fff" + insensitive_color "#808080" size 20 outlines [ (2, "#000", 0, 0) ]