Skip to content

Commit

Permalink
Merge dcdb2ab into 57c2cd3
Browse files Browse the repository at this point in the history
  • Loading branch information
Gjum committed Sep 22, 2015
2 parents 57c2cd3 + dcdb2ab commit 274b3fa
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 51 deletions.
3 changes: 2 additions & 1 deletion spock/plugins/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from spock.plugins.core import auth, event, net, ticker, timer
from spock.plugins.helpers import chat, clientinfo, entities, interact, \
inventory, keepalive, movement, physics, respawn, start, world
inventory, keepalive, movement, physics, respawn, start, taskmanager, world
from spock.plugins.base import PluginBase # noqa


Expand All @@ -22,6 +22,7 @@
('physics', physics.PhysicsPlugin),
('respawn', respawn.RespawnPlugin),
('start', start.StartPlugin),
('taskmanager', taskmanager.TaskManager),
('world', world.WorldPlugin),
]
default_plugins = core_plugins + helper_plugins
7 changes: 3 additions & 4 deletions spock/plugins/helpers/craft.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
from spock.mcdata.recipes import find_recipe, ingredient_positions, \
total_ingredient_amounts
from spock.plugins.base import PluginBase
from spock.task import RunTask, TaskFailed
from spock.plugins.tools.task import TaskFailed
from spock.utils import pl_announce


@pl_announce('Craft')
class CraftPlugin(PluginBase):
requires = ('Event', 'Inventory')
requires = 'Inventory'

def __init__(self, ploader, settings):
super(CraftPlugin, self).__init__(ploader, settings)
Expand All @@ -28,8 +28,7 @@ def craft(self, item=None, meta=None, amount=1, recipe=None, parent=None):
else:
recipe = find_recipe(item, meta)
if recipe:
RunTask(self.craft_task(recipe, amount),
self.event.reg_event_handler, parent)
self.taskmanager.run_task(self.craft_task(recipe, amount), parent)
return recipe

def craft_task(self, recipe, amount=1):
Expand Down
18 changes: 18 additions & 0 deletions spock/plugins/helpers/taskmanager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from spock.plugins.base import PluginBase
from spock.plugins.tools.task import Task
from spock.utils import pl_announce


@pl_announce('TaskManager')
class TaskManager(PluginBase):
requires = 'Event'

def __init__(self, ploader, settings):
super(TaskManager, self).__init__(ploader, settings)
ploader.provides('TaskManager', self)

def run_task(self, task, parent=None):
if not isinstance(task, Task):
task = Task(task, parent)
task.run(self)
return task
2 changes: 1 addition & 1 deletion spock/plugins/tools/inventory_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Asynchronous task wrappers for inventory
"""
from spock.mcdata import constants
from spock.task import TaskFailed, check_key
from spock.plugins.tools.task import TaskFailed, check_key


def unpack_slots_list(slots):
Expand Down
72 changes: 37 additions & 35 deletions spock/task.py → spock/plugins/tools/task.py
Original file line number Diff line number Diff line change
@@ -1,56 +1,73 @@
import types


def accept(evt, data):
return True


def check_key(key, value):
return lambda event, data: data[key] == value


class TaskFailed(Exception):
def __init__(self, *args):
self.args = args


def accept(evt, data):
return True
class TaskCallback(object):
def __init__(self, cb=None, eb=None):
self.cb = cb
self.eb = eb

def on_success(self, data):
if self.cb:
self.cb(data)

def check_key(key, value):
return lambda event, data: data[key] == value
def on_error(self, error):
if self.eb:
self.eb(error)


class RunTask(object):
def __init__(self, task, reg_event_handler, parent=None):
class Task(object):
def __init__(self, task, parent=None, name=None):
self.name = name or task.__name__
self.task = task
self.reg_event_handler = reg_event_handler
self.parent = parent
self.expected = {} # event -> check

self.run(lambda: next(self.task))
def run(self, task_manager):
self.task_manager = task_manager
self.continue_with(lambda: next(self.task))

def run(self, func):
def continue_with(self, func):
try:
response = func()
except StopIteration as exception:
if self.parent:
self.parent.on_success(exception.value)
self.parent.on_success(exception.args)
except TaskFailed as exception:
if self.parent:
self.parent.on_error(exception)
else:
self.register(response)

def on_success(self, data):
self.run(lambda: self.task.send((None, data)))
self.continue_with(lambda: self.task.send((None, data)))

def on_error(self, exception):
self.run(lambda: self.task.throw(exception))

def handler(self, event, data):
check = self.expected.get(event, lambda *_: False)
if check(event, data): # TODO does check really need event?
self.run(lambda: self.task.send((event, data)))
return True # remove this handler
self.continue_with(lambda: self.task.throw(exception))

def register(self, response):
self.expected.clear()
self.parse_response(response)
for event, check in self.expected.items():
self.reg_event_handler(event, self.handler)
self.task_manager.event.reg_event_handler(event, self.on_event)

def on_event(self, event, data):
check = self.expected.get(event, lambda *_: False)
if check(event, data): # TODO does check really need event?
self.continue_with(lambda: self.task.send((event, data)))
return True # remove this handler

def parse_response(self, response):
# TODO what format do we want to use? also documentation
Expand All @@ -61,9 +78,8 @@ def parse_response(self, response):
# - str/generator: list of events, register recursively
# - other (func): evt name + test func

self.expected.clear()
if isinstance(response, types.GeneratorType): # subtask
RunTask(response, self.reg_event_handler, parent=self)
self.task_manager.run_task(response, parent=self)
elif isinstance(response, str): # event name
self.expected[response] = accept
elif hasattr(response, '__getitem__'):
Expand All @@ -80,17 +96,3 @@ def parse_response(self, response):
self.expected.clear()
raise ValueError('Illegal task yield argument of type %s: %s'
% type(response), response)


class TaskCallback(object):
def __init__(self, cb=None, eb=None):
self.cb = cb
self.eb = eb

def on_success(self, data):
if self.cb:
self.cb(data)

def on_error(self, error):
if self.eb:
self.eb(error)
7 changes: 2 additions & 5 deletions tests/plugins/helpers/test_chat.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
from collections import namedtuple
from unittest import TestCase

from spock.plugins.helpers.chat import ChatCore, ChatPlugin


Packet = namedtuple('Packet', 'ident data')


class DataDict(dict):
def __init__(self, **kwargs):
super(DataDict, self).__init__(**kwargs)
Expand Down Expand Up @@ -42,7 +38,6 @@ def push_packet(self, ident, data):
data_dict = DataDict(**data)
self.idents.append(ident)
self.datas.append(data_dict)
print(ident, data)


class EventMock(object):
Expand All @@ -60,5 +55,7 @@ def setUp(self):
def test_chat(self):
self.plug.chatcore.chat('Hello')
self.assertEqual(NetMock.datas[-1].message, 'Hello')

def test_whisper(self):
self.plug.chatcore.whisper('Guy', 'Hello')
self.assertEqual(NetMock.datas[-1].message, '/tell Guy Hello')
5 changes: 0 additions & 5 deletions tests/plugins/helpers/test_interact.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from collections import namedtuple
from unittest import TestCase

from spock.mcdata.constants import \
Expand All @@ -8,9 +7,6 @@
from spock.vector import Vector3


Packet = namedtuple('Packet', 'ident data')


class DataDict(dict):
def __init__(self, **kwargs):
super(DataDict, self).__init__(**kwargs)
Expand Down Expand Up @@ -41,7 +37,6 @@ def push_packet(self, ident, data):
data_dict = DataDict(**data)
self.idents.append(ident)
self.datas.append(data_dict)
print(ident, data)


class SlotMock(object):
Expand Down
128 changes: 128 additions & 0 deletions tests/plugins/helpers/test_taskmanager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
from collections import defaultdict
from unittest import TestCase

from spock.plugins.helpers.taskmanager import TaskManager
from spock.plugins.tools.task import Task


class EventMock(object):
def __init__(self):
self.handlers = defaultdict(list)

def reg_event_handler(self, evt, handler):
self.handlers[evt].append(handler)

def emit(self, event, data):
to_remove = []
for handler in reversed(self.handlers[event]):
if handler(event, data):
to_remove.append(handler)
else:
raise AssertionError('%s %s' % (handler.__name__, handler))

for handler in to_remove:
self.handlers[event].remove(handler)


class PluginLoaderMock(object):
def provides(self, ident, obj):
self.provides_ident = ident
self.provides_obj = obj

def requires(self, plug_name):
assert plug_name == 'Event', 'Unexpected requirement %s' % plug_name
return EventMock()


class TaskManagerTest(TestCase):

def setUp(self):
ploader = PluginLoaderMock()
self.task_manager = TaskManager(ploader, {})
assert ploader.provides_ident == 'TaskManager'
assert isinstance(ploader.provides_obj, TaskManager)

def test_run_task(self):
def some_task(level):
if level <= 0:
raise StopIteration('We are done!', level)

# one event name
event, data = yield 'some_event'
self.assertEqual('some_event', event)
self.assertDictEqual({'some': 'data'}, data)

# multiple event names
event, data = yield 'other_event', 'never_emitted', 'this_neither'
self.assertEqual('other_event', event)
self.assertDictEqual({'other': 'data'}, data)

# event name + check
event, data = yield 'cool_event', lambda e, d: True
self.assertEqual('cool_event', event)
self.assertDictEqual({'more': 'data'}, data)

# subtask
event, data = yield some_task(level - 1)
self.assertEqual(None, event)
self.assertTupleEqual(('We are done!', level - 1), data)

raise StopIteration('We are done!', level)

evts = self.task_manager.event

def emit_them(level):
"""Recursively emit and check the events waited for by the task"""
if level <= 0:
return

self.assertIn('some_event', self.task_manager.event.handlers,
'Event not registered')

evts.emit('some_event', {'some': 'data'})
self.assertFalse(self.task_manager.event.handlers['some_event'],
'Emitted event not unregistered')

self.assertIn('other_event', self.task_manager.event.handlers,
'Event not registered')
self.assertIn('never_emitted', self.task_manager.event.handlers,
'Event not registered')
self.assertIn('this_neither', self.task_manager.event.handlers,
'Event not registered')

evts.emit('other_event', {'other': 'data'})
self.assertFalse(self.task_manager.event.handlers['other_Event'],
'Emitted event not unregistered')

self.assertIn('cool_event', self.task_manager.event.handlers,
'Event not registered')

evts.emit('cool_event', {'more': 'data'})
self.assertFalse(self.task_manager.event.handlers['cool_event'],
'Emitted event not unregistered')

emit_them(level - 1)

done = [False] # because we can't use nonlocal in Python 2.7

test_case = self # to make flake8 happy

class SomeParent(object):
def on_success(self, data):
done[0] = True

def on_error(self, e):
test_case.fail('Task failed with %s: %s'
% (e.__class__.__name__, e.args))

levels = 3

task = some_task(levels)
parent = SomeParent()

ret = self.task_manager.run_task(task, parent)
self.assertIsInstance(ret, Task)

emit_them(levels)

assert done[0]

0 comments on commit 274b3fa

Please sign in to comment.