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 if (auto o = prop.value<QObject*>())
124 o->deleteLater();
125 }
126 }
127}
128
129bool QRemoteObjectReplicaImplementation::needsDynamicInitialization() const
130{
131 return m_metaObject == nullptr;
132}
133
134void 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
155void 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
163bool 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
175QList<int> QConnectedReplicaImplementation::childIndices() const
176{
177 return m_childIndices;
178}
179
180void 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
220void 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*/
231void 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*/
243QVariantList 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
252void QRemoteObjectReplicaImplementation::setDynamicMetaObject(const QMetaObject *meta)
253{
254 Q_ASSERT(!m_metaObject);
255
256 m_metaObject = meta;
257}
258
259void 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
270void 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
283void 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
306bool QConnectedReplicaImplementation::isInitialized() const
307{
308 return m_state.loadAcquire() > QRemoteObjectReplica::Default && m_state.loadAcquire() != QRemoteObjectReplica::SignatureMismatch;
309}
310
311bool 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
343void 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
376QRemoteObjectPendingCall 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
390QRemoteObjectPendingCall 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
404void 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
425bool 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
451const 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
457void 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
464void QConnectedReplicaImplementation::setProperty(int i, const QVariant &prop)
465{
466 m_propertyStorage[i] = prop;
467}
468
469void 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
478void 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
495void QConnectedReplicaImplementation::requestRemoteObjectSource()
496{
497 Q_ASSERT(connectionToSource);
498 connectionToSource->d_func()->m_codec->serializeAddObjectPacket(name: m_objectName, isDynamic: needsDynamicInitialization());
499 sendCommand();
500}
501
502void 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
563void 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*/
694QRemoteObjectReplica::QRemoteObjectReplica(ConstructorType t)
695 : QObject(nullptr)
696 , d_impl(t == DefaultConstructor ? new QStubReplicaImplementation : nullptr)
697{
698 qRegisterMetaType<State>(typeName: "State");
699}
700
701QRemoteObjectReplica::QRemoteObjectReplica(QObjectPrivate &dptr, QObject *parent)
702 : QObject(dptr, parent)
703 , d_impl(new QStubReplicaImplementation)
704{
705}
706
707/*!
708 \internal
709*/
710QRemoteObjectReplica::~QRemoteObjectReplica()
711{
712}
713
714/*!
715 \internal
716*/
717void 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*/
727QRemoteObjectPendingCall 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*/
735const QVariant QRemoteObjectReplica::propAsVariant(int i) const
736{
737 return d_impl->getProperty(i);
738}
739
740/*!
741 \internal
742*/
743void QRemoteObjectReplica::initializeNode(QRemoteObjectNode *node, const QString &name)
744{
745 node->initializeReplica(instance: this, name);
746}
747
748/*!
749 \internal
750*/
751void QRemoteObjectReplica::setProperties(QVariantList &&properties)
752{
753 d_impl->setProperties(std::move(properties));
754}
755
756/*!
757 \internal
758*/
759void 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*/
770bool 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*/
780QRemoteObjectReplica::State QRemoteObjectReplica::state() const
781{
782 return d_impl->state();
783}
784
785QRemoteObjectNode *QRemoteObjectReplica::node() const
786{
787 return d_impl->node();
788}
789
790void 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*/
804void 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*/
815bool 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*/
829bool QRemoteObjectReplica::waitForSource(int timeout)
830{
831 return d_impl->waitForSource(timeout);
832}
833
834QInProcessReplicaImplementation::QInProcessReplicaImplementation(const QString &name, const QMetaObject *meta, QRemoteObjectNode * node)
835 : QRemoteObjectReplicaImplementation(name, meta, node)
836{
837}
838
839QInProcessReplicaImplementation::~QInProcessReplicaImplementation()
840{
841}
842
843const 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
852void QInProcessReplicaImplementation::setProperties(QVariantList &&)
853{
854 //TODO some verification here maybe?
855}
856
857void 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
866void 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
886QRemoteObjectPendingCall 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
906QStubReplicaImplementation::QStubReplicaImplementation() {}
907
908QStubReplicaImplementation::~QStubReplicaImplementation() {}
909
910const 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
916void 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
923void QStubReplicaImplementation::setProperty(int i, const QVariant &prop)
924{
925 m_propertyStorage[i] = prop;
926}
927
928void 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
936QRemoteObjectPendingCall 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
945QT_END_NAMESPACE
946

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