view mde/scheduler/runTime.d @ 26:611f7b9063c6

Changed the licensing and removed a few dead files. Changed licensing to "GPL version 2 or later" to avoid future compatibility issues. Also a unittest fix to the previous commit. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Thu, 03 Apr 2008 18:15:02 +0100
parents 47478557428d
children
line wrap: on
line source

/* LICENSE BLOCK
Part of mde: a Modular D game-oriented Engine
Copyright © 2007-2008 Diggory Hardy

This program is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either
version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>. */

/** Scheduler
*/
module mde.scheduler.runTime;

import tango.time.Time;

debug {
    import tango.util.log.Log : Log, Logger;
    private Logger logger;
}
static this() {
    debug logger = Log.getLogger ("mde.scheduler.runTime");
}

/** Some enums used by per request functions. */
enum RF_KEYS : uint { DRAW };

// NOTE: Currently has no support for removing functions. To fix, assign ID and store fct pointers
// in an associative array, returning the ID [on adding fct pointer].
// NOTE: support delegates or not?
/// This class can run scheduled functions per frame or every t seconds (sim-time).
abstract class Scheduler
{
    /** The type of function pointer to be passed to the scheduler.
    *
    * The double $(I time) parameter gives the number of (sim) seconds since the function was last
    * called, or zero on the first run. */
    alias void function (double time) scheduleFct;
    
    /** Add a function to be called per frame. */
    static void perFrame (scheduleFct fct) {
        frameFcts ~= fct;
    }
    
    /** Add a function to be called per t secs or n 100-nano-sec intevals.
    *
    * Since the scheduler cannot guarantee a maximum time between calls, the interval at which
    * functions are called is always greater than or equal to the inverval specified here. Of
    * course, the actual inteval is given when the function is run.
    */
    static void perTime (double t, scheduleFct fct) {
        perTime (TimeSpan.interval(t), fct);
    }
    /** ditto */
    static void perTime (TimeSpan n, scheduleFct fct)
    in { assert (n > TimeSpan (0L)); }
    body {
        timeFcts ~= new TimeFct (fct, n);
    }
    
    /** Add a function to be called per requested update.
    *
    * A bool parameter is stored locally describing whether or not the function needs recalling,
    * and is set true upon creation and when requestUpdate is called with the same key. The
    * function is then called by scheduler's run() whenever this bool variable is true.
    */
    static void perRequest (uint key, void function() fct)
    in {
        debug if ((key in requestFcts) is null)
            logger.warn ("perRequest: replacing existing function with same key!");
    }
    body {
        requestFcts[key] = fct;
        requestFctsUpdate[key] = true;
    }
    
    /** Request an update to request function key. */
    static void requestUpdate (uint key) {
        // Note: check the value for this key actually exists
        bool* p = key in requestFctsUpdate;
        if (p) *p = true;
        else debug logger.warn ("requestUpdate called with invalid key");
    }
    
    /** This function should get called by the main loop, once per frame.
    *
    * The parameter time should be the current sim-time, using the tango.core.Types.Time enum; all
    * time evaluations will use this.
    */
    static void run (Time time) {
        double interval;
        
        // Call all per-frame functions:
        if (lastTime == Time (0L)) interval = 0.0;		// 0 interval for first loop
        else interval = (time-lastTime).interval();
        
        foreach (fct; frameFcts) fct(interval);
        
        // Call all per-interval functions:
        foreach (fct; timeFcts) if (time >= fct.nextCall) {
            if (fct.nextCall == Time (0L)) interval = 0.0;	// 0 interval for first call
            else interval = (time - (fct.nextCall - fct.interval)).interval();
            fct.nextCall = time + fct.interval;		// when to call next
            
            fct.fct (interval);				// call
        }
        
        // Call all per-request functions:
        foreach (key, fct; requestFcts) {
            if (requestFctsUpdate[key]) {
                fct();
                requestFctsUpdate[key] = false;
            }
        }
    }
    
    /* Holds details for functions called per time interval.
    * Needs to be a reference type, and using a class is easiest for this. */
    private static class TimeFct {
        scheduleFct fct;			// function to call
        
        TimeSpan interval;			// interval to call at
        // Storing nextCall is more efficient than storing lastCall since only this number has to
        // be compared to time every frame where fct is not called:
        Time nextCall = Time (0L);
        
        this (scheduleFct f, TimeSpan t) {
            fct = f;
            interval = t;
        }
    }
    
    private static Time lastTime = Time (0L);
    private static scheduleFct[] frameFcts;
    private static TimeFct[] timeFcts;
    private static void function()[uint] requestFcts;
    private static bool[uint] requestFctsUpdate;    // associated with requestFcts
}