pythonfmu3 0.3.4__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythonfmu3/_version.py +1 -1
- pythonfmu3/builder.py +96 -24
- pythonfmu3/fmi3slave.py +23 -19
- pythonfmu3/modelexchange.py +17 -2
- pythonfmu3/osutil.py +34 -9
- pythonfmu3/pythonfmu-export/CMakeLists.txt +36 -4
- pythonfmu3/pythonfmu-export/generate_shim.py +88 -0
- pythonfmu3/pythonfmu-export/src/CMakeLists.txt +41 -13
- pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_common.hpp +3 -0
- pythonfmu3/pythonfmu-export/src/cppfmu/fmi_functions.cpp +6 -4
- pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp +560 -571
- pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp +20 -7
- pythonfmu3/pythonfmu-export/src/shim/fmi3_forwarders.cpp +612 -0
- pythonfmu3/pythonfmu-export/src/shim/shim_loader.cpp +157 -0
- pythonfmu3/pythonfmu-export/src/shim/shim_loader.hpp +29 -0
- pythonfmu3/resources/binaries/x86_64-linux/libpythonfmu-backend.so +0 -0
- pythonfmu3/resources/binaries/x86_64-linux/libpythonfmu-export.so +0 -0
- pythonfmu3/resources/binaries/x86_64-windows/pythonfmu-export.dll +0 -0
- pythonfmu3/tests/data/fmi3_host.c +31 -0
- pythonfmu3/tests/fmi3_ctypes_driver.py +137 -0
- pythonfmu3/tests/test_builder.py +48 -2
- pythonfmu3/tests/test_docs.py +70 -61
- pythonfmu3/tests/test_non_python_host.py +75 -0
- pythonfmu3/tests/test_osutil.py +23 -0
- pythonfmu3/tests/test_variables.py +1 -1
- pythonfmu3/tests/test_wrapper_ctypes.py +61 -0
- pythonfmu3/variables.py +4 -3
- {pythonfmu3-0.3.4.dist-info → pythonfmu3-1.0.0.dist-info}/METADATA +61 -25
- {pythonfmu3-0.3.4.dist-info → pythonfmu3-1.0.0.dist-info}/RECORD +33 -23
- {pythonfmu3-0.3.4.dist-info → pythonfmu3-1.0.0.dist-info}/WHEEL +1 -1
- {pythonfmu3-0.3.4.dist-info → pythonfmu3-1.0.0.dist-info}/entry_points.txt +0 -0
- {pythonfmu3-0.3.4.dist-info → pythonfmu3-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {pythonfmu3-0.3.4.dist-info → pythonfmu3-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -6,32 +6,101 @@
|
|
|
6
6
|
|
|
7
7
|
#include "cppfmu/cppfmu_cs.hpp"
|
|
8
8
|
|
|
9
|
+
#include <algorithm>
|
|
10
|
+
#include <cctype>
|
|
9
11
|
#include <fstream>
|
|
10
|
-
#include <functional>
|
|
11
12
|
#include <mutex>
|
|
12
13
|
#include <regex>
|
|
13
14
|
#include <sstream>
|
|
15
|
+
#include <stdexcept>
|
|
14
16
|
#include <utility>
|
|
15
17
|
|
|
16
18
|
namespace pythonfmu
|
|
17
19
|
{
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
namespace
|
|
20
22
|
{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
|
|
24
|
+
// Holds the GIL for the lifetime of the object. Releasing in the destructor guarantees
|
|
25
|
+
// the GIL is given back on every exit path, including C++ exceptions.
|
|
26
|
+
class GilGuard
|
|
27
|
+
{
|
|
28
|
+
public:
|
|
29
|
+
GilGuard()
|
|
30
|
+
: state_(PyGILState_Ensure())
|
|
31
|
+
{ }
|
|
32
|
+
~GilGuard() { PyGILState_Release(state_); }
|
|
33
|
+
GilGuard(const GilGuard&) = delete;
|
|
34
|
+
GilGuard& operator=(const GilGuard&) = delete;
|
|
35
|
+
|
|
36
|
+
private:
|
|
37
|
+
PyGILState_STATE state_;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Owning reference to a PyObject (Py_XDECREF on destruction).
|
|
41
|
+
// Must only be destroyed while the GIL is held; declare it after the GilGuard.
|
|
42
|
+
class PyRef
|
|
43
|
+
{
|
|
44
|
+
public:
|
|
45
|
+
explicit PyRef(PyObject* obj = nullptr)
|
|
46
|
+
: obj_(obj)
|
|
47
|
+
{ }
|
|
48
|
+
~PyRef() { Py_XDECREF(obj_); }
|
|
49
|
+
PyRef(const PyRef&) = delete;
|
|
50
|
+
PyRef& operator=(const PyRef&) = delete;
|
|
51
|
+
PyRef(PyRef&& other) noexcept
|
|
52
|
+
: obj_(other.release())
|
|
53
|
+
{ }
|
|
54
|
+
|
|
55
|
+
PyObject* get() const { return obj_; }
|
|
56
|
+
PyObject* release()
|
|
57
|
+
{
|
|
58
|
+
PyObject* tmp = obj_;
|
|
59
|
+
obj_ = nullptr;
|
|
60
|
+
return tmp;
|
|
61
|
+
}
|
|
62
|
+
explicit operator bool() const { return obj_ != nullptr; }
|
|
63
|
+
|
|
64
|
+
private:
|
|
65
|
+
PyObject* obj_;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
template<typename F>
|
|
69
|
+
auto py_safe_run(F&& f) -> decltype(f())
|
|
70
|
+
{
|
|
71
|
+
GilGuard gil;
|
|
72
|
+
return f();
|
|
25
73
|
}
|
|
26
74
|
|
|
27
|
-
|
|
75
|
+
// Reads the first two lines of resources/slavemodule.txt: the module name and
|
|
76
|
+
// (for FMUs built by newer versions of PythonFMU3) the name of the slave class.
|
|
77
|
+
std::pair<std::string, std::string> readSlaveModuleInfo(const std::string& fileName)
|
|
78
|
+
{
|
|
79
|
+
std::ifstream infile(fileName);
|
|
80
|
+
if (!infile) {
|
|
81
|
+
throw cppfmu::FatalError(("Unable to open " + fileName).c_str());
|
|
82
|
+
}
|
|
83
|
+
std::string moduleName, className;
|
|
84
|
+
std::getline(infile, moduleName);
|
|
85
|
+
std::getline(infile, className);
|
|
86
|
+
auto trim = [](std::string& s) {
|
|
87
|
+
const auto ws = " \t\r\n";
|
|
88
|
+
s.erase(s.find_last_not_of(ws) + 1);
|
|
89
|
+
s.erase(0, s.find_first_not_of(ws));
|
|
90
|
+
};
|
|
91
|
+
trim(moduleName);
|
|
92
|
+
trim(className);
|
|
93
|
+
return {moduleName, className};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Fallback for FMUs built by older versions that do not record the class name.
|
|
97
|
+
std::string findClassName(const std::string& fileName)
|
|
28
98
|
{
|
|
29
99
|
std::string line;
|
|
30
100
|
std::ifstream infile(fileName);
|
|
31
|
-
std::
|
|
32
|
-
while (getline(infile, line)) {
|
|
101
|
+
const std::regex re(R"(^class (\w+)\(([^)]*\bFmi3Slave(?:Base)?\b[^)]*)\)\s*:)");
|
|
102
|
+
while (std::getline(infile, line)) {
|
|
33
103
|
std::smatch m;
|
|
34
|
-
std::regex re(regexStr);
|
|
35
104
|
if (std::regex_search(line, m, re)) {
|
|
36
105
|
return m[1];
|
|
37
106
|
}
|
|
@@ -39,63 +108,137 @@ inline std::string findClassName(const std::string& fileName)
|
|
|
39
108
|
return "";
|
|
40
109
|
}
|
|
41
110
|
|
|
42
|
-
|
|
111
|
+
// Returns nullptr (with a Python error set) on failure.
|
|
112
|
+
PyObject* makeVrList(const cppfmu::FMIValueReference* vr, std::size_t nvr)
|
|
113
|
+
{
|
|
114
|
+
PyRef list(PyList_New(static_cast<Py_ssize_t>(nvr)));
|
|
115
|
+
if (!list) return nullptr;
|
|
116
|
+
for (std::size_t i = 0; i < nvr; i++) {
|
|
117
|
+
PyObject* item = PyLong_FromUnsignedLong(vr[i]);
|
|
118
|
+
if (item == nullptr) return nullptr;
|
|
119
|
+
PyList_SetItem(list.get(), static_cast<Py_ssize_t>(i), item); // steals item
|
|
120
|
+
}
|
|
121
|
+
return list.release();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
template<typename T, typename Convert>
|
|
125
|
+
PyObject* makeValueList(const T* values, std::size_t n, Convert convert)
|
|
126
|
+
{
|
|
127
|
+
PyRef list(PyList_New(static_cast<Py_ssize_t>(n)));
|
|
128
|
+
if (!list) return nullptr;
|
|
129
|
+
for (std::size_t i = 0; i < n; i++) {
|
|
130
|
+
PyObject* item = convert(values[i]);
|
|
131
|
+
if (item == nullptr) return nullptr;
|
|
132
|
+
PyList_SetItem(list.get(), static_cast<Py_ssize_t>(i), item); // steals item
|
|
133
|
+
}
|
|
134
|
+
return list.release();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
PyObject* makeString(cppfmu::FMIString s)
|
|
138
|
+
{
|
|
139
|
+
if (s == nullptr) {
|
|
140
|
+
Py_RETURN_NONE;
|
|
141
|
+
}
|
|
142
|
+
return PyUnicode_FromString(s);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
std::string toUtf8(PyObject* str)
|
|
146
|
+
{
|
|
147
|
+
PyRef bytes(str ? PyUnicode_AsEncodedString(str, "utf-8", "replace") : nullptr);
|
|
148
|
+
const char* data = bytes ? PyBytes_AsString(bytes.get()) : nullptr;
|
|
149
|
+
if (data == nullptr) {
|
|
150
|
+
PyErr_Clear();
|
|
151
|
+
return {};
|
|
152
|
+
}
|
|
153
|
+
return data;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Formats the pending Python exception, including its traceback, and clears it.
|
|
157
|
+
std::string fetchPythonError()
|
|
158
|
+
{
|
|
159
|
+
PyObject *type = nullptr, *value = nullptr, *traceback = nullptr;
|
|
160
|
+
PyErr_Fetch(&type, &value, &traceback);
|
|
161
|
+
if (type == nullptr) {
|
|
162
|
+
return "unknown error (no Python exception set)";
|
|
163
|
+
}
|
|
164
|
+
PyErr_NormalizeException(&type, &value, &traceback);
|
|
165
|
+
PyRef pType(type), pValue(value), pTraceback(traceback);
|
|
166
|
+
|
|
167
|
+
std::string result;
|
|
168
|
+
PyRef tbModule(PyImport_ImportModule("traceback"));
|
|
169
|
+
if (tbModule) {
|
|
170
|
+
PyRef lines(PyObject_CallMethod(tbModule.get(), "format_exception", "OOO",
|
|
171
|
+
pType.get(), pValue ? pValue.get() : Py_None, pTraceback ? pTraceback.get() : Py_None));
|
|
172
|
+
if (lines) {
|
|
173
|
+
PyRef sep(PyUnicode_FromString(""));
|
|
174
|
+
PyRef joined(sep ? PyUnicode_Join(sep.get(), lines.get()) : nullptr);
|
|
175
|
+
if (joined) result = toUtf8(joined.get());
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (result.empty()) {
|
|
179
|
+
PyRef repr(PyObject_Repr(pValue ? pValue.get() : pType.get()));
|
|
180
|
+
result = toUtf8(repr.get());
|
|
181
|
+
if (result.empty()) result = "unknown error";
|
|
182
|
+
}
|
|
183
|
+
PyErr_Clear(); // in case formatting itself raised
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
// Reads `obj.name` as a bool. Returns `fallback` when the attribute is absent.
|
|
189
|
+
// Returns -1 (Python error set) on failure.
|
|
190
|
+
int readBoolAttr(PyObject* obj, const char* name, bool fallback)
|
|
43
191
|
{
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
192
|
+
if (!PyObject_HasAttrString(obj, name)) return fallback ? 1 : 0;
|
|
193
|
+
PyRef attr(PyObject_GetAttrString(obj, name));
|
|
194
|
+
if (!attr) return -1;
|
|
195
|
+
return PyObject_IsTrue(attr.get());
|
|
47
196
|
}
|
|
48
197
|
|
|
198
|
+
} // namespace
|
|
199
|
+
|
|
49
200
|
PySlaveInstance::PySlaveInstance(std::string instanceName, std::string resources, const cppfmu::Logger& logger, const bool visible, std::shared_ptr<IPyState> pyState)
|
|
50
201
|
: pyState_{ std::move(pyState) }
|
|
202
|
+
, visible_(visible)
|
|
51
203
|
, instanceName_(std::move(instanceName))
|
|
52
204
|
, resources_(std::move(resources))
|
|
53
205
|
, logger_(logger)
|
|
54
|
-
, visible_(visible)
|
|
55
206
|
{
|
|
56
|
-
py_safe_run([this](
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (success != 0) {
|
|
70
|
-
handle_py_exception("[ctor] PyList_Insert", gilState);
|
|
71
|
-
}
|
|
207
|
+
py_safe_run([this]() {
|
|
208
|
+
// The destructor does not run when a constructor throws, so release what was
|
|
209
|
+
// acquired so far before propagating the error.
|
|
210
|
+
try {
|
|
211
|
+
// Prepend resources path to python sys path
|
|
212
|
+
PyRef sysModule(PyImport_ImportModule("sys"));
|
|
213
|
+
if (!sysModule) handle_py_exception("[ctor] PyImport_ImportModule(sys)");
|
|
214
|
+
PyRef sysPath(PyObject_GetAttrString(sysModule.get(), "path"));
|
|
215
|
+
if (!sysPath) handle_py_exception("[ctor] PyObject_GetAttrString(sys.path)");
|
|
216
|
+
PyRef resourcesStr(PyUnicode_FromString(resources_.c_str()));
|
|
217
|
+
if (!resourcesStr || PyList_Insert(sysPath.get(), 0, resourcesStr.get()) != 0) {
|
|
218
|
+
handle_py_exception("[ctor] PyList_Insert");
|
|
219
|
+
}
|
|
72
220
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
handle_py_exception("[ctor] PyImport_ImportModule", gilState);
|
|
78
|
-
}
|
|
221
|
+
const auto info = readSlaveModuleInfo(resources_ + "/slavemodule.txt");
|
|
222
|
+
const std::string& moduleName = info.first;
|
|
223
|
+
PyRef module(PyImport_ImportModule(moduleName.c_str()));
|
|
224
|
+
if (!module) handle_py_exception("[ctor] PyImport_ImportModule(" + moduleName + ")");
|
|
79
225
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
226
|
+
std::string className = info.second;
|
|
227
|
+
if (className.empty()) {
|
|
228
|
+
className = findClassName(resources_ + "/" + moduleName + ".py");
|
|
229
|
+
}
|
|
230
|
+
if (className.empty()) {
|
|
231
|
+
throw cppfmu::FatalError("Unable to find class extending Fmi3SlaveBase!");
|
|
232
|
+
}
|
|
85
233
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
handle_py_exception("[ctor] Py_BuildValue", gilState);
|
|
89
|
-
}
|
|
234
|
+
pClass_ = PyObject_GetAttrString(module.get(), className.c_str());
|
|
235
|
+
if (pClass_ == nullptr) handle_py_exception("[ctor] PyObject_GetAttr(" + className + ")");
|
|
90
236
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
handle_py_exception("[ctor] PyObject_GetAttr", gilState);
|
|
237
|
+
initialize();
|
|
238
|
+
} catch (...) {
|
|
239
|
+
cleanPyObject();
|
|
240
|
+
throw;
|
|
96
241
|
}
|
|
97
|
-
|
|
98
|
-
initialize(gilState);
|
|
99
242
|
});
|
|
100
243
|
}
|
|
101
244
|
|
|
@@ -103,568 +246,423 @@ void PySlaveInstance::clearLogBuffer() const
|
|
|
103
246
|
{
|
|
104
247
|
clearLogStrBuffer();
|
|
105
248
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
PyObject* statusField = Py_BuildValue("s", "status");
|
|
249
|
+
if (pMessages_ == nullptr || !PyList_Check(pMessages_)) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
110
252
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
PyObject* msg = PyList_GetItem(pMessages_, i);
|
|
253
|
+
// Logging must never mask the original problem: keep any pending exception aside.
|
|
254
|
+
PyObject *errType, *errValue, *errTraceback;
|
|
255
|
+
PyErr_Fetch(&errType, &errValue, &errTraceback);
|
|
115
256
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
257
|
+
auto& logger = const_cast<cppfmu::Logger&>(logger_);
|
|
258
|
+
const auto size = PyList_Size(pMessages_);
|
|
259
|
+
for (Py_ssize_t i = 0; i < size; i++) {
|
|
260
|
+
PyObject* msg = PyList_GetItem(pMessages_, i); // borrowed
|
|
120
261
|
|
|
121
|
-
|
|
262
|
+
PyRef debugAttr(PyObject_GetAttrString(msg, "debug"));
|
|
263
|
+
PyRef msgAttr(PyObject_GetAttrString(msg, "msg"));
|
|
264
|
+
PyRef categoryAttr(PyObject_GetAttrString(msg, "category"));
|
|
265
|
+
PyRef statusAttr(PyObject_GetAttrString(msg, "status"));
|
|
266
|
+
if (!debugAttr || !msgAttr || !categoryAttr || !statusAttr) {
|
|
267
|
+
PyErr_Clear();
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
122
270
|
|
|
123
|
-
|
|
124
|
-
char* msgStr = PyBytes_AsString(msgValue);
|
|
125
|
-
logStrBuffer.emplace_back(msgValue);
|
|
271
|
+
auto statusValue = static_cast<cppfmu::FMIStatus>(PyLong_AsLong(statusAttr.get()));
|
|
126
272
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
273
|
+
PyObject* msgValue = PyUnicode_AsEncodedString(msgAttr.get(), "utf-8", "replace");
|
|
274
|
+
if (msgValue == nullptr) {
|
|
275
|
+
PyErr_Clear();
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
const char* msgStr = PyBytes_AsString(msgValue);
|
|
279
|
+
logStrBuffer.emplace_back(msgValue);
|
|
280
|
+
|
|
281
|
+
const char* categoryStr = "";
|
|
282
|
+
if (categoryAttr.get() != Py_None) {
|
|
283
|
+
PyObject* categoryValue = PyUnicode_AsEncodedString(categoryAttr.get(), "utf-8", "replace");
|
|
284
|
+
if (categoryValue != nullptr) {
|
|
130
285
|
categoryStr = PyBytes_AsString(categoryValue);
|
|
131
286
|
logStrBuffer.emplace_back(categoryValue);
|
|
132
287
|
}
|
|
288
|
+
}
|
|
133
289
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
290
|
+
if (PyObject_IsTrue(debugAttr.get()) == 1) {
|
|
291
|
+
logger.DebugLog(statusValue, categoryStr, msgStr);
|
|
292
|
+
} else {
|
|
293
|
+
logger.Log(statusValue, categoryStr, msgStr);
|
|
139
294
|
}
|
|
140
|
-
|
|
295
|
+
PyErr_Clear();
|
|
141
296
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
Py_DECREF(statusField);
|
|
297
|
+
PyList_SetSlice(pMessages_, 0, size, nullptr);
|
|
298
|
+
|
|
299
|
+
PyErr_Restore(errType, errValue, errTraceback);
|
|
146
300
|
}
|
|
147
301
|
|
|
148
|
-
void PySlaveInstance::initialize(
|
|
302
|
+
void PySlaveInstance::initialize()
|
|
149
303
|
{
|
|
150
|
-
|
|
151
|
-
|
|
304
|
+
clearLogBuffer();
|
|
305
|
+
Py_CLEAR(pInstance_);
|
|
306
|
+
Py_CLEAR(pMessages_);
|
|
152
307
|
|
|
153
|
-
|
|
154
|
-
|
|
308
|
+
PyRef args(PyTuple_New(0));
|
|
309
|
+
PyRef kwargs(Py_BuildValue("{ss,ss,sn,si}",
|
|
155
310
|
"instance_name", instanceName_.c_str(),
|
|
156
311
|
"resources", resources_.c_str(),
|
|
157
|
-
"logger", &logger_,
|
|
158
|
-
"visible", visible_);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (pInstance_ == nullptr)
|
|
163
|
-
|
|
164
|
-
}
|
|
312
|
+
"logger", reinterpret_cast<Py_ssize_t>(&logger_),
|
|
313
|
+
"visible", static_cast<int>(visible_)));
|
|
314
|
+
if (!args || !kwargs) handle_py_exception("[initialize] Py_BuildValue");
|
|
315
|
+
|
|
316
|
+
pInstance_ = PyObject_Call(pClass_, args.get(), kwargs.get());
|
|
317
|
+
if (pInstance_ == nullptr) handle_py_exception("[initialize] PyObject_Call");
|
|
318
|
+
|
|
165
319
|
pMessages_ = PyObject_CallMethod(pInstance_, "_get_log_queue", nullptr);
|
|
320
|
+
if (pMessages_ == nullptr) handle_py_exception("[initialize] _get_log_queue");
|
|
321
|
+
clearLogBuffer();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
void PySlaveInstance::callVoidMethod(const char* method) const
|
|
325
|
+
{
|
|
326
|
+
PyRef result(PyObject_CallMethod(pInstance_, method, nullptr));
|
|
327
|
+
if (!result) handle_py_exception(std::string("[") + method + "]");
|
|
328
|
+
clearLogBuffer();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
void PySlaveInstance::callSetter(const char* method, const cppfmu::FMIValueReference* vr, std::size_t nvr, PyObject* valuesList)
|
|
332
|
+
{
|
|
333
|
+
PyRef values(valuesList);
|
|
334
|
+
if (!values) handle_py_exception(std::string("[") + method + "] building values");
|
|
335
|
+
PyRef vrs(makeVrList(vr, nvr));
|
|
336
|
+
if (!vrs) handle_py_exception(std::string("[") + method + "] building value references");
|
|
337
|
+
|
|
338
|
+
PyRef result(PyObject_CallMethod(pInstance_, method, "(OO)", vrs.get(), values.get()));
|
|
339
|
+
if (!result) handle_py_exception(std::string("[") + method + "]");
|
|
340
|
+
clearLogBuffer();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
PyObject* PySlaveInstance::callGetter(const char* method, const cppfmu::FMIValueReference* vr, std::size_t nvr, std::size_t nValues) const
|
|
344
|
+
{
|
|
345
|
+
PyRef vrs(makeVrList(vr, nvr));
|
|
346
|
+
if (!vrs) handle_py_exception(std::string("[") + method + "] building value references");
|
|
347
|
+
|
|
348
|
+
PyRef refs(PyObject_CallMethod(pInstance_, method, "(O)", vrs.get()));
|
|
349
|
+
if (!refs) handle_py_exception(std::string("[") + method + "]");
|
|
350
|
+
checkListSize(refs.get(), nValues, method);
|
|
351
|
+
return refs.release();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
PyObject* PySlaveInstance::callListMethod(const char* method, std::size_t expected) const
|
|
355
|
+
{
|
|
356
|
+
PyRef refs(PyObject_CallMethod(pInstance_, method, nullptr));
|
|
357
|
+
if (!refs) handle_py_exception(std::string("[") + method + "]");
|
|
358
|
+
checkListSize(refs.get(), expected, method);
|
|
359
|
+
return refs.release();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
void PySlaveInstance::checkListSize(PyObject* list, std::size_t expected, const char* method) const
|
|
363
|
+
{
|
|
364
|
+
if (!PyList_Check(list)) {
|
|
365
|
+
clearLogBuffer();
|
|
366
|
+
throw std::runtime_error(std::string("[") + method + "] must return a list");
|
|
367
|
+
}
|
|
368
|
+
const auto actual = static_cast<std::size_t>(PyList_Size(list));
|
|
369
|
+
if (actual < expected) {
|
|
370
|
+
clearLogBuffer();
|
|
371
|
+
std::ostringstream oss;
|
|
372
|
+
oss << "[" << method << "] returned " << actual << " values, but " << expected << " were requested";
|
|
373
|
+
throw std::runtime_error(oss.str());
|
|
374
|
+
}
|
|
166
375
|
}
|
|
167
376
|
|
|
168
377
|
void PySlaveInstance::SetupExperiment(cppfmu::FMIBoolean, cppfmu::FMIFloat64, cppfmu::FMIFloat64 startTime, cppfmu::FMIBoolean, cppfmu::FMIFloat64)
|
|
169
378
|
{
|
|
170
|
-
py_safe_run([this, startTime](
|
|
171
|
-
|
|
172
|
-
if (
|
|
173
|
-
handle_py_exception("[setupExperiment] PyObject_CallMethod", gilState);
|
|
174
|
-
}
|
|
175
|
-
Py_DECREF(f);
|
|
379
|
+
py_safe_run([this, startTime]() {
|
|
380
|
+
PyRef result(PyObject_CallMethod(pInstance_, "setup_experiment", "(d)", startTime));
|
|
381
|
+
if (!result) handle_py_exception("[setup_experiment]");
|
|
176
382
|
clearLogBuffer();
|
|
177
383
|
});
|
|
178
384
|
}
|
|
179
385
|
|
|
180
386
|
void PySlaveInstance::EnterInitializationMode()
|
|
181
387
|
{
|
|
182
|
-
py_safe_run([this](
|
|
183
|
-
auto f = PyObject_CallMethod(pInstance_, "enter_initialization_mode", nullptr);
|
|
184
|
-
if (f == nullptr) {
|
|
185
|
-
handle_py_exception("[enterInitializationMode] PyObject_CallMethod", gilState);
|
|
186
|
-
}
|
|
187
|
-
Py_DECREF(f);
|
|
188
|
-
clearLogBuffer();
|
|
189
|
-
});
|
|
388
|
+
py_safe_run([this]() { callVoidMethod("enter_initialization_mode"); });
|
|
190
389
|
}
|
|
191
390
|
|
|
192
391
|
void PySlaveInstance::ExitInitializationMode()
|
|
193
392
|
{
|
|
194
|
-
py_safe_run([this](
|
|
195
|
-
auto f = PyObject_CallMethod(pInstance_, "exit_initialization_mode", nullptr);
|
|
196
|
-
if (f == nullptr) {
|
|
197
|
-
handle_py_exception("[exitInitializationMode] PyObject_CallMethod", gilState);
|
|
198
|
-
}
|
|
199
|
-
Py_DECREF(f);
|
|
200
|
-
clearLogBuffer();
|
|
201
|
-
});
|
|
393
|
+
py_safe_run([this]() { callVoidMethod("exit_initialization_mode"); });
|
|
202
394
|
}
|
|
203
395
|
|
|
204
396
|
cppfmu::FMIStatus PySlaveInstance::DoStep(cppfmu::FMIFloat64 currentTime,
|
|
205
397
|
cppfmu::FMIFloat64 stepSize,
|
|
206
|
-
cppfmu::FMIBoolean
|
|
398
|
+
cppfmu::FMIBoolean,
|
|
207
399
|
cppfmu::FMIBoolean* eventHandlingNeeded,
|
|
208
400
|
cppfmu::FMIBoolean* terminateSimulation,
|
|
209
401
|
cppfmu::FMIBoolean* earlyReturn,
|
|
210
402
|
cppfmu::FMIFloat64& endOfStep)
|
|
211
403
|
{
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
404
|
+
// The FMI standard requires these outputs to always be written.
|
|
405
|
+
if (eventHandlingNeeded) *eventHandlingNeeded = false;
|
|
406
|
+
if (terminateSimulation) *terminateSimulation = false;
|
|
407
|
+
if (earlyReturn) *earlyReturn = false;
|
|
408
|
+
|
|
409
|
+
return py_safe_run([&]() {
|
|
410
|
+
cppfmu::FMIStatus fmuStatus = cppfmu::FMIOK;
|
|
411
|
+
PyRef result(PyObject_CallMethod(pInstance_, "do_step", "(dd)", currentTime, stepSize));
|
|
412
|
+
if (!result) handle_py_exception("[do_step]");
|
|
413
|
+
PyObject* f = result.get();
|
|
218
414
|
|
|
219
415
|
if (PyObject_HasAttrString(f, "status")) {
|
|
220
|
-
|
|
221
|
-
if (pyStatus)
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
416
|
+
PyRef pyStatus(PyObject_GetAttrString(f, "status"));
|
|
417
|
+
if (!pyStatus) handle_py_exception("[do_step] status");
|
|
418
|
+
fmuStatus = static_cast<cppfmu::FMIStatus>(PyLong_AsLong(pyStatus.get()));
|
|
419
|
+
if (PyErr_Occurred()) handle_py_exception("[do_step] status conversion");
|
|
225
420
|
} else {
|
|
226
|
-
|
|
227
|
-
if (
|
|
228
|
-
|
|
229
|
-
}
|
|
421
|
+
const int ok = PyObject_IsTrue(f);
|
|
422
|
+
if (ok < 0) handle_py_exception("[do_step] return value");
|
|
423
|
+
if (!ok) fmuStatus = cppfmu::FMIDiscard;
|
|
230
424
|
}
|
|
231
425
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
426
|
+
const int ehn = readBoolAttr(f, "eventHandlingNeeded", false);
|
|
427
|
+
const int term = readBoolAttr(f, "terminateSimulation", false);
|
|
428
|
+
const int early = readBoolAttr(f, "earlyReturn", false);
|
|
429
|
+
if (ehn < 0 || term < 0 || early < 0) handle_py_exception("[do_step] result");
|
|
430
|
+
|
|
431
|
+
if (eventHandlingNeeded) *eventHandlingNeeded = ehn == 1;
|
|
432
|
+
if (terminateSimulation) *terminateSimulation = term == 1;
|
|
433
|
+
if (earlyReturn) *earlyReturn = early == 1;
|
|
434
|
+
if (fmuStatus == cppfmu::FMIOK) endOfStep = currentTime + stepSize;
|
|
435
|
+
|
|
240
436
|
clearLogBuffer();
|
|
437
|
+
return fmuStatus;
|
|
241
438
|
});
|
|
242
|
-
|
|
243
|
-
return fmuStatus;
|
|
244
439
|
}
|
|
245
440
|
|
|
246
441
|
void PySlaveInstance::Reset()
|
|
247
442
|
{
|
|
248
|
-
py_safe_run([this](
|
|
249
|
-
initialize(gilState);
|
|
250
|
-
});
|
|
443
|
+
py_safe_run([this]() { initialize(); });
|
|
251
444
|
}
|
|
252
445
|
|
|
253
446
|
void PySlaveInstance::Terminate()
|
|
254
447
|
{
|
|
255
|
-
py_safe_run([this](
|
|
256
|
-
auto f = PyObject_CallMethod(pInstance_, "terminate", nullptr);
|
|
257
|
-
if (f == nullptr) {
|
|
258
|
-
handle_py_exception("[terminate] PyObject_CallMethod", gilState);
|
|
259
|
-
}
|
|
260
|
-
Py_DECREF(f);
|
|
261
|
-
clearLogBuffer();
|
|
262
|
-
});
|
|
448
|
+
py_safe_run([this]() { callVoidMethod("terminate"); });
|
|
263
449
|
}
|
|
264
450
|
|
|
265
451
|
void PySlaveInstance::SetFloat64(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIFloat64* values, std::size_t nValues)
|
|
266
452
|
{
|
|
267
|
-
py_safe_run([
|
|
268
|
-
|
|
269
|
-
PyObject* refs = PyList_New(nValues);
|
|
270
|
-
for (int i = 0; i < nvr; i++) {
|
|
271
|
-
PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i]));
|
|
272
|
-
}
|
|
273
|
-
for (int i = 0; i < nValues; i++) {
|
|
274
|
-
PyList_SetItem(refs, i, Py_BuildValue("d", values[i]));
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
auto f = PyObject_CallMethod(pInstance_, "set_float64", "(OO)", vrs, refs);
|
|
278
|
-
Py_DECREF(vrs);
|
|
279
|
-
Py_DECREF(refs);
|
|
280
|
-
if (f == nullptr) {
|
|
281
|
-
handle_py_exception("[setFloat64] PyObject_CallMethod", gilState);
|
|
282
|
-
}
|
|
283
|
-
Py_DECREF(f);
|
|
284
|
-
clearLogBuffer();
|
|
453
|
+
py_safe_run([&]() {
|
|
454
|
+
callSetter("set_float64", vr, nvr, makeValueList(values, nValues, [](cppfmu::FMIFloat64 v) { return PyFloat_FromDouble(v); }));
|
|
285
455
|
});
|
|
286
456
|
}
|
|
287
457
|
|
|
288
458
|
void PySlaveInstance::SetInt32(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIInt32* values, std::size_t nValues)
|
|
289
459
|
{
|
|
290
|
-
py_safe_run([
|
|
291
|
-
|
|
292
|
-
PyObject* refs = PyList_New(nValues);
|
|
293
|
-
for (int i = 0; i < nvr; i++) {
|
|
294
|
-
PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i]));
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
for (int i = 0; i < nValues; i++) {
|
|
298
|
-
PyList_SetItem(refs, i, Py_BuildValue("i", values[i]));
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
auto f = PyObject_CallMethod(pInstance_, "set_int32", "(OO)", vrs, refs);
|
|
302
|
-
Py_DECREF(vrs);
|
|
303
|
-
Py_DECREF(refs);
|
|
304
|
-
if (f == nullptr) {
|
|
305
|
-
handle_py_exception("[setInt32] PyObject_CallMethod", gilState);
|
|
306
|
-
}
|
|
307
|
-
Py_DECREF(f);
|
|
308
|
-
clearLogBuffer();
|
|
460
|
+
py_safe_run([&]() {
|
|
461
|
+
callSetter("set_int32", vr, nvr, makeValueList(values, nValues, [](cppfmu::FMIInt32 v) { return PyLong_FromLong(v); }));
|
|
309
462
|
});
|
|
310
463
|
}
|
|
311
464
|
|
|
312
465
|
void PySlaveInstance::SetInt64(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIInt64* values, std::size_t nValues)
|
|
313
466
|
{
|
|
314
|
-
py_safe_run([
|
|
315
|
-
|
|
316
|
-
PyObject* refs = PyList_New(nValues);
|
|
317
|
-
for (int i = 0; i < nvr; i++) {
|
|
318
|
-
PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i]));
|
|
319
|
-
}
|
|
320
|
-
for (int i = 0; i < nValues; i++) {
|
|
321
|
-
PyList_SetItem(refs, i, Py_BuildValue("L", values[i]));
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
auto f = PyObject_CallMethod(pInstance_, "set_int64", "(OO)", vrs, refs);
|
|
325
|
-
Py_DECREF(vrs);
|
|
326
|
-
Py_DECREF(refs);
|
|
327
|
-
if (f == nullptr) {
|
|
328
|
-
handle_py_exception("[setInt64] PyObject_CallMethod", gilState);
|
|
329
|
-
}
|
|
330
|
-
Py_DECREF(f);
|
|
331
|
-
clearLogBuffer();
|
|
467
|
+
py_safe_run([&]() {
|
|
468
|
+
callSetter("set_int64", vr, nvr, makeValueList(values, nValues, [](cppfmu::FMIInt64 v) { return PyLong_FromLongLong(v); }));
|
|
332
469
|
});
|
|
333
470
|
}
|
|
334
471
|
|
|
335
472
|
void PySlaveInstance::SetUInt64(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIUInt64* values, std::size_t nValues)
|
|
336
473
|
{
|
|
337
|
-
py_safe_run([
|
|
338
|
-
|
|
339
|
-
PyObject* refs = PyList_New(nValues);
|
|
340
|
-
for (int i = 0; i < nvr; i++) {
|
|
341
|
-
PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i]));
|
|
342
|
-
}
|
|
343
|
-
for (int i = 0; i < nValues; i++) {
|
|
344
|
-
PyList_SetItem(refs, i, Py_BuildValue("K", values[i]));
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
auto f = PyObject_CallMethod(pInstance_, "set_uint64", "(OO)", vrs, refs);
|
|
348
|
-
Py_DECREF(vrs);
|
|
349
|
-
Py_DECREF(refs);
|
|
350
|
-
if (f == nullptr) {
|
|
351
|
-
handle_py_exception("[setUInt64] PyObject_CallMethod", gilState);
|
|
352
|
-
}
|
|
353
|
-
Py_DECREF(f);
|
|
354
|
-
clearLogBuffer();
|
|
474
|
+
py_safe_run([&]() {
|
|
475
|
+
callSetter("set_uint64", vr, nvr, makeValueList(values, nValues, [](cppfmu::FMIUInt64 v) { return PyLong_FromUnsignedLongLong(v); }));
|
|
355
476
|
});
|
|
356
477
|
}
|
|
357
478
|
|
|
358
479
|
void PySlaveInstance::SetBoolean(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIBoolean* values, std::size_t nValues)
|
|
359
480
|
{
|
|
360
|
-
py_safe_run([
|
|
361
|
-
|
|
362
|
-
PyObject* refs = PyList_New(nValues);
|
|
363
|
-
for (int i = 0; i < nvr; i++) {
|
|
364
|
-
PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i]));
|
|
365
|
-
}
|
|
366
|
-
for (int i = 0; i < nValues; i++) {
|
|
367
|
-
PyList_SetItem(refs, i, PyBool_FromLong(values[i]));
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
auto f = PyObject_CallMethod(pInstance_, "set_boolean", "(OO)", vrs, refs);
|
|
371
|
-
Py_DECREF(vrs);
|
|
372
|
-
Py_DECREF(refs);
|
|
373
|
-
if (f == nullptr) {
|
|
374
|
-
handle_py_exception("[setBoolean] PyObject_CallMethod", gilState);
|
|
375
|
-
}
|
|
376
|
-
Py_DECREF(f);
|
|
377
|
-
clearLogBuffer();
|
|
481
|
+
py_safe_run([&]() {
|
|
482
|
+
callSetter("set_boolean", vr, nvr, makeValueList(values, nValues, [](cppfmu::FMIBoolean v) { return PyBool_FromLong(v ? 1 : 0); }));
|
|
378
483
|
});
|
|
379
484
|
}
|
|
380
485
|
|
|
381
486
|
void PySlaveInstance::SetString(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIString const* values, std::size_t nValues)
|
|
382
487
|
{
|
|
383
|
-
py_safe_run([
|
|
384
|
-
|
|
385
|
-
PyObject* refs = PyList_New(nvr);
|
|
386
|
-
for (int i = 0; i < nvr; i++) {
|
|
387
|
-
PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i]));
|
|
388
|
-
}
|
|
389
|
-
for (int i = 0; i < nValues; i++) {
|
|
390
|
-
PyList_SetItem(refs, i, Py_BuildValue("s", values[i]));
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
auto f = PyObject_CallMethod(pInstance_, "set_string", "(OO)", vrs, refs);
|
|
394
|
-
Py_DECREF(vrs);
|
|
395
|
-
Py_DECREF(refs);
|
|
396
|
-
if (f == nullptr) {
|
|
397
|
-
handle_py_exception("[setString] PyObject_CallMethod", gilState);
|
|
398
|
-
}
|
|
399
|
-
Py_DECREF(f);
|
|
400
|
-
clearLogBuffer();
|
|
488
|
+
py_safe_run([&]() {
|
|
489
|
+
callSetter("set_string", vr, nvr, makeValueList(values, nValues, makeString));
|
|
401
490
|
});
|
|
402
491
|
}
|
|
403
492
|
|
|
404
493
|
void PySlaveInstance::GetFloat64(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIFloat64* values, std::size_t nValues) const
|
|
405
494
|
{
|
|
406
|
-
py_safe_run([
|
|
407
|
-
|
|
408
|
-
for (
|
|
409
|
-
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
auto refs = PyObject_CallMethod(pInstance_, "get_float64", "O", vrs);
|
|
413
|
-
Py_DECREF(vrs);
|
|
414
|
-
if (refs == nullptr) {
|
|
415
|
-
handle_py_exception("[getFloat64] PyObject_CallMethod", gilState);
|
|
495
|
+
py_safe_run([&]() {
|
|
496
|
+
PyRef refs(callGetter("get_float64", vr, nvr, nValues));
|
|
497
|
+
for (std::size_t i = 0; i < nValues; i++) {
|
|
498
|
+
values[i] = PyFloat_AsDouble(PyList_GetItem(refs.get(), i));
|
|
416
499
|
}
|
|
417
|
-
|
|
418
|
-
for (int i = 0; i < nValues; i++) {
|
|
419
|
-
PyObject* value = PyList_GetItem(refs, i);
|
|
420
|
-
values[i] = PyFloat_AsDouble(value);
|
|
421
|
-
}
|
|
422
|
-
Py_DECREF(refs);
|
|
500
|
+
if (PyErr_Occurred()) handle_py_exception("[get_float64] conversion");
|
|
423
501
|
clearLogBuffer();
|
|
424
502
|
});
|
|
425
503
|
}
|
|
426
504
|
|
|
427
505
|
void PySlaveInstance::GetInt32(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIInt32* values, std::size_t nValues) const
|
|
428
506
|
{
|
|
429
|
-
py_safe_run([
|
|
430
|
-
|
|
431
|
-
for (
|
|
432
|
-
|
|
507
|
+
py_safe_run([&]() {
|
|
508
|
+
PyRef refs(callGetter("get_int32", vr, nvr, nValues));
|
|
509
|
+
for (std::size_t i = 0; i < nValues; i++) {
|
|
510
|
+
values[i] = static_cast<cppfmu::FMIInt32>(PyLong_AsLong(PyList_GetItem(refs.get(), i)));
|
|
433
511
|
}
|
|
434
|
-
|
|
435
|
-
Py_DECREF(vrs);
|
|
436
|
-
if (refs == nullptr) {
|
|
437
|
-
handle_py_exception("[getInt32] PyObject_CallMethod", gilState);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
for (int i = 0; i < nValues; i++) {
|
|
441
|
-
PyObject* value = PyList_GetItem(refs, i);
|
|
442
|
-
values[i] = static_cast<cppfmu::FMIInt32>(PyLong_AsLong(value));
|
|
443
|
-
}
|
|
444
|
-
Py_DECREF(refs);
|
|
512
|
+
if (PyErr_Occurred()) handle_py_exception("[get_int32] conversion");
|
|
445
513
|
clearLogBuffer();
|
|
446
514
|
});
|
|
447
515
|
}
|
|
448
516
|
|
|
449
517
|
void PySlaveInstance::GetInt64(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIInt64* values, std::size_t nValues) const
|
|
450
518
|
{
|
|
451
|
-
py_safe_run([
|
|
452
|
-
|
|
453
|
-
for (
|
|
454
|
-
|
|
455
|
-
}
|
|
456
|
-
auto refs = PyObject_CallMethod(pInstance_, "get_int64", "O", vrs);
|
|
457
|
-
Py_DECREF(vrs);
|
|
458
|
-
if (refs == nullptr) {
|
|
459
|
-
handle_py_exception("[getInt64] PyObject_CallMethod", gilState);
|
|
519
|
+
py_safe_run([&]() {
|
|
520
|
+
PyRef refs(callGetter("get_int64", vr, nvr, nValues));
|
|
521
|
+
for (std::size_t i = 0; i < nValues; i++) {
|
|
522
|
+
values[i] = static_cast<cppfmu::FMIInt64>(PyLong_AsLongLong(PyList_GetItem(refs.get(), i)));
|
|
460
523
|
}
|
|
461
|
-
|
|
462
|
-
for (int i = 0; i < nValues; i++) {
|
|
463
|
-
PyObject* value = PyList_GetItem(refs, i);
|
|
464
|
-
values[i] = static_cast<cppfmu::FMIInt64>(PyLong_AsLongLong(value));
|
|
465
|
-
}
|
|
466
|
-
Py_DECREF(refs);
|
|
524
|
+
if (PyErr_Occurred()) handle_py_exception("[get_int64] conversion");
|
|
467
525
|
clearLogBuffer();
|
|
468
526
|
});
|
|
469
527
|
}
|
|
470
528
|
|
|
471
529
|
void PySlaveInstance::GetUInt64(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIUInt64* values, std::size_t nValues) const
|
|
472
530
|
{
|
|
473
|
-
py_safe_run([
|
|
474
|
-
|
|
475
|
-
for (
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
for (int i = 0; i < nValues; i++) {
|
|
485
|
-
PyObject* item = PyList_GetItem(refs, i);
|
|
486
|
-
PyObject* value = PyObject_GetAttrString(item, "value");
|
|
487
|
-
if (value && PyLong_Check(value))
|
|
488
|
-
{
|
|
489
|
-
values[i] = static_cast<cppfmu::FMIUInt64>(PyLong_AsUnsignedLongLong(value));
|
|
531
|
+
py_safe_run([&]() {
|
|
532
|
+
PyRef refs(callGetter("get_uint64", vr, nvr, nValues));
|
|
533
|
+
for (std::size_t i = 0; i < nValues; i++) {
|
|
534
|
+
PyObject* item = PyList_GetItem(refs.get(), i);
|
|
535
|
+
if (PyLong_Check(item)) {
|
|
536
|
+
values[i] = static_cast<cppfmu::FMIUInt64>(PyLong_AsUnsignedLongLong(item));
|
|
537
|
+
} else {
|
|
538
|
+
// ctypes.c_uint64 (as returned by Fmi3SlaveBase.get_uint64)
|
|
539
|
+
PyRef value(PyObject_GetAttrString(item, "value"));
|
|
540
|
+
if (!value) handle_py_exception("[get_uint64] value attribute");
|
|
541
|
+
values[i] = static_cast<cppfmu::FMIUInt64>(PyLong_AsUnsignedLongLong(value.get()));
|
|
490
542
|
}
|
|
491
543
|
}
|
|
492
|
-
|
|
493
|
-
Py_DECREF(refs);
|
|
544
|
+
if (PyErr_Occurred()) handle_py_exception("[get_uint64] conversion");
|
|
494
545
|
clearLogBuffer();
|
|
495
546
|
});
|
|
496
547
|
}
|
|
497
548
|
|
|
498
549
|
void PySlaveInstance::GetBoolean(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIBoolean* values, std::size_t nValues) const
|
|
499
550
|
{
|
|
500
|
-
py_safe_run([
|
|
501
|
-
|
|
502
|
-
for (
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
Py_DECREF(vrs);
|
|
507
|
-
if (refs == nullptr) {
|
|
508
|
-
handle_py_exception("[getBoolean] PyObject_CallMethod", gilState);
|
|
551
|
+
py_safe_run([&]() {
|
|
552
|
+
PyRef refs(callGetter("get_boolean", vr, nvr, nValues));
|
|
553
|
+
for (std::size_t i = 0; i < nValues; i++) {
|
|
554
|
+
const int v = PyObject_IsTrue(PyList_GetItem(refs.get(), i));
|
|
555
|
+
if (v < 0) handle_py_exception("[get_boolean] conversion");
|
|
556
|
+
values[i] = v == 1;
|
|
509
557
|
}
|
|
510
|
-
|
|
511
|
-
for (int i = 0; i < nValues; i++) {
|
|
512
|
-
PyObject* value = PyList_GetItem(refs, i);
|
|
513
|
-
values[i] = PyObject_IsTrue(value);
|
|
514
|
-
}
|
|
515
|
-
Py_DECREF(refs);
|
|
516
558
|
clearLogBuffer();
|
|
517
559
|
});
|
|
518
560
|
}
|
|
519
561
|
|
|
520
562
|
void PySlaveInstance::GetString(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIString* values, std::size_t nValues) const
|
|
521
563
|
{
|
|
522
|
-
py_safe_run([
|
|
564
|
+
py_safe_run([&]() {
|
|
565
|
+
// Strings returned to the importer stay valid until the next GetString call.
|
|
523
566
|
clearStrBuffer();
|
|
524
|
-
|
|
525
|
-
for (
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
auto refs = PyObject_CallMethod(pInstance_, "get_string", "O", vrs);
|
|
529
|
-
Py_DECREF(vrs);
|
|
530
|
-
if (refs == nullptr) {
|
|
531
|
-
handle_py_exception("[getString] PyObject_CallMethod", gilState);
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
for (int i = 0; i < nValues; i++) {
|
|
535
|
-
PyObject* value = PyUnicode_AsEncodedString(PyList_GetItem(refs, i), "utf-8", nullptr);
|
|
567
|
+
PyRef refs(callGetter("get_string", vr, nvr, nValues));
|
|
568
|
+
for (std::size_t i = 0; i < nValues; i++) {
|
|
569
|
+
PyObject* value = PyUnicode_AsEncodedString(PyList_GetItem(refs.get(), i), "utf-8", nullptr);
|
|
570
|
+
if (value == nullptr) handle_py_exception("[get_string] conversion");
|
|
536
571
|
values[i] = PyBytes_AsString(value);
|
|
537
572
|
strBuffer.emplace_back(value);
|
|
538
573
|
}
|
|
539
|
-
Py_DECREF(refs);
|
|
540
574
|
clearLogBuffer();
|
|
541
575
|
});
|
|
542
576
|
}
|
|
543
577
|
|
|
544
578
|
void PySlaveInstance::GetContinuousStates(cppfmu::FMIFloat64* continuousStates, std::size_t nStates) const
|
|
545
579
|
{
|
|
546
|
-
py_safe_run([
|
|
547
|
-
|
|
548
|
-
if (f == nullptr) {
|
|
549
|
-
handle_py_exception("[get_continuous_states] PyObject_CallMethod", gilState);
|
|
550
|
-
}
|
|
551
|
-
// Assuming f is a list of floats
|
|
580
|
+
py_safe_run([&]() {
|
|
581
|
+
PyRef refs(callListMethod("get_continuous_states", nStates));
|
|
552
582
|
for (std::size_t i = 0; i < nStates; i++) {
|
|
553
|
-
|
|
554
|
-
continuousStates[i] = static_cast<cppfmu::FMIFloat64>(PyFloat_AsDouble(item));
|
|
583
|
+
continuousStates[i] = PyFloat_AsDouble(PyList_GetItem(refs.get(), i));
|
|
555
584
|
}
|
|
556
|
-
|
|
585
|
+
if (PyErr_Occurred()) handle_py_exception("[get_continuous_states] conversion");
|
|
557
586
|
clearLogBuffer();
|
|
558
587
|
});
|
|
559
588
|
}
|
|
560
589
|
|
|
561
590
|
void PySlaveInstance::GetContinuousStateDerivatives(cppfmu::FMIFloat64* continuousStateDerivatives, std::size_t nStates) const
|
|
562
591
|
{
|
|
563
|
-
py_safe_run([
|
|
564
|
-
|
|
565
|
-
if (f == nullptr) {
|
|
566
|
-
PyErr_Print();
|
|
567
|
-
handle_py_exception("[get_continuous_state_derivatives] PyObject_CallMethod", gilState);
|
|
568
|
-
}
|
|
569
|
-
// Assuming f is a list of floats
|
|
592
|
+
py_safe_run([&]() {
|
|
593
|
+
PyRef refs(callListMethod("get_continuous_state_derivatives", nStates));
|
|
570
594
|
for (std::size_t i = 0; i < nStates; i++) {
|
|
571
|
-
|
|
572
|
-
continuousStateDerivatives[i] = static_cast<cppfmu::FMIFloat64>(PyFloat_AsDouble(item));
|
|
595
|
+
continuousStateDerivatives[i] = PyFloat_AsDouble(PyList_GetItem(refs.get(), i));
|
|
573
596
|
}
|
|
574
|
-
|
|
597
|
+
if (PyErr_Occurred()) handle_py_exception("[get_continuous_state_derivatives] conversion");
|
|
575
598
|
clearLogBuffer();
|
|
576
599
|
});
|
|
577
600
|
}
|
|
578
601
|
|
|
579
602
|
void PySlaveInstance::GetNominalsOfContinuousStates(cppfmu::FMIFloat64* nominalsOfContinuousStates, std::size_t nStates) const
|
|
580
603
|
{
|
|
581
|
-
py_safe_run([
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
handle_py_exception("[getNominalContinuousStates] PyObject_CallMethod", gilState);
|
|
586
|
-
}
|
|
587
|
-
// Assuming f is a list of floats
|
|
604
|
+
py_safe_run([&]() {
|
|
605
|
+
PyRef refs(PyObject_CallMethod(pInstance_, "get_nominals_of_continuous_states", "(n)", static_cast<Py_ssize_t>(nStates)));
|
|
606
|
+
if (!refs) handle_py_exception("[get_nominals_of_continuous_states]");
|
|
607
|
+
checkListSize(refs.get(), nStates, "get_nominals_of_continuous_states");
|
|
588
608
|
for (std::size_t i = 0; i < nStates; i++) {
|
|
589
|
-
|
|
590
|
-
nominalsOfContinuousStates[i] = static_cast<cppfmu::FMIFloat64>(PyFloat_AsDouble(item));
|
|
609
|
+
nominalsOfContinuousStates[i] = PyFloat_AsDouble(PyList_GetItem(refs.get(), i));
|
|
591
610
|
}
|
|
592
|
-
|
|
611
|
+
if (PyErr_Occurred()) handle_py_exception("[get_nominals_of_continuous_states] conversion");
|
|
593
612
|
clearLogBuffer();
|
|
594
613
|
});
|
|
595
614
|
}
|
|
596
615
|
|
|
597
616
|
void PySlaveInstance::SetContinuousStates(const cppfmu::FMIFloat64* continuousStates, std::size_t nStates)
|
|
598
617
|
{
|
|
599
|
-
py_safe_run([
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
auto f = PyObject_CallMethod(pInstance_, "set_continuous_states", "(O)", refs);
|
|
606
|
-
Py_DECREF(refs);
|
|
607
|
-
if (f == nullptr) {
|
|
608
|
-
handle_py_exception("[setContinuousStates] PyObject_CallMethod", gilState);
|
|
609
|
-
}
|
|
610
|
-
Py_DECREF(f);
|
|
618
|
+
py_safe_run([&]() {
|
|
619
|
+
PyRef refs(makeValueList(continuousStates, nStates, [](cppfmu::FMIFloat64 v) { return PyFloat_FromDouble(v); }));
|
|
620
|
+
if (!refs) handle_py_exception("[set_continuous_states] building values");
|
|
621
|
+
PyRef result(PyObject_CallMethod(pInstance_, "set_continuous_states", "(O)", refs.get()));
|
|
622
|
+
if (!result) handle_py_exception("[set_continuous_states]");
|
|
611
623
|
clearLogBuffer();
|
|
612
624
|
});
|
|
613
625
|
}
|
|
614
626
|
|
|
615
627
|
void PySlaveInstance::GetNumberOfContinuousStates(std::size_t& nStates) const
|
|
616
628
|
{
|
|
617
|
-
py_safe_run([
|
|
618
|
-
|
|
619
|
-
if (
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
nStates = static_cast<std::size_t>(PyLong_AsLong(f));
|
|
623
|
-
Py_DECREF(f);
|
|
629
|
+
py_safe_run([&]() {
|
|
630
|
+
PyRef result(PyObject_CallMethod(pInstance_, "get_number_of_continuous_states", nullptr));
|
|
631
|
+
if (!result) handle_py_exception("[get_number_of_continuous_states]");
|
|
632
|
+
nStates = PyLong_AsSize_t(result.get());
|
|
633
|
+
if (PyErr_Occurred()) handle_py_exception("[get_number_of_continuous_states] conversion");
|
|
624
634
|
clearLogBuffer();
|
|
625
635
|
});
|
|
626
636
|
}
|
|
627
637
|
|
|
628
638
|
void PySlaveInstance::GetNumberOfEventIndicators(std::size_t& nIndicators) const
|
|
629
639
|
{
|
|
630
|
-
py_safe_run([
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
Py_DECREF(f);
|
|
637
|
-
clearLogBuffer();
|
|
640
|
+
py_safe_run([&]() {
|
|
641
|
+
PyRef result(PyObject_CallMethod(pInstance_, "get_number_of_event_indicators", nullptr));
|
|
642
|
+
if (!result) handle_py_exception("[get_number_of_event_indicators]");
|
|
643
|
+
nIndicators = PyLong_AsSize_t(result.get());
|
|
644
|
+
if (PyErr_Occurred()) handle_py_exception("[get_number_of_event_indicators] conversion");
|
|
645
|
+
clearLogBuffer();
|
|
638
646
|
});
|
|
639
647
|
}
|
|
640
648
|
|
|
641
649
|
void PySlaveInstance::GetEventIndicators(cppfmu::FMIFloat64* eventIndicators, std::size_t nIndicators) const
|
|
642
650
|
{
|
|
643
|
-
py_safe_run([
|
|
644
|
-
|
|
645
|
-
if (f == nullptr) {
|
|
646
|
-
handle_py_exception("[getEventIndicators] PyObject_CallMethod", gilState);
|
|
647
|
-
}
|
|
648
|
-
// Assuming f is a list of floats
|
|
651
|
+
py_safe_run([&]() {
|
|
652
|
+
PyRef refs(callListMethod("get_event_indicators", nIndicators));
|
|
649
653
|
for (std::size_t i = 0; i < nIndicators; i++) {
|
|
650
|
-
|
|
651
|
-
eventIndicators[i] = static_cast<cppfmu::FMIFloat64>(PyFloat_AsDouble(item));
|
|
654
|
+
eventIndicators[i] = PyFloat_AsDouble(PyList_GetItem(refs.get(), i));
|
|
652
655
|
}
|
|
653
|
-
|
|
656
|
+
if (PyErr_Occurred()) handle_py_exception("[get_event_indicators] conversion");
|
|
654
657
|
clearLogBuffer();
|
|
655
658
|
});
|
|
656
|
-
|
|
657
659
|
}
|
|
658
660
|
|
|
659
|
-
|
|
660
661
|
void PySlaveInstance::SetTime(cppfmu::FMIFloat64 time)
|
|
661
662
|
{
|
|
662
|
-
py_safe_run([this, time](
|
|
663
|
-
|
|
664
|
-
if (
|
|
665
|
-
handle_py_exception("[setTime] PyObject_CallMethod", gilState);
|
|
666
|
-
}
|
|
667
|
-
Py_DECREF(f);
|
|
663
|
+
py_safe_run([this, time]() {
|
|
664
|
+
PyRef result(PyObject_CallMethod(pInstance_, "set_time", "(d)", time));
|
|
665
|
+
if (!result) handle_py_exception("[set_time]");
|
|
668
666
|
clearLogBuffer();
|
|
669
667
|
});
|
|
670
668
|
}
|
|
@@ -677,181 +675,181 @@ void PySlaveInstance::UpdateDiscreteStates(
|
|
|
677
675
|
cppfmu::FMIBoolean* nextEventTimeDefined,
|
|
678
676
|
cppfmu::FMIFloat64* nextEventTime)
|
|
679
677
|
{
|
|
680
|
-
py_safe_run([
|
|
681
|
-
|
|
682
|
-
if (
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
*
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
678
|
+
py_safe_run([&]() {
|
|
679
|
+
PyRef result(PyObject_CallMethod(pInstance_, "update_discrete_states", nullptr));
|
|
680
|
+
if (!result) handle_py_exception("[update_discrete_states]");
|
|
681
|
+
PyObject* f = result.get();
|
|
682
|
+
|
|
683
|
+
struct Field
|
|
684
|
+
{
|
|
685
|
+
const char* name;
|
|
686
|
+
cppfmu::FMIBoolean* out;
|
|
687
|
+
};
|
|
688
|
+
const Field fields[] = {
|
|
689
|
+
{"discreteStatesNeedUpdate", discreteStatesNeedUpdate},
|
|
690
|
+
{"terminateSimulation", terminateSimulation},
|
|
691
|
+
{"nominalContinuousStatesChanged", nominalContinuousStatesChanged},
|
|
692
|
+
{"valuesOfContinuousStatesChanged", valuesOfContinuousStatesChanged},
|
|
693
|
+
{"nextEventTimeDefined", nextEventTimeDefined},
|
|
694
|
+
};
|
|
695
|
+
for (const auto& field : fields) {
|
|
696
|
+
const int v = readBoolAttr(f, field.name, false);
|
|
697
|
+
if (v < 0) handle_py_exception(std::string("[update_discrete_states] ") + field.name);
|
|
698
|
+
if (field.out) *field.out = v == 1;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (nextEventTimeDefined && *nextEventTimeDefined && nextEventTime) {
|
|
702
|
+
PyRef pyNextEventTime(PyObject_GetAttrString(f, "nextEventTime"));
|
|
703
|
+
if (!pyNextEventTime) handle_py_exception("[update_discrete_states] nextEventTime");
|
|
704
|
+
*nextEventTime = PyFloat_AsDouble(pyNextEventTime.get());
|
|
705
|
+
if (PyErr_Occurred()) handle_py_exception("[update_discrete_states] nextEventTime conversion");
|
|
695
706
|
}
|
|
696
|
-
if (PyObject_HasAttrString(f, "nominalContinuousStatesChanged")) {
|
|
697
|
-
PyObject* pyNominalContinuousStatesChanged = PyObject_GetAttrString(f, "nominalContinuousStatesChanged");
|
|
698
|
-
*nominalContinuousStatesChanged = static_cast<bool>(PyObject_IsTrue(pyNominalContinuousStatesChanged));
|
|
699
|
-
Py_DECREF(pyNominalContinuousStatesChanged);
|
|
700
|
-
}
|
|
701
|
-
if (PyObject_HasAttrString(f, "valuesOfContinuousStatesChanged")) {
|
|
702
|
-
PyObject* pyValuesOfContinuousStatesChanged = PyObject_GetAttrString(f, "valuesOfContinuousStatesChanged");
|
|
703
|
-
*valuesOfContinuousStatesChanged = static_cast<bool>(PyObject_IsTrue(pyValuesOfContinuousStatesChanged));
|
|
704
|
-
Py_DECREF(pyValuesOfContinuousStatesChanged);
|
|
705
|
-
}
|
|
706
|
-
if (PyObject_HasAttrString(f, "nextEventTimeDefined")) {
|
|
707
|
-
PyObject* pyNextEventTimeDefined = PyObject_GetAttrString(f, "nextEventTimeDefined");
|
|
708
|
-
*nextEventTimeDefined = static_cast<bool>(PyObject_IsTrue(pyNextEventTimeDefined));
|
|
709
|
-
Py_DECREF(pyNextEventTimeDefined);
|
|
710
|
-
}
|
|
711
|
-
if (nextEventTimeDefined && *nextEventTimeDefined) {
|
|
712
|
-
PyObject* pyNextEventTime = PyObject_GetAttrString(f, "nextEventTime");
|
|
713
|
-
*nextEventTime = PyFloat_AsDouble(pyNextEventTime);
|
|
714
|
-
Py_DECREF(pyNextEventTime);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
Py_DECREF(f);
|
|
718
707
|
clearLogBuffer();
|
|
719
708
|
});
|
|
720
709
|
}
|
|
721
710
|
|
|
722
711
|
void PySlaveInstance::GetFMUstate(fmi3FMUState& state)
|
|
723
712
|
{
|
|
724
|
-
py_safe_run([
|
|
725
|
-
|
|
726
|
-
if (
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
state = reinterpret_cast<fmi3FMUState
|
|
713
|
+
py_safe_run([&]() {
|
|
714
|
+
PyRef newState(PyObject_CallMethod(pInstance_, "_get_fmu_state", nullptr));
|
|
715
|
+
if (!newState) handle_py_exception("[_get_fmu_state]");
|
|
716
|
+
// FMI 3.0: a non-NULL state was returned by an earlier call and must be overwritten.
|
|
717
|
+
PyObject* previous = reinterpret_cast<PyObject*>(state);
|
|
718
|
+
state = reinterpret_cast<fmi3FMUState>(newState.release());
|
|
719
|
+
Py_XDECREF(previous);
|
|
730
720
|
clearLogBuffer();
|
|
731
721
|
});
|
|
732
722
|
}
|
|
733
723
|
|
|
734
724
|
void PySlaveInstance::SetFMUstate(const fmi3FMUState& state)
|
|
735
725
|
{
|
|
736
|
-
py_safe_run([
|
|
726
|
+
py_safe_run([&]() {
|
|
737
727
|
auto pyState = reinterpret_cast<PyObject*>(state);
|
|
738
|
-
|
|
739
|
-
if (
|
|
740
|
-
handle_py_exception("[_set_fmu_state] PyObject_CallMethod", gilState);
|
|
741
|
-
}
|
|
728
|
+
PyRef result(PyObject_CallMethod(pInstance_, "_set_fmu_state", "(O)", pyState));
|
|
729
|
+
if (!result) handle_py_exception("[_set_fmu_state]");
|
|
742
730
|
clearLogBuffer();
|
|
743
731
|
});
|
|
744
732
|
}
|
|
745
733
|
|
|
746
734
|
void PySlaveInstance::FreeFMUstate(fmi3FMUState& state)
|
|
747
735
|
{
|
|
748
|
-
py_safe_run([
|
|
749
|
-
|
|
750
|
-
|
|
736
|
+
py_safe_run([&]() {
|
|
737
|
+
Py_XDECREF(reinterpret_cast<PyObject*>(state));
|
|
738
|
+
state = nullptr;
|
|
751
739
|
});
|
|
752
740
|
}
|
|
753
741
|
|
|
754
742
|
size_t PySlaveInstance::SerializedFMUstateSize(const fmi3FMUState& state)
|
|
755
743
|
{
|
|
756
|
-
|
|
757
|
-
py_safe_run([this, &state, &size](PyGILState_STATE gilState) {
|
|
744
|
+
return py_safe_run([&]() {
|
|
758
745
|
auto pyState = reinterpret_cast<PyObject*>(state);
|
|
759
|
-
|
|
760
|
-
if (
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
size = PyBytes_Size(pyStateBytes);
|
|
764
|
-
Py_DECREF(pyStateBytes);
|
|
746
|
+
PyRef bytes(PyObject_CallMethod(pClass_, "_fmu_state_to_bytes", "(O)", pyState));
|
|
747
|
+
if (!bytes) handle_py_exception("[_fmu_state_to_bytes]");
|
|
748
|
+
const Py_ssize_t size = PyBytes_Size(bytes.get());
|
|
749
|
+
if (size < 0) handle_py_exception("[_fmu_state_to_bytes] must return bytes");
|
|
765
750
|
clearLogBuffer();
|
|
751
|
+
return static_cast<size_t>(size);
|
|
766
752
|
});
|
|
767
|
-
return size;
|
|
768
753
|
}
|
|
769
754
|
|
|
770
755
|
void PySlaveInstance::SerializeFMUstate(const fmi3FMUState& state, fmi3Byte* bytes, size_t size)
|
|
771
756
|
{
|
|
772
|
-
py_safe_run([
|
|
757
|
+
py_safe_run([&]() {
|
|
773
758
|
auto pyState = reinterpret_cast<PyObject*>(state);
|
|
774
|
-
|
|
775
|
-
if (
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
handle_py_exception("[SerializeFMUstate] PyBytes_AsString", gilState);
|
|
759
|
+
PyRef pyBytes(PyObject_CallMethod(pClass_, "_fmu_state_to_bytes", "(O)", pyState));
|
|
760
|
+
if (!pyBytes) handle_py_exception("[_fmu_state_to_bytes]");
|
|
761
|
+
char* data = nullptr;
|
|
762
|
+
Py_ssize_t length = 0;
|
|
763
|
+
if (PyBytes_AsStringAndSize(pyBytes.get(), &data, &length) != 0) {
|
|
764
|
+
handle_py_exception("[_fmu_state_to_bytes] must return bytes");
|
|
781
765
|
}
|
|
782
|
-
|
|
783
|
-
|
|
766
|
+
if (static_cast<size_t>(length) > size) {
|
|
767
|
+
throw std::runtime_error("[SerializeFMUstate] buffer is smaller than fmi3SerializedFMUStateSize");
|
|
784
768
|
}
|
|
785
|
-
|
|
769
|
+
std::copy(data, data + length, bytes);
|
|
786
770
|
clearLogBuffer();
|
|
787
771
|
});
|
|
788
772
|
}
|
|
789
773
|
|
|
790
774
|
void PySlaveInstance::DeSerializeFMUstate(const fmi3Byte bytes[], size_t size, fmi3FMUState& state)
|
|
791
775
|
{
|
|
792
|
-
py_safe_run([
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
handle_py_exception("[DeSerializeFMUstate] PyObject_CallMethod", gilState);
|
|
801
|
-
}
|
|
802
|
-
state = reinterpret_cast<fmi3FMUState*>(pyState);
|
|
803
|
-
Py_DECREF(pyStateBytes);
|
|
776
|
+
py_safe_run([&]() {
|
|
777
|
+
PyRef pyBytes(PyBytes_FromStringAndSize(reinterpret_cast<const char*>(bytes), static_cast<Py_ssize_t>(size)));
|
|
778
|
+
if (!pyBytes) handle_py_exception("[DeSerializeFMUstate] PyBytes_FromStringAndSize");
|
|
779
|
+
PyRef pyState(PyObject_CallMethod(pClass_, "_fmu_state_from_bytes", "(O)", pyBytes.get()));
|
|
780
|
+
if (!pyState) handle_py_exception("[_fmu_state_from_bytes]");
|
|
781
|
+
PyObject* previous = reinterpret_cast<PyObject*>(state);
|
|
782
|
+
state = reinterpret_cast<fmi3FMUState>(pyState.release());
|
|
783
|
+
Py_XDECREF(previous);
|
|
804
784
|
clearLogBuffer();
|
|
805
785
|
});
|
|
806
786
|
}
|
|
807
787
|
|
|
808
|
-
void PySlaveInstance::handle_py_exception(const std::string& what
|
|
788
|
+
void PySlaveInstance::handle_py_exception(const std::string& what) const
|
|
809
789
|
{
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
PyErr_Fetch(&pExcType, &pExcValue, &pExcTraceback);
|
|
790
|
+
// Must be called with the GIL held. The GIL is released by the enclosing GilGuard
|
|
791
|
+
// while the exception unwinds; Python objects stay valid so the instance can still
|
|
792
|
+
// be freed (or reset) safely afterwards.
|
|
793
|
+
std::string details = fetchPythonError();
|
|
794
|
+
clearLogBuffer(); // forward messages logged before the failure
|
|
816
795
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
if (pExcValue != nullptr) {
|
|
821
|
-
PyObject* pRepr = PyObject_Repr(pExcValue);
|
|
822
|
-
PyObject* pyStr = PyUnicode_AsEncodedString(pRepr, "utf-8", nullptr);
|
|
823
|
-
oss << PyBytes_AsString(pyStr);
|
|
824
|
-
Py_DECREF(pyStr);
|
|
825
|
-
Py_DECREF(pRepr);
|
|
826
|
-
} else {
|
|
827
|
-
oss << "unknown error";
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
PyErr_Clear();
|
|
831
|
-
|
|
832
|
-
Py_XDECREF(pExcType);
|
|
833
|
-
Py_XDECREF(pExcValue);
|
|
834
|
-
Py_XDECREF(pExcTraceback);
|
|
835
|
-
|
|
836
|
-
PyGILState_Release(gilState);
|
|
837
|
-
|
|
838
|
-
auto msg = oss.str();
|
|
839
|
-
throw cppfmu::FatalError(msg.c_str());
|
|
840
|
-
}
|
|
796
|
+
std::ostringstream oss;
|
|
797
|
+
oss << "Fatal py exception encountered: " << what << "\n" << details;
|
|
798
|
+
throw cppfmu::FatalError(oss.str().c_str());
|
|
841
799
|
}
|
|
842
800
|
|
|
843
801
|
PySlaveInstance::~PySlaveInstance()
|
|
844
802
|
{
|
|
845
|
-
py_safe_run([this](
|
|
846
|
-
cleanPyObject();
|
|
847
|
-
});
|
|
803
|
+
py_safe_run([this]() { cleanPyObject(); });
|
|
848
804
|
}
|
|
849
805
|
|
|
850
806
|
} // namespace pythonfmu
|
|
851
807
|
|
|
852
808
|
namespace {
|
|
853
|
-
|
|
854
|
-
|
|
809
|
+
// Never destroyed (allocated once, no static destructor). On Linux the library's
|
|
810
|
+
// destructor function (onLibraryUnload, run by _dl_fini) resets the state after the C++
|
|
811
|
+
// static destructors have run at exit: a namespace-scope shared_ptr was destroyed by then,
|
|
812
|
+
// and resetting it again decremented a reference count in freed memory, corrupting the
|
|
813
|
+
// heap (glibc then aborted in a later free, e.g. libgfortran's close_units).
|
|
814
|
+
std::mutex& pyStateMutex()
|
|
815
|
+
{
|
|
816
|
+
static auto* const mutex = new std::mutex();
|
|
817
|
+
return *mutex;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
std::shared_ptr<pythonfmu::PyState>& pyState()
|
|
821
|
+
{
|
|
822
|
+
static auto* const state = new std::shared_ptr<pythonfmu::PyState>();
|
|
823
|
+
return *state;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// Turns an FMI 3 resource path (or a legacy file:// URI) into a filesystem path.
|
|
827
|
+
std::string resourcePathFromLocation(const std::string& location)
|
|
828
|
+
{
|
|
829
|
+
std::string path = location;
|
|
830
|
+
const std::string scheme = "file://";
|
|
831
|
+
if (path.compare(0, scheme.size(), scheme) == 0) {
|
|
832
|
+
path.erase(0, scheme.size());
|
|
833
|
+
// file:///C:/... on Windows -> C:/...
|
|
834
|
+
if (path.size() > 2 && path[0] == '/' && path[2] == ':') path.erase(0, 1);
|
|
835
|
+
|
|
836
|
+
// Percent-decode (URIs only; plain FMI 3 paths may legitimately contain '%')
|
|
837
|
+
std::string decoded;
|
|
838
|
+
decoded.reserve(path.size());
|
|
839
|
+
for (std::size_t i = 0; i < path.size(); i++) {
|
|
840
|
+
if (path[i] == '%' && i + 2 < path.size() && std::isxdigit(static_cast<unsigned char>(path[i + 1])) &&
|
|
841
|
+
std::isxdigit(static_cast<unsigned char>(path[i + 2]))) {
|
|
842
|
+
decoded += static_cast<char>(std::stoi(path.substr(i + 1, 2), nullptr, 16));
|
|
843
|
+
i += 2;
|
|
844
|
+
} else {
|
|
845
|
+
decoded += path[i];
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
path = decoded;
|
|
849
|
+
}
|
|
850
|
+
while (path.size() > 1 && (path.back() == '/' || path.back() == '\\')) path.pop_back();
|
|
851
|
+
return path;
|
|
852
|
+
}
|
|
855
853
|
}
|
|
856
854
|
|
|
857
855
|
std::unique_ptr<cppfmu::SlaveInstance> CppfmuInstantiateSlave(
|
|
@@ -864,29 +862,19 @@ std::unique_ptr<cppfmu::SlaveInstance> CppfmuInstantiateSlave(
|
|
|
864
862
|
cppfmu::FMIBoolean,
|
|
865
863
|
const cppfmu::Logger& logger)
|
|
866
864
|
{
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
auto resources = std::regex_replace(std::string(fmuResourceLocation), std::regex("%20"), " ");
|
|
870
|
-
auto find = resources.find("file://");
|
|
871
|
-
|
|
872
|
-
if (find != std::string::npos) {
|
|
873
|
-
#ifdef _MSC_VER
|
|
874
|
-
resources.replace(find, 8, "");
|
|
875
|
-
#else
|
|
876
|
-
resources.replace(find, 7, "");
|
|
877
|
-
#endif
|
|
865
|
+
if (fmuResourceLocation == nullptr) {
|
|
866
|
+
throw cppfmu::FatalError("No resource path given: PythonFMU3 needs the FMU resources folder");
|
|
878
867
|
}
|
|
868
|
+
auto resources = resourcePathFromLocation(fmuResourceLocation);
|
|
879
869
|
|
|
880
|
-
{
|
|
881
|
-
auto const
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
instanceName, resources, logger, visible, pyState);
|
|
889
|
-
}
|
|
870
|
+
auto const ensurePyStateAlive = [&]() {
|
|
871
|
+
auto const lock = std::lock_guard{pyStateMutex()};
|
|
872
|
+
if (nullptr == pyState()) pyState() = std::make_shared<pythonfmu::PyState>();
|
|
873
|
+
return pyState();
|
|
874
|
+
};
|
|
875
|
+
|
|
876
|
+
return std::make_unique<pythonfmu::PySlaveInstance>(
|
|
877
|
+
instanceName, resources, logger, visible, ensurePyStateAlive());
|
|
890
878
|
}
|
|
891
879
|
|
|
892
880
|
extern "C" {
|
|
@@ -897,7 +885,8 @@ extern "C" {
|
|
|
897
885
|
// Thus, use DllMain on Windows and __attribute__((destructor)) on Linux for signaling to the PyState about de-initialization.
|
|
898
886
|
void finalizePythonInterpreter()
|
|
899
887
|
{
|
|
900
|
-
|
|
888
|
+
auto const lock = std::lock_guard{pyStateMutex()};
|
|
889
|
+
pyState().reset();
|
|
901
890
|
}
|
|
902
891
|
}
|
|
903
892
|
|