77
88using System ;
99using System . Collections . Generic ;
10+ using System . Diagnostics ;
1011using System . IO ;
1112using System . Threading ;
1213using System . Threading . Tasks ;
@@ -17,7 +18,7 @@ namespace SimSharp {
1718 /// </summary>
1819 /// <remarks>
1920 /// This class is not thread-safe against manipulation of the event queue. If you supply a termination
20- /// event that is set outside the simulation, please use the <see cref="ThreadSafeSimulation"/> environment.
21+ /// event that is set outside the simulation thread , please use the <see cref="ThreadSafeSimulation"/> environment.
2122 ///
2223 /// For most purposes <see cref="Simulation"/> is however the better and faster choice.
2324 /// </remarks>
@@ -37,10 +38,11 @@ public double NowD {
3738 get { return ( Now - StartDate ) . TotalSeconds / DefaultTimeStepSeconds ; }
3839 }
3940
41+ private DateTime now ;
4042 /// <summary>
4143 /// The current simulation time as a calendar date.
4244 /// </summary>
43- public DateTime Now { get ; protected set ; }
45+ public virtual DateTime Now { get => now ; protected set => now = value ; }
4446
4547 /// <summary>
4648 /// The calendar date when the simulation started. This defaults to 1970-1-1 if
@@ -206,7 +208,6 @@ public virtual object Run(Event stopEvent = null) {
206208 var stop = ScheduleQ . Count == 0 || _stop . IsCancellationRequested ;
207209 while ( ! stop ) {
208210 Step ( ) ;
209- ProcessedEvents ++ ;
210211 stop = ScheduleQ . Count == 0 || _stop . IsCancellationRequested ;
211212 }
212213 } catch ( StopSimulationException e ) { OnRunFinished ( ) ; return e . Value ; }
@@ -242,6 +243,7 @@ public virtual void Step() {
242243 Now = next . PrimaryPriority ;
243244 evt = next . Event ;
244245 evt . Process ( ) ;
246+ ProcessedEvents ++ ;
245247 }
246248
247249 /// <summary>
@@ -373,6 +375,7 @@ public TimeSpan RandExponential(TimeSpan mean) {
373375
374376 private bool useSpareNormal = false ;
375377 private double spareNormal = double . NaN ;
378+
376379 /// <summary>
377380 /// Uses the Marsaglia polar method to generate a random variable
378381 /// from two uniform random distributed values.
@@ -803,7 +806,6 @@ public override object Run(Event stopEvent = null) {
803806 }
804807 while ( ! stop ) {
805808 Step ( ) ;
806- ProcessedEvents ++ ;
807809 lock ( _locker ) {
808810 stop = ScheduleQ . Count == 0 || _stop . IsCancellationRequested ;
809811 }
@@ -815,6 +817,23 @@ public override object Run(Event stopEvent = null) {
815817 return stopEvent . Value ;
816818 }
817819
820+ public Task < object > RunAsync ( TimeSpan duration ) {
821+ return Task . Run ( ( ) => Run ( duration ) ) ;
822+ }
823+
824+ public Task < object > RunAsync ( DateTime until ) {
825+ return Task . Run ( ( ) => Run ( until ) ) ;
826+ }
827+
828+ /// <summary>
829+ /// Run until a certain event is processed, but does not block.
830+ /// </summary>
831+ /// <param name="stopEvent">The event that stops the simulation.</param>
832+ /// <returns></returns>
833+ public Task < object > RunAsync ( Event stopEvent = null ) {
834+ return Task . Run ( ( ) => Run ( stopEvent ) ) ;
835+ }
836+
818837 /// <summary>
819838 /// Performs a single step of the simulation, i.e. process a single event
820839 /// </summary>
@@ -829,6 +848,7 @@ public override void Step() {
829848 evt = next . Event ;
830849 }
831850 evt . Process ( ) ;
851+ ProcessedEvents ++ ;
832852 }
833853
834854 /// <summary>
@@ -857,60 +877,115 @@ public override DateTime Peek() {
857877 }
858878 }
859879
860- public class PseudoRealTimeSimulation : ThreadSafeSimulation {
861- private const double DefaultRealTimeFactor = 1.0 ;
862-
863- public double ? RealTimeFactor { get ; protected set ; } = DefaultRealTimeFactor ;
864- public bool IsRunningInRealTime => RealTimeFactor . HasValue ;
880+ /// <summary>
881+ /// Provides a simulation environment where delays in simulation time may result in a similar
882+ /// delay in wall-clock time. The environment is not an actual realtime simulation environment
883+ /// in that there is no guarantee that 3 seconds in model time are also exactly 3 seconds in
884+ /// observed wall-clock time. This simulation environment is a bit slower, as the overhead of
885+ /// the simulation kernel (event creation, queuing, processing, etc.) is not accounted for.
886+ ///
887+ /// However, it features a switch between virtual and realtime, thus allowing it to be used
888+ /// in contexts where realtime is only necessary sometimes (e.g. during interaction with
889+ /// long-running co-processes). Such use cases may arise in simulation control problems.
890+ /// </summary>
891+ public class PseudoRealtimeSimulation : ThreadSafeSimulation {
892+ public const double DefaultRealtimeScale = 1 ;
865893
866- public PseudoRealTimeSimulation ( ) : this ( new DateTime ( 1970 , 1 , 1 ) ) { }
867- public PseudoRealTimeSimulation ( TimeSpan ? defaultStep ) : this ( new DateTime ( 1970 , 1 , 1 ) , defaultStep ) { }
868- public PseudoRealTimeSimulation ( DateTime initialDateTime , TimeSpan ? defaultStep = null ) : this ( new PcgRandom ( ) , initialDateTime , defaultStep ) { }
869- public PseudoRealTimeSimulation ( int randomSeed , TimeSpan ? defaultStep = null ) : this ( new DateTime ( 1970 , 1 , 1 ) , randomSeed , defaultStep ) { }
870- public PseudoRealTimeSimulation ( DateTime initialDateTime , int randomSeed , TimeSpan ? defaultStep = null ) : this ( new PcgRandom ( randomSeed ) , initialDateTime , defaultStep ) { }
871- public PseudoRealTimeSimulation ( IRandom random , DateTime initialDateTime , TimeSpan ? defaultStep = null ) : base ( random , initialDateTime , defaultStep ) { }
894+ /// <summary>
895+ /// The scale at which the simulation runs in comparison to realtime. A value smaller
896+ /// than 1 results in longer-than-realtime delays, while a value larger than 1 results
897+ /// in shorter-than-realtime delays. A value of exactly 1 is realtime.
898+ /// </summary>
899+ public double ? RealtimeScale { get ; protected set ; } = DefaultRealtimeScale ;
900+ /// <summary>
901+ /// Whether a non-null <see cref="RealtimeScale"/> has been set.
902+ /// </summary>
903+ public bool IsRunningInRealtime => RealtimeScale . HasValue ;
872904
873- public override void StopAsync ( ) {
874- base . StopAsync ( ) ;
905+ private object _timeLocker = new object ( ) ;
906+ /// <summary>
907+ /// The current model time. Note that, while in realtime, this may continuously change.
908+ /// </summary>
909+ public override DateTime Now {
910+ get { lock ( _timeLocker ) { return base . Now + _rtDelayTime . Elapsed ; } }
911+ protected set => base . Now = value ;
875912 }
876913
877- protected CancellationTokenSource _delay = null ;
914+ protected CancellationTokenSource _rtDelayCtrl = null ;
915+ protected Stopwatch _rtDelayTime = new Stopwatch ( ) ;
916+
917+
918+ public PseudoRealtimeSimulation ( ) : this ( new DateTime ( 1970 , 1 , 1 ) ) { }
919+ public PseudoRealtimeSimulation ( TimeSpan ? defaultStep ) : this ( new DateTime ( 1970 , 1 , 1 ) , defaultStep ) { }
920+ public PseudoRealtimeSimulation ( DateTime initialDateTime , TimeSpan ? defaultStep = null ) : this ( new PcgRandom ( ) , initialDateTime , defaultStep ) { }
921+ public PseudoRealtimeSimulation ( int randomSeed , TimeSpan ? defaultStep = null ) : this ( new DateTime ( 1970 , 1 , 1 ) , randomSeed , defaultStep ) { }
922+ public PseudoRealtimeSimulation ( DateTime initialDateTime , int randomSeed , TimeSpan ? defaultStep = null ) : this ( new PcgRandom ( randomSeed ) , initialDateTime , defaultStep ) { }
923+ public PseudoRealtimeSimulation ( IRandom random , DateTime initialDateTime , TimeSpan ? defaultStep = null ) : base ( random , initialDateTime , defaultStep ) { }
924+
925+ protected override EventQueueNode DoSchedule ( DateTime date , Event @event , int priority = 0 ) {
926+ if ( ScheduleQ . Count > 0 && date < ScheduleQ . First . PrimaryPriority ) _rtDelayCtrl ? . Cancel ( ) ;
927+ return base . DoSchedule ( date , @event , priority ) ;
928+ }
878929
879930 public override void Step ( ) {
880- if ( IsRunningInRealTime ) {
931+ var delay = TimeSpan . Zero ;
932+ double ? rtScale = null ;
933+ lock ( _locker ) {
934+ if ( IsRunningInRealtime ) {
935+ rtScale = RealtimeScale ;
936+ var next = ScheduleQ . First . PrimaryPriority ;
937+ delay = next - base . Now ;
938+ if ( rtScale . Value != 1.0 ) delay = TimeSpan . FromMilliseconds ( delay . TotalMilliseconds / rtScale . Value ) ;
939+ _rtDelayCtrl = CancellationTokenSource . CreateLinkedTokenSource ( _stop . Token ) ;
940+ }
941+ }
942+
943+ if ( delay > TimeSpan . Zero ) {
944+ _rtDelayTime . Start ( ) ;
945+ Task . Delay ( delay , _rtDelayCtrl . Token ) . ContinueWith ( _ => { } ) . Wait ( ) ;
946+ _rtDelayTime . Stop ( ) ;
947+ var observed = _rtDelayTime . Elapsed ;
948+
881949 lock ( _locker ) {
882- if ( IsRunningInRealTime ) {
883- var next = ScheduleQ . First . PrimaryPriority ;
884- var delay = next - Now ;
885- if ( RealTimeFactor . Value != 1.0 ) delay = TimeSpan . FromMilliseconds ( delay . Milliseconds / RealTimeFactor . Value ) ;
886- if ( delay > TimeSpan . Zero ) {
887- _delay = CancellationTokenSource . CreateLinkedTokenSource ( _stop . Token ) ;
888- var then = DateTime . UtcNow ;
889- Task . Delay ( delay , _delay . Token ) . Wait ( ) ;
890- if ( _delay . IsCancellationRequested ) {
891- var now = DateTime . UtcNow ;
892- var observedDelay = now - then ;
893- if ( observedDelay < delay ) {
894- Now += observedDelay ;
895- return ; // next event is not processed
896- }
897- }
950+ if ( rtScale . Value != 1.0 ) observed = TimeSpan . FromMilliseconds ( observed . TotalMilliseconds / rtScale . Value ) ;
951+ if ( _rtDelayCtrl . IsCancellationRequested && observed < delay ) {
952+ lock ( _timeLocker ) {
953+ Now = base . Now + observed ;
954+ _rtDelayTime . Reset ( ) ;
898955 }
956+ return ; // next event is not processed, step is not actually completed
899957 }
900958 }
901959 }
902- base . Step ( ) ;
960+
961+ Event evt ;
962+ lock ( _locker ) {
963+ var next = ScheduleQ . Dequeue ( ) ;
964+ lock ( _timeLocker ) {
965+ _rtDelayTime . Reset ( ) ;
966+ Now = next . PrimaryPriority ;
967+ }
968+ evt = next . Event ;
969+ }
970+ evt . Process ( ) ;
971+ ProcessedEvents ++ ;
903972 }
904973
905974 /// <summary>
906- /// Switches the simulation to virtual time mode. In this mode, events
907- /// are processed without delay just like in a <see cref="ThreadSafeSimulation"/>.
975+ /// Switches the simulation to virtual time mode, i.e., running as fast as possible.
976+ /// In this mode, events are processed without delay just like in a <see cref="ThreadSafeSimulation"/>.
908977 /// </summary>
909- public virtual void SwitchToVirtualTime ( ) {
910- if ( ! IsRunningInRealTime ) return ;
978+ /// <remarks>
979+ /// An ongoing real-time delay is being canceled when this method is called. Usually, this
980+ /// is only the case when this method is called from a thread other than the main simulation thread.
981+ ///
982+ /// If the simulation is already in virtual time mode, this method has no effect.
983+ /// </remarks>
984+ public virtual void SetVirtualtime ( ) {
911985 lock ( _locker ) {
912- RealTimeFactor = null ;
913- _delay ? . Cancel ( ) ; // TODO: Same lock region
986+ if ( ! IsRunningInRealtime ) return ;
987+ RealtimeScale = null ;
988+ _rtDelayCtrl ? . Cancel ( ) ;
914989 }
915990 }
916991
@@ -919,21 +994,37 @@ public virtual void SwitchToVirtualTime() {
919994 /// this default mode is configurable.
920995 /// </summary>
921996 /// <remarks>
922- /// Per default, a <see cref="PseudoRealTimeSimulation"/> is executed
923- /// in real time with a simulation speed factor of 1.0.
997+ /// If this method is called while running in real-time mode, but given a different
998+ /// <paramref name="realtimeScale"/>, the current delay is canceled and the remaining
999+ /// time is delayed using the new time factor.
9241000 ///
925- /// With a factor of 1.0, a timeout of 5.0 seconds would delay the
926- /// simulation for 5.0 seconds. With a factor of 2.0 , the same timeout
927- /// would delay the simulation for 2.5 seconds, whereas a factor of
928- /// 0.5 would delay the simulation for 10.0 seconds .
1001+ /// The default factor is 1, i.e., real time - a timeout of 5 seconds would cause
1002+ /// a wall-clock delay of 5 seconds. With a factor of 2, the delay as measured by
1003+ /// a wall clock would be 2.5 seconds, whereas a factor of 0.5, a wall-clock delay of
1004+ /// 10 seconds would be observed .
9291005 /// </remarks>
930- /// <param name="realTimeFactor ">A factor greater than 0.0 used to scale real time events (higher value = faster execution) .</param>
931- public virtual void SwitchToRealTime ( double realTimeFactor = DefaultRealTimeFactor ) {
1006+ /// <param name="realtimeScale ">A value strictly greater than 0 used to scale real time events.</param>
1007+ public virtual void SetRealtime ( double realtimeScale = DefaultRealtimeScale ) {
9321008 lock ( _locker ) {
933- if ( realTimeFactor <= 0.0 ) throw new ArgumentException ( "The simulation speed scaling factor must be strictly positive." , nameof ( realTimeFactor ) ) ;
934- RealTimeFactor = realTimeFactor ;
1009+ if ( realtimeScale <= 0.0 ) throw new ArgumentException ( "The simulation speed scaling factor must be strictly positive." , nameof ( realtimeScale ) ) ;
1010+ if ( IsRunningInRealtime && realtimeScale != RealtimeScale ) _rtDelayCtrl ? . Cancel ( ) ;
1011+ RealtimeScale = realtimeScale ;
9351012 }
9361013 }
1014+
1015+ /// <summary>
1016+ /// This is only a convenience for mixed real- and virtual time simulations.
1017+ /// It creates a new pseudo realtime process which will set the simulation
1018+ /// to realtime every time it continues (e.g., if it has been set to virtual time).
1019+ /// The process is automatically scheduled to be started at the current simulation time.
1020+ /// </summary>
1021+ /// <param name="generator">The generator function that represents the process.</param>
1022+ /// <param name="priority">The priority to rank events at the same time (smaller value = higher priority).</param>
1023+ /// <param name="realtimeScale">A value strictly greater than 0 used to scale real time events (1 = realtime).</param>
1024+ /// <returns>The scheduled process that was created.</returns>
1025+ public Process PseudoRealtimeProcess ( IEnumerable < Event > generator , int priority = 0 , double realtimeScale = DefaultRealtimeScale ) {
1026+ return new PseudoRealtimeProcess ( this , generator , priority , realtimeScale ) ;
1027+ }
9371028 }
9381029
9391030 /// <summary>
0 commit comments