source: 2014/24/EemeliK/Zombieland/Jypeli/Physics2DDotNet/PhysicsTimer.cs @ 5974

Revision 5974, 9.3 KB checked in by empaheik, 5 years ago (diff)
Line 
1#region MIT License
2/*
3 * Copyright (c) 2005-2008 Jonathan Mark Porter. http://physics2d.googlepages.com/
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights to
8 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 * the Software, and to permit persons to whom the Software is furnished to do so,
10 * subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
17 * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20 * OTHER DEALINGS IN THE SOFTWARE.
21 */
22#endregion
23
24
25
26
27#if UseDouble
28using Scalar = System.Double;
29#else
30using Scalar = System.Single;
31#endif
32using System;
33using System.Threading;
34
35
36namespace Physics2DDotNet
37{
38
39    /// <summary>
40    /// The State of a PhysicsTimer
41    /// </summary>
42    public enum TimerState
43    {
44        NotStarted,
45        /// <summary>
46        /// The PhysicsTimer is Paused.
47        /// </summary>
48        Paused,
49        /// <summary>
50        /// The PhysicsTimer's calls to the Callback are on time.
51        /// </summary>
52        Normal,
53        /// <summary>
54        /// The PhysicsTimer's calls to the Callback are behind schedule.
55        /// </summary>
56        Slow,
57        /// <summary>
58        /// The PhysicsTimer's calls to the Callback are delayed to be on time.
59        /// </summary>
60        Fast,
61        /// <summary>
62        /// The PhysicsTimer is Disposed.
63        /// </summary>
64        Disposed,
65    }
66    /// <summary>
67    /// A Callback used by the PhysicsTimer
68    /// </summary>
69    /// <param name="dt">The change in time.</param>
70    public delegate void PhysicsCallback(Scalar dt, Scalar trueDt);
71
72    /// <summary>
73    /// A class to update the PhysicsEngine at regular intervals.
74    /// </summary>
75    public sealed class PhysicsTimer : IDisposable
76    {
77        #region static
78        static int threadCount;
79        #endregion
80        #region events
81        public event EventHandler IsRunningChanged;
82        #endregion
83        #region fields
84        bool isBackground;
85        bool isDisposed;
86        bool isRunning;
87
88        TimerState state;
89        Scalar targetInterval;
90        PhysicsCallback callback;
91        AutoResetEvent waitHandle;
92        Thread engineThread;
93        #endregion
94        #region constructors
95        /// <summary>
96        /// Creates a new PhysicsTimer Instance.
97        /// </summary>
98        /// <param name="callback">The callback to call.</param>
99        /// <param name="targetDt">The target change in time. (in seconds)</param>
100        public PhysicsTimer(PhysicsCallback callback, Scalar targetInterval)
101        {
102            if (callback == null) { throw new ArgumentNullException("callback"); }
103            if (targetInterval <= 0) { throw new ArgumentOutOfRangeException("targetInterval"); }
104            this.isBackground = true;
105            this.state = TimerState.NotStarted;
106            this.targetInterval = targetInterval;
107            this.callback = callback;
108            this.waitHandle = new AutoResetEvent(true);
109        }
110        #endregion
111        #region properties
112        /// <summary>
113        /// Gets or sets a value indicating whether or not the thread that runs the time is a background thread.
114        /// </summary>
115        public bool IsBackground
116        {
117            get { return isBackground; }
118            set
119            {
120                if (isDisposed) { throw new ObjectDisposedException(typeof(PhysicsTimer).Name); }
121                if (isBackground ^ value)
122                {
123                    isBackground = value;
124                    if (engineThread != null)
125                    {
126                        engineThread.IsBackground = value;
127                    }
128                }
129            }
130        }
131        /// <summary>
132        /// Gets and Sets if the PhysicsTimer is currently calling the Callback.
133        /// </summary>
134        public bool IsRunning
135        {
136            get
137            {
138                return isRunning;
139            }
140            set
141            {
142                if (isDisposed) { throw new ObjectDisposedException(typeof(PhysicsTimer).Name); }
143                if (this.isRunning ^ value)
144                {
145                    this.isRunning = value;
146                    if (value)
147                    {
148                        if (this.engineThread == null)
149                        {
150                            this.engineThread = new Thread(EngineProcess);
151                            this.engineThread.IsBackground = isBackground;
152                            this.engineThread.Name = string.Format("PhysicsEngine Thread: {0}", Interlocked.Increment(ref threadCount));
153                            this.engineThread.Start();
154                        }
155                        else
156                        {
157                            waitHandle.Set();
158                        }
159                    }
160                    if (IsRunningChanged != null) { IsRunningChanged(this, EventArgs.Empty); }
161                }
162            }
163        }
164        /// <summary>
165        /// Gets and Sets the desired Interval between Callback calls.
166        /// </summary>
167        public Scalar TargetInterval
168        {
169            get { return targetInterval; }
170            set
171            {
172                if (isDisposed) { throw new ObjectDisposedException(this.ToString()); }
173                if (value <= 0) { throw new ArgumentOutOfRangeException("value"); }
174                this.targetInterval = value;
175            }
176        }
177        /// <summary>
178        /// Gets the current State of the PhysicsTimer.
179        /// </summary>
180        public TimerState State
181        {
182            get { return state; }
183        }
184        /// <summary>
185        /// Gets and Sets the current Callback that will be called.
186        /// </summary>
187        public PhysicsCallback Callback
188        {
189            get { return callback; }
190            set
191            {
192                if (isDisposed) { throw new ObjectDisposedException(this.ToString()); }
193                if (value == null) { throw new ArgumentNullException("value"); }
194                callback = value;
195            }
196        }
197        #endregion
198        #region methods
199        /// <summary>
200        /// Stops the Timer
201        /// </summary>
202        public void Dispose()
203        {
204            if (!isDisposed)
205            {
206                isDisposed = true;
207                isRunning = false;
208                waitHandle.Set();
209                waitHandle.Close();
210                state = TimerState.Disposed;
211            }
212        }
213        void EngineProcess()
214        {
215            Scalar desiredDt = targetInterval * 1000;
216            DateTime lastRun = DateTime.Now;
217            Scalar extraDt = 0;
218            while (!isDisposed)
219            {
220                if (isRunning)
221                {
222                    DateTime now = DateTime.Now;
223                    Scalar dt = (Scalar)(now.Subtract(lastRun).TotalMilliseconds);
224                    Scalar currentDt = extraDt + dt;
225                    if (currentDt < desiredDt)
226                    {
227                        state = TimerState.Fast;
228                        int sleep = (int)Math.Ceiling(desiredDt - currentDt);
229#if SILVERLIGHT || WINDOWS_PHONE || XBOX
230                        waitHandle.WaitOne(sleep);
231#else
232                        waitHandle.WaitOne(sleep, false);
233#endif
234                    }
235                    else
236                    {
237                        extraDt = currentDt - desiredDt;
238                        if (extraDt > desiredDt)
239                        {
240                            extraDt = desiredDt;
241                            state = TimerState.Slow;
242                        }
243                        else
244                        {
245                            state = TimerState.Normal;
246                        }
247                        lastRun = now;
248                        callback(targetInterval, dt * (1 / 1000f));
249                    }
250                }
251                else
252                {
253                    state = TimerState.Paused;
254                    waitHandle.WaitOne();
255                    lastRun = DateTime.Now;
256                    extraDt = 0;
257                }
258            }
259            state = TimerState.Disposed;
260        }
261        public void RunOnCurrentThread()
262        {
263            if (this.engineThread != null) { throw new InvalidOperationException("Timer must be NotStarted"); }
264            this.engineThread = Thread.CurrentThread;
265            this.isRunning = true;
266            EngineProcess();
267        }
268        #endregion
269    }
270}
Note: See TracBrowser for help on using the repository browser.