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()