TODO: consider checking item creation / experience gain just in case */
| 537 | TODO: consider checking item creation / experience gain just in case |
| 538 | */ |
| 539 | static void manageJobCompletedEvent(color_ostream& out) { |
| 540 | if (!df::global::world) |
| 541 | return; |
| 542 | |
| 543 | multimap<Plugin*, EventHandler> copy(handlers[EventType::JOB_COMPLETED].begin(), handlers[EventType::JOB_COMPLETED].end()); |
| 544 | std::vector<JobCompleteData> nowJobs; |
| 545 | // predict the size in advance, this will prevent or reduce memory reallocation. |
| 546 | nowJobs.reserve(prevJobs.size()); |
| 547 | for (const auto jobPtr : df::global::world->jobs.list) { |
| 548 | auto& job = *jobPtr; |
| 549 | |
| 550 | auto seenIt = seenJobs.find(job.id); |
| 551 | if (seenIt != seenJobs.end()) { |
| 552 | /* |
| 553 | * No reference here, to prevent dangling reference situation |
| 554 | * when job is re-cloned. |
| 555 | */ |
| 556 | auto seenJob = seenIt->second.get(); |
| 557 | // The key here is to strategically check the most important bits to reduce churn. |
| 558 | if (seenJob->flags.whole != job.flags.whole |
| 559 | || (seenJob->items.size() != job.items.size()) |
| 560 | || (seenJob->general_refs.size() != job.general_refs.size())) { |
| 561 | seenIt->second = Job::JobUniquePtr(Job::cloneJobStruct(&job, true)); |
| 562 | } |
| 563 | } else if (job.completion_timer != -1) { |
| 564 | // Restrict additions to seenJobs to jobs that we know have started. |
| 565 | seenJobs.emplace(job.id, Job::JobUniquePtr(Job::cloneJobStruct(&job, true))); |
| 566 | } |
| 567 | /* |
| 568 | * We still need to push back all jobs to maintain the invariant of the |
| 569 | * algorithm used with prevJobs and nowJobs. |
| 570 | * |
| 571 | * Consider a list of job IDs from the job list, including those |
| 572 | * that haven't started: |
| 573 | * 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 |
| 574 | * |
| 575 | * We push back all jobs, including those that haven't started or have |
| 576 | * been assigned yet. Jobs that haven't started or finished have a |
| 577 | * completion_timer of -1. |
| 578 | * |
| 579 | * If we don't push back all jobs to the vector, we could encounter |
| 580 | * this situation: |
| 581 | * |
| 582 | * prevJob IDs (jobs with completion_timer != -1): |
| 583 | * 2, 4, 8, 10 |
| 584 | * |
| 585 | * nowJobs IDs (completion_timer != -1 or had completion_timer != -1): |
| 586 | * 1, 2, 4, 8, 10 |
| 587 | * |
| 588 | * In this case, Job with ID 1 has started after jobs 2, 4, 8, and 10. |
| 589 | * But, nowJobs is not greater than or equal to prevJobs because the ID |
| 590 | * 1 in nowJobs is less than the smallest ID in prevJobs, |
| 591 | * which breaks the algorithm. |
| 592 | */ |
| 593 | nowJobs.emplace_back(job.id, job.completion_timer, (bool)job.flags.bits.repeat); |
| 594 | } |
| 595 | |
| 596 | // Do we want this check? |