statecharts_core.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. import abc
  2. import re
  3. import time
  4. import threading
  5. from infinity import INFINITY
  6. from event_queue import EventQueue
  7. from Queue import Queue, Empty
  8. class RuntimeException(Exception):
  9. def __init__(self, message):
  10. self.message = message
  11. def __str__(self):
  12. return repr(self.message)
  13. class AssociationException(RuntimeException):
  14. pass
  15. class AssociationReferenceException(RuntimeException):
  16. pass
  17. class ParameterException(RuntimeException):
  18. pass
  19. class InputException(RuntimeException):
  20. pass
  21. class Association(object):
  22. #wrapper object for one association relation
  23. def __init__(self, name, class_name, min_card, max_card):
  24. self.min_card = min_card
  25. self.max_card = max_card
  26. self.name = name
  27. self.class_name = class_name
  28. self.instances = {} #list of all instance wrappers
  29. self.instances_to_idx = {}
  30. self.idx = 0
  31. def getName(self):
  32. return self.name
  33. def getClassName(self):
  34. return self.class_name
  35. def allowedToAdd(self):
  36. return self.max_card == -1 or len(self.instances) < self.max_card
  37. def allowedToRemove(self):
  38. return self.min_card == -1 or len(self.instances) > self.min_card
  39. def addInstance(self, instance):
  40. if self.allowedToAdd() :
  41. self.instances[self.idx] = instance
  42. self.instances_to_idx[instance] = self.idx
  43. self.idx += 1
  44. else :
  45. raise AssociationException("Not allowed to add the instance to the association.")
  46. return self.idx - 1
  47. def removeInstance(self, instance):
  48. if self.allowedToRemove() :
  49. del self.instances[self.instances_to_idx[instance]]
  50. del self.instances_to_idx[instance]
  51. else :
  52. raise AssociationException("Not allowed to remove the instance to the association.")
  53. def getInstance(self, index):
  54. try :
  55. return self.instances[index]
  56. except IndexError :
  57. raise AssociationException("Invalid index for fetching instance(s) from association.")
  58. class InstanceWrapper(object):
  59. #wrapper object for an instance and its relevant information needed in the object manager
  60. def __init__(self, instance, associations):
  61. self.instance = instance
  62. self.associations = {}
  63. for association in associations :
  64. self.associations[association.getName()] = association
  65. def getAssociation(self, name):
  66. try :
  67. return self.associations[name]
  68. except KeyError :
  69. raise AssociationReferenceException("Unknown association %s." % name)
  70. def getInstance(self):
  71. return self.instance
  72. class ObjectManagerBase(object):
  73. __metaclass__ = abc.ABCMeta
  74. def __init__(self, controller):
  75. self.controller = controller
  76. self.events = EventQueue()
  77. self.instances_map = {} #a dictionary that maps RuntimeClassBase to InstanceWrapper
  78. def addEvent(self, event, time_offset = 0.0):
  79. self.events.add(event, time_offset)
  80. # Broadcast an event to all instances
  81. def broadcast(self, new_event):
  82. for i in self.instances_map:
  83. i.addEvent(new_event)
  84. def getWaitTime(self):
  85. #first get waiting time of the object manager's events
  86. smallest_time = self.events.getEarliestTime();
  87. #check all the instances
  88. for instance in self.instances_map.iterkeys() :
  89. smallest_time = min(smallest_time, instance.getEarliestEventTime())
  90. return smallest_time;
  91. def stepAll(self, delta):
  92. self.step(delta)
  93. for i in self.instances_map.iterkeys():
  94. i.step(delta)
  95. def step(self, delta):
  96. self.events.decreaseTime(delta);
  97. for event in self.events.popDueEvents() :
  98. self.handleEvent(event)
  99. def start(self):
  100. for i in self.instances_map:
  101. i.start()
  102. def handleEvent(self, e):
  103. if e.getName() == "narrow_cast" :
  104. self.handleNarrowCastEvent(e.getParameters())
  105. elif e.getName() == "broad_cast" :
  106. self.handleBroadCastEvent(e.getParameters())
  107. elif e.getName() == "create_instance" :
  108. self.handleCreateEvent(e.getParameters())
  109. elif e.getName() == "associate_instance" :
  110. self.handleAssociateEvent(e.getParameters())
  111. elif e.getName() == "start_instance" :
  112. self.handleStartInstanceEvent(e.getParameters())
  113. elif e.getName() == "delete_instance" :
  114. self.handleDeleteInstanceEvent(e.getParameters())
  115. def processAssociationReference(self, input_string):
  116. if len(input_string) == 0 :
  117. raise AssociationReferenceException("Empty association reference.")
  118. regex_pattern = re.compile("^([a-zA-Z_]\w*)(?:\[(\d+)\])?$");
  119. path_string = input_string.split("/")
  120. result = []
  121. for piece in path_string :
  122. match = regex_pattern.match(piece)
  123. if match :
  124. name = match.group(1)
  125. index = match.group(2)
  126. if index is None :
  127. index = -1
  128. result.append((name,int(index)))
  129. else :
  130. raise AssociationReferenceException("Invalid entry in association reference.")
  131. return result
  132. def handleStartInstanceEvent(self, parameters):
  133. if len(parameters) != 2 :
  134. raise ParameterException ("The start instance event needs 2 parameters.")
  135. else :
  136. source = parameters[0]
  137. traversal_list = self.processAssociationReference(parameters[1])
  138. for i in self.getInstances(source, traversal_list) :
  139. i.instance.start()
  140. source.addEvent(Event("instance_started", parameters = [parameters[1]]))
  141. def handleBroadCastEvent(self, parameters):
  142. if len(parameters) != 1 :
  143. raise ParameterException ("The broadcast event needs 1 parameter.")
  144. self.broadcast(parameters[0])
  145. def handleCreateEvent(self, parameters):
  146. if len(parameters) < 2 :
  147. raise ParameterException ("The create event needs at least 2 parameters.")
  148. else :
  149. source = parameters[0]
  150. association_name = parameters[1]
  151. association = self.instances_map[source].getAssociation(association_name)
  152. if association.allowedToAdd() :
  153. ''' allow subclasses to be instantiated '''
  154. class_name = association.class_name if len(parameters) == 2 else parameters[2]
  155. new_instance_wrapper = self.createInstance(class_name, parameters[3:])
  156. idx = association.addInstance(new_instance_wrapper)
  157. try:
  158. new_instance_wrapper.getAssociation('parent').addInstance(self.instances_map[source])
  159. except AssociationReferenceException:
  160. pass
  161. source.addEvent(Event("instance_created", parameters = ['%s[%i]' % (association_name, idx)]))
  162. else :
  163. source.addEvent(Event("instance_creation_error", parameters = [association_name]))
  164. def handleDeleteInstanceEvent(self, parameters):
  165. if len(parameters) < 2 :
  166. raise ParameterException ("The delete event needs at least 2 parameters.")
  167. else :
  168. source = parameters[0]
  169. association_name = parameters[1]
  170. traversal_list = self.processAssociationReference(association_name)
  171. instances = self.getInstances(source, traversal_list)
  172. association = self.instances_map[source].getAssociation(traversal_list[0][0])
  173. for i in instances:
  174. association.removeInstance(i)
  175. i.getInstance().stop()
  176. if hasattr(i.instance, '__del__'):
  177. i.instance.__del__()
  178. source.addEvent(Event("instance_deleted", parameters = [parameters[1]]))
  179. def handleAssociateEvent(self, parameters):
  180. if len(parameters) != 3 :
  181. raise ParameterException ("The associate_instance event needs 3 parameters.");
  182. else :
  183. source = parameters[0]
  184. to_copy_list = self.getInstances(source,self.processAssociationReference(parameters[1]))
  185. if len(to_copy_list) != 1 :
  186. raise AssociationReferenceException ("Invalid source association reference.")
  187. wrapped_to_copy_instance = to_copy_list[0]
  188. dest_list = self.processAssociationReference(parameters[2])
  189. if len(dest_list) == 0 :
  190. raise AssociationReferenceException ("Invalid destination association reference.")
  191. last = dest_list.pop()
  192. if last[1] != -1 :
  193. raise AssociationReferenceException ("Last association name in association reference should not be accompanied by an index.")
  194. for i in self.getInstances(source, dest_list) :
  195. i.getAssociation(last[0]).addInstance(wrapped_to_copy_instance)
  196. def handleNarrowCastEvent(self, parameters):
  197. if len(parameters) != 3 :
  198. raise ParameterException ("The associate_instance event needs 3 parameters.")
  199. source = parameters[0]
  200. traversal_list = self.processAssociationReference(parameters[1])
  201. cast_event = parameters[2]
  202. for i in self.getInstances(source, traversal_list) :
  203. i.instance.addEvent(cast_event)
  204. def getInstances(self, source, traversal_list):
  205. currents = [self.instances_map[source]]
  206. for (name, index) in traversal_list :
  207. nexts = []
  208. for current in currents :
  209. association = current.getAssociation(name)
  210. if (index >= 0 ) :
  211. nexts.append ( association.getInstance(index) );
  212. elif (index == -1) :
  213. nexts.extend ( association.instances.values() );
  214. else :
  215. raise AssociationReferenceException("Incorrect index in association reference.")
  216. currents = nexts
  217. return currents
  218. @abc.abstractmethod
  219. def instantiate(self, class_name, construct_params):
  220. pass
  221. def createInstance(self, class_name, construct_params = []):
  222. instance_wrapper = self.instantiate(class_name, construct_params)
  223. if instance_wrapper:
  224. self.instances_map[instance_wrapper.getInstance()] = instance_wrapper
  225. return instance_wrapper
  226. class Event(object):
  227. def __init__(self, event_name, port = "", parameters = []):
  228. self.name = event_name
  229. self.parameters = parameters
  230. self.port = port
  231. def getName(self):
  232. return self.name
  233. def getPort(self):
  234. return self.port
  235. def getParameters(self):
  236. return self.parameters
  237. def __repr__(self):
  238. representation = "(event name : " + str(self.name) + "; port : " + str(self.port)
  239. if self.parameters :
  240. representation += "; parameters : " + str(self.parameters)
  241. representation += ")"
  242. return representation
  243. class OutputListener(object):
  244. def __init__(self, port_names):
  245. self.port_names = port_names
  246. self.queue = Queue()
  247. def add(self, event):
  248. if len(self.port_names) == 0 or event.getPort() in self.port_names :
  249. self.queue.put_nowait(event)
  250. """ Tries for timeout seconds to fetch an event, returns None if failed.
  251. 0 as timeout means no blocking.
  252. -1 as timeout means blocking until an event can be fetched. """
  253. def fetch(self, timeout = 0):
  254. try :
  255. if timeout == 0 :
  256. return self.queue.get(False)
  257. elif timeout < 0 :
  258. return self.queue.get(True, None)
  259. else :
  260. return self.queue.get(True, timeout)
  261. except Empty:
  262. return None
  263. class InputPortEntry(object):
  264. def __init__(self, virtual_name, instance):
  265. self.virtual_name = virtual_name
  266. self.instance = instance
  267. class ControllerBase(object):
  268. def __init__(self, object_manager, keep_running):
  269. self.object_manager = object_manager
  270. self.keep_running = keep_running
  271. self.private_port_counter = 0
  272. # Keep track of input ports
  273. self.input_ports = {}
  274. self.input_queue = EventQueue()
  275. # Keep track of output ports
  276. self.output_ports = []
  277. self.output_listeners = []
  278. # Let statechart run one last time before stopping
  279. self.done = False
  280. def addInputPort(self, virtual_name, instance = None):
  281. if instance == None :
  282. port_name = virtual_name
  283. else:
  284. port_name = "private_" + str(self.private_port_counter) + "_" + virtual_name
  285. self.private_port_counter += 1
  286. self.input_ports[port_name] = InputPortEntry(virtual_name, instance)
  287. #print "added port " + port_name
  288. return port_name
  289. def addOutputPort(self, port_name):
  290. self.output_ports.append(port_name)
  291. def broadcast(self, new_event):
  292. self.object_manager.broadcast(new_event)
  293. def start(self):
  294. self.object_manager.start()
  295. def stop(self):
  296. pass
  297. def addInput(self, input_event, time_offset = 0.0):
  298. if input_event.getName() == "" :
  299. raise InputException("Input event can't have an empty name.")
  300. if input_event.getPort() not in self.input_ports :
  301. raise InputException("Input port mismatch, no such port: " + input_event.getPort() + ".")
  302. self.input_queue.add(input_event, time_offset)
  303. def outputEvent(self, event):
  304. for listener in self.output_listeners :
  305. listener.add(event)
  306. def addOutputListener(self, ports):
  307. listener = OutputListener(ports)
  308. self.output_listeners.append(listener)
  309. return listener
  310. def addMyOwnOutputListener(self, listener):
  311. self.output_listeners.append(listener)
  312. def addEventList(self, event_list):
  313. for (event, time_offset) in event_list :
  314. self.addInput(event, time_offset)
  315. def getObjectManager(self):
  316. return self.object_manager;
  317. class GameLoopControllerBase(ControllerBase):
  318. def __init__(self, object_manager, keep_running):
  319. super(GameLoopControllerBase, self).__init__(object_manager, keep_running)
  320. def update(self, delta):
  321. self.input_queue.decreaseTime(delta)
  322. for event in self.input_queue.popDueEvents() :
  323. input_port = self.input_ports[event.getPort()]
  324. event.port = input_port.virtual_name
  325. target_instance = input_port.instance
  326. if target_instance == None:
  327. self.broadcast(event)
  328. else:
  329. target_instance.addEvent(event)
  330. self.object_manager.stepAll(delta)
  331. class ThreadsControllerBase(ControllerBase):
  332. def __init__(self, object_manager, keep_running):
  333. super(ThreadsControllerBase, self).__init__(object_manager, keep_running)
  334. self.input_condition = threading.Condition()
  335. self.stop_thread = False
  336. self.thread = threading.Thread(target=self.run)
  337. def handleInput(self, delta):
  338. self.input_condition.acquire()
  339. self.input_queue.decreaseTime(delta)
  340. for event in self.input_queue.popDueEvents():
  341. input_port = self.input_ports[event.getPort()]
  342. event.port = input_port.virtual_name
  343. target_instance = input_port.instance
  344. if target_instance == None:
  345. self.broadcast(event)
  346. else:
  347. target_instance.addEvent(event)
  348. self.input_condition.release()
  349. def start(self):
  350. self.thread.start()
  351. def stop(self):
  352. self.input_condition.acquire()
  353. self.stop_thread = True
  354. self.input_condition.notifyAll()
  355. self.input_condition.release()
  356. def getWaitTime(self):
  357. """Compute time untill earliest next event"""
  358. self.input_condition.acquire()
  359. wait_time = min(self.object_manager.getWaitTime(), self.input_queue.getEarliestTime())
  360. self.input_condition.release()
  361. if wait_time == INFINITY :
  362. if self.done :
  363. self.done = False
  364. else :
  365. self.done = True
  366. return 0.0
  367. return wait_time
  368. def handleWaiting(self):
  369. wait_time = self.getWaitTime()
  370. if(wait_time <= 0.0):
  371. return
  372. self.input_condition.acquire()
  373. if wait_time == INFINITY :
  374. if self.keep_running :
  375. self.input_condition.wait() #Wait for a signals
  376. else :
  377. self.stop_thread = True
  378. elif wait_time != 0.0 :
  379. actual_wait_time = wait_time - (time.time() - self.last_recorded_time)
  380. if actual_wait_time > 0.0 :
  381. self.input_condition.wait(actual_wait_time)
  382. self.input_condition.release()
  383. def run(self):
  384. self.last_recorded_time = time.time()
  385. super(ThreadsControllerBase, self).start()
  386. last_iteration_time = 0.0
  387. while True:
  388. self.handleInput(last_iteration_time)
  389. #Compute the new state based on internal events
  390. self.object_manager.stepAll(last_iteration_time)
  391. self.handleWaiting()
  392. self.input_condition.acquire()
  393. if self.stop_thread :
  394. break
  395. self.input_condition.release()
  396. previous_recorded_time = self.last_recorded_time
  397. self.last_recorded_time = time.time()
  398. last_iteration_time = self.last_recorded_time - previous_recorded_time
  399. def join(self):
  400. self.thread.join()
  401. def addInput(self, input_event, time_offset = 0.0):
  402. self.input_condition.acquire()
  403. super(ThreadsControllerBase, self).addInput(input_event, time_offset)
  404. self.input_condition.notifyAll()
  405. self.input_condition.release()
  406. def addEventList(self, event_list):
  407. self.input_condition.acquire()
  408. super(ThreadsControllerBase, self).addEventList(event_list)
  409. self.input_condition.release()
  410. class RuntimeClassBase(object):
  411. __metaclass__ = abc.ABCMeta
  412. def __init__(self):
  413. self.active = False
  414. self.state_changed = False
  415. self.events = EventQueue();
  416. self.timers = None;
  417. def addEvent(self, event, time_offset = 0.0):
  418. self.events.add(event, time_offset);
  419. def getEarliestEventTime(self) :
  420. if self.timers :
  421. return min(self.events.getEarliestTime(), min(self.timers.itervalues()))
  422. return self.events.getEarliestTime();
  423. def step(self, delta):
  424. if not self.active :
  425. return
  426. self.events.decreaseTime(delta);
  427. if self.timers :
  428. next_timers = {}
  429. for (key,value) in self.timers.iteritems() :
  430. time = value - delta
  431. if time <= 0.0 :
  432. self.addEvent( Event("_" + str(key) + "after"), time);
  433. else :
  434. next_timers[key] = time
  435. self.timers = next_timers;
  436. self.microstep()
  437. while (self.state_changed) :
  438. self.microstep()
  439. def microstep(self):
  440. due = self.events.popDueEvents()
  441. if (len(due) == 0) :
  442. self.transition()
  443. else :
  444. for event in due :
  445. self.transition(event);
  446. @abc.abstractmethod
  447. def transition(self, event = None):
  448. pass
  449. def start(self):
  450. self.active = True
  451. def stop(self):
  452. self.active = False