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
23QT_BEGIN_NAMESPACE
24
25using namespace QRemoteObjectPackets;
26
27QT_WARNING_PUSH
28QT_WARNING_DISABLE_GCC("-Wtautological-compare")
29
30#if !defined(Q_OS_WIN) && !defined(Q_OS_INTEGRITY)
31Q_STATIC_ASSERT_X(&QRemoteObjectReplica::staticMetaObject == &QRemoteObjectDynamicReplica::staticMetaObject,
32 "m_signalOffset initializer expects QRemoteObjectDynamicReplica to not have a unique staticMetaObject");
33#endif
34
35QT_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
41QRemoteObjectReplicaImplementation::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
54QRemoteObjectReplicaImplementation::~QRemoteObjectReplicaImplementation()
55{
56}
57
58QConnectedReplicaImplementation::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
114QConnectedReplicaImplementation::~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
127bool QRemoteObjectReplicaImplementation::needsDynamicInitialization() const
128{
129 return m_metaObject == nullptr;
130}
131
132void 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
153void 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
161bool 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
173QList<int> QConnectedReplicaImplementation::childIndices() const
174{
175 return m_childIndices;
176}
177
178void 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
218void 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*/
229void 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*/
241QVariantList 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
250void QRemoteObjectReplicaImplementation::setDynamicMetaObject(const QMetaObject *meta)
251{
252 Q_ASSERT(!m_metaObject);
253
254 m_metaObject = meta;
255}
256
257void 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
268void 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
281void 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
304bool QConnectedReplicaImplementation::isInitialized() const
305{
306 return m_state.loadAcquire() > QRemoteObjectReplica::Default && m_state.loadAcquire() != QRemoteObjectReplica::SignatureMismatch;
307}
308
309bool 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
341void 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
374QRemoteObjectPendingCall 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
388QRemoteObjectPendingCall 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
402void 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
423bool 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
449const 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
455void 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
462void QConnectedReplicaImplementation::setProperty(int i, const QVariant &prop)
463{
464 m_propertyStorage[i] = prop;
465}
466
467void 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
476void 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
489void QConnectedReplicaImplementation::requestRemoteObjectSource()
490{
491 Q_ASSERT(connectionToSource);
492 connectionToSource->d_func()->m_codec->serializeAddObjectPacket(name: m_objectName, isDynamic: needsDynamicInitialization());
493 sendCommand();
494}
495
496void 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
557void 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*/
688QRemoteObjectReplica::QRemoteObjectReplica(ConstructorType t)
689 : QObject(nullptr)
690 , d_impl(t == DefaultConstructor ? new QStubReplicaImplementation : nullptr)
691{
692 qRegisterMetaType<State>(typeName: "State");
693}
694
695QRemoteObjectReplica::QRemoteObjectReplica(QObjectPrivate &dptr, QObject *parent)
696 : QObject(dptr, parent)
697 , d_impl(new QStubReplicaImplementation)
698{
699}
700
701/*!
702 \internal
703*/
704QRemoteObjectReplica::~QRemoteObjectReplica()
705{
706}
707
708/*!
709 \internal
710*/
711void 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*/
721QRemoteObjectPendingCall 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*/
729const QVariant QRemoteObjectReplica::propAsVariant(int i) const
730{
731 return d_impl->getProperty(i);
732}
733
734/*!
735 \internal
736*/
737void QRemoteObjectReplica::initializeNode(QRemoteObjectNode *node, const QString &name)
738{
739 node->initializeReplica(instance: this, name);
740}
741
742/*!
743 \internal
744*/
745void QRemoteObjectReplica::setProperties(QVariantList &&properties)
746{
747 d_impl->setProperties(std::move(properties));
748}
749
750/*!
751 \internal
752*/
753void 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*/
764bool 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*/
775QRemoteObjectReplica::State QRemoteObjectReplica::state() const
776{
777 return d_impl->state();
778}
779
780QRemoteObjectNode *QRemoteObjectReplica::node() const
781{
782 return d_impl->node();
783}
784
785void 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*/
799void 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*/
810bool 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*/
824bool QRemoteObjectReplica::waitForSource(int timeout)
825{
826 return d_impl->waitForSource(timeout);
827}
828
829QInProcessReplicaImplementation::QInProcessReplicaImplementation(const QString &name, const QMetaObject *meta, QRemoteObjectNode * node)
830 : QRemoteObjectReplicaImplementation(name, meta, node)
831{
832}
833
834QInProcessReplicaImplementation::~QInProcessReplicaImplementation()
835{
836}
837
838const 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
847void QInProcessReplicaImplementation::setProperties(QVariantList &&)
848{
849 //TODO some verification here maybe?
850}
851
852void 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
861void 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
881QRemoteObjectPendingCall 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
901QStubReplicaImplementation::QStubReplicaImplementation() {}
902
903QStubReplicaImplementation::~QStubReplicaImplementation() {}
904
905const 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
911void 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
918void QStubReplicaImplementation::setProperty(int i, const QVariant &prop)
919{
920 m_propertyStorage[i] = prop;
921}
922
923void 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
931QRemoteObjectPendingCall 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
940QT_END_NAMESPACE
941

source code of qtremoteobjects/src/remoteobjects/qremoteobjectreplica.cpp