diff --git a/CHANGELOG.md b/CHANGELOG.md index 41575e3..72412ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.2.0 + +* Setting to determine how often `Future.delayed` is used instead of `Future.microtask` during event execution to allow GUI refresh. +* Adding numeric properties and counter. + ## 0.1.0 * Initial release diff --git a/example/example.dart b/example/example.dart index 1676426..11ee7aa 100644 --- a/example/example.dart +++ b/example/example.dart @@ -1,5 +1,4 @@ import 'package:simdart/simdart.dart'; -import 'package:simdart/src/sim_result.dart'; void main() async { final SimDart sim = SimDart(includeTracks: true); diff --git a/lib/simdart.dart b/lib/simdart.dart index 385c099..68e6f2f 100644 --- a/lib/simdart.dart +++ b/lib/simdart.dart @@ -1,4 +1,5 @@ export 'src/event.dart'; +export 'src/event_context.dart'; export 'src/interval.dart'; export 'src/observable.dart'; export 'src/resource_configurator.dart' hide ResourcesConfiguratorHelper; @@ -6,3 +7,6 @@ export 'src/simdart.dart' hide SimDartHelper; export 'src/simulation_track.dart'; export 'src/start_time_handling.dart'; export 'src/sim_result.dart'; +export 'src/sim_num.dart'; +export 'src/sim_property.dart'; +export 'src/sim_counter.dart'; diff --git a/lib/src/event.dart b/lib/src/event.dart index bca21f0..df8a9de 100644 --- a/lib/src/event.dart +++ b/lib/src/event.dart @@ -1,29 +1,7 @@ -import 'dart:async'; -import 'dart:math'; - -import 'package:simdart/src/internal/event_scheduler_interface.dart'; +import 'package:simdart/simdart.dart'; /// The event to be executed. /// /// A function that represents an event in the simulation. It receives an /// [EventContext] that provides data about the event's execution state and context. typedef Event = void Function(EventContext context); - -/// Represents the context of an event in the simulation. -/// -/// Encapsulates the information and state of an event being executed -/// within the simulation. -abstract interface class EventContext implements EventSchedulerInterface { - /// Pauses the execution of the event for the specified [delay] in simulation time. - /// - /// The event is re-added to the simulation's event queue and will resume after - /// the specified delay has passed. - /// - /// Throws an [ArgumentError] if the delay is negative. - Future wait(int delay); - - /// The instance of the random number generator used across the simulation. - /// It is initialized once and reused to improve performance, avoiding the need to - /// instantiate a new `Random` object for each event. - Random get random; -} diff --git a/lib/src/event_context.dart b/lib/src/event_context.dart new file mode 100644 index 0000000..ed5a65e --- /dev/null +++ b/lib/src/event_context.dart @@ -0,0 +1,36 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:simdart/simdart.dart'; +import 'package:simdart/src/internal/event_scheduler_interface.dart'; + +/// Represents the context of an event in the simulation. +/// +/// Encapsulates the information and state of an event being executed +/// within the simulation. +abstract interface class EventContext implements EventSchedulerInterface { + /// Pauses the execution of the event for the specified [delay] in simulation time. + /// + /// The event is re-added to the simulation's event queue and will resume after + /// the specified delay has passed. + /// + /// Throws an [ArgumentError] if the delay is negative. + Future wait(int delay); + + /// The instance of the random number generator used across the simulation. + /// It is initialized once and reused to improve performance, avoiding the need to + /// instantiate a new `Random` object for each event. + Random get random; + + /// Creates a new [SimCounter] instance with the given name. + /// + /// - [name]: The name of the counter. This is used to identify the counter in logs or reports. + /// - Returns: A new instance of [SimCounter]. + SimCounter counter(String name); + + /// Creates a new [SimNum] instance with the given name. + /// + /// - [name]: The name of the numeric metric. This is used to identify the metric in logs or reports. + /// - Returns: A new instance of [SimNum]. + SimNum num(String name); +} diff --git a/lib/src/internal/event_action.dart b/lib/src/internal/event_action.dart index 46237a4..9d83024 100644 --- a/lib/src/internal/event_action.dart +++ b/lib/src/internal/event_action.dart @@ -3,9 +3,12 @@ import 'dart:math'; import 'package:meta/meta.dart'; import 'package:simdart/src/event.dart'; +import 'package:simdart/src/event_context.dart'; import 'package:simdart/src/internal/resource.dart'; import 'package:simdart/src/internal/time_action.dart'; import 'package:simdart/src/interval.dart'; +import 'package:simdart/src/sim_counter.dart'; +import 'package:simdart/src/sim_num.dart'; import 'package:simdart/src/simdart.dart'; import 'package:simdart/src/simulation_track.dart'; @@ -24,6 +27,7 @@ class EventAction extends TimeAction implements EventContext { /// The name of the event. final String? _eventName; + String get eventName => _eventName ?? hashCode.toString(); /// The event to be executed. @@ -161,4 +165,14 @@ class EventAction extends TimeAction implements EventContext { resourceId: resourceId, name: name); } + + @override + SimCounter counter(String name) { + return SimDartHelper.counter(sim: _sim, name: name); + } + + @override + SimNum num(String name) { + return SimDartHelper.num(sim: _sim, name: name); + } } diff --git a/lib/src/internal/resource.dart b/lib/src/internal/resource.dart index b268525..014db3b 100644 --- a/lib/src/internal/resource.dart +++ b/lib/src/internal/resource.dart @@ -1,5 +1,5 @@ import 'package:meta/meta.dart'; -import 'package:simdart/src/event.dart'; +import 'package:simdart/src/event_context.dart'; @internal abstract class Resource { diff --git a/lib/src/internal/sim_configuration_interface.dart b/lib/src/internal/sim_configuration_interface.dart index ef9552a..dd5d311 100644 --- a/lib/src/internal/sim_configuration_interface.dart +++ b/lib/src/internal/sim_configuration_interface.dart @@ -15,10 +15,10 @@ abstract interface class SimConfigurationInterface { /// Default: `false` bool get includeTracks; - /// Determines how often `Future.delayed` is used instead of `microtask` during event execution. + /// Determines how often `Future.delayed` is used instead of `Future.microtask` during events execution. /// /// - `0`: Always uses `microtask`. /// - `1`: Alternates between `microtask` and `Future.delayed`. /// - `N > 1`: Executes `N` events with `microtask` before using `Future.delayed`. - int get executionPriorityCounter; + int get executionPriority; } diff --git a/lib/src/internal/time_loop.dart b/lib/src/internal/time_loop.dart deleted file mode 100644 index 395f6a6..0000000 --- a/lib/src/internal/time_loop.dart +++ /dev/null @@ -1,152 +0,0 @@ -import 'dart:async'; -import 'dart:math' as math; - -import 'package:collection/collection.dart'; -import 'package:meta/meta.dart'; -import 'package:simdart/src/internal/now_interface.dart'; -import 'package:simdart/src/internal/sim_result_interface.dart'; -import 'package:simdart/src/internal/time_action.dart'; -import 'package:simdart/src/internal/sim_configuration_interface.dart'; -import 'package:simdart/src/sim_result.dart'; -import 'package:simdart/src/simulation_track.dart'; -import 'package:simdart/src/start_time_handling.dart'; - -/// Represents the temporal loop in the algorithm, managing the execution of actions at specified times. -@internal -class TimeLoop - implements SimConfigurationInterface, NowInterface, SimResultInterface { - TimeLoop( - {required int? now, - required this.beforeRun, - required this.includeTracks, - required int executionPriorityCounter, - required this.startTimeHandling}) - : executionPriorityCounter = math.max(executionPriorityCounter, 0), - _now = now ?? 0; - - @override - final StartTimeHandling startTimeHandling; - - @override - final int executionPriorityCounter; - - int _executionCount = 0; - - final Function beforeRun; - - @override - final bool includeTracks; - - /// Queue that holds the [TimeAction] instances to be executed at their respective times. - final PriorityQueue _actions = PriorityQueue( - (a, b) { - final primaryComparison = a.start.compareTo(b.start); - if (primaryComparison != 0) { - return primaryComparison; - } - return a.secondaryCompareTo(b); - }, - ); - - @override - int get startTime => _startTime ?? 0; - int? _startTime; - - @override - int get duration => _duration; - int _duration = 0; - - bool _nextEventScheduled = false; - - late int? _until; - - @override - int get now => _now; - late int _now; - - List? _tracks; - - Completer? _terminator; - - /// Runs the simulation, processing actions in chronological order. - Future run({int? until}) async { - if (until != null && _now > until) { - throw ArgumentError('`now` must be less than or equal to `until`.'); - } - _until = until; - - if (_terminator != null) { - return _buildResult(); - } - if (_actions.isEmpty) { - _duration = 0; - _startTime = 0; - return _buildResult(); - } - _duration = 0; - _startTime = null; - - beforeRun(); - - _terminator = Completer(); - _scheduleNextEvent(); - await _terminator?.future; - _duration = _now - startTime; - _terminator = null; - return _buildResult(); - } - - SimResult _buildResult() { - return SimResult(startTime: startTime, duration: duration, tracks: _tracks); - } - - void _scheduleNextEvent() { - assert(!_nextEventScheduled, 'Multiple schedules for the next event.'); - _nextEventScheduled = true; - if (executionPriorityCounter == 0 || - _executionCount < executionPriorityCounter) { - _executionCount++; - Future.microtask(_consumeFirstEvent); - } else { - _executionCount = 0; - Future.delayed(Duration.zero, _consumeFirstEvent); - } - } - - void addAction(TimeAction action) { - _actions.add(action); - } - - void addTrack(SimulationTrack track) { - _tracks ??= []; - _tracks!.add(track); - } - - Future _consumeFirstEvent() async { - _nextEventScheduled = false; - if (_actions.isEmpty) { - _terminator?.complete(); - return; - } - - TimeAction action = _actions.removeFirst(); - - // Advance the simulation time to the action's start time. - if (action.start > now) { - _now = action.start; - if (_until != null && now > _until!) { - _startTime ??= now; - _terminator?.complete(); - return; - } - } else if (action.start < now) { - action.start = now; - } - - _startTime ??= now; - - action.execute(); - - _scheduleNextEvent(); - } -} diff --git a/lib/src/sim_counter.dart b/lib/src/sim_counter.dart new file mode 100644 index 0000000..af721b4 --- /dev/null +++ b/lib/src/sim_counter.dart @@ -0,0 +1,37 @@ +import 'package:simdart/src/sim_property.dart'; + +/// A class to track event counts in a discrete event simulation. +/// +/// This class extends [SimProperty] and provides methods to increment and reset a counter. +/// It is useful for counting occurrences of specific events, such as arrivals, departures, or errors. +class SimCounter extends SimProperty { + int _value = 0; + + /// Creates a new [SimCounter] instance. + /// + /// Optionally, provide a [name] to identify the counter. + SimCounter({super.name}); + + /// The current value of the counter. + int get value => _value; + + /// Increments the counter by 1. + void inc() { + _value++; + } + + /// Increments the counter by a specified value. + /// + /// - [value]: The value to increment the counter by. + void incBy(int value) { + if (value > 0) { + _value += value; + } + } + + /// Resets the counter to 0. + @override + void reset() { + _value = 0; + } +} diff --git a/lib/src/sim_num.dart b/lib/src/sim_num.dart new file mode 100644 index 0000000..a8f7818 --- /dev/null +++ b/lib/src/sim_num.dart @@ -0,0 +1,123 @@ +import 'dart:math'; + +import 'package:simdart/src/sim_property.dart'; + +/// A class to track numeric metrics in a discrete event simulation. +/// +/// This class allows you to store and update numeric values (both integers and doubles), +/// while automatically tracking minimum, maximum, and average values. +class SimNum extends SimProperty { + num? _value; + num? _min; + num? _max; + num _total = 0; + int _count = 0; + num _sumOfSquares = 0; + + final Map _frequencyMap = {}; + + /// Creates a new [SimNum] instance. + /// + /// Optionally, provide a [name] to identify the metric. + SimNum({super.name}); + + /// The current value of the metric. + /// + /// Returns `null` if no value has been set. + num? get value => _value; + + /// Sets the current value of the metric. + /// + /// If the value is not `null`, it updates the minimum, maximum, total, and count. + set value(num? value) { + _value = value; + + if (value != null) { + _frequencyMap[value] = (_frequencyMap[value] ?? 0) + 1; + + // Update min and max + _min = (_min == null || value < _min!) ? value : _min; + _max = (_max == null || value > _max!) ? value : _max; + + // Update total, sum of squares, and count + _total += value; + _sumOfSquares += value * value; + _count++; + } + } + + @override + void reset() { + _value = null; + _min = null; + _max = null; + _total = 0; + _count = 0; + _sumOfSquares = 0; + _frequencyMap.clear(); + } + + /// The minimum value recorded. + /// + /// Returns `null` if no value has been set. + num? get min => _min; + + /// The maximum value recorded. + /// + /// Returns `null` if no value has been set. + num? get max => _max; + + /// The average of all recorded values. + /// + /// Returns `null` if no value has been set. + num? get average => _count > 0 ? _total / _count : null; + + /// Calculates the rate of the current value relative to a reference value. + /// + /// - [value]: The reference value. + /// - Returns: The rate as a proportion (current value / reference value). + /// If the reference value is `0` or the current value is `null`, returns `0`. + num rate(num value) { + if (value == 0 || _value == null) { + return 0; + } + return _value! / value; + } + + /// Calculates the variance of the recorded values. + /// + /// Returns `null` if fewer than 2 values have been recorded. + num? get variance { + if (_count < 2) return null; + return (_sumOfSquares - (_total * _total) / _count) / _count; + } + + /// Calculates the standard deviation of the recorded values. + /// + /// Returns `null` if fewer than 2 values have been recorded. + num? get standardDeviation { + final varianceValue = variance; + return varianceValue != null ? sqrt(varianceValue) : null; + } + + /// Returns the mode of the recorded values. + /// + /// The mode is the value that appears most frequently. + /// If there are multiple values with the same highest frequency, returns the first one. + /// Returns `null` if no values have been recorded. + num? get mode { + if (_frequencyMap.isEmpty) return null; + + num? modeValue; + int maxFrequency = 0; + + _frequencyMap.forEach((value, frequency) { + if (frequency > maxFrequency) { + modeValue = value; + maxFrequency = frequency; + } + }); + + return modeValue; + } +} diff --git a/lib/src/sim_property.dart b/lib/src/sim_property.dart new file mode 100644 index 0000000..5836afc --- /dev/null +++ b/lib/src/sim_property.dart @@ -0,0 +1,18 @@ +/// A base class for simulation properties. +/// +/// This class provides a common interface for properties used in discrete event simulations, +/// such as counters, numeric metrics, or other tracked values. +abstract class SimProperty { + /// The name of this property (optional). + /// + /// Useful for identifying the property in logs or reports. + final String name; + + /// Creates a new [SimProperty] instance. + /// + /// Optionally, provide a [name] to identify the property. + SimProperty({this.name = ''}); + + /// Resets the property to its initial state. + void reset(); +} diff --git a/lib/src/sim_result.dart b/lib/src/sim_result.dart index d04b176..e78853e 100644 --- a/lib/src/sim_result.dart +++ b/lib/src/sim_result.dart @@ -1,14 +1,20 @@ import 'dart:collection'; import 'package:simdart/src/internal/sim_result_interface.dart'; +import 'package:simdart/src/sim_counter.dart'; +import 'package:simdart/src/sim_num.dart'; import 'package:simdart/src/simulation_track.dart'; class SimResult implements SimResultInterface { SimResult( {required this.duration, required this.startTime, - required List? tracks}) - : tracks = tracks != null ? UnmodifiableListView(tracks) : null; + required List? tracks, + required Map numProperties, + required Map counterProperties}) + : tracks = tracks != null ? UnmodifiableListView(tracks) : null, + numProperties = UnmodifiableMapView(numProperties), + counterProperties = UnmodifiableMapView(counterProperties); @override final int duration; @@ -17,4 +23,7 @@ class SimResult implements SimResultInterface { final int startTime; final List? tracks; + + final Map numProperties; + final Map counterProperties; } diff --git a/lib/src/simdart.dart b/lib/src/simdart.dart index 78ce43f..ff2ad5b 100644 --- a/lib/src/simdart.dart +++ b/lib/src/simdart.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:collection'; import 'dart:math'; +import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; import 'package:simdart/src/event.dart'; import 'package:simdart/src/internal/event_action.dart'; @@ -9,11 +10,12 @@ import 'package:simdart/src/internal/event_scheduler_interface.dart'; import 'package:simdart/src/internal/now_interface.dart'; import 'package:simdart/src/internal/repeat_event_action.dart'; import 'package:simdart/src/internal/resource.dart'; -import 'package:simdart/src/internal/time_action.dart'; -import 'package:simdart/src/internal/time_loop.dart'; import 'package:simdart/src/internal/sim_configuration_interface.dart'; +import 'package:simdart/src/internal/time_action.dart'; import 'package:simdart/src/interval.dart'; import 'package:simdart/src/resource_configurator.dart'; +import 'package:simdart/src/sim_counter.dart'; +import 'package:simdart/src/sim_num.dart'; import 'package:simdart/src/sim_result.dart'; import 'package:simdart/src/simulation_track.dart'; import 'package:simdart/src/start_time_handling.dart'; @@ -43,28 +45,16 @@ class SimDart /// /// - [executionPriority]: Defines the priority of task execution in the simulation. SimDart( - {StartTimeHandling startTimeHandling = StartTimeHandling.throwErrorIfPast, - int? now, + {this.startTimeHandling = StartTimeHandling.throwErrorIfPast, + int now = 0, this.secondarySortByName = false, this.includeTracks = false, - this.executionPriorityCounter = 0, + this.executionPriority = 0, int? seed}) - : random = Random(seed) { - _loop = TimeLoop( - now: now, - includeTracks: includeTracks, - beforeRun: _beforeRun, - executionPriorityCounter: executionPriorityCounter, - startTimeHandling: startTimeHandling); - } - - late final TimeLoop _loop; + : random = Random(seed), + _now = now; - @override - final int executionPriorityCounter; - - @override - final bool includeTracks; + bool _hasRun = false; /// Determines whether events with the same start time are sorted by their event name. /// @@ -75,6 +65,8 @@ class SimDart final bool secondarySortByName; final Map _resources = {}; + final Map _numProperties = {}; + final Map _counterProperties = {}; /// Holds the configurations for the resources in the simulator. /// @@ -93,6 +85,44 @@ class SimDart /// to await the opportunity to be processed once the resource is released. final Queue _waitingForResource = Queue(); + /// Queue that holds the [TimeAction] instances to be executed at their respective times. + final PriorityQueue _actions = PriorityQueue( + (a, b) { + final primaryComparison = a.start.compareTo(b.start); + if (primaryComparison != 0) { + return primaryComparison; + } + return a.secondaryCompareTo(b); + }, + ); + + @override + final StartTimeHandling startTimeHandling; + + @override + final int executionPriority; + + int _executionCount = 0; + + @override + final bool includeTracks; + + int? _startTime; + + int _duration = 0; + + bool _nextEventScheduled = false; + + late int? _until; + + @override + int get now => _now; + late int _now; + + List? _tracks; + + Completer? _terminator; + void _beforeRun() { for (ResourceConfiguration rc in ResourcesConfiguratorHelper.iterable(configurator: resources)) { @@ -107,7 +137,35 @@ class SimDart /// - [until]: The time at which execution should stop. Execution will include events /// scheduled at this time (inclusive). If null, execution will continue indefinitely. Future run({int? until}) async { - return _loop.run(until: until); + if (_hasRun) { + throw StateError('The simulation has already been run.'); + } + + _hasRun = true; + if (until != null && _now > until) { + throw ArgumentError('`now` must be less than or equal to `until`.'); + } + _until = until; + + if (_terminator != null) { + return _buildResult(); + } + if (_actions.isEmpty) { + _duration = 0; + _startTime = 0; + return _buildResult(); + } + _duration = 0; + _startTime = null; + + _beforeRun(); + + _terminator = Completer(); + _scheduleNextEvent(); + await _terminator?.future; + _duration = _now - (_startTime ?? 0); + _terminator = null; + return _buildResult(); } @override @@ -184,7 +242,7 @@ class SimDart start ??= now; if (interval != null && rejectedEventPolicy != null) { - _loop.addAction(RepeatEventAction( + _addAction(RepeatEventAction( sim: this, rejectedEventPolicy: rejectedEventPolicy, start: start, @@ -193,7 +251,7 @@ class SimDart resourceId: resourceId, interval: interval)); } else { - _loop.addAction(EventAction( + _addAction(EventAction( sim: this, start: start, eventName: name, @@ -204,11 +262,63 @@ class SimDart } } - @override - int get now => _loop.now; + SimResult _buildResult() { + return SimResult( + startTime: _startTime ?? 0, + duration: _duration, + tracks: _tracks, + numProperties: _numProperties, + counterProperties: _counterProperties); + } - @override - StartTimeHandling get startTimeHandling => _loop.startTimeHandling; + void _scheduleNextEvent() { + assert(!_nextEventScheduled, 'Multiple schedules for the next event.'); + _nextEventScheduled = true; + if (executionPriority == 0 || _executionCount < executionPriority) { + _executionCount++; + Future.microtask(_consumeFirstEvent); + } else { + _executionCount = 0; + Future.delayed(Duration.zero, _consumeFirstEvent); + } + } + + void _addAction(TimeAction action) { + _actions.add(action); + } + + void _addTrack(SimulationTrack track) { + _tracks ??= []; + _tracks!.add(track); + } + + Future _consumeFirstEvent() async { + _nextEventScheduled = false; + if (_actions.isEmpty) { + _terminator?.complete(); + return; + } + + TimeAction action = _actions.removeFirst(); + + // Advance the simulation time to the action's start time. + if (action.start > now) { + _now = action.start; + if (_until != null && now > _until!) { + _startTime ??= now; + _terminator?.complete(); + return; + } + } else if (action.start < now) { + action.start = now; + } + + _startTime ??= now; + + action.execute(); + + _scheduleNextEvent(); + } } /// Defines the behavior of the interval after a newly created event has been rejected. @@ -248,12 +358,12 @@ class SimDartHelper { /// Adds an [TimeAction] to the loop. static void addAction({required SimDart sim, required TimeAction action}) { - sim._loop.addAction(action); + sim._addAction(action); } static void restoreWaitingEventsForResource({required SimDart sim}) { while (sim._waitingForResource.isNotEmpty) { - sim._loop.addAction(sim._waitingForResource.removeFirst()); + sim._addAction(sim._waitingForResource.removeFirst()); } } @@ -275,10 +385,19 @@ class SimDartHelper { for (Resource resource in sim._resources.values) { resourceUsage[resource.id] = resource.queue.length; } - sim._loop.addTrack(SimulationTrack( + sim._addTrack(SimulationTrack( status: status, name: eventName, time: sim.now, resourceUsage: resourceUsage)); } + + static SimCounter counter({required SimDart sim, required String name}) { + return sim._counterProperties + .putIfAbsent(name, () => SimCounter(name: name)); + } + + static SimNum num({required SimDart sim, required String name}) { + return sim._numProperties.putIfAbsent(name, () => SimNum(name: name)); + } } diff --git a/test/process_test.dart b/test/process_test.dart index a919150..160b165 100644 --- a/test/process_test.dart +++ b/test/process_test.dart @@ -1,5 +1,4 @@ import 'package:simdart/simdart.dart'; -import 'package:simdart/src/sim_result.dart'; import 'package:test/test.dart'; import 'track_tester.dart'; diff --git a/test/property_test.dart b/test/property_test.dart new file mode 100644 index 0000000..cf8448c --- /dev/null +++ b/test/property_test.dart @@ -0,0 +1,105 @@ +import 'package:simdart/src/sim_counter.dart'; +import 'package:simdart/src/sim_num.dart'; +import 'package:test/test.dart'; + +void main() { + group('SimCounter', () { + late SimCounter counter; + + setUp(() { + counter = SimCounter(name: 'Test Counter'); + }); + + test('Initial count should be 0', () { + expect(counter.value, 0); + }); + + test('Increment should increase count by 1', () { + counter.inc(); + expect(counter.value, 1); + }); + + test('Increment by value should increase count by specified amount', () { + counter.incBy(5); + expect(counter.value, 5); + }); + + test('Increment by negative value should not change count', () { + counter.incBy(-3); + expect(counter.value, 0); + }); + + test('Reset should set count to 0', () { + counter.incBy(10); + counter.reset(); + expect(counter.value, 0); + }); + }); + group('SimNum', () { + late SimNum metric; + + setUp(() { + metric = SimNum(name: 'Test Metric'); + }); + + test('Initial value should be null', () { + expect(metric.value, isNull); + }); + + test('Setting value should update current value', () { + metric.value = 10.0; + expect(metric.value, 10.0); + }); + + test('Setting null value should not update min, max, or average', () { + metric.value = 10.0; + metric.value = null; + expect(metric.min, 10.0); + expect(metric.max, 10.0); + expect(metric.average, 10.0); + }); + + test('Min and max should track smallest and largest values', () { + metric.value = 10.0; + metric.value = 5.0; + metric.value = 15.0; + expect(metric.min, 5.0); + expect(metric.max, 15.0); + }); + + test('Average should calculate correctly', () { + metric.value = 10.0; + metric.value = 20.0; + metric.value = 30.0; + expect(metric.average, 20.0); + }); + + test('Variance should calculate correctly', () { + metric.value = 10.0; + metric.value = 20.0; + metric.value = 30.0; + expect(metric.variance, closeTo(66.666, 0.001)); + }); + + test('Standard deviation should calculate correctly', () { + metric.value = 10.0; + metric.value = 20.0; + metric.value = 30.0; + expect(metric.standardDeviation, closeTo(8.164, 0.001)); + }); + + test('Rate', () { + metric.value = 10.0; + expect(0.1, metric.rate(100)); + }); + + test('Reset should clear all values', () { + metric.value = 10.0; + metric.reset(); + expect(metric.value, isNull); + expect(metric.min, isNull); + expect(metric.max, isNull); + expect(metric.average, isNull); + }); + }); +} diff --git a/test/repeat_process_test.dart b/test/repeat_process_test.dart index 226e53c..74d4c74 100644 --- a/test/repeat_process_test.dart +++ b/test/repeat_process_test.dart @@ -1,5 +1,4 @@ import 'package:simdart/simdart.dart'; -import 'package:simdart/src/sim_result.dart'; import 'package:test/test.dart'; import 'track_tester.dart'; diff --git a/test/resource_test.dart b/test/resource_test.dart index e4889da..cd8338a 100644 --- a/test/resource_test.dart +++ b/test/resource_test.dart @@ -1,5 +1,4 @@ import 'package:simdart/simdart.dart'; -import 'package:simdart/src/sim_result.dart'; import 'package:test/test.dart'; import 'track_tester.dart'; diff --git a/test/time_loop_test.dart b/test/time_loop_test.dart index f841ba1..eac4b47 100644 --- a/test/time_loop_test.dart +++ b/test/time_loop_test.dart @@ -1,6 +1,6 @@ import 'package:simdart/simdart.dart'; import 'package:simdart/src/internal/time_action.dart'; -import 'package:simdart/src/internal/time_loop.dart'; +import 'package:simdart/src/simdart.dart'; import 'package:test/test.dart'; class TestAction extends TimeAction { @@ -18,23 +18,27 @@ class TestAction extends TimeAction { void main() { group('TimeLoop', () { test('Loop', () async { - TimeLoop loop = TimeLoop( + SimDart sim = SimDart( includeTracks: true, - now: null, - executionPriorityCounter: 0, - beforeRun: () {}, + executionPriority: 0, startTimeHandling: StartTimeHandling.throwErrorIfPast); List names = []; - loop.addAction(TestAction(start: 0, name: 'A', names: names)); - loop.addAction(TestAction(start: 1, name: 'B', names: names)); - loop.addAction(TestAction(start: 10, name: 'C', names: names)); - loop.addAction(TestAction(start: 5, name: 'D', names: names)); - loop.addAction(TestAction(start: 2, name: 'E', names: names)); - loop.addAction(TestAction(start: 9, name: 'F', names: names)); - - await loop.run(); + SimDartHelper.addAction( + sim: sim, action: TestAction(start: 0, name: 'A', names: names)); + SimDartHelper.addAction( + sim: sim, action: TestAction(start: 1, name: 'B', names: names)); + SimDartHelper.addAction( + sim: sim, action: TestAction(start: 10, name: 'C', names: names)); + SimDartHelper.addAction( + sim: sim, action: TestAction(start: 5, name: 'D', names: names)); + SimDartHelper.addAction( + sim: sim, action: TestAction(start: 2, name: 'E', names: names)); + SimDartHelper.addAction( + sim: sim, action: TestAction(start: 9, name: 'F', names: names)); + + await sim.run(); expect(names, ['A', 'B', 'E', 'D', 'F', 'C']); }); diff --git a/test/track_tester.dart b/test/track_tester.dart index 7ec07ca..77e33b0 100644 --- a/test/track_tester.dart +++ b/test/track_tester.dart @@ -1,5 +1,4 @@ import 'package:simdart/simdart.dart'; -import 'package:simdart/src/sim_result.dart'; import 'package:test/expect.dart'; class TrackTester { diff --git a/test/wait_test.dart b/test/wait_test.dart index c3e7278..37c9573 100644 --- a/test/wait_test.dart +++ b/test/wait_test.dart @@ -1,5 +1,4 @@ import 'package:simdart/simdart.dart'; -import 'package:simdart/src/sim_result.dart'; import 'package:test/test.dart'; import 'track_tester.dart';