Skip to content

Commit

Permalink
Merge branch 'dev' into audio-new-feature-dev
Browse files Browse the repository at this point in the history
  • Loading branch information
qcapen committed Aug 26, 2016
2 parents 6d4af13 + 39ce20d commit 33ddbac
Show file tree
Hide file tree
Showing 31 changed files with 595 additions and 125 deletions.
39 changes: 39 additions & 0 deletions mpf/core/async_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""Base class for asyncio modes."""
import abc
import asyncio

from mpf.core.mode import Mode


class AsyncMode(Mode, metaclass=abc.ABCMeta):

"""Base class for asyncio modes."""

def __init__(self, machine, config, name, path):
"""Initialise async mode."""
super().__init__(machine, config, name, path)

self._task = None

def _started(self):
"""Start main task."""
super()._started()

self._task = self.machine.clock.loop.create_task(self._run())

def _stopped(self):
"""Cancel task."""
super()._stopped()

self._task.cancel()

@abc.abstractmethod
@asyncio.coroutine
def _run(self):
"""Main task which runs as long as the mode is active.
Overwrite this function in your mode.
Its automatically canceled when the mode stops. You can catch CancelError to handle mode stop.
"""
pass
2 changes: 0 additions & 2 deletions mpf/core/ball_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ def __init__(self, machine):
self.log.debug("Loading the BallController")
self.delay = DelayManager(self.machine.delayRegistry)

self.game = None

self.num_balls_known = -999

# register for events
Expand Down
8 changes: 8 additions & 0 deletions mpf/core/config_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ def __init__(self, machine):
if not ConfigValidator.config_spec:
ConfigValidator.load_config_spec()

@classmethod
def load_mode_config_spec(cls, mode_string, config_spec):
"""Load config specs for a mode."""
if '_mode_settings' not in cls.config_spec:
cls.config_spec['_mode_settings'] = {}
if mode_string not in cls.config_spec['_mode_settings']:
cls.config_spec['_mode_settings'][mode_string] = YamlInterface.process(config_spec)

@classmethod
def load_config_spec(cls, config_spec=None):
"""Load config specs."""
Expand Down
78 changes: 61 additions & 17 deletions mpf/core/events.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
"""Classes for the EventManager and QueuedEvents."""

import logging
from collections import deque
from collections import deque, namedtuple
import uuid

import asyncio
from functools import partial

EventHandlerKey = namedtuple("EventHandlerKey", ["key", "event"])
RegisteredHandler = namedtuple("RegisteredHandler", ["callback", "priority", "kwargs", "key"])
PostedEvent = namedtuple("PostedEvent", ["event", "type", "callback", "kwargs"])


class EventManager(object):

Expand All @@ -13,7 +20,7 @@ def __init__(self, machine):
"""Initialise EventManager."""
self.log = logging.getLogger("Events")
self.machine = machine
self.registered_handlers = {}
self.registered_handlers = {} # type: {str: [RegisteredHandler]}
self.event_queue = deque([])
self.callback_queue = deque([])

Expand Down Expand Up @@ -71,7 +78,7 @@ def add_handler(self, event, handler, priority=1, **kwargs):
# An event 'handler' in our case is a tuple with 4 elements:
# the handler method, priority, dict of kwargs, & uuid key

self.registered_handlers[event].append((handler, priority, kwargs, key))
self.registered_handlers[event].append(RegisteredHandler(handler, priority, kwargs, key))
if self.debug:
try:
self.log.debug("Registered %s as a handler for '%s', priority: %s, "
Expand All @@ -83,9 +90,9 @@ def add_handler(self, event, handler, priority=1, **kwargs):
# Sort the handlers for this event based on priority. We do it now
# so the list is pre-sorted so we don't have to do that with each
# event post.
self.registered_handlers[event].sort(key=lambda x: x[1], reverse=True)
self.registered_handlers[event].sort(key=lambda x: x.priority, reverse=True)

return key
return EventHandlerKey(key, event)

def replace_handler(self, event, handler, priority=1, **kwargs):
"""Check to see if a handler (optionally with kwargs) is registered for an event and replaces it if so.
Expand Down Expand Up @@ -171,20 +178,21 @@ def remove_handler_by_event(self, event, handler):
for event in events_to_delete_if_empty:
self._remove_event_if_empty(event)

def remove_handler_by_key(self, key):
def remove_handler_by_key(self, key: EventHandlerKey):
"""Remove a registered event handler by key.
Args:
key: The key of the handler you want to remove
"""
if key.event not in self.registered_handlers:
return
events_to_delete_if_empty = []
for event, handler_list in self.registered_handlers.items():
for handler_tup in handler_list[:]: # copy via slice
if handler_tup[3] == key:
handler_list.remove(handler_tup)
if self.debug:
self.log.debug("Removing method %s from event %s", (str(handler_tup[0]).split(' '))[2], event)
events_to_delete_if_empty.append(event)
for handler_tup in self.registered_handlers[key.event][:]: # copy via slice
if handler_tup.key == key.key:
self.registered_handlers[key.event].remove(handler_tup)
if self.debug:
self.log.debug("Removing method %s from event %s", (str(handler_tup[0]).split(' '))[2], key.event)
events_to_delete_if_empty.append(key.event)
for event in events_to_delete_if_empty:
self._remove_event_if_empty(event)

Expand All @@ -210,6 +218,42 @@ def _remove_event_if_empty(self, event):
self.log.debug("Removing event %s since there are no more"
" handlers registered for it", event)

def wait_for_event(self, event_name: str):
"""Wait for event."""
return self.wait_for_any_event([event_name])

def wait_for_any_event(self, event_names: [str]):
"""Wait for any event from event_names."""
future = asyncio.Future(loop=self.machine.clock.loop)
keys = []
for event_name in event_names:
keys.append(self.add_handler(event_name, partial(self._wait_handler,
_future=future,
_keys=keys,
event=event_name)))
return future

def wait_for_event_group_race(self, event_groups: {str: [str]}):
"""Wait for any event from event_group and return the key of the group."""
future = asyncio.Future(loop=self.machine.clock.loop)
keys = []
for group, event_names in event_groups.items():
for event_name in event_names:
keys.append(self.add_handler(event_name, partial(self._wait_handler,
_future=future,
_keys=keys,
group=group,
event=event_name)))
return future

def _wait_handler(self, _future: asyncio.Future, _keys: [str], **kwargs):
for key in _keys:
self.remove_handler_by_key(key)

if _future.cancelled():
return
_future.set_result(result=kwargs)

def does_event_exist(self, event_name):
"""Check to see if any handlers are registered for the event name that is passed.
Expand Down Expand Up @@ -361,7 +405,7 @@ def _post(self, event, ev_type, callback, **kwargs):
if not self.event_queue and hasattr(self.machine.clock, "loop"):
self.machine.clock.loop.call_soon(self.process_event_queue)

self.event_queue.append((event, ev_type, callback, kwargs))
self.event_queue.append(PostedEvent(event, ev_type, callback, kwargs))
if self.debug:
self.log.debug("============== EVENTS QUEUE =============")
for event in list(self.event_queue):
Expand Down Expand Up @@ -391,20 +435,20 @@ def _process_event(self, event, ev_type, callback=None, **kwargs):

# merge the post's kwargs with the registered handler's kwargs
# in case of conflict, posts kwargs will win
merged_kwargs = dict(list(handler[2].items()) + list(kwargs.items()))
merged_kwargs = dict(list(handler.kwargs.items()) + list(kwargs.items()))

# log if debug is enabled and this event is not the timer tick
if self.debug:
try:
self.log.debug("%s (priority: %s) responding to event '%s'"
" with args %s",
(str(handler[0]).split(' '))[2], handler[1],
(str(handler.callback).split(' ')), handler.priority,
event, merged_kwargs)
except IndexError:
pass

# call the handler and save the results
result = handler[0](**merged_kwargs)
result = handler.callback(**merged_kwargs)

# If whatever handler we called returns False, we stop
# processing the remaining handlers for boolean or queue events
Expand Down
2 changes: 1 addition & 1 deletion mpf/core/logic_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ def __init__(self, machine: MachineController, name: str, player: Player, config
elif self.config['direction'] == 'up' and self.hit_value < 0:
self.hit_value *= -1

if not self.config['persist_state']:
if not self.config['persist_state'] or not self.player.is_player_var(self.config['player_variable']):
self.player[self.config['player_variable']] = self.config['starting_count']

def add_event_handlers(self):
Expand Down
4 changes: 2 additions & 2 deletions mpf/core/machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ class MachineController(object):
used to launch mpf.py.
config(dict): A dictionary of machine's configuration settings, merged from
various sources.
game(Game): the current game
game(mpf.modes.game.code.game.Game): the current game
machine_path: The root path of this machine_files folder
plugins:
scriptlets:
hardware_platforms:
events(EventManager):
events(mpf.core.events.EventManager):
"""

Expand Down
22 changes: 20 additions & 2 deletions mpf/core/mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,18 @@ class Mode(object):

"""Parent class for in-game mode code."""

def __init__(self, machine, config, name, path):
"""Initialise mode."""
def __init__(self, machine, config: dict, name: str, path):
"""Initialise mode.
Args:
machine(mpf.core.machine.MachineController): the machine controller
config: config dict for mode
name: name of mode
path: path of mode
Returns:
"""
self.machine = machine
self.config = config
self.name = name.lower()
Expand Down Expand Up @@ -75,6 +85,14 @@ def __init__(self, machine, config, name, path):

self.mode_init()

@staticmethod
def get_config_spec():
"""Return config spec for mode_settings."""
return '''
__valid_in__: mode
__allow_others__:
'''

def __repr__(self):
"""Return string representation."""
return '<Mode.{}>'.format(self.name)
Expand Down
21 changes: 13 additions & 8 deletions mpf/core/mode_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ def _load_mode_config(self, mode_string):

return config

def _load_mode_config_spec(self, mode_string, mode_class):
self.machine.config_validator.load_mode_config_spec(mode_string, mode_class.get_config_spec())

def _load_mode(self, mode_string):
"""Load a mode, reads in its config, and creates the Mode object.
Expand All @@ -173,7 +176,7 @@ def _load_mode(self, mode_string):

config = self._load_mode_config(mode_string)

self.machine.config_validator.validate_config("mode", config['mode'])
config['mode'] = self.machine.config_validator.validate_config("mode", config['mode'])

# Figure out where the code is for this mode.
if config['mode']['code']:
Expand All @@ -183,9 +186,7 @@ def _load_mode(self, mode_string):
self._machine_mode_folders[mode_string] + '.code.' +
config['mode']['code'].split('.')[0])

mode_object = getattr(i, config['mode']['code'].split('.')[1])(
self.machine, config,
self._machine_mode_folders[mode_string], mode_path)
mode_class = getattr(i, config['mode']['code'].split('.')[1])

if self.debug:
self.log.debug("Loaded code from %s",
Expand All @@ -199,8 +200,7 @@ def _load_mode(self, mode_string):
self._mpf_mode_folders[mode_string] + '.code.' +
config['mode']['code'].split('.')[0])

mode_object = getattr(i, config['mode']['code'].split('.')[1])(
self.machine, config, mode_string, mode_path)
mode_class = getattr(i, config['mode']['code'].split('.')[1])

if self.debug:
self.log.debug("Loaded code from %s",
Expand All @@ -209,11 +209,16 @@ def _load_mode(self, mode_string):
config['mode']['code'].split('.')[0])

else: # no code specified, so using the default Mode class
mode_object = Mode(self.machine, config, mode_string, mode_path)
mode_class = Mode
if self.debug:
self.log.debug("Loaded default Mode() class code.")

return mode_object
self._load_mode_config_spec(mode_string, mode_class)

config['mode_settings'] = self.machine.config_validator.validate_config(
"_mode_settings:{}".format(mode_string), config.get('mode_settings', None))

return mode_class(self.machine, config, mode_string, mode_path)

def _build_mode_folder_dicts(self):
self._mpf_mode_folders = (
Expand Down
18 changes: 18 additions & 0 deletions mpf/core/mpf_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""Base class for MPF controllers."""
import abc


class MpfController(metaclass=abc.ABCMeta):

"""Base class for MPF controllers."""

def __init__(self, machine):
"""Initialise controller.
Args:
machine(mpf.core.machine.MachineController): the machine controller
Returns:
"""
self.machine = machine
Loading

0 comments on commit 33ddbac

Please sign in to comment.