NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

shipSkinSequencingSvc.py (17510B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\cosmetics\client\shipSkinSequencingSvc.py
      2 import uthread2
      3 import uuid
      4 from assetholding.client.inventory.utils import are_items_available, get_items_available
      5 from collections import defaultdict
      6 from cosmetics.client.shipSkinComponentSvc import get_ship_skin_component_svc
      7 from cosmetics.client.shipSkinDataSvc import get_ship_skin_data_svc
      8 from cosmetics.client.ships import ship_skin_svc_signals
      9 from cosmetics.client.ships.feature_flag import ship_skin_sequencing_cost_discount, ship_skin_sequencing_time_discount, ship_skin_legion_tier_data_enabled
     10 from cosmetics.client.ships.skins.live_data import current_skin_design
     11 from cosmetics.client.messengers.cosmetics.ship.shipSkinSequencingNoticeMessenger import PublicShipSkinSequencingNoticeMessenger
     12 from cosmetics.client.messengers.cosmetics.ship.shipSkinSequencingRequestMessenger import PublicShipSkinSequencingRequestMessenger
     13 from cosmetics.client.ships.skins.live_data.skin_design import SkinDesign
     14 from cosmetics.client.ships.skins.live_data.sequencing_job import SequencingJob, SequencingJobState
     15 from cosmetics.client.ships.skins.errors import SequencingJobError, GenericError, NoTimeRemainingException
     16 import cosmetics.client.ships.ship_skin_signals as ship_skin_signals
     17 from cosmetics.common.ships.skins import sequencing_util
     18 from cosmetics.common.ships.skins.live_data.component_license_type import ComponentLicenseType
     19 from cosmetics.common.ships.skins.static_data.component import ComponentsDataLoader
     20 from logging import getLogger
     21 from eve.client.script.ui.cosmetics.ship.pages.sequence import sequence_ui_signals
     22 from eve.common.script.sys.idCheckers import IsSequenceBinder
     23 from omega.client.clone_grade_svc import get_clone_grade_svc
     24 from plex.client.api.repository import get_repository
     25 from skills.client.util import get_skill_service
     26 _instance = None
     27 logger = getLogger(__name__)
     28 
     29 def get_ship_skin_sequencing_svc():
     30     global _instance
     31     if _instance is None:
     32         _instance = _ShipSkinSequencingSvc()
     33     return _instance
     34 
     35 
     36 class _ShipSkinSequencingSvc(object):
     37     __startupdependencies__ = ['publicGatewaySvc']
     38     __notifyevents__ = ['OnSessionChanged', 'OnItemsChanged', 'OnSkillsChanged']
     39 
     40     def __init__(self):
     41         self._sequencing_jobs_cache = {}
     42         self._my_sequencing_jobs_fetched_already = False
     43         self._cached_plex_by_tier_level = None
     44         self._cached_sequencing_cost_discount = None
     45         self._cached_time_by_tier_level = None
     46         self._cached_sequencing_time_discount = None
     47         self._cached_time_limits_and_prices = None
     48         self._cached_licenses_used = {}
     49         self._num_runs = 1
     50         self._expedited_jobs = set()
     51         ship_skin_sequencing_cost_discount()
     52         ship_skin_sequencing_time_discount()
     53         ship_skin_legion_tier_data_enabled()
     54         self._connect_signals()
     55         public_gateway = sm.GetService('publicGatewaySvc')
     56         self._request_messenger = PublicShipSkinSequencingRequestMessenger(public_gateway)
     57         self._notice_messenger = PublicShipSkinSequencingNoticeMessenger(public_gateway)
     58         sm.RegisterNotify(self)
     59 
     60     def __del__(self):
     61         self._disconnect_signals()
     62 
     63     def _connect_signals(self):
     64         ship_skin_svc_signals.on_sequencing_started_internal.connect(self._on_sequencing_started_internal)
     65         ship_skin_svc_signals.on_sequencing_failed_internal.connect(self._on_sequencing_failed_internal)
     66         ship_skin_svc_signals.on_sequencing_completed_internal.connect(self._on_sequencing_completed_internal)
     67         ship_skin_signals.on_ship_skin_sequencing_cost_discount_changed.connect(self._on_ship_skin_sequencing_cost_discount_changed)
     68         ship_skin_signals.on_ship_skin_sequencing_time_discount_changed.connect(self._on_ship_skin_sequencing_time_discount_changed)
     69         ship_skin_signals.on_skinr_tier_information_legion_changed.connect(self._on_skinr_tier_information_legion_changed)
     70 
     71     def _disconnect_signals(self):
     72         ship_skin_svc_signals.on_sequencing_started_internal.disconnect(self._on_sequencing_started_internal)
     73         ship_skin_svc_signals.on_sequencing_failed_internal.disconnect(self._on_sequencing_failed_internal)
     74         ship_skin_svc_signals.on_sequencing_completed_internal.disconnect(self._on_sequencing_completed_internal)
     75         ship_skin_signals.on_ship_skin_sequencing_cost_discount_changed.disconnect(self._on_ship_skin_sequencing_cost_discount_changed)
     76         ship_skin_signals.on_ship_skin_sequencing_time_discount_changed.disconnect(self._on_ship_skin_sequencing_time_discount_changed)
     77         ship_skin_signals.on_skinr_tier_information_legion_changed.disconnect(self._on_skinr_tier_information_legion_changed)
     78 
     79     def OnSessionChanged(self, _isRemote, _sess, change):
     80         if 'charid' in change:
     81             self._sequencing_jobs_cache = {}
     82             self._my_sequencing_jobs_fetched_already = False
     83             self._cached_plex_by_tier_level = None
     84             self._cached_sequencing_cost_discount = None
     85             self._cached_time_limits_and_prices = None
     86             self._expedited_jobs = set()
     87         sequence_ui_signals.on_sequence_binders_changed()
     88 
     89     def OnItemsChanged(self, items, _change, _location):
     90         if any([ IsSequenceBinder(item.groupID) for item in items ]):
     91             sequence_ui_signals.on_sequence_binders_changed()
     92 
     93     def _clear_cache(self):
     94         self._sequencing_jobs_cache = {}
     95         self._my_sequencing_jobs_fetched_already = False
     96         self._cached_plex_by_tier_level = None
     97         self._cached_sequencing_cost_discount = None
     98         self._cached_time_by_tier_level = None
     99         self._cached_sequencing_time_discount = None
    100         self._cached_licenses_used = {}
    101         self.set_num_runs(1)
    102         ship_skin_signals.on_skin_sequencing_cache_invalidated()
    103 
    104     def get_all_my_active_sequencing_jobs(self, force_refresh = False):
    105         if force_refresh or not self._my_sequencing_jobs_fetched_already:
    106             self._sequencing_jobs_cache = {}
    107             jobs = self._request_messenger.get_all_active_sequencing_jobs_request()
    108             for job_id, job in jobs.items():
    109                 self._sequencing_jobs_cache[job_id] = job
    110 
    111             self._my_sequencing_jobs_fetched_already = True
    112         return {k:v for k, v in self._sequencing_jobs_cache.items() if v.is_active()}
    113 
    114     def get_active_sequencing_jobs_count(self):
    115         return len(self.get_all_my_active_sequencing_jobs())
    116 
    117     def get_sequencing_job(self, job_id, force_refresh = False):
    118         if job_id not in self._sequencing_jobs_cache or force_refresh:
    119             job = self._request_messenger.get_sequencing_job_request(job_id)
    120             self._sequencing_jobs_cache[job_id] = job
    121         return self._sequencing_jobs_cache[job_id]
    122 
    123     def validate_design_for_sequencing(self, design):
    124         errors = []
    125         number_of_runs = self.get_num_runs()
    126         if not current_skin_design.get().name:
    127             errors.append(SequencingJobError.SKIN_NAME_MISSING)
    128         price, _ = self.get_sequencing_plex_price(design, number_of_runs)
    129         if price > get_repository().get_balance():
    130             errors.append(SequencingJobError.INSUFFICIENT_FUNDS)
    131         if not self.can_sequence_for_hull(design.ship_type_id):
    132             errors.append(SequencingJobError.INSUFFICIENT_SKILLS_FOR_SHIP_TYPE)
    133         if number_of_runs > sequencing_util.get_maximum_runs_per_job(get_skill_service().GetMyLevel):
    134             errors.append(SequencingJobError.MASS_SEQUENCING_LIMIT_REACHED)
    135         if len(self.get_all_my_active_sequencing_jobs()) >= sequencing_util.get_maximum_concurrent_sequencing_jobs(get_skill_service().GetMyLevel):
    136             errors.append(SequencingJobError.MAX_CONCURRENT_SEQUENCING_JOBS_REACHED)
    137         if not design.validate_layout():
    138             errors.append(SequencingJobError.INVALID_DESIGN)
    139         if not design.validate_licenses(number_of_runs):
    140             errors.append(SequencingJobError.INSUFFICIENT_COMPONENT_LICENSES)
    141         amount_required_by_type = design.get_sequence_binder_amounts_required(number_of_runs)
    142         if not are_items_available(items=amount_required_by_type, sourceLocationsOptionsList=[(None, True, None)]):
    143             errors.append(SequencingJobError.INSUFFICIENT_SEQUENCE_BINDERS)
    144         return errors
    145 
    146     def _update_limited_license_amount(self, design):
    147         number_of_runs = self.get_num_runs()
    148         for component_instance in design.slot_layout.slots.itervalues():
    149             if component_instance is not None:
    150                 license_data = component_instance.component_license_to_use
    151                 license_type = license_data.license_type
    152                 if license_type != ComponentLicenseType.LIMITED:
    153                     continue
    154                 remaining_uses = license_data.remaining_license_uses
    155                 license_data.set_remaining_license_uses(remaining_uses - number_of_runs)
    156 
    157     def sequence_design(self, design):
    158         if design is not None:
    159             design.clean_up_skin()
    160         number_of_runs = self.get_num_runs()
    161         component_licenses = []
    162         component_quantity_by_id_and_license_type = defaultdict(int)
    163         component_category_by_id = {}
    164         for component_instance in design.slot_layout.slots.itervalues():
    165             if component_instance is not None:
    166                 if component_instance.component_license_to_use is None:
    167                     return SequencingJobError.INSUFFICIENT_COMPONENT_LICENSES
    168                 license_data = component_instance.component_license_to_use
    169                 if license_data is None:
    170                     return SequencingJobError.INSUFFICIENT_COMPONENT_LICENSES
    171                 component_id = component_instance.component_id
    172                 license_type = license_data.license_type
    173                 component_data = ComponentsDataLoader.get_component_data(component_id)
    174                 if component_data is None:
    175                     return GenericError.UNKNOWN
    176                 component_category_by_id[component_id] = component_data.category
    177                 component_quantity_by_id_and_license_type[component_id, license_type] += number_of_runs
    178                 component_licenses.append(license_data)
    179 
    180         job, error = self._request_messenger.initiate_sequencing_request(design, component_quantity_by_id_and_license_type, component_category_by_id, number_of_runs)
    181         if error is not None:
    182             return error
    183         elif job is not None and job.state == SequencingJobState.PENDING:
    184             self._update_limited_license_amount(design)
    185             self._sequencing_jobs_cache[job.job_id] = job
    186             self._cached_licenses_used[job.job_id] = component_licenses
    187             return
    188         else:
    189             return GenericError.UNKNOWN
    190 
    191     def set_num_runs(self, value):
    192         self._num_runs = value
    193         sequence_ui_signals.on_num_skins_changed(value)
    194 
    195     def get_num_runs(self):
    196         return self._num_runs
    197 
    198     def expedite_sequencing(self, job_id):
    199         if job_id in self._expedited_jobs:
    200             return
    201         self._expedited_jobs.add(job_id)
    202         try:
    203             error = self._request_messenger.expedite_sequencing(job_id)
    204             if error is not None:
    205                 self._expedited_jobs.remove(job_id)
    206             return error
    207         except Exception as e:
    208             self._expedited_jobs.remove(job_id)
    209             raise e
    210 
    211     def can_sequence_for_hull(self, ship_type_id):
    212         is_type_restricted_to_clone_state = get_clone_grade_svc().is_requirements_restricted(ship_type_id)
    213         return not is_type_restricted_to_clone_state
    214 
    215     def get_predicted_job_duration(self, design):
    216         if self._cached_time_by_tier_level is None:
    217             self._get_tier_times_and_discount()
    218         duration = self._cached_time_by_tier_level[design.tier_level]
    219         logger.info('SKIN SEQUENCING - predicted job duration for skin tier %s is %s seconds, taking skills into consideration, with a %s percent discount applied' % (design.tier_level, duration, self._cached_sequencing_time_discount))
    220         return (duration, self._cached_sequencing_time_discount)
    221 
    222     def get_sequencing_plex_price(self, design, nb_runs):
    223         if self._cached_plex_by_tier_level is None:
    224             self._get_tier_pricing_and_discount()
    225         logger.info('SKIN SEQUENCING - Get sequencing PLEX price requested for tier level %s, returned: %s', design.tier_level, self._cached_plex_by_tier_level[design.tier_level])
    226         return (self._cached_plex_by_tier_level[design.tier_level] * nb_runs, self._cached_sequencing_cost_discount)
    227 
    228     def get_sequencing_cost_discount(self):
    229         if self._cached_sequencing_cost_discount is None:
    230             self._get_tier_pricing_and_discount()
    231         return self._cached_sequencing_cost_discount
    232 
    233     def get_early_completion_cost(self, job_id):
    234         job = self.get_sequencing_job(job_id)
    235         plex = self._get_early_completion_cost_from_job(job)
    236         logger.info('SKIN SEQUENCING - Get early completion cost requested, returned: %s', plex)
    237         return plex
    238 
    239     def _get_early_completion_cost_from_job(self, job):
    240         time_remaining = job.time_remaining
    241         if time_remaining is None:
    242             raise NoTimeRemainingException('Cannot calculate early completion cost as no time remains for this job')
    243         if self._cached_time_limits_and_prices is None:
    244             self._cached_time_limits_and_prices = self._request_messenger.get_early_completion_pricing_request()
    245             logger.info('SKIN SEQUENCING - Get early completion cost table requested. Time remaining: %s. Returned: %s', time_remaining.total_seconds(), self._cached_time_limits_and_prices)
    246         for remaining_greater, remaining_lesser_or_equal, plex in self._cached_time_limits_and_prices:
    247             if (remaining_greater < 0 or remaining_greater < time_remaining.total_seconds()) and (remaining_lesser_or_equal < 0 or time_remaining.total_seconds() < remaining_lesser_or_equal):
    248                 return plex
    249 
    250     def _get_tier_pricing_and_discount(self):
    251         self._cached_plex_by_tier_level, self._cached_sequencing_cost_discount = self._request_messenger.get_tier_pricing_request()
    252         logger.info('SKIN SEQUENCING - Get sequencing PLEX price table requested, returned: %s', self._cached_plex_by_tier_level)
    253         logger.info('SKIN SEQUENCING - Sequencing cost discount requested, returned: %s' % self._cached_sequencing_cost_discount)
    254 
    255     def _get_tier_times_and_discount(self):
    256         self._cached_time_by_tier_level, self._cached_sequencing_time_discount = self._request_messenger.get_tier_durations_request()
    257         logger.info('SKIN SEQUENCING - Get sequencing time table requested, returned: %s', self._cached_time_by_tier_level)
    258         logger.info('SKIN SEQUENCING - Sequencing time discount requested, returned: %s' % self._cached_sequencing_time_discount)
    259 
    260     def _update_cached_job(self, job_id):
    261         uthread2.start_tasklet(self._update_cached_job_thread, job_id)
    262 
    263     def _update_cached_job_thread(self, job_id):
    264         if job_id in self._sequencing_jobs_cache:
    265             job = self._request_messenger.get_sequencing_job_request(job_id)
    266             if job is not None:
    267                 if job != self._sequencing_jobs_cache[job_id]:
    268                     self._sequencing_jobs_cache[job_id].copy_data_from_other(job)
    269                     ship_skin_signals.on_skin_sequencing_job_updated(job_id)
    270 
    271     def _on_sequencing_started_internal(self, job_id):
    272         logger.info('SKIN SEQUENCING - job %s started notice received' % job_id)
    273         self._update_cached_job(job_id)
    274         self._invalidate_component_licenses_after_sequencing(job_id)
    275 
    276     def _on_sequencing_failed_internal(self, job_id, reason):
    277         logger.info('SKIN SEQUENCING - job %s failed notice received. Reason: %s', job_id, reason)
    278         self._update_cached_job(job_id)
    279         ship_skin_signals.on_skin_sequencing_job_failed(job_id, reason)
    280         self._invalidate_component_licenses_after_sequencing(job_id)
    281 
    282     def _on_sequencing_completed_internal(self, job_id):
    283         logger.info('SKIN SEQUENCING - job %s completed notice received' % job_id)
    284         self._update_cached_job(job_id)
    285         ship_skin_signals.on_skin_sequencing_job_completed(job_id)
    286 
    287     def _invalidate_component_licenses_after_sequencing(self, job_id):
    288         if job_id in self._cached_licenses_used:
    289             get_ship_skin_component_svc().clear_cache([ x.component_id for x in self._cached_licenses_used[job_id] if x.license_type == ComponentLicenseType.LIMITED ])
    290             self._cached_licenses_used.pop(job_id)
    291 
    292     def get_available_at_active_location(self, quantity_by_type_id):
    293         return get_items_available(quantity_by_type_id=quantity_by_type_id, sourceLocationOptions=(None, True, None))
    294 
    295     def _on_ship_skin_sequencing_cost_discount_changed(self, *args):
    296         logger.info('SKIN SEQUENCING - sequencing cost discount flag changed, clearing cache')
    297         self._clear_cache()
    298 
    299     def _on_ship_skin_sequencing_time_discount_changed(self, *args):
    300         logger.info('SKIN SEQUENCING - sequencing time discount flag changed, clearing cache')
    301         self._clear_cache()
    302 
    303     def _on_skinr_tier_information_legion_changed(self, *args):
    304         logger.info('SKIN SEQUENCING - legion tier information flag changed, clearing cache')
    305         self._clear_cache()
    306 
    307     def OnSkillsChanged(self, *args):
    308         logger.info('SKIN SEQUENCING - skills changed, clearing cache')
    309         self._clear_cache()