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