diff --git a/radioactivity.module b/radioactivity.module index 88beb81..0cdc764 100644 --- a/radioactivity.module +++ b/radioactivity.module @@ -98,7 +98,8 @@ function radioactivity_get_field_names() { * Implements hook_cron(). */ function radioactivity_cron() { + /** @var \Drupal\radioactivity\RadioactivityProcessorInterface $processor */ $processor = \Drupal::service('radioactivity.processor'); $processor->processDecay(); - $processor->processEmits(); + $processor->processIncidents(); } diff --git a/src/RadioactivityProcessor.php b/src/RadioactivityProcessor.php index 9831550..39f560c 100644 --- a/src/RadioactivityProcessor.php +++ b/src/RadioactivityProcessor.php @@ -16,28 +16,28 @@ use Drupal\field\FieldStorageConfigInterface; class RadioactivityProcessor implements RadioactivityProcessorInterface { /** - * Entity type manager. + * The entity type manager. * * @var \Drupal\Core\Entity\EntityTypeManager */ protected $entityTypeManager; /** - * State key-value storage. + * The state key-value storage. * * @var \Drupal\Core\State\State */ protected $state; /** - * Logger. + * The radioactivity logger. * * @var \Drupal\Core\Logger\LoggerChannelInterface */ protected $log; /** - * Radioactivity storage. + * The radioactivity storage. * * @var \Drupal\Core\Entity\Query\QueryFactory */ @@ -76,79 +76,104 @@ class RadioactivityProcessor implements RadioactivityProcessorInterface { * {@inheritdoc} */ public function processDecay() { - $result_count = 0; + $resultCount = 0; + $processed = FALSE; - /** @var \Drupal\field\Entity\FieldStorageConfig[] $field_storage_configs */ - $field_storage_configs = $this->entityTypeManager->getStorage('field_storage_config')->loadByProperties(['type' => 'radioactivity']); + /** @var \Drupal\field\Entity\FieldStorageConfig[] $fieldConfigs */ + $fieldConfigs = $this->entityTypeManager->getStorage('field_storage_config')->loadByProperties(['type' => 'radioactivity']); - if (!$field_storage_configs) { - return; + if (!$fieldConfigs) { + return 0; } - $last_cron_timestamp = $this->state->get('radioactivity_last_cron_timestamp', 0); - - $this->state->set('radioactivity_last_cron_timestamp', $this->requestTime); - - foreach ($field_storage_configs as $field_storage) { - - // Only act when field has data. - if ($field_storage->hasData()) { - $this->updateField($field_storage, $last_cron_timestamp); + foreach ($fieldConfigs as $fieldConfig) { + $profile = $fieldConfig->getSetting('profile'); + if ($fieldConfig->hasData() && + ($profile === 'linear' || $profile === 'decay') && + $this->hasReachedGranularityThreshold($fieldConfig) + ) { + $resultCount += $this->processFieldDecay($fieldConfig); + $processed = TRUE; } } - $this->log->notice('Processed @count entities.', ['@count' => $result_count]); + if ($processed) { + $this->state->set('radioactivity_last_processed_timestamp', $this->requestTime); + } + $this->log->notice('Processed @count radioactivity decays.', ['@count' => $resultCount]); + return $resultCount; + } + + /** + * Determines if the field has reached the next granularity threshold. + * + * For some profiles profile, we only calculate the decay when x seconds have + * passed since the last cron run. The number of seconds is stored in + * 'granularity' field setting. + * + * @param \Drupal\field\FieldStorageConfigInterface $fieldConfig + * Configuration of the field to be checked. + * + * @return bool + * True if the threshold was reached. + */ + private function hasReachedGranularityThreshold(FieldStorageConfigInterface $fieldConfig) { + $lastCronTimestamp = $this->state->get('radioactivity_last_processed_timestamp', 0); + $granularity = $fieldConfig->getSetting('granularity'); + $threshold_timestamp = $lastCronTimestamp - ($lastCronTimestamp % $granularity) + $granularity; + return $this->requestTime >= $threshold_timestamp; } /** * Update entities attached to given field storage. * - * @param \Drupal\field\FieldStorageConfigInterface $field_storage - * @param [type] $last_cron_timestamp [description] + * @param \Drupal\field\FieldStorageConfigInterface $fieldConfig + * Configuration of the field to be processed. + * + * @return int + * The number of processed entities. */ - private function updateField(FieldStorageConfigInterface $field_storage, $last_cron_timestamp) { - $result_count = 0; + private function processFieldDecay(FieldStorageConfigInterface $fieldConfig) { + $fieldName = $fieldConfig->get('field_name'); + $entityType = $fieldConfig->getTargetEntityTypeId(); - $profile = $field_storage->getSetting('profile'); + // @todo The number of nodes may grow very large on active sites and/or + // long cron cycle times. Prepare for queue processing. - // For count profile, we don't need to calculate a decay. - if ($profile == 'count') { - return; - } + $query = $this->entityTypeManager->getStorage($entityType)->getQuery() + ->condition($fieldName . '.timestamp', $this->requestTime, ' < ') + ->condition($fieldName . '.energy', NULL, 'IS NOT NULL'); + $entityIds = $query->execute(); - $halflife = $field_storage->getSetting('halflife'); - $granularity = $field_storage->getSetting('granularity'); - $cutoff = $field_storage->getSetting('cutoff'); - - // For decay profile, we only calculate the decay when 'granularity' - // seconds have passed since the last cron run. - if ($profile == 'decay') { - $threshold_timestamp = $last_cron_timestamp - ($last_cron_timestamp % $granularity) + $granularity; - if ($this->requestTime < $threshold_timestamp) { - // Granularity not reached yet. - return; - } - } + $this->queueProcessDecay($fieldConfig, $entityIds); + return count($entityIds); + } - $field_name = $field_storage->get('field_name'); - $entity_type = $field_storage->getTargetEntityTypeId(); + /** + * Queue processing of Radioactivity decays. + * + * @param \Drupal\field\FieldStorageConfigInterface $fieldConfig + * Configuration of the field to be processed. + * @param array $entityIds + * Entity IDs to be processed. + */ + private function queueProcessDecay(FieldStorageConfigInterface $fieldConfig, array $entityIds) { + $entityType = $fieldConfig->getTargetEntityTypeId(); + $fieldName = $fieldConfig->get('field_name'); + $profile = $fieldConfig->getSetting('profile'); + $halfLife = $fieldConfig->getSetting('halflife'); + $cutoff = $fieldConfig->getSetting('cutoff'); - // @todo The number of nodes may grow very large on active sites and/or - // long cron cycle times. Prepare for queue processing. - $query = $this->entityTypeManager->getStorage($entity_type)->getQuery() - ->condition($field_name . '.timestamp', $this->requestTime, ' < ') - ->condition($field_name . '.energy', NULL, 'IS NOT NULL'); - $nids = $query->execute(); /** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */ - $entities = $this->entityTypeManager->getStorage($entity_type)->loadMultiple($nids); + $entities = $this->entityTypeManager + ->getStorage($entityType) + ->loadMultiple($entityIds); - // Loop through entities: foreach ($entities as $entity) { - - $timestamp = $entity->get($field_name)->timestamp; + $timestamp = $entity->get($fieldName)->timestamp; $elapsed = $timestamp ? $this->requestTime - $timestamp : 0; - $energy = $entity->get($field_name)->energy; + $energy = $entity->get($fieldName)->energy; switch ($profile) { case 'linear': @@ -156,58 +181,66 @@ class RadioactivityProcessor implements RadioactivityProcessorInterface { break; case 'decay': - $energy = $energy * pow(2, -$elapsed / $halflife); + $energy = $energy * pow(2, -$elapsed / $halfLife); break; } if ($energy > $cutoff) { // Set the new energy level and update the timestamp. - $entity->get($field_name)->setValue([ + $entity->get($fieldName)->setValue([ 'energy' => $energy, 'timestamp' => $this->requestTime, ]); } else { // Reset energy level to 0 if they are below the cutoff value. - $entity->get($field_name)->setValue(NULL); + $entity->get($fieldName)->setValue(NULL); } $entity->save(); - $result_count++; - } - } /** * {@inheritdoc} */ - public function processEmits() { - $count = 0; + public function processIncidents() { + $resultCount = 0; // @todo The number of incidents may grow very large on active sites and/or // long cron cycle times. Prepare for queue processing. - // Get incident data. $incidents_by_type = $this->storage->getIncidentsByType(); $this->storage->clearIncidents(); - foreach ($incidents_by_type as $type => $incidents) { - /** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */ - $entities = $this->entityTypeManager->getStorage($type)->loadMultiple(array_keys($incidents)); - foreach ($entities as $entity) { - /** @var \Drupal\radioactivity\Incident $incident */ - foreach ($incidents[$entity->id()] as $incident) { - $entity->get($incident->getFieldName())->energy += $incident->getEnergy(); - } - $entity->save(); - $count++; - } + foreach ($incidents_by_type as $entityType => $incidents) { + $this->queueProcessIncidents($entityType, array_keys($incidents)); + $resultCount += count($incidents); } - $this->log->notice('Processed @count incidents.', ['@count' => $count]); + $this->log->notice('Processed @count radioactivity incidents.', ['@count' => $resultCount]); + return $resultCount; + } + + /** + * Queue processing of Radioactivity emission incidents. + * + * @param string $entityType + * Incident entity type. + * @param array $entityIds + * Incident entity IDs to be processed. + */ + private function queueProcessIncidents($entityType, array $entityIds) { + /** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */ + $entities = $this->entityTypeManager->getStorage($entityType)->loadMultiple($entityIds); - return $count; + foreach ($entities as $entity) { + /** @var \Drupal\radioactivity\Incident $incident */ + foreach ($entityIds[$entity->id()] as $incident) { + $entity->get($incident->getFieldName())->energy += $incident->getEnergy(); + } + $entity->save(); + } } } diff --git a/src/RadioactivityProcessorInterface.php b/src/RadioactivityProcessorInterface.php index 9a8521c..1a819d1 100644 --- a/src/RadioactivityProcessorInterface.php +++ b/src/RadioactivityProcessorInterface.php @@ -11,12 +11,18 @@ interface RadioactivityProcessorInterface { /** * Apply decay to entities. + * + * @return int + * The number of decays processed. */ public function processDecay(); /** * Process emits from the queue. + * + * @return int + * The number of emits processed. */ - public function processEmits(); + public function processIncidents(); }