efro.call
Call related functionality shared between all efro components.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Call related functionality shared between all efro components.""" 4 5from __future__ import annotations 6 7import threading 8import weakref 9from typing import TYPE_CHECKING, TypeVar, Generic 10 11T = TypeVar('T') 12 13if TYPE_CHECKING: 14 pass 15 16 17class CallbackSet(Generic[T]): 18 """A simple way to manage a set of callbacks. 19 20 Any number of calls can be added to a callback set. Each add results 21 in an entry that can be used to remove the call from the set later. 22 Callbacks are also implicitly removed when an entry is deallocated, 23 so make sure to hold on to the return value when adding. 24 25 CallbackSet instances should be used from a single thread only 26 (this will be checked in debug mode). 27 """ 28 29 def __init__(self) -> None: 30 self._entries: list[weakref.ref[CallbackRegistration[T]]] = [] 31 self.thread: threading.Thread 32 if __debug__: 33 self.thread = threading.current_thread() 34 35 def add(self, call: T) -> CallbackRegistration[T]: 36 """Add a callback.""" 37 assert threading.current_thread() == self.thread 38 39 self._prune() 40 41 entry = CallbackRegistration(call, self) 42 self._entries.append(weakref.ref(entry)) 43 return entry 44 45 def getcalls(self) -> list[T]: 46 """Return the current set of registered calls. 47 48 Note that this returns a flattened list of calls; generally this 49 should protect against calls which themselves add or remove 50 callbacks. 51 """ 52 assert threading.current_thread() == self.thread 53 54 self._prune() 55 56 # Ignore calls that have been deallocated or explicitly cleared. 57 entries = [e() for e in self._entries] 58 return [e.call for e in entries if e is not None and e.call is not None] 59 60 def _prune(self) -> None: 61 62 # Quick-out if all our entries are intact. 63 needs_prune = False 64 for entry in self._entries: 65 entrytarget = entry() 66 if entrytarget is None or entrytarget.call is None: 67 needs_prune = True 68 break 69 if not needs_prune: 70 return 71 72 newentries: list[weakref.ref[CallbackRegistration[T]]] = [] 73 for entry in self._entries: 74 entrytarget = entry() 75 if entrytarget is not None and entrytarget.call is not None: 76 newentries.append(entry) 77 self._entries = newentries 78 79 80class CallbackRegistration(Generic[T]): 81 """An entry for a callback set.""" 82 83 def __init__(self, call: T, callbackset: CallbackSet[T]) -> None: 84 self.call: T | None = call 85 self.callbackset: CallbackSet[T] | None = callbackset 86 87 def clear(self) -> None: 88 """Explicitly remove a callback from a CallbackSet.""" 89 assert ( 90 self.callbackset is None 91 or threading.current_thread() == self.callbackset.thread 92 ) 93 # Simply clear the call to mark us as dead. 94 self.call = None
class
CallbackSet(typing.Generic[~T]):
18class CallbackSet(Generic[T]): 19 """A simple way to manage a set of callbacks. 20 21 Any number of calls can be added to a callback set. Each add results 22 in an entry that can be used to remove the call from the set later. 23 Callbacks are also implicitly removed when an entry is deallocated, 24 so make sure to hold on to the return value when adding. 25 26 CallbackSet instances should be used from a single thread only 27 (this will be checked in debug mode). 28 """ 29 30 def __init__(self) -> None: 31 self._entries: list[weakref.ref[CallbackRegistration[T]]] = [] 32 self.thread: threading.Thread 33 if __debug__: 34 self.thread = threading.current_thread() 35 36 def add(self, call: T) -> CallbackRegistration[T]: 37 """Add a callback.""" 38 assert threading.current_thread() == self.thread 39 40 self._prune() 41 42 entry = CallbackRegistration(call, self) 43 self._entries.append(weakref.ref(entry)) 44 return entry 45 46 def getcalls(self) -> list[T]: 47 """Return the current set of registered calls. 48 49 Note that this returns a flattened list of calls; generally this 50 should protect against calls which themselves add or remove 51 callbacks. 52 """ 53 assert threading.current_thread() == self.thread 54 55 self._prune() 56 57 # Ignore calls that have been deallocated or explicitly cleared. 58 entries = [e() for e in self._entries] 59 return [e.call for e in entries if e is not None and e.call is not None] 60 61 def _prune(self) -> None: 62 63 # Quick-out if all our entries are intact. 64 needs_prune = False 65 for entry in self._entries: 66 entrytarget = entry() 67 if entrytarget is None or entrytarget.call is None: 68 needs_prune = True 69 break 70 if not needs_prune: 71 return 72 73 newentries: list[weakref.ref[CallbackRegistration[T]]] = [] 74 for entry in self._entries: 75 entrytarget = entry() 76 if entrytarget is not None and entrytarget.call is not None: 77 newentries.append(entry) 78 self._entries = newentries
A simple way to manage a set of callbacks.
Any number of calls can be added to a callback set. Each add results in an entry that can be used to remove the call from the set later. Callbacks are also implicitly removed when an entry is deallocated, so make sure to hold on to the return value when adding.
CallbackSet instances should be used from a single thread only (this will be checked in debug mode).
36 def add(self, call: T) -> CallbackRegistration[T]: 37 """Add a callback.""" 38 assert threading.current_thread() == self.thread 39 40 self._prune() 41 42 entry = CallbackRegistration(call, self) 43 self._entries.append(weakref.ref(entry)) 44 return entry
Add a callback.
def
getcalls(self) -> list[~T]:
46 def getcalls(self) -> list[T]: 47 """Return the current set of registered calls. 48 49 Note that this returns a flattened list of calls; generally this 50 should protect against calls which themselves add or remove 51 callbacks. 52 """ 53 assert threading.current_thread() == self.thread 54 55 self._prune() 56 57 # Ignore calls that have been deallocated or explicitly cleared. 58 entries = [e() for e in self._entries] 59 return [e.call for e in entries if e is not None and e.call is not None]
Return the current set of registered calls.
Note that this returns a flattened list of calls; generally this should protect against calls which themselves add or remove callbacks.
class
CallbackRegistration(typing.Generic[~T]):
81class CallbackRegistration(Generic[T]): 82 """An entry for a callback set.""" 83 84 def __init__(self, call: T, callbackset: CallbackSet[T]) -> None: 85 self.call: T | None = call 86 self.callbackset: CallbackSet[T] | None = callbackset 87 88 def clear(self) -> None: 89 """Explicitly remove a callback from a CallbackSet.""" 90 assert ( 91 self.callbackset is None 92 or threading.current_thread() == self.callbackset.thread 93 ) 94 # Simply clear the call to mark us as dead. 95 self.call = None
An entry for a callback set.
CallbackRegistration(call: ~T, callbackset: CallbackSet[~T])
callbackset: Optional[CallbackSet[~T]]
def
clear(self) -> None:
88 def clear(self) -> None: 89 """Explicitly remove a callback from a CallbackSet.""" 90 assert ( 91 self.callbackset is None 92 or threading.current_thread() == self.callbackset.thread 93 ) 94 # Simply clear the call to mark us as dead. 95 self.call = None
Explicitly remove a callback from a CallbackSet.