Package horizons :: Package util :: Module changelistener
[hide private]
[frames] | no frames]

Source Code for Module horizons.util.changelistener

  1  # ################################################### 
  2  # Copyright (C) 2008-2017 The Unknown Horizons Team 
  3  # team@unknown-horizons.org 
  4  # This file is part of Unknown Horizons. 
  5  # 
  6  # Unknown Horizons is free software; you can redistribute it and/or modify 
  7  # it under the terms of the GNU General Public License as published by 
  8  # the Free Software Foundation; either version 2 of the License, or 
  9  # (at your option) any later version. 
 10  # 
 11  # This program is distributed in the hope that it will be useful, 
 12  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 14  # GNU General Public License for more details. 
 15  # 
 16  # You should have received a copy of the GNU General Public License 
 17  # along with this program; if not, write to the 
 18  # Free Software Foundation, Inc., 
 19  # 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
 20  # ################################################### 
 21   
 22  import logging 
 23  import traceback 
 24   
 25  from horizons.util.python.callback import Callback 
 26  from horizons.util.python.weakmethodlist import WeakMethodList 
 27   
 28   
29 -class ChangeListener:
30 """Trivial ChangeListener. 31 The object that changes and the object that listens have to inherit from this class. 32 An object calls _changed every time something has changed, obviously. 33 This function calls every Callback, that has been registered to listen for a change. 34 NOTE: ChangeListeners aren't saved, they have to be reregistered on load 35 NOTE: RemoveListeners must not access the object, as it is in progress of being destroyed. 36 """ 37 38 log = logging.getLogger('changelistener') 39
40 - def __init__(self, *args, **kwargs):
41 super().__init__() #TODO: check if this call is needed 42 self.__init()
43
44 - def __init(self):
45 self.__listeners = WeakMethodList() 46 self.__remove_listeners = WeakMethodList() 47 # number of event calls 48 # if any event is triggered increase the number, after all callbacks are executed decrease it 49 # if it reaches 0 it means that in the current object all event callbacks were executed 50 self.__event_call_number = 0 51 self.__hard_remove = True
52
53 - def __remove_listener(self, listener_list, listener):
54 # check if the listener should be hard removed 55 # if so switch it in the list to None 56 try: 57 if self.__hard_remove: 58 listener_list.remove(listener) 59 else: 60 listener_list[listener_list.index(listener)] = None 61 except ValueError as e: # nicer error: 62 raise ValueError(str(e) + 63 "\nTried to remove: " + str(listener) + "\nat " + str(self) + 64 "\nList: " + str([str(i) for i in listener_list]))
65
66 - def __call_listeners(self, listener_list):
67 # instead of removing from list, switch the listener in position to None 68 # this way, iteration won't be affected while listeners may modify the list 69 self.__hard_remove = False 70 # increase the event call number 71 self.__event_call_number += 1 72 for listener in listener_list: 73 if listener: 74 try: 75 listener() 76 except ReferenceError as e: 77 # listener object is dead, don't crash since it doesn't need updates now anyway 78 self.log.warning('The dead are listening to %s: %s', self, e) 79 traceback.print_stack() 80 81 self.__event_call_number -= 1 82 83 if self.__event_call_number == 0: 84 self.__hard_remove = True 85 listener_list[:] = [l for l in listener_list if l]
86 87 ## Normal change listener
88 - def add_change_listener(self, listener, call_listener_now=False, no_duplicates=False):
89 assert callable(listener) 90 if not no_duplicates or listener not in self.__listeners: 91 self.__listeners.append(listener) 92 if call_listener_now: # also call if duplicate is added 93 listener()
94
95 - def remove_change_listener(self, listener):
96 self.__remove_listener(self.__listeners, listener)
97
98 - def has_change_listener(self, listener):
99 return (listener in self.__listeners)
100
101 - def discard_change_listener(self, listener):
102 """Remove listener if it's there""" 103 if self.has_change_listener(listener): 104 self.remove_change_listener(listener)
105
106 - def clear_change_listeners(self):
107 """Removes all change listeners""" 108 self.__listeners = WeakMethodList()
109
110 - def _changed(self):
111 """Calls every listener when an object changed""" 112 self.__call_listeners(self.__listeners)
113 114 ## Removal change listener
115 - def add_remove_listener(self, listener, no_duplicates=False):
116 """A listener that listens for removal of the object""" 117 assert callable(listener) 118 if no_duplicates and listener in self.__remove_listeners: 119 return # don't allow duplicate entries 120 self.__remove_listeners.append(listener)
121
122 - def remove_remove_listener(self, listener):
123 self.__remove_listener(self.__remove_listeners, listener)
124
125 - def has_remove_listener(self, listener):
126 return (listener in self.__remove_listeners)
127
128 - def discard_remove_listener(self, listener):
129 if self.has_remove_listener(listener): 130 self.remove_remove_listener(listener)
131
132 - def load(self, db, world_id):
133 self.__init()
134
135 - def remove(self):
136 self.__call_listeners(self.__remove_listeners) 137 self.end()
138
139 - def end(self):
140 self.__listeners = None 141 self.__remove_listeners = None
142 143 144 """ Class decorator that adds methods for listening for certain events to a class. 145 These methods get added automatically (eventname is the name you pass to the decorator): 146 - add_eventname_listener(listener): 147 Adds listener callback. This function must take the object as first parameter plus 148 any parameter that might be provided additionally to on_eventname. 149 - remove_eventname_listener(listener); 150 Removes a listener previously added. 151 - has_eventname_listener(listener) 152 Checks if a certain listener has been added. 153 - on_eventname 154 This is used to call the callbacks when the event occurred. 155 Additional parameters may be provided, which are passed to the callback. 156 157 The goal is to simplify adding special listeners, as for example used in the 158 production_finished listener. 159 """ 160 161
162 -def metaChangeListenerDecorator(event_name):
163 def decorator(clas): 164 list_name = "__" + event_name + "_listeners" 165 event_call_number = "__" + event_name + "call_number" 166 hard_remove_event = "__hard_remove" + event_name 167 168 # trivial changelistener operations 169 def add(self, listener): 170 assert callable(listener) 171 getattr(self, list_name).append(listener)
172 173 def rem(self, listener): 174 if getattr(self, hard_remove_event): 175 getattr(self, list_name).remove(listener) 176 else: 177 listener_list = getattr(self, list_name) 178 listener_list[listener_list.index(listener)] = None 179 180 def has(self, listener): 181 return listener in getattr(self, list_name) 182 183 def on(self, *args, **kwargs): 184 setattr(self, hard_remove_event, False) 185 call_number = getattr(self, event_call_number) + 1 186 setattr(self, event_call_number, call_number) 187 for f in getattr(self, list_name): 188 if f: 189 # workaround for encapsuled arguments 190 if isinstance(f, Callback): 191 f() 192 else: 193 f(self, *args, **kwargs) 194 195 call_number = getattr(self, event_call_number) - 1 196 setattr(self, event_call_number, call_number) 197 if getattr(self, event_call_number) == 0: 198 setattr(self, hard_remove_event, True) 199 setattr(self, list_name, [l for l in getattr(self, list_name) if l]) 200 201 # add methods to class 202 setattr(clas, "add_" + event_name + "_listener", add) 203 setattr(clas, "remove_" + event_name + "_listener", rem) 204 setattr(clas, "has_" + event_name + "_listener", has) 205 setattr(clas, "on_" + event_name, on) 206 207 # use black __new__ magic to add the methods to the instances 208 # think of it as being executed in __init__ 209 old_new = clas.__new__ 210 211 def new(cls, *args, **kwargs): 212 # this is a proposed way of calling the "old" new: 213 #obj = super(cls, cls).__new__(cls) 214 # which results in endless recursion, if you construct an instance of a class, 215 # that inherits from a base class on which the decorator has been applied. 216 # therefore, this workaround is used: 217 obj = old_new(cls) 218 setattr(obj, list_name, []) 219 setattr(obj, event_call_number, 0) 220 setattr(obj, hard_remove_event, True) 221 return obj 222 clas.__new__ = staticmethod(new) 223 return clas 224 return decorator 225