diff --git a/CMakeLists.txt b/CMakeLists.txt
index fd43acf2..fad84c56 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,16 +1,19 @@
cmake_minimum_required(VERSION 3.10)
+
# Read version from the package.xml file.
file(READ ${CMAKE_CURRENT_SOURCE_DIR}/package.xml package_xml_str)
+
if(NOT package_xml_str MATCHES "([0-9]+.[0-9]+.[0-9]+)")
message(FATAL_ERROR "Could not parse project version from package.xml. Aborting.")
endif()
+
project(symmetri VERSION ${CMAKE_MATCH_1})
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-uninitialized -Wno-psabi -Wno-unused-parameter -Werror -Wall -Wextra -O3")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror -Wall -Wextra -pedantic")
if(BUILD_TESTING)
enable_testing()
@@ -18,6 +21,8 @@ if(BUILD_TESTING)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fsanitize=address,undefined -fno-omit-frame-pointer -O0")
elseif(TSAN_BUILD AND NOT ASAN_BUILD)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fsanitize=thread -fno-omit-frame-pointer -O0")
+ else()
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto -O3")
endif()
endif()
@@ -26,7 +31,7 @@ add_subdirectory(symmetri)
# some examples using the lib
if(BUILD_EXAMPLES)
- add_subdirectory(examples/flight)
- add_subdirectory(examples/hello_world)
- add_subdirectory(examples/model_benchmark)
+ add_subdirectory(examples/flight)
+ add_subdirectory(examples/hello_world)
+ add_subdirectory(examples/model_benchmark)
endif()
diff --git a/examples/flight/flight.cc b/examples/flight/flight.cc
index 955966be..be88f3d4 100644
--- a/examples/flight/flight.cc
+++ b/examples/flight/flight.cc
@@ -5,45 +5,32 @@
#include "symmetri/parsers.h"
#include "symmetri/symmetri.h"
+#include "symmetri/utilities.hpp"
#include "transition.hpp"
+using namespace symmetri;
/**
* @brief We want to use the Foo class with Symmetri; Foo has nice
* functionalities such as Pause and Resume and it can also get
* preempted/cancelled. We need to define functions to let Symmetri use these
* functionalities. It is as simple by creating specialized version of the
- * fire/cancel/isDirect/pause/resume functions. One does not need to implement
+ * fire/cancel/pause/resume functions. One does not need to implement
* all - if nothing is defined, a default version is used.
*
*/
-namespace symmetri {
-template <>
Result fire(const Foo &f) {
return f.fire() ? Result{{}, State::Error} : Result{{}, State::Completed};
}
-template <>
Result cancel(const Foo &f) {
f.cancel();
return {{}, State::UserExit};
}
-template <>
-bool isDirect(const Foo &) {
- return false;
-}
-
-template <>
-void pause(const Foo &f) {
- f.pause();
-}
+void pause(const Foo &f) { f.pause(); }
-template <>
-void resume(const Foo &f) {
- f.resume();
-}
-} // namespace symmetri
+void resume(const Foo &f) { f.resume(); }
/**
* @brief A simple printer for the eventlog
@@ -82,17 +69,16 @@ int main(int, char *argv[]) {
// Here we create the first PetriNet based on composing pnml1 and pnml2
// using flat composition. The associated transitions are two instance of
// the Foo-class.
- symmetri::PetriNet subnet({pnml1, pnml2}, {{"P2", 1}},
- {{"T0", Foo("SubFoo")}, {"T1", Foo("SubBar")}}, {},
- "SubNet", pool);
+ PetriNet subnet({pnml1, pnml2}, {{"P2", 1}},
+ {{"T0", Foo("SubFoo")}, {"T1", Foo("SubBar")}}, {}, "SubNet",
+ pool);
// We create another PetriNet by flatly composing all three petri nets.
// Again we have 2 Foo-transitions, and the first transition (T0) is the
// subnet. This show how you can also nest PetriNets.
- symmetri::PetriNet bignet(
- {pnml1, pnml2, pnml3}, {{"P3", 5}},
- {{"T0", subnet}, {"T1", Foo("Bar")}, {"T2", Foo("Foo")}}, {}, "RootNet",
- pool);
+ PetriNet bignet({pnml1, pnml2, pnml3}, {{"P3", 5}},
+ {{"T0", subnet}, {"T1", Foo("Bar")}, {"T2", Foo("Foo")}}, {},
+ "RootNet", pool);
// a flag to check if we are running
std::atomic running(true);
@@ -100,11 +86,12 @@ int main(int, char *argv[]) {
// a thread that polls the eventlog and writes it to a file
auto gantt = std::thread([&] {
while (running) {
- writeMermaidHtmlToFile(
- symmetri::mermaidFromEventlog(bignet.getEventLog()));
std::this_thread::sleep_for(std::chrono::seconds(3));
+ if (!running) {
+ break;
+ }
+ writeMermaidHtmlToFile(symmetri::mermaidFromEventlog(getLog(bignet)));
}
- writeMermaidHtmlToFile(symmetri::mermaidFromEventlog(bignet.getEventLog()));
});
// Parallel to the PetriNet execution, we run a thread through which we
@@ -141,7 +128,8 @@ int main(int, char *argv[]) {
// print the results and eventlog
spdlog::info("Result of this net: {0}", printState(result));
printLog(el);
- t.join(); // clean up
gantt.join(); // clean up
+ t.join(); // clean up
+ writeMermaidHtmlToFile(symmetri::mermaidFromEventlog(el));
return 0;
}
diff --git a/examples/flight/mermaid.html b/examples/flight/mermaid.html
deleted file mode 100644
index e69de29b..00000000
diff --git a/examples/hello_world/main.cc b/examples/hello_world/main.cc
index f5bc9bd9..f5988965 100644
--- a/examples/hello_world/main.cc
+++ b/examples/hello_world/main.cc
@@ -9,6 +9,7 @@
#include
#include "symmetri/symmetri.h"
+#include "symmetri/utilities.hpp"
// Before main, we define a bunch of functions that can be bound to
// transitions in the petri net.
@@ -55,8 +56,8 @@ int main(int, char *argv[]) {
// store based on the petri net. You can specifiy a final marking, the amount
// of threads it can use (maximum amount of stuff it can do in parallel) and a
// name so the net is easy to identifiy in a log.
- symmetri::PetriNet net({pnml_path_start, pnml_path_passive}, {}, store, {},
- "CASE_X", pool);
+ PetriNet net({pnml_path_start, pnml_path_passive}, {}, store, {}, "CASE_X",
+ pool);
// We use a simple boolean flag to terminate the threads once the net
// finishes. Without it, these threads would prevent the program from cleanly
@@ -84,8 +85,8 @@ int main(int, char *argv[]) {
// becomes false.
});
- auto [el, result] = symmetri::fire(net); // This function blocks until either
- // the net completes, deadlocks
+ auto [el, result] = fire(net); // This function blocks until either
+ // the net completes, deadlocks
// or user requests exit (ctrl-c)
running.store(false); // We set this to false so the thread that we launched
// gets interrupted.
diff --git a/examples/model_benchmark/model_benchmark.cc b/examples/model_benchmark/model_benchmark.cc
index c21ed69d..e166b237 100644
--- a/examples/model_benchmark/model_benchmark.cc
+++ b/examples/model_benchmark/model_benchmark.cc
@@ -2,6 +2,7 @@
#include "symmetri/parsers.h"
#include "symmetri/symmetri.h"
+#include "symmetri/utilities.hpp"
int main(int, char *argv[]) {
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%f] [%^%l%$] [thread %t] %v");
@@ -16,14 +17,14 @@ int main(int, char *argv[]) {
if (bolus) {
store.insert({t, []() {}});
} else {
- store.insert({t, nullptr});
+ store.insert({t, DirectMutation{}});
}
}
- symmetri::PetriNet bignet(net, m0, {}, store, {}, "pluto", pool);
+ PetriNet bignet(net, m0, {}, store, {}, "pluto", pool);
spdlog::info("start!");
const auto start_time = symmetri::Clock::now();
- auto [el, result] = symmetri::fire(bignet); // infinite loop
+ auto [el, result] = fire(bignet); // infinite loop
const auto end_time = symmetri::Clock::now();
auto trans_count = el.size() / 2;
auto delta_t = (double((end_time - start_time).count()) / 1e9);
diff --git a/symmetri/CMakeLists.txt b/symmetri/CMakeLists.txt
index 5c7ef66d..3c2bb4bb 100644
--- a/symmetri/CMakeLists.txt
+++ b/symmetri/CMakeLists.txt
@@ -1,5 +1,3 @@
-find_package(spdlog REQUIRED)
-
include(FetchContent)
FetchContent_Declare(
@@ -21,17 +19,17 @@ include_directories(include submodules)
#lib
add_library(${PROJECT_NAME} SHARED
tasks.cpp
- symmetri.cc
model.cc
model_utilities.cc
pnml_parser.cc
grml_parser.cc
eventlog_parsers.cc
types.cpp
+ symmetri.cc
submodules/tinyxml2/tinyxml2.cpp
)
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
-target_link_libraries(${PROJECT_NAME} PUBLIC pthread PRIVATE concurrentqueue spdlog::spdlog)
+target_link_libraries(${PROJECT_NAME} PUBLIC pthread PRIVATE concurrentqueue)
include(GenerateExportHeader)
generate_export_header(${PROJECT_NAME})
diff --git a/symmetri/eventlog_parsers.cc b/symmetri/eventlog_parsers.cc
index f37bc933..febacd1d 100644
--- a/symmetri/eventlog_parsers.cc
+++ b/symmetri/eventlog_parsers.cc
@@ -31,7 +31,7 @@ std::string mermaidFromEventlog(symmetri::Eventlog el) {
std::stringstream mermaid;
mermaid << "\n---\ndisplayMode : compact\n---\ngantt\ntitle A Gantt "
- "Diagram\ndateFormat x\naxisFormat \%H:\%M:\%S\n";
+ "Diagram\ndateFormat x\naxisFormat %H:%M:%S\n";
std::string current_section("");
for (auto it = el.begin(); std::next(it) != el.end(); it = std::next(it)) {
const auto &start = *it;
diff --git a/symmetri/include/symmetri/polytransition.h b/symmetri/include/symmetri/polytransition.h
index 4270b440..0b566b23 100644
--- a/symmetri/include/symmetri/polytransition.h
+++ b/symmetri/include/symmetri/polytransition.h
@@ -3,7 +3,8 @@
#include
#include "symmetri/types.h"
-namespace symmetri {
+
+struct DirectMutation {};
/**
* @brief Checks if the transition-function can be invoked.
@@ -15,7 +16,7 @@ namespace symmetri {
*/
template
bool isDirect(const T &) {
- return !std::is_invocable_v;
+ return false;
}
/**
@@ -27,8 +28,8 @@ bool isDirect(const T &) {
* @return Result
*/
template
-Result cancel(const T &) {
- return {{}, State::UserExit};
+symmetri::Result cancel(const T &) {
+ return {{}, symmetri::State::UserExit};
}
/**
@@ -58,24 +59,28 @@ void resume(const T &) {}
* possible eventlog of the transition.
*/
template
-Result fire(const T &transition) {
- if constexpr (!std::is_invocable_v) {
- return {{}, State::Completed};
- } else if constexpr (std::is_same_v) {
+symmetri::Result fire(const T &transition) {
+ if constexpr (std::is_same_v) {
+ return {{}, symmetri::State::Completed};
+ } else if constexpr (std::is_same_v) {
return {{}, transition()};
- } else if constexpr (std::is_same_v) {
+ } else if constexpr (std::is_same_v) {
return transition();
} else {
transition();
- return {{}, State::Completed};
+ return {{}, symmetri::State::Completed};
}
}
template
-Eventlog getLog(const T &) {
+symmetri::Eventlog getLog(const T &) {
return {};
}
+namespace symmetri {
+
/**
* @brief PolyTransition is a wrapper around any type that you want to tie to a
* transition. Typically this is an invokable object, such as a function, that
diff --git a/symmetri/include/symmetri/symmetri.h b/symmetri/include/symmetri/symmetri.h
index 7b2a5df2..62924833 100644
--- a/symmetri/include/symmetri/symmetri.h
+++ b/symmetri/include/symmetri/symmetri.h
@@ -1,6 +1,5 @@
#pragma once
-#include
#include
#include
@@ -8,6 +7,18 @@
#include "symmetri/tasks.h"
#include "symmetri/types.h"
+/**
+ * @brief The PetriNet class is a class that can create, configure and
+ * run a Petri net.
+ *
+ */
+class PetriNet;
+symmetri::Result fire(const PetriNet &);
+symmetri::Result cancel(const PetriNet &);
+void pause(const PetriNet &);
+void resume(const PetriNet &);
+symmetri::Eventlog getLog(const PetriNet &);
+
namespace symmetri {
/**
* @brief A Store is a mapping from Transitions, represented by a string that is
@@ -25,11 +36,8 @@ using Store = std::unordered_map;
*/
struct Petri;
-/**
- * @brief The PetriNet class is a class that can create, configure and
- * run a Petri net.
- *
- */
+} // namespace symmetri
+
class PetriNet final {
public:
/**
@@ -43,9 +51,9 @@ class PetriNet final {
* @param stp
*/
PetriNet(const std::set &path_to_pnml,
- const Marking &final_marking, const Store &store,
- const PriorityTable &priority, const std::string &case_id,
- std::shared_ptr stp);
+ const symmetri::Marking &final_marking, const symmetri::Store &store,
+ const symmetri::PriorityTable &priority, const std::string &case_id,
+ std::shared_ptr stp);
/**
* @brief Construct a new PetriNet object from a set of paths to
@@ -58,8 +66,9 @@ class PetriNet final {
* @param stp
*/
PetriNet(const std::set &path_to_grml,
- const Marking &final_marking, const Store &store,
- const std::string &case_id, std::shared_ptr stp);
+ const symmetri::Marking &final_marking, const symmetri::Store &store,
+ const std::string &case_id,
+ std::shared_ptr stp);
/**
* @brief Construct a new PetriNet object from a net and initial marking
@@ -72,9 +81,10 @@ class PetriNet final {
* @param case_id
* @param stp
*/
- PetriNet(const Net &net, const Marking &m0, const Marking &final_marking,
- const Store &store, const PriorityTable &priority,
- const std::string &case_id, std::shared_ptr stp);
+ PetriNet(const symmetri::Net &net, const symmetri::Marking &m0,
+ const symmetri::Marking &final_marking, const symmetri::Store &store,
+ const symmetri::PriorityTable &priority, const std::string &case_id,
+ std::shared_ptr stp);
/**
* @brief register transition gives a handle to manually force a transition to
@@ -89,27 +99,7 @@ class PetriNet final {
std::function registerTransitionCallback(
const std::string &transition) const noexcept;
- /**
- * @brief Get the Event Log object. If the Petri net is running, this call is
- * blocking as it is executed on the Petri net execution loop. Otherwise it
- * directly returns the log.
- *
- *
- * @return Eventlog
- */
- Eventlog getEventLog() const noexcept;
-
- /**
- * @brief Get the State, represented by a vector of *active* transitions (who
- * can still produce reducers and hence marking mutations) and the *current
- * marking*. If the Petri net is running, this call is blocking as it is
- * executed on the Petri net execution loop. Otherwise it directly returns the
- * state.
- *
- * @return std::pair, std::vector>
- */
- std::pair, std::vector> getState()
- const noexcept;
+ std::vector getMarking() const noexcept;
/**
* @brief reuseApplication resets the application such that the same net can
@@ -119,59 +109,14 @@ class PetriNet final {
*/
bool reuseApplication(const std::string &case_id);
- /**
- * @brief Fire for a PetriNet means that it executes the Petri net until it
- * reaches a final marking, deadlocks or is preempted by a user.
- *
- * @return Result
- */
- friend Result fire(const PetriNet &app);
-
- /**
- * @brief cancel interrupts and stops the Petri net execution and
- * calls cancel on all child transitions that are active. If transitions do
- * not have a cancel functionality implemented, they will not be cancelled.
- * Their reducers however will not be processed.
- *
- * @return Result
- */
- friend Result cancel(const PetriNet &app);
-
- /**
- * @brief pause interrupts and pauses the Petri net execution and
- * calls pause on all child transitions that are active. The Petri net will
- * still consume reducers produced by finished transitions but it will not
- * queue new transitions for execution. This mostly happens when active
- * transitions do not have a pause-functionality implemented.
- *
- * @param app
- */
- friend void pause(const PetriNet &app);
-
- /**
- * @brief resume breaks the pause and immediately will try to fire all
- * possible transitions. It will also call resume on all active transitions.
- *
- */
- friend void resume(const PetriNet &app);
-
- friend Eventlog getLog(const PetriNet &app);
+ friend symmetri::Result(::fire)(const PetriNet &);
+ friend symmetri::Result(::cancel)(const PetriNet &);
+ friend void(::pause)(const PetriNet &);
+ friend void(::resume)(const PetriNet &);
+ friend symmetri::Eventlog(::getLog)(const PetriNet &);
private:
- std::shared_ptr impl; ///< Pointer to the implementation, all
- ///< information is stored in Petri
- std::function
- register_functor; ///< At PetriNet construction this function is
- ///< created. It can be used to assign a trigger to
- ///< transitions - allowing the user to invoke a
- ///< transition without meeting the pre-conditions.
+ const std::shared_ptr
+ impl; ///< Pointer to the implementation, all
+ ///< information is stored in Petri
};
-
-Result fire(const PetriNet &);
-Result cancel(const PetriNet &);
-void pause(const PetriNet &);
-void resume(const PetriNet &);
-bool isDirect(const PetriNet &);
-Eventlog getLog(const PetriNet &);
-
-} // namespace symmetri
diff --git a/symmetri/include/symmetri/types.h b/symmetri/include/symmetri/types.h
index 228864ff..1fbfa548 100644
--- a/symmetri/include/symmetri/types.h
+++ b/symmetri/include/symmetri/types.h
@@ -24,6 +24,7 @@ enum class State {
Completed, ///< The transition completed as expected
Deadlock, ///< The transition deadlocked
UserExit, ///< The transition or interrupted and possibly stopped
+ Paused, ///< The transition is paused
Error ///< None of the above
};
@@ -59,72 +60,4 @@ using PriorityTable =
std::vector>; ///< Priority is limited from
///< -128 to 127
-/**
- * @brief Checks if the markings are exactly the same. Note that this uses a
- * different type for Marking compared to the Marking type used to
- * construct a net (an unordered map of strings). In this format the amount of
- * tokens in a particular place is represented by how often that place occurs in
- * the vector. For example: {"A","A","B"} is a marking with two tokens in place
- * "A" and one token in place "B". This format does not have the overhead of
- * mentioning all empty places.
- *
- * @tparam T
- * @param m1
- * @param m2
- * @return true
- * @return false
- */
-template
-bool MarkingEquality(const std::vector& m1, const std::vector& m2);
-
-/**
- * @brief Checks if marking is at least a subset of final_marking. Note
- * that this uses a different type for Marking compared to the Marking
- * type used to construct a net (an unordered map of strings). In this
- * format the amount of tokens in a particular place is represented by how often
- * that place occurs in the vector. For example: {"A","A","B"} is a marking with
- * two tokens in place "A" and one token in place "B". This format does not have
- * the overhead of mentioning all empty places.
- *
- * @tparam T
- * @param marking
- * @param final_marking
- * @return true
- * @return false
- */
-template
-bool MarkingReached(const std::vector& marking,
- const std::vector& final_marking);
-
-/**
- * @brief Checks if two petri-nets have equal amount of arcs between places
- * and transitions of the same name.
- *
- * @param net1
- * @param net2
- * @return true
- * @return false
- */
-bool stateNetEquality(const Net& net1, const Net& net2);
-
-/**
- * @brief Calculates a hash given an event log. This hash is only influenced by
- * the order of the completions of transitions and if the output of those
- * transitions is Completed, or something else.
- *
- * @param event_log An eventlog, can both be from a terminated or a still active
- * net.
- * @return size_t The hashed result.
- */
-size_t calculateTrace(const Eventlog& event_log) noexcept;
-
-/**
- * @brief A convenience function to get a string representation of the
- * state-enum.
- *
- * @param s The State
- * @return std::string The State as a human readable string.
- */
-std::string printState(State s) noexcept;
-
} // namespace symmetri
diff --git a/symmetri/include/symmetri/utilities.hpp b/symmetri/include/symmetri/utilities.hpp
new file mode 100644
index 00000000..895abf5d
--- /dev/null
+++ b/symmetri/include/symmetri/utilities.hpp
@@ -0,0 +1,95 @@
+#pragma once
+#include
+
+#include "symmetri/types.h"
+
+namespace symmetri {
+
+/**
+ * @brief Checks if the markings are exactly the same. Note that this uses a
+ * different type for Marking compared to the Marking type used to
+ * construct a net (an unordered map of strings). In this format the amount of
+ * tokens in a particular place is represented by how often that place occurs in
+ * the vector. For example: {"A","A","B"} is a marking with two tokens in place
+ * "A" and one token in place "B". This format does not have the overhead of
+ * mentioning all empty places.
+ *
+ * @tparam T
+ * @param m1
+ * @param m2
+ * @return true
+ * @return false
+ */
+template
+bool MarkingEquality(const std::vector& m1, const std::vector& m2) {
+ auto m1_sorted = m1;
+ auto m2_sorted = m2;
+ std::sort(m1_sorted.begin(), m1_sorted.end());
+ std::sort(m2_sorted.begin(), m2_sorted.end());
+ return m1_sorted == m2_sorted;
+}
+
+/**
+ * @brief Checks if marking is at least a subset of final_marking. Note
+ * that this uses a different type for Marking compared to the Marking
+ * type used to construct a net (an unordered map of strings). In this
+ * format the amount of tokens in a particular place is represented by how often
+ * that place occurs in the vector. For example: {"A","A","B"} is a marking with
+ * two tokens in place "A" and one token in place "B". This format does not have
+ * the overhead of mentioning all empty places.
+ *
+ * @tparam T
+ * @param marking
+ * @param final_marking
+ * @return true
+ * @return false
+ */
+template
+bool MarkingReached(const std::vector& marking,
+ const std::vector& final_marking) {
+ if (final_marking.empty()) {
+ return false;
+ }
+ auto unique = final_marking;
+ std::sort(unique.begin(), unique.end());
+ auto last = std::unique(unique.begin(), unique.end());
+ unique.erase(last, unique.end());
+
+ return std::all_of(std::begin(unique), std::end(unique), [&](const auto& p) {
+ return std::count(marking.begin(), marking.end(), p) ==
+ std::count(final_marking.begin(), final_marking.end(), p);
+ });
+}
+
+/**
+ * @brief Checks if two petri-nets have equal amount of arcs between places
+ * and transitions of the same name.
+ *
+ * @param net1
+ * @param net2
+ * @return true
+ * @return false
+ */
+bool stateNetEquality(const Net& net1, const Net& net2);
+
+/**
+ * @brief Calculates a hash given an event log. This hash is only influenced by
+ * the order of the completions of transitions and if the output of those
+ * transitions is Completed, or something else.
+ *
+ * @param event_log An eventlog, can both be from a terminated or a still active
+ * net.
+ * @return size_t The hashed result.
+ */
+size_t calculateTrace(const Eventlog& event_log) noexcept;
+
+/**
+ * @brief A convenience function to get a string representation of the
+ * state-enum.
+ *
+ * @param s The State
+ * @return std::string The State as a human readable string.
+ */
+std::string printState(State s) noexcept;
+
+} // namespace symmetri
diff --git a/symmetri/model.cc b/symmetri/model.cc
index b280ef3d..f204c727 100644
--- a/symmetri/model.cc
+++ b/symmetri/model.cc
@@ -1,7 +1,12 @@
#include "model.h"
-namespace symmetri {
+bool isDirect(const DirectMutation &) { return true; }
+
+symmetri::Result fire(const DirectMutation &) {
+ return {{}, symmetri::State::Completed};
+}
+namespace symmetri {
std::tuple, std::vector,
std::vector>
convert(const Net &_net, const Store &_store) {
@@ -76,9 +81,17 @@ std::vector createPriorityLookup(
return priority;
}
-Model::Model(const Net &_net, const Store &store,
- const PriorityTable &_priority, const Marking &M0)
- : event_log({}), is_paused(false) {
+Petri::Petri(const Net &_net, const Store &store,
+ const PriorityTable &_priority, const Marking &M0,
+ const Marking &final_marking, const std::string &case_id,
+ std::shared_ptr stp)
+ : event_log({}),
+ state(State::Scheduled),
+ case_id(case_id),
+ thread_id_(std::nullopt),
+ active_reducers(
+ std::make_shared>(32)),
+ pool(stp) {
event_log.reserve(1000);
std::tie(net.transition, net.place, net.store) = convert(_net, store);
std::tie(net.input_n, net.output_n) = populateIoLookups(_net, net.place);
@@ -87,9 +100,10 @@ Model::Model(const Net &_net, const Store &store,
net.priority = createPriorityLookup(net.transition, _priority);
initial_tokens = toTokens(M0);
tokens_n = initial_tokens;
+ final_marking_n = toTokens(final_marking);
}
-std::vector Model::toTokens(const Marking &marking) const noexcept {
+std::vector Petri::toTokens(const Marking &marking) const noexcept {
std::vector tokens;
for (const auto &[p, c] : marking) {
for (int i = 0; i < c; i++) {
@@ -99,7 +113,7 @@ std::vector Model::toTokens(const Marking &marking) const noexcept {
return tokens;
}
-std::vector Model::getFireableTransitions() const {
+std::vector Petri::getFireableTransitions() const {
auto possible_transition_list_n =
possibleTransitions(tokens_n, net.p_to_ts_n, net.priority);
std::vector fireable_transitions;
@@ -113,11 +127,11 @@ std::vector Model::getFireableTransitions() const {
return fireable_transitions;
}
-bool Model::Fire(
+bool Petri::Fire(
const size_t t,
const std::shared_ptr>
&reducers,
- std::shared_ptr pool, const std::string &case_id) {
+ const std::string &case_id) {
auto timestamp = Clock::now();
// deduct the marking
for (const size_t place : net.input_n[t]) {
@@ -141,31 +155,28 @@ bool Model::Fire(
active_transitions_n.push_back(t);
event_log.push_back({case_id, transition, State::Scheduled, timestamp});
pool->push([=] {
- reducers->enqueue(
- createReducerForTransition(t, transition, task, case_id, reducers));
+ reducers->enqueue(fireTransition(t, transition, task, case_id, reducers));
});
return false;
}
}
-bool Model::fire(
+bool Petri::fire(
const Transition &t,
const std::shared_ptr>
&reducers,
- std::shared_ptr pool, const std::string &case_id) {
+ const std::string &case_id) {
auto it = std::find(net.transition.begin(), net.transition.end(), t);
return it != net.transition.end() &&
canFire(net.input_n[std::distance(net.transition.begin(), it)],
tokens_n) &&
- !Fire(std::distance(net.transition.begin(), it), reducers, pool,
- case_id);
+ !Fire(std::distance(net.transition.begin(), it), reducers, case_id);
}
-void Model::fireTransitions(
+void Petri::fireTransitions(
const std::shared_ptr>
&reducers,
- std::shared_ptr pool, bool run_all,
- const std::string &case_id) {
+ bool run_all, const std::string &case_id) {
// find possible transitions
auto possible_transition_list_n =
possibleTransitions(tokens_n, net.p_to_ts_n, net.priority);
@@ -187,7 +198,7 @@ void Model::fireTransitions(
do {
// if Fire returns true, update the possible transition list
- if (Fire(possible_transition_list_n.front(), reducers, pool, case_id)) {
+ if (Fire(possible_transition_list_n.front(), reducers, case_id)) {
possible_transition_list_n =
possibleTransitions(tokens_n, net.p_to_ts_n, net.priority);
}
@@ -198,11 +209,7 @@ void Model::fireTransitions(
return;
}
-std::pair, std::vector> Model::getState() const {
- return {getActiveTransitions(), getMarking()};
-}
-
-std::vector Model::getMarking() const {
+std::vector Petri::getMarking() const {
std::vector marking;
marking.reserve(tokens_n.size());
std::transform(tokens_n.cbegin(), tokens_n.cend(),
@@ -211,7 +218,19 @@ std::vector Model::getMarking() const {
return marking;
}
-std::vector Model::getActiveTransitions() const {
+Eventlog Petri::getLog() const {
+ Eventlog eventlog = event_log;
+ // get event log from parent nets:
+ for (size_t pt_idx : active_transitions_n) {
+ auto sub_el = ::getLog(net.store[pt_idx]);
+ if (!sub_el.empty()) {
+ eventlog.insert(eventlog.end(), sub_el.begin(), sub_el.end());
+ }
+ }
+ return eventlog;
+}
+
+std::vector Petri::getActiveTransitions() const {
std::vector transition_list;
if (active_transitions_n.size() > 0) {
transition_list.reserve(active_transitions_n.size());
diff --git a/symmetri/model.h b/symmetri/model.h
index 154ff504..1ccd3e9d 100644
--- a/symmetri/model.h
+++ b/symmetri/model.h
@@ -1,15 +1,18 @@
#pragma once
#include
-#include
#include
#include
+#include
#include "small_vector.hpp"
#include "symmetri/polytransition.h"
#include "symmetri/tasks.h"
#include "symmetri/types.h"
+bool isDirect(const DirectMutation &);
+symmetri::Result fire(const DirectMutation &);
+
namespace symmetri {
using SmallVector = gch::small_vector;
@@ -51,8 +54,8 @@ gch::small_vector possibleTransitions(
*/
bool canFire(const SmallVector &pre, const std::vector &tokens);
-struct Model;
-using Reducer = std::function;
+struct Petri;
+using Reducer = std::function;
/**
* @brief Create a Reducer For Transition object
@@ -63,21 +66,21 @@ using Reducer = std::function;
* @param reducers
* @return Reducer
*/
-Reducer createReducerForTransition(
+Reducer fireTransition(
size_t T_i, const std::string &transition, const PolyTransition &task,
const std::string &case_id,
const std::shared_ptr>
&reducers);
/**
- * @brief Model is a different data structure to encode the Petri net. It is
+ * @brief Petri is a different data structure to encode the Petri net. It is
* optimized for calculating the fireable transitions and quick lookups in
* ordered vectors.
*
*/
-struct Model {
+struct Petri {
/**
- * @brief Construct a new Model from a multiset description of a Petri
+ * @brief Construct a new Petri from a multiset description of a Petri
* net, a lookup table for the transitions, optional priorities and an initial
* marking. A lot of conversion work is done in the constructor, so you should
* avoid creating Models during the run-time of a Petri application.
@@ -87,14 +90,23 @@ struct Model {
* @param priority
* @param M0
*/
- explicit Model(const Net &net, const Store &store,
- const PriorityTable &priority, const Marking &M0);
- ~Model() noexcept = default;
- Model(Model const &) = delete;
- Model(Model &&) noexcept = delete;
- Model &operator=(Model const &) = default;
- Model &operator=(Model &&) noexcept = default;
+ explicit Petri(const Net &net, const Store &store,
+ const PriorityTable &priority, const Marking &M0,
+ const Marking &final_marking, const std::string &case_id,
+ std::shared_ptr stp);
+ ~Petri() noexcept = default;
+ Petri(Petri const &) = delete;
+ Petri(Petri &&) noexcept = delete;
+ Petri &operator=(Petri const &) = delete;
+ Petri &operator=(Petri &&) noexcept = delete;
+ /**
+ * @brief outputs the marking as a vector of tokens; e.g. [1 1 1 0 5] means 3
+ * tokens in place 1, 1 token in place 0 and 1 token in place 5.
+ *
+ * @param marking
+ * @return std::vector
+ */
std::vector toTokens(const Marking &marking) const noexcept;
/**
@@ -107,6 +119,14 @@ struct Model {
*/
std::vector getMarking() const;
+ /**
+ * @brief get the current eventlog, also copies in all child eventlogs of
+ * active petri nets.
+ *
+ * @return Eventlog
+ */
+ Eventlog getLog() const;
+
/**
* @brief Get a vector of transitions that are *active*. Active means that
* they are either in the transition queue or its transition has been fired.
@@ -126,14 +146,6 @@ struct Model {
*/
std::vector getFireableTransitions() const;
- /**
- * @brief Get the State; this is a pair of active transitions *and* the
- * current marking.
- *
- * @return std::pair, std::vector>
- */
- std::pair, std::vector> getState() const;
-
/**
* @brief Try to fire a single transition.
*
@@ -148,7 +160,6 @@ struct Model {
bool fire(const Transition &t,
const std::shared_ptr>
&reducers,
- std::shared_ptr polymorphic_actions,
const std::string &case_id = "undefined_case_id");
/**
@@ -164,8 +175,7 @@ struct Model {
void fireTransitions(
const std::shared_ptr>
&reducers,
- std::shared_ptr polymorphic_actions, bool run_all = true,
- const std::string &case_id = "undefined_case_id");
+ bool run_all = true, const std::string &case_id = "undefined_case_id");
struct {
std::vector
@@ -189,17 +199,24 @@ struct Model {
///< `transition` so it is compatible with index lookup.
} net; ///< Is a data-oriented design of a Petri net
- std::vector initial_tokens; ///< The intial marking
+ std::vector initial_tokens; ///< The initial marking
std::vector tokens_n; ///< The current marking
+ std::vector final_marking_n; ///< The final marking
std::vector active_transitions_n; ///< List of active transitions
Eventlog event_log; ///< The most actual event_log
- bool is_paused;
+ State state;
+ std::string case_id;
+ std::atomic>
+ thread_id_; ///< The id of the thread from which run is called.
+
+ std::shared_ptr> active_reducers;
+ std::shared_ptr pool;
private:
/**
* @brief fires a transition.
*
- * @param t
+ * @param t transition as index in transition vector
* @param reducers
* @param polymorphic_actions
* @param case_id
@@ -209,7 +226,6 @@ struct Model {
bool Fire(const size_t t,
const std::shared_ptr>
&reducers,
- std::shared_ptr polymorphic_actions,
const std::string &case_id);
};
diff --git a/symmetri/model_utilities.cc b/symmetri/model_utilities.cc
index d99236ff..c11b15c5 100644
--- a/symmetri/model_utilities.cc
+++ b/symmetri/model_utilities.cc
@@ -12,7 +12,7 @@ bool canFire(const SmallVector &pre, const std::vector &tokens) {
return std::count(tokens.begin(), tokens.end(), m_p) >=
std::count(pre.begin(), pre.end(), m_p);
});
-};
+}
gch::small_vector possibleTransitions(
const std::vector &tokens,
@@ -37,8 +37,9 @@ gch::small_vector possibleTransitions(
return possible_transition_list_n;
}
-Reducer processTransition(size_t t_i, const Eventlog &ev, State result) {
- return [=](Model &&model) {
+Reducer createReducerForTransition(size_t t_i, const Eventlog &ev,
+ State result) {
+ return [=](Petri &model) {
// if it is in the active transition set it means it is finished and we
// should process it.
auto it = std::find(model.active_transitions_n.begin(),
@@ -52,25 +53,23 @@ Reducer processTransition(size_t t_i, const Eventlog &ev, State result) {
}
model.event_log.insert(model.event_log.end(), ev.begin(), ev.end());
};
- return std::ref(model);
};
}
-Reducer createReducerForTransition(
+Reducer fireTransition(
size_t t_i, const std::string &transition, const PolyTransition &task,
const std::string &case_id,
const std::shared_ptr>
&reducers) {
const auto start_time = Clock::now();
- reducers->enqueue([=](Model &&model) {
+ reducers->enqueue([=](Petri &model) {
model.event_log.push_back(
{case_id, transition, State::Started, start_time});
- return std::ref(model);
});
auto [ev, res] = fire(task);
ev.push_back({case_id, transition, res, Clock::now()});
- return processTransition(t_i, ev, res);
+ return createReducerForTransition(t_i, ev, res);
}
} // namespace symmetri
diff --git a/symmetri/symmetri.cc b/symmetri/symmetri.cc
index 11551255..f8db5888 100644
--- a/symmetri/symmetri.cc
+++ b/symmetri/symmetri.cc
@@ -1,20 +1,14 @@
#include "symmetri/symmetri.h"
-#include
-#include
-
-#include
#include
#include
#include
-#include
-#include
-#include
+#include
#include
-#include
#include "model.h"
#include "symmetri/parsers.h"
+#include "symmetri/utilities.hpp"
namespace symmetri {
@@ -30,377 +24,179 @@ unsigned int getThreadId() {
std::hash()(std::this_thread::get_id()));
}
-using namespace moodycamel;
-
-bool areAllTransitionsInStore(const Store &store, const Net &net) noexcept {
- return std::all_of(net.cbegin(), net.cend(), [&store](const auto &p) {
- const auto &t = std::get<0>(p);
+void areAllTransitionsInStore(const Store &store, const Net &net) {
+ for (const auto &pair : net) {
bool store_has_transition =
std::find_if(store.begin(), store.end(), [&](const auto &e) {
- return e.first == t;
+ return e.first == pair.first;
}) != store.end();
if (!store_has_transition) {
- spdlog::error("Transition {0} is not in store", t);
- }
- return store_has_transition;
- });
-}
-
-/**
- * @brief Petri is the class that holds the implementation of the Petri net. It
- * holds pointers to the reducer queue and that thread pool. Calling `fire()` on
- * this class will do the *actual* execution of the Petri net.
- *
- */
-struct Petri {
- Model m; ///< The Petri net model
- std::atomic>
- thread_id_; ///< The id of the thread from which run is called.
- const Marking m0_; ///< The initial marking for this instance
- const Net net_; ///< The net
- const PriorityTable priorities_; ///< The priority table for this instance
- const std::vector
- final_marking; ///< The net will stop queueing reducers
- ///< once the marking has been reached
- const std::shared_ptr stp;
- std::array>, 2> reducers;
- unsigned int reducer_selector;
- std::string case_id; ///< The case id of this particular Petri instance
- std::atomic early_exit; ///< once it is true, no more new transitions
- ///< will be queued and the run will exit.
-
- /**
- * @brief Construct a new Petri object. Most importantly, it also creates the
- * reducer queue and exposes the `run` function to actually run the petri
- * net.
- *
- * @param net
- * @param m0
- * @param stp
- * @param final_marking
- * @param store
- * @param priority
- * @param case_id
- */
- Petri(const Net &net, const Marking &m0, std::shared_ptr stp,
- const Marking &final_marking, const Store &store,
- const PriorityTable &priorities, const std::string &case_id)
- : m(net, store, priorities, m0),
- thread_id_(std::nullopt),
- m0_(m0),
- net_(net),
- priorities_(priorities),
- final_marking(m.toTokens(final_marking)),
- stp(stp),
- reducers({std::make_shared>(32),
- std::make_shared>(32)}),
- reducer_selector(0),
- case_id(case_id),
- early_exit(false) {}
-
- const std::shared_ptr> &setFreshQueue() {
- // increment index to get the already prepared queue.
- reducer_selector = reducer_selector > 0 ? 0 : 1;
- // create a new queue for later use at the next index.
- stp->push([reducer = reducers[reducer_selector > 0 ? 0 : 1]]() mutable {
- reducer.reset(new BlockingConcurrentQueue{32});
- });
- return reducers[reducer_selector];
- }
-
- /**
- * @brief Get the Event Log object
- *
- * @return Eventlog
- */
- Eventlog getEventLog() const noexcept {
- const auto maybe_thread_id = thread_id_.load();
- if (maybe_thread_id && maybe_thread_id.value() != getThreadId()) {
- std::promise el;
- std::future el_getter = el.get_future();
- reducers[reducer_selector]->enqueue([&](Model &&model) {
- auto eventlog = model.event_log;
- // get event log from parent nets:
- for (size_t pt_idx : model.active_transitions_n) {
- auto sub_el = getLog(model.net.store[pt_idx]);
- if (!sub_el.empty()) {
- eventlog.insert(eventlog.end(), sub_el.begin(), sub_el.end());
- }
- }
- el.set_value(std::move(eventlog));
- return std::ref(model);
- });
- return el_getter.get();
- } else {
- return m.event_log;
- }
- }
-
- std::pair, std::vector> getState()
- const noexcept {
- const auto maybe_thread_id = thread_id_.load();
- if (maybe_thread_id && maybe_thread_id.value() != getThreadId()) {
- std::promise, std::vector>>
- state;
- auto getter = state.get_future();
- reducers[reducer_selector]->enqueue([&](Model &&model) {
- state.set_value(model.getState());
- return std::ref(model);
- });
- return getter.get();
- } else {
- return m.getState();
+ std::string err = "transition " + pair.first + " is not in store";
+ throw std::runtime_error(err);
}
}
+}
- std::vector getFireableTransitions() const noexcept {
- const auto maybe_thread_id = thread_id_.load();
- if (maybe_thread_id && maybe_thread_id.value() != getThreadId()) {
- std::promise> transitions;
- std::future> transitions_getter =
- transitions.get_future();
- reducers[reducer_selector]->enqueue([&](Model &&model) {
- transitions.set_value(model.getFireableTransitions());
- return std::ref(model);
- });
- return transitions_getter.get();
- } else {
- return m.getFireableTransitions();
- }
- };
-};
+} // namespace symmetri
-/**
- * @brief A factory function that creates a Petri and a handler that allows to
- * register triggers to functions.
- *
- * @param net
- * @param m0
- * @param final_marking
- * @param store
- * @param priority
- * @param case_id
- * @param stp
- * @return std::tuple, std::function>
- */
-std::tuple, std::function>
-create(const Net &net, const Marking &m0, const Marking &final_marking,
- const Store &store, const PriorityTable &priority,
- const std::string &case_id, std::shared_ptr stp) {
- auto impl = std::make_shared(net, m0, stp, final_marking, store,
- priority, case_id);
- return {impl, [=](const Transition &t) {
- if (impl->thread_id_.load()) {
- const auto &reducer = impl->reducers[impl->reducer_selector];
- reducer->enqueue([=](Model &&m) {
- const auto t_index = toIndex(m.net.transition, t);
- m.active_transitions_n.push_back(t_index);
- reducer->enqueue(createReducerForTransition(
- t_index, m.net.transition[t_index], m.net.store[t_index],
- impl->case_id, reducer));
- return std::ref(m);
- });
- }
- }};
-}
+using namespace symmetri;
PetriNet::PetriNet(const std::set &files,
const Marking &final_marking, const Store &store,
const PriorityTable &priorities, const std::string &case_id,
- std::shared_ptr stp) {
- const auto [net, m0] = readPnml(files);
- if (areAllTransitionsInStore(store, net)) {
- std::tie(impl, register_functor) =
- create(net, m0, final_marking, store, priorities, case_id, stp);
- }
-}
+ std::shared_ptr stp)
+ : impl([&] {
+ const auto [net, m0] = readPnml(files);
+ areAllTransitionsInStore(store, net);
+ return std::make_shared(net, store, priorities, m0,
+ final_marking, case_id, stp);
+ }()) {}
PetriNet::PetriNet(const std::set &files,
const Marking &final_marking, const Store &store,
- const std::string &case_id,
- std::shared_ptr stp) {
- const auto [net, m0, priorities] = readGrml(files);
- if (areAllTransitionsInStore(store, net)) {
- std::tie(impl, register_functor) =
- create(net, m0, final_marking, store, priorities, case_id, stp);
- }
-}
+ const std::string &case_id, std::shared_ptr stp)
+ : impl([&] {
+ const auto [net, m0, priorities] = readGrml(files);
+ areAllTransitionsInStore(store, net);
+ return std::make_shared(net, store, priorities, m0,
+ final_marking, case_id, stp);
+ }()) {}
PetriNet::PetriNet(const Net &net, const Marking &m0,
const Marking &final_marking, const Store &store,
const PriorityTable &priorities, const std::string &case_id,
- std::shared_ptr stp) {
- if (areAllTransitionsInStore(store, net)) {
- std::tie(impl, register_functor) =
- create(net, m0, final_marking, store, priorities, case_id, stp);
- }
-}
-
-Eventlog PetriNet::getEventLog() const noexcept { return impl->getEventLog(); };
-
-std::pair, std::vector> PetriNet::getState()
- const noexcept {
- return impl->getState();
+ std::shared_ptr stp)
+ : impl(std::make_shared(net, store, priorities, m0, final_marking,
+ case_id, stp)) {
+ areAllTransitionsInStore(store, net);
}
std::function PetriNet::registerTransitionCallback(
const Transition &transition) const noexcept {
- return [transition, this] { register_functor(transition); };
+ return [transition, this] {
+ if (impl->thread_id_.load()) {
+ impl->active_reducers->enqueue([=](Petri &m) {
+ if (m.thread_id_.load()) {
+ const auto t_index = toIndex(m.net.transition, transition);
+ m.active_transitions_n.push_back(t_index);
+ m.active_reducers->enqueue(fireTransition(
+ t_index, m.net.transition[t_index], m.net.store[t_index],
+ m.case_id, m.active_reducers));
+ }
+ });
+ }
+ };
}
-bool PetriNet::reuseApplication(const std::string &new_case_id) {
- if (impl) {
- auto &petri = *impl;
- if (petri.thread_id_.load().has_value() || new_case_id == petri.case_id) {
- return false;
- }
- petri.case_id = new_case_id;
- petri.thread_id_.store(std::nullopt);
- return true;
+std::vector PetriNet::getMarking() const noexcept {
+ if (impl->thread_id_.load()) {
+ std::promise> el;
+ std::future> el_getter = el.get_future();
+ impl->active_reducers->enqueue(
+ [&](Petri &model) { el.set_value(model.getMarking()); });
+ return el_getter.get();
} else {
- return false;
+ return impl->getMarking();
}
}
-Result fire(const PetriNet &app) {
- if (app.impl == nullptr) {
- spdlog::error("Something went seriously wrong. Please send a bug report.");
- return {{}, State::Error};
+bool PetriNet::reuseApplication(const std::string &new_case_id) {
+ if (!impl->thread_id_.load().has_value() && new_case_id != impl->case_id) {
+ impl->case_id = new_case_id;
+ return true;
}
+ return false;
+}
- auto &petri = *app.impl;
- auto &m = petri.m;
- if (app.impl->thread_id_.load()) {
- spdlog::warn(
- "[{0}] is already active, it can not run it again before it is "
- "finished.",
- app.impl->case_id);
- return {{}, State::Error};
+symmetri::Result fire(const PetriNet &app) {
+ if (app.impl->thread_id_.load().has_value()) {
+ return {{}, symmetri::State::Error};
}
+ auto &m = *app.impl;
+ m.thread_id_.store(symmetri::getThreadId());
// we are running!
- auto &active_reducers = petri.reducers[petri.reducer_selector];
- petri.thread_id_.store(getThreadId());
- petri.early_exit.store(false);
m.event_log.clear();
m.tokens_n = m.initial_tokens;
-
- bool waiting_for_resume = false;
- Reducer f;
+ m.state = State::Started;
+ symmetri::Reducer f;
+ while (m.active_reducers->try_dequeue(f)) { /* get rid of old reducers */
+ }
// start!
- m.fireTransitions(active_reducers, petri.stp, true, petri.case_id);
- // get a reducer. Immediately, or wait a bit
- while ((m.active_transitions_n.size() > 0 || m.is_paused) &&
- active_reducers->wait_dequeue_timed(f, -1)) {
+ m.fireTransitions(m.active_reducers, true, m.case_id);
+ while ((m.state == State::Started || m.state == State::Paused) &&
+ m.active_reducers->wait_dequeue_timed(f, -1)) {
do {
- m = f(std::move(m));
- } while (!petri.early_exit.load() && active_reducers->try_dequeue(f));
+ f(m);
+ } while (m.active_reducers->try_dequeue(f));
- if (MarkingReached(m.tokens_n, petri.final_marking) ||
- petri.early_exit.load()) {
- // we're done
- break;
- } else if (!m.is_paused && !waiting_for_resume) {
- // we're firing
- m.fireTransitions(active_reducers, petri.stp, true, petri.case_id);
- } else if (m.is_paused && !waiting_for_resume) {
- // we've been asked to pause
- waiting_for_resume = true;
- for (const auto transition_index : m.active_transitions_n) {
- pause(m.net.store.at(transition_index));
- }
- } else if (!m.is_paused && waiting_for_resume) {
- // we've been asked to resume
- waiting_for_resume = false;
- m.fireTransitions(active_reducers, petri.stp, true, petri.case_id);
- for (const auto transition_index : m.active_transitions_n) {
- resume(m.net.store.at(transition_index));
- }
- }
- }
+ m.state = symmetri::MarkingReached(m.tokens_n, m.final_marking_n)
+ ? State::Completed
+ : m.state;
- // determine what was the reason we terminated.
- State result;
- if (petri.early_exit.load()) {
- {
- // populate that eventlog with child eventlog and possible cancelations.
- for (const auto transition_index : m.active_transitions_n) {
- auto [el, state] = cancel(m.net.store.at(transition_index));
- if (!el.empty()) {
- m.event_log.insert(m.event_log.end(), el.begin(), el.end());
- }
- m.event_log.push_back({petri.case_id,
- m.net.transition[transition_index], state,
- Clock::now()});
- }
- result = State::UserExit;
+ if (m.state == State::Started) {
+ // we're firing
+ m.fireTransitions(m.active_reducers, true, m.case_id);
+ // if there's nothing to fire; we deadlocked
+ m.state = m.active_transitions_n.size() == 0 ? State::Deadlock : m.state;
}
- } else if (MarkingReached(m.tokens_n, petri.final_marking)) {
- result = State::Completed;
- } else if (m.active_transitions_n.empty()) {
- result = State::Deadlock;
- } else {
- result = State::Error;
}
-
+ m.thread_id_.store(std::nullopt);
// empty reducers
m.active_transitions_n.clear();
- while (active_reducers->try_dequeue(f)) {
- m = f(std::move(m));
+ while (m.active_reducers->try_dequeue(f)) {
+ f(m);
}
- petri.thread_id_.store(std::nullopt);
- petri.setFreshQueue();
-
- return {m.event_log, result};
-};
+ return {m.event_log, m.state};
+}
-Result cancel(const PetriNet &app) {
- const auto maybe_thread_id = app.impl->thread_id_.load();
- if (maybe_thread_id && maybe_thread_id.value() != getThreadId() &&
- !app.impl->early_exit.load()) {
- std::mutex m;
- std::condition_variable cv;
- bool ready = false;
- app.impl->reducers[app.impl->reducer_selector]->enqueue(
- [=, &m, &cv, &ready](Model &&model) {
- app.impl->early_exit.store(true);
- {
- std::lock_guard lk(m);
- ready = true;
- }
- cv.notify_one();
- return std::ref(model);
- });
- std::unique_lock lk(m);
- cv.wait(lk, [&] { return ready; });
- } else {
- app.impl->reducers[app.impl->reducer_selector]->enqueue([=](Model &&model) {
- app.impl->early_exit.store(true);
- return std::ref(model);
- });
- }
+symmetri::Result cancel(const PetriNet &app) {
+ app.impl->active_reducers->enqueue([=](symmetri::Petri &model) {
+ model.state = symmetri::State::UserExit;
+ // populate that eventlog with child eventlog and possible
+ // cancelations.
+ for (const auto transition_index : model.active_transitions_n) {
+ auto [el, state] = cancel(model.net.store.at(transition_index));
+ if (!el.empty()) {
+ model.event_log.insert(model.event_log.end(), el.begin(), el.end());
+ }
+ model.event_log.push_back({model.case_id,
+ model.net.transition[transition_index], state,
+ symmetri::Clock::now()});
+ }
+ model.active_transitions_n.clear();
+ });
- return {app.getEventLog(), State::UserExit};
+ return {getLog(app), symmetri::State::UserExit};
}
void pause(const PetriNet &app) {
- app.impl->reducers[app.impl->reducer_selector]->enqueue([&](Model &&model) {
- model.is_paused = true;
- return std::ref(model);
+ app.impl->active_reducers->enqueue([](symmetri::Petri &model) {
+ model.state = State::Paused;
+ for (const auto transition_index : model.active_transitions_n) {
+ pause(model.net.store.at(transition_index));
+ }
});
-};
+}
void resume(const PetriNet &app) {
- app.impl->reducers[app.impl->reducer_selector]->enqueue([&](Model &&model) {
- model.is_paused = false;
- return std::ref(model);
+ app.impl->active_reducers->enqueue([](symmetri::Petri &model) {
+ model.state = State::Started;
+ for (const auto transition_index : model.active_transitions_n) {
+ resume(model.net.store.at(transition_index));
+ }
});
-};
-
-bool isDirect(const PetriNet &) { return false; };
-
-Eventlog getLog(const PetriNet &app) { return app.getEventLog(); }
+}
-} // namespace symmetri
+symmetri::Eventlog getLog(const PetriNet &app) {
+ if (app.impl->thread_id_.load()) {
+ std::promise el;
+ std::future el_getter = el.get_future();
+ app.impl->active_reducers->enqueue(
+ [&](Petri &model) { el.set_value(model.getLog()); });
+ return el_getter.get();
+ } else {
+ return app.impl->getLog();
+ }
+}
diff --git a/symmetri/tests/test_bugs.cc b/symmetri/tests/test_bugs.cc
index a4f1379c..1b50958a 100644
--- a/symmetri/tests/test_bugs.cc
+++ b/symmetri/tests/test_bugs.cc
@@ -2,6 +2,7 @@
#include
#include "model.h"
+#include "symmetri/utilities.hpp"
using namespace symmetri;
@@ -33,13 +34,12 @@ std::tuple testNet() {
TEST_CASE("Firing the same transition before it can complete should work") {
auto [net, store, priority, m0] = testNet();
- Model m(net, store, priority, m0);
-
+ auto stp = std::make_shared(2);
+ Petri m(net, store, priority, m0, {}, "s", stp);
auto reducers =
std::make_shared>(4);
- auto stp = std::make_shared(2);
REQUIRE(m.active_transitions_n.empty());
- m.fireTransitions(reducers, stp, true);
+ m.fireTransitions(reducers, true);
REQUIRE(m.getMarking().empty());
CHECK(m.getActiveTransitions() ==
std::vector{"t", "t"});
@@ -47,7 +47,7 @@ TEST_CASE("Firing the same transition before it can complete should work") {
Reducer r;
while (reducers->wait_dequeue_timed(r, std::chrono::milliseconds(250))) {
- m = r(std::move(m));
+ r(m);
}
{
@@ -58,7 +58,7 @@ TEST_CASE("Firing the same transition before it can complete should work") {
cv.notify_one();
reducers->wait_dequeue_timed(r, std::chrono::milliseconds(250));
- m = r(std::move(m));
+ r(m);
REQUIRE(MarkingEquality(m.getMarking(), {"Pb"}));
// offending test, but fixed :-)
@@ -70,7 +70,7 @@ TEST_CASE("Firing the same transition before it can complete should work") {
}
cv.notify_one();
reducers->wait_dequeue_timed(r, std::chrono::milliseconds(250));
- m = r(std::move(m));
+ r(m);
REQUIRE(MarkingEquality(m.getMarking(), {"Pb", "Pb"}));
diff --git a/symmetri/tests/test_external_input.cc b/symmetri/tests/test_external_input.cc
index d051b765..39228cae 100644
--- a/symmetri/tests/test_external_input.cc
+++ b/symmetri/tests/test_external_input.cc
@@ -11,7 +11,7 @@ void t() { i_ran.store(true); }
TEST_CASE("Test external input.") {
Net net = {
{"t0", {{}, {"Pa"}}}, {"t1", {{"Pa"}, {"Pb"}}}, {"t2", {{"Pc"}, {"Pb"}}}};
- Store store = {{"t0", nullptr},
+ Store store = {{"t0", DirectMutation{}},
{"t1", &t},
{"t2", []() {
std::this_thread::sleep_for(std::chrono::milliseconds(15));
@@ -20,8 +20,7 @@ TEST_CASE("Test external input.") {
Marking final_m = {{"Pb", 2}};
auto stp = std::make_shared(3);
- symmetri::PetriNet app(net, m0, final_m, store, {}, "test_net_ext_input",
- stp);
+ PetriNet app(net, m0, final_m, store, {}, "test_net_ext_input", stp);
// enqueue a trigger;
stp->push([trigger = app.registerTransitionCallback("t0")]() {
@@ -31,7 +30,7 @@ TEST_CASE("Test external input.") {
});
// run the net
- auto [ev, res] = symmetri::fire(app);
+ auto [ev, res] = fire(app);
REQUIRE(res == State::Completed);
REQUIRE(i_ran);
diff --git a/symmetri/tests/test_model.cc b/symmetri/tests/test_model.cc
index cac024f9..e1de9f33 100644
--- a/symmetri/tests/test_model.cc
+++ b/symmetri/tests/test_model.cc
@@ -2,6 +2,8 @@
#include