Skip to content

Commit 46ee535

Browse files
committed
Register plugin exceptions as PluginExit events.
refs #149
1 parent aaa2b89 commit 46ee535

File tree

2 files changed

+94
-46
lines changed

2 files changed

+94
-46
lines changed

sc2reader/engine/engine.py

Lines changed: 38 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ def handleAbilityEvent(self, event, replay):
102102
replay::
103103
104104
code, details = replay.plugins['MyPlugin']
105+
106+
If your plugin depends on another plugin, it is a good idea to implement handlePluginExit
107+
and be alerted if the plugin that you require fails. This way you can exit gracefully. You
108+
can also check to see if the plugin name is in ``replay.plugin_failures``::
109+
110+
if 'RequiredPlugin' in replay.plugin_failures:
111+
code, details = replay.plugins['RequiredPlugin']
112+
message = "RequiredPlugin failed with code: {0}. Cannot continue.".format(code)
113+
yield PluginExit(self, code=1, details=dict(msg=message))
105114
"""
106115
def __init__(self, plugins=[]):
107116
self._plugins = list()
@@ -126,8 +135,11 @@ def run(self, replay):
126135
# remove them from this list and regenerate event handlers.
127136
plugins = list(self._plugins)
128137

129-
# Create a dict for storing plugin exit codes and details
130-
replay.plugins = dict()
138+
# Create a dict for storing plugin exit codes and details.
139+
replay.plugin_result = replay.plugins = dict()
140+
141+
# Create a list storing replay.plugins keys for failures.
142+
replay.plugin_failures = list()
131143

132144
# Fill event event queue with the replay events, bookmarked by Init and End events.
133145
event_queue = collections.deque()
@@ -144,8 +156,9 @@ def run(self, replay):
144156
# Remove the plugin and reset the handlers.
145157
plugins.remove(event.plugin)
146158
handlers.clear()
147-
replay.plugins[event.plugin.name] = (event.code, event.details)
148-
continue
159+
replay.plugin_result[event.plugin.name] = (event.code, event.details)
160+
if event.code != 0:
161+
replay.plugin_failures.append(event.plugin.name)
149162

150163
# If we haven't compiled a list of handlers for this event yet, do so!
151164
if event.name not in handlers:
@@ -155,19 +168,31 @@ def run(self, replay):
155168
event_handlers = handlers[event.name]
156169

157170
# Events have the option of yielding one or more additional events
158-
# which get processed after the current event finishes.
159-
new_events = list()
171+
# which get processed after the current event finishes. The new_events
172+
# batch is constructed in reverse order because extendleft reverses
173+
# the order again with a series of appendlefts.
174+
new_events = collections.deque()
160175
for event_handler in event_handlers:
161-
new_events.extend(event_handler(event, replay) or [])
162-
163-
# extendleft does a series of appendlefts and reverses the order so we
164-
# need to reverse the list first to have them added in order.
176+
try:
177+
for new_event in (event_handler(event, replay) or []):
178+
if new_event.name == 'PluginExit':
179+
new_events.append(new_event)
180+
break
181+
else:
182+
new_events.appendleft(new_event)
183+
except Exception as e:
184+
if event_handler.im_self.name in ['ContextLoader']:
185+
# Certain built in plugins should probably still cause total failure
186+
raise # Maybe??
187+
else:
188+
new_event = PluginExit(event_handler.im_self, code=1, details=dict(error=e))
189+
new_events.append(new_event)
165190
event_queue.extendleft(new_events)
166191

167-
# For any plugins that didn't yield a PluginExit event, record a successful
168-
# completion.
192+
# For any plugins that didn't yield a PluginExit event or throw unexpected exceptions,
193+
# record a successful completion.
169194
for plugin in plugins:
170-
replay.plugins[plugin.name] = (0, dict())
195+
replay.plugin_result[plugin.name] = (0, dict())
171196

172197
def _get_event_handlers(self, event, plugins):
173198
return sum([self._get_plugin_event_handlers(plugin, event) for plugin in plugins], [])
@@ -197,35 +222,3 @@ def _has_event_handler(self, plugin, event):
197222

198223
def _get_event_handler(self, plugin, event):
199224
return getattr(plugin, 'handle'+event.name, None)
200-
201-
202-
if __name__ == '__main__':
203-
from sc2reader.events import UserOptionsEvent, GameStartEvent, PlayerLeaveEvent
204-
205-
class TestEvent(object):
206-
name = 'TestEvent'
207-
208-
def __init__(self, source):
209-
self.source = source
210-
211-
class TestPlugin(object):
212-
yields = TestEvent
213-
214-
def handleInitGame(self, event, replay,):
215-
yield TestEvent(event.name)
216-
217-
def handleTestEvent(self, event, replay):
218-
print(event.source)
219-
220-
def handleGameStartEvent(self, event, replay):
221-
yield TestEvent(event.name)
222-
223-
def handleEndGame(self, event, replay):
224-
yield TestEvent(event.name)
225-
226-
class TestReplay(object):
227-
events = [UserOptionsEvent, UserOptionsEvent, GameStartEvent, PlayerLeaveEvent]
228-
229-
engine = GameEngine()
230-
engine.register_plugin(TestPlugin())
231-
events = engine.run(TestReplay)

test_replays/test_all.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,6 @@ def test_gameheartnormalizer_plugin(self):
361361
def test_replay_event_order(self):
362362
replay = sc2reader.load_replay("test_replays/event_order.SC2Replay")
363363

364-
365364
def test_creepTracker(self):
366365
from sc2reader.engine.plugins import CreepTracker
367366

@@ -388,5 +387,61 @@ def test_creepTracker(self):
388387
assert replay.player[2].creep_spread_by_minute[420] == 9.4
389388
assert replay.player[2].creep_spread_by_minute[780] == 22.42
390389

390+
391+
class TestGameEngine(unittest.TestCase):
392+
class TestEvent(object):
393+
name='TestEvent'
394+
def __init__(self, value):
395+
self.value = value
396+
def __str__(self):
397+
return self.value
398+
399+
class TestPlugin1(object):
400+
name = 'TestPlugin1'
401+
402+
def handleInitGame(self, event, replay):
403+
yield TestGameEngine.TestEvent('b')
404+
yield TestGameEngine.TestEvent('c')
405+
406+
def handleTestEvent(self, event, replay):
407+
print("morestuff")
408+
if event.value == 'd':
409+
yield sc2reader.engine.PluginExit(self, code=1, details=dict(msg="Fail!"))
410+
else:
411+
yield TestGameEngine.TestEvent('d')
412+
413+
def handleEndGame(self, event, replay):
414+
yield TestGameEngine.TestEvent('g')
415+
416+
class TestPlugin2(object):
417+
name = 'TestPlugin2'
418+
def handleInitGame(self, event, replay):
419+
replay.engine_events = list()
420+
421+
def handleTestEvent(self, event, replay):
422+
replay.engine_events.append(event)
423+
424+
def handlePluginExit(self, event, replay):
425+
yield TestGameEngine.TestEvent('e')
426+
427+
def handleEndGame(self, event, replay):
428+
yield TestGameEngine.TestEvent('f')
429+
430+
class MockReplay(object):
431+
def __init__(self, events):
432+
self.events = events
433+
434+
def test_plugin1(self):
435+
engine = sc2reader.engine.GameEngine()
436+
engine.register_plugin(self.TestPlugin1())
437+
engine.register_plugin(self.TestPlugin2())
438+
replay = self.MockReplay([self.TestEvent('a')])
439+
engine.run(replay)
440+
self.assertEqual(''.join(str(e) for e in replay.engine_events), 'bdecaf')
441+
self.assertEqual(replay.plugin_failures, ['TestPlugin1'])
442+
self.assertEqual(replay.plugin_result['TestPlugin1'], (1, dict(msg="Fail!")))
443+
self.assertEqual(replay.plugin_result['TestPlugin2'], (0, dict()))
444+
445+
391446
if __name__ == '__main__':
392447
unittest.main()

0 commit comments

Comments
 (0)