I am trying to connect a gui to my logic thread using boosts signals and slots, the logic class has a neat method to connect functions to the signal.
Here is a simplified replica of the locig class:
#include <boost/signals2.hpp>
#include <boost/function.hpp>
typedef boost::signals2::signal<void (const double&)> some_signal;
typedef some_signal::slot_type some_slot;
class LogicClass {
some_signal sig;
public:
LogicClass();
~LogicClass();
void register_callback(boost::function<void (const double&)>) {
sig.connect(boost::bind(&LogicClass::doStuff(), this, _1));
}
void doStuff(); // Does a bunch of stuff in a separate thread and fires LogicClass::sig every now and then
}
Here is a simplified replica of the gui class
#include <boost/signals2.hpp>
#include <QWidget.h>
class GuiClass : public QWidget {
Q_OBJECT //etc. etc. w.r.t. Qt stuff
public:
GuiClass ();
~GuiClass ();
void draw_stuff(const double&); // Want this to listen to LogicClass::sig;
}
At some point in my code the gui class is already instantiated but the logic class isn't. So i want to instantiate the LogicClass and subscribe the GuiClass::draw_stuff(const double&) to the LogicClass::sig signal. Something like
#include <logicclass.h>
#include <guiclass.h>
GuiClass *gui; //Was initialized elsewhere, but available here;
void some_function() {
LogicClass *logic = new LogicClass();
logic->register_callback(gui->drawStuff);
logic->do_stuff(); //Start drawing on the gui
delete logic;
}
This unfortunately doesn't work. Goes without saying it would like it to work very much!
I know Qt also implements signals & slots, but i would like to use boost for portability with other UI libraries.
Looks like you meant to bind draw_stuff instead of hardcoded doStuff inside of that register function:
logic->register_callback(boost::bind(&GuiClass::draw_stuff, gui, _1));
And then
void register_callback(boost::function<void(double)> handler) {
sig.connect(handler);
}
Simple Demo
Live On Coliru
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/signals2.hpp>
#include <boost/thread.hpp>
#include <iostream>
typedef boost::signals2::signal<void(double)> some_signal;
typedef some_signal::slot_type some_slot;
class LogicClass {
some_signal sig;
public:
LogicClass() = default;
~LogicClass() = default;
void register_callback(boost::function<void(double)> handler) {
sig.connect(handler);
}
void doStuff() {
std::cout << __PRETTY_FUNCTION__ << "\n";
// Does a bunch of stuff in a separate thread and fires LogicClass::sig every now and then
boost::thread([this] {
for (double d : {1,2,3}) {
boost::this_thread::sleep_for(boost::chrono::milliseconds(500));
sig(d);
}
}).join();
}
};
#define Q_OBJECT
struct QWidget {};
class GuiClass : public QWidget {
Q_OBJECT // etc. etc. w.r.t. Qt stuff
public:
GuiClass() = default;
~GuiClass() = default;
void draw_stuff(double v) { std::cout << __PRETTY_FUNCTION__ << "(" << v << ")\n"; }
};
//#include <logicclass.h>
//#include <guiclass.h>
GuiClass *gui; // Was initialized elsewhere, but available here;
void some_function() {
LogicClass *logic = new LogicClass();
logic->register_callback(boost::bind(&GuiClass::draw_stuff, gui, _1));
logic->doStuff(); // Start drawing on the gui
delete logic;
}
int main() {
GuiClass gui_instance;
gui = &gui_instance;
some_function();
}
Prints
void LogicClass::doStuff()
void GuiClass::draw_stuff(double)(1)
void GuiClass::draw_stuff(double)(2)
void GuiClass::draw_stuff(double)(3)
Related
I have a class with an interface that is doing some uploading, which is done async, when the upload finishes, it fires a signal to inform the consumer of this class that the work has been done. I'm trying to write a test for the consumer class that gets a mock injected and the basic idea was when the upload method is called, the mock should trigger the finished signal on itself. Problem is that the tested consumer doesn't get the signal. I'm either missing something or this doesn't work.
For simplicity reasons, here are some fake implementations:
The interface of the uploader:
#include <QObject>
class IUploader : public QObject
{
Q_OBJECT
public:
IUploader() = default;
~IUploader() override = default;
virtual void upload(int i) = 0;
signals:
void finished(int i);
};
The mock:
#include <gmock/gmock.h>
#include "IUploader.h"
class UploaderMock : public IUploader
{
public:
MOCK_METHOD(void, upload, (int i), (override));
};
The test:
#include <gtest/gtest.h>
#include "Consumer.h"
#include "UploaderMock.h"
TEST(Consumer, UploaderFinished_Should_TriggerCallback)
{
// arrange
UploaderMock uploader;
ON_CALL(uploader, upload(11)).WillByDefault([&uploader](int i) {
uploader.finished(i * 2);
});
Consumer consumer(&uploader);
// act
consumer.doUpload(11);
// assert
EXPECT_EQ(consumer.uploadResult(), 22);
}
Consumer.h
#include
#include "IUploader.h"
class Consumer : public QObject
{
Q_OBJECT
public:
explicit Consumer(IUploader *uploader);
~Consumer() override = default;
void doUpload(int i);
int uploadResult() const;
private:
IUploader *mUploader = nullptr;
int mResult = -1;
private slots:
void setResult(int i);
};
Consumer.cpp
#include "Consumer.h"
Consumer::Consumer(IUploader *uploader) :
mUploader(uploader)
{
connect(uploader, &IUploader::finished, this, &Consumer::setResult);
}
void Consumer::doUpload(int i)
{
mUploader->upload(i);
}
int Consumer::uploadResult() const
{
return mResult;
}
void Consumer::setResult(int i)
{
mResult = i;
}
What happens is that I do see the action being triggered in the ON_CALL but I don't see any signal received by the Consumer object.
Is such a test case possible with Qt and GMock? Could it be a timing problem that the signal is processed after the test method finishes?
I am using Qt5.15.2.
I have a QObject used in my GUI and defined as below:
#pragma once
#include "actionstep.h"
#include "anotherthread.h"
#include <QTableWidget>
class TableWidget : public QTableWidget {
Q_OBJECT
public:
TableWidget(QWidget *parent = nullptr) : QTableWidget(4, 4, parent) {
int id = qRegisterMetaType<ActionStep::ActionStatusInfos>();
std::cout << id << std::endl;
connect(this, &TableWidget::SigUpdateActionStatusInfos, this,
&TableWidget::SlotUpdateActionStatusInfos,
Qt::ConnectionType::QueuedConnection);
th.start();
}
void methodCalledByAnotherThread(ActionStep::ActionStatusInfos ActionStepInfosObj)
{
emit SigUpdateActionStatusInfos(ActionStepInfosObj);
}
signals:
void SigUpdateActionStatusInfos(ActionStep::ActionStatusInfos as);
private slots:
void SlotUpdateActionStatusInfos(ActionStep::ActionStatusInfos as) {
}
private:
AnotherThread th;
};
The idea is to be able to send my custom object of type ActionStep::ActionStatusInfos through the signal/slot service.
Content of "actionstep.h", which contains my custom object:
#pragma once
#include <iostream>
#include <QMetaType>
class ActionStep {
public:
class ActionStatusInfos {
public:
// Default constructor
ActionStatusInfos() : Progress(0) {
}
// Default destructor
~ActionStatusInfos() = default;
// Copy constructor
ActionStatusInfos(ActionStatusInfos &source) {
SetProgress(source.GetProgress());
}
// Copy assignment
ActionStatusInfos &operator=(ActionStatusInfos &source) {
SetProgress(source.GetProgress());
return *this;
}
// move constructor
ActionStatusInfos(ActionStatusInfos &&source) noexcept
{
SetProgress(source.GetProgress());
}
//move assignment
ActionStatusInfos &operator=(ActionStatusInfos &&source) noexcept
{
SetProgress(source.GetProgress());
return *this;
}
void SetProgress(int pg) {
Progress = pg;
}
int GetProgress() {
return Progress;
}
private:
int Progress;
};
};
Q_DECLARE_METATYPE(ActionStep::ActionStatusInfos)
I've added both Q_DECLARE_METATYPE(ActionStep::ActionStatusInfos) and qRegisterMetaType<ActionStep::ActionStatusInfos>(), and my class seems to meet the minimum requirements needed by Qt for registering a custom class (default ctor, dtor and copy ctor).
At runtime, methodCalledByAnotherThread is called by a different thread than the GUI. After a few cycles, I get a SIGFAULT, and the debugger shows that there is a dangling reference when the Qt stuff is executed.
Am I missing something obvious?
I try to create a some standart signal definitionsfor some classes with macros like:
#define CREATE_SIGNALS signals: void error_signal(QString error);
Functions are created, but moc did not create methods for these functions. It seems that moc is running before macros processed. How can i make this work?
Moc preprocesses the sources it works on, so your macro will work once you actually make the macro define some signals, and not regular methods.
The following is a complete example:
#include <QtCore>
#include <array>
#include <algorithm>
#define CREATE_SIGNALS\
Q_SIGNAL void signal1(const QString & = QString());\
Q_SIGNAL void signal2(int = 0);
struct Foo : QObject {
CREATE_SIGNALS
Q_OBJECT
};
struct Bar : QObject {
CREATE_SIGNALS
Q_OBJECT
};
int main()
{
std::array<int, 4> s;
Foo foo;
Bar bar;
s.fill(0);
QObject::connect(&foo, &Foo::signal1, [&]{++s[0];});
QObject::connect(&foo, &Foo::signal2, [&]{++s[1];});
QObject::connect(&bar, &Bar::signal1, [&]{++s[2];});
QObject::connect(&bar, &Bar::signal2, [&]{++s[3];});
emit foo.signal1();
emit foo.signal2();
emit bar.signal1();
emit bar.signal2();
Q_ASSERT(std::all_of(std::begin(s), std::end(s), [](int val) { return val == 1; }));
}
#include "main.moc"
I need to create a thread to run the Networking portion of my game. I would prefer to use SFML threads as my compiler doesn't yet support C++11 threads. However the class which contains the thread is created with make_shared(). Here is the code:
Game.cpp (not all the code just the declaration of GameScreen)
std::shared_ptr<Screen> Game::screen = std::make_shared<GameScreen>();
Screen is just a base class containing pure virtual functions. You should be able to figure out which ones are virtual based off the override keywords.
GameScreen.h
#ifndef GAMESCREEN_H
#define GAMESCREEN_H
#include <SFML/Graphics.hpp>
#include "Events.h"
#include "Screen.h"
#include "Map.h"
#include "Network.h"
class GameScreen : public Screen
{
public:
GameScreen();
void handleInput(sf::RenderWindow&) override;
void update(sf::RenderWindow&, sf::View&) override;
void render(sf::RenderWindow&) override;
private:
Map m_map;
Network network;
Events eventManager;
sf::Thread networkThread;
};
#endif // GAMESCREEN_H
GameScreen.cpp
#include <memory>
#include <iostream>
#include "GameScreen.h"
#include "Game.h"
GameScreen::GameScreen()
: networkThread(network.receive(eventManager))
{
network.Connect();
}
void GameScreen::handleInput(sf::RenderWindow& window)
{
/*Code*/
}
void GameScreen::update(sf::RenderWindow& window, sf::View& view)
{
/*Code*/
}
void GameScreen::render(sf::RenderWindow& window)
{
/*Code*/
}
Network.cpp (receive function only)
void Network::Recieve(Events& eManager)
{
sf::Packet m_rPacket;
m_socket.receive(m_rPacket, m_serverIP, port);
m_rPacket >> /*Data*/
eManager.addEvent(tmp);
}
You can use this in the constructor's initialization list:
MyClass::MyClass()
: AClass(&MyFunction(*this))
{
/*do stuff*/
}
However, this doesn't make sense in your example, because you are trying to pass a pointer to MyFunction (or its non-existent return value) to AClass(), and you can't quality a pointer with parameters. You can only pass parameters to MyFunction() when actually calling MyFunction(). Are you sure you don't actually mean something more like this instead:
MyClass::MyClass()
: AClass()
{
/*do stuff*/
MyFunction(*this);
}
Without seeing what AClass() actually is, or what it expects as input, it is difficult to know for sure what you are trying to do.
Update clearly you have not read the SFML documentation or SFML Tutorial on threading. The Thread constructor takes a pointer to a function/method as one input parameter, and an optional input value for the function/method as a separate parameter. Try this instead:
class MyClass : public sf::Thread
{
private:
static void MyFunction(MyClass &cls);
public:
MyClass();
};
MyClass::MyClass()
: sf::Thread(&MyClass::MyFunction, *this)
{
/*do stuff*/
}
void MyClass::MyFunction(MyClass &cls)
{
/*do stuff with 'cls'*/
}
Or this, as you can use a non-static class method with an SFML thread:
class MyClass : public sf::Thread
{
private:
void MyFunction();
public:
MyClass();
};
MyClass::MyClass()
: sf::Thread(&MyClass::MyFunction, *this)
{
/*do stuff*/
}
void MyClass::MyFunction()
{
/*do stuff with 'this'*/
}
Update: based on your new code, you are still not even close to constructing the sf::Thread object correctly (did you read the documentation/tutorial I linked to?). Also, your thread needs access to multiple objects owned by GameScreen, so you can't pass them all to the sf::Thread constructor. You need to do something more like this instead:
class GameScreen : public Screen
{
public:
GameScreen();
...
private:
...
Network network;
Events eventManager;
sf::Thread networkThread;
void networkThreadFunc();
};
GameScreen::GameScreen()
: networkThread(&GameScreen::networkThreadFunc, *this)
{
network.Connect();
}
void GameScreen::networkThreadFunc()
{
network.Receive(eventManager);
}
I have some trouble with using enum types in signals. Basicly I have two classes, a state machine and a thread handling the state machine. When the state is changed I want to send a signal with the new state. I also want to represent the state using an enum. In my full blown code the state machine is implemented in a separate shared library, but the code below gives the exact same error.
When I run the code I get the following behaviour:
kotte#EMO-Ubuntu:sigenum $ ./sigenum
Object::connect: No such slot MyThread::onNewState(state)
Test signal
Test signal
...
I have four files in my sample code: statemachine.h, statemachine.cpp, main.h and main.cpp. The main function simply starts the thread, the thread then creates an instance of the StateMachine and processes signals from the StateMachine. I am pretty new to Qt, so I was a bit puzzled when I realised that you have to enclose the enum with Q_ENUMS and register it with the type system. So It's fully possible that I've made some rookie mistake
The code below is a bit long, but I wanted it to be as similar to my real code as possible.
statemachine.h looks like:
// statemachine.h
#ifndef _STATEMACHINE_H
#define _STATEMACHINE_H
#include <QtCore>
class StateMachine : public QObject
{
Q_OBJECT
Q_ENUMS(state)
public:
enum state {S0, S1, S2};
void setState(state newState);
signals:
void stateChanged(state newState);
void testSignal(void);
};
Q_DECLARE_METATYPE(StateMachine::state);
#endif
And it is implemented as:
// statemachine.cpp
#include <QtCore>
#include "statemachine.h"
void StateMachine::setState(state newState)
{
emit stateChanged(newState);
emit testSignal();
}
The thread is defined as
// main.h
#ifndef _MAIN_H
#define _MAIN_H
#include <QtCore>
#include "statemachine.h"
class MyThread : public QThread
{
Q_OBJECT
private:
void run(void);
private slots:
void onNewState(StateMachine::state);
void onTestSignal(void);
private:
StateMachine *myStateMachine;
};
#endif
And it is implemented as follows:
// main.cpp
#include <QtCore>
#include <QApplication>
#include "statemachine.h"
#include "main.h"
void MyThread::run()
{
myStateMachine = new StateMachine();
qRegisterMetaType<StateMachine::state>("state");
// This does not work
connect(myStateMachine, SIGNAL(stateChanged(state)),
this, SLOT(onNewState(state)));
// But this does...
connect(myStateMachine, SIGNAL(testSignal()),
this, SLOT(onTestSignal()));
forever {
// ...
myStateMachine->setState(StateMachine::S0);
}
}
void MyThread::onTestSignal()
{
qDebug() << "Test signal";
}
void MyThread::onNewState(StateMachine::state newState)
{
qDebug() << "New state is:" << newState;
}
By using fully qualified names everywhere I got it to work
If I change the declaration of stateChanged() to
signals:
void stateChanged(StateMachine::state newState);
And registers the type with
qRegisterMetaType<StateMachine::state>("StateMachine::state");
and also uses this name in the connect statement
connect(myStateMachine, SIGNAL(stateChanged(StateMachine::state)),
this, SLOT(onNewState(StateMachine::state)));
Wouldn't have solved this without the input from drescherjm, thanks :-)
I believe the following is state is not defined in your MyThread class.
Use the following
connect(myStateMachine, SIGNAL(stateChanged(StateMachine::state)),
this, SLOT(onNewState(StateMachine::state)));
Edit:
Maybe this will work
connect(myStateMachine, SIGNAL(stateChanged(state)),
this, SLOT(onNewState(StateMachine::state)));
You should get rid of SIGNAL and SLOT since Qt can detect mismatches at compile time. You can also avoid having to use Q_DECLARE_METATYPE and qRegisterMetaType() by using Q_ENUM instead of Q_ENUMS - this was introduced in Qt 5.5, Finally enum class is a strongly typed version of enum:
// statemachine.h
#ifndef _STATEMACHINE_H
#define _STATEMACHINE_H
#include <QtCore>
class StateMachine : public QObject
{
Q_OBJECT
Q_ENUM(state)
public:
enum class state {S0, S1, S2};
void setState(state newState);
signals:
void stateChanged(state newState);
void testSignal(void);
};
#endif
// main.cpp
#include <QtCore>
#include <QApplication>
#include "statemachine.h"
#include "main.h"
void MyThread::run()
{
myStateMachine = new StateMachine();
connect(myStateMachine, &StateMachine::stateChanged, this, &MyThread::NewState);
connect(myStateMachine, &StateMachine::testSignal, this, &MyThread::onTestSignal);
forever {
// ...
myStateMachine->setState(StateMachine::S0);
}
}