# Released under the MIT License. See LICENSE for details.#"""Call related functionality shared between all efro components."""from__future__importannotationsimportweakrefimportthreadingfromtypingimportTYPE_CHECKING,TypeVar,GenericT=TypeVar('T')ifTYPE_CHECKING:fromtypingimportCallable
[docs]classCallbackSet(Generic[T]):"""A simple way to manage a set of callbacks. Any number of calls can be registered with a callback set. Each registration results in a Registration object that can be used to deregister the call from the set later. Callbacks are also implicitly deregistered 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). """def__init__(self)->None:self._entries:list[weakref.ref[CallbackRegistration[T]]]=[]self.thread:threading.Threadif__debug__:self.thread=threading.current_thread()
[docs]defregister(self,call:T)->CallbackRegistration[T]:"""Register a new callback."""assertthreading.current_thread()==self.threadself._prune()entry=CallbackRegistration(call,self)self._entries.append(weakref.ref(entry))returnentry
[docs]defgetcalls(self)->list[T]:"""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. """assertthreading.current_thread()==self.threadself._prune()# Ignore calls that have been deallocated or explicitly# deregistered.entries=[e()foreinself._entries]return[e.callforeinentriesifeisnotNoneande.callisnotNone]
def_prune(self)->None:# Quick-out if all our entries are intact.needs_prune=Falseforentryinself._entries:entrytarget=entry()ifentrytargetisNoneorentrytarget.callisNone:needs_prune=Truebreakifnotneeds_prune:return# Ok; something needs pruning. Rebuild the entries list.newentries:list[weakref.ref[CallbackRegistration[T]]]=[]forentryinself._entries:entrytarget=entry()ifentrytargetisnotNoneandentrytarget.callisnotNone:newentries.append(entry)self._entries=newentries
[docs]classCallbackRegistration(Generic[T]):"""An entry for a callback set."""def__init__(self,call:T,callbackset:CallbackSet[T])->None:self.call:T|None=callself.callbackset:CallbackSet[T]|None=callbackset
[docs]defderegister(self)->None:"""Explicitly remove a callback from a CallbackSet."""assert(self.callbacksetisNoneorthreading.current_thread()==self.callbackset.thread)# Simply clear the call to mark us as dead.self.call=None