From e490afdf0058da782f6d9b8c1c09f559fcd2c81f Mon Sep 17 00:00:00 2001 From: Stephen Baynham Date: Thu, 24 Nov 2016 22:36:11 -0800 Subject: [PATCH] Rebuilt slightly to offer bool return val We fail on unknown general ref types now, without modifying the job at all yet --- library/LuaApi.cpp | 2 + library/include/modules/Job.h | 12 +++- library/modules/Job.cpp | 126 ++++++++++++++++++++-------------- 3 files changed, 89 insertions(+), 51 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 4416a9066..7f0e629a7 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1469,6 +1469,8 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = { WRAPM(Job,getName), WRAPM(Job,linkIntoWorld), WRAPM(Job,removePostings), + WRAPM(Job,disconnectJobItem), + WRAPM(Job,disconnectJobGeneralRef), WRAPM(Job,removeJob), WRAPN(is_equal, jobEqual), WRAPN(is_item_equal, jobItemEqual), diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h index 78a694b6f..1448b6656 100644 --- a/library/include/modules/Job.h +++ b/library/include/modules/Job.h @@ -66,10 +66,20 @@ namespace DFHack DFHACK_EXPORT void setJobCooldown(df::building *workshop, df::unit *worker, int cooldown = 100); DFHACK_EXPORT bool removeWorker(df::job *job, int cooldown = 100); + // This helpful method only removes the backref from the item to the job, but it doesn't + // remove the item ref from the job's vector, or delete it or anything. Think of it as a method + // that does all the needful to make an item ref ready to delete. + DFHACK_EXPORT void disconnectJobItem(df::job_item_ref *item, df::job *job); + // This helpful method only removes the backref from whatever the general_ref points to, + // it doesn't remove the general_ref from the job's vector, or delete it or anything. + // Think of it as a method that does all the needful to make a ref ready to delete. + // If it returns false, you've found a ref that the method doesn't know how to handle. Congratulations! + // You should report that and/or check in a fix. + DFHACK_EXPORT bool disconnectJobGeneralRef(df::general_ref *ref, df::job *job); // Delete a job & remove all refs from everywhere. // This method DELETES the job object! Everything related to it will be wiped // clean from the earth, so make sure you pull what you need out before calling this! - DFHACK_EXPORT void removeJob(df::job *job); + DFHACK_EXPORT bool removeJob(df::job *job); // Instruct the game to check and assign workers DFHACK_EXPORT void checkBuildingsNow(); diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 0ee17ddd1..b1f262f09 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -304,73 +304,98 @@ void DFHack::Job::setJobCooldown(df::building *workshop, df::unit *worker, int c } } -void DFHack::Job::removeJob(df::job *job) { +void DFHack::Job::disconnectJobItem(df::job_item_ref *ref, df::job *job) { + if (!ref) return; + + auto item = ref->item; + if (!item) return; + + //Work backward through the specific refs & remove/delete all specific refs to this job + int refCount = item->specific_refs.size(); + bool stillHasJobs = false; + for(int refIndex = refCount-1; refIndex >= 0; refIndex--) { + auto ref = item->specific_refs[refIndex]; + + if (ref->type == df::specific_ref_type::JOB) { + if (ref->job == job) { + vector_erase_at(item->specific_refs, refIndex); + delete ref; + } else { + stillHasJobs = true; + } + } + } + + if (!stillHasJobs) item->flags.bits.in_job = false; +} + +bool DFHack::Job::disconnectJobGeneralRef(df::general_ref *ref, df::job *job) { + if (ref == NULL) return true; + + switch (ref->getType()) { + case general_ref_type::BUILDING_HOLDER: + auto building = ref->getBuilding(); + + if (building != NULL) { + int jobIndex = linear_index(building->jobs, job); + if (jobIndex >= 0) { + vector_erase_at(building->jobs, jobIndex); + } + } + break; + case general_ref_type::UNIT_WORKER: + auto unit = ref->getUnit(); + + if (unit != NULL) { + if (unit->job.current_job == job) { + unit->job.current_job = NULL; + } + } + break; + default: + return false; + } + + return true; +} + +bool DFHack::Job::removeJob(df::job *job) { using df::global::world; CHECK_NULL_POINTER(job); if (job->flags.bits.special) //I don't think you can cancel these, because DF wasn't build to expect it? - return; + return false; - //As far as I know there are only two general refs jobs have, the unit assigned to work it (if any) - //and the workshop it was created at (if any). It's possible there are others, so we go ahead and wipe all - //refs, but these two are the only ones that we really handle with any intelligence. If other refs - //exist that might have return-references that need to be cleared, that needs to be implemented!!!! - auto holderRef = getGeneralRef(job, general_ref_type::BUILDING_HOLDER); - auto workerRef = getGeneralRef(job, general_ref_type::UNIT_WORKER); - df::building *holder = NULL; - df::unit *worker = NULL; - - if (holderRef) holder = holderRef->getBuilding(); - if (workerRef) worker = workerRef->getUnit(); - - //removeWorker() adds a job cd about right now, but I chose not to do that because I'm pretty sure - //that's only to stop removed workers from immediately reclaiming the job before doing something - //else, and this job is gonna be dead in a second. - - //Remove return-refs from the holder & worker - if (holder) { - int jobIndex = linear_index(holder->jobs, job); - if (jobIndex >= 0) - vector_erase_at(holder->jobs, jobIndex); - } + //We actually only know how to handle BUILDING_HOLDER and UNIT_WORKER refs- there's probably a great + //way to handle them, but until we have a good example, we'll just fail to remove jobs that have other sorts + //of refs, or any specific refs + if (job->specific_refs.size() > 0) + return false; - if (worker) { - if (worker->job.current_job == job) - worker->job.current_job = NULL; + for (auto genRefItr = job->general_refs.begin(); genRefItr != job->general_refs.end(); ++genRefItr) { + auto ref = *genRefItr; + if (ref != NULL && (ref->getType() != general_ref_type::BUILDING_HOLDER && ref->getType() != general_ref_type::UNIT_WORKER)) + return false; } - //Wipe all refs out + //Disconnect, delete, and wipe all general refs while (job->general_refs.size() > 0) { auto ref = job->general_refs[0]; + + //Our code above should have ensured that this won't return false- if it does, there's not + //a great way of recovering since we can't properly destroy the job & we can't leave it + //around. Better to know the moment that becomes a problem. + assert(disconnectJobGeneralRef(ref, job)); vector_erase_at(job->general_refs, 0); - delete ref; + if (ref != NULL) delete ref; } //Detach all items from the job while (job->items.size() > 0) { auto itemRef = job->items[0]; - df::item *item = NULL; - - if (itemRef) { - item = itemRef->item; - - if (item) { - item->flags.bits.in_job = false; - - //Work backward through the specific refs & remove/delete all specific refs to this job - int refCount = item->specific_refs.size(); - for(int refIndex = refCount-1; refIndex >= 0; refIndex--) { - auto ref = item->specific_refs[refIndex]; - if (ref->type == df::specific_ref_type::JOB && ref->job == job) { - vector_erase_at(item->specific_refs, refIndex); - delete ref; - } - } - } - - delete itemRef; - } + disconnectJobItem(itemRef, job); vector_erase_at(job->items, 0); + if (itemRef != NULL) delete itemRef; } //Remove job from job board @@ -400,6 +425,7 @@ void DFHack::Job::removeJob(df::job *job) { } delete job; + return true; } bool DFHack::Job::removeWorker(df::job *job, int cooldown)