<commit_before>"""Contains the MpfMc base class, which is the main App instance for the
mpf-mc."""
import importlib
import os
import queue
import sys
import threading
import time
import logging

import gc
import weakref

from pkg_resources import iter_entry_points

from kivy.app import App
from kivy.clock import Clock
from kivy.resources import resource_add_path
from kivy.logger import Logger

from kivy.config import Config

import mpf
from mpf.core.config_processor import ConfigProcessor as MpfConfigProcessor
from mpf._version import __version__ as __mpfversion__
from mpf.core.case_insensitive_dict import CaseInsensitiveDict
from mpf.core.config_validator import ConfigValidator
from mpf.core.events import EventManager
from mpf.core.player import Player
from mpf.core.device_manager import DeviceCollection
from mpf.core.utility_functions import Util

import mpfmc
from mpfmc._version import __version__
from mpfmc.assets.video import VideoAsset
from mpfmc.core.bcp_processor import BcpProcessor
from mpfmc.core.config_processor import ConfigProcessor
from mpfmc.core.mode_controller import ModeController
from mpfmc.uix.transitions import TransitionManager
from mpfmc.uix.effects import EffectsManager
from mpfmc.core.config_collection import create_config_collections
from mpfmc.assets.image import ImageAsset
from mpfmc.assets.bitmap_font import BitmapFontAsset
from mpfmc.core.dmd import Dmd, RgbDmd
from mpfmc.core.assets import ThreadedAssetManager
from mpfmc.core.mc_placeholder_manager import McPlaceholderManager
from mpfmc.core.mc_settings_controller import McSettingsController

try:
    from mpfmc.core.audio import SoundSystem
    from mpfmc.assets.sound import SoundAsset
except ImportError:
    SoundSystem = None
    SoundAsset = None
    logging.warning("mpfmc.core.audio library could not be loaded. Audio "
                    "features will not be available")

# The following line is needed to allow mpfmc modules to use the
# getLogger(name) method
logging.Logger.manager.root = Logger


# pylint: disable-msg=too-many-instance-attributes
class MpfMc(App):

    """Kivy app for the mpf media controller."""

    # pylint: disable-msg=too-many-statements
    def __init__(self, options, machine_path,
                 thread_stopper=None, **kwargs):

        self.log = logging.getLogger('mpfmc')
        self.log.info("Mission Pinball Framework Media Controller v%s", __version__)
        self.log.info("Mission Pinball Framework Game Engine v%s", __mpfversion__)

        if (__version__.split('.')[0] != __mpfversion__.split('.')[0] or
                __version__.split('.')[1] != __mpfversion__.split('.')[1]):

            self.log.error("MPF MC and MPF Game engines must be same "
                           "major.minor versions. You have MPF v%s and MPF-MC"
                           " v%s", __mpfversion__, __version__)

            raise ValueError("MPF MC and MPF Game engines must be same "
                             "major.minor versions. You have MPF v{} and MPF-MC"
                             " v{}".format(__mpfversion__, __version__))

        super().__init__(**kwargs)

        self.options = options
        self.log.info("Machine path: %s", machine_path)
        self.machine_path = machine_path
        # load machine into path to load modules
        if machine_path not in sys.path:
            sys.path.append(machine_path)
        self.config_validator = ConfigValidator(self)
        self.mpf_config_processor = MpfConfigProcessor(self.config_validator)
        self.machine_config = self._load_config()

        self.clock = Clock
        # pylint: disable-msg=protected-access
        self.log.info("Starting clock at %sHz", Clock._max_fps)
        self._boot_holds = set()
        self.is_init_done = threading.Event()
        self.mpf_path = os.path.dirname(mpf.__file__)
        self.modes = CaseInsensitiveDict()
        self.player_list = list()
        self.player = None
        self.num_players = 0
        self.bcp_client_connected = False
        self.placeholder_manager = McPlaceholderManager(self)
        self.settings = McSettingsController(self)

        self.animation_configs = dict()
        self.active_slides = dict()
        self.custom_code = list()

        self.register_boot_hold('init')
        self.displays = DeviceCollection(self, "displays", "displays")
        self.machine_vars = CaseInsensitiveDict()
        self.machine_var_monitor = False
        self.monitors = dict()
        self.targets = dict()
        """Dict which contains all the active slide frames in the machine that
        a slide can target. Will always contain an entry called 'default'
        which will be used if a slide doesn't specify targeting.
        """

        self.keyboard = None
        self.dmds = []
        self.rgb_dmds = []
        self.crash_queue = queue.Queue()
        self.ticks = 0
        self.start_time = 0
        self.debug_refs = []

        if thread_stopper:
            self.thread_stopper = thread_stopper
        else:
            self.thread_stopper = threading.Event()

        # Core components
        self.events = EventManager(self)
        self.mode_controller = ModeController(self)
        create_config_collections(self, self.machine_config['mpf-mc']['config_collections'])

        self.config_processor = ConfigProcessor(self)
        self.transition_manager = TransitionManager(self)
        self.effects_manager = EffectsManager(self)

        self._set_machine_path()

        self._load_font_paths()

        # Initialize the sound system (must be done prior to creating the AssetManager).
        # If the sound system is not available, do not load any other sound-related modules.
        if SoundSystem is None or self.options.get("no_sound"):
            self.sound_system = None
        else:
            self.sound_system = SoundSystem(self)
            if self.sound_system.audio_interface is None:
                self.sound_system = None

        self.asset_manager = ThreadedAssetManager(self)
        self.bcp_processor = BcpProcessor(self)

        # Asset classes
        ImageAsset.initialize(self)
        VideoAsset.initialize(self)
        BitmapFontAsset.initialize(self)

        self._initialise_sound_system()

        self.clock.schedule_interval(self._check_crash_queue, 1)

        self.events.add_handler("client_connected", self._create_dmds)
        self.events.add_handler("player_turn_start", self.player_start_turn)

        self.create_machine_var('mpfmc_ver', __version__)
        # force setting it here so we have it before MPF connects
        self.receive_machine_var_update('mpfmc_ver', __version__, 0, True)

    def load_external_platform_config_specs(self):
        """Load config spec for external platforms."""
        for platform_entry in iter_entry_points(group='mpf.platforms'):
            config_spec = platform_entry.load().get_config_spec()

            if config_spec:
                # add specific config spec if platform has any
                self.config_validator.load_device_config_spec(config_spec[0], config_spec[1])

    def track_leak_reference(self, element):
        """Track elements to find leaks."""
        if not self.options["production"]:
            self.debug_refs.append(weakref.ref(element))
            # cleanup all dead references
            self.debug_refs = [element for element in self.debug_refs if element()]

    @staticmethod
    def _preprocess_config(config):
        kivy_config = config['kivy_config']

        try:
            kivy_config['graphics'].update(config['window'])
        except KeyError:
            pass<commit_msg>honour -a and -A. fix https://github.com/missionpinball/mpf/issues/1274<commit_after>"""Contains the MpfMc base class, which is the main App instance for the
mpf-mc."""
import importlib
import os
import queue
import sys
import threading
import time
import logging

import gc
import weakref

from pkg_resources import iter_entry_points

from kivy.app import App
from kivy.clock import Clock
from kivy.resources import resource_add_path
from kivy.logger import Logger

from kivy.config import Config

import mpf
from mpf.core.config_processor import ConfigProcessor as MpfConfigProcessor
from mpf._version import __version__ as __mpfversion__
from mpf.core.case_insensitive_dict import CaseInsensitiveDict
from mpf.core.config_validator import ConfigValidator
from mpf.core.events import EventManager
from mpf.core.player import Player
from mpf.core.device_manager import DeviceCollection
from mpf.core.utility_functions import Util

import mpfmc
from mpfmc._version import __version__
from mpfmc.assets.video import VideoAsset
from mpfmc.core.bcp_processor import BcpProcessor
from mpfmc.core.config_processor import ConfigProcessor
from mpfmc.core.mode_controller import ModeController
from mpfmc.uix.transitions import TransitionManager
from mpfmc.uix.effects import EffectsManager
from mpfmc.core.config_collection import create_config_collections
from mpfmc.assets.image import ImageAsset
from mpfmc.assets.bitmap_font import BitmapFontAsset
from mpfmc.core.dmd import Dmd, RgbDmd
from mpfmc.core.assets import ThreadedAssetManager
from mpfmc.core.mc_placeholder_manager import McPlaceholderManager
from mpfmc.core.mc_settings_controller import McSettingsController

try:
    from mpfmc.core.audio import SoundSystem
    from mpfmc.assets.sound import SoundAsset
except ImportError:
    SoundSystem = None
    SoundAsset = None
    logging.warning("mpfmc.core.audio library could not be loaded. Audio "
                    "features will not be available")

# The following line is needed to allow mpfmc modules to use the
# getLogger(name) method
logging.Logger.manager.root = Logger


# pylint: disable-msg=too-many-instance-attributes
class MpfMc(App):

    """Kivy app for the mpf media controller."""

    # pylint: disable-msg=too-many-statements
    def __init__(self, options, machine_path,
                 thread_stopper=None, **kwargs):

        self.log = logging.getLogger('mpfmc')
        self.log.info("Mission Pinball Framework Media Controller v%s", __version__)
        self.log.info("Mission Pinball Framework Game Engine v%s", __mpfversion__)

        if (__version__.split('.')[0] != __mpfversion__.split('.')[0] or
                __version__.split('.')[1] != __mpfversion__.split('.')[1]):

            self.log.error("MPF MC and MPF Game engines must be same "
                           "major.minor versions. You have MPF v%s and MPF-MC"
                           " v%s", __mpfversion__, __version__)

            raise ValueError("MPF MC and MPF Game engines must be same "
                             "major.minor versions. You have MPF v{} and MPF-MC"
                             " v{}".format(__mpfversion__, __version__))

        super().__init__(**kwargs)

        self.options = options
        self.log.info("Machine path: %s", machine_path)
        self.machine_path = machine_path
        # load machine into path to load modules
        if machine_path not in sys.path:
            sys.path.append(machine_path)
        self.config_validator = ConfigValidator(self, not options["no_load_cache"], options["create_config_cache"])
        self.mpf_config_processor = MpfConfigProcessor(self.config_validator)
        self.machine_config = self._load_config()

        self.clock = Clock
        # pylint: disable-msg=protected-access
        self.log.info("Starting clock at %sHz", Clock._max_fps)
        self._boot_holds = set()
        self.is_init_done = threading.Event()
        self.mpf_path = os.path.dirname(mpf.__file__)
        self.modes = CaseInsensitiveDict()
        self.player_list = list()
        self.player = None
        self.num_players = 0
        self.bcp_client_connected = False
        self.placeholder_manager = McPlaceholderManager(self)
        self.settings = McSettingsController(self)

        self.animation_configs = dict()
        self.active_slides = dict()
        self.custom_code = list()

        self.register_boot_hold('init')
        self.displays = DeviceCollection(self, "displays", "displays")
        self.machine_vars = CaseInsensitiveDict()
        self.machine_var_monitor = False
        self.monitors = dict()
        self.targets = dict()
        """Dict which contains all the active slide frames in the machine that
        a slide can target. Will always contain an entry called 'default'
        which will be used if a slide doesn't specify targeting.
        """

        self.keyboard = None
        self.dmds = []
        self.rgb_dmds = []
        self.crash_queue = queue.Queue()
        self.ticks = 0
        self.start_time = 0
        self.debug_refs = []

        if thread_stopper:
            self.thread_stopper = thread_stopper
        else:
            self.thread_stopper = threading.Event()

        # Core components
        self.events = EventManager(self)
        self.mode_controller = ModeController(self)
        create_config_collections(self, self.machine_config['mpf-mc']['config_collections'])

        self.config_processor = ConfigProcessor(self)
        self.transition_manager = TransitionManager(self)
        self.effects_manager = EffectsManager(self)

        self._set_machine_path()

        self._load_font_paths()

        # Initialize the sound system (must be done prior to creating the AssetManager).
        # If the sound system is not available, do not load any other sound-related modules.
        if SoundSystem is None or self.options.get("no_sound"):
            self.sound_system = None
        else:
            self.sound_system = SoundSystem(self)
            if self.sound_system.audio_interface is None:
                self.sound_system = None

        self.asset_manager = ThreadedAssetManager(self)
        self.bcp_processor = BcpProcessor(self)

        # Asset classes
        ImageAsset.initialize(self)
        VideoAsset.initialize(self)
        BitmapFontAsset.initialize(self)

        self._initialise_sound_system()

        self.clock.schedule_interval(self._check_crash_queue, 1)

        self.events.add_handler("client_connected", self._create_dmds)
        self.events.add_handler("player_turn_start", self.player_start_turn)

        self.create_machine_var('mpfmc_ver', __version__)
        # force setting it here so we have it before MPF connects
        self.receive_machine_var_update('mpfmc_ver', __version__, 0, True)

    def track_leak_reference(self, element):
        """Track elements to find leaks."""
        if not self.options["production"]:
            self.debug_refs.append(weakref.ref(element))
            # cleanup all dead references
            self.debug_refs = [element for element in self.debug_refs if element()]

    @staticmethod
    def _preprocess_config(config):
        kivy_config = config['kivy_config']

        try:
            kivy_config['graphics'].update(config['window'])
        except KeyError:
            pass
{'fraction_non_alphanumeric': 0.08054498096573832, 'fraction_numerical': 0.0016028851933480265, 'mean_word_length': 2.927091528979806, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '300317', 'n_tokens_mistral': 4274, 'n_tokens_neox': 4128, 'n_words': 1093}