/// <summary>
    /// Course correction calculations are Async on worker threads. In order to allow the rest of the game to
    /// run, the update runs as a state machine and should be polled periodically to check when the full
    /// correction calculation is done.
    ///
    /// A calculation is started with CalculationStart() and then this routine is polled until it returns true.
    ///
    /// Results are retrieved with GetCorrection();
    ///
    /// </summary>
    /// <returns></returns>
    public bool CalculationUpdate()
    {
        switch (calcState)
        {
        case CalcState.INITIAL_THREE:
            if (threadsPending == 0)
            {
                Debug.Log(CorrectionsLog());
                // results are in, figure out which direction is working and start a new thread to
                // see if we are not within the desired accuracy
                if (CorrectionInitialEstimate())
                {
                    calcState = CalcState.REFINING;
                }
                else
                {
                    // Correction estimation failed. Use the 0 correction path
                    correctionFinal = correctionData[1];
                    calcState       = CalcState.DONE;
                }
            }
            break;

        case CalcState.REFINING:
            if (threadsPending == 0)
            {
                Debug.Log(CorrectionsLog());
                // TODO: check the result, refine if necessary
                calcState = CalcState.DO_CALLBACK;
                Debug.LogFormat("correction={0} gives distance={1} for target={2}",
                                correctionData[0].correction, correctionData[0].distance, targetDistance);
                correctionFinal = correctionData[0];
            }
            break;

        case CalcState.CLOSEST_APPROACH:
            if (threadsPending == 0)
            {
                calcState = CalcState.DO_CALLBACK;
            }
            break;

        case CalcState.NOT_STARTED:
        case CalcState.DONE:
        case CalcState.DO_CALLBACK:
            break;

        default:
            Debug.LogError("Unsupported state");
            break;
        }

        if (calcState == CalcState.DO_CALLBACK)
        {
            calcCallback(this);
            calcState = CalcState.DONE;
        }

        return(calcState == CalcState.DONE);
    }
 private void CalcResultHandler(double distance, CorrectionData threadArgs)
 {
     Debug.LogFormat("thread done: correction={0}  distance={1} @ t={2} exec time={3} (ms)",
                     threadArgs.correction, distance, threadArgs.timeAtApproach, threadArgs.execTimeMs);
     threadCountMutex.WaitOne();
     threadsPending--;
     threadCountMutex.ReleaseMutex();
     CalculationUpdate();
 }
 public void ClosestApproachAsync(CorrectionData correctionData, CalcCallback calcCallback)
 {
     correctionData.gravityState.isAsync = true;
     this.calcCallback = calcCallback;
     calcState         = CalcState.CLOSEST_APPROACH;
     // setup Mutex
     threadCountMutex.WaitOne();
     threadsPending++;
     threadCountMutex.ReleaseMutex();
     System.Threading.ThreadPool.QueueUserWorkItem(
         new System.Threading.WaitCallback(CalcCorrectionThread),
         new object[] { correctionData, CreateUnityAdapter(), (JobResultHandler)CalcResultHandler });
 }
    //---------------------------------------------------------------------------------------
    // Code from: http://blog.yamanyar.com/2015/05/unity-creating-c-thread-with-callback.html
    //---------------------------------------------------------------------------------------

    public void CalcCorrectionThread(object state)
    {
        object[]         array    = state as object[];
        ThreadAdapter    adapter  = array[1] as ThreadAdapter;
        JobResultHandler callback = array[2] as JobResultHandler;

        CorrectionData calcData = array[0] as CorrectionData;
        double         distance = ClosestApproach(calcData);

        //if adapter is not null; callback is also not null.
        if (adapter != null)
        {
            adapter.ExecuteOnUi(delegate {
                callback(distance, calcData);
            });
        }
    }
    public string CorrectionCalcAsync(double targetDistance,
                                      double targetAccuracy,
                                      double approachDistance,
                                      double maxTime,
                                      CalcCallback calcCallback)
    {
        this.calcCallback = calcCallback;
        double[] corrections = { -0.001, 0, 0.001 };
        this.targetDistance = targetDistance;
        this.targetAccuracy = targetAccuracy;

        threadsPending = 0;
        calcState      = CalcState.NOT_STARTED;

        string        s  = "";
        GravityEngine ge = GravityEngine.Instance();

        correctionData = new CorrectionData[corrections.Length];
        int i = 0;

        foreach (double correction in corrections)
        {
            // Run each computation as a dedicated thread
            correctionData[i] = new CorrectionData();
            correctionData[i].gravityState     = ge.GetGravityStateCopy();
            correctionData[i].approachDistance = approachDistance;
            correctionData[i].maxPhysTime      = maxTime;
            correctionData[i].correction       = correction;
            threadCountMutex.WaitOne();
            threadsPending++;
            threadCountMutex.ReleaseMutex();
            System.Threading.ThreadPool.QueueUserWorkItem(
                new System.Threading.WaitCallback(CalcCorrectionThread),
                new object[] { correctionData[i], CreateUnityAdapter(), (JobResultHandler)CalcResultHandler });
            i++;
        }
        calcState = CalcState.INITIAL_THREE;
        return(s);
    }
    /// <summary>
    /// Perform Lunar closest approach and course correction calculations.
    ///
    /// ClosestApproach calculations can be done on the main thread or asynch with callback.
    ///
    /// Course correction calculations can only be done async with a callback.
    ///
    /// Note that the threading APIs assume that a specific Async call will complete before another one
    /// is started. If multiple parallel computations are required, instantiate additional copies of this
    /// class.
    ///
    /// </summary>
    /// <param name="targetDistance"></param>
    /// <returns></returns>
    public double ClosestApproach(CorrectionData calcData)
    {
        long start_ms = System.DateTimeOffset.Now.Millisecond;

        Vector3 shipPos      = calcData.gravityState.GetPhysicsPosition(spaceship);
        Vector3 moonPos      = calcData.gravityState.GetPhysicsPosition(moon);
        float   lastDistance = Vector3.Distance(shipPos, moonPos);
        float   distance     = 0f;

        double end_phys_time = calcData.gravityState.time + calcData.maxPhysTime;

        searchDt = searchDt_coarse;

        // correction is applied to the ship velocity purely in the direction the ship is travelling
        // VERY simplistic approach for first attempt
        double[] shipVel = new double[] { 0, 0, 0 };
        calcData.gravityState.GetVelocityDouble(spaceship, ref shipVel);
        shipVel[0] *= (1 + calcData.correction);
        shipVel[1] *= (1 + calcData.correction);
        shipVel[2] *= (1 + calcData.correction);
        calcData.gravityState.SetVelocityDouble(spaceship, ref shipVel);

        // @TODO: dumb implementation: needs refinement
        // HACK: add a time limit on simulation run time
        const float TIME_LIMIT_SEC = 20f;
        float       timeEnd_ms     = start_ms + TIME_LIMIT_SEC * 1000;

        while ((System.DateTimeOffset.Now.Millisecond < timeEnd_ms) &&
               (calcData.gravityState.time < end_phys_time))
        {
            calcData.gravityState.Evolve(GravityEngine.Instance(), searchDt);
            // check if we have passed through min distance
            shipPos  = calcData.gravityState.GetPhysicsPosition(spaceship);
            moonPos  = calcData.gravityState.GetPhysicsPosition(moon);
            distance = Vector3.Distance(shipPos, moonPos);
            float delta = distance - lastDistance;
            // Need to be within approach distance to care (screens out cases where ship is in orbit around
            // planet and sign change in delta is triggered)
            if (distance < calcData.approachDistance)
            {
                searchDt = searchDt_fine;
                if (delta > 0f)
                {
                    break;
                }
            }
            lastDistance = distance;
        }
        calcData.distance       = distance;
        calcData.timeAtApproach = calcData.gravityState.time;
        calcData.execTimeMs     = System.DateTimeOffset.Now.Millisecond - start_ms;
        Debug.Log(string.Format("Closest approach d={0} @ t={1} maxTime={2}",
                                calcData.distance,
                                calcData.timeAtApproach,
                                calcData.maxPhysTime));
        if (calcData.gravityState.time > end_phys_time)
        {
            calcData.distance = -1;
            Debug.LogWarning("Physics evolution time exceeded");
        }
        else if (System.DateTimeOffset.Now.Millisecond > timeEnd_ms)
        {
            calcData.distance = -2;
            Debug.LogWarning("Run-time limit exceeded");
        }
        return(calcData.distance);
    }