stdlib: Add beta simulate module to the gem5 stdlib
This module is used to semi-automate the running of gem5 simulation, mostly by handling exit events automatically and removing instantiation boilerplate code. NOTE: This module is still in beta. Change-Id: I4706119478464efcf4d92e3a1da05bddd0953b6a Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/50753 Maintainer: Bobby Bruce <bbruce@ucdavis.edu> Reviewed-by: Jason Lowe-Power <power.jg@gmail.com> Tested-by: kokoro <noreply+kokoro@google.com> Reviewed-by: Bobby Bruce <bbruce@ucdavis.edu>
This commit is contained in:
committed by
Bobby Bruce
parent
9bd3f9588a
commit
6baea72d8e
@@ -32,6 +32,10 @@ PySource('gem5', 'gem5/__init__.py')
|
||||
PySource('gem5', 'gem5/coherence_protocol.py')
|
||||
PySource('gem5', 'gem5/isas.py')
|
||||
PySource('gem5', 'gem5/runtime.py')
|
||||
PySource('gem5.simulate', 'gem5/simulate/__init__.py')
|
||||
PySource('gem5.simulate', 'gem5/simulate/simulator.py')
|
||||
PySource('gem5.simulate', 'gem5/simulate/exit_event.py')
|
||||
PySource('gem5.simulate', 'gem5/simulate/exit_event_generators.py')
|
||||
PySource('gem5.components', 'gem5/components/__init__.py')
|
||||
PySource('gem5.components.boards', 'gem5/components/boards/__init__.py')
|
||||
PySource('gem5.components.boards', 'gem5/components/boards/abstract_board.py')
|
||||
|
||||
0
src/python/gem5/simulate/__init__.py
Normal file
0
src/python/gem5/simulate/__init__.py
Normal file
86
src/python/gem5/simulate/exit_event.py
Normal file
86
src/python/gem5/simulate/exit_event.py
Normal file
@@ -0,0 +1,86 @@
|
||||
# Copyright (c) 2021 The Regents of the University of California
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met: redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer;
|
||||
# redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution;
|
||||
# neither the name of the copyright holders nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ExitEvent(Enum):
|
||||
"""
|
||||
An enum class holding all the supported simulator exit events.
|
||||
|
||||
The simulator will exit in certain conditions. The simulate package has
|
||||
been designed to categorize these into sensible states of exit, listed
|
||||
below.
|
||||
"""
|
||||
|
||||
EXIT = "exit" # A standard vanilla exit.
|
||||
WORKBEGIN = "workbegin" # An exit because a ROI has been reached.
|
||||
WORKEND = "workend" # An exit because a ROI has ended.
|
||||
SWITCHCPU = "switchcpu" # An exit needed to switch CPU cores.
|
||||
FAIL = "fail" # An exit because the simulation has failed.
|
||||
CHECKPOINT = "checkpoint" # An exit to load a checkpoint.
|
||||
MAX_TICK = "max tick" # An exit due to a maximum tick value being met.
|
||||
USER_INTERRUPT = ( # An exit due to a user interrupt (e.g., cntr + c)
|
||||
"user interupt"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def translate_exit_status(cls, exit_string: str) -> "ExitEvent":
|
||||
"""
|
||||
This function will translate common exit strings to their correct
|
||||
ExitEvent categorization.
|
||||
|
||||
|
||||
**Note:** At present, we do not guarantee this list is complete, as
|
||||
there are no bounds on what string may be returned by the simulator
|
||||
given an exit event.
|
||||
"""
|
||||
|
||||
if exit_string == "m5_workbegin instruction encountered":
|
||||
return ExitEvent.WORKBEGIN
|
||||
elif exit_string == "workbegin":
|
||||
return ExitEvent.WORKBEGIN
|
||||
elif exit_string == "m5_workend instruction encountered":
|
||||
return ExitEvent.WORKEND
|
||||
elif exit_string == "workend":
|
||||
return ExitEvent.WORKEND
|
||||
elif exit_string == "m5_exit instruction encountered":
|
||||
return ExitEvent.EXIT
|
||||
elif exit_string == "exiting with last active thread context":
|
||||
return ExitEvent.EXIT
|
||||
elif exit_string == "simulate() limit reached":
|
||||
return ExitEvent.MAX_TICK
|
||||
elif exit_string == "switchcpu":
|
||||
return ExitEvent.SWITCHCPU
|
||||
elif exit_string == "m5_fail instruction encountered":
|
||||
return ExitEvent.FAIL
|
||||
elif exit_string == "checkpoint":
|
||||
return ExitEvent.CHECKPOINT
|
||||
elif exit_string == "user interrupt received":
|
||||
return ExitEvent.USER_INTERRUPT
|
||||
raise NotImplementedError(
|
||||
"Exit event '{}' not implemented".format(exit_string)
|
||||
)
|
||||
76
src/python/gem5/simulate/exit_event_generators.py
Normal file
76
src/python/gem5/simulate/exit_event_generators.py
Normal file
@@ -0,0 +1,76 @@
|
||||
# Copyright (c) 2021 The Regents of the University of California
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met: redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer;
|
||||
# redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution;
|
||||
# neither the name of the copyright holders nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import m5.stats
|
||||
from ..components.processors.abstract_processor import AbstractProcessor
|
||||
from ..components.processors.switchable_processor import SwitchableProcessor
|
||||
|
||||
"""
|
||||
In this package we store generators for simulation exit events.
|
||||
"""
|
||||
|
||||
|
||||
def default_exit_generator():
|
||||
"""
|
||||
A default generator for an exit event. It will return True, indicating that
|
||||
the Simulator run loop should exit.
|
||||
"""
|
||||
while True:
|
||||
yield True
|
||||
|
||||
|
||||
def default_switch_generator(processor: AbstractProcessor):
|
||||
"""
|
||||
A default generator for a switch exit event. If the processor is a
|
||||
SwitchableProcessor, this generator will switch it. Otherwise nothing will
|
||||
happen.
|
||||
"""
|
||||
is_switchable = isinstance(processor, SwitchableProcessor)
|
||||
while True:
|
||||
if is_switchable:
|
||||
yield processor.switch()
|
||||
else:
|
||||
yield False
|
||||
|
||||
|
||||
def default_workbegin_generator():
|
||||
"""
|
||||
A default generator for a workbegin exit event. It will reset the
|
||||
simulation statistics.
|
||||
"""
|
||||
while True:
|
||||
m5.stats.reset()
|
||||
yield False
|
||||
|
||||
|
||||
def default_workend_generator():
|
||||
"""
|
||||
A default generator for a workend exit event. It will dump the simulation
|
||||
statistics.
|
||||
"""
|
||||
while True:
|
||||
m5.stats.dump()
|
||||
yield False
|
||||
349
src/python/gem5/simulate/simulator.py
Normal file
349
src/python/gem5/simulate/simulator.py
Normal file
@@ -0,0 +1,349 @@
|
||||
# Copyright (c) 2021 The Regents of the University of California
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met: redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer;
|
||||
# redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution;
|
||||
# neither the name of the copyright holders nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import m5
|
||||
import m5.ticks
|
||||
from m5.stats import addStatVisitor
|
||||
from m5.stats.gem5stats import get_simstat
|
||||
from m5.objects import Root
|
||||
from m5.util import warn
|
||||
|
||||
import os
|
||||
from typing import Optional, List, Tuple, Dict, Generator, Union
|
||||
|
||||
from .exit_event_generators import (
|
||||
default_exit_generator,
|
||||
default_switch_generator,
|
||||
default_workbegin_generator,
|
||||
default_workend_generator,
|
||||
)
|
||||
from .exit_event import ExitEvent
|
||||
from ..components.boards.abstract_board import AbstractBoard
|
||||
from ..components.processors.cpu_types import CPUTypes
|
||||
|
||||
|
||||
class Simulator:
|
||||
"""
|
||||
This Simulator class is used to manage the execution of a gem5 simulation.
|
||||
|
||||
**Warning:** The simulate package is still in a beta state. The gem5
|
||||
project does not guarantee the APIs within this package will remain
|
||||
consistent in future across upcoming releases.
|
||||
|
||||
Example
|
||||
-------
|
||||
Examples using the Simulator class can be found under
|
||||
`configs/example/gem5_library`.
|
||||
|
||||
The most basic run would be as follows:
|
||||
|
||||
```
|
||||
simulator = Simulator(board=board)
|
||||
simulator.run()
|
||||
```
|
||||
|
||||
This will run a simulation and execute default behavior for exit events.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
board: AbstractBoard,
|
||||
full_system: bool = True,
|
||||
on_exit_event: Optional[
|
||||
Dict[Union[str, ExitEvent], Generator[Optional[bool], None, None]]
|
||||
] = None,
|
||||
expected_execution_order: Optional[List[ExitEvent]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
:param board: The board to be simulated.
|
||||
:param full_system: Whether to run in full-system simulation or not. If
|
||||
False, the simulation will run in Syscall-Execution mode. True by
|
||||
default.
|
||||
:param on_exit_event: An optional map to specify the generator to
|
||||
execute on each exit event. The generator may yield a boolean which,
|
||||
if True, will have the Simulator exit the run loop.
|
||||
:param expected_execution_order: May be specified to check the exit
|
||||
events come in a specified order. If the order specified is not
|
||||
encountered (e.g., 'Workbegin', 'Workend', then 'Exit'), an Exception
|
||||
is thrown. If this parameter is not specified, any ordering of exit
|
||||
events is valid.
|
||||
|
||||
`on_exit_event` usage notes
|
||||
---------------------------
|
||||
|
||||
The `on_exit_event` parameter specifies a Python generator for each
|
||||
exit event. `next(<generator>)` is run each time an exit event. The
|
||||
generator may yield a boolean. If this value of this boolean is True
|
||||
the Simulator run loop will exit, otherwise
|
||||
the Simulator run loop will continue execution. If the generator has
|
||||
finished (i.e. a `StopIteration` exception is thrown when
|
||||
`next(<generator>)` is executed), then the default behavior for that
|
||||
exit event is run.
|
||||
|
||||
As an example, a user may specify their own exit event setup like so:
|
||||
|
||||
```
|
||||
def unique_exit_event():
|
||||
processor.switch()
|
||||
yield False
|
||||
m5.stats.dump()
|
||||
yield False
|
||||
yield True
|
||||
|
||||
simulator = Simulator(
|
||||
board=board
|
||||
on_exit_event = {
|
||||
ExitEvent.Exit : unique_exit_event(),
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
This will execute `processor.switch()` the first time an exit event is
|
||||
encountered, will dump gem5 statistics the second time an exit event is
|
||||
encountered, and will terminate the Simulator run loop the third time.
|
||||
|
||||
Each exit event has a default behavior if none is specified by the
|
||||
user. These are as follows:
|
||||
|
||||
* ExitEvent.EXIT: default_exit_list
|
||||
* ExitEvent.CHECKPOINT: default_exit_list
|
||||
* ExitEvent.FAIL : default_exit_list
|
||||
* ExitEvent.SWITCHCPU: default_switch_list
|
||||
* ExitEvent.WORKBEGIN: default_workbegin_list
|
||||
* ExitEvent.WORKEND: default_workend_list
|
||||
* ExitEvent.USER_INTERRUPT: default_exit_generator
|
||||
* ExitEvent.MAX_TICK: default_exit_generator()
|
||||
|
||||
These generators can be found in the `exit_event_generator.py` module.
|
||||
|
||||
"""
|
||||
|
||||
warn(
|
||||
"The simulate package is still in a beta state. The gem5 "
|
||||
"project does not guarantee the APIs within this package will "
|
||||
"remain consistent across upcoming releases."
|
||||
)
|
||||
|
||||
# We specify a dictionary here outlining the default behavior for each
|
||||
# exit event. Each exit event is mapped to a generator.
|
||||
self._default_on_exit_dict = {
|
||||
ExitEvent.EXIT: default_exit_generator(),
|
||||
# TODO: Something else should be done here for CHECKPOINT
|
||||
ExitEvent.CHECKPOINT: default_exit_generator(),
|
||||
ExitEvent.FAIL: default_exit_generator(),
|
||||
ExitEvent.SWITCHCPU: default_switch_generator(
|
||||
processor=board.get_processor()
|
||||
),
|
||||
ExitEvent.WORKBEGIN: default_workbegin_generator(),
|
||||
ExitEvent.WORKEND: default_workend_generator(),
|
||||
ExitEvent.USER_INTERRUPT: default_exit_generator(),
|
||||
ExitEvent.MAX_TICK: default_exit_generator(),
|
||||
}
|
||||
|
||||
if on_exit_event:
|
||||
self._on_exit_event = on_exit_event
|
||||
else:
|
||||
self._on_exit_event = self._default_on_exit_dict
|
||||
|
||||
self._instantiated = False
|
||||
self._board = board
|
||||
self._full_system = full_system
|
||||
self._expected_execution_order = expected_execution_order
|
||||
self._tick_stopwatch = []
|
||||
|
||||
self._last_exit_event = None
|
||||
self._exit_event_count = 0
|
||||
|
||||
def get_stats(self) -> Dict:
|
||||
"""
|
||||
Obtain the current simulation statistics as a Dictionary, conforming
|
||||
to a JSON-style schema.
|
||||
|
||||
**Warning:** Will throw an Exception if called before `run()`. The
|
||||
board must be initialized before obtaining statistics
|
||||
"""
|
||||
|
||||
if not self._instantiated:
|
||||
raise Exception(
|
||||
"Cannot obtain simulation statistics prior to inialization."
|
||||
)
|
||||
|
||||
return get_simstat(self._root).to_json()
|
||||
|
||||
def add_text_stats_output(self, path: str) -> None:
|
||||
"""
|
||||
This function is used to set an output location for text stats. If
|
||||
specified, when stats are dumped they will be output to this location
|
||||
as a text file file, in addition to any other stats' output locations
|
||||
specified.
|
||||
|
||||
:param path: That path in which the file should be output to.
|
||||
"""
|
||||
if not os.is_path_exists_or_creatable(path):
|
||||
raise Exception(
|
||||
f"Path '{path}' is is not a valid text stats output location."
|
||||
)
|
||||
addStatVisitor(path)
|
||||
|
||||
def add_json_stats_output(self, path: str) -> None:
|
||||
"""
|
||||
This function is used to set an output location for JSON. If specified,
|
||||
when stats are dumped they will be output to this location as a JSON
|
||||
file, in addition to any other stats' output locations specified.
|
||||
|
||||
:param path: That path in which the JSON should be output to.
|
||||
"""
|
||||
if not os.is_path_exists_or_creatable(path):
|
||||
raise Exception(
|
||||
f"Path '{path}' is is not a valid JSON output location."
|
||||
)
|
||||
addStatVisitor(f"json://{path}")
|
||||
|
||||
def get_last_exit_event_cause(self) -> str:
|
||||
"""
|
||||
Returns the last exit event cause.
|
||||
"""
|
||||
return self._last_exit_event.getCause()
|
||||
|
||||
def get_current_tick(self) -> int:
|
||||
"""
|
||||
Returns the current tick.
|
||||
"""
|
||||
return m5.curTick()
|
||||
|
||||
def get_tick_stopwatch(self) -> List[Tuple[ExitEvent, int]]:
|
||||
"""
|
||||
Returns a list of tuples, which each tuple specifying an exit event
|
||||
and the ticks at that event.
|
||||
"""
|
||||
return self._tick_stopwatch
|
||||
|
||||
def get_roi_ticks(self) -> List[int]:
|
||||
"""
|
||||
Returns a list of the tick counts for every ROI encountered (specified
|
||||
as a region of code between a Workbegin and Workend exit event).
|
||||
"""
|
||||
start = 0
|
||||
to_return = []
|
||||
for (exit_event, tick) in self._tick_stopwatch:
|
||||
if exit_event == ExitEvent.WORKBEGIN:
|
||||
start = tick
|
||||
elif exit_event == ExitEvent.WORKEND:
|
||||
to_return.append(tick - start)
|
||||
|
||||
return to_return
|
||||
|
||||
def _instantiate(self) -> None:
|
||||
"""
|
||||
This method will instantiate the board and carry out necessary
|
||||
boilerplate code before the instantiation such as setting up root and
|
||||
setting the sim_quantum (if running in KVM mode).
|
||||
"""
|
||||
|
||||
if not self._instantiated:
|
||||
root = Root(full_system=self._full_system, board=self._board)
|
||||
|
||||
# We take a copy of the Root in case it's required elsewhere
|
||||
# (for example, in `get_stats()`).
|
||||
self._root = root
|
||||
|
||||
if CPUTypes.KVM in [
|
||||
core.get_type()
|
||||
for core in self._board.get_processor().get_cores()
|
||||
]:
|
||||
m5.ticks.fixGlobalFrequency()
|
||||
root.sim_quantum = m5.ticks.fromSeconds(0.001)
|
||||
|
||||
m5.instantiate()
|
||||
self._instantiated = True
|
||||
|
||||
def run(self, max_ticks: int = m5.MaxTick) -> None:
|
||||
"""
|
||||
This function will start or continue the simulator run and handle exit
|
||||
events accordingly.
|
||||
|
||||
:param max_ticks: The maximum number of ticks to execute per simulation
|
||||
run. If this max_ticks value is met, a MAX_TICK exit event is
|
||||
received, if another simulation exit event is met the tick count is
|
||||
reset. This is the **maximum number of ticks per simululation run**.
|
||||
"""
|
||||
|
||||
# We instantiate the board if it has not already been instantiated.
|
||||
self._instantiate()
|
||||
|
||||
# This while loop will continue until an a generator yields True.
|
||||
while True:
|
||||
|
||||
self._last_exit_event = m5.simulate(max_ticks)
|
||||
|
||||
# Translate the exit event cause to the exit event enum.
|
||||
exit_enum = ExitEvent.translate_exit_status(
|
||||
self.get_last_exit_event_cause()
|
||||
)
|
||||
|
||||
# Check to see the run is corresponding to the expected execution
|
||||
# order (assuming this check is demanded by the user).
|
||||
if self._expected_execution_order:
|
||||
expected_enum = self._expected_execution_order[
|
||||
self._exit_event_count
|
||||
]
|
||||
if exit_enum.value != expected_enum.value:
|
||||
raise Exception(
|
||||
f"Expected a '{expected_enum.value}' exit event but a "
|
||||
f"'{exit_enum.value}' exit event was encountered."
|
||||
)
|
||||
|
||||
# Record the current tick and exit event enum.
|
||||
self._tick_stopwatch.append((exit_enum, self.get_current_tick()))
|
||||
|
||||
try:
|
||||
# If the user has specified their own generator for this exit
|
||||
# event, use it.
|
||||
exit_on_completion = next(self._on_exit_event[exit_enum])
|
||||
except StopIteration:
|
||||
# If the user's generator has ended, throw a warning and use
|
||||
# the default generator for this exit event.
|
||||
warn(
|
||||
"User-specified generator for the exit event "
|
||||
f"'{exit_enum.value}' has ended. Using the default "
|
||||
"generator."
|
||||
)
|
||||
exit_on_completion = next(
|
||||
self._default_on_exit_dict[exit_enum]
|
||||
)
|
||||
except KeyError:
|
||||
# If the user has not specified their own generator for this
|
||||
# exit event, use the default.
|
||||
exit_on_completion = next(
|
||||
self._default_on_exit_dict[exit_enum]
|
||||
)
|
||||
|
||||
self._exit_event_count += 1
|
||||
|
||||
# If the generator returned True we will return from the Simulator
|
||||
# run loop.
|
||||
if exit_on_completion:
|
||||
return
|
||||
Reference in New Issue
Block a user