Richard Stallman dnes v Liberci přednáší o svobodném softwaru a svobodě v digitální společnosti. Od 16:30 v aule budovy G na Technické univerzitě v Liberci. V anglickém jazyce s automaticky generovanými českými titulky. Vstup je zdarma i pro širokou veřejnost.
sudo-rs, tj. sudo a su přepsáné do programovacího jazyka Rust, nahradí v Ubuntu 25.10 klasické sudo. V plánu je také přechod od klasických coreutils k uutils coreutils napsaných v Rustu.
Fedora se stala oficiální distribucí WSL (Windows Subsystem for Linux).
Společnost IBM představila server IBM LinuxONE Emperor 5 poháněný procesorem IBM Telum II.
Byla vydána verze 4.0 multiplatformního integrovaného vývojového prostředí (IDE) pro rychlý vývoj aplikaci (RAD) ve Free Pascalu Lazarus (Wikipedie). Přehled novinek v poznámkách k vydání. Využíván je Free Pascal Compiler (FPC) 3.2.2.
Podpora Windows 10 končí 14. října 2025. Připravovaná kampaň Konec desítek (End of 10) může uživatelům pomoci s přechodem na Linux.
Již tuto středu proběhne 50. Virtuální Bastlírna, tedy dle římského číslování L. Bude L značit velikost, tedy více diskutujících než obvykle, či délku, neboť díky svátku lze diskutovat dlouho do noci? Bude i příští Virtuální Bastlírna virtuální nebo reálná? Nejen to se dozvíte, když dorazíte na diskuzní večer o elektronice, softwaru, ale technice obecně, který si můžete představit jako virtuální posezení u piva spojené s učenou
… více »Český statistický úřad rozšiřuje Statistický geoportál o Datový portál GIS s otevřenými geografickými daty. Ten umožňuje stahování datových sad podle potřeb uživatelů i jejich prohlížení v mapě a přináší nové možnosti v oblasti analýzy a využití statistických dat.
Kevin Lin zkouší využívat chytré brýle Mentra při hraní na piano. Vytváří aplikaci AugmentedChords, pomocí které si do brýlí posílá notový zápis (YouTube). Uvnitř brýlí běží AugmentOS (GitHub), tj. open source operační systém pro chytré brýle.
Píši GUI aplikaci, která má číst standardní vstup a chci aby načtená data průběžně zobrazovala. Používám Qt a C++. Jsou tam potřeba dvě vlákna -- jedno pro GUI a jedno pro čtení vstupu. Z toho vlákna pro čtení nemůžu pracovat s GUI komponentami a plnit do nich hodnoty, to je jasné.
Dočetl jsem se, že nejsnazší způsob, jak tahle dvě vlákna propojit jsou signály a sloty. To jsem udělal a funguje mi to. Akorát mi připadá, že bylo potřeba napsat moc kódu, který nic zajímavého nedělá, jen přeposílá události -- a to je potřeba udělat pro každou metodu. Výsledek:
#pragma once #include <QObject> #include <relpipe/reader/typedefs.h> #include <relpipe/reader/TypeId.h> #include <relpipe/reader/handlers/RelationalReaderStringHandler.h> #include <relpipe/reader/handlers/AttributeMetadata.h> using namespace relpipe::reader; using namespace relpipe::reader::handlers; // signal/slot parameters must be declared here and registered with qRegisterMetaType() Q_DECLARE_METATYPE(string_t) Q_DECLARE_METATYPE(std::vector<AttributeMetadata>) class QtRelationalReaderStringHadler : public QObject, public RelationalReaderStringHadler { Q_OBJECT private: RelationalReaderStringHadler* target; public: QtRelationalReaderStringHadler(QObject* parent, RelationalReaderStringHadler* target) : QObject(parent), target(target) { // see Q_DECLARE_METATYPE above qRegisterMetaType<string_t>(); qRegisterMetaType<std::vector < AttributeMetadata >> (); QObject::connect(this, &QtRelationalReaderStringHadler::signal_startRelation, this, &QtRelationalReaderStringHadler::slot_startRelation); QObject::connect(this, &QtRelationalReaderStringHadler::signal_attribute, this, &QtRelationalReaderStringHadler::slot_attribute); QObject::connect(this, &QtRelationalReaderStringHadler::signal_endOfPipe, this, &QtRelationalReaderStringHadler::slot_endOfPipe); } virtual ~QtRelationalReaderStringHadler() { } virtual void startRelation(string_t name, std::vector<AttributeMetadata> attributes) override { emit signal_startRelation(name, attributes); } virtual void attribute(const string_t& value) override { emit signal_attribute(value); }; virtual void endOfPipe() override { emit signal_endOfPipe(); }; signals: void signal_startRelation(string_t name, std::vector<AttributeMetadata> attributes); void signal_attribute(const string_t& value); void signal_endOfPipe(); private slots: void slot_startRelation(string_t name, std::vector<AttributeMetadata> attributes) { target->startRelation(name, attributes); }; void slot_attribute(const string_t& value) { target->attribute(value); }; void slot_endOfPipe() { target->endOfPipe(); }; };
Jde mi o to, že mám rozhraní RelationalReaderStringHadler
a toto rozhraní mi provolává metoda, která parsuje vstup. V CLI aplikaci to můžu napojit napřímo, ale tady jsem mezi to musel vložit ještě tu "proxy" QtRelationalReaderStringHadler
, která to prožene přes signály/sloty a tím zajistí, že se data mezi dvěma vlákny předají správně. Napadá vás nějaké elegantnější řešení?
V dokumentaci Qt jsem našel, že to jde napojovat i na std::bind
nebo lambdu, což by trošku kódu ušetřilo (mohl bych kód slotů předat do QObject::connect()
jako funkci). Nešlo by to napojit nějak automaticky? Protože ta rozhraní jsou v obou vláknech totožná.
Případně by se mi líbilo, kdybych mohl v těch metodách místo emit signal_...
jen předat někam lambdu s tím, že se má provést v GUI vlákně. (to bych si asi zvládl napsat sám a protáhnout všechna volání přes jeden signál/slot, ale nechci vymýšlet kolo a radši bych se naučil nějaký standardní postup).
Řešení dotazu:
RelationStringHandler
u? Fungovalo by to IMHO stejně a ušetřil by sis psaní toho proxy.
Ten RelationalReaderStringHadler
je rozhraní definované v knihovně, kterou tato aplikace používá. Třída QtRelationalReaderStringHadler
je implementace tohoto rozhraní v aplikaci. Stejně tak toto rozhraní implementuje GUI třída:
class RelpipeChartMainWindow : public QMainWindow, public RelationalReaderStringHadler {...
A v ní jsou pak tyto metody (sloužící i jako sloty) stylem:
void RelpipeChartMainWindow::endOfPipe() { // TODO: just display a message statusBar()->addWidget(new QPushButton("endOfPipe", widget.statusbar)); }
Zpracování se pak spouští ve vlákně na pozadí:
RelpipeChartMainWindow window; QtRelationalReaderStringHadler handler(&app, &window); reader->addHandler(&handler); WorkerThread t(reader); // QThread volající reader->process(); tzn. čtení STDIN a volání metod handleru t.start();
V CLI aplikaci bych místo reader->addHandler(&handler);
udělal reader->addHandler(&window);
a poslal události rovnou objektu, který je vypíše (a celé by to běželo v jednom vlákně). V Qt aplikaci jsou vlákna dvě a nemůžu to napojit takhle na přímo, takže jsem to proložil tím QtRelationalReaderStringHadler
. Ale rád bych našel jednodušší řešení, protože takhle tam mám 80 řádků kódu jen kvůli tomu, abych řekl že se to má provolat z jednoho vlákna do druhého (a s rostoucím počtem metod by toho kódu bylo ještě víc).
P.S. Ta knihovna, ve které je RelationalReaderStringHadler
není (a nemá být) závislá na Qt -- je to čisté C++.
P.P.S. Není nutné, aby GUI třída implementovala RelationalReaderStringHadler
-- klidně by se ty metody mohly jmenoval jinak a mít jiné parametry, ale přišlo mi zbytečné vymýšlet nové rozhraní, když obsah je stejný.
QObject::connect
má volitený pátý parametr, který řídí, zda se má příslušný slot zavolat okamžitě jako funkce z vlákna odesílatele nebo zda se má na event queue vlákna příjemce vložit informace, že se má ten slot zavolat. Ten parametr má výchozí hodnotu Qt::AutoConnection
. To znamená, že se při zpracovávání signálu handler podívá, zda příjemce náleží stejnému vláknu, ze kterého byl zavolán signál a podle toho se zachová. Proto ti to tvoje docela kostrbaté řešení funguje. Kdybys ale třeba ten proxy objekt vytvořil z jiného než hlavního vlákna, rozbilo by se to.
Pokud nechceš, aby samotná business logika závisela na Qtčkách, budeš si sice pořád muset pomoct nějakým proxy objektem, který se ale dá napsat daleko jednodušeji. Napadá mě třeba toto:
Mějme nějakou business funkci, která klidně může běžet v samostatném vlákně. Ta podle situace generuje nějaký progress report:
void businessFunc() { /* ... do work ... */ switch (report) { case 0: outputProxy->startRelation(); break; case 1: outputProxy->attribute(); break; case 2: outputProxy->endOfPipe(); break; } }Dále mějme jednoduchý interface
OutputProxy
:
class OutputProxy { public: virtual void startRelation() = 0; virtual void attribute() = 0; virtual void endOfPipe() = 0; };Ten interface budou implementovat konkrétní proxy třídy určené k prodrátování dat z business logiky ke konkrétnímu výstupu. Nejjednodušeji třeba takto:
class QtOutputProxy : public OutputProxy, public QObject { Q_OBJECT public: QtOutputProxy(MyUIWidget *target, QObject *parent = nullptr) : QObject(parent) { connect(this, &QtOutputProxy::sig_startRelation, target, &MyUIWidget::onStartRelation); connect(this, &QtOutputProxy::sig_attribute, target, &MyUIWidget::onAttribute); connect(this, &QtOutputProxy::sig_endOfPipe, target, &MyUIWidget::onEndOfPipe); } void startRelation() override { emit sig_startRelation(); } void attribute() override { emit sig_attribute(); } void endOfPipe() override { emit sig_endOfPipe(); } signals: void sig_startRelation(); void sig_attribute(); void sig_endOfPipe(); };Pokud bys měl jiný typ výstupu, který by přesně nekopíroval způsob, jakým business logika reportuje, stačí ti upravit implementaci té proxy třídy; např. takhle:
class QtOutputProxyV2 : public OutputProxy, public QObject { Q_OBJECT public: QtOutputProxyV2(MyUIWidgetV2 *target, QObject *parent = nullptr) : QObject(parent) { connect(this, &QtOutputProxyV2::updateDisplay, target, &MyUIWidgetV2::onUpdateDisplay); } void startRelation() override { emit updateDisplay("Relation started..."); } void attribute() override { emit updateDisplay("Displaying attribute"); } void endOfPipe() override { emit updateDisplay("End of pipe"); } signals: void updateDisplay(const QString &text); };
To znamená, že se při zpracovávání signálu handler podívá, zda příjemce náleží stejnému vláknu, ze kterého byl zavolán signál a podle toho se zachová.
Koukám na to a je mi divné, že to vůbec funguje, žádné chyby ani varování... Signály i sloty jsou v QtRelationalReaderStringHadler
a connect()
dělám taky v této třídě (ovšem ještě v hlavním vlákně). Pak se ve vlákně na pozadí pustí ten reader->process()
, který provolává QtRelationalReaderStringHadler
ovšem jako normální metody -- a až uvnitř těchto metod to jde přes signál/slot (ale v rámci toho samého vlákna) a ze slotů se provolává target->startRelation(name, attributes)
ovšem už zase jako normální metoda. A to ten target
patří do jiného vlákna. Asi si s tím budu muset pohrát v debuggeru, protože takhle mi přijde, že to nemůže fungovat (ale na první pohled to funguje).
V tom void attribute(const string_t& value)
se předává pouze reference a handler
si má tu hodnotu přečíst/zkopírovat, ale nepatří mu -- jakmile skončí tahle metoda, tak ten, kdo ji volal, tu hodnotu smaže (resp. skončí rozsah její platnosti). Tady by mi ASan nadával, že používám již uvolněnou paměť, což se neděje. To nasvědčuje tomu, že ty signály/sloty jsou v mém případě synchronní (protože jsou v rámci jednoho vlákna/objektu). A celé to asi funguje jen díky tomu, že Qt se (náhodou) nerozbije, i když manipuluji s jeho GUI komponentami z jiného vlákna, než bych měl.
Předělám to zatím tím prvním způsobem -- tzn. přesunu sloty do RelpipeChartMainWindow
-- tím by to mělo být asynchronní -- a zároveň změním jejich signatury, aby se pracovalo s kopií a ne referencí. BTW: když je to asynchronní, je zaručené pořadí? Protože na tom tady záleží -- ty atributy se plní do tabulky a řádek/sloupec se odvozuje od pořadí, v jakém ta hodnota přišla.
Tu metodu, která jenom emituje příslušný signál ale voláš z worker vlákna.To znamená, že se při zpracovávání signálu handler podívá, zda příjemce náleží stejnému vláknu, ze kterého byl zavolán signál a podle toho se zachová.Koukám na to a je mi divné, že to vůbec funguje, žádné chyby ani varování... Signály i sloty jsou v
QtRelationalReaderStringHadler
aconnect()
dělám taky v této třídě (ovšem ještě v hlavním vlákně). Pak se ve vlákně na pozadí pustí tenreader->process()
, který provoláváQtRelationalReaderStringHadler
ovšem jako normální metody -- a až uvnitř těchto metod to jde přes signál/slot (ale v rámci toho samého vlákna) a ze slotů se provolávátarget->startRelation(name, attributes)
ovšem už zase jako normální metoda. A to tentarget
patří do jiného vlákna. Asi si s tím budu muset pohrát v debuggeru, protože takhle mi přijde, že to nemůže fungovat (ale na první pohled to funguje).
QObject::activate
se tedy volá z worker vlákna. Pokud jsi instanci QtRelationalReaderStringHandler
u vytvořil z hlavního vlákna, Qt to vidí a místo přímého zavolání slotu provedou queued zavolání z hlavního vlákna. Kdybys ten QtRelationalReaderStringHandler
vytvořil z worker vlákna, už by to nechodilo.
Teď bych nerad kecal, ale pokud se provádí queued zavolání slotu, všechny jeho parametry ze zkopírují, takže původní objekty klidně mohou být zničeny. Proto taky musí mít vše, co zaregistruješ pomocí makraV tom
void attribute(const string_t& value)
se předává pouze reference ahandler
si má tu hodnotu přečíst/zkopírovat, ale nepatří mu -- jakmile skončí tahle metoda, tak ten, kdo ji volal, tu hodnotu smaže (resp. skončí rozsah její platnosti). Tady by mi ASan nadával, že používám již uvolněnou paměť, což se neděje. To nasvědčuje tomu, že ty signály/sloty jsou v mém případě synchronní (protože jsou v rámci jednoho vlákna/objektu). A celé to asi funguje jen díky tomu, že Qt se (náhodou) nerozbije, i když manipuluji s jeho GUI komponentami z jiného vlákna, než bych měl.
Q_DECLARE_METATYPE
implicitiní defaultní konstruktor i copy konstruktor. Pokus o manipulaci s GUI objekty odjinud než z hlavního vlákna by velmi pravděpodobně aspoň vypsal varování do konzole.
Já bych ty argumenty klidně předával referencí, IMHO si tím ušetříš dvě zbytečná volání copy konstruktoru. Můžeš si to ověřit tím, že v copy konstruktoru necháš něco vypsat. Pořadí vykonání slotů by i při queued aktivaci zachováno být mělo.Předělám to zatím tím prvním způsobem -- tzn. přesunu sloty do
RelpipeChartMainWindow
-- tím by to mělo být asynchronní -- a zároveň změním jejich signatury, aby se pracovalo s kopií a ne referencí. BTW: když je to asynchronní, je zaručené pořadí? Protože na tom tady záleží -- ty atributy se plní do tabulky a řádek/sloupec se odvozuje od pořadí, v jakém ta hodnota přišla.
Kdyz napises emit signal_endOfPipe(); tak je to uplne obycejny volani funkce (ten emit nic neni). Tu funkci ti vygeneruje MOC, jen se podiv do nejakeho moc_*.cpp souboru, tam uvidis jak presne to funguje.
Me dost pomohl k pochopeni tenhle clanek. Runtime se da delat vsechno mozny, napr. si z QMetaObject vytahnout seznam signalu/slotu a volat je pak pres int/string ID a list variant argumentu (ja mam takhle reseny remote method invocation). Otazka je, jestli pro tvy reseni neni lepsi to proste napsat rucne, nez si pridelavat zbytecny runtime overhead.
Kdyz napises emit signal_endOfPipe(); tak je to uplne obycejny volani funkce (ten emit nic neni).
Podle toho, co jsem četl v dokumentaci a na SO, takt to takhle funguje jen, když děláš emit ve stejném vlákně ve kterém to daný slot přijímá.
Navíc tam píší, že mezi více vlákny je to volání asynchronní, takže se vůbec divím, že mi to tak pěkně funguje a ASan mi nehlásí žádné chyby
Asi přes ty signály/sloty budu všude předávat radši kopii hodnoty než referenci...
# define Q_EMIT #ifndef QT_NO_EMIT # define emit #endifv src/corelib/kernel/qobjectdefs.h. Emit opravdu nic nedela, je to prazdny define.
Ano, má to číst postupně. Šlo mi o to, aby se uživateli zobrazily první řádky prvních tabulek, hned jak přijdou jejich data, a uživatel s nimi mohl pracovat, i když se ještě průběžně načítají další.
Pouštěl jsem si to přes:
(for x in `seq 5`; do sleep 1; relpipe-in-fstab; done) | pv --quiet --rate-limit 50 | relpipe-out-chart.qt
a tam je hezky vidět, jak data postupně naskakují v jednotlivých buňkách a dá se s tím GUI současně pracovat.
Nakonec jsem to vyřešil tím, že jsem proxy zjednodušil:
class QtRelationalReaderStringHadler : public QObject, public RelationalReaderStringHadler { Q_OBJECT public: QtRelationalReaderStringHadler(QObject* parent) : QObject(parent) { } virtual ~QtRelationalReaderStringHadler() { } virtual void startRelation(string_t name, std::vector<AttributeMetadata> attributes) override { emit startRelationReceived(name, attributes); } virtual void attribute(const string_t& value) override { emit attributeReceived(value); }; virtual void endOfPipe() override { emit endOfPipeReceived(); }; signals: void startRelationReceived(const string_t name, std::vector<AttributeMetadata> attributes); void attributeReceived(const string_t value); void endOfPipeReceived(); };
Napojení signálů a slotů dělám v hlavním vlákně (původně to bylo v té proxy):
QObject::connect(&handler, &QtRelationalReaderStringHadler::startRelationReceived, &window, &RelpipeChartMainWindow::startRelation, Qt::ConnectionType::QueuedConnection); QObject::connect(&handler, &QtRelationalReaderStringHadler::attributeReceived, &window, &RelpipeChartMainWindow::attribute, Qt::ConnectionType::QueuedConnection); QObject::connect(&handler, &QtRelationalReaderStringHadler::endOfPipeReceived, &window, &RelpipeChartMainWindow::endOfPipe, Qt::ConnectionType::QueuedConnection);
A v GUI třídě implementuji sloty:
void RelpipeChartMainWindow::startRelation(const string_t name, std::vector<AttributeMetadata> attributes) { setStatusMessage(L"Reading relation: " + name); attributeCounter = 0; QSplitter* splitter = new QSplitter(Qt::Orientation::Vertical, tabs); currentTable = new QTableWidget(0, attributes.size(), splitter); QStringList headers; for (AttributeMetadata a : attributes) headers << QString::fromWCharArray(a.getAttributeName().c_str()); currentTable->setHorizontalHeaderLabels(headers); currentTable->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::ResizeToContents); // TODO: chart splitter->addWidget(new QPushButton("here will be the chart", splitter)); splitter->addWidget(currentTable); int index = tabs->addTab(splitter, QString::fromWCharArray(name.c_str())); if (tabs->count() == 2) tabs->setCurrentIndex(index); // switch to the first relation (first tab is Options tab) tabs->setTabIcon(index, QIcon::fromTheme("application-vnd.oasis.opendocument.spreadsheet")); } void RelpipeChartMainWindow::attribute(const string_t value) { // TODO: draw chart integer_t column = attributeCounter % currentTable->columnCount(); integer_t row = attributeCounter / currentTable->columnCount(); if (row >= currentTable->rowCount()) currentTable->insertRow(currentTable->rowCount()); currentTable->setItem(row, column, new QTableWidgetItem(QString::fromWCharArray(value.c_str()))); attributeCounter++; } void RelpipeChartMainWindow::setStatusMessage(string_t message) { status->setText(QString::fromWCharArray(message.c_str())); } void RelpipeChartMainWindow::endOfPipe() { setStatusMessage(L"Reading successfully finished."); }
Tu proxy bych nejradši zredukoval ještě víc, ale ty metody dané rozhraním RelationalReaderStringHadler
jsou virtuální a nešlo mi je použít jako signály, takže jsou to normální metody a signál se z nich jen emituje, místo aby sama ta metoda byla signálem. Ale to už není tolik kódu navíc, takže jsem s tím už víceméně spokojený.
Nedokáži posoudit, jestli by to přes QFuture bylo jednodušší/lepší...
Tiskni
Sdílej: