Wardrobe performance improvements and bug fixes
* Implemented new DollThread method with thread-safe locking mechanism and pickling support for thread event queues * Added memoization for wardrobe elements * Added threading for various wardrobe-related methods * Added lazyloading (to avoid render stalls) * Added button generation for DollCloth and DollOutfit instances * Significantly reduced code repetition inside the wardrobe loop * Added new methods for the Doll, and improved others. * Fixed viewport adjustment values resetting on interaction * Fixed character chit-chats performance issues * Updated saves compatibility patch
This commit is contained in:
parent
a2794e3e47
commit
4c98cbe669
File diff suppressed because it is too large
Load Diff
@ -51,6 +51,7 @@ init -1 python:
|
|||||||
class DollMethods(SlottedObject):
|
class DollMethods(SlottedObject):
|
||||||
"""Container class for commonly used methods and attributes"""
|
"""Container class for commonly used methods and attributes"""
|
||||||
|
|
||||||
|
_loading = Text("Loading", align=(0.5, 0.5))
|
||||||
_image = Null()
|
_image = Null()
|
||||||
_image_cached = False
|
_image_cached = False
|
||||||
blacklist_toggles = ("hair", "glasses", "pubes", "piercing", "makeup", "tattoo", "earrings")
|
blacklist_toggles = ("hair", "glasses", "pubes", "piercing", "makeup", "tattoo", "earrings")
|
||||||
|
@ -265,9 +265,9 @@ init python:
|
|||||||
"""Takes argument containing string cloth type. Returns equipped object for cloth type."""
|
"""Takes argument containing string cloth type. Returns equipped object for cloth type."""
|
||||||
return self.states[slot][0]
|
return self.states[slot][0]
|
||||||
|
|
||||||
def get_equipped_item(self, items):
|
def get_equipped_wardrobe_item(self, items, subcat):
|
||||||
"""Returns first equipped item from a list or None."""
|
"""Returns first equipped item from a list or None."""
|
||||||
for i in items:
|
for i in items.get(subcat):
|
||||||
if self.is_equipped_item(i):
|
if self.is_equipped_item(i):
|
||||||
return i
|
return i
|
||||||
return None
|
return None
|
||||||
@ -391,10 +391,17 @@ init python:
|
|||||||
|
|
||||||
def is_equipped_item(self, item):
|
def is_equipped_item(self, item):
|
||||||
"""Takes DollCloth object or list of objects. Returns True if item is equipped, False otherwise."""
|
"""Takes DollCloth object or list of objects. Returns True if item is equipped, False otherwise."""
|
||||||
|
|
||||||
|
if isinstance(item, DollCloth):
|
||||||
if item.is_multislot():
|
if item.is_multislot():
|
||||||
return bool(next((k for k, v in self.states.items() if v[0] == item), False))
|
return bool(next((k for k, v in self.states.items() if v[0] == item), False))
|
||||||
|
|
||||||
return self.get_equipped(item.type) == item
|
return self.get_equipped(item.type) == item
|
||||||
|
elif isinstance(item, DollOutfit):
|
||||||
|
compare_object = self.create_outfit(temp=True)
|
||||||
|
# current_item = next( (x for x in char_active.outfits if _outfit == x), None)
|
||||||
|
|
||||||
|
return item == compare_object
|
||||||
|
|
||||||
def is_worn(self, *args):
|
def is_worn(self, *args):
|
||||||
"""Takes argument(s) containing string cloth type(s). Returns True if worn, False otherwise."""
|
"""Takes argument(s) containing string cloth type(s). Returns True if worn, False otherwise."""
|
||||||
@ -592,3 +599,11 @@ init python:
|
|||||||
self.emote = emote
|
self.emote = emote
|
||||||
|
|
||||||
self.is_stale()
|
self.is_stale()
|
||||||
|
|
||||||
|
def clear_outfit_button_cache(self):
|
||||||
|
|
||||||
|
DollThread.stop_all()
|
||||||
|
|
||||||
|
for i in self.outfits:
|
||||||
|
i._button.last_item = i._loading
|
||||||
|
i.clear_button_cache()
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,18 +1,92 @@
|
|||||||
init python:
|
init python early:
|
||||||
import threading
|
import threading
|
||||||
|
import queue
|
||||||
|
|
||||||
class DollThread(threading.Thread, NoRollback):
|
class DollThread(threading.Thread, NoRollback):
|
||||||
def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None):
|
__lock = threading.RLock()
|
||||||
|
_count = 0
|
||||||
|
_instances = _list()
|
||||||
|
_interval = 0.05 if renpy.android else 0.025
|
||||||
|
|
||||||
|
def __init__(self, interval=None, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None):
|
||||||
threading.Thread.__init__(self, group, target, name, args, kwargs, daemon=daemon)
|
threading.Thread.__init__(self, group, target, name, args, kwargs, daemon=daemon)
|
||||||
|
|
||||||
self._return = None
|
self._return = None
|
||||||
|
self.interval = interval or self._interval
|
||||||
|
self._delay = threading.Timer(self.interval, self._execute)
|
||||||
|
self._stop = threading.Event()
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
DollThread._instances.append(self)
|
||||||
|
DollThread._count += 1
|
||||||
|
super().start()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
with DollThread.__lock:
|
||||||
|
self._delay.start()
|
||||||
|
self._delay.join()
|
||||||
|
self.stop()
|
||||||
|
|
||||||
|
def _execute(self):
|
||||||
|
while not self._stop.is_set():
|
||||||
if self._target is not None:
|
if self._target is not None:
|
||||||
self._return = self._target(*self._args, **self._kwargs)
|
self._return = self._target(*self._args, **self._kwargs)
|
||||||
|
|
||||||
renpy.restart_interaction()
|
renpy.restart_interaction()
|
||||||
|
break
|
||||||
|
|
||||||
def join(self, timeout=1):
|
def join(self, timeout=None):
|
||||||
threading.Thread.join(self, timeout=timeout)
|
super().join(timeout=timeout)
|
||||||
return self._return
|
return self._return
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self._stop.set()
|
||||||
|
DollThread._instances.remove(self)
|
||||||
|
DollThread._count -= 1
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def stop_all(cls):
|
||||||
|
with cls.__lock:
|
||||||
|
for thread in cls._instances:
|
||||||
|
thread.stop()
|
||||||
|
|
||||||
|
# Allow threads to exit gracefully before forceful termination
|
||||||
|
for thread in cls._instances:
|
||||||
|
thread.join()
|
||||||
|
|
||||||
|
class DefaultQueue(queue.Queue, NoRollback):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.last_item = None
|
||||||
|
|
||||||
|
def put(self, item, block=True, timeout=None):
|
||||||
|
super().put(item, block, timeout)
|
||||||
|
self.last_item = item
|
||||||
|
|
||||||
|
def get_with_default(self, default):
|
||||||
|
try:
|
||||||
|
return self.get(block=False)
|
||||||
|
except queue.Empty:
|
||||||
|
return self.last_item or default
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
state = self.__dict__.copy()
|
||||||
|
del state['last_item']
|
||||||
|
del state['mutex']
|
||||||
|
del state['not_empty']
|
||||||
|
del state['not_full']
|
||||||
|
del state['all_tasks_done']
|
||||||
|
return state
|
||||||
|
|
||||||
|
def __setstate__(self, state):
|
||||||
|
self.__dict__.update(state)
|
||||||
|
self.last_item = None
|
||||||
|
self.mutex = threading.Lock()
|
||||||
|
self.not_empty = threading.Condition(self.mutex)
|
||||||
|
self.not_full = threading.Condition(self.mutex)
|
||||||
|
self.all_tasks_done = threading.Condition(self.mutex)
|
||||||
|
|
||||||
|
def __reduce__(self):
|
||||||
|
return (DefaultQueue, ())
|
||||||
|
|
||||||
|
def __reduce_ex__(self, protocol):
|
||||||
|
return DefaultQueue, (), self.__getstate__(), None, iter([])
|
@ -101,11 +101,19 @@ init python:
|
|||||||
|
|
||||||
if current < 1.452:
|
if current < 1.452:
|
||||||
|
|
||||||
# Fix makeup object types inside saved outfits
|
|
||||||
for i in states.dolls:
|
for i in states.dolls:
|
||||||
doll = getattr(store, i)
|
doll = getattr(store, i)
|
||||||
|
|
||||||
|
for j in doll.wardrobe_list:
|
||||||
|
# Add new button handler for clothes
|
||||||
|
j._button = DefaultQueue()
|
||||||
|
|
||||||
for j in doll.outfits:
|
for j in doll.outfits:
|
||||||
|
# Add new button handler for outfits
|
||||||
|
j._button = DefaultQueue()
|
||||||
|
|
||||||
|
# Fix makeup object types inside saved outfits
|
||||||
if j.has_type("makeup"):
|
if j.has_type("makeup"):
|
||||||
|
|
||||||
objects = [x.parent.clone() for x in j.group]
|
objects = [x.parent.clone() for x in j.group]
|
||||||
|
@ -173,9 +173,27 @@ init -1 python:
|
|||||||
return
|
return
|
||||||
|
|
||||||
def list_outfit_files():
|
def list_outfit_files():
|
||||||
path = "{}/outfits/".format(config.gamedir)
|
|
||||||
|
@functools.cache
|
||||||
|
def build_button(rp):
|
||||||
|
style = "wardrobe_button"
|
||||||
|
child = Fixed(Transform(rp, xsize=96, fit="contain", yalign=1.0, yoffset=-6), Frame(gui.format("interface/frames/{}/iconframe.webp"), 6, 6), xysize=(96, 168))
|
||||||
|
action = Return(["import", rp])
|
||||||
|
hover_foreground = "#ffffff80"
|
||||||
|
|
||||||
|
return Button(child=child, action=action, hover_foreground=hover_foreground, style=style)
|
||||||
|
|
||||||
|
path = f"{config.gamedir}/outfits/"
|
||||||
|
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
os.makedirs(path)
|
os.makedirs(path)
|
||||||
|
|
||||||
return [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f)) and f.endswith(".png")]
|
files = []
|
||||||
|
for f in os.listdir(path):
|
||||||
|
fp = os.path.join(path, f)
|
||||||
|
rp = os.path.relpath(fp, config.gamedir)
|
||||||
|
|
||||||
|
if os.path.isfile(os.path.join(path, f)) and f.endswith(".png"):
|
||||||
|
files.append(build_button(rp))
|
||||||
|
|
||||||
|
return files
|
||||||
|
@ -68,10 +68,6 @@ init python:
|
|||||||
if wardrobe_chitchats:
|
if wardrobe_chitchats:
|
||||||
_skipping = True
|
_skipping = True
|
||||||
renpy.suspend_rollback(False)
|
renpy.suspend_rollback(False)
|
||||||
renpy.hide_screen("wardrobe")
|
|
||||||
renpy.hide_screen("wardrobe_menuitem")
|
|
||||||
renpy.hide_screen("wardrobe_outfit_menuitem")
|
|
||||||
renpy.show("gui_fade", zorder=10, behind=get_character_tag(states.active_girl))
|
|
||||||
renpy.block_rollback()
|
renpy.block_rollback()
|
||||||
renpy.call(get_character_response(states.active_girl, what), arg)
|
renpy.call_in_new_context(get_character_response(states.active_girl, what), arg)
|
||||||
return
|
return
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user