Skip to content

Commit

Permalink
Merge pull request #393 from PyPlanet/feature/354
Browse files Browse the repository at this point in the history
[FEATURE][BREAKING] Make the signal manager app context aware. Prepar…
  • Loading branch information
tomvlk committed Jun 26, 2017
2 parents cfa2ed4 + 986ba8b commit a67f374
Show file tree
Hide file tree
Showing 30 changed files with 344 additions and 117 deletions.
24 changes: 22 additions & 2 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
Changelog
=========

0.5.0 (unreleased)
------------------

Core
~~~~

* **Breaking**: App context aware signal manager.

This is a *deprecation* for the property ``signal_manager`` of the ``instance``. This means that ``self.instance.signal_manager``
needs to be replaced by ``self.context.signals`` to work with the life cycle changes in 0.8.0.
More info: https://github.com/PyPlanet/PyPlanet/issues/392

**The old way will break your app from version 0.8.0**

Apps
~~~~

* Improvement: Applied context aware signal manager everywhere.


0.4.3
-----

Expand Down Expand Up @@ -70,7 +90,7 @@ Core
This is a *deprecation* for the method ``get_player_data``. From now on, use the ``get_all_player_data`` or the better ``get_per_player_data``.
More info: :doc:`/api/views`.

**The old method will not be called from 0.6.0**
**The old method will not be called from 0.7.0**

* Feature: UI Overhaul is done! We replaced the whole GUI for a nicer, simple and modern one! With large inspiration of LongLife's posted image (https://github.com/PyPlanet/PyPlanet/issues/223).
* Feature: UI Update queue, Don't make the dedicated hot by sending UI updates in realtime, but queue up and sent every 0,25 seconds. (Performance)
Expand All @@ -93,7 +113,7 @@ Apps
This requires a config change:
Change ``pyplanet.apps.contrib.mapinfo`` into ``pyplanet.apps.contrib.info`` and you are done!

**The old app will be removed in 0.6.0**
**The old app will be removed in 0.7.0**

* Feature: **New App**: Shootmania Royal Dynamic Point Limit is here! Add it with ``pyplanet.apps.contrib.dynamic_points``.
* Feature: **New App**: Trackmania Checkpoint/Sector time widget is here! Add it with ``pyplanet.apps.contrib.sector_times``.
Expand Down
2 changes: 2 additions & 0 deletions docs/source/apps/lifecycle.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Life Cycle
Currently the life cycle **isn't fully implemented**. Only the ``on_init`` and ``on_start`` will be called, but please
prepare your app to support the following life cycle methods.

To support the life cycle in the future, use the ``self.context.signals`` instead of the ``self.instance.signal_manager``

on_init
~~~~~~~

Expand Down
90 changes: 66 additions & 24 deletions pyplanet/apps/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from collections import OrderedDict

from pyplanet.utils.toposort import toposort
from pyplanet.apps.config import AppConfig
from pyplanet.apps.config import AppConfig, AppState
from pyplanet.core.exceptions import ImproperlyConfigured


Expand All @@ -20,13 +20,14 @@ class Apps:
def __init__(self, instance):
"""
Initiate registry with pre-loaded apps.
:param instance: Instance of the controller.
:type instance: pyplanet.core.instance.Instance
"""
self.instance = instance

self.apps = OrderedDict()
self.unloaded_apps = OrderedDict()

# Set ready states.
self.apps_ready = self.ready = False
Expand All @@ -35,7 +36,7 @@ def __init__(self, instance):
self._lock = threading.Lock()

# Listen to events
self.instance.signal_manager.listen('contrib.mode:script_mode_changed', self._on_mode_change)
self.instance.signals.listen('contrib.mode:script_mode_changed', self._on_mode_change)

def populate(self, apps, in_order=False):
"""
Expand Down Expand Up @@ -63,6 +64,9 @@ def populate(self, apps, in_order=False):
# Inject apps instance into app itself.
app.apps = self

# Set state on app.
app.state = AppState.UNLOADED

# Get dependencies to other apps.
deps = getattr(app, 'app_dependencies', list())
if not type(deps) is list:
Expand Down Expand Up @@ -95,26 +99,57 @@ def populate(self, apps, in_order=False):

async def check(self):
"""
Check and remove unsupported apps based on the current game and script mode.
Check and remove unsupported apps based on the current game and script mode. Also loads unloaded apps and try
if the mode and game does support it again.
"""
apps_dict = OrderedDict()
for label, app in self.apps.items():
if not app.is_game_supported('trackmania' if self.instance.game.game == 'tm' else 'shootmania'):
logging.info('Unloading app {}. Doesn\'t support the current game!'.format(label))
await app.on_stop()
await app.on_destroy()
del app

elif not app.is_mode_supported(await self.instance.mode_manager.get_current_script()):
logging.info('Unloading app {}. Doesn\'t support the current script mode!'.format(label))
await app.on_stop()
await app.on_destroy()
del app

else:
apps_dict[label] = app

self.apps = apps_dict
# Check if disabled apps can be loaded again.
# TODO: ACTIVATE THIS AFTER SIGNAL MANAGER DEPRECATION IS REMOVED!
# for app_label, app_module in self.unloaded_apps.items():
# try:
# # Load the module and initiate by creating the app class instance.
# self.populate([app_module], in_order=True)
# if app_label not in self.apps:
# raise Exception() # Flow control, stop executing restart of app.
#
# # Init + start the app again.
# await self.apps[app_label].on_init()
# await self.apps[app_label].on_start()
#
# # Clear the label from the unloaded list.
# del self.unloaded_apps[app_label]
#
# logging.info('(Re)loaded app {} as it seems that it supports this game/mode again.'.format(app_label))
# except Exception as e:
# logging.debug('Can\'t start app {}, Got exception with error: {}'.format(app_label, str(e)))
# # logging.exception(e)
# # Some apps can't be reloaded.
# pass

# Check enabled apps, and replace the apps dictionary with the up-to-date apps.
# TODO: Same for this line, activate after life cycle has been fully implemented.
# script_name = await self.instance.mode_manager.get_current_script(refresh=True)
# apps_dict = OrderedDict()
# for label, app in self.apps.items():
# if not app.is_game_supported('trackmania' if self.instance.game.game == 'tm' else 'shootmania'):
# logging.info('Unloading app {}. Doesn\'t support the current game!'.format(label))
# await app.on_stop()
# await app.on_destroy()
#
# self.unloaded_apps[label] = app.module.__name__
# del app
#
# elif not app.is_mode_supported(script_name):
# logging.info('Unloading app {}. Doesn\'t support the current script mode!'.format(label))
# await app.on_stop()
# await app.on_destroy()
#
# self.unloaded_apps[label] = app.module.__name__
# del app
#
# else:
# apps_dict[label] = app
#
# self.apps = apps_dict

async def discover(self):
"""
Expand All @@ -126,24 +161,31 @@ async def discover(self):
self.instance.db.registry.init_app(app)

# Discover signals.
self.instance.signal_manager.init_app(app)
self.instance.signals.init_app(app)

# Finishing signal manager.
self.instance.signal_manager.finish_reservations()
self.instance.signals.finish_reservations()

async def init(self):
"""
This method will initiate all apps in order and in series.
"""
if self.apps_ready:
raise Exception('Apps are not yet ordered!')
for label, app in self.apps.items():
await app.on_init()

async def start(self):
"""
This method will start all apps that are previously initiated.
"""
if self.apps_ready:
raise Exception('Apps are not yet ordered!')

# The apps are in order, lets loop over them.
for label, app in self.apps.items():
await app.on_start()
app.state = AppState.LOADED
logging.debug('App is ready: {}'.format(label))
logging.info('Apps successfully started!')

Expand Down
21 changes: 19 additions & 2 deletions pyplanet/apps/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ def __init__(self, app):
Setting Contrib Component. See :doc:`Setting Classes </api/contrib_setting>`.
"""

self.signals = app.instance.signals.create_app_manager(app)
"""
Signal manager. See :doc:`Signal Manager </api/core_events>`.
"""

async def on_destroy(self):
await self.ui.on_destroy()
await self.signals.on_destroy()


class AppState:
UNLOADED = 0
LOADED = 1


class AppConfig:
"""
Expand Down Expand Up @@ -130,14 +144,17 @@ def __init__(self, app_name, app_module, instance):
self.instance = instance
self.context = _AppContext(self)

# State of app.
self.state = AppState.UNLOADED

def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self.label)

@property
def ui(self):
"""
.. deprecated:: 0.0.1
Use ``context.ui`` instead.
Use ``context.ui`` instead. Will be removed in 0.6.0!
"""
logging.warning(DeprecationWarning(
'AppConfig.ui is deprecated, use AppConfig.context.ui instead.'
Expand Down Expand Up @@ -178,7 +195,7 @@ async def on_destroy(self):
"""
On destroy is being called when unloading the app from the memory.
"""
pass
await self.context.on_destroy()

###################################################################################################

Expand Down
14 changes: 7 additions & 7 deletions pyplanet/apps/contrib/dedimania/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,16 @@ async def on_start(self):
await self.reload_settings()

# Register signals
self.instance.signal_manager.listen(mp_signals.map.map_begin, self.map_begin)
self.instance.signal_manager.listen(mp_signals.map.map_start, self.map_start)
self.instance.signal_manager.listen(mp_signals.map.map_end, self.map_end)
self.context.signals.listen(mp_signals.map.map_begin, self.map_begin)
self.context.signals.listen(mp_signals.map.map_start, self.map_start)
self.context.signals.listen(mp_signals.map.map_end, self.map_end)

# TODO Activate after server bug has fixed!
# self.instance.signal_manager.listen(mp_signals.flow.podium_start, self.podium_start)
# self.context.signals.listen(mp_signals.flow.podium_start, self.podium_start)

self.instance.signal_manager.listen(tm_signals.finish, self.player_finish)
self.instance.signal_manager.listen(mp_signals.player.player_connect, self.player_connect)
self.instance.signal_manager.listen(mp_signals.player.player_disconnect, self.player_disconnect)
self.context.signals.listen(tm_signals.finish, self.player_finish)
self.context.signals.listen(mp_signals.player.player_connect, self.player_connect)
self.context.signals.listen(mp_signals.player.player_disconnect, self.player_disconnect)

# Change round results widget location.

Expand Down
8 changes: 4 additions & 4 deletions pyplanet/apps/contrib/dynamic_points/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ def is_mode_supported(self, mode):
return 'royal' in mode.lower()

async def on_start(self):
self.instance.signal_manager.listen(mp_signals.player.player_connect, self.on_changes)
self.instance.signal_manager.listen(mp_signals.player.player_disconnect, self.on_changes)
self.instance.signal_manager.listen(mp_signals.player.player_info_changed, self.on_changes)
self.instance.signal_manager.listen(mp_signals.map.map_start, self.map_start)
self.context.signals.listen(mp_signals.player.player_connect, self.on_changes)
self.context.signals.listen(mp_signals.player.player_disconnect, self.on_changes)
self.context.signals.listen(mp_signals.player.player_info_changed, self.on_changes)
self.context.signals.listen(mp_signals.map.map_start, self.map_start)

await self.context.setting.register(
self.setting_enable_dynamic_points, self.setting_min_points, self.setting_max_points,
Expand Down
8 changes: 4 additions & 4 deletions pyplanet/apps/contrib/info/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ def __init__(self, *args, **kwargs):
self.update_pending = False

async def on_start(self):
self.instance.signal_manager.listen(mp_signals.map.map_begin, self.map_begin)
self.instance.signal_manager.listen(mp_signals.player.player_connect, self.player_connect)
self.instance.signal_manager.listen(mp_signals.player.player_disconnect, self.any_change)
self.instance.signal_manager.listen(mp_signals.player.player_info_changed, self.any_change)
self.context.signals.listen(mp_signals.map.map_begin, self.map_begin)
self.context.signals.listen(mp_signals.player.player_connect, self.player_connect)
self.context.signals.listen(mp_signals.player.player_disconnect, self.any_change)
self.context.signals.listen(mp_signals.player.player_info_changed, self.any_change)

# Move the multilapinfo a bit. (Only Trackmania).
self.instance.ui_manager.properties.set_attribute('multilap_info', 'pos', '107., 88., 5.')
Expand Down
2 changes: 1 addition & 1 deletion pyplanet/apps/contrib/jukebox/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ async def on_start(self):
)

# Register callback.
self.instance.signal_manager.listen(mp_signals.flow.podium_start, self.podium_start)
self.context.signals.listen(mp_signals.flow.podium_start, self.podium_start)

def insert_map(self, player, map):
self.jukebox = [{'player': player, 'map': map}] + self.jukebox
Expand Down
6 changes: 3 additions & 3 deletions pyplanet/apps/contrib/karma/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ async def on_start(self):
await self.instance.command_manager.register(Command(command='whokarma', target=self.show_map_list))

# Register signals.
self.instance.signal_manager.listen(mp_signals.map.map_begin, self.map_begin)
self.instance.signal_manager.listen(mp_signals.player.player_chat, self.player_chat)
self.instance.signal_manager.listen(mp_signals.player.player_connect, self.player_connect)
self.context.signals.listen(mp_signals.map.map_begin, self.map_begin)
self.context.signals.listen(mp_signals.player.player_chat, self.player_chat)
self.context.signals.listen(mp_signals.player.player_connect, self.player_connect)

await self.context.setting.register(self.setting_finishes_before_voting)

Expand Down
12 changes: 6 additions & 6 deletions pyplanet/apps/contrib/live_rankings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ def __init__(self, *args, **kwargs):

async def on_start(self):
# Register signals
self.instance.signal_manager.listen(mp_signals.map.map_start, self.map_start)
self.instance.signal_manager.listen(tm_signals.finish, self.player_finish)
self.instance.signal_manager.listen(tm_signals.waypoint, self.player_waypoint)
self.instance.signal_manager.listen(mp_signals.player.player_connect, self.player_connect)
self.instance.signal_manager.listen(tm_signals.give_up, self.player_giveup)
self.instance.signal_manager.listen(tm_signals.scores, self.scores)
self.context.signals.listen(mp_signals.map.map_start, self.map_start)
self.context.signals.listen(tm_signals.finish, self.player_finish)
self.context.signals.listen(tm_signals.waypoint, self.player_waypoint)
self.context.signals.listen(mp_signals.player.player_connect, self.player_connect)
self.context.signals.listen(tm_signals.give_up, self.player_giveup)
self.context.signals.listen(tm_signals.scores, self.scores)

# Make sure we move the rounds_scores and other gui elements.
self.instance.ui_manager.properties.set_attribute('round_scores', 'pos', '-126.5 87. 150.')
Expand Down
6 changes: 3 additions & 3 deletions pyplanet/apps/contrib/local_records/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ async def on_start(self):
await self.instance.command_manager.register(Command(command='records', target=self.show_records_list))

# Register signals
self.instance.signal_manager.listen(mp_signals.map.map_begin, self.map_begin)
self.instance.signal_manager.listen(tm_signals.finish, self.player_finish)
self.instance.signal_manager.listen(mp_signals.player.player_connect, self.player_connect)
self.context.signals.listen(mp_signals.map.map_begin, self.map_begin)
self.context.signals.listen(tm_signals.finish, self.player_finish)
self.context.signals.listen(mp_signals.player.player_connect, self.player_connect)

await self.context.setting.register(self.setting_chat_announce, self.setting_record_limit)

Expand Down
4 changes: 2 additions & 2 deletions pyplanet/apps/contrib/mapinfo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ async def output_deprecated(self):
await self.instance.chat.execute(*msg)

async def on_start(self):
self.instance.signal_manager.listen(mp_signals.map.map_begin, self.map_begin)
self.instance.signal_manager.listen(mp_signals.player.player_connect, self.player_connect)
self.context.signals.listen(mp_signals.map.map_begin, self.map_begin)
self.context.signals.listen(mp_signals.player.player_connect, self.player_connect)

# Move the multilapinfo a bit. (Only Trackmania).
self.instance.ui_manager.properties.set_attribute('multilap_info', 'pos', '107., 88., 5.')
Expand Down
8 changes: 4 additions & 4 deletions pyplanet/apps/contrib/sector_times/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ def __init__(self, *args, **kwargs):
self.widget = SectorTimesWidget(self)

async def on_start(self):
self.instance.signal_manager.listen(mp_signals.player.player_connect, self.player_connect)
self.instance.signal_manager.listen(mp_signals.map.map_start, self.map_start)
self.instance.signal_manager.listen(mp_signals.flow.podium_start, self.podium_start)
self.context.signals.listen(mp_signals.player.player_connect, self.player_connect)
self.context.signals.listen(mp_signals.map.map_start, self.map_start)
self.context.signals.listen(mp_signals.flow.podium_start, self.podium_start)

self.instance.signal_manager.listen(pyplanet_start_after, self.on_after_start)
self.context.signals.listen(pyplanet_start_after, self.on_after_start)

async def on_after_start(self, *args, **kwargs):
await asyncio.sleep(1)
Expand Down

0 comments on commit a67f374

Please sign in to comment.