From 1109fb44e70d2f83e724b69bd8b161c5b8d3f489 Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Leite de Andrade Date: Sat, 25 Jan 2025 09:51:35 -0300 Subject: [PATCH] Methods for scheduling new events have been added to the EventContext. (#6) --- README.md | 2 +- example/example.dart | 2 +- lib/simdart.dart | 8 +-- lib/src/event.dart | 7 +- lib/src/internal/event_action.dart | 65 +++++++++++++++---- .../internal/event_scheduler_interface.dart | 52 +++++++++++++++ lib/src/internal/now_interface.dart | 7 ++ lib/src/internal/time_loop.dart | 4 +- ...op_mixin.dart => time_loop_interface.dart} | 6 +- lib/src/simdart.dart | 36 ++-------- test/process_test.dart | 9 +-- 11 files changed, 132 insertions(+), 66 deletions(-) create mode 100644 lib/src/internal/event_scheduler_interface.dart create mode 100644 lib/src/internal/now_interface.dart rename lib/src/internal/{time_loop_mixin.dart => time_loop_interface.dart} (92%) diff --git a/README.md b/README.md index f5d9e3a..d6a679c 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ void main() async { void _a(EventContext context) async { await context.wait(10); - context.sim.process(event: _c, delay: 1, name: 'C'); + context.process(event: _c, delay: 1, name: 'C'); } void _b(EventContext context) async { diff --git a/example/example.dart b/example/example.dart index 52169d6..c0e5d93 100644 --- a/example/example.dart +++ b/example/example.dart @@ -10,5 +10,5 @@ void main() async { void _a(EventContext context) async { await context.wait(2); - context.sim.process(event: _a, delay: 2, name: 'A'); + context.process(event: _a, delay: 2, name: 'A'); } diff --git a/lib/simdart.dart b/lib/simdart.dart index beb761e..f5bc856 100644 --- a/lib/simdart.dart +++ b/lib/simdart.dart @@ -1,8 +1,8 @@ -export 'src/start_time_handling.dart'; -export 'src/simdart.dart' hide SimDartHelper; export 'src/event.dart'; -export 'src/simulation_track.dart'; +export 'src/execution_priority.dart'; export 'src/interval.dart'; export 'src/observable.dart'; export 'src/resource_configurator.dart' hide ResourcesConfiguratorHelper; -export 'src/execution_priority.dart'; +export 'src/simdart.dart' hide SimDartHelper; +export 'src/simulation_track.dart'; +export 'src/start_time_handling.dart'; diff --git a/lib/src/event.dart b/lib/src/event.dart index fa3cb59..6363d6d 100644 --- a/lib/src/event.dart +++ b/lib/src/event.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:simdart/src/simdart.dart'; +import 'package:simdart/src/internal/event_scheduler_interface.dart'; /// The event to be executed. /// @@ -12,10 +12,7 @@ typedef Event = void Function(EventContext context); /// /// Encapsulates the information and state of an event being executed /// within the simulation. -mixin EventContext { - /// The simulation instance managing this event. - SimDart get sim; - +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 diff --git a/lib/src/internal/event_action.dart b/lib/src/internal/event_action.dart index a30f20c..0fb9bb0 100644 --- a/lib/src/internal/event_action.dart +++ b/lib/src/internal/event_action.dart @@ -2,15 +2,16 @@ import 'dart:async'; import 'package:meta/meta.dart'; import 'package:simdart/src/event.dart'; -import 'package:simdart/src/internal/time_action.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/simdart.dart'; import 'package:simdart/src/simulation_track.dart'; @internal -class EventAction extends TimeAction with EventContext { +class EventAction extends TimeAction implements EventContext { EventAction( - {required this.sim, + {required SimDart sim, required super.start, required String? eventName, required this.event, @@ -18,7 +19,8 @@ class EventAction extends TimeAction with EventContext { required this.onTrack, required this.onReject, required this.secondarySortByName}) - : _eventName = eventName; + : _sim = sim, + _eventName = eventName; /// The name of the event. final String? _eventName; @@ -34,8 +36,7 @@ class EventAction extends TimeAction with EventContext { final Function? onReject; - @override - final SimDart sim; + final SimDart _sim; /// The resource id required by the event. final String? resourceId; @@ -64,7 +65,7 @@ class EventAction extends TimeAction with EventContext { if (resume != null) { if (onTrack != null) { onTrack!(SimDartHelper.buildSimulationTrack( - sim: sim, eventName: eventName, status: Status.resumed)); + sim: _sim, eventName: eventName, status: Status.resumed)); } // Resume the event if it is waiting, otherwise execute its action. resume.call(); @@ -72,7 +73,7 @@ class EventAction extends TimeAction with EventContext { } Resource? resource = - SimDartHelper.getResource(sim: sim, resourceId: resourceId); + SimDartHelper.getResource(sim: _sim, resourceId: resourceId); if (resource != null) { _resourceAcquired = resource.acquire(this); } @@ -83,7 +84,7 @@ class EventAction extends TimeAction with EventContext { status = Status.rejected; } onTrack!(SimDartHelper.buildSimulationTrack( - sim: sim, eventName: eventName, status: status)); + sim: _sim, eventName: eventName, status: status)); } if (_canRun) { @@ -94,12 +95,12 @@ class EventAction extends TimeAction with EventContext { _resourceAcquired = false; } // Event released some resource, others events need retry. - SimDartHelper.restoreWaitingEventsForResource(sim: sim); + SimDartHelper.restoreWaitingEventsForResource(sim: _sim); } }); } else { onReject?.call(); - SimDartHelper.queueOnWaitingForResource(sim: sim, action: this); + SimDartHelper.queueOnWaitingForResource(sim: _sim, action: this); } } @@ -109,9 +110,9 @@ class EventAction extends TimeAction with EventContext { return; } - start = sim.now + delay; + start = _sim.now + delay; // Adds it back to the loop to be resumed in the future. - SimDartHelper.addAction(sim: sim, action: this); + SimDartHelper.addAction(sim: _sim, action: this); final Completer resume = Completer(); _resume = () { @@ -124,4 +125,42 @@ class EventAction extends TimeAction with EventContext { Future _runEvent() async { return event(this); } + + @override + int get now => _sim.now; + + @override + void process( + {required Event event, + String? resourceId, + String? name, + int? start, + int? delay}) { + _sim.process( + event: event, + resourceId: resourceId, + name: name, + start: start, + delay: delay); + } + + @override + void repeatProcess( + {required Event event, + int? start, + int? delay, + required Interval interval, + RejectedEventPolicy rejectedEventPolicy = + RejectedEventPolicy.keepRepeating, + String? resourceId, + String? name}) { + _sim.repeatProcess( + event: event, + interval: interval, + start: start, + delay: delay, + rejectedEventPolicy: rejectedEventPolicy, + resourceId: resourceId, + name: name); + } } diff --git a/lib/src/internal/event_scheduler_interface.dart b/lib/src/internal/event_scheduler_interface.dart new file mode 100644 index 0000000..ef5a644 --- /dev/null +++ b/lib/src/internal/event_scheduler_interface.dart @@ -0,0 +1,52 @@ +import 'package:meta/meta.dart'; +import 'package:simdart/src/event.dart'; +import 'package:simdart/src/internal/now_interface.dart'; +import 'package:simdart/src/interval.dart'; +import 'package:simdart/src/simdart.dart'; + +@internal +abstract interface class EventSchedulerInterface implements NowInterface { + /// Schedules a new event to occur repeatedly based on the specified interval configuration. + /// + /// [event] is the function that represents the action to be executed when the event occurs. + /// [start] is the absolute time at which the event should occur. If null, the event will + /// occur at the [now] simulation time. + /// [delay] is the number of time units after the [now] when the event has been scheduled. + /// It cannot be provided if [start] is specified. + /// [interval] defines the timing configuration for the event, including its start time and + /// the interval between repetitions. The specific details of the interval behavior depend + /// on the implementation of the [Interval]. + /// [resourceId] is an optional parameter that specifies the ID of the resource required by the event. + /// [name] is an optional identifier for the event. + /// [rejectedEventPolicy] defines the behavior of the interval after a newly created event has been rejected. + /// + /// Throws an [ArgumentError] if the provided interval configuration is invalid, such as + /// containing negative or inconsistent timing values. + void repeatProcess( + {required Event event, + int? start, + int? delay, + required Interval interval, + RejectedEventPolicy rejectedEventPolicy = + RejectedEventPolicy.keepRepeating, + String? resourceId, + String? name}); + + /// Schedules a new event to occur at a specific simulation time or after a delay. + /// + /// [event] is the function that represents the action to be executed when the event occurs. + /// [start] is the absolute time at which the event should occur. If null, the event will + /// occur at the [now] simulation time. + /// [delay] is the number of time units after the [now] when the event has been scheduled. + /// It cannot be provided if [start] is specified. + /// [resourceId] is an optional parameter that specifies the ID of the resource required by the event. + /// [name] is an optional identifier for the event. + /// + /// Throws an [ArgumentError] if both [start] and [delay] are provided or if [delay] is negative. + void process( + {required Event event, + String? resourceId, + String? name, + int? start, + int? delay}); +} diff --git a/lib/src/internal/now_interface.dart b/lib/src/internal/now_interface.dart new file mode 100644 index 0000000..b8ed185 --- /dev/null +++ b/lib/src/internal/now_interface.dart @@ -0,0 +1,7 @@ +import 'package:meta/meta.dart'; + +@internal +abstract interface class NowInterface { + /// Gets the current simulation time. + int get now; +} diff --git a/lib/src/internal/time_loop.dart b/lib/src/internal/time_loop.dart index 6ed9578..acdc90e 100644 --- a/lib/src/internal/time_loop.dart +++ b/lib/src/internal/time_loop.dart @@ -4,12 +4,12 @@ import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; import 'package:simdart/src/execution_priority.dart'; import 'package:simdart/src/internal/time_action.dart'; -import 'package:simdart/src/internal/time_loop_mixin.dart'; +import 'package:simdart/src/internal/time_loop_interface.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 with TimeLoopMixin { +class TimeLoop implements TimeLoopInterface { TimeLoop( {required int? now, required this.beforeRun, diff --git a/lib/src/internal/time_loop_mixin.dart b/lib/src/internal/time_loop_interface.dart similarity index 92% rename from lib/src/internal/time_loop_mixin.dart rename to lib/src/internal/time_loop_interface.dart index a2d1fc7..ea88397 100644 --- a/lib/src/internal/time_loop_mixin.dart +++ b/lib/src/internal/time_loop_interface.dart @@ -1,10 +1,11 @@ import 'package:meta/meta.dart'; import 'package:simdart/src/execution_priority.dart'; +import 'package:simdart/src/internal/now_interface.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 -mixin TimeLoopMixin { +abstract interface class TimeLoopInterface implements NowInterface { /// Specifies how the simulation handles start times in the past. StartTimeHandling get startTimeHandling; @@ -32,7 +33,4 @@ mixin TimeLoopMixin { /// /// The value will be `null` if the duration has not been calculated or set. int? get duration; - - /// Gets the current simulation time. - int get now; } diff --git a/lib/src/simdart.dart b/lib/src/simdart.dart index 24cd4d4..b224dc3 100644 --- a/lib/src/simdart.dart +++ b/lib/src/simdart.dart @@ -6,18 +6,19 @@ import 'package:meta/meta.dart'; import 'package:simdart/src/event.dart'; import 'package:simdart/src/execution_priority.dart'; import 'package:simdart/src/internal/event_action.dart'; +import 'package:simdart/src/internal/event_scheduler_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/time_loop_mixin.dart'; +import 'package:simdart/src/internal/time_loop_interface.dart'; import 'package:simdart/src/interval.dart'; -import 'package:simdart/src/internal/resource.dart'; import 'package:simdart/src/resource_configurator.dart'; import 'package:simdart/src/simulation_track.dart'; import 'package:simdart/src/start_time_handling.dart'; /// Represents a discrete-event simulation engine. -class SimDart with TimeLoopMixin { +class SimDart implements TimeLoopInterface, EventSchedulerInterface { /// Creates a simulation instance. /// /// - [now]: The starting time of the simulation. Defaults to `0` if null. @@ -104,22 +105,7 @@ class SimDart with TimeLoopMixin { return _loop.run(until: until); } - /// Schedules a new event to occur repeatedly based on the specified interval configuration. - /// - /// [event] is the function that represents the action to be executed when the event occurs. - /// [start] is the absolute time at which the event should occur. If null, the event will - /// occur at the [now] simulation time. - /// [delay] is the number of time units after the [now] when the event has been scheduled. - /// It cannot be provided if [start] is specified. - /// [interval] defines the timing configuration for the event, including its start time and - /// the interval between repetitions. The specific details of the interval behavior depend - /// on the implementation of the [Interval]. - /// [resourceId] is an optional parameter that specifies the ID of the resource required by the event. - /// [name] is an optional identifier for the event. - /// [rejectedEventPolicy] defines the behavior of the interval after a newly created event has been rejected. - /// - /// Throws an [ArgumentError] if the provided interval configuration is invalid, such as - /// containing negative or inconsistent timing values. + @override void repeatProcess( {required Event event, int? start, @@ -140,17 +126,7 @@ class SimDart with TimeLoopMixin { rejectedEventPolicy: rejectedEventPolicy); } - /// Schedules a new event to occur at a specific simulation time or after a delay. - /// - /// [event] is the function that represents the action to be executed when the event occurs. - /// [start] is the absolute time at which the event should occur. If null, the event will - /// occur at the [now] simulation time. - /// [delay] is the number of time units after the [now] when the event has been scheduled. - /// It cannot be provided if [start] is specified. - /// [resourceId] is an optional parameter that specifies the ID of the resource required by the event. - /// [name] is an optional identifier for the event. - /// - /// Throws an [ArgumentError] if both [start] and [delay] are provided or if [delay] is negative. + @override void process( {required Event event, String? resourceId, diff --git a/test/process_test.dart b/test/process_test.dart index e375ac9..8562dbd 100644 --- a/test/process_test.dart +++ b/test/process_test.dart @@ -34,8 +34,7 @@ void main() { helper = TestHelper(); helper.sim.process( event: (context) async { - context.sim - .process(event: TestHelper.emptyEvent, delay: 10, name: 'b'); + context.process(event: TestHelper.emptyEvent, delay: 10, name: 'b'); }, start: 5, name: 'a'); @@ -47,15 +46,13 @@ void main() { helper = TestHelper(); helper.sim.process( event: (context) async { - context.sim - .process(event: TestHelper.emptyEvent, delay: 10, name: 'b'); + context.process(event: TestHelper.emptyEvent, delay: 10, name: 'b'); }, start: 0, name: 'a'); helper.sim.process( event: (context) async { - context.sim - .process(event: TestHelper.emptyEvent, delay: 2, name: 'd'); + context.process(event: TestHelper.emptyEvent, delay: 2, name: 'd'); }, start: 2, name: 'c');