7
7
8
8
using System ;
9
9
using System . Collections . Generic ;
10
+ using System . Diagnostics ;
10
11
using System . IO ;
11
12
using System . Threading ;
12
13
using System . Threading . Tasks ;
@@ -17,7 +18,7 @@ namespace SimSharp {
17
18
/// </summary>
18
19
/// <remarks>
19
20
/// 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.
21
22
///
22
23
/// For most purposes <see cref="Simulation"/> is however the better and faster choice.
23
24
/// </remarks>
@@ -37,10 +38,11 @@ public double NowD {
37
38
get { return ( Now - StartDate ) . TotalSeconds / DefaultTimeStepSeconds ; }
38
39
}
39
40
41
+ private DateTime now ;
40
42
/// <summary>
41
43
/// The current simulation time as a calendar date.
42
44
/// </summary>
43
- public DateTime Now { get ; protected set ; }
45
+ public virtual DateTime Now { get => now ; protected set => now = value ; }
44
46
45
47
/// <summary>
46
48
/// 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) {
206
208
var stop = ScheduleQ . Count == 0 || _stop . IsCancellationRequested ;
207
209
while ( ! stop ) {
208
210
Step ( ) ;
209
- ProcessedEvents ++ ;
210
211
stop = ScheduleQ . Count == 0 || _stop . IsCancellationRequested ;
211
212
}
212
213
} catch ( StopSimulationException e ) { OnRunFinished ( ) ; return e . Value ; }
@@ -242,6 +243,7 @@ public virtual void Step() {
242
243
Now = next . PrimaryPriority ;
243
244
evt = next . Event ;
244
245
evt . Process ( ) ;
246
+ ProcessedEvents ++ ;
245
247
}
246
248
247
249
/// <summary>
@@ -373,6 +375,7 @@ public TimeSpan RandExponential(TimeSpan mean) {
373
375
374
376
private bool useSpareNormal = false ;
375
377
private double spareNormal = double . NaN ;
378
+
376
379
/// <summary>
377
380
/// Uses the Marsaglia polar method to generate a random variable
378
381
/// from two uniform random distributed values.
@@ -803,7 +806,6 @@ public override object Run(Event stopEvent = null) {
803
806
}
804
807
while ( ! stop ) {
805
808
Step ( ) ;
806
- ProcessedEvents ++ ;
807
809
lock ( _locker ) {
808
810
stop = ScheduleQ . Count == 0 || _stop . IsCancellationRequested ;
809
811
}
@@ -815,6 +817,23 @@ public override object Run(Event stopEvent = null) {
815
817
return stopEvent . Value ;
816
818
}
817
819
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
+
818
837
/// <summary>
819
838
/// Performs a single step of the simulation, i.e. process a single event
820
839
/// </summary>
@@ -829,6 +848,7 @@ public override void Step() {
829
848
evt = next . Event ;
830
849
}
831
850
evt . Process ( ) ;
851
+ ProcessedEvents ++ ;
832
852
}
833
853
834
854
/// <summary>
@@ -857,60 +877,115 @@ public override DateTime Peek() {
857
877
}
858
878
}
859
879
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 ;
865
893
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 ;
872
904
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 ;
875
912
}
876
913
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
+ }
878
929
879
930
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
+
881
949
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 ( ) ;
898
955
}
956
+ return ; // next event is not processed, step is not actually completed
899
957
}
900
958
}
901
959
}
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 ++ ;
903
972
}
904
973
905
974
/// <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"/>.
908
977
/// </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 ( ) {
911
985
lock ( _locker ) {
912
- RealTimeFactor = null ;
913
- _delay ? . Cancel ( ) ; // TODO: Same lock region
986
+ if ( ! IsRunningInRealtime ) return ;
987
+ RealtimeScale = null ;
988
+ _rtDelayCtrl ? . Cancel ( ) ;
914
989
}
915
990
}
916
991
@@ -919,21 +994,37 @@ public virtual void SwitchToVirtualTime() {
919
994
/// this default mode is configurable.
920
995
/// </summary>
921
996
/// <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.
924
1000
///
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 .
929
1005
/// </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 ) {
932
1008
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 ;
935
1012
}
936
1013
}
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
+ }
937
1028
}
938
1029
939
1030
/// <summary>
0 commit comments