public bool CancelJob(uint id, bool cancelSpawnedJobs = false, Exception exception = null) { if (!RegisteredJobs.ContainsKey(id)) { return(false); } if (cancelSpawnedJobs) { // There will be only 0 or 1 of these: var otherJob = RegisteredJobs.FirstOrDefault(e => e.Value.JobIdToUnPauseWhenDone.Equals(id)); if (otherJob.Value != null) { CancelJob(otherJob.Key, true); } } if (id != CurrentlyRunningJobId) { StopCoroutine(RegisteredJobs[id].Coroutine); } OnCancelInternal(id, false, exception); return(true); }
// For convenience, this call has all of the parameters as the main call, except the jobIdToUnPauseWhenDone parameter, which is set automatically // This method is really "Start a new job, and if it does not complete immediately, pause the calling job, and register that it will be un-paused when the new job completes" public uint StartJobAndPause ( IEnumerator job, OnJobCompletionHandler completionHandler = null, OnJobCancellationHandler cancellationHandler = null, string jobName = "", List <uint> dependentJobs = null, float timeoutTime = Single.MaxValue, int maxExecutionsPerFrame = Int32.MaxValue, bool canSkipFrames = false, bool startImmediately = false ) { if (CurrentlyRunningJobId == UInt32.MaxValue) { throw new Exception("JobManager: Call to StartJobAndPause received from code that is not a job"); } var id = StartJob(job, completionHandler, cancellationHandler, jobName, dependentJobs, timeoutTime, maxExecutionsPerFrame, canSkipFrames, startImmediately, CurrentlyRunningJobId); if (RegisteredJobs.ContainsKey(id)) // If the job has not already finished { PauseJob(CurrentlyRunningJobId); } return(id); }
public bool FinishJobThisFrame(uint id) { if (!RegisteredJobs.ContainsKey(id)) { return(false); } RegisteredJobs[id].FinishJobNow = true; return(true); }
public bool UnPauseJob(uint id) { if (!RegisteredJobs.ContainsKey(id)) { return(false); } if (RegisteredJobs[id].State == JobState.Paused) { RegisteredJobs[id].State = RegisteredJobs[id].StateBeforePaused; } return(true); }
// This is the WRAPPER. This is what is actually executing as a Unity coroutine. // It is structured to use a given timeslice share (expressed as number of stopwatch ticks) private IEnumerator RunJob(uint id, IEnumerator job, bool startImmediately) { bool jobDone = false; var jobName = RegisteredJobs[id].Name; var timeoutStopwatch = new Stopwatch(); timeoutStopwatch.Start(); var wasWaitingForDependentJobs = RegisteredJobs[id].DependentJobs.Count > 0; if (wasWaitingForDependentJobs) { RegisteredJobs[id].State = JobState.Waiting; } while (RegisteredJobs[id].DependentJobs.Count > 0) { RegisteredJobs[id].DependentJobs.RemoveAll(item => !RegisteredJobs.ContainsKey(item)); if (RegisteredJobs[id].DependentJobs.Count > 0) { yield return(null); // Yield, as we are still waiting for at least one dependent job to complete if (timeoutStopwatch.ElapsedTicks >= RegisteredJobs[id].TimeoutTimeInTicks) { Debug.Log("JobManager: \"" + jobName + "\" has timed out (before it even started) and is therefore being cancelled"); OnCancelInternal(id, true); yield break; // And we're completely done } } } // Note: We put this here because if the job has been waiting for dependent jobs, and the last dependent job has finished, and in the same frame, // we're now starting this job, the budgeting can effectively give more time than it should to this job execution, because the basic per-job // budget is calculated once per frame and is based on the number of active jobs. if (!startImmediately || wasWaitingForDependentJobs) { yield return(null); } if (RegisteredJobs[id].State != JobState.Paused) { RegisteredJobs[id].State = JobState.Running; } //Debug.Log("JobManager: Started \"" + jobName + "\"" + (RegisteredJobs[id].State == JobState.Paused ? " but in paused state" : "")); var sw = new Stopwatch(); var timedOut = false; var exceptionOccurred = false; Exception exception = null; var savedCurrentlyRunningJobId = CurrentlyRunningJobId; CurrentlyRunningJobId = id; do { if (RegisteredJobs[id].FinishJobNow) { Debug.Log("JobManager: \"" + jobName + "\" request to complete entire job now..."); try { while (job.MoveNext()) { ; } } catch (Exception e) { DumpJobExceptionMessage(id, e); exceptionOccurred = true; exception = e; } break; // Break out of the outer do loop } var budgetedTimeInTicks = JobBudget - LastJobOverTicks; // Note that at this point, budgetedTimeInTicks can be negative. // In that case the job will execute exactly one iteration below, EXCEPT if CanSkipFrames is set to true, in which case we don't execute it at all //Debug.Log("budgetedTimeInTicks:" + budgetedTimeInTicks + " for job " + jobName); if (budgetedTimeInTicks < 0 && RegisteredJobs[id].CanSkipFrames) { //Debug.Log("JobManager: \"" + RegisteredJobs[id].Name + "\" is being skipped this frame"); LastJobOverTicks = 0 - budgetedTimeInTicks; CurrentlyRunningJobId = savedCurrentlyRunningJobId; yield return(null); savedCurrentlyRunningJobId = CurrentlyRunningJobId; CurrentlyRunningJobId = id; } else { long elapsedTicks = 0; if (RegisteredJobs[id].State != JobState.Paused) { sw.Start(); var executionCount = 0; do { try { if (!job.MoveNext()) // Call the actual code to be executed; returns false if entire job is done { jobDone = true; elapsedTicks = sw.ElapsedTicks; break; } } catch (Exception e) { DumpJobExceptionMessage(id, e); exceptionOccurred = true; exception = e; jobDone = true; elapsedTicks = sw.ElapsedTicks; break; } elapsedTicks = sw.ElapsedTicks; if (++executionCount >= RegisteredJobs[id].MaxExecutionsPerFrame) { break; } if (RegisteredJobs[id].State == JobState.Paused) { break; } } while (elapsedTicks < budgetedTimeInTicks); sw.Reset(); } LastJobOverTicks = elapsedTicks - budgetedTimeInTicks; // Note that this can be negative TimeSpentInJobsTicks += elapsedTicks; //if (elapsedTicks < budgetedTimeInTicks) // Debug.Log( "JobManager: Job slice finished earlier than budget!" ); } if (!jobDone) { if (timeoutStopwatch.ElapsedTicks < RegisteredJobs[id].TimeoutTimeInTicks) { CurrentlyRunningJobId = savedCurrentlyRunningJobId; yield return(null); savedCurrentlyRunningJobId = CurrentlyRunningJobId; CurrentlyRunningJobId = id; } else { Debug.Log("JobManager: \"" + jobName + "\" has timed out and is therefore being cancelled"); OnCancelInternal(id, true); jobDone = true; timedOut = true; } } } while (!jobDone); if (exceptionOccurred) { var spawningJobId = RegisteredJobs[id].JobIdToUnPauseWhenDone; // Cancel this job, and all jobs spawned by this job CancelJob(id, true, exception); // Cancel all job(s) that spawned this job while (spawningJobId != UInt32.MaxValue) { var nextSpawningJobId = RegisteredJobs[spawningJobId].JobIdToUnPauseWhenDone; CancelJob(spawningJobId); if (savedCurrentlyRunningJobId == spawningJobId) { savedCurrentlyRunningJobId = UInt32.MaxValue; } spawningJobId = nextSpawningJobId; } } else if (!timedOut) { var onCompletion = RegisteredJobs[id].JobCompletionHandler; var jobIdToUnPauseWhenDone = RegisteredJobs[id].JobIdToUnPauseWhenDone; RegisteredJobs.Remove(id); //Debug.Log("JobManager: \"" + jobName + "\" completed"); if (jobIdToUnPauseWhenDone != UInt32.MaxValue) { if (!UnPauseJob(jobIdToUnPauseWhenDone)) { Debug.Log("JobManager: Warning: \"" + jobName + "\" tried to unpause job id " + jobIdToUnPauseWhenDone + " but wasn't able to"); } } if (onCompletion != null) { onCompletion(id); // Call the caller's 'on completion' code if it was provided } } CurrentlyRunningJobId = savedCurrentlyRunningJobId; }
public bool IsJobpaused(uint id) { return(RegisteredJobs.ContainsKey(id) && RegisteredJobs[id].State == JobState.Paused); }
public bool IsJobWaiting(uint id) { return(RegisteredJobs.ContainsKey(id) && RegisteredJobs[id].State == JobState.Waiting); }
public bool IsJobRegistered(uint id) { return(RegisteredJobs.ContainsKey(id)); }
public uint StartJob ( IEnumerator job, OnJobCompletionHandler completionHandler = null, OnJobCancellationHandler cancellationHandler = null, string jobName = "", List <uint> dependentJobs = null, float timeoutTime = Single.MaxValue, int maxExecutionsPerFrame = Int32.MaxValue, bool canSkipFrames = false, bool startImmediately = false, // Note that if startImmediately is set to true, the time budgeting for the current frame might be thrown off uint jobIdToUnPauseWhenDone = UInt32.MaxValue ) { uint id = NextUid++; if (jobName.Equals("")) { jobName = "(unnamed)"; } RegisteredJobs[id] = new Job(); // Add to the list of registered jobs PRIOR to launching the coroutine, since the job might finish immediately var jobEntry = RegisteredJobs[id]; jobEntry.Coroutine = null; jobEntry.JobCompletionHandler = completionHandler; jobEntry.JobCancellationHandler = cancellationHandler; jobEntry.Name = jobName; if (dependentJobs == null) { jobEntry.DependentJobs = new List <uint>( ); // This is because we can't pass a non-null default item as a function parameter above if the item is a reference } else { // This rule (dependent jobs must have been registered prior to the dependee) prevents circularity if (dependentJobs.Any(jobId => jobId >= id)) { throw new Exception("JobManager: Error starting job \"" + jobName + "\"; invalid dependent job ID (must be a job that has already been started)"); } jobEntry.DependentJobs = dependentJobs; // For all the dependent jobs, ensure they can't skip frames (that could cause a lockup) foreach (var jobId in dependentJobs) { if (RegisteredJobs.ContainsKey(jobId)) { RegisteredJobs[jobId].CanSkipFrames = false; } } } jobEntry.TimeoutTimeInTicks = (timeoutTime > 1e20f ? Int64.MaxValue : Stopwatch.Frequency * (long)timeoutTime); jobEntry.MaxExecutionsPerFrame = maxExecutionsPerFrame; jobEntry.CanSkipFrames = canSkipFrames; jobEntry.JobIdToUnPauseWhenDone = jobIdToUnPauseWhenDone; jobEntry.State = JobState.Running; jobEntry.FinishJobNow = false; var coroutine = StartCoroutine(RunJob(id, job, startImmediately)); // Now that we have a reference to the coroutine, we can save it in the dictionary. However the job may have ran // and immediately finished by the time we get here, so in that case the registered item has already been removed. if (RegisteredJobs.ContainsKey(id)) { RegisteredJobs[id].Coroutine = coroutine; } return(id); }