1 | // Copyright (C) 2017 Ford Motor Company |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qremoteobjectreplica.h" |
5 | #include "qremoteobjectreplica_p.h" |
6 | |
7 | #include "qremoteobjectnode.h" |
8 | #include "qremoteobjectnode_p.h" |
9 | #include "qremoteobjectdynamicreplica.h" |
10 | #include "qremoteobjectpacket_p.h" |
11 | #include "qremoteobjectpendingcall_p.h" |
12 | #include "qconnectionfactories_p.h" |
13 | #include "qremoteobjectsource_p.h" |
14 | |
15 | #include <QtCore/qcoreapplication.h> |
16 | #include <QtCore/qdatastream.h> |
17 | #include <QtCore/qelapsedtimer.h> |
18 | #include <QtCore/qvariant.h> |
19 | #include <QtCore/qthread.h> |
20 | |
21 | #include <limits> |
22 | |
23 | QT_BEGIN_NAMESPACE |
24 | |
25 | using namespace QRemoteObjectPackets; |
26 | |
27 | QT_WARNING_PUSH |
28 | QT_WARNING_DISABLE_GCC("-Wtautological-compare" ) |
29 | |
30 | #if !defined(Q_OS_WIN) && !defined(Q_OS_INTEGRITY) |
31 | Q_STATIC_ASSERT_X(&QRemoteObjectReplica::staticMetaObject == &QRemoteObjectDynamicReplica::staticMetaObject, |
32 | "m_signalOffset initializer expects QRemoteObjectDynamicReplica to not have a unique staticMetaObject" ); |
33 | #endif |
34 | |
35 | QT_WARNING_POP |
36 | |
37 | // If QRemoteObjectDynamicReplica ever gets its own staticMetaObject, some commented out code will need to be |
38 | // used. It was changed to avoid a Coverity complaint. We use the above static assert to detect if this changes |
39 | // in the future. See FIX #1, #2, #3 in this file. |
40 | |
41 | QRemoteObjectReplicaImplementation::QRemoteObjectReplicaImplementation(const QString &name, const QMetaObject *meta, QRemoteObjectNode *_node) |
42 | : QObject(nullptr), m_objectName(name), m_metaObject(meta), m_numSignals(0), m_methodOffset(0) |
43 | // Uncomment the following two lines if QRemoteObjectDynamicReplica gets a unique staticMetaObject (FIX #1, #2) |
44 | //, m_signalOffset(meta ? QRemoteObjectReplica::staticMetaObject.methodCount() : QRemoteObjectDynamicReplica::staticMetaObject.methodCount()) |
45 | //, m_propertyOffset(meta ? QRemoteObjectReplica::staticMetaObject.propertyCount() : QRemoteObjectDynamicReplica::staticMetaObject.propertyCount()) |
46 | , m_signalOffset(QRemoteObjectReplica::staticMetaObject.methodCount()) |
47 | , m_propertyOffset(QRemoteObjectReplica::staticMetaObject.propertyCount()) |
48 | , m_node(_node) |
49 | , m_objectSignature(QtPrivate::qtro_classinfo_signature(metaObject: m_metaObject)) |
50 | , m_state(meta ? QRemoteObjectReplica::Default : QRemoteObjectReplica::Uninitialized) |
51 | { |
52 | } |
53 | |
54 | QRemoteObjectReplicaImplementation::~QRemoteObjectReplicaImplementation() |
55 | { |
56 | } |
57 | |
58 | QConnectedReplicaImplementation::QConnectedReplicaImplementation(const QString &name, const QMetaObject *meta, QRemoteObjectNode *node) |
59 | : QRemoteObjectReplicaImplementation(name, meta, node), connectionToSource(nullptr) |
60 | { |
61 | m_heartbeatTimer.setTimerType(Qt::CoarseTimer); |
62 | m_heartbeatTimer.setSingleShot(true); |
63 | m_heartbeatTimer.setInterval(node->heartbeatInterval()); |
64 | |
65 | connect(sender: node, signal: &QRemoteObjectNode::heartbeatIntervalChanged, context: this, slot: [this](int interval) { |
66 | m_heartbeatTimer.stop(); |
67 | m_heartbeatTimer.setInterval(interval); |
68 | if (interval) |
69 | m_heartbeatTimer.start(); |
70 | }); |
71 | connect(sender: &m_heartbeatTimer, signal: &QTimer::timeout, context: this, slot: [this] { |
72 | // TODO: Revisit if a baseclass method can be used to avoid specialized cast |
73 | // conditional logic. |
74 | |
75 | if (m_pendingCalls.contains(key: 0)) { |
76 | m_pendingCalls.take(key: 0); |
77 | // The source didn't respond in time, disconnect the connection |
78 | if (connectionToSource) { |
79 | auto clientIo = qobject_cast<QtROClientIoDevice *>(object: connectionToSource); |
80 | if (clientIo) |
81 | clientIo->disconnectFromServer(); |
82 | else |
83 | connectionToSource->close(); |
84 | } |
85 | } else { |
86 | if (connectionToSource.isNull()) { |
87 | qCDebug(QT_REMOTEOBJECT) << "Ignoring heartbeat as there is no source connected." ; |
88 | return; |
89 | } |
90 | connectionToSource->d_func()->m_codec->serializePingPacket(name: m_objectName); |
91 | if (sendCommandWithReply(serialId: 0).d->serialId == -1) { |
92 | m_heartbeatTimer.stop(); |
93 | auto clientIo = qobject_cast<QtROClientIoDevice *>(object: connectionToSource); |
94 | if (clientIo) |
95 | clientIo->disconnectFromServer(); |
96 | else |
97 | connectionToSource->close(); |
98 | } |
99 | } |
100 | }); |
101 | |
102 | if (!meta) |
103 | return; |
104 | |
105 | auto offsetMeta = m_metaObject; |
106 | QtRemoteObjects::getTypeNameAndMetaobjectFromClassInfo(meta&: offsetMeta); |
107 | for (int index = offsetMeta->propertyOffset(); index < offsetMeta->propertyCount(); ++index) { |
108 | const QMetaProperty property = offsetMeta->property(index); |
109 | if (property.metaType().flags().testFlag(flag: QMetaType::PointerToQObject)) |
110 | m_childIndices << index - offsetMeta->propertyOffset(); |
111 | } |
112 | } |
113 | |
114 | QConnectedReplicaImplementation::~QConnectedReplicaImplementation() |
115 | { |
116 | if (!connectionToSource.isNull()) { |
117 | qCDebug(QT_REMOTEOBJECT) << "Replica deleted: sending RemoveObject to RemoteObjectSource" << m_objectName; |
118 | connectionToSource->d_func()->m_codec->serializeRemoveObjectPacket(name: m_objectName); |
119 | sendCommand(); |
120 | } |
121 | for (auto prop : m_propertyStorage) { |
122 | if (prop.canConvert<QObject*>()) { |
123 | if (auto o = prop.value<QObject*>()) |
124 | o->deleteLater(); |
125 | } |
126 | } |
127 | } |
128 | |
129 | bool QRemoteObjectReplicaImplementation::needsDynamicInitialization() const |
130 | { |
131 | return m_metaObject == nullptr; |
132 | } |
133 | |
134 | void QRemoteObjectReplicaImplementation::setState(QRemoteObjectReplica::State state) |
135 | { |
136 | if (m_state.loadAcquire() != QRemoteObjectReplica::Suspect && m_state.loadAcquire() >= state) |
137 | return; |
138 | |
139 | int oldState = m_state.loadAcquire(); |
140 | m_state.storeRelease(newValue: state); |
141 | |
142 | // We should emit initialized before emitting any changed signals in case connections are made in a |
143 | // Slot responding to initialized/validChanged. |
144 | if (m_state.loadAcquire() == QRemoteObjectReplica::Valid) { |
145 | // we're initialized now, emit signal |
146 | emitInitialized(); |
147 | } |
148 | |
149 | const static int stateChangedIndex = QRemoteObjectReplica::staticMetaObject.indexOfMethod(method: "stateChanged(State,State)" ); |
150 | Q_ASSERT(stateChangedIndex != -1); |
151 | void *args[] = {nullptr, &state, &oldState}; |
152 | QMetaObject::activate(sender: this, metaObject(), local_signal_index: stateChangedIndex, argv: args); |
153 | } |
154 | |
155 | void QRemoteObjectReplicaImplementation::emitNotified() |
156 | { |
157 | const static int notifiedIndex = QRemoteObjectReplica::staticMetaObject.indexOfMethod(method: "notified()" ); |
158 | Q_ASSERT(notifiedIndex != -1); |
159 | void *args[] = {nullptr}; |
160 | QMetaObject::activate(sender: this, metaObject(), local_signal_index: notifiedIndex, argv: args); |
161 | } |
162 | |
163 | bool QConnectedReplicaImplementation::sendCommand() |
164 | { |
165 | Q_ASSERT(connectionToSource); |
166 | if (!connectionToSource->isOpen()) |
167 | return false; |
168 | |
169 | connectionToSource->d_func()->m_codec->send(connection: connectionToSource); |
170 | if (m_heartbeatTimer.interval()) |
171 | m_heartbeatTimer.start(); |
172 | return true; |
173 | } |
174 | |
175 | QList<int> QConnectedReplicaImplementation::childIndices() const |
176 | { |
177 | return m_childIndices; |
178 | } |
179 | |
180 | void QConnectedReplicaImplementation::initialize(QVariantList &&values) |
181 | { |
182 | qCDebug(QT_REMOTEOBJECT) << "initialize()" << m_propertyStorage.size(); |
183 | const int nParam = int(values.size()); |
184 | QVarLengthArray<int> changedProperties(nParam); |
185 | const int offset = m_propertyOffset; |
186 | for (int i = 0; i < nParam; ++i) { |
187 | qCDebug(QT_REMOTEOBJECT) << " in loop" << i << m_propertyStorage.size(); |
188 | changedProperties[i] = -1; |
189 | if (m_propertyStorage[i] != values.at(i)) { |
190 | const QMetaProperty property = m_metaObject->property(index: i+offset); |
191 | m_propertyStorage[i] = QRemoteObjectPackets::decodeVariant(value: std::move(values[i]), metaType: property.metaType()); |
192 | changedProperties[i] = i; |
193 | } |
194 | qCDebug(QT_REMOTEOBJECT) << "SETPROPERTY" << i << m_metaObject->property(index: i+offset).name() |
195 | << m_propertyStorage[i].typeName() |
196 | << m_propertyStorage[i].toString(); |
197 | } |
198 | |
199 | Q_ASSERT(m_state.loadAcquire() < QRemoteObjectReplica::Valid || m_state.loadAcquire() == QRemoteObjectReplica::Suspect); |
200 | setState(QRemoteObjectReplica::Valid); |
201 | |
202 | void *args[] = {nullptr, nullptr}; |
203 | for (int i = 0; i < nParam; ++i) { |
204 | if (changedProperties[i] < 0) |
205 | continue; |
206 | const int notifyIndex = m_metaObject->property(index: changedProperties[i]+offset).notifySignalIndex(); |
207 | if (notifyIndex < 0) |
208 | continue; |
209 | qCDebug(QT_REMOTEOBJECT) << " Before activate" << notifyIndex << m_metaObject->property(index: notifyIndex).name(); |
210 | args[1] = m_propertyStorage[i].data(); |
211 | QMetaObject::activate(sender: this, metaObject(), local_signal_index: notifyIndex, argv: args); |
212 | } |
213 | emitNotified(); |
214 | |
215 | qCDebug(QT_REMOTEOBJECT) << "isSet = true for" << m_objectName; |
216 | if (node()->heartbeatInterval()) |
217 | m_heartbeatTimer.start(); |
218 | } |
219 | |
220 | void QRemoteObjectReplicaImplementation::emitInitialized() |
221 | { |
222 | const static int initializedIndex = QRemoteObjectReplica::staticMetaObject.indexOfMethod(method: "initialized()" ); |
223 | Q_ASSERT(initializedIndex != -1); |
224 | void *noArgs[] = {nullptr}; |
225 | QMetaObject::activate(sender: this, metaObject(), local_signal_index: initializedIndex, argv: noArgs); |
226 | } |
227 | |
228 | /*! |
229 | \internal |
230 | */ |
231 | void QRemoteObjectReplica::persistProperties(const QString &repName, const QByteArray &repSig, const QVariantList &props) const |
232 | { |
233 | if (!node()) { |
234 | qWarning(msg: "Tried calling persistProperties on a replica (%s) that hasn't been initialized with a node" , qPrintable(repName)); |
235 | return; |
236 | } |
237 | node()->persistProperties(repName, repSig, props); |
238 | } |
239 | |
240 | /*! |
241 | \internal |
242 | */ |
243 | QVariantList QRemoteObjectReplica::retrieveProperties(const QString &repName, const QByteArray &repSig) const |
244 | { |
245 | if (!node()) { |
246 | qWarning(msg: "Tried calling retrieveProperties on a replica (%s) that hasn't been initialized with a node" , qPrintable(repName)); |
247 | return QVariantList(); |
248 | } |
249 | return node()->retrieveProperties(repName, repSig); |
250 | } |
251 | |
252 | void QRemoteObjectReplicaImplementation::setDynamicMetaObject(const QMetaObject *meta) |
253 | { |
254 | Q_ASSERT(!m_metaObject); |
255 | |
256 | m_metaObject = meta; |
257 | } |
258 | |
259 | void QConnectedReplicaImplementation::setDynamicMetaObject(const QMetaObject *meta) |
260 | { |
261 | QRemoteObjectReplicaImplementation::setDynamicMetaObject(meta); |
262 | |
263 | for (int index = m_metaObject->propertyOffset(); index < m_metaObject->propertyCount(); ++index) { |
264 | const QMetaProperty property = m_metaObject->property(index); |
265 | if (property.metaType().flags().testFlag(flag: QMetaType::PointerToQObject)) |
266 | m_childIndices << index - m_metaObject->propertyOffset(); |
267 | } |
268 | } |
269 | |
270 | void QRemoteObjectReplicaImplementation::setDynamicProperties(QVariantList &&values) |
271 | { |
272 | const int offset = m_propertyOffset; |
273 | int propertyIndex = -1; |
274 | for (auto &prop : values) { |
275 | propertyIndex++; |
276 | const QMetaProperty property = m_metaObject->property(index: propertyIndex+offset); |
277 | prop = QRemoteObjectPackets::decodeVariant(value: std::move(prop), metaType: property.metaType()); |
278 | } |
279 | //rely on order of properties; |
280 | setProperties(std::move(values)); |
281 | } |
282 | |
283 | void QConnectedReplicaImplementation::setDynamicProperties(QVariantList &&values) |
284 | { |
285 | QRemoteObjectReplicaImplementation::setDynamicProperties(std::move(values)); |
286 | for (QRemoteObjectReplica *obj : std::exchange(obj&: m_parentsNeedingConnect, new_val: {})) |
287 | configurePrivate(obj); |
288 | |
289 | Q_ASSERT(m_state.loadAcquire() < QRemoteObjectReplica::Valid); |
290 | setState(QRemoteObjectReplica::Valid); |
291 | |
292 | void *args[] = {nullptr, nullptr}; |
293 | for (int index = m_metaObject->propertyOffset(); index < m_metaObject->propertyCount(); ++index) { |
294 | const QMetaProperty mp = m_metaObject->property(index); |
295 | if (mp.hasNotifySignal()) { |
296 | qCDebug(QT_REMOTEOBJECT) << " Before activate" << index << m_metaObject->property(index).name(); |
297 | args[1] = this->m_propertyStorage[index-m_propertyOffset].data(); |
298 | QMetaObject::activate(sender: this, metaObject(), local_signal_index: mp.notifySignalIndex(), argv: args); |
299 | } |
300 | } |
301 | emitNotified(); |
302 | |
303 | qCDebug(QT_REMOTEOBJECT) << "isSet = true for" << m_objectName; |
304 | } |
305 | |
306 | bool QConnectedReplicaImplementation::isInitialized() const |
307 | { |
308 | return m_state.loadAcquire() > QRemoteObjectReplica::Default && m_state.loadAcquire() != QRemoteObjectReplica::SignatureMismatch; |
309 | } |
310 | |
311 | bool QConnectedReplicaImplementation::waitForSource(int timeout) |
312 | { |
313 | switch (state()) { |
314 | case QRemoteObjectReplica::State::Valid: |
315 | return true; |
316 | case QRemoteObjectReplica::State::SignatureMismatch: |
317 | return false; |
318 | default: |
319 | break; |
320 | } |
321 | |
322 | const static int stateChangedIndex = QRemoteObjectReplica::staticMetaObject.indexOfMethod(method: "stateChanged(State,State)" ); |
323 | Q_ASSERT(stateChangedIndex != -1); |
324 | |
325 | QEventLoop loop; |
326 | QMetaObject::connect(sender: this, signal_index: stateChangedIndex, |
327 | receiver: &loop, method_index: QEventLoop::staticMetaObject.indexOfMethod(method: "quit()" ), |
328 | type: Qt::DirectConnection, types: nullptr); |
329 | |
330 | QTimer t; // NB: Related to QTBUG-94570 - don't use QTimer::singleShot here. |
331 | if (timeout >= 0) { |
332 | t.setSingleShot(true); |
333 | connect(sender: &t, signal: &QTimer::timeout, context: &loop, slot: &QEventLoop::quit); |
334 | t.start(msec: timeout); |
335 | } |
336 | |
337 | // enter the event loop and wait for a reply |
338 | loop.exec(flags: QEventLoop::ExcludeUserInputEvents | QEventLoop::WaitForMoreEvents); |
339 | |
340 | return state() == QRemoteObjectReplica::State::Valid; |
341 | } |
342 | |
343 | void QConnectedReplicaImplementation::_q_send(QMetaObject::Call call, int index, const QVariantList &args) |
344 | { |
345 | static const bool debugArgs = qEnvironmentVariableIsSet(varName: "QT_REMOTEOBJECT_DEBUG_ARGUMENTS" ); |
346 | |
347 | Q_ASSERT(call == QMetaObject::InvokeMetaMethod || call == QMetaObject::WriteProperty); |
348 | if (connectionToSource.isNull()) { |
349 | qCWarning(QT_REMOTEOBJECT) << "connectionToSource is null" ; |
350 | return; |
351 | } |
352 | |
353 | if (call == QMetaObject::InvokeMetaMethod) { |
354 | if (debugArgs) { |
355 | qCDebug(QT_REMOTEOBJECT) << "Send" << call << this->m_metaObject->method(index).name() << index << args << connectionToSource; |
356 | } else { |
357 | qCDebug(QT_REMOTEOBJECT) << "Send" << call << this->m_metaObject->method(index).name() << index << connectionToSource; |
358 | } |
359 | if (index < m_methodOffset) //index - m_methodOffset < 0 is invalid, and can't be resolved on the Source side |
360 | qCWarning(QT_REMOTEOBJECT) << "Skipping invalid method invocation. Index not found:" << index << "( offset =" << m_methodOffset << ") object:" << m_objectName << this->m_metaObject->method(index).name(); |
361 | else { |
362 | connectionToSource->d_func()->m_codec->serializeInvokePacket(name: m_objectName, call, index: index - m_methodOffset, args); |
363 | sendCommand(); |
364 | } |
365 | } else { |
366 | qCDebug(QT_REMOTEOBJECT) << "Send" << call << this->m_metaObject->property(index).name() << index << args << connectionToSource; |
367 | if (index < m_propertyOffset) //index - m_propertyOffset < 0 is invalid, and can't be resolved on the Source side |
368 | qCWarning(QT_REMOTEOBJECT) << "Skipping invalid property invocation. Index not found:" << index << "( offset =" << m_propertyOffset << ") object:" << m_objectName << this->m_metaObject->property(index).name(); |
369 | else { |
370 | connectionToSource->d_func()->m_codec->serializeInvokePacket(name: m_objectName, call, index: index - m_propertyOffset, args); |
371 | sendCommand(); |
372 | } |
373 | } |
374 | } |
375 | |
376 | QRemoteObjectPendingCall QConnectedReplicaImplementation::_q_sendWithReply(QMetaObject::Call call, int index, const QVariantList &args) |
377 | { |
378 | Q_ASSERT(call == QMetaObject::InvokeMetaMethod); |
379 | if (connectionToSource.isNull()) { |
380 | qCWarning(QT_REMOTEOBJECT) << "connectionToSource is null" ; |
381 | return QRemoteObjectPendingCall(); |
382 | } |
383 | |
384 | qCDebug(QT_REMOTEOBJECT) << "Send" << call << this->m_metaObject->method(index).name() << index << args << connectionToSource; |
385 | int serialId = (m_curSerialId == std::numeric_limits<int>::max() ? 1 : m_curSerialId++); |
386 | connectionToSource->d_func()->m_codec->serializeInvokePacket(name: m_objectName, call, index: index - m_methodOffset, args, serialId); |
387 | return sendCommandWithReply(serialId); |
388 | } |
389 | |
390 | QRemoteObjectPendingCall QConnectedReplicaImplementation::sendCommandWithReply(int serialId) |
391 | { |
392 | bool success = sendCommand(); |
393 | if (!success) { |
394 | return QRemoteObjectPendingCall(); // invalid |
395 | } |
396 | |
397 | qCDebug(QT_REMOTEOBJECT) << "Sent InvokePacket with serial id:" << serialId; |
398 | QRemoteObjectPendingCall pendingCall(new QRemoteObjectPendingCallData(serialId, this)); |
399 | Q_ASSERT(!m_pendingCalls.contains(serialId)); |
400 | m_pendingCalls[serialId] = pendingCall; |
401 | return pendingCall; |
402 | } |
403 | |
404 | void QConnectedReplicaImplementation::notifyAboutReply(int ackedSerialId, const QVariant &value) |
405 | { |
406 | QRemoteObjectPendingCall call = m_pendingCalls.take(key: ackedSerialId); |
407 | if (ackedSerialId == 0) { |
408 | m_heartbeatTimer.stop(); |
409 | if (m_heartbeatTimer.interval()) |
410 | m_heartbeatTimer.start(); |
411 | return; |
412 | } |
413 | |
414 | QMutexLocker mutex(&call.d->mutex); |
415 | |
416 | // clear error flag |
417 | call.d->error = QRemoteObjectPendingCall::NoError; |
418 | call.d->returnValue = value; |
419 | |
420 | // notify watchers if needed |
421 | if (call.d->watcherHelper) |
422 | call.d->watcherHelper->emitSignals(); |
423 | } |
424 | |
425 | bool QConnectedReplicaImplementation::waitForFinished(const QRemoteObjectPendingCall& call, int timeout) |
426 | { |
427 | if (!call.d->watcherHelper) |
428 | call.d->watcherHelper.reset(other: new QRemoteObjectPendingCallWatcherHelper); |
429 | |
430 | call.d->mutex.unlock(); |
431 | |
432 | QEventLoop loop; |
433 | loop.connect(sender: call.d->watcherHelper.data(), signal: &QRemoteObjectPendingCallWatcherHelper::finished, |
434 | context: &loop, slot: &QEventLoop::quit); |
435 | |
436 | QTimer t; // NB: Related to QTBUG-94570 - don't use QTimer::singleShot here. |
437 | if (timeout >= 0) { |
438 | t.setSingleShot(true); |
439 | connect(sender: &t, signal: &QTimer::timeout, context: &loop, slot: &QEventLoop::quit); |
440 | t.start(msec: timeout); |
441 | } |
442 | |
443 | // enter the event loop and wait for a reply |
444 | loop.exec(flags: QEventLoop::ExcludeUserInputEvents | QEventLoop::WaitForMoreEvents); |
445 | |
446 | call.d->mutex.lock(); |
447 | |
448 | return call.d->error != QRemoteObjectPendingCall::InvalidMessage; |
449 | } |
450 | |
451 | const QVariant QConnectedReplicaImplementation::getProperty(int i) const |
452 | { |
453 | Q_ASSERT_X(i >= 0 && i < m_propertyStorage.size(), __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 < %2" )).arg(i).arg(m_propertyStorage.size()))); |
454 | return m_propertyStorage[i]; |
455 | } |
456 | |
457 | void QConnectedReplicaImplementation::setProperties(QVariantList &&properties) |
458 | { |
459 | Q_ASSERT(m_propertyStorage.isEmpty()); |
460 | m_propertyStorage.reserve(asize: properties.size()); |
461 | m_propertyStorage = std::move(properties); |
462 | } |
463 | |
464 | void QConnectedReplicaImplementation::setProperty(int i, const QVariant &prop) |
465 | { |
466 | m_propertyStorage[i] = prop; |
467 | } |
468 | |
469 | void QConnectedReplicaImplementation::setConnection(QtROIoDeviceBase *conn) |
470 | { |
471 | if (connectionToSource.isNull()) { |
472 | connectionToSource = conn; |
473 | qCDebug(QT_REMOTEOBJECT) << "setConnection started" << conn << m_objectName; |
474 | } |
475 | requestRemoteObjectSource(); |
476 | } |
477 | |
478 | void QConnectedReplicaImplementation::setDisconnected() |
479 | { |
480 | Q_ASSERT(connectionToSource); |
481 | connectionToSource.clear(); |
482 | setState(QRemoteObjectReplica::State::Suspect); |
483 | for (const int index : childIndices()) { |
484 | auto pointerToQObject = qvariant_cast<QObject *>(v: getProperty(i: index)); |
485 | auto child = qobject_cast<QRemoteObjectReplica *>(object: pointerToQObject); |
486 | if (child) { |
487 | QConnectedReplicaImplementation *childReplicaImplData |
488 | = static_cast<QConnectedReplicaImplementation *>(child->d_impl.data()); |
489 | if (childReplicaImplData && !childReplicaImplData->connectionToSource.isNull()) |
490 | childReplicaImplData->setDisconnected(); |
491 | } |
492 | } |
493 | } |
494 | |
495 | void QConnectedReplicaImplementation::requestRemoteObjectSource() |
496 | { |
497 | Q_ASSERT(connectionToSource); |
498 | connectionToSource->d_func()->m_codec->serializeAddObjectPacket(name: m_objectName, isDynamic: needsDynamicInitialization()); |
499 | sendCommand(); |
500 | } |
501 | |
502 | void QRemoteObjectReplicaImplementation::configurePrivate(QRemoteObjectReplica *rep) |
503 | { |
504 | qCDebug(QT_REMOTEOBJECT) << "configurePrivate starting for" << this->m_objectName; |
505 | //We need to connect the Replicant only signals too |
506 | // Uncomment the following two lines if QRemoteObjectDynamicReplica gets a unique staticMetaObject (FIX #3) |
507 | //const QMetaObject *m = rep->inherits("QRemoteObjectDynamicReplica") ? |
508 | // &QRemoteObjectDynamicReplica::staticMetaObject : &QRemoteObjectReplica::staticMetaObject; |
509 | const QMetaObject *m = &QRemoteObjectReplica::staticMetaObject; |
510 | for (int i = m->methodOffset(); i < m->methodCount(); ++i) |
511 | { |
512 | const QMetaMethod mm = m->method(index: i); |
513 | if (mm.methodType() == QMetaMethod::Signal) { |
514 | const bool res = QMetaObject::connect(sender: this, signal_index: i, receiver: rep, method_index: i, type: Qt::DirectConnection, types: nullptr); |
515 | qCDebug(QT_REMOTEOBJECT) << " Rep connect" <<i<<res<<mm.name(); |
516 | Q_UNUSED(res) |
517 | } |
518 | } |
519 | if (m_methodOffset == 0) //We haven't initialized the offsets yet |
520 | { |
521 | const int index = m_metaObject->indexOfClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE); |
522 | const QMetaObject *metaObject = m_metaObject; |
523 | if (index != -1) { //We have an object created from repc or at least with QCLASSINFO defined |
524 | while (true) { |
525 | Q_ASSERT(metaObject->superClass()); //This recurses to QObject, which doesn't have QCLASSINFO_REMOTEOBJECT_TYPE |
526 | if (index != metaObject->superClass()->indexOfClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE)) //At the point we don't find the same QCLASSINFO_REMOTEOBJECT_TYPE, |
527 | //we have the metaobject we should work from |
528 | break; |
529 | metaObject = metaObject->superClass(); |
530 | } |
531 | } |
532 | |
533 | for (int i = m_signalOffset; i < metaObject->methodCount(); ++i) { |
534 | const QMetaMethod mm = metaObject->method(index: i); |
535 | if (mm.methodType() == QMetaMethod::Signal) { |
536 | ++m_numSignals; |
537 | const bool res = QMetaObject::connect(sender: this, signal_index: i, receiver: rep, method_index: i, type: Qt::DirectConnection, types: nullptr); |
538 | qCDebug(QT_REMOTEOBJECT) << " Connect" <<i<<res<<mm.name(); |
539 | Q_UNUSED(res) |
540 | } |
541 | } |
542 | m_methodOffset = m_signalOffset + m_numSignals; |
543 | qCDebug(QT_REMOTEOBJECT) << QStringLiteral("configurePrivate finished, signalOffset = %1, methodOffset = %2, #Signals = %3" ).arg(a: m_signalOffset).arg(a: m_methodOffset).arg(a: m_numSignals); |
544 | } else { //We have initialized offsets, this is an additional Replica attaching |
545 | for (int i = m_signalOffset; i < m_methodOffset; ++i) { |
546 | const bool res = QMetaObject::connect(sender: this, signal_index: i, receiver: rep, method_index: i, type: Qt::DirectConnection, types: nullptr); |
547 | qCDebug(QT_REMOTEOBJECT) << " Connect" <<i<<res<<m_metaObject->method(index: i).name(); |
548 | Q_UNUSED(res) |
549 | } |
550 | if (isInitialized()) { |
551 | qCDebug(QT_REMOTEOBJECT) << QStringLiteral("ReplicaImplementation initialized, emitting signal on replica" ); |
552 | emit rep->initialized(); //Emit from new replica only |
553 | } |
554 | if (state() != QRemoteObjectReplica::Valid) { |
555 | qCDebug(QT_REMOTEOBJECT) << QStringLiteral("ReplicaImplementation not currently valid, emitting signal on replica" ); |
556 | emit rep->stateChanged(state: state(), oldState: m_metaObject ? QRemoteObjectReplica::Default : QRemoteObjectReplica::Uninitialized); |
557 | } |
558 | |
559 | qCDebug(QT_REMOTEOBJECT) << QStringLiteral("configurePrivate finished, added replica to existing ReplicaImplementation" ); |
560 | } |
561 | } |
562 | |
563 | void QConnectedReplicaImplementation::configurePrivate(QRemoteObjectReplica *rep) |
564 | { |
565 | if (m_metaObject) { |
566 | // see QRemoteObjectReplicaImplementation::configurePrivate |
567 | const bool firstReplicaInstance = (m_methodOffset == 0); |
568 | |
569 | QRemoteObjectReplicaImplementation::configurePrivate(rep); |
570 | |
571 | // ensure that notify signals are emitted for the new replica, when |
572 | // we are initializing an nth replica of the same type |
573 | if (!firstReplicaInstance) { |
574 | const int offset = m_propertyOffset; |
575 | const int nParam = int(m_propertyStorage.size()); |
576 | void *args[] = {nullptr, nullptr}; |
577 | for (int i = 0; i < nParam; ++i) { |
578 | const int notifyIndex = m_metaObject->property(index: i+offset).notifySignalIndex(); |
579 | if (notifyIndex < 0) |
580 | continue; |
581 | qCDebug(QT_REMOTEOBJECT) << " Before activate" << notifyIndex << m_metaObject->property(index: i+offset).name(); |
582 | args[1] = m_propertyStorage[i].data(); |
583 | // NOTE: this over-emits (assumes all values have changed) |
584 | QMetaObject::activate(sender: rep, rep->metaObject(), local_signal_index: notifyIndex - m_signalOffset, argv: args); |
585 | } |
586 | } |
587 | } else |
588 | m_parentsNeedingConnect.append(t: rep); |
589 | } |
590 | |
591 | /*! |
592 | \class QRemoteObjectReplica |
593 | \inmodule QtRemoteObjects |
594 | \brief A class interacting with (but not implementing) a Qt API on the Remote Object network. |
595 | |
596 | A Remote Object Replica is a QObject proxy for another QObject (called the |
597 | \l {Source} object). Once initialized, a replica can be considered a |
598 | "latent copy" of the \l {Source} object. That is, every change to a |
599 | Q_PROPERTY on the \l {Source}, or signal emitted by the \l {Source} will be |
600 | updated/emitted by all \l {Replica} objects. Latency |
601 | is introduced by process scheduling by any OSes involved and network |
602 | communication latency. As long as the replica has been initialized and the |
603 | communication is not disrupted, receipt and order of changes is guaranteed. |
604 | |
605 | The \l {isInitialized} and \l {state} properties (and corresponding |
606 | \l {initialized()}/\l {stateChanged()} signals) allow the state of a |
607 | \l {Replica} to be determined. |
608 | |
609 | While Qt Remote Objects (QtRO) handles the initialization and |
610 | synchronization of \l {Replica} objects, there are numerous steps happening |
611 | behind the scenes which can fail and that aren't encountered in single |
612 | process Qt applications. See \l {Troubleshooting} for advice on how to |
613 | handle such issues when using a remote objects network. |
614 | */ |
615 | |
616 | /*! |
617 | \enum QRemoteObjectReplica::State |
618 | |
619 | This enum type specifies the various state codes associated with QRemoteObjectReplica states: |
620 | |
621 | \value Uninitialized Initial value of DynamicReplica, where nothing is |
622 | known about the replica before connection to source. |
623 | |
624 | \value Default Initial value of static replica, where any defaults set in |
625 | the .rep file are available so it can be used if necessary. |
626 | |
627 | \value Valid Indicates the replica is connected, has good property values |
628 | and can be interacted with. |
629 | |
630 | \value Suspect Error state that occurs if the connection to the source is |
631 | lost after it is initialized. |
632 | |
633 | \value SignatureMismatch Error state that occurs if a connection to the |
634 | source is made, but the source and replica are not derived from the same |
635 | .rep (only possible for static Replicas). |
636 | */ |
637 | |
638 | /*! |
639 | \fn void QRemoteObjectReplica::stateChanged(State state, State oldState) |
640 | |
641 | This signal is emitted whenever a replica's state toggles between |
642 | \l QRemoteObjectReplica::State. |
643 | |
644 | The change in state is represented with \a state and \a oldState. |
645 | |
646 | \sa state(), initialized() |
647 | */ |
648 | |
649 | /*! |
650 | \fn void QRemoteObjectReplica::initialized() |
651 | |
652 | This signal is emitted once the replica is initialized. An intialized replica |
653 | has all property values set, but has not yet emitted any property change |
654 | notifications. |
655 | |
656 | \sa isInitialized(), stateChanged() |
657 | */ |
658 | |
659 | /*! |
660 | \fn void QRemoteObjectReplica::notified() |
661 | |
662 | This signal is emitted once the replica is initialized and all property change |
663 | notifications have been emitted. |
664 | |
665 | It is sometimes useful to respond to property changes as events. |
666 | For example, you might want to display a user notification when a certain |
667 | property change occurs. However, this user notification would then also be |
668 | triggered when a replica first became \c QRemoteObjectReplica::Valid, as |
669 | all property change signals are emitted at that time. This isn't always desirable, |
670 | and \c notified allows the developer to distinguish between these two cases. |
671 | */ |
672 | |
673 | /*! |
674 | \internal |
675 | \enum QRemoteObjectReplica::ConstructorType |
676 | */ |
677 | |
678 | /*! |
679 | \property QRemoteObjectReplica::state |
680 | \brief Returns the replica state. |
681 | |
682 | This property holds the replica \l QRemoteObjectReplica::State. |
683 | */ |
684 | |
685 | /*! |
686 | \property QRemoteObjectReplica::node |
687 | \brief A pointer to the node this object was acquired from. |
688 | */ |
689 | |
690 | /*! |
691 | \internal This (protected) constructor for QRemoteObjectReplica can be used to create |
692 | replica objects from QML. |
693 | */ |
694 | QRemoteObjectReplica::QRemoteObjectReplica(ConstructorType t) |
695 | : QObject(nullptr) |
696 | , d_impl(t == DefaultConstructor ? new QStubReplicaImplementation : nullptr) |
697 | { |
698 | qRegisterMetaType<State>(typeName: "State" ); |
699 | } |
700 | |
701 | QRemoteObjectReplica::QRemoteObjectReplica(QObjectPrivate &dptr, QObject *parent) |
702 | : QObject(dptr, parent) |
703 | , d_impl(new QStubReplicaImplementation) |
704 | { |
705 | } |
706 | |
707 | /*! |
708 | \internal |
709 | */ |
710 | QRemoteObjectReplica::~QRemoteObjectReplica() |
711 | { |
712 | } |
713 | |
714 | /*! |
715 | \internal |
716 | */ |
717 | void QRemoteObjectReplica::send(QMetaObject::Call call, int index, const QVariantList &args) |
718 | { |
719 | Q_ASSERT(index != -1); |
720 | |
721 | d_impl->_q_send(call, index, args); |
722 | } |
723 | |
724 | /*! |
725 | \internal |
726 | */ |
727 | QRemoteObjectPendingCall QRemoteObjectReplica::sendWithReply(QMetaObject::Call call, int index, const QVariantList &args) |
728 | { |
729 | return d_impl->_q_sendWithReply(call, index, args); |
730 | } |
731 | |
732 | /*! |
733 | \internal |
734 | */ |
735 | const QVariant QRemoteObjectReplica::propAsVariant(int i) const |
736 | { |
737 | return d_impl->getProperty(i); |
738 | } |
739 | |
740 | /*! |
741 | \internal |
742 | */ |
743 | void QRemoteObjectReplica::initializeNode(QRemoteObjectNode *node, const QString &name) |
744 | { |
745 | node->initializeReplica(instance: this, name); |
746 | } |
747 | |
748 | /*! |
749 | \internal |
750 | */ |
751 | void QRemoteObjectReplica::setProperties(QVariantList &&properties) |
752 | { |
753 | d_impl->setProperties(std::move(properties)); |
754 | } |
755 | |
756 | /*! |
757 | \internal |
758 | */ |
759 | void QRemoteObjectReplica::setChild(int i, const QVariant &value) |
760 | { |
761 | d_impl->setProperty(i, value); |
762 | } |
763 | |
764 | /*! |
765 | Returns \c true if this replica has been initialized with data from the |
766 | \l {Source} object. Returns \c false otherwise. |
767 | |
768 | \sa state() |
769 | */ |
770 | bool QRemoteObjectReplica::isInitialized() const |
771 | { |
772 | return d_impl->isInitialized(); |
773 | } |
774 | |
775 | /*! |
776 | Returns the current \l {QRemoteObjectReplica::State}{state} of the replica. |
777 | |
778 | \sa isInitialized |
779 | */ |
780 | QRemoteObjectReplica::State QRemoteObjectReplica::state() const |
781 | { |
782 | return d_impl->state(); |
783 | } |
784 | |
785 | QRemoteObjectNode *QRemoteObjectReplica::node() const |
786 | { |
787 | return d_impl->node(); |
788 | } |
789 | |
790 | void QRemoteObjectReplica::setNode(QRemoteObjectNode *_node) |
791 | { |
792 | const QRemoteObjectNode *curNode = node(); |
793 | if (curNode) { |
794 | qCWarning(QT_REMOTEOBJECT) << "Ignoring call to setNode as the node has already been set" ; |
795 | return; |
796 | } |
797 | d_impl.clear(); |
798 | _node->initializeReplica(instance: this); |
799 | } |
800 | |
801 | /*! |
802 | \internal |
803 | */ |
804 | void QRemoteObjectReplica::initialize() |
805 | { |
806 | } |
807 | |
808 | /*! |
809 | Returns \c true if this replica has been initialized and has a valid |
810 | connection with the \l {QRemoteObjectNode} {node} hosting the \l {Source}. |
811 | Returns \c false otherwise. |
812 | |
813 | \sa isInitialized() |
814 | */ |
815 | bool QRemoteObjectReplica::isReplicaValid() const |
816 | { |
817 | return state() == Valid; |
818 | } |
819 | |
820 | /*! |
821 | Blocking call that waits for the replica to become initialized or until the |
822 | \a timeout (in ms) expires. Returns \c true if the replica is initialized |
823 | when the call completes, \c false otherwise. |
824 | |
825 | If \a timeout is -1, this function will not time out. |
826 | |
827 | \sa isInitialized(), initialized() |
828 | */ |
829 | bool QRemoteObjectReplica::waitForSource(int timeout) |
830 | { |
831 | return d_impl->waitForSource(timeout); |
832 | } |
833 | |
834 | QInProcessReplicaImplementation::QInProcessReplicaImplementation(const QString &name, const QMetaObject *meta, QRemoteObjectNode * node) |
835 | : QRemoteObjectReplicaImplementation(name, meta, node) |
836 | { |
837 | } |
838 | |
839 | QInProcessReplicaImplementation::~QInProcessReplicaImplementation() |
840 | { |
841 | } |
842 | |
843 | const QVariant QInProcessReplicaImplementation::getProperty(int i) const |
844 | { |
845 | Q_ASSERT(connectionToSource); |
846 | Q_ASSERT(connectionToSource->m_object); |
847 | const int index = i + QRemoteObjectSource::qobjectPropertyOffset; |
848 | Q_ASSERT(index >= 0 && index < connectionToSource->m_object->metaObject()->propertyCount()); |
849 | return connectionToSource->m_object->metaObject()->property(index).read(obj: connectionToSource->m_object); |
850 | } |
851 | |
852 | void QInProcessReplicaImplementation::setProperties(QVariantList &&) |
853 | { |
854 | //TODO some verification here maybe? |
855 | } |
856 | |
857 | void QInProcessReplicaImplementation::setProperty(int i, const QVariant &property) |
858 | { |
859 | Q_ASSERT(connectionToSource); |
860 | Q_ASSERT(connectionToSource->m_object); |
861 | const int index = i + QRemoteObjectSource::qobjectPropertyOffset; |
862 | Q_ASSERT(index >= 0 && index < connectionToSource->m_object->metaObject()->propertyCount()); |
863 | connectionToSource->m_object->metaObject()->property(index).write(obj: connectionToSource->m_object, value: property); |
864 | } |
865 | |
866 | void QInProcessReplicaImplementation::_q_send(QMetaObject::Call call, int index, const QVariantList &args) |
867 | { |
868 | Q_ASSERT(call == QMetaObject::InvokeMetaMethod || call == QMetaObject::WriteProperty); |
869 | |
870 | const SourceApiMap *api = connectionToSource->m_api; |
871 | if (call == QMetaObject::InvokeMetaMethod) { |
872 | const int resolvedIndex = api->sourceMethodIndex(index: index - m_methodOffset); |
873 | if (resolvedIndex < 0) |
874 | qCWarning(QT_REMOTEOBJECT) << "Skipping invalid invocation. Index not found:" << index - m_methodOffset; |
875 | else |
876 | connectionToSource->invoke(c: call, index: index - m_methodOffset, args); |
877 | } else { |
878 | const int resolvedIndex = connectionToSource->m_api->sourcePropertyIndex(index: index - m_propertyOffset); |
879 | if (resolvedIndex < 0) |
880 | qCWarning(QT_REMOTEOBJECT) << "Skipping invalid property setter. Index not found:" << index - m_propertyOffset; |
881 | else |
882 | connectionToSource->invoke(c: call, index: index - m_propertyOffset, args); |
883 | } |
884 | } |
885 | |
886 | QRemoteObjectPendingCall QInProcessReplicaImplementation::_q_sendWithReply(QMetaObject::Call call, int index, const QVariantList &args) |
887 | { |
888 | Q_ASSERT(call == QMetaObject::InvokeMetaMethod); |
889 | |
890 | const int ReplicaIndex = index - m_methodOffset; |
891 | auto metaType = QMetaType::fromName(name: connectionToSource->m_api->typeName(index: ReplicaIndex).constData()); |
892 | if (!metaType.sizeOf()) |
893 | metaType = QMetaType(QMetaType::UnknownType); |
894 | QVariant returnValue(metaType, nullptr); |
895 | |
896 | const int resolvedIndex = connectionToSource->m_api->sourceMethodIndex(index: ReplicaIndex); |
897 | if (resolvedIndex < 0) { |
898 | qCWarning(QT_REMOTEOBJECT) << "Skipping invalid invocation. Index not found:" << ReplicaIndex; |
899 | return QRemoteObjectPendingCall(); |
900 | } |
901 | |
902 | connectionToSource->invoke(c: call, index: ReplicaIndex, args, returnValue: &returnValue); |
903 | return QRemoteObjectPendingCall::fromCompletedCall(returnValue); |
904 | } |
905 | |
906 | QStubReplicaImplementation::QStubReplicaImplementation() {} |
907 | |
908 | QStubReplicaImplementation::~QStubReplicaImplementation() {} |
909 | |
910 | const QVariant QStubReplicaImplementation::getProperty(int i) const |
911 | { |
912 | Q_ASSERT_X(i >= 0 && i < m_propertyStorage.size(), __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 < %2" )).arg(i).arg(m_propertyStorage.size()))); |
913 | return m_propertyStorage[i]; |
914 | } |
915 | |
916 | void QStubReplicaImplementation::setProperties(QVariantList &&properties) |
917 | { |
918 | Q_ASSERT(m_propertyStorage.isEmpty()); |
919 | m_propertyStorage.reserve(asize: properties.size()); |
920 | m_propertyStorage = std::move(properties); |
921 | } |
922 | |
923 | void QStubReplicaImplementation::setProperty(int i, const QVariant &prop) |
924 | { |
925 | m_propertyStorage[i] = prop; |
926 | } |
927 | |
928 | void QStubReplicaImplementation::_q_send(QMetaObject::Call call, int index, const QVariantList &args) |
929 | { |
930 | Q_UNUSED(call) |
931 | Q_UNUSED(index) |
932 | Q_UNUSED(args) |
933 | qWarning(msg: "Tried calling a slot or setting a property on a replica that hasn't been initialized with a node" ); |
934 | } |
935 | |
936 | QRemoteObjectPendingCall QStubReplicaImplementation::_q_sendWithReply(QMetaObject::Call call, int index, const QVariantList &args) |
937 | { |
938 | Q_UNUSED(call) |
939 | Q_UNUSED(index) |
940 | Q_UNUSED(args) |
941 | qWarning(msg: "Tried calling a slot or setting a property on a replica that hasn't been initialized with a node" ); |
942 | return QRemoteObjectPendingCall(); //Invalid |
943 | } |
944 | |
945 | QT_END_NAMESPACE |
946 | |