Mercurial > projects > mde
view mde/scheduler/Scheduler.d @ 101:71f0f1f83620
Some path adjustments for windows (untested) and fonts. All types of option can be edited.
paths: support for getting the full path for a font when just the file name is entered, in order to unify usage on windows and linux.
paths: Used getSpecialPath for some windows paths; needs testing.
Content: Moved line-editing code to abstract ValueContent class and added some conversion functions, so that any type of ValueContent can be edited as text.
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Sun, 16 Nov 2008 17:03:47 +0000 |
parents | 2a364c7d82c9 |
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/>. */ /** A fairly generic scheduler. * * This class implements most functionality a generic scheduler might want, however currently it * doesn't any uses where equivalent functionality couldn't be achived very easily anyway. */ module mde.scheduler.Scheduler; import mde.util; public import tango.time.Time; debug { import tango.util.log.Log : Log, Logger; private Logger logger; static this() { logger = Log.getLogger ("mde.scheduler.Scheduler"); } } /// This class can run scheduled functions per frame, per time interval and per request. class Scheduler { /** The type of function callback to be added to the scheduler. * * The parameter time gives the time since the function was last called, or zero on the first * run. */ alias void function (TimeSpan time) scheduleFct; alias void delegate (TimeSpan time) scheduleDlg; /// ditto alias uint ID; /// This is the type of identifier used by add/get/remove/request /** The struct used to store the function and scheduling information */ class ScheduleFunc { /// Set the function this represents this (scheduleDlg f) { fct = f; } /** Quick way to set scheduling. * * Function will be scheduled each _frame if frame is true, or when requested, or each * interval if interval is positive. */ ScheduleFunc set (bool frame, TimeSpan interval) { this.frame = frame; if (interval < TimeSpan.zero) interval = TimeSpan.zero; this.interval = interval; return this; } bool frame; /// Call function each time execute() runs bool request; /// Call function the next time execute() runs TimeSpan interval; /// Call the function when the last call was longer than interval ago package: scheduleDlg fct = null; // function to call Time lastCall = zero; // time of last call; zero at start (special case) } /** Add a function to be scheduled. * * This function should have a unique identifier, which can be used with get/remove/request. * The identifier can be supplied or generated by getNewID(). * * Use the returned pointer to set the scheduling, e.g.: * ----- * scheduler.add(scheduler.getNewID, myFunction).set(false, TimeSpan.fromMillis (10)); * scheduler.get(15).frame = true; */ ScheduleFunc add (ID id, scheduleFct func) { return add (id, toDg(func)); } /** ditto */ ScheduleFunc add (ID id, scheduleDlg func) in { debug if ((id in funcs) !is null) logger.error ("Duplicate ID used!"); } body { ScheduleFunc sf = new ScheduleFunc (func); funcs[id] = sf; // add return sf; // and return for chain-calling } /** Get function with ID id. */ ScheduleFunc get (ID id) { try { return funcs[id]; } catch (Exception) { debug logger.error ("get(): ID does not exist!"); } } /** Remove function with ID id. */ void remove (ID id) { try { funcs.remove(id); } catch (Exception) { debug logger.error ("remove(): ID does not exist!"); } } /** Request that function with ID id is called next time execute() runs. */ void request (ID id) { get(id).request = true; } /** Generate an ID. All generated IDs are >= 0xF000_0000 to provide plenty of room for other * IDs. */ ID getNewID () { if (funcs.length == 0) return 0xF000_0000; // otherwise would get an out-of-bounds error // Take the last used ID and add one, making sure it's at least 0xF000_0000. // Don't bother checking if it's out of bounds since there's 2^28 available IDs. ID i = funcs.keys[$-1] + 1; if (i < 0xF000_0000) i = 0xF000_0000; return i; } /** This function should get called by the main loop, once per frame. * * Params: * time = the current sim-time (tango.time.Time.Time); all time evaluations will use this * all = skip normal tests and call all functions (still cancelling requests and updating call * times for correct running next time execute() is called with all = false) */ void execute (Time time, bool all = false) { foreach (func; funcs) { // The interval since the function was last run. In order to be correct for more // complex cases, it must be calculated per function. TimeSpan interval; // Per frame/request: if (func.frame || func.request || all) { if (func.lastCall == zero) interval = TimeSpan.zero; // first call else interval = (time - func.lastCall); func.fct (interval); // call func.request = false; // cancel regardless of last value func.lastCall = time; } // Per-interval functions: else if ((func.interval != TimeSpan.zero) && // has a per-interval schedule (time >= (func.lastCall + func.interval))) // time to call again { if (func.lastCall == zero) interval = TimeSpan.zero; // first call else interval = (time - func.lastCall); func.fct (interval); // call func.lastCall = time; } } } private: static const Time zero = Time(0L); Time lastTime = zero; ScheduleFunc[ID] funcs; debug (mdeUnitTest) unittest { Scheduler s = new Scheduler; int ctr1 = 0; void inc1 (TimeSpan) { ++ctr1; } s.add(1,&inc1).frame = true; TimeSpan interval = TimeSpan.fromMillis(1);// non-zero so we can check zero after first call void perInt (TimeSpan i) { interval = i; } s.add(2,&perInt).set(false, TimeSpan.fromMillis(10)); Time t = Time.epoch1970; // starting time (value isn't important) s.execute (t); assert (ctr1 == 1); // called once assert (interval == TimeSpan.zero); // initial interval t += TimeSpan.fromMillis (5); // 5ms later... s.execute (t); assert (ctr1 == 2); assert (interval == TimeSpan.zero); // perInt shouldn't get called s.get(1).frame = false; // don't call per-frame anymore s.get(1).request = true; // but request next call t += TimeSpan.fromMillis (5); s.execute (t); assert (ctr1 == 3); // as requested assert (interval == TimeSpan.fromMillis (10)); // perInt should get called (just!) s.request(2); // request this t += TimeSpan.fromMillis (8); s.execute (t); assert (ctr1 == 3); // inc1 shouldn't run assert (interval == TimeSpan.fromMillis (8)); // perInt was requested t += TimeSpan.fromMillis (4); s.execute (t); // check perInt's last call-time was updated by the request, so it doesn't get run now: assert (interval == TimeSpan.fromMillis (8)); logger.info ("Unittest complete."); } }