Exemple #1
0
        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);
        }
Exemple #2
0
        // 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);
        }
Exemple #3
0
        public bool FinishJobThisFrame(uint id)
        {
            if (!RegisteredJobs.ContainsKey(id))
            {
                return(false);
            }

            RegisteredJobs[id].FinishJobNow = true;
            return(true);
        }
Exemple #4
0
        private void OnCancelInternal(uint id, bool timedOut, Exception exception = null)
        {
            var onCancel = RegisteredJobs[id].JobCancellationHandler;

            RegisteredJobs.Remove(id);

            if (onCancel != null)
            {
                onCancel(id, timedOut, exception); // Call the caller's 'cleanup' code if it was provided
            }
        }
Exemple #5
0
        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);
        }
Exemple #6
0
        // 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;
        }
Exemple #7
0
 public bool IsJobpaused(uint id)
 {
     return(RegisteredJobs.ContainsKey(id) && RegisteredJobs[id].State == JobState.Paused);
 }
Exemple #8
0
 public bool IsJobWaiting(uint id)
 {
     return(RegisteredJobs.ContainsKey(id) && RegisteredJobs[id].State == JobState.Waiting);
 }
Exemple #9
0
 public bool IsJobRegistered(uint id)
 {
     return(RegisteredJobs.ContainsKey(id));
 }
Exemple #10
0
        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);
        }