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):
|
||||
"""Container class for commonly used methods and attributes"""
|
||||
|
||||
_loading = Text("Loading", align=(0.5, 0.5))
|
||||
_image = Null()
|
||||
_image_cached = False
|
||||
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."""
|
||||
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."""
|
||||
for i in items:
|
||||
for i in items.get(subcat):
|
||||
if self.is_equipped_item(i):
|
||||
return i
|
||||
return None
|
||||
@ -391,10 +391,17 @@ init python:
|
||||
|
||||
def is_equipped_item(self, item):
|
||||
"""Takes DollCloth object or list of objects. Returns True if item is equipped, False otherwise."""
|
||||
|
||||
if isinstance(item, DollCloth):
|
||||
if item.is_multislot():
|
||||
return bool(next((k for k, v in self.states.items() if v[0] == item), False))
|
||||
|
||||
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):
|
||||
"""Takes argument(s) containing string cloth type(s). Returns True if worn, False otherwise."""
|
||||
@ -592,3 +599,11 @@ init python:
|
||||
self.emote = emote
|
||||
|
||||
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 queue
|
||||
|
||||
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)
|
||||
|
||||
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):
|
||||
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:
|
||||
self._return = self._target(*self._args, **self._kwargs)
|
||||
|
||||
renpy.restart_interaction()
|
||||
break
|
||||
|
||||
def join(self, timeout=1):
|
||||
threading.Thread.join(self, timeout=timeout)
|
||||
def join(self, timeout=None):
|
||||
super().join(timeout=timeout)
|
||||
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:
|
||||
|
||||
# Fix makeup object types inside saved outfits
|
||||
|
||||
for i in states.dolls:
|
||||
doll = getattr(store, i)
|
||||
|
||||
for j in doll.wardrobe_list:
|
||||
# Add new button handler for clothes
|
||||
j._button = DefaultQueue()
|
||||
|
||||
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"):
|
||||
|
||||
objects = [x.parent.clone() for x in j.group]
|
||||
|
@ -173,9 +173,27 @@ init -1 python:
|
||||
return
|
||||
|
||||
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):
|
||||
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:
|
||||
_skipping = True
|
||||
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.call(get_character_response(states.active_girl, what), arg)
|
||||
renpy.call_in_new_context(get_character_response(states.active_girl, what), arg)
|
||||
return
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user