diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 103e054e4..df0f35563 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -9,7 +9,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - uses: actions/checkout@v7 diff --git a/common/coroutines.h b/common/coroutines.h new file mode 100644 index 000000000..32d7e881f --- /dev/null +++ b/common/coroutines.h @@ -0,0 +1,29 @@ +/* coroutines.h + * + * Copyright (c) 2026 Andrey Kutejko + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#ifndef _COROUTINES_H_ +#define _COROUTINES_H_ + +#include +#include + +template using task = beman::execution::task; + +#endif diff --git a/common/meson.build b/common/meson.build index 67cd3881c..8941ee337 100644 --- a/common/meson.build +++ b/common/meson.build @@ -1,4 +1,5 @@ common_sources = files( + 'racquireasync.cc', 'rconfiguration.cc', 'rinstallprogress.cc', 'rpackage.cc', @@ -23,7 +24,7 @@ libsynaptic = static_library( 'synaptic', common_sources, cpp_args: rpm_compile_args, - dependencies: common_deps, + dependencies: common_deps + [task_dep], include_directories: [root_inc, common_inc], ) diff --git a/common/racquireasync.cc b/common/racquireasync.cc new file mode 100644 index 000000000..4ea0ccb2e --- /dev/null +++ b/common/racquireasync.cc @@ -0,0 +1,177 @@ +#include "config.h" // IWYU pragma: associated + +#include "racquireasync.h" + +#include + +// Fetcher message + +struct MessageMediaChange +{ + std::string media; + std::string drive; +}; +struct MessageIMSHit +{ + pkgAcquire::ItemDesc item; +}; +struct MessageFetch +{ + pkgAcquire::ItemDesc item; +}; +struct MessageDone +{ + pkgAcquire::ItemDesc item; +}; +struct MessageFail +{ + pkgAcquire::ItemDesc item; +}; +struct MessageStart +{}; +struct MessageStop +{}; +struct MessageFinish +{ + std::any result; +}; +using FetcherMessage = std::variant; + +template class EventQueue +{ + public: + void push(T event) + { + std::lock_guard lock(mutex_); + queue_.push(std::move(event)); + } + + std::optional poll() + { + std::lock_guard lock(mutex_); + + if (queue_.empty()) + return std::nullopt; + + auto event = std::move(queue_.front()); + queue_.pop(); + return event; + } + + T pop() + { + while (true) { + if (auto answer = poll()) { + return answer.value(); + } else { + usleep(100000); + } + } + } + + private: + std::mutex mutex_; + std::queue queue_; +}; + +class pkgAcquireStatusQueue : public pkgAcquireStatus +{ + public: + virtual bool MediaChange(std::string Media, std::string Drive) override + { + messageQueue.push(MessageMediaChange{Media, Drive}); + return answerQueue.pop(); + } + virtual void IMSHit(pkgAcquire::ItemDesc &Itm) override + { + messageQueue.push(MessageIMSHit{Itm}); + } + virtual void Fetch(pkgAcquire::ItemDesc &Itm) override + { + messageQueue.push(MessageFetch{Itm}); + } + virtual void Done(pkgAcquire::ItemDesc &Itm) override + { + messageQueue.push(MessageDone{Itm}); + } + virtual void Fail(pkgAcquire::ItemDesc &Itm) override + { + messageQueue.push(MessageFail{Itm}); + } + virtual void Start() override + { + messageQueue.push(MessageStart{}); + } + virtual void Stop() override + { + messageQueue.push(MessageStop{}); + } + + explicit pkgAcquireStatusQueue(EventQueue &messageQueue, + EventQueue &answerQueue) + : messageQueue(messageQueue), answerQueue(answerQueue) + {} + + private: + EventQueue &messageQueue; + EventQueue &answerQueue; +}; + +task runWithStatusAsyncAny( + std::function &&body, + RPkgAcquireStatusAsync *status) +{ + EventQueue messageQueue; + EventQueue answerQueue; + pkgAcquireStatusQueue statusQueue{messageQueue, answerQueue}; + + std::thread worker([&]() { + auto result = body(statusQueue); + messageQueue.push(FetcherMessage{MessageFinish{result}}); + }); + + while (true) { + if (auto event = messageQueue.poll()) { + std::optional result = co_await std::visit( + [status, + &answerQueue](auto &&event) -> task> { + using T = std::decay_t; + if constexpr (std::is_same_v) { + co_return std::optional{event.result}; + } + if constexpr (std::is_same_v) { + bool result = + co_await status->MediaChange(event.media, event.drive); + answerQueue.push(result); + } else if constexpr (std::is_same_v) { + co_await status->IMSHit(event.item); + } else if constexpr (std::is_same_v) { + co_await status->Fetch(event.item); + } else if constexpr (std::is_same_v) { + co_await status->Done(event.item); + } else if constexpr (std::is_same_v) { + co_await status->Fail(event.item); + } else if constexpr (std::is_same_v) { + co_await status->Start(); + } else if constexpr (std::is_same_v) { + co_await status->Stop(); + } + co_return std::optional{}; + }, + event.value()); + if (result) { + worker.join(); + co_return result.value(); + } + } else { + // co_await sleep_ms{100}; + } + } +} diff --git a/common/racquireasync.h b/common/racquireasync.h new file mode 100644 index 000000000..ef2f4cb64 --- /dev/null +++ b/common/racquireasync.h @@ -0,0 +1,57 @@ +#pragma once + +#include "coroutines.h" +#include +#include +#include + +class RPkgAcquireStatusAsync +{ + public: + [[nodiscard]] virtual task MediaChange(std::string Media, + std::string Drive) = 0; + [[nodiscard]] virtual task IMSHit(pkgAcquire::ItemDesc &Itm) = 0; + [[nodiscard]] virtual task Fetch(pkgAcquire::ItemDesc &Itm) = 0; + [[nodiscard]] virtual task Done(pkgAcquire::ItemDesc &Itm) = 0; + [[nodiscard]] virtual task Fail(pkgAcquire::ItemDesc &Itm) = 0; + [[nodiscard]] virtual task Start() = 0; + [[nodiscard]] virtual task Stop() = 0; +}; + +[[nodiscard]] task runWithStatusAsyncAny( + std::function &&body, + RPkgAcquireStatusAsync *status); + +template +[[nodiscard]] task runWithStatusAsync( + std::function &&body, + RPkgAcquireStatusAsync *status) +{ + auto anyBody = [&](pkgAcquireStatus &status) -> std::any { + return body(status); + }; + auto result = co_await runWithStatusAsyncAny(std::move(anyBody), status); + co_return std::any_cast(result); +} + +[[nodiscard]] inline task acquireRunAsync( + pkgAcquire *acquire, + RPkgAcquireStatusAsync *status, + int PulseInterval = 500000) +{ + pkgAcquire::RunResult result = + co_await runWithStatusAsync( + [acquire, + PulseInterval](pkgAcquireStatus &status) -> pkgAcquire::RunResult { + acquire->SetLog(&status); + pkgAcquire::RunResult result = acquire->Run( +#ifndef HAVE_RPM + PulseInterval +#endif + ); + acquire->SetLog(nullptr); + return result; + }, + status); + co_return result; +} diff --git a/common/rinstallprogress.cc b/common/rinstallprogress.cc index bbbd1838f..863409cb7 100644 --- a/common/rinstallprogress.cc +++ b/common/rinstallprogress.cc @@ -86,7 +86,7 @@ std::optional RInstallProgress::start( res = pm->DoInstallPreFork(); if (res == pkgPackageManager::Failed) - return res; + co_return res; /* * This will make a pipe from where we can read child's output diff --git a/common/rinstallprogress.h b/common/rinstallprogress.h index aeeb5f866..3db5d23e0 100644 --- a/common/rinstallprogress.h +++ b/common/rinstallprogress.h @@ -31,6 +31,8 @@ #include #include +#include "coroutines.h" + class RInstallProgress { protected: @@ -64,17 +66,27 @@ class RInstallProgress int numPackages = 0, int numPackagesTotal = 0); virtual std::optional poll(); - virtual void finish() - {} + [[nodiscard]] virtual task finish() + { + co_return; + } - virtual void startUpdate() - {} - virtual void updateInterface() - {} - virtual void finishUpdate() - {} + [[nodiscard]] virtual task startUpdate() + { + co_return; + } + [[nodiscard]] virtual task updateInterface() + { + co_return; + } + [[nodiscard]] virtual task finishUpdate() + { + co_return; + } RInstallProgress() : _donePackagesTotal(0), _numPackagesTotal(0), _updateFinished(false) {} + virtual ~RInstallProgress() + {} }; diff --git a/common/rpackage.cc b/common/rpackage.cc index 925043468..45cc635d9 100644 --- a/common/rpackage.cc +++ b/common/rpackage.cc @@ -960,7 +960,9 @@ void RPackage::setRemove(bool purge) _lister->notifyChange(this); } -string RPackage::getScreenshotFile(pkgAcquire *fetcher, bool thumb) +task RPackage::getScreenshotFile(pkgAcquire *fetcher, + RPkgAcquireStatusAsync *status, + bool thumb) { string descr("Screenshot for "); descr += name(); @@ -985,12 +987,13 @@ string RPackage::getScreenshotFile(pkgAcquire *fetcher, bool thumb) new pkgAcqFile( fetcher, uri, HashStringList(), 0, descr, name(), "", filename); - fetcher->Run(); + co_await acquireRunAsync(fetcher, status); - return filename; + co_return filename; } -string RPackage::getChangelogFile(pkgAcquire *fetcher) +task RPackage::getChangelogFile(pkgAcquire *fetcher, + RPkgAcquireStatusAsync *status) { string descr("Changelog for "); descr += name(); @@ -1009,7 +1012,7 @@ string RPackage::getChangelogFile(pkgAcquire *fetcher) ofstream out(filename.c_str()); - if (fetcher->Run() == pkgAcquire::Failed) { + if (co_await acquireRunAsync(fetcher, status) == pkgAcquire::Failed) { out << "Failed to download the list of changes. " << endl; out << "Please check your Internet connection." << endl; // FIXME: Need to dequeue the item @@ -1029,7 +1032,7 @@ string RPackage::getChangelogFile(pkgAcquire *fetcher) }; out.close(); - return filename; + co_return filename; } string RPackage::getCandidateOriginStr() diff --git a/common/rpackage.h b/common/rpackage.h index 8aaaaf4a3..ab081346f 100644 --- a/common/rpackage.h +++ b/common/rpackage.h @@ -30,6 +30,8 @@ #include "config.h" // IWYU pragma: associated +#include "coroutines.h" +#include "racquireasync.h" #include "i18n.h" #include @@ -156,9 +158,14 @@ class RPackage bool isMultiArchDuplicate(); // get changelog file from the debian server - std::string getChangelogFile(pkgAcquire *fetcher); + [[nodiscard]] task getChangelogFile( + pkgAcquire *fetcher, + RPkgAcquireStatusAsync *status); // get screenshot file from the debian server - std::string getScreenshotFile(pkgAcquire *fetcher, bool thumb = true); + [[nodiscard]] task getScreenshotFile( + pkgAcquire *fetcher, + RPkgAcquireStatusAsync *status, + bool thumb = true); std::vector provides(); diff --git a/common/rpackagelister.cc b/common/rpackagelister.cc index 246a1ade8..9e2d5a825 100644 --- a/common/rpackagelister.cc +++ b/common/rpackagelister.cc @@ -31,6 +31,7 @@ #include "i18n.h" #include "raptoptions.h" +#include "racquireasync.h" #include "rcacheactor.h" #include "rconfiguration.h" #include "rinstallprogress.h" @@ -69,13 +70,16 @@ #include #include #include +#include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -1468,8 +1472,8 @@ bool RPackageLister::getDownloadUris(vector &uris) return true; } -bool RPackageLister::commitChanges(pkgAcquireStatus *status, - RInstallProgress *iprog) +task RPackageLister::commitChanges(RPkgAcquireStatusAsync *status, + RInstallProgress *iprog) { FileFd lock; int numPackages = 0; @@ -1479,17 +1483,17 @@ bool RPackageLister::commitChanges(pkgAcquireStatus *status, _updating = true; if (!lockPackageCache(lock)) - return false; + co_return false; if (_config->FindB("Synaptic::Log::Changes", true)) makeCommitLog(); - pkgAcquire fetcher(status); + pkgAcquire fetcher; assert(_cache->list() != NULL); // Read the source list if (_cache->list()->ReadMainList() == false) { - _userDialog->warning( + co_await _userDialog->warning( _("Ignoring invalid record(s) in sources.list file!")); } @@ -1497,22 +1501,18 @@ bool RPackageLister::commitChanges(pkgAcquireStatus *status, if (!rPM->GetArchives(&fetcher, _cache->list(), _records) || _error->PendingError()) { - return false; + co_return false; } // ripped from apt-get while (1) { bool Transient = false; -#ifdef HAVE_RPM - if (fetcher.Run() == pkgAcquire::Failed) { - return false; - } -#else - if (fetcher.Run(50000) == pkgAcquire::Failed) { - return false; - } -#endif + auto result = co_await acquireRunAsync(&fetcher, status, 50000); + if (result == pkgAcquire::Failed) + co_return false; + else + break; string serverError; @@ -1557,14 +1557,14 @@ bool RPackageLister::commitChanges(pkgAcquireStatus *status, if (_config->FindB("Volatile::Download-Only", false)) { _updating = false; - return !Failed; + co_return !Failed; } if (Failed) { string message; if (Transient || numPackages == 0) { - return false; + co_return false; } message = _("Some of the packages could not be retrieved from the " @@ -1573,14 +1573,14 @@ bool RPackageLister::commitChanges(pkgAcquireStatus *status, message += "(" + serverError + ")\n"; message += _("Do you want to continue, ignoring these packages?"); - if (!_userDialog->confirm(message.c_str())) { - return false; + if (!co_await _userDialog->confirm(message.c_str())) { + co_return false; } } // Try to deal with missing package files if (Failed == true && rPM->FixMissing() == false) { _error->Error(_("Unable to correct missing packages")); - return false; + co_return false; } // need this so that we first fetch everything and then install (for CDs) if (Transient == false || @@ -1595,16 +1595,16 @@ bool RPackageLister::commitChanges(pkgAcquireStatus *status, std::optional Res; Res = iprog->start(rPM.get(), numPackages, numPackagesTotal); if (!Res.has_value()) { - iprog->startUpdate(); + co_await iprog->startUpdate(); while (!(Res = iprog->poll()).has_value()) { - iprog->updateInterface(); + co_await iprog->updateInterface(); } - iprog->finishUpdate(); + co_await iprog->finishUpdate(); } _system->LockInner(); if (*Res == pkgPackageManager::Failed || _error->PendingError()) { if (Transient == false) { - return false; + co_return false; } Ret = false; // TODO: We must not discard errors here. The right @@ -1622,7 +1622,7 @@ bool RPackageLister::commitChanges(pkgAcquireStatus *status, fetcher.Shutdown(); if (!rPM->GetArchives(&fetcher, _cache->list(), _records)) { - return false; + co_return false; } } @@ -1635,7 +1635,7 @@ bool RPackageLister::commitChanges(pkgAcquireStatus *status, if (_config->FindB("Synaptic::Log::Changes", true)) writeCommitLog(); - return Ret; + co_return Ret; } void RPackageLister::writeCommitLog() diff --git a/common/rpackagelister.h b/common/rpackagelister.h index 8bb31d11e..aaf3b2a62 100644 --- a/common/rpackagelister.h +++ b/common/rpackagelister.h @@ -28,7 +28,9 @@ #include "rpackagecache.h" #include "rpackagestatus.h" +#include "coroutines.h" +#include #include #include #include @@ -58,6 +60,7 @@ class RPackageViewSearch; class RUserDialog; class pkgAcquireStatus; class pkgRecords; +class RPkgAcquireStatusAsync; class RPackageObserver { @@ -333,7 +336,8 @@ class RPackageLister bool distUpgrade(); bool cleanPackageCache(bool forceClean = false); bool updateCache(pkgAcquireStatus *status, std::string &error); - bool commitChanges(pkgAcquireStatus *status, RInstallProgress *iprog); + [[nodiscard]] task commitChanges(RPkgAcquireStatusAsync *status, + RInstallProgress *iprog); // some information bool getDownloadUris(std::vector &uris); diff --git a/common/ruserdialog.cc b/common/ruserdialog.cc index 5d102ef6c..76e77d6f3 100644 --- a/common/ruserdialog.cc +++ b/common/ruserdialog.cc @@ -27,10 +27,10 @@ #include #include "ruserdialog.h" -bool RUserDialog::showErrors() +task RUserDialog::showErrors() { if (_error->empty()) - return false; + co_return false; while (!_error->empty()) { std::string message; @@ -42,13 +42,13 @@ bool RUserDialog::showErrors() if (!message.empty()) { if (iserror) - error(message.c_str()); + co_await error(message.c_str()); else - warning(message.c_str()); + co_await warning(message.c_str()); } } - return true; + co_return true; } // vim:ts=3:sw=3:et diff --git a/common/ruserdialog.h b/common/ruserdialog.h index 8cb6f945c..8c68f95e0 100644 --- a/common/ruserdialog.h +++ b/common/ruserdialog.h @@ -25,6 +25,8 @@ #include "config.h" // IWYU pragma: associated +#include "coroutines.h" + class RUserDialog { public: @@ -37,31 +39,39 @@ class RUserDialog enum DialogType { DialogInfo, DialogWarning, DialogQuestion, DialogError }; - virtual bool message(const char *msg, - DialogType dialog = DialogInfo, - ButtonsType buttons = ButtonsDefault, - bool defaultResponse = true) = 0; + [[nodiscard]] virtual task message( + const char *msg, + DialogType dialog = DialogInfo, + ButtonsType buttons = ButtonsDefault, + bool defaultResponse = true) = 0; - virtual bool confirm(const char *msg, bool defaultResponse = true) + [[nodiscard]] virtual task confirm(const char *msg, + bool defaultResponse = true) { - return message(msg, DialogQuestion, ButtonsYesNo, defaultResponse); + co_return co_await message( + msg, DialogQuestion, ButtonsYesNo, defaultResponse); } - virtual bool proceed(const char *msg, bool defaultResponse = true) + [[nodiscard]] virtual task proceed(const char *msg, + bool defaultResponse = true) { - return message(msg, DialogInfo, ButtonsOkCancel, defaultResponse); + co_return co_await message( + msg, DialogInfo, ButtonsOkCancel, defaultResponse); } - virtual bool warning(const char *msg, bool nocancel = true) + [[nodiscard]] virtual task warning(const char *msg, + bool nocancel = true) { - return nocancel ? message(msg, DialogWarning) - : message(msg, DialogWarning, ButtonsOkCancel, false); + if (nocancel) + co_return co_await message(msg, DialogWarning); + else + co_return co_await message(msg, DialogWarning, ButtonsOkCancel, false); } - virtual void error(const char *msg) + [[nodiscard]] virtual task error(const char *msg) { - message(msg, DialogError); + co_await message(msg, DialogError); } - virtual bool showErrors(); + [[nodiscard]] virtual task showErrors(); }; diff --git a/debian/control b/debian/control index 3f1ee8934..2d227abfa 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: synaptic Section: admin Priority: optional Maintainer: Michael Vogt -Build-Depends: debhelper-compat (= 12), gettext, meson, ninja-build, libapt-pkg-dev, libgtk-3-dev, libvte-2.91-dev, libpolkit-gobject-1-dev, libsm-dev, lsb-release, libxapian-dev, xmlto +Build-Depends: debhelper-compat (= 12), gettext, meson, ninja-build, cmake, libapt-pkg-dev, libgtk-4-dev, libvte-2.91-gtk4-dev, libpolkit-gobject-1-dev, libsm-dev, lsb-release, libxapian-dev, libtbb-dev, xmlto Build-Conflicts: librpm-dev Standards-Version: 4.5.0 Vcs-Git: https://github.com/mvo5/synaptic.git diff --git a/gtk/gsynaptic.cc b/gtk/gsynaptic.cc index af52491c2..0e62471e3 100644 --- a/gtk/gsynaptic.cc +++ b/gtk/gsynaptic.cc @@ -60,6 +60,9 @@ static gint applicationHandleLocalOptions(GApplication *app, gpointer user_data); static void applicationStartup(GApplication *app, gpointer user_data); static void applicationActivate(GApplication *app, gpointer user_data); +[[nodiscard]] static task coApplicationActivate( + RGMainWindow *mainWindow, + RPackageLister *packageLister); static GOptionEntry option_entries[] = { @@ -212,13 +215,13 @@ static void SetLanguages() _config->Set("Volatile::Languages", LangList); } -void welcome_dialog(RGMainWindow *mainWindow) +[[nodiscard]] task welcome_dialog(RGMainWindow *mainWindow) { // show welcome dialog if (_config->FindB("Synaptic::showWelcomeDialog", true) && !_config->FindB("Volatile::Upgrade-Mode", false)) { RGGtkBuilderUserDialog dia(mainWindow); - dia.run("welcome"); + co_await dia.co_run("welcome"); GtkWidget *cb = GTK_WIDGET( gtk_builder_get_object(dia.getGtkBuilder(), "checkbutton_show_again")); assert(cb); @@ -311,10 +314,10 @@ pid_t TestLock(string File) // *) if not, send signal // 2. check if we can get a /var/lib/dpkg/lock // *) if not, show message and fail -void check_and_aquire_lock() +[[nodiscard]] static task check_and_aquire_lock() { if (getuid() != 0) - return; + co_return; GtkWidget *dia; gchar *msg = NULL; @@ -351,8 +354,8 @@ void check_and_aquire_lock() NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, NULL); gtk_message_dialog_set_markup(GTK_MESSAGE_DIALOG(dia), msg); - gtk_dialog_run(GTK_DIALOG(dia)); - gtk_widget_destroy(dia); + co_await co_run_dialog(GTK_DIALOG(dia)); + gtk_window_destroy(GTK_WINDOW(dia)); } g_free(msg); @@ -378,7 +381,7 @@ void check_and_aquire_lock() NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, NULL); gtk_message_dialog_set_markup(GTK_MESSAGE_DIALOG(dia), msg); - gtk_dialog_run(GTK_DIALOG(dia)); + co_await co_run_dialog(GTK_DIALOG(dia)); g_free(msg); exit(1); } @@ -486,19 +489,19 @@ static gint applicationHandleLocalOptions(GApplication *app, static void applicationStartup(GApplication *app, gpointer user_data) { - gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(), - PACKAGE_DATA_DIR "/icons"); + gtk_icon_theme_add_search_path( + gtk_icon_theme_get_for_display(gdk_display_get_default()), + PACKAGE_DATA_DIR "/icons"); if (!RInitConfiguration("synaptic.conf")) { - RGUserDialog userDialog; - userDialog.showErrors(); - exit(1); + start_task([app]() -> task { + RGUserDialog userDialog; + co_await userDialog.showErrors(); + exit(1); + }); + return; } - // check if there is another application runing and - // act accordingly - check_and_aquire_lock(); - // read configuration early _roptions->restore(); @@ -507,6 +510,11 @@ static void applicationStartup(GApplication *app, gpointer user_data) // init the static pkgStatus class. this loads the status pixmaps // and colors RGPackageStatus::pkgStatus.init(); + + start_task([]() -> task { + // check if there is another application runing and act accordingly + co_await check_and_aquire_lock(); + }); } static void applicationActivate(GApplication *app, gpointer user_data) @@ -516,9 +524,6 @@ static void applicationActivate(GApplication *app, gpointer user_data) return; } - bool UpdateMode = _config->FindB("Volatile::Update-Mode", false); - bool NonInteractive = _config->FindB("Volatile::Non-Interactive", false); - RPackageLister *packageLister = new RPackageLister(); RGMainWindow *mainWindow = new RGMainWindow(GTK_APPLICATION(app), packageLister, "main"); @@ -560,9 +565,20 @@ static void applicationActivate(GApplication *app, gpointer user_data) else mainWindow->show(); - RGFlushInterface(); + start_task([mainWindow, packageLister]() -> task { + co_await coApplicationActivate(mainWindow, packageLister); + }); +} + +[[nodiscard]] static task coApplicationActivate( + RGMainWindow *mainWindow, + RPackageLister *packageLister) +{ + bool UpdateMode = _config->FindB("Volatile::Update-Mode", false); + bool NonInteractive = _config->FindB("Volatile::Non-Interactive", false); - mainWindow->setInterfaceLocked(true); + co_await RGFlushInterface(); + co_await mainWindow->setInterfaceLocked(true); string cd_mount_point = _config->Find("Volatile::AddCdrom-Mode", ""); if (!cd_mount_point.empty()) { @@ -578,11 +594,11 @@ static void applicationActivate(GApplication *app, gpointer user_data) if (!UpdateMode) { mainWindow->setTreeLocked(true); if (!packageLister->openCache()) { - mainWindow->showErrors(); + co_await mainWindow->showErrors(); exit(1); } - mainWindow->restoreState(); - mainWindow->showErrors(); + co_await mainWindow->restoreState(); + co_await mainWindow->showErrors(); mainWindow->setTreeLocked(false); } @@ -608,19 +624,20 @@ static void applicationActivate(GApplication *app, gpointer user_data) packageLister->registerObserver(mainWindow); } - mainWindow->setInterfaceLocked(false); + co_await mainWindow->setInterfaceLocked(false); if (UpdateMode) { mainWindow->cbUpdateClicked(nullptr, nullptr, mainWindow); mainWindow->setTreeLocked(true); if (!packageLister->openCache()) { - mainWindow->showErrors(); + co_await mainWindow->showErrors(); exit(1); } - mainWindow->restoreState(); + co_await mainWindow->restoreState(); mainWindow->setTreeLocked(false); - mainWindow->showErrors(); - mainWindow->changeView(PACKAGE_VIEW_STATUS, _("Installed (upgradable)")); + co_await mainWindow->showErrors(); + co_await mainWindow->changeView(PACKAGE_VIEW_STATUS, + _("Installed (upgradable)")); } if (_config->FindB("Volatile::TestMeHarder", false)) { @@ -629,7 +646,7 @@ static void applicationActivate(GApplication *app, gpointer user_data) while (true) { mainWindow->cbUpdateClicked(nullptr, nullptr, mainWindow); - mainWindow->changeView(PACKAGE_VIEW_STATUS, _("Installed")); + co_await mainWindow->changeView(PACKAGE_VIEW_STATUS, _("Installed")); GtkTreePath *p = gtk_tree_path_new_from_string("0"); mainWindow->cbPackageListRowActivated(NULL, p, NULL, mainWindow); @@ -643,7 +660,7 @@ static void applicationActivate(GApplication *app, gpointer user_data) if (_config->FindB("Volatile::Upgrade-Mode", false) || _config->FindB("Volatile::DistUpgrade-Mode", false)) { mainWindow->cbUpgradeClicked(nullptr, nullptr, mainWindow); - mainWindow->changeView(PACKAGE_VIEW_CUSTOM, _("Marked Changes")); + co_await mainWindow->changeView(PACKAGE_VIEW_CUSTOM, _("Marked Changes")); } if (_config->FindB("Volatile::TaskWindow", false)) { @@ -652,13 +669,13 @@ static void applicationActivate(GApplication *app, gpointer user_data) string filter = _config->Find("Volatile::initialFilter", ""); if (filter != "") - mainWindow->changeView(PACKAGE_VIEW_CUSTOM, filter); + co_await mainWindow->changeView(PACKAGE_VIEW_CUSTOM, filter); if (NonInteractive) { mainWindow->cbProceedClicked(nullptr, nullptr, mainWindow); exit(0); } else { - welcome_dialog(mainWindow); + co_await welcome_dialog(mainWindow); gtk_widget_grab_focus(GTK_WIDGET(gtk_builder_get_object( mainWindow->getGtkBuilder(), "entry_fast_search"))); } diff --git a/gtk/gtkbuilder/dialog_authentication.ui b/gtk/gtkbuilder/dialog_authentication.ui index 0cf695fea..35516ac96 100644 --- a/gtk/gtkbuilder/dialog_authentication.ui +++ b/gtk/gtkbuilder/dialog_authentication.ui @@ -1,124 +1,77 @@ - - False - 5 HTTP authentication - normal - + - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK vertical + 12 + 12 + 12 + 12 12 - 6 - - - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK - end - - - _Cancel - True - False - False - True - - - False - True - 0 - - - - - _OK - True - False - False - True - - - False - True - 1 - - - - - False - True - end - 0 - - - True - False 12 6 - True - False Username 0 + + 0 + 0 + - - 0 - 0 - - - True - False - Password - 0 + + True + + 1 + 0 + - - 0 - 1 - - - True - True - True + + Password + 0 + + 0 + 1 + - - 1 - 0 - - True - True False True + + 1 + 1 + - - 1 - 1 - - - False - True - 1 - + + + _Cancel + False + True + + + + + _OK + False + True + + button2 button1 diff --git a/gtk/gtkbuilder/dialog_change_version.ui b/gtk/gtkbuilder/dialog_change_version.ui index c313e0ee1..a4f1ba2e6 100644 --- a/gtk/gtkbuilder/dialog_change_version.ui +++ b/gtk/gtkbuilder/dialog_change_version.ui @@ -1,153 +1,76 @@ - False - False - 6 False True - normal - + - True - False + 12 + 12 + 12 + 12 12 - - - True - False - end - - - _Cancel - True - True - True - False - True - - - False - True - 0 - - - - - _Force Version - True - True - True - False - True - - - False - True - 1 - - - - - False - True - end - 0 - - - True - False + horizontal 12 - True - False - 0 - 0 + start + start dialog-warning - 6 + 2 - - False - True - 0 - - True - False vertical 12 - True - False True True True - - False - False - 0 - - True - False - 10 + horizontal + 12 - True - False Force version: - - False - False - 0 - - True - False + True - - True - True - 1 - - - False - False - 1 - - - True - True - 1 - - - True - True - 0 - + + + _Cancel + False + True + + + + + _Force Version + False + True + + cancelbutton1 okbutton1 diff --git a/gtk/gtkbuilder/dialog_changelog.ui b/gtk/gtkbuilder/dialog_changelog.ui index 30b69e3ee..ba0417ff4 100644 --- a/gtk/gtkbuilder/dialog_changelog.ui +++ b/gtk/gtkbuilder/dialog_changelog.ui @@ -1,102 +1,51 @@ - 600 400 False - False - 6 True - normal - + - True - False vertical + 12 + 12 + 12 + 12 6 - - - True - False - end - - - _Close - True - True - True - False - True - - - False - True - 0 - - + + + Complete changelog of the latest version: + True + 0 - - False - True - end - 0 - - - True - False - vertical - 6 + + True + True - - True - False - Complete changelog of the latest version: - True - 0 + + 3 + 3 + False + 3 + 3 + False - - False - False - 0 - - - - - True - True - in - - - True - True - 3 - 3 - False - 3 - 3 - False - - - - - True - True - 1 - - - True - True - 0 - + + + _Close + False + True + + closebutton1 diff --git a/gtk/gtkbuilder/dialog_conffile.ui b/gtk/gtkbuilder/dialog_conffile.ui index 33d45649c..739d31ff8 100644 --- a/gtk/gtkbuilder/dialog_conffile.ui +++ b/gtk/gtkbuilder/dialog_conffile.ui @@ -1,169 +1,81 @@ - 600 False - False - 5 + False True - dialog - True - + - True - False vertical + 12 + 12 + 12 + 12 12 - - - True - False - end - - - True - True - True - True - True - False - _Keep - True - - - False - True - 0 - - - - - True - True - True - False - _Replace - True - - - False - True - 1 - - - - - False - True - end - 0 - - - True - False 12 + True - True - False - 0 - 0 + start + start dialog-question - 6 + 2 - - False - False - 0 - - - True - False - vertical - 12 - - - True - False - True - True - 0 - - - False - False - 0 - - + + True + True + 0 - - True - True - 1 - - - False - True - 1 - - - True - True - - - True - False - vertical + + True + start + + + True + True + 600 + 400 - - True - True - in - 600 - 400 - - - True - True - False - False - - + + False + False - - True - True - 0 - - - - - True - False + + + Difference between the files - + - - True - True - 2 - + + + False + _Keep + True + + + + + False + _Replace + True + + button2 button3 diff --git a/gtk/gtkbuilder/dialog_quit.ui b/gtk/gtkbuilder/dialog_quit.ui index 658a28ece..fbfc9756c 100644 --- a/gtk/gtkbuilder/dialog_quit.ui +++ b/gtk/gtkbuilder/dialog_quit.ui @@ -1,122 +1,54 @@ - False - False - 6 False - normal - + - True - False vertical - 12 - - - True - False - end - - - _Cancel - True - True - True - False - True - - - False - True - 0 - - - - - _Quit - True - True - True - False - True - - - False - True - 1 - - - - - False - True - end - 0 - - + 12 + 12 + 12 + 12 - True - False 12 - True - False - 0 - 0 dialog-warning - 6 + 2 - - True - True - 0 - - - True - False - vertical - 12 - - - True - False - <b><big>Quit and discard marked changes?</big></b> + + <b><big>Quit and discard marked changes?</big></b> There are still marked changes that have not yet been applied. They will get lost if you choose to quit 'Synaptic'. - True - True - 0 - 0 - - - False - False - 0 - - + True + True + 0 + 0 - - True - True - 1 - - - True - True - 0 - + + + _Cancel + False + True + + + + + _Quit + False + True + + cancelbutton1 okbutton1 diff --git a/gtk/gtkbuilder/dialog_task_descr.ui b/gtk/gtkbuilder/dialog_task_descr.ui index 4f46b65fa..2bdede9b2 100644 --- a/gtk/gtkbuilder/dialog_task_descr.ui +++ b/gtk/gtkbuilder/dialog_task_descr.ui @@ -1,89 +1,45 @@ - 600 400 False - False - 6 True - normal - + - True - False vertical + 12 + 12 + 12 + 12 6 - - - True - False - end - - - _Close - True - True - True - False - True - - - False - True - 0 - - - - - False - True - end - 0 - - - - True - False - vertical - 6 + + True + True - - True - True - in - - - True - True - 3 - 3 - False - word - 3 - 3 - False - - + + 3 + 3 + False + word + 3 + 3 + False - - True - True - 0 - - - True - True - 0 - + + + _Close + False + True + + closebutton1 diff --git a/gtk/gtkbuilder/dialog_unmet.ui b/gtk/gtkbuilder/dialog_unmet.ui index 3c2ae3da5..9b8ec1aa1 100644 --- a/gtk/gtkbuilder/dialog_unmet.ui +++ b/gtk/gtkbuilder/dialog_unmet.ui @@ -1,79 +1,37 @@ - False - False - 6 True - center 500 350 - normal - + - True - False vertical + 12 + 12 + 12 + 12 12 - - - True - False - end - - - _Close - True - True - True - False - True - - - False - True - 0 - - - - - False - True - end - 0 - - - True - False + 12 + True - True - False - 0 - 0 + start + start dialog-error - 6 + 2 - - False - False - 0 - - True - False vertical 12 - True - False <span weight="bold" size="larger">Could not mark all packages for installation or upgrade</span> The following packages have unresolvable dependencies. Make sure that all required repositories are added and enabled in the preferences. @@ -81,21 +39,13 @@ The following packages have unresolvable dependencies. Make sure that all requir True 0 - - False - False - 0 - - True - True - etched-in + True + True - True - True 3 3 2 @@ -105,28 +55,20 @@ The following packages have unresolvable dependencies. Make sure that all requir - - True - True - 1 - - - False - True - 1 - - - True - True - 0 - + + + _Close + False + True + + button2 diff --git a/gtk/gtkbuilder/dialog_update_failed.ui b/gtk/gtkbuilder/dialog_update_failed.ui index 6d629d38f..6872501ea 100644 --- a/gtk/gtkbuilder/dialog_update_failed.ui +++ b/gtk/gtkbuilder/dialog_update_failed.ui @@ -1,80 +1,38 @@ - False - False - 5 True - center 500 350 - normal - + - True - False vertical + 12 + 12 + 12 + 12 12 - - - True - False - end - - - _Close - True - True - True - False - True - - - False - True - 0 - - - - - False - True - end - 0 - - - True - False 12 + True - True - False - 0 - 0 + start + start dialog-error - 6 + 2 - - False - False - 0 - - True - False vertical 12 + True - True - False <big><b>Could not download all repository indexes</b></big> The repository may no longer be available or could not be contacted because of network problems. If available an older version of the failed index will be used. Otherwise the repository will be ignored. Check your network connection and ensure the repository address in the preferences is correct. @@ -83,21 +41,13 @@ The repository may no longer be available or could not be contacted because of n True 0 - - False - False - 0 - - True - True - etched-in + True + True - True - True 3 3 2 @@ -107,28 +57,20 @@ The repository may no longer be available or could not be contacted because of n - - True - True - 1 - - - True - True - 1 - - - True - True - 0 - + + + _Close + False + True + + button2 diff --git a/gtk/gtkbuilder/dialog_upgrade.ui b/gtk/gtkbuilder/dialog_upgrade.ui index cca881aa2..6f1b36a6e 100644 --- a/gtk/gtkbuilder/dialog_upgrade.ui +++ b/gtk/gtkbuilder/dialog_upgrade.ui @@ -1,108 +1,34 @@ - False - False - 6 False - dialog - + - True - False vertical + 12 + 12 + 12 + 12 12 - - - True - False - end - - - _Cancel - True - True - True - False - True - - - False - True - 0 - - - - - _Default Upgrade - True - True - True - False - True - - - False - True - 1 - - - - - _Smart Upgrade - True - True - True - True - False - True - - - False - True - 2 - - - - - False - True - end - 0 - - - True - False 12 - True - False - 0 - 0 + start + start dialog-question - 6 + 2 - - False - False - 0 - - True - False vertical 12 - True - False <b><big>Mark upgrades in a smart way?</big></b> The default upgrade method skips upgrades that would introduce conflicts or require installation of additional packages. @@ -114,45 +40,42 @@ The smart upgrade (dist-upgrade) attempts to resolve conflicts and to fulfil all True 0 - - False - True - 0 - Remember my answer for future upgrades - True - True False This behavior can be changed in the preferences later. True - 0 - True - - False - False - 1 - - - True - True - 1 - - - True - True - 0 - + + + _Cancel + False + True + + + + + _Default Upgrade + False + True + + + + + _Smart Upgrade + False + True + + button1 cancelbutton1 diff --git a/gtk/gtkbuilder/dialog_welcome.ui b/gtk/gtkbuilder/dialog_welcome.ui index 9e4c6a7d1..2ec8d0b5f 100644 --- a/gtk/gtkbuilder/dialog_welcome.ui +++ b/gtk/gtkbuilder/dialog_welcome.ui @@ -1,329 +1,204 @@ - False - False - 6 Quick Introduction False - dialog - - - True - False - vertical - 6 - - - True - False - end - - - _Close - True - True - True - True - False - True - - - False - True - 0 - - + + + horizontal + 12 + 12 + 12 + 12 + 12 + + + dialog-information + 2 + start - - False - True - end - 0 - - - True - False - 12 + + vertical + 6 + + + The software on your system is organized in so called <i>packages</i>. The package manager enables you to install, to upgrade or to remove software packages. + True + True + 0 + 0 + + + + + You should reload the package information regularly. Otherwise you could miss important security upgrades. + True + 0 + + - - True - False + + <b>Note:</b> Changes are not applied instantly. At first you have to mark all changes and then apply them. + True + True + 0 0 - dialog-information - 6 - - True - True - 0 - - - True - False - vertical - 12 + + You can mark packages for installation, upgrade or removal in several ways: + True + 0 + 0 + + + + + 6 + 6 + + + start + Select the package and choose the action from the 'Package' menu. + fill + True + 0 + 0 + + 1 + 0 + + + - - Show this dialog at startup - True - True - False - end - True + + start + Double click on the package name. + fill + True 0 - True + 0 + + 1 + 1 + - - False - False - end - 0 - - - True - False - vertical - 6 - - - True - False - The software on your system is organized in so called <i>packages</i>. The package manager enables you to install, to upgrade or to remove software packages. - True - True - 0 - 0 - - - False - True - 0 - - - - - True - False - You should reload the package information regularly. Otherwise you could miss important security upgrades. - True - 0 - - - False - False - 1 - - - - - True - False - <b>Note:</b> Changes are not applied instantly. At first you have to mark all changes and then apply them. - True - True - 0 - 0 - - - False - False - 2 - - - - - True - False - You can mark packages for installation, upgrade or removal in several ways: - True - 0 - 0 - - - False - False - 3 - - - - - True - False - - - True - False - start - Select the package and choose the action from the 'Package' menu. - fill - True - 0 - 0 - - - 1 - 0 - - - - - True - False - start - Double click on the package name. - fill - True - 0 - 0 - - - 1 - 1 - - - - - True - False - start - Choose the action from the context menu of the package. - fill - True - 0 - 0 - - - 1 - 2 - - - - - True - False - start - Click on the status icon to open a menu that contains all actions. - fill - True - 0 - 0 - - - 1 - 3 - - - - - True - False - start - start - 6 - - - fill - True - 0 - 0 - - - 0 - 0 - - - - - True - False - start - start - 6 - - - fill - True - 0 - 0 - - - 0 - 1 - - - - - True - False - start - start - 6 - - - fill - True - 0 - 0 - - - 0 - 2 - - - - - True - False - start - start - 6 - - - fill - True - 0 - 0 - - - 0 - 3 - - - - - True - True - 4 - - + + start + Choose the action from the context menu of the package. + fill + True + 0 + 0 + + 1 + 2 + - - False - True - 1 - + + + start + Click on the status icon to open a menu that contains all actions. + fill + True + 0 + 0 + + 1 + 3 + + + + + + start + start + - + fill + True + 0 + 0 + + 0 + 0 + + + + + + start + start + - + fill + True + 0 + 0 + + 0 + 1 + + + + + + start + start + - + fill + True + 0 + 0 + + 0 + 2 + + + + + + start + start + - + fill + True + 0 + 0 + + 0 + 3 + + + + + + + + Show this dialog at startup + False + True + 6 - - False - True - 1 - - - True - True - 0 - + + + _Close + False + True + end + + okbutton1 diff --git a/gtk/gtkbuilder/window_changes.ui b/gtk/gtkbuilder/window_changes.ui index 749185e38..17ca3b9c4 100644 --- a/gtk/gtkbuilder/window_changes.ui +++ b/gtk/gtkbuilder/window_changes.ui @@ -1,184 +1,88 @@ - False - False - 6 True - center-on-parent 450 350 - dialog - True - + - True - False vertical + 12 + 12 + 12 + 12 12 - - - True - False - end - - - _Cancel - True - True - True - False - True - - - False - True - 0 - - - - - True - True - True - True - True - False - _Mark - True - - - False - True - 1 - - - - - False - False - end - 0 - - horizontal - True - False - 6 12 - True - False - 0 + start dialog-information - 6 + 2 - - False - False - 0 - vertical - True - False 12 - False + False 0 True True - - False - False - 0 - - True - False 0 <span weight="bold" size="larger">Mark additional required changes?</span> True - - False - False - 1 - - True - False 0 The chosen action also affects other packages. The following changes are required in order to proceed. True True - - False - False - 2 - - - True - False - 0 - none + + True + True - - True - True - in - - - True - False - 5 - False - False - - - - - + + False + False - - True - True - 3 - - - True - True - 1 - - - True - True - 1 - + + + _Cancel + False + True + + + + + False + _Mark + True + + cancel proceed diff --git a/gtk/gtkbuilder/window_details.ui b/gtk/gtkbuilder/window_details.ui index 63886e7fb..21dd6ceb8 100644 --- a/gtk/gtkbuilder/window_details.ui +++ b/gtk/gtkbuilder/window_details.ui @@ -1,717 +1,470 @@ - No package is selected. - False - 6 540 - normal - + - True - False vertical - - - True - False - end - - - _Close - True - True - True - False - True - - - - False - False - 0 - - - - - False - True - end - 0 - - + 12 + 12 + 12 + 12 - True - True - 6 True vertical - True - False - 12 + 12 + 12 + 12 + 12 18 - True - False 12 6 False - True - False 0 0 <b>Package:</b> True True + + 0 + 0 + - - 0 - 0 - - - True - False + + never + never + True + + + 3 + 3 + False + word + 3 + 3 + False + textbuffer1 + + + + 1 + 0 + + + + + 0 + 0 + <b>Status:</b> True - True + + 0 + 1 + + + + + + horizontal + 6 True + True + + + + + + + True + + + + 1 + 1 + - - 1 - 4 - - - True - False + 0 0 - <b>Section:</b> + <b>Maintainer:</b> True - right + + 0 + 2 + + + + + + 0 + True + True + True + + 1 + 2 + - - 0 - 4 - - True - False 0 0 <b>Priority:</b> True + + 0 + 3 + - - 0 - 3 - - True - False 0 True True + + 1 + 3 + - - 1 - 3 - - - True - False + 0 0 - <b>Maintainer:</b> + <b>Section:</b> True + right + + 0 + 4 + - - 0 - 2 - - - True - False + 0 - 0 - <b>Status:</b> True - - - 0 - 1 - - - - - horizontal - True - False - 6 + True True - True - - - True - False - 0 - - - False - False - 0 - - - - - True - False - True - - - False - False - 1 - - - - - 1 - 1 - - - - - True - True - never - never - etched-in - - - True - True - 3 - 3 - False - word - 3 - 3 - False - textbuffer1 - - + + 1 + 4 + - - 1 - 0 - - False 0 <b>Tags:</b> True + + 0 + 5 + - - 0 - 5 - - False 0 True + + 1 + 5 + - - 1 - 5 - - - - - True - True - 0 - True - True - True - - - 1 - 2 - - False 0 - True <b>Source:</b> True + + 0 + 6 + - - 0 - 6 - - True - False 0 True True True + + 1 + 6 + - - 1 - 6 - - True - False 0 <b>Installed Version</b> True 18 + + 0 + 7 + 2 + - - 0 - 7 - 2 - - True - False 0 Version: - 12 + 12 + + 0 + 8 + - - 0 - 8 - - True - False 0 True True + + 1 + 8 + - - 1 - 8 - - True - False 0 Size: - 12 + 12 + + 0 + 9 + - - 0 - 9 - - True - False 0 True True + + 1 + 9 + - - 1 - 9 - - True - False 0 <b>Latest Available Version</b> True 18 + + 0 + 10 + 2 + - - 0 - 10 - 2 - - True - False 0 Version: - 12 + 12 + + 0 + 11 + - - 0 - 11 - - True - False 0 True True + + 1 + 11 + - - 1 - 11 - - True - False 0 Size: - 12 + 12 + + 0 + 12 + - - 0 - 12 - - True - False 0 True True + + 1 + 12 + - - 1 - 12 - - True - False 0 Download: - 12 + 12 + + 0 + 13 + - - 0 - 13 - - True - False 0 True True + + 1 + 13 + - - 1 - 13 - - - False - False - 0 - - True - False Common center - - False - vertical - True - False - 12 + 12 + 12 + 12 + 12 6 - True - False - - False - False - 0 - vertical - True - False - True - False False False - - vertical - True - False - 6 + + True + True - - True - True - etched-in - - - True - True - False - False - - - - - - - - True - True - 0 - - - - - True - False - + + False + False - - False - False - 1 - - True - False - - False - - - True - True - etched-in + + True + True - True - True False - - - - - 1 - - True - False - - 1 - False - - - vertical - True - False - 6 + + True + True - - True - True - etched-in - - - True - True - False - - - - - - - - True - True - 0 - - - - - True - False - + + False - - False - False - 1 - - - 2 - - True - False - - 2 - False - - - True - True - etched-in + + True + True - True - True False - - - - - 3 - - True - False - - 3 - False - - - True - True - 0 - - - True - True - 1 - - - 1 - - True - False Dependencies center - - 1 - False - - True - 12 - etched-in + 12 + 12 + 12 + 12 + True - True - True 3 3 False @@ -721,175 +474,78 @@ - - 2 - - True - False Installed Files - - 2 - False - vertical - True - False - 12 + 12 + 12 + 12 + 12 6 - True - False 0 Available versions: True - - False - False - 0 - - + + True + True + + + False + + + + + + horizontal - True - False + 12 - - vertical - True - False - 12 - - - vertical - True - False - 6 - - - True - True - in - - - True - True - False - - - - - - - - True - True - 0 - - - - - True - True - 0 - - - - - horizontal - True - False - 12 - - - True - False - 0 - dialog-information - 5 - - - False - False - 0 - - - - - True - False - <b>Note:</b> To install a version that is different from the default one, choose <b>Package -> Force Version...</b> from the menu. - True - True - - - False - False - 1 - - - - - False - False - 1 - - + + dialog-information + 2 + + + + + <b>Note:</b> To install a version that is different from the default one, choose <b>Package -> Force Version...</b> from the menu. + True + True - - True - True - 0 - - - True - True - 1 - - - 3 - - True - False Versions - - 3 - False - vertical - True - False - 12 + 12 + 12 + 12 + 12 6 - True - False - etched-in + True + True - True - True 3 3 False @@ -900,38 +556,26 @@ - - True - True - 0 - - - 4 - - True - False Description center - - 4 - False - - - True - True - 1 - + + + _Close + False + True + + button_close diff --git a/gtk/gtkbuilder/window_disc_name.ui b/gtk/gtkbuilder/window_disc_name.ui index 6560ebd89..11f8dfb5a 100644 --- a/gtk/gtkbuilder/window_disc_name.ui +++ b/gtk/gtkbuilder/window_disc_name.ui @@ -1,172 +1,91 @@ - False - False - 6 False True - + horizontal - True - False + 12 + 12 + 12 + 12 12 horizontal - True - False - 6 12 - True - False - 0 - 0 + start + start dialog-question - 6 + 2 - - True - True - 0 - vertical - True - False 12 + True - True - False 0 <span weight="bold" size="larger">Enter the label of the CD-ROM</span> True - - False - False - 0 - - True - False 0 The label will be used to identify the CD-ROM if you want to install packages from it later. True - - False - False - 1 - horizontal - True - False 12 - True - False Disc label: - - False - False - 0 - - True - True + True - - True - True - 1 - - - True - True - 2 - - - True - True - 1 - - - False - True - 0 - - + horizontal - True - False - 5 - 10 - end + 6 _Cancel - True - True - True False True - + True + end - - False - True - 0 - _OK - True - True - True False True - - - False - True - 1 - - - False - True - 1 - - + diff --git a/gtk/gtkbuilder/window_fetch.ui b/gtk/gtkbuilder/window_fetch.ui index 24a195637..c3a177e3a 100644 --- a/gtk/gtkbuilder/window_fetch.ui +++ b/gtk/gtkbuilder/window_fetch.ui @@ -1,168 +1,76 @@ - - False - 6 True - center-on-parent 450 - dialog - + False + vertical - True - False - 6 + 12 + 12 + 12 + 12 + 12 - - vertical - True - False - 6 - 12 - - - vertical - True - False - 6 - - - True - False - 0 - True - True - - - False - False - 0 - - - - - True - False - 0.10000000149 - - - True - False - 1 - - + + 0 + True + True + + + + + 0.1 + + + + + 0 + + + + + True + start + + + 200 + True + always + always + True - - True - False - 0 + + True + True + False - - False - False - 2 - - - False - False - 0 - - - - - True - True - 6 - - - vertical - True - False - 6 - - - 200 - True - True - always - always - in - - - True - True - True - True - False - - - - - - - - True - True - 0 - - - - - - - True - False - Show individual files - - + + + + Show individual files - - True - True - 1 - - + - - True - True - 0 - - + horizontal - True - False - end _Cancel - True - True False - 5 True - + True + end - - False - True - 0 - - - False - True - 1 - - + diff --git a/gtk/gtkbuilder/window_filters.ui b/gtk/gtkbuilder/window_filters.ui index 76d6eba3e..26881c98c 100644 --- a/gtk/gtkbuilder/window_filters.ui +++ b/gtk/gtkbuilder/window_filters.ui @@ -1,6 +1,5 @@ - @@ -41,1359 +40,638 @@ False - False - 6 Filters False True 640 480 - dialog - + - True - False + 12 + 12 + 12 + 12 vertical - - - True - False - end - - - _Cancel - True - True - True - False - True - - - - False - False - 0 - - - - - _OK - True - True - True - False - True - - - - False - False - 1 - - - - - False - False - end - 0 - - horizontal - True - False - 6 12 vertical - True - False + True 6 - True - True - - - False - False - 2 - 0 - - True - True never - in + True + True - True - True False False - - - - - True - True - 1 - vertical - True - False - + horizontal - True - False 6 - start _New - True - True - True False True - - False - False - 0 - _Delete - True - True - True False True - - False - False - 1 - - - True - True - 0 - - - False - False - 2 - - - True - True - 0 - horizontal - True - False - True - True vertical - True - False - 12 + 12 + 12 + 12 + 12 18 horizontal - True - False horizontal - True - False 24 vertical - True - False 18 vertical - True - False 6 - True - False 0 <b>Current</b> True - - False - False - 0 - horizontal - True - False - True - False - - False - False - 0 - vertical - True - False Installed - True - True False Installed packages that are up-to-date True - 0.5 - True - - False - False - 0 - Upgradable - True - True False Installed packages that are upgradable True - 0.5 - True - - False - False - 1 - Upgradable (upstream) - True - True False Installed packages that are upgradable to a later upstream version True - 0.5 - True - - False - False - 2 - Residual config - True - True False Removed packages that have left configuration files on the system True - 0.5 - True - - False - False - 3 - Not installed - True - True False Not installed packages True - 0.5 - True - - False - False - 4 - - - False - False - 1 - - - True - True - 1 - - - False - False - 0 - vertical - True - False 6 - True - False 0 <b>Marked</b> True - - False - False - 0 - horizontal - True - False - True - False - - False - False - 0 - vertical - True - False Not marked - True - True False Packages that won't be changed True - 0.5 - True - - False - False - 0 - For installation or upgrade - True - True False Packages that will be installed or upgraded True - 0.5 - True - - False - False - 1 - For removal - True - True False Packages that will be removed True - 0.5 - True - - False - False - 2 - - - True - True - 1 - - - True - True - 1 - - - False - False - 1 - - - True - True - 0 - vertical - True - False 6 - True - False 0 <b>Other</b> True - - False - False - 0 - horizontal - True - False - True - False - - False - False - 0 - vertical - True - False New in repository - True - True False Packages that are new in the repository since that last "Reload" True - 0.5 - True - - False - False - 0 - Pinned - True - True False Packages that will never be upgraded True - 0.5 - True - - False - False - 1 - Orphaned - True - True False Library packages that are no longer needed (deborphan is required) True - 0.5 - True - - False - False - 2 - Not installable - True - True False Packages that are not available in any repository True - 0.5 - True - - False - False - 3 - Broken - True - True False Packages with broken dependencies True - 0.5 - True - - False - False - 4 - Automatic install - True - True False Packages installed automatically as part of a dependency True - 0.5 - True - - False - False - 5 - Automatic removable - True - True False Packages installed automatically but no longer required by any other package True - 0.5 - True - - False - False - 6 - Policy broken - True - True False Currently in broken policy state True - 0.5 - True - - False - False - 7 - Manual installed - True - True False Packages installed manually (not as a dependency of something else) True - 0.5 - True - - False - False - 8 - - - False - False - 1 - - - False - False - 1 - - - True - True - 1 - - - False - False - 0 - - - True - True - 0 - - + horizontal - True - False 6 - end _Select All - True - True - True False True - - - False - False - 0 - _Deselect All - True - True - True False True - - - False - False - 1 - _Invert All - True - True - True False True - - - False - False - 2 - - - False - False - 1 - - True - False Status - - False - vertical - True - False - 12 + 12 + 12 + 12 + 12 6 - True - True - in + True + True - True - True - 1 False - - - - - True - True - 0 - vertical - True - False - + Include selected sections only - True - True False True - 0.5 - True - - False - False - 0 - - + Exclude selected sections - True - True False True - 0.5 - True radiobutton_incl - - False - False - 1 - - - False - False - 1 - - - 1 - - True - False Section - - 1 - False - vertical - True - False - 12 + 12 + 12 + 12 + 12 6 - True - True + True - True - False - True - True - - - - - True - True - 0 - horizontal - True - False 12 - True - False + True ls_pattern_what - - True - True - 0 - - True - False + True ls_pattern_do - - True - True - 1 - - True - True - + True - - True - True - 2 - - - False - True - 1 - horizontal - True - False 6 - True - False Boolean operator between property criterias: - - False - False - 0 - - + AND - True - True False True - 0.5 - True - - False - False - 1 - - + OR - True - True False True - 0.5 - True radiobutton_properties_and - - False - False - 2 - - - False - False - 2 - - + horizontal - True - False 6 - end _New - True - True - True False True - - - False - False - 0 - _Delete - True - True - True False True - - - False - False - 1 - - - False - False - 3 - - - 2 - - True - False Properties - - 2 - False - horizontal - False horizontal - True - True - 12 + 12 + 12 + 12 + 12 + False + False vertical - True - False 6 - True - - False - False - 0 - - True - True + True - True - False - True - True - - - - - True - True - 1 - - + horizontal - True - False - 2 + 2 + 2 + 2 + 2 5 - start - True - True - True False - - - True - False - 0 - 0 + + horizontal + 2 - - horizontal - True - False - 2 - - - True - False - list-add - - - False - False - 0 - - - - - True - False - Include - True - - - False - False - 1 - - + + list-add + + + + + Include + True - - False - False - 0 - - True - True - True False - - - True - False - 0 - 0 + + horizontal + 2 - - horizontal - True - False - 2 - - - True - False - list-remove - - - False - False - 0 - - - - - True - False - Exclude - True - - - False - False - 1 - - + + list-remove + + + + + Exclude + True - - False - False - 1 - - - False - False - 2 - - - False - False - - - True - True + - - True - False + + vertical - - vertical - True - False - - - True - True - - - - - - - True - True - 0 - - - - - True - True - - - - - - - True - True - 1 - - + + True + + + + + True - - True - True - - - True - True - 0 - - - 3 - - True - False Tags - - 3 - False - - - True - True - 0 - - - True - True - 1 - - - False - True - 1 - + + + _Cancel + False + True + + + + + _OK + False + True + + button_cancel button_ok diff --git a/gtk/gtkbuilder/window_find.ui b/gtk/gtkbuilder/window_find.ui index 8d9a1ca79..21d8eed4a 100644 --- a/gtk/gtkbuilder/window_find.ui +++ b/gtk/gtkbuilder/window_find.ui @@ -1,6 +1,5 @@ - @@ -15,232 +14,96 @@ False - False - 6 - dialog - + vertical - True - False + 12 + 12 + 12 + 12 6 - - - True - False - end + + + 12 + 6 - - _Cancel - True - True - True - False - True - + + 0 + Search: + + 0 + 0 + - - False - False - 0 - - - True - True - True - True - False - - - - - - True - False - 0 - 0 - - - horizontal - True - False - 2 - - - True - False - edit-find - - - False - False - 0 - - - - - True - False - _Search - True - - - False - False - 1 - - - - + + ls_search_cache + True + True + + + + 1 + 0 + + + + + + 0 + Look in: + True + + 0 + 1 + + + + + + ls_lookin + True + + 1 + 1 + - - False - False - 1 - - - False - False - end - 0 - + + + + + _Cancel + False + True + + + + + False + - + horizontal - True - False - 6 - 12 + 2 - - vertical - True - False - 6 - - - vertical - True - False - 6 - - - horizontal - True - False - - - True - False - - - False - False - 0 - - - - - True - False - 12 - 6 - - - True - False - 0 - Search: - - - 0 - 0 - - - - - True - False - 0 - Look in: - True - - - 0 - 1 - - - - - True - False - ls_lookin - True - - - 1 - 1 - - - - - True - False - ls_search_cache - True - True - - - True - - - - - 1 - 0 - - - - - True - True - 1 - - - - - True - True - 0 - - - - - True - True - 0 - - + + edit-find + + + + + _Search + True - - True - True - 0 - - - False - True - 1 - diff --git a/gtk/gtkbuilder/window_iconlegend.ui b/gtk/gtkbuilder/window_iconlegend.ui index f0df98f59..70814d652 100644 --- a/gtk/gtkbuilder/window_iconlegend.ui +++ b/gtk/gtkbuilder/window_iconlegend.ui @@ -1,80 +1,48 @@ - - - False - 6 + Icon Legend False - normal - - - True - False - vertical - 6 - - - True - False - end + + + 12 + 12 + 12 + 12 + 6 + 6 + + + 0 + <b>The following icons are used to indicate the current status of a package:</b> + True + + 0 + 0 + 2 + + + + + + horizontal + + 0 + 100 + 2 + _Close - True - True - True + True + end False True - - - - False - True - 0 - - - - - False - True - end - 0 - - - - - vertical - True - False - 6 - 6 - - - True - False - 0 - <b>The following icons are used to indicate the current status of a package:</b> - True - True - - False - False - 0 - - - True - True - 1 - - - - button_close - + diff --git a/gtk/gtkbuilder/window_logview.ui b/gtk/gtkbuilder/window_logview.ui index ba3df99be..798273055 100644 --- a/gtk/gtkbuilder/window_logview.ui +++ b/gtk/gtkbuilder/window_logview.ui @@ -1,192 +1,94 @@ - - - False - 6 + History 600 400 - dialog - - - True - False + + vertical + 12 + 12 + 12 + 12 6 - - - True - False - end - - - _Close - True - True - True - False - True - - - - False - True - 0 - - + + + 0 + History of installed, upgraded and removed software packages. + True - - False - True - end - 0 - - - vertical - True - False - 6 - 6 + + horizontal + True + True - - True - False - 0 - History of installed, upgraded and removed software packages. - True - True + + True + 200 + + + False + + - - False - False - 0 - - - horizontal - True - True + + vertical + 6 - - True - True - in - 200 + + True + True - - True - True - False - - - + + False + word + 6 + False - - False - False - - - vertical - True - False + + horizontal 6 - - True - True - in - - - True - True - False - word - 6 - False - - + + True - - True - True - 0 - - - horizontal - True - False - 6 - - - True - True - True - True - True - - - - True - True - 0 - - - - - _Find - True - True - False - True - - - - False - False - 1 - - + + _Find + False + True - - False - True - 1 - - - True - True - - - True - True - 1 - - - True - True - 1 - + + + + horizontal + + + _Close + True + True + end + + + - - - button_close - + diff --git a/gtk/gtkbuilder/window_main.ui b/gtk/gtkbuilder/window_main.ui index e3eec9661..780fe2086 100644 --- a/gtk/gtkbuilder/window_main.ui +++ b/gtk/gtkbuilder/window_main.ui @@ -1,454 +1,308 @@ - False Synaptic 640 480 - True - False vertical + True - - True - False + + main_menu - True - False horizontal 10 - True - False - GTK_RELIEF_NONE + False Reload the package information to become informed about new, removed or upgraded software packages. True win.reload - True 6 - True view-refresh - True Reload - - False - - True - False - GTK_RELIEF_NONE + False Mark all possible upgrades True win.mark-all-upgrades - True 6 - True system-upgrade - True Mark All Upgrades - - False - - True - False True start - GTK_RELIEF_NONE + False Apply all marked changes True win.apply - True 6 - True system-run - True Apply - - False - - True - False vertical 6 6 6 - True - False Quick filter end + True 0 - - True - False - 0 - 200 - True - True - - - False - False - 1 - - - False - False - - True - False - GTK_RELIEF_NONE + False View package properties True win.package-properties - True 6 - True document-properties - True Properties - - False - - True - False - GTK_RELIEF_NONE + False Search for packages True win.search - True 6 - True edit-find - True Search - - False - - True - True + True - True - False vertical - True - True + True 150 - True - True False - - - - - True - True - 0 - - True - False - - False - True - 1 - - True - False - 6 + 6 + 6 + 6 + 6 6 6 True True - + _Sections - True - True False True - 0.5 - False - + + 0 + 0 + - - 0 - 0 - - + S_earch Results - True - True False True - 0.5 - False radiobutton_sections - + + 0 + 4 + - - 0 - 4 - - + S_tatus - True - True False True - 0.5 - False radiobutton_sections True - + + 0 + 1 + - - 0 - 1 - - + _Custom Filters - True - True False True - 0.5 - False radiobutton_sections - True + + 0 + 3 + - - 0 - 3 - - + Origin - True - True False True - 0.5 - False radiobutton_sections True - + + 0 + 2 + - - 0 - 2 - - + _Architecture - True - True False True - 0.5 - False radiobutton_sections + + 0 + 5 + - - 0 - 5 - - - False - True - 1 - - - False - False - - True - True vertical - True - False vertical - True - True + True - True - True True True - - - - - True - True - 0 - - - True - False - - True - False vertical - True - False 6 False True + True - True - False - 6 - etched-in + 6 + 6 + 6 + 6 + True - True - True 3 3 False @@ -463,184 +317,135 @@ - True - False Description center - - False - - True - True - True - False - none - True - False - 12 - 12 - 12 - 12 + 12 + 12 + 12 + 12 vertical 18 - True - False 12 6 False - True - False <b>Package:</b> True 0 0 + + 0 + 0 + - - 0 - 0 - - True - False True 0 True + + 1 + 4 + - - 1 - 4 - - True - False <b>Section:</b> True 0 0 + + 0 + 4 + - - 0 - 4 - - True - False <b>Priority:</b> True 0 0 + + 0 + 3 + - - 0 - 3 - - True - False 0 True + + 1 + 3 + - - 1 - 3 - - True - False <b>Maintainer:</b> True 0 0 + + 0 + 2 + - - 0 - 2 - - True - False <b>Status:</b> True right 0 0 + + 0 + 1 + - - 0 - 1 - - True - False 6 True True - True - False - 0 - - False - False - 0 - - True - False - - False - False - 1 - + + 1 + 1 + - - 1 - 1 - - True - True never never - etched-in + True - True - True 3 3 False @@ -650,505 +455,330 @@ False + + 1 + 0 + - - 1 - 0 - - False <b>Tags:</b> True 0 + + 0 + 5 + - - 0 - 5 - - False 0 True + + 1 + 5 + - - 1 - 5 - - True - False <b>Source:</b> True 0 + + 0 + 6 + - - 0 - 6 - - True - False True True 0 True + + 1 + 6 + - - 1 - 6 - - True - True True True 0 True + + 1 + 2 + - - 1 - 2 - - True - False <b>Installed Version</b> True 0 18 + + 0 + 7 + 2 + - - 0 - 7 - 2 - - True - False Version: 0 - 12 + 12 + + 0 + 8 + - - 0 - 8 - - True - False 0 True + + 1 + 8 + - - 1 - 8 - - True - False Size: 0 - 12 + 12 + + 0 + 9 + - - 0 - 9 - - True - False True 0 True + + 1 + 9 + - - 1 - 9 - - True - False <b>Latest Available Version</b> True 0 18 + + 0 + 10 + 2 + - - 0 - 10 - 2 - - True - False Version: 0 - 12 + 12 + + 0 + 11 + - - 0 - 11 - - True - False 0 True + + 1 + 11 + - - 1 - 11 - - True - False Size: 0 - 12 + 12 + + 0 + 12 + - - 0 - 12 - - True - False True 0 True + + 1 + 12 + - - 1 - 12 - - True - False Download: 0 - 12 + 12 + + 0 + 13 + - - 0 - 13 - - True - False 0 True + + 1 + 13 + - - 1 - 13 - - - False - False - 0 - - - 1 - - True - False Common center + + 1 + False + - - 1 - False - - True - False - 6 - 6 - 6 - 6 + 6 + 6 + 6 + 6 vertical 6 - True - False - - False - True - 0 - - True - False vertical + True - True - False False False + True - - True - False - vertical - 6 - - - True - True - etched-in - - - True - True - False - False - - - - - - - - True - True - 0 - - + + True + True - - True - False - + + False + False - - False - False - 1 - - True - False - - False - - True - True - etched-in + True + True - True - True False - - - - - 1 - - True - False - - 1 - False - - - True - False - vertical - 6 - - - True - True - etched-in - - - True - True - False - - - - - - - - True - True - 0 - - + + True + True - - True - False - + + False - - False - False - 1 - - - 2 - - True - False - - 2 - False - - True - True - etched-in + True + True - True - True False - - - - - 3 - - True - False - - 3 - False - - - True - True - 0 - - - True - True - 1 - - - 2 - - True - False Dependencies center + + 2 + False + - - 2 - False - - True - True - 6 - etched-in + 6 + 6 + 6 + 6 + True - True - True 3 3 False @@ -1158,253 +788,141 @@ - - 3 - - True - False Installed Files + + 3 + False + - - 3 - False - - True - False - 6 + vertical + 6 + 6 + 6 + 6 - True - False vertical 6 + True - True - False vertical 6 + True - True - True - in + True + True - True - True False - - - - - True - True - 0 - - - True - True - 0 - - True - False 12 - True - False dialog-information - 5 + 2 - - False - False - 0 - - True - False <b>Note:</b> To install a version that is different from the default one, choose <b>Package -> Force Version...</b> from the menu. True True - - False - False - 1 - - - False - False - 1 - - - True - True - 0 - - - 4 - - True - False Versions + + 4 + False + - - 4 - False - - - True - True - 0 - - True - False - - False - False - 2 - + + True + True + - - True - True - + + True + True + - - True - True - - - True - True - - - True - False - 18 + + 18 + 2 - - True - False - 2 + + never + never - - True - True - never - never - - - True - False - queue - none - - - True - False - 4 - 4 - 7 - 7 - - - 100 - True - False - 0 - 0 - - - - - - + + 100 + 4 + 4 + 7 + 7 + 0 + 0 - - True - True - 0 - + + + + + center + 0.10000000149 + + + + + False + True + end + 6 + You will not be able to apply any changes, but you can still export the marked changes or create a download script for them. - - True - False - center - 0.10000000149 + + warning + 1 - - False - False - end - 1 - - - 6 - You will not be able to apply any changes, but you can still export the marked changes or create a download script for them. - - - True - warning - 1 - - - - - True - Running without administrative privileges - - + + Running without administrative privileges - - False - False - end - 1 - diff --git a/gtk/gtkbuilder/window_preferences.ui b/gtk/gtkbuilder/window_preferences.ui index a54b5543e..17943454d 100644 --- a/gtk/gtkbuilder/window_preferences.ui +++ b/gtk/gtkbuilder/window_preferences.ui @@ -1,6 +1,5 @@ - 1000 20 @@ -51,2381 +50,1294 @@ - - True - False - go-down - - - True - False - go-up - - False Preferences False True - center - + vertical - True - False - 6 + 12 + 12 + 12 + 12 + 12 - True - True - 6 vertical - True - False - 12 + 12 + 12 + 12 + 12 18 vertical - True - False 6 - True - False 0 <b>Appearance</b> True - - False - False - 0 - horizontal - True - False - True - False - - False - False - 0 - - - vertical - True - False - 6 - - - Show package properties in the main window - True - True - False - 2 - True - 0.5 - True - - - False - False - 0 - - + + Show package properties in the main window + False + True - - False - False - 1 - - - False - False - 1 - - - False - False - 0 - vertical - True - False 6 - True - False 0 <b>Marking Changes</b> True - - False - False - 0 - horizontal - True - False - True - False - - False - False - 0 - vertical - True - False - 2 6 Ask to confirm changes that also affect other packages - True - True False - 2 True - 0.5 - True - - False - False - 0 - Consider recommended packages as dependencies - True - True False - 2 True - 0.5 - True - - False - False - 1 - Clicking on the status icon marks the most likely action - True - True False - 2 True - 0.5 - True - - False - False - 2 - - True - False 12 6 - True - False 0 Removal of packages: True + + 0 + 0 + - - 0 - 0 - - True - False ls_removal_action True + + 1 + 0 + - - 1 - 0 - - True - False 0 System upgrade: + + 0 + 1 + - - 0 - 1 - - True - False 0 Number of undo operations: + + 0 + 3 + - - 0 - 3 - horizontal - True - False - True - True adjustment1 1 - - False - True - 0 - - True - False + True - - True - True - 1 - + + 1 + 3 + - - 1 - 3 - - True - False 0 Reloading outdated package information: + + 0 + 2 + - - 0 - 2 - - True - False ls_upgrade_method + + 1 + 1 + - - 1 - 1 - - True - False ls_update_ask + + 1 + 2 + - - 1 - 2 - - - True - True - 3 - - - True - True - 1 - - - True - True - 1 - - - False - False - 1 - vertical - True - False 6 - True - False 0 <b>Applying Changes</b> True - - False - False - 0 - horizontal - True - False - True - False - - False - False - 0 - vertical - True - False Apply changes in a terminal window - True - True False - 2 True - 0.5 - True - - False - False - 0 - Ask to quit after the changes have been applied successfully - True - True False - 2 True - 0.5 - True - - False - False - 1 - - - True - True - 1 - - - True - True - 1 - - - False - True - 2 - - - False - - True - False General center - - False - vertical - True - False - 12 + 12 + 12 + 12 + 12 18 vertical - True - False 6 - True - False 0 <b>Columns</b> True - - False - False - 0 - horizontal 174 - True - False + True - True - False - - False - False - 0 - horizontal - True - False + True 12 - True - True - in + True + True - True - True - - - - - True - True - 0 - vertical - True - False 6 Move _Up - True - True False - move_up_image + go-up True - - - False - False - 0 - Move D_own - True - True False - move_down_image + go-down True - - - False - False - 1 - - - False - False - 1 - - - True - True - 1 - - - True - True - 1 - - - False - False - 0 - vertical - True - False 6 - True - False 0 <b>Fonts</b> True - - False - False - 0 - horizontal - True - False + True - True - False - - False - False - 0 - - True - False + True 12 6 Use custom application font - True - True False True - 0.5 - True - + + 0 + 0 + - - 0 - 0 - Use custom terminal font - True - True False True - 0.5 - True - + + 0 + 1 + - - 0 - 1 - - True - True False - - - True - False - 0 - 0 + + horizontal + 2 - - horizontal - True - False - 2 - - - True - False - font - - - False - False - 0 - - - - - True - False - A_pplication Font - True - - - False - False - 1 - - + + font + + + + + A_pplication Font + True + + 1 + 0 + - - 1 - 0 - - True - True False - - - True - False - 0 - 0 + + horizontal + 2 - - horizontal - True - False - 2 - - - True - False - font - - - False - False - 0 - - - - - True - False - _Terminal Font - True - - - False - False - 1 - - + + font + + + + + _Terminal Font + True + + 1 + 1 + - - 1 - 1 - - - True - True - 1 - - - True - True - 1 - - - False - False - 1 - - - 1 - False - - True - False Columns and Fonts - - 1 - False - vertical - True - False - 12 + 12 + 12 + 12 + 12 18 vertical - True - False 6 - True - False 0 <b>Colors</b> True - - False - False - 0 - horizontal - True - False - True - False - - False - False - 0 - vertical - True - False + True 6 Color packages by their status - True - True False - 2 True - 0.5 - True - - False - False - 0 - horizontal - True - False - True - False + True 24 6 horizontal - True - False 12 True - True - False 0 Marked for installation: + True - - False - False - 0 - Color - True - True False True - - False - False - end - 1 - + + 0 + 0 + - - 0 - 0 - - + horizontal - True - False 12 True - - True - False + 0 - Marked for removal: + Marked for reinstallation: + True - - False - False - 0 - - + Color - True - True False True - - False - False - end - 1 - + + 1 + 0 + - - 0 - 1 - - + horizontal - True - False 12 True - - True - False + 0 - Marked for complete removal: - center - - - False - False - 0 - + Marked for removal: + True + - + Color - True - True False True - - False - False - end - 1 - + + 0 + 1 + - - 1 - 1 - - + horizontal - True - False 12 True - - True - False + 0 - Upgradable: - center + Marked for complete removal: + True - - False - False - 0 - - + Color - True - True False True - - False - False - end - 1 - + + 1 + 1 + - - 0 - 2 - - + horizontal - True - False 12 True - - True - False + 0 - Marked for reinstallation: + Upgradable: + True - - False - False - 0 - - + Color - True - True False True - - False - False - end - 1 - + + 0 + 2 + - - 1 - 0 - horizontal - True - False 12 True - True - False 0 Marked for upgrade: - center + True - - False - False - 0 - Color - True - True False True - - False - False - end - 1 - + + 1 + 2 + - - 1 - 2 - - + horizontal - True - False 12 True - - True - False + 0 - Not installed: - center + Broken: + True - - False - False - 0 - - + Color - True - True False True - - False - False - end - 1 - + + 0 + 3 + - - 0 - 5 - - + horizontal - True - False 12 True - - True - False + 0 - Not installed (locked): + Marked for downgrade: + True - - False - False - 0 - - + Color - True - True False True - - False - False - end - 1 - + + 1 + 3 + - - 1 - 5 - - + horizontal - True - False 12 True - - True - False + 0 - Installed (locked): - center + Installed: + True - - False - False - 0 - - + Color - True - True False True - - False - False - end - 1 - + + 0 + 4 + - - 1 - 4 - - + horizontal - True - False 12 True - - True - False + 0 - Installed: - center + Installed (locked): + True - - False - False - 0 - - + Color - True - True False True - - False - False - end - 1 - + + 1 + 4 + - - 0 - 4 - - + horizontal - True - False 12 True - - True - False + 0 - New in repository: - center + Not installed: + True - - False - False - 0 - - + Color - True - True False True - - False - False - end - 1 - + + 0 + 5 + - - 0 - 6 - - + horizontal - True - False 12 True - - True - False + 0 - Marked for downgrade: - center + Not installed (locked): + True - - False - False - 0 - - + Color - True - True False True - - False - False - end - 1 - + + 1 + 5 + - - 1 - 3 - - + horizontal - True - False 12 True - - True - False + 0 - Broken: - center + New in repository: + True - - False - False - 0 - - + Color - True - True False True - - False - False - end - 1 - + + 0 + 6 + - - 0 - 3 - - - True - True - 0 - - - False - False - 1 - - - True - True - 1 - - - False - False - 1 - - - False - False - 0 - - - 2 - False - - True - False Colors center - - 2 - False - vertical - True - False - 12 + 12 + 12 + 12 + 12 18 vertical - True - False 6 - True - False 0 <b>Temporary Files</b> True - - False - False - 0 - horizontal - True - False - True - False - - False - False - 0 - vertical - True - False 6 - + _Leave all downloaded packages in the cache - True - True False True - 0.5 True - True - - False - False - 0 - - + _Delete downloaded packages after installation - True - True False True - 0.5 - True radio_cache_leave - - False - False - 1 - - + _Only delete packages which are no longer available - True - True False True - 0.5 - True radio_cache_leave - - False - False - 2 - horizontal - True - False _Delete Cached Package Files - True - True False Delete all cache package files now. True - - - False - False - 0 - - - - - - False - False - 3 - - - False - False - 1 - - - False - False - 1 - - - False - False - 0 - vertical - True - False 6 + True - True - False 0 <b>History files</b> True - - False - False - 0 - horizontal - True - False - True - False - - False - False - 0 - vertical - True - False 6 - + _Keep history - True - True False True - 0.5 - True - - False - False - 0 - horizontal - True - False 6 - + Delete _History files older than: - True - True False True - 0.5 - True radio_keep_history - - False - False - 0 - - True - True adjustment2 1 - - True - True - 1 - - True - False days - - False - False - 2 - - - False - False - 1 - - - False - False - 1 - - - False - False - 1 - - - True - True - 1 - - - 3 - False - - True - False Files center - - 3 - False - vertical - True - False - 12 + 12 + 12 + 12 + 12 6 - True - False 0 <b>Proxy Server</b> True - - False - False - 0 - horizontal - True - False - True - False - - False - False - 0 - vertical - True - False + True 6 - + Direct connection to the Internet - True - True False True - 0.5 True - True - - False - False - 0 - - + Manual proxy configuration - True - True False True - 0.5 - True radio_no_proxy - - - False - False - 1 - horizontal - True - False + True - True - False - - False - False - 0 - - True - False + True 12 6 - True - True IP address or host name of the http proxy server - True + + 1 + 0 + - - 1 - 0 - - True - False 0 Port: + + 2 + 0 + - - 2 - 0 - - True - True Port number of the http proxy server adjustment3 1 + + 3 + 0 + - - 3 - 0 - - True - True IP address or host name of the ftp proxy server True + + 1 + 1 + - - 1 - 1 - - True - False 0 Port: + + 2 + 1 + - - 2 - 1 - - True - True Port number of the ftp proxy server adjustment4 1 + + 3 + 1 + - - 3 - 1 - - True - False 0 No proxy for: + + 0 + 2 + - - 0 - 2 - - True - False 0 FTP proxy: + + 0 + 1 + - - 0 - 1 - - True - False 0 HTTP proxy: + + 0 + 0 + - - 0 - 0 - - True - True Comma separated list of hosts and domains that will not be contacted through the proxy (e.g. localhost, 192.168.1.231, .net) True + + 1 + 2 + 3 + - - 1 - 2 - 3 - Authentication - True - True False True - + + 4 + 0 + - - 4 - 0 - - - True - True - 1 - - - True - True - 2 - - - True - True - 1 - - - False - False - 1 - - - 4 - False - - True - False Network - - 4 - False - vertical - True - False - 12 + 12 + 12 + 12 + 12 18 horizontal - True - False 12 - True - False - 0 - 0 + start + start dialog-warning - 5 + 2 - - False - False - 0 - - True - False 0 <span size="large" weight="bold">These settings affect the core of your system. Consider any changes carefully.</span> True True - - False - False - 1 - - - False - False - 0 - vertical - True - False 6 + True - True - False 0 <b>Package upgrade behavior (default distribution)</b> True - - False - False - 0 - horizontal - True - False + True - True - False - - False - False - 0 - vertical - True - False + True 12 - + Always prefer the highest version - True - True False True - 0.5 - True - - - False - False - 0 - - + Always prefer the installed version - True - True False True - 0.5 - True radiobutton_ignore - - - False - False - 1 - horizontal - True - False - + Prefer versions from: - True - True False True - 0.5 - True radiobutton_ignore - - - False - False - 0 - - True - False + True ls_distros - - True - True - 1 - - - False - False - 2 - - - True - True - 1 - - - True - True - 1 - - - True - True - 1 - - - 5 - - True - False Distribution - - 5 - False - - - True - True - 0 - - + horizontal - True - False - 6 6 - end _Apply - True - True - True + True + end False True - - - False - False - 0 - _Cancel - True - True - True False True - - - False - False - 1 - _OK - True - True - True False True - - - False - False - 2 - - - False - False - 1 - - + diff --git a/gtk/gtkbuilder/window_repositories.ui b/gtk/gtkbuilder/window_repositories.ui index ffd418522..75fb36ca1 100644 --- a/gtk/gtkbuilder/window_repositories.ui +++ b/gtk/gtkbuilder/window_repositories.ui @@ -1,408 +1,205 @@ - - False - 6 + False Repositories - + vertical - True - False + 12 + 12 + 12 + 12 6 vertical - True - False - 6 6 + True horizontal - True - False 6 + True - True - True - in + True + True 400 187 - True - True - - - - - True - True - 0 - vertical - True - False 6 spread _Up - True - True - True False True - - - False - True - 0 - _Down - True - True - True False True - - - False - True - 1 - - - False - False - 1 - - - True - True - 0 - - True - False 12 6 - True - False 0 URI: + + 0 + 1 + - - 0 - 1 - - True - False 0 Distribution: + + 0 + 2 + - - 0 - 2 - - True - False 0 Section(s): + + 0 + 3 + - - 0 - 3 - horizontal - True - False 12 True - - True - True - - - False - True - 0 - + - - True - True - - - False - True - 1 - + - True - True False - - - True - False - 0 - 0 + + horizontal + 2 - - horizontal - True - False - 2 - - - True - False - preferences-system - - - False - False - 0 - - - - - True - False - Vendors... - True - - - False - False - 1 - - + + preferences-system + + + + + Vendors... + True - - False - False - 2 - + + 1 + 0 + - - 1 - 0 - - True - True + + 1 + 1 + - - 1 - 1 - - True - True + + 1 + 2 + - - 1 - 2 - - True - True + + 1 + 3 + - - 1 - 3 - - - - - - - - - - - - False - True - 1 - - - True - True - 0 - horizontal - True - False - - False - False - 1 - horizontal - True - False + 6 + True - - horizontal - True - False - start - - - _New - True - True - True - False - 5 - True - - - - False - True - 0 - - - - - _Delete - True - True - True - False - 5 - True - - - - False - True - 1 - - + + _New + False + True - - True - True - 0 - - - horizontal - True - False - end - - - _Cancel - True - True - True - False - 5 - True - - - - False - True - 0 - - - - - _OK - True - True - True - False - 5 - True - - - - False - True - 1 - - + + _Delete + False + True + True + start + + + + + _Cancel + False + True + + + + + _OK + False + True - - True - True - 1 - - - False - False - 2 - - + diff --git a/gtk/gtkbuilder/window_rgdebinstall_progress.ui b/gtk/gtkbuilder/window_rgdebinstall_progress.ui index 2541f8c0f..29a8e9bb8 100644 --- a/gtk/gtkbuilder/window_rgdebinstall_progress.ui +++ b/gtk/gtkbuilder/window_rgdebinstall_progress.ui @@ -1,221 +1,102 @@ - - False - 12 False True - center-on-parent - dialog - + vertical - True - False + 12 + 12 + 12 + 12 12 - - - False - - - True - True - 0 - - vertical - True - False 12 horizontal - True - False 12 - False dialog-information - 6 + 2 - - False - False - 0 - - True - False 0 True True + True - - True - True - 1 - - - True - True - 0 - - - vertical - True - False - 6 - - - True - False - 0.10000000149 - - - True - True - 0 - - - - - 450 - True - False - 0 - <i>Preparing packages...</i> - True - True - end - - - True - True - 1 - - + + 0.1 + + + + + 450 + 0 + <i>Preparing packages...</i> + True + True + end - - True - True - 1 - Automatically close after the changes have been successfully applied - True - True False True - 0.5 - True - - False - False - 2 - - True - True - True - True - True - + True + horizontal - True - False - - - - - - - - - - True - False + + + Details - + - - True - True - 3 - - - False - True - 1 - - + horizontal - True - False - end _Cancel - True - True False True - + True + end - - False - True - 0 - _Close - True False - True - True - True - True False True - - - False - True - 1 - - - False - False - 2 - - + diff --git a/gtk/gtkbuilder/window_rginstall_progress.ui b/gtk/gtkbuilder/window_rginstall_progress.ui index 81097bd82..6dba6d8e6 100644 --- a/gtk/gtkbuilder/window_rginstall_progress.ui +++ b/gtk/gtkbuilder/window_rginstall_progress.ui @@ -1,108 +1,52 @@ - 400 - False - 12 False True - center - + vertical - True - False + 12 + 12 + 12 + 12 12 - False + True - - True - True - 0 - vertical - True - False 6 - True - False 0 - - False - True - 0 - - True - False 0 - - False - True - 1 - - True - False - - False - False - 2 - - - False - True - 1 - - - True - False - 0.80000001192092896 - - - horizontal - True - False - - + + horizontal - - False - True - 2 - - True - False - - False - False - 3 - - + diff --git a/gtk/gtkbuilder/window_rginstall_progress_msgs.ui b/gtk/gtkbuilder/window_rginstall_progress_msgs.ui index 07fedfd54..99572b8fa 100644 --- a/gtk/gtkbuilder/window_rginstall_progress_msgs.ui +++ b/gtk/gtkbuilder/window_rginstall_progress_msgs.ui @@ -1,33 +1,30 @@ - - 6 Package Manager output - GTK_WINDOW_TOPLEVEL - GTK_WIN_POS_NONE True 400 300 True False - + vertical - True False - 0 + 12 + 12 + 12 + 12 + 6 vertical - 6 - True False 12 + True - - True + Extra output was generated during Package Manager operation False True @@ -36,27 +33,17 @@ False 0 0.5 - 0 - 0 - - 0 - False - False - - True - True GTK_POLICY_ALWAYS GTK_POLICY_ALWAYS - GTK_SHADOW_IN + True GTK_CORNER_TOP_LEFT + True - True - True False GTK_JUSTIFY_LEFT GTK_WRAP_WORD @@ -70,45 +57,23 @@ - - 0 - True - True - - - 0 - True - True - - + horizontal - 6 - True - GTK_BUTTONBOX_END - 0 - True - True - True _Close True - GTK_RELIEF_NORMAL - + True + end - - 0 - False - True - - + diff --git a/gtk/gtkbuilder/window_setopt.ui b/gtk/gtkbuilder/window_setopt.ui index d4202f869..79fdcd47d 100644 --- a/gtk/gtkbuilder/window_setopt.ui +++ b/gtk/gtkbuilder/window_setopt.ui @@ -1,189 +1,111 @@ - - 6 False - GTK_WINDOW_TOPLEVEL - GTK_WIN_POS_NONE False 350 140 False False True - False - False - GDK_WINDOW_TYPE_HINT_DIALOG - GDK_GRAVITY_NORTH_WEST - True - + vertical - True False + 12 + 12 + 12 + 12 6 - - - horizontal - True - GTK_BUTTONBOX_END - - - True - True - True - True - _Close - True - GTK_RELIEF_NORMAL - True - - - - - - True - True - True - _Apply - True - GTK_RELIEF_NORMAL - True - - - - - - 0 - False - True - GTK_PACK_END - - horizontal - 6 - True False + True 12 - True dialog-warning - 6 - 0.5 - 0 - 0 - 0 + 2 + center + start - - 0 - False - False - vertical - True False + True 12 - True <span size="large" weight="bold">Set an internal option</span> False True GTK_JUSTIFY_LEFT - False False 0 0.5 - 0 - 0 PANGO_ELLIPSIZE_NONE -1 False - 0 - - 0 - False - False - - True Only experts should use this. False False GTK_JUSTIFY_LEFT - True False 0 0.5 - 0 - 0 PANGO_ELLIPSIZE_NONE -1 False - 0 - - 0 - False - False - - True 6 12 - - True - True - True - True - 0 - - True - * - False + + Variable: + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + PANGO_ELLIPSIZE_NONE + -1 + False True + + 0 + 0 + - - 1 - 0 - - - True - True + True True 0 - - True - * False True + + 1 + 0 + - - 1 - 1 - - - True + Value: False False @@ -192,65 +114,51 @@ False 0 0.5 - 5 - 0 PANGO_ELLIPSIZE_NONE -1 False - 0 + + 0 + 1 + - - 0 - 1 - - - True - Variable: - False - False - GTK_JUSTIFY_LEFT - False - False - 0 - 0.5 - 5 - 0 - PANGO_ELLIPSIZE_NONE - -1 - False - 0 + + True + True + 0 + True + False True + + 1 + 1 + - - 0 - 0 - - - 0 - True - True - - - 0 - True - True - - - 5 - True - True - + + + _Close + True + True + + + + + _Apply + True + True + + button_close button_apply diff --git a/gtk/gtkbuilder/window_summary.ui b/gtk/gtkbuilder/window_summary.ui index 804bb3977..69ef23317 100644 --- a/gtk/gtkbuilder/window_summary.ui +++ b/gtk/gtkbuilder/window_summary.ui @@ -1,421 +1,197 @@ - - False - 6 True - dialog - True - + - True - False vertical + 12 + 12 + 12 + 12 6 - - - True - False - end + + + horizontal + 12 + True - - _Cancel - True - True - True - False - Return to the main screen - True - + + start + start + dialog-question + 2 - - False - True - 0 - - - _Apply - True - True - True - True - True - False - Apply all marked changes - True - - - - False - True - 1 - - - - - False - True - end - 0 - - - - - vertical - True - False - 6 - 8 - - - horizontal - True - False + + vertical 12 + True - - True - False + 0 - 0 - dialog-question - 6 + <span weight="bold" size="large">Apply the following changes?</span> + True + True - - False - False - 0 - - - vertical - True - False - 12 - - - True - False - 0 - <span weight="bold" size="large">Apply the following changes?</span> - True - True - - - False - False - 0 - - + + 0 + This is your last opportunity to look through the list of marked changes before they are applied. + True + + + + + False + 0 + True + True + + + + + True + 120 + never + True - - True - False - 0 - This is your last opportunity to look through the list of marked changes before they are applied. - True + + False + False - - False - False - 1 - + + + + + False + True - - False - 0 + + start + start True - True + True - - False - False - 2 - + + + + + horizontal + 12 - - 120 - True - True - never - in + + vertical + 6 - - True - True - False - False - - - + + 0 + <b>Summary</b> + True - - - True - True - 3 - - - - - True - - True - False + + horizontal - - True - True - 0 - 0 - True - True + + - - - - - True - True - 4 - - - - - horizontal - True - False - 12 - - - horizontal - True - False - + vertical - True - False 6 - - True - False + 0 - <b>Summary</b> + 0 + True - - False - False - 0 - - - horizontal - True - False - - - True - False - - - - False - False - 0 - - - - - vertical - True - False - 6 - - - True - False - 0 - 0 - - True - - - False - False - 0 - - - - - True - False - 0 - 0 - - True - - - False - False - 1 - - - - - False - False - 1 - - + + 0 + 0 + + True - - False - False - 1 - - - False - False - 0 - - - - - False - False - 0 - - - - - vertical - True - False - - - _Show Details - True - True - False - True - - - False - False - 0 - - - - - True - False - - - - False - False - 1 - - - False - False - end - 1 - - - False - False - 5 - - + vertical - True - False - 6 - - _Download package files only - True - True + + _Show Details False - The package files will be downloaded, but not installed True - 0.5 - True - - False - False - 0 - - - _Verify package signatures - True - False - Vendors sign their packages to verify the origin and integrity of the packages. Disabling the verification is a security risk. - True - 0.5 - True + + - - False - False - 1 - - - False - True - 6 - - - True - True - 1 - + + + + vertical + 6 + + + _Download package files only + False + The package files will be downloaded, but not installed + True + + + + + False + _Verify package signatures + False + Vendors sign their packages to verify the origin and integrity of the packages. Disabling the verification is a security risk. + True + + + - - True - True - 0 - - - True - True - 1 - + + + _Cancel + False + Return to the main screen + True + + + + + _Apply + False + Apply all marked changes + True + + button_cancel button_execute diff --git a/gtk/gtkbuilder/window_tasks.ui b/gtk/gtkbuilder/window_tasks.ui index 26166d362..c440a5487 100644 --- a/gtk/gtkbuilder/window_tasks.ui +++ b/gtk/gtkbuilder/window_tasks.ui @@ -1,57 +1,42 @@ - - True - False - 12 True - center 400 400 - + vertical - True - False + 12 + 12 + 12 + 12 18 vertical - True - False 12 + True horizontal - True - False 12 + True - True - False - 0 - 0 + start + start dialog-question - 6 + 2 - - False - False - 0 - vertical - True - False 12 + True - True - True 0 0.4699999988079071 <span size="large" weight="bold">Which tasks should be performed by your computer?</span> @@ -61,132 +46,58 @@ These are preselected groups of packages to perform each task. If you select a t True True - - False - False - 0 - - True - True - in + True + True - True - True False - - - - - True - True - 1 - - - True - True - 1 - - - True - True - 0 - - - True - True - 0 - - + horizontal - True - False + True + horizontal + 6 - - horizontal - True - False - 6 - end - - - _Description - True - False - True - True - False - True - - - - False - True - 0 - - - - - _Cancel - True - True - True - False - True - - - - False - True - 1 - - - - - _OK - True - False - True - True - False - True - - - - False - True - 2 - - + + True + end + _Description + False + False + True + + + + + _Cancel + False + True + + + + + _OK + False + False + True - - True - True - 0 - - - False - False - 1 - - + diff --git a/gtk/gtkbuilder/window_zvtinstallprogress.ui b/gtk/gtkbuilder/window_zvtinstallprogress.ui index bd5a38fc7..cf8f530b5 100644 --- a/gtk/gtkbuilder/window_zvtinstallprogress.ui +++ b/gtk/gtkbuilder/window_zvtinstallprogress.ui @@ -1,141 +1,59 @@ - - False - 6 - dialog - + False + - True - False vertical + 12 + 12 + 12 + 12 + 6 - - True - False - end + + horizontal - - _Close - True - True - True - False - True + + 0 + <b>Terminal Output:</b> + True - - False - True - 0 - - - - False - True - end - 0 - - - - - vertical - True - False - 6 - 6 - - vertical - True - False - 6 - - - horizontal - True - False - - - True - False - 0 - <b>Terminal Output:</b> - True - - - False - False - 0 - - - - - True - False - True - - - False - False - end - 1 - - - - - False - False - 0 - - - - - horizontal - True - False - - - - - - True - True - 1 - - + + True - - True - True - 0 - + + + + + horizontal + True + + + + + Close this dialog after the changes have been successfully applied + False + True + + + + - - Close this dialog after the changes have been successfully applied - True - True + + _Close False True - 0.5 - True + True + end - - False - False - 1 - - - True - True - 1 - - + diff --git a/gtk/gtkpkglist.h b/gtk/gtkpkglist.h index 9e429c92e..66d74e4dd 100644 --- a/gtk/gtkpkglist.h +++ b/gtk/gtkpkglist.h @@ -25,8 +25,6 @@ #include "rgutils.h" #include "rpackagelistactor.h" -#include -#include #include #define GTK_TYPE_PKG_LIST (gtk_pkg_list_get_type()) diff --git a/gtk/rgcacheprogress.cc b/gtk/rgcacheprogress.cc index 03af250d6..2a8b56a1f 100644 --- a/gtk/rgcacheprogress.cc +++ b/gtk/rgcacheprogress.cc @@ -53,34 +53,39 @@ RGCacheProgress::~RGCacheProgress() void RGCacheProgress::Update() { if (!CheckChange()) { - // RGFlushInterface(); + // co_await RGFlushInterface(); return; } - if (!_mapped) { - gtk_widget_show(_prog); - RGFlushInterface(); - _mapped = true; - } - - if (MajorChange) - gtk_label_set_text(GTK_LABEL(_label), utf8(Op.c_str())); - - // only call set_fraction when the changes are noticable (0.1%) - if (fabs(Percent - gtk_progress_bar_get_fraction(GTK_PROGRESS_BAR(_prog)) * - 100.0) > 0.1) - gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(_prog), - (float)Percent / 100.0); - - RGFlushInterface(); + start_task([this]() -> task { + if (!_mapped) { + gtk_widget_show(_prog); + co_await RGFlushInterface(); + _mapped = true; + } + + if (MajorChange) + gtk_label_set_text(GTK_LABEL(_label), utf8(Op.c_str())); + + // only call set_fraction when the changes are noticable (0.1%) + if (fabs(Percent - + gtk_progress_bar_get_fraction(GTK_PROGRESS_BAR(_prog)) * 100.0) > + 0.1) + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(_prog), + (float)Percent / 100.0); + + co_await RGFlushInterface(); + }); } void RGCacheProgress::Done() { - gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(_prog), 1.0); - RGFlushInterface(); + start_task([this]() -> task { + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(_prog), 1.0); + co_await RGFlushInterface(); - gtk_widget_hide(_prog); + gtk_widget_hide(_prog); - _mapped = false; + _mapped = false; + }); } diff --git a/gtk/rgcdscanner.cc b/gtk/rgcdscanner.cc index b1ff12a8c..5a845685c 100644 --- a/gtk/rgcdscanner.cc +++ b/gtk/rgcdscanner.cc @@ -55,7 +55,7 @@ void RGCDScanner::update(string text, int current) gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(_pbar), ((float)current) / _total); show(); - RGFlushInterface(); + co_await RGFlushInterface(); } RGCDScanner::RGCDScanner(RGMainWindow *main, RUserDialog *userDialog) diff --git a/gtk/rgchangelogdialog.cc b/gtk/rgchangelogdialog.cc index ac8845c8d..19a9a7e95 100644 --- a/gtk/rgchangelogdialog.cc +++ b/gtk/rgchangelogdialog.cc @@ -21,6 +21,7 @@ #include "config.h" // IWYU pragma: associated #include "i18n.h" +#include "racquireasync.h" #include "rgchangelogdialog.h" #include "rgfetchprogress.h" #include "rguserdialog.h" @@ -31,7 +32,6 @@ #include #include #include -#include #include #include #include @@ -40,16 +40,16 @@ class RGWindow; using namespace std; -void ShowChangelogDialog(RGWindow *me, RPackage *pkg) +task ShowChangelogDialog(RGWindow *me, RPackage *pkg) { RGFetchProgress *status = new RGFetchProgress(me); - ; + status->setDescription(_("Downloading Changelog"), _("The changelog contains information about the" " changes and closed bugs in each version of" " the package.")); - pkgAcquire fetcher(status); - string filename = pkg->getChangelogFile(&fetcher); + pkgAcquire fetcher; + string filename = co_await pkg->getChangelogFile(&fetcher, status); RGGtkBuilderUserDialog dia(me, "changelog"); @@ -83,7 +83,7 @@ void ShowChangelogDialog(RGWindow *me, RPackage *pkg) gtk_text_buffer_insert_at_cursor(buffer, "\n", -1); } - dia.run(); + co_await dia.co_run(); // clean up delete status; diff --git a/gtk/rgchangelogdialog.h b/gtk/rgchangelogdialog.h index 94d4a2d4d..af7b01400 100644 --- a/gtk/rgchangelogdialog.h +++ b/gtk/rgchangelogdialog.h @@ -22,7 +22,9 @@ #include "config.h" // IWYU pragma: associated +#include "coroutines.h" + class RGWindow; class RPackage; -void ShowChangelogDialog(RGWindow *me, RPackage *pkg); +[[nodiscard]] task ShowChangelogDialog(RGWindow *me, RPackage *pkg); diff --git a/gtk/rgchangeswindow.cc b/gtk/rgchangeswindow.cc index 9e6b15204..63a493887 100644 --- a/gtk/rgchangeswindow.cc +++ b/gtk/rgchangeswindow.cc @@ -32,8 +32,6 @@ #include #include -#include -#include #include #include #include diff --git a/gtk/rgdebinstallprogress.cc b/gtk/rgdebinstallprogress.cc index 4434a2856..4e40d4431 100644 --- a/gtk/rgdebinstallprogress.cc +++ b/gtk/rgdebinstallprogress.cc @@ -168,7 +168,7 @@ int ipc_send_fd(int fd) return 0; } -int ipc_recv_fd() +static int ipc_recv_fd() { // setup socket struct sockaddr_un servaddr, cliaddr; @@ -188,13 +188,12 @@ int ipc_recv_fd() // wait for connections socklen_t clilen = sizeof(cliaddr); - // wait max 5s (5000 * 1000/1000000) for the client + // wait max 5s for the client for (int i = 0; i < 5000 || connfd > 0; i++) { connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); if (connfd > 0) break; usleep(1000); - RGFlushInterface(); } // read_fd read_fd(connfd, &c, 1, &fd); @@ -205,7 +204,7 @@ int ipc_recv_fd() return fd; } -void RGDebInstallProgress::conffile(gchar *conffile, gchar *status) +task RGDebInstallProgress::conffile(gchar *conffile, gchar *status) { string primary, secondary; gchar *m, *s, *p; @@ -275,7 +274,7 @@ void RGDebInstallProgress::conffile(gchar *conffile, gchar *status) GTK_WIDGET(gtk_builder_get_object(dia_builder, "textview_diff")); GtkStyleContext *styleContext = gtk_widget_get_style_context(text_view); gtk_css_provider_load_from_data( - _cssProvider, "GtkTextView { font-family: monospace; }", -1, NULL); + _cssProvider, "GtkTextView { font-family: monospace; }", -1); gtk_style_context_add_provider(styleContext, GTK_STYLE_PROVIDER(_cssProvider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); @@ -283,7 +282,7 @@ void RGDebInstallProgress::conffile(gchar *conffile, gchar *status) gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view)); gtk_text_buffer_set_text(text_buffer, diff.c_str(), -1); - int res = dia.run(NULL, true); + int res = co_await dia.co_run(NULL, true); if (res == GTK_RESPONSE_YES) vte_terminal_feed_child(VTE_TERMINAL(_term), "y\n", 2); else @@ -293,11 +292,11 @@ void RGDebInstallProgress::conffile(gchar *conffile, gchar *status) last_term_action = time(NULL); } -void RGDebInstallProgress::startUpdate() +task RGDebInstallProgress::startUpdate() { child_has_exited = false; show(); - RGFlushInterface(); + co_await RGFlushInterface(); } void RGDebInstallProgress::cbCancel(GtkWidget *self, void *data) @@ -358,9 +357,8 @@ RGDebInstallProgress::RGDebInstallProgress(RGMainWindow *main, // point in showing this here if (_config->FindB("Volatile::Non-Interactive", false)) gtk_widget_hide(_autoClose); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(_autoClose), - _config->FindB("Synaptic::closeZvt", false)); - //_image = GTK_WIDGET(gtk_builder_get_object(_builder, "image")); + gtk_check_button_set_active(GTK_CHECK_BUTTON(_autoClose), + _config->FindB("Synaptic::closeZvt", false)); // work around for kdesudo blocking our SIGCHLD (LP: #156041) sigset_t sset; @@ -387,48 +385,46 @@ RGDebInstallProgress::RGDebInstallProgress(RGMainWindow *main, vte_terminal_set_font(VTE_TERMINAL(_term), fontdesc); pango_font_description_free(fontdesc); - gtk_box_pack_start( - GTK_BOX(GTK_WIDGET(gtk_builder_get_object(_builder, "hbox_vte"))), - _term, - TRUE, - TRUE, - 0); - g_signal_connect( - G_OBJECT(_term), "key-press-event", G_CALLBACK(key_press_event), this); - g_signal_connect(G_OBJECT(_term), - "button-press-event", - (GCallback)cbTerminalClicked, + gtk_widget_set_hexpand(_term, TRUE); + gtk_widget_set_vexpand(_term, TRUE); + gtk_box_append( + GTK_BOX(GTK_WIDGET(gtk_builder_get_object(_builder, "hbox_vte"))), _term); + + GtkEventController *key_controller = gtk_event_controller_key_new(); + g_signal_connect(G_OBJECT(key_controller), + "key-pressed", + G_CALLBACK(key_press_event), this); + gtk_widget_add_controller(_term, key_controller); - gtk_widget_show(_term); + GtkGesture *click_controller = gtk_gesture_click_new(); + gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(click_controller), 3); + g_signal_connect(G_OBJECT(click_controller), + "pressed", + (GCallback)cbTerminalClicked, + this); + gtk_widget_add_controller(_term, GTK_EVENT_CONTROLLER(click_controller)); - gtk_box_pack_end( + gtk_box_append( GTK_BOX(GTK_WIDGET(gtk_builder_get_object(_builder, "hbox_vte"))), - scrollbar, - FALSE, - FALSE, - 0); + scrollbar); // Terminal contextual menu - GtkWidget *menuitem; - _popupMenu = gtk_menu_new(); - menuitem = gtk_menu_item_new_with_label(_("Copy")); - g_object_set_data(G_OBJECT(menuitem), "me", this); + GSimpleActionGroup *term_actions = g_simple_action_group_new(); + GSimpleAction *term_action = + g_simple_action_new("terminal-action", G_VARIANT_TYPE_STRING); g_signal_connect( - menuitem, "activate", (GCallback)cbMenuitemClicked, (void *)EDIT_COPY); - gtk_menu_shell_append(GTK_MENU_SHELL(_popupMenu), menuitem); - gtk_widget_show(menuitem); - - menuitem = gtk_menu_item_new_with_label(_("Select All")); - g_object_set_data(G_OBJECT(menuitem), "me", this); - g_signal_connect(menuitem, - "activate", - (GCallback)cbMenuitemClicked, - (void *)EDIT_SELECT_ALL); - gtk_menu_shell_append(GTK_MENU_SHELL(_popupMenu), menuitem); - gtk_widget_show(menuitem); - - gtk_widget_show(scrollbar); + term_action, "activate", (GCallback)cbTeerminalAction, this); + g_action_map_add_action(G_ACTION_MAP(term_actions), G_ACTION(term_action)); + gtk_widget_insert_action_group(_win, "term", G_ACTION_GROUP(term_actions)); + + GMenu *menu = g_menu_new(); + g_menu_append_item(menu, + g_menu_item_new(_("Copy"), "term.terminal-action::copy")); + g_menu_append_item( + menu, + g_menu_item_new(_("Select All"), "term.terminal-action::select-all")); + _popupMenu = gtk_popover_menu_new_from_model(G_MENU_MODEL(menu)); gtk_window_set_default_size(GTK_WINDOW(_win), 500, -1); @@ -449,9 +445,6 @@ RGDebInstallProgress::RGDebInstallProgress(RGMainWindow *main, if (_userDialog == NULL) _userDialog = new RGUserDialog(this); - - gtk_window_set_urgency_hint(GTK_WINDOW(_win), FALSE); - // init the timer last_term_action = time(NULL); @@ -467,67 +460,72 @@ void RGDebInstallProgress::content_changed(GObject *object, gpointer data) me->last_term_action = time(NULL); } -gboolean RGDebInstallProgress::key_press_event(GtkWidget *widget, - GdkEventKey *event, - gpointer user_data) +gboolean RGDebInstallProgress::key_press_event( + GtkEventControllerKey *controller, + guint keyval, + guint keycode, + GdkModifierType state, + gpointer user_data) { RGDebInstallProgress *me = (RGDebInstallProgress *)user_data; // user pressed ctrl-c - if (event->keyval == GDK_KEY_c && event->state & GDK_CONTROL_MASK) { - gchar *summary = _("Ctrl-c pressed"); - char *msg = _("This will abort the operation and may leave the system " - "in a broken state. Are you sure you want to do that?"); - GtkWidget *dia = gtk_message_dialog_new(GTK_WINDOW(me->_win), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_WARNING, - GTK_BUTTONS_YES_NO, - "%s", - summary); - gtk_message_dialog_format_secondary_text( - GTK_MESSAGE_DIALOG(dia), "%s", msg); - int res = gtk_dialog_run(GTK_DIALOG(dia)); - gtk_widget_destroy(dia); - switch (res) { - case GTK_RESPONSE_YES: - return false; - case GTK_RESPONSE_NO: - return true; - } - } else if (event->keyval == GDK_KEY_C && - event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) { + if (keyval == GDK_KEY_c && state & GDK_CONTROL_MASK) { + start_task([me]() -> task { + gchar *summary = _("Ctrl-c pressed"); + char *msg = _("This will abort the operation and may leave the system " + "in a broken state. Are you sure you want to do that?"); + GtkWidget *dia = gtk_message_dialog_new(GTK_WINDOW(me->_win), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_YES_NO, + "%s", + summary); + gtk_message_dialog_format_secondary_text( + GTK_MESSAGE_DIALOG(dia), "%s", msg); + int res = co_await co_run_dialog(GTK_DIALOG(dia)); + gtk_window_destroy(GTK_WINDOW(dia)); + if (res == GTK_RESPONSE_YES) { + vte_terminal_feed_child( + VTE_TERMINAL(me->_term), "\x03", 1); // Send Ctrl+C to terminal + } + }); + return true; + } else if (keyval == GDK_KEY_C && + state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) { // ctrl+shift+C copy to clipboard to mimic gnome-terminal behavior me->terminalAction(me->_term, EDIT_COPY); return true; - } else if (event->keyval == GDK_KEY_a && event->state & GDK_CONTROL_MASK) { + } else if (keyval == GDK_KEY_a && state & GDK_CONTROL_MASK) { me->terminalAction(me->_term, EDIT_SELECT_ALL); return true; - } else if (event->keyval == GDK_KEY_A && - event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) { + } else if (keyval == GDK_KEY_A && + state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) { me->terminalAction(me->_term, EDIT_SELECT_NONE); return true; } return false; } -gboolean RGDebInstallProgress::cbTerminalClicked(GtkWidget *widget, - GdkEventButton *event, - gpointer user_data) +void RGDebInstallProgress::cbTerminalClicked(GtkGestureClick *gesture, + gint n_press, + gdouble x, + gdouble y, + gpointer user_data) { - if (event->button == 3) { - RGDebInstallProgress *me = (RGDebInstallProgress *)user_data; - gtk_menu_popup_at_pointer(GTK_MENU(me->_popupMenu), (GdkEvent *)event); - return true; - } - return false; + RGDebInstallProgress *me = (RGDebInstallProgress *)user_data; + GdkRectangle rect{x, y, 0, 0}; + gtk_popover_set_pointing_to(GTK_POPOVER(me->_popupMenu), &rect); + gtk_popover_popup(GTK_POPOVER(me->_popupMenu)); } -void RGDebInstallProgress::cbMenuitemClicked(GtkMenuItem *menuitem, +void RGDebInstallProgress::cbTeerminalAction(GSimpleAction *action, + GVariant *parameter, gpointer user_data) { - RGDebInstallProgress *me = - (RGDebInstallProgress *)g_object_get_data(G_OBJECT(menuitem), "me"); - me->terminalAction(me->_term, (TermAction)GPOINTER_TO_INT(user_data)); + RGDebInstallProgress *me = (RGDebInstallProgress *)user_data; + TermAction term_action = (TermAction)g_variant_get_int32(parameter); + me->terminalAction(me->_term, term_action); } void RGDebInstallProgress::terminalAction(GtkWidget *terminal, @@ -547,7 +545,7 @@ void RGDebInstallProgress::terminalAction(GtkWidget *terminal, } } -void RGDebInstallProgress::updateInterface() +task RGDebInstallProgress::updateInterface() { char buf[2]; static char line[1024] = ""; @@ -592,16 +590,12 @@ void RGDebInstallProgress::updateInterface() } else if (strstr(status, "pmconffile") != NULL) { // conffile-request from dpkg, needs to be parsed different // cout << split[2] << " " << split[3] << endl; - conffile(pkg, split[3]); + co_await conffile(pkg, split[3]); } else { _startCounting = true; gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(_pbarTotal), 0); } - // reset the urgency hint, something changed on the terminal - if (gtk_window_get_urgency_hint(GTK_WINDOW(_win))) - gtk_window_set_urgency_hint(GTK_WINDOW(_win), FALSE); - float val = atof(percent) / 100.0; // cout << "progress: " << val << endl; if (fabs(val - gtk_progress_bar_get_fraction( @@ -624,7 +618,7 @@ void RGDebInstallProgress::updateInterface() time_t now = time(NULL); if (!_startCounting) { - usleep(100000); + co_await sleep_ms{100}; gtk_progress_bar_pulse(GTK_PROGRESS_BAR(_pbarTotal)); // wait until we get the first message from apt last_term_action = now; @@ -643,18 +637,10 @@ void RGDebInstallProgress::updateInterface() w = GTK_WIDGET(gtk_builder_get_object(_builder, "expander_terminal")); gtk_expander_set_expanded(GTK_EXPANDER(w), TRUE); last_term_action = time(NULL); - // try to get the attention of the user - gtk_window_set_urgency_hint(GTK_WINDOW(_win), TRUE); } - - if (gtk_events_pending()) { - while (gtk_events_pending()) - gtk_main_iteration(); - } else { - // 25fps - usleep(1000000 / 25); - } + // 25fps + co_await sleep_ms{40}; } std::optional RGDebInstallProgress::start( @@ -672,16 +658,18 @@ std::optional RGDebInstallProgress::start( if (_child_id < 0) { cerr << "vte_terminal_forkpty() failed. " << strerror(errno) << endl; res = pkgPackageManager::Failed; - GtkWidget *dialog; - dialog = gtk_message_dialog_new(GTK_WINDOW(window()), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_ERROR, - GTK_BUTTONS_CLOSE, - NULL); - gtk_message_dialog_set_markup(GTK_MESSAGE_DIALOG(dialog), - _("Error failed to fork pty")); - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); + /* + GtkWidget *dialog; + dialog = gtk_message_dialog_new(GTK_WINDOW(window()), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + NULL); + gtk_message_dialog_set_markup(GTK_MESSAGE_DIALOG(dialog), + _("Error failed to fork pty")); + co_await co_run_dialog(GTK_DIALOG(dialog)); + gtk_window_destroy(GTK_WINDOW(dialog)); + */ return res; } else if (_child_id == 0) { int fd[2]; @@ -753,18 +741,18 @@ std::optional RGDebInstallProgress::poll() } -void RGDebInstallProgress::finishUpdate() +task RGDebInstallProgress::finishUpdate() { if (_startCounting) { gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(_pbarTotal), 1.0); } - RGFlushInterface(); + co_await RGFlushInterface(); GtkWidget *_closeB = GTK_WIDGET(gtk_builder_get_object(_builder, "button_close")); gtk_widget_set_sensitive(_closeB, TRUE); - bool autoClose = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(_autoClose)); + bool autoClose = gtk_check_button_get_active(GTK_CHECK_BUTTON(_autoClose)); if (res == 0) { gtk_widget_grab_focus(_closeB); if (autoClose) @@ -788,17 +776,14 @@ void RGDebInstallProgress::finishUpdate() GTK_WIDGET(gtk_builder_get_object(_builder, "image_finished")); switch (res) { case 0: // success - gtk_image_set_from_icon_name( - GTK_IMAGE(img), "synaptic", GTK_ICON_SIZE_DIALOG); + gtk_image_set_from_icon_name(GTK_IMAGE(img), "synaptic"); break; case 1: // error - gtk_image_set_from_icon_name( - GTK_IMAGE(img), "dialog-error", GTK_ICON_SIZE_DIALOG); - _userDialog->showErrors(); + gtk_image_set_from_icon_name(GTK_IMAGE(img), "dialog-error"); + co_await _userDialog->showErrors(); break; case 2: // incomplete - gtk_image_set_from_icon_name( - GTK_IMAGE(img), "dialog-information", GTK_ICON_SIZE_DIALOG); + gtk_image_set_from_icon_name(GTK_IMAGE(img), "dialog-information"); break; } gtk_widget_show(img); @@ -806,20 +791,19 @@ void RGDebInstallProgress::finishUpdate() // wait for user action while (true) { // events - while (gtk_events_pending()) - gtk_main_iteration(); + co_await glib_idle{}; // user clicked "close" button if (_updateFinished) break; // user has autoClose set *and* there was no error - autoClose = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(_autoClose)); + autoClose = gtk_check_button_get_active(GTK_CHECK_BUTTON(_autoClose)); if (autoClose && res != 1) break; // wait a bit - g_usleep(100000); + co_await sleep_ms{100}; } // set the value again, it may have changed diff --git a/gtk/rgdebinstallprogress.h b/gtk/rgdebinstallprogress.h index 7bbf717b8..7caa9119c 100644 --- a/gtk/rgdebinstallprogress.h +++ b/gtk/rgdebinstallprogress.h @@ -120,7 +120,7 @@ class RGDebInstallProgress : public RInstallProgress, public RGGtkBuilderWindow virtual void prepare(RPackageLister *lister); - void conffile(gchar *conffile, gchar *status); + [[nodiscard]] task conffile(gchar *conffile, gchar *status); // gtk stuff static void cbCancel(GtkWidget *self, void *data); @@ -129,13 +129,19 @@ class RGDebInstallProgress : public RInstallProgress, public RGGtkBuilderWindow static void expander_callback(GObject *object, GParamSpec *param_spec, gpointer user_data); - static gboolean key_press_event(GtkWidget *widget, - GdkEventKey *event, + static gboolean key_press_event(GtkEventControllerKey *controller, + guint keyval, + guint keycode, + GdkModifierType state, gpointer user_data); - static gboolean cbTerminalClicked(GtkWidget *widget, - GdkEventButton *event, - gpointer user_data); - static void cbMenuitemClicked(GtkMenuItem *menuitem, gpointer user_data); + static void cbTerminalClicked(GtkGestureClick *gesture, + gint n_press, + gdouble x, + gdouble y, + gpointer user_data); + static void cbTeerminalAction(GSimpleAction *action, + GVariant *parameter, + gpointer user_data); public: RGDebInstallProgress(RGMainWindow *main, RPackageLister *lister); @@ -147,9 +153,9 @@ class RGDebInstallProgress : public RInstallProgress, public RGGtkBuilderWindow int totalPackages = 0) override; virtual std::optional poll() override; - virtual void startUpdate() override; - virtual void updateInterface() override; - virtual void finishUpdate() override; + [[nodiscard]] virtual task startUpdate() override; + [[nodiscard]] virtual task updateInterface() override; + [[nodiscard]] virtual task finishUpdate() override; }; #endif // WITH_DPKG_STATUSFD diff --git a/gtk/rgdummyinstallprogress.cc b/gtk/rgdummyinstallprogress.cc index 7a355bb45..b2387e9dc 100644 --- a/gtk/rgdummyinstallprogress.cc +++ b/gtk/rgdummyinstallprogress.cc @@ -26,25 +26,17 @@ #include "rgutils.h" -#include -#include - -void RGDummyInstallProgress::startUpdate() +task RGDummyInstallProgress::startUpdate() { - RGFlushInterface(); + co_await RGFlushInterface(); } -void RGDummyInstallProgress::finishUpdate() +task RGDummyInstallProgress::finishUpdate() { - RGFlushInterface(); + co_await RGFlushInterface(); } -void RGDummyInstallProgress::updateInterface() +task RGDummyInstallProgress::updateInterface() { - if (gtk_events_pending()) { - while (gtk_events_pending()) - gtk_main_iteration(); - } else { - usleep(1000000); - } + co_await sleep_ms{1000}; } diff --git a/gtk/rgdummyinstallprogress.h b/gtk/rgdummyinstallprogress.h index 8d2504ea8..206f7d788 100644 --- a/gtk/rgdummyinstallprogress.h +++ b/gtk/rgdummyinstallprogress.h @@ -28,11 +28,12 @@ class RGDummyInstallProgress : public RInstallProgress { + public: RGDummyInstallProgress() : RInstallProgress() {}; virtual ~RGDummyInstallProgress() {}; - virtual void startUpdate() override; - virtual void updateInterface() override; - virtual void finishUpdate() override; + [[nodiscard]] virtual task startUpdate() override; + [[nodiscard]] virtual task updateInterface() override; + [[nodiscard]] virtual task finishUpdate() override; }; diff --git a/gtk/rgfetchprogress.cc b/gtk/rgfetchprogress.cc index 5bbad5b75..feb44f4bb 100644 --- a/gtk/rgfetchprogress.cc +++ b/gtk/rgfetchprogress.cc @@ -129,10 +129,6 @@ RGFetchProgress::RGFetchProgress(RGWindow *win) gtk_widget_realize(_win); - // reset the urgency hint here (gtk seems to like showing it for - // dialogs that come up) - gtk_window_set_urgency_hint(GTK_WINDOW(_win), FALSE); - // emit a signal if the user changed the cursor g_signal_connect( G_OBJECT(_table), "cursor-changed", G_CALLBACK(cursorChanged), this); @@ -183,7 +179,7 @@ void RGFetchProgress::setDescription(string mainText, string secondText) g_free(str); } -bool RGFetchProgress::MediaChange(string Media, string Drive) +task RGFetchProgress::MediaChange(string Media, string Drive) { gchar *msg; @@ -192,11 +188,11 @@ bool RGFetchProgress::MediaChange(string Media, string Drive) Drive.c_str()); RGUserDialog userDialog(this); - _cancelled = !userDialog.proceed(msg); + _cancelled = !co_await userDialog.proceed(msg); - RGFlushInterface(); + co_await RGFlushInterface(); g_free(msg); - return true; + co_return true; #if 0 // this code can be used when apt fixed ubuntu #2281 (patch pending) bool res = !userDialog.proceed(msg); @@ -233,65 +229,58 @@ void RGFetchProgress::updateStatus(pkgAcquire::ItemDesc &Itm, int status) } } -void RGFetchProgress::IMSHit(pkgAcquire::ItemDesc &Itm) +task RGFetchProgress::IMSHit(pkgAcquire::ItemDesc &Itm) { // cout << "void RGFetchProgress::IMSHit(pkgAcquire::ItemDesc &Itm)" << endl; updateStatus(Itm, DLHit); - RGFlushInterface(); + co_await RGFlushInterface(); } -void RGFetchProgress::Fetch(pkgAcquire::ItemDesc &Itm) +task RGFetchProgress::Fetch(pkgAcquire::ItemDesc &Itm) { updateStatus(Itm, DLQueued); - RGFlushInterface(); + co_await RGFlushInterface(); } -void RGFetchProgress::Done(pkgAcquire::ItemDesc &Itm) +task RGFetchProgress::Done(pkgAcquire::ItemDesc &Itm) { updateStatus(Itm, DLDone); - RGFlushInterface(); + co_await RGFlushInterface(); } -void RGFetchProgress::Fail(pkgAcquire::ItemDesc &Itm) +task RGFetchProgress::Fail(pkgAcquire::ItemDesc &Itm) { if (Itm.Owner->Status == pkgAcquire::Item::StatIdle) - return; + co_return; updateStatus(Itm, DLFailed); - RGFlushInterface(); + co_await RGFlushInterface(); } bool RGFetchProgress::Pulse(pkgAcquire *Owner) { // cout << "RGFetchProgress::Pulse(pkgAcquire *Owner)" << endl; - pkgAcquireStatus::Pulse(Owner); + _status.Pulse(Owner); // only show here if there is actually something to download/get - if (TotalBytes > 0 && !gtk_widget_get_visible(_win)) + if (_status.TotalBytes > 0 && !gtk_widget_get_visible(_win)) show(); - float percent = long(double((CurrentBytes + CurrentItems) * 100.0) / - double(TotalBytes + TotalItems)); + float percent = + long(double((_status.CurrentBytes + _status.CurrentItems) * 100.0) / + double(_status.TotalBytes + _status.TotalItems)); // work-around a stupid problem with libapt - if (CurrentItems == TotalItems) + if (_status.CurrentItems == _status.TotalItems) percent = 100.0; - // only do something if there is some real progress - if (fabsf(percent - - gtk_progress_bar_get_fraction(GTK_PROGRESS_BAR(_mainProgressBar)) * - 100.0) < 0.1) { - RGFlushInterface(); - return !_cancelled; - } - gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(_mainProgressBar), percent / 100.0); @@ -316,20 +305,22 @@ bool RGFetchProgress::Pulse(pkgAcquire *Owner) } unsigned long ETA; - if (CurrentCPS > 0) - ETA = (unsigned long)((TotalBytes - CurrentBytes) / CurrentCPS); + if (_status.CurrentCPS > 0) + ETA = (unsigned long)((_status.TotalBytes - _status.CurrentBytes) / + _status.CurrentCPS); else ETA = 0; // if the ETA is greater than two weeks, show unknown time if (ETA > 14 * 24 * 60 * 60) ETA = 0; - long i = CurrentItems < TotalItems ? CurrentItems + 1 : CurrentItems; + long i = _status.CurrentItems < _status.TotalItems ? _status.CurrentItems + 1 + : _status.CurrentItems; gchar *s; GObject *label_eta = gtk_builder_get_object(_builder, "label_eta"); - if (CurrentCPS != 0 && ETA != 0) { + if (_status.CurrentCPS != 0 && ETA != 0) { s = g_strdup_printf(_("Download rate: %s/s - %s remaining"), - SizeToStr(CurrentCPS).c_str(), + SizeToStr(_status.CurrentCPS).c_str(), TimeToStr(ETA).c_str()); gtk_label_set_text(GTK_LABEL(GTK_WIDGET(label_eta)), s); g_free(s); @@ -337,35 +328,33 @@ bool RGFetchProgress::Pulse(pkgAcquire *Owner) gtk_label_set_text(GTK_LABEL(GTK_WIDGET(label_eta)), _("Download rate: ...")); } - s = g_strdup_printf(_("Downloading file %li of %li"), i, TotalItems); + s = g_strdup_printf(_("Downloading file %li of %li"), i, _status.TotalItems); gtk_progress_bar_set_text(GTK_PROGRESS_BAR(_mainProgressBar), s); g_free(s); - RGFlushInterface(); - return !_cancelled; } -void RGFetchProgress::Start() +task RGFetchProgress::Start() { // cout << "RGFetchProgress::Start()" << endl; - pkgAcquireStatus::Start(); + _status.Start(); _cancelled = false; - RGFlushInterface(); + co_await RGFlushInterface(); } -void RGFetchProgress::Stop() +task RGFetchProgress::Stop() { // cout << "RGFetchProgress::Stop()" << endl; - RGFlushInterface(); hide(); - pkgAcquireStatus::Stop(); + _status.Stop(); // FIXME: this needs to be handled in a better way (gtk-2 maybe?) sleep(1); // this sucks, but if ommited, the window will not always // closed (e.g. when a package is only deleted) - RGFlushInterface(); + + co_await RGFlushInterface(); } void RGFetchProgress::stopDownload(GtkWidget *self, void *data) diff --git a/gtk/rgfetchprogress.h b/gtk/rgfetchprogress.h index e97eb574d..7a2ee2c5f 100644 --- a/gtk/rgfetchprogress.h +++ b/gtk/rgfetchprogress.h @@ -24,7 +24,10 @@ #include "config.h" // IWYU pragma: associated +#include "rpackagelister.h" #include "rggtkbuilderwindow.h" +#include "coroutines.h" +#include "racquireasync.h" #include #include @@ -34,8 +37,20 @@ class RGWindow; -class RGFetchProgress : public pkgAcquireStatus, public RGGtkBuilderWindow +class RPkgAcquireStatusImpl : public pkgAcquireStatus { + friend class RGFetchProgress; + + protected: + virtual bool MediaChange(std::string Media, std::string Drive) override + { + return true; + } +}; + +class RGFetchProgress : public RPkgAcquireStatusAsync, public RGGtkBuilderWindow +{ + RPkgAcquireStatusImpl _status; struct Item { @@ -72,13 +87,15 @@ class RGFetchProgress : public pkgAcquireStatus, public RGGtkBuilderWindow // GdkPixmap *statusDraw(int width, int height, int status); public: - virtual bool MediaChange(std::string Media, std::string Drive); - virtual void IMSHit(pkgAcquire::ItemDesc &Itm); - virtual void Fetch(pkgAcquire::ItemDesc &Itm); - virtual void Done(pkgAcquire::ItemDesc &Itm); - virtual void Fail(pkgAcquire::ItemDesc &Itm); - virtual void Start(); - virtual void Stop(); + [[nodiscard]] virtual task MediaChange(std::string Media, + std::string Drive) override; + [[nodiscard]] virtual task IMSHit(pkgAcquire::ItemDesc &Itm) override; + [[nodiscard]] virtual task Fetch(pkgAcquire::ItemDesc &Itm) override; + [[nodiscard]] virtual task Done(pkgAcquire::ItemDesc &Itm) override; + [[nodiscard]] virtual task Fail(pkgAcquire::ItemDesc &Itm) override; + [[nodiscard]] virtual task Start() override; + [[nodiscard]] virtual task Stop() override; + virtual void close() override; bool Pulse(pkgAcquire *Owner); diff --git a/gtk/rgfiltermanager.cc b/gtk/rgfiltermanager.cc index 865a9e774..ecf74b53a 100644 --- a/gtk/rgfiltermanager.cc +++ b/gtk/rgfiltermanager.cc @@ -35,9 +35,6 @@ #include #include -#include -#include -#include #include #include #include @@ -72,9 +69,6 @@ RGFilterManagerWindow::RGFilterManagerWindow(RGWindow *win, setTitle(_("Filters")); - _busyCursor = - gdk_cursor_new_for_display(gdk_display_get_default(), GDK_WATCH); - _comboPatternWhat = GTK_WIDGET(gtk_builder_get_object(_builder, "combobox_pattern_what")); comboStore = @@ -165,7 +159,7 @@ RGFilterManagerWindow::RGFilterManagerWindow(RGWindow *win, this); g_signal_connect( - G_OBJECT(_win), "delete_event", G_CALLBACK(deleteEventAction), this); + G_OBJECT(_win), "close-request", G_CALLBACK(deleteEventAction), this); _filterDetailsBox = @@ -270,8 +264,6 @@ RGFilterManagerWindow::RGFilterManagerWindow(RGWindow *win, // set the details filter to not sesitive gtk_widget_set_sensitive(_filterDetailsBox, false); - - skipTaskbar(true); } void RGFilterManagerWindow::filterNameChanged(GObject *o, gpointer data) @@ -282,7 +274,7 @@ void RGFilterManagerWindow::filterNameChanged(GObject *o, gpointer data) if (me->_selectedPath == NULL || me->_selectedFilter == NULL) return; - const gchar *s = gtk_entry_get_text(GTK_ENTRY(me->_filterEntry)); + const gchar *s = gtk_editable_get_text(GTK_EDITABLE(me->_filterEntry)); // test for empty filtername if (s == NULL || !strcmp(s, "")) return; @@ -302,8 +294,8 @@ void RGFilterManagerWindow::statusInvertClicked(GObject *o, gpointer data) gboolean x; for (int i = 0; i < NrOfStatusBits; i++) { - x = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(me->_statusB[i])); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(me->_statusB[i]), !x); + x = gtk_check_button_get_active(GTK_CHECK_BUTTON(me->_statusB[i])); + gtk_check_button_set_active(GTK_CHECK_BUTTON(me->_statusB[i]), !x); } } @@ -313,7 +305,7 @@ void RGFilterManagerWindow::statusAllClicked(GObject *o, gpointer data) // cout << "RGFilterManagerWindow::statusAllClicked()"<_statusB[i]), TRUE); + gtk_check_button_set_active(GTK_CHECK_BUTTON(me->_statusB[i]), TRUE); } } @@ -322,7 +314,7 @@ void RGFilterManagerWindow::statusNoneClicked(GObject *o, gpointer data) RGFilterManagerWindow *me = (RGFilterManagerWindow *)data; // cout << "RGFilterManagerWindow::statusNoneClicked()"<_statusB[i]), FALSE); + gtk_check_button_set_active(GTK_CHECK_BUTTON(me->_statusB[i]), FALSE); } } @@ -379,7 +371,7 @@ void RGFilterManagerWindow::patternChanged(GObject *o, gpointer data) GtkWidget *patternEntry = GTK_WIDGET(gtk_builder_get_object(me->_builder, "entry_pattern_text")); - const gchar *str = gtk_entry_get_text(GTK_ENTRY(patternEntry)); + const gchar *str = gtk_editable_get_text(GTK_EDITABLE(patternEntry)); // get path GtkTreeSelection *selection; @@ -442,7 +434,7 @@ void RGFilterManagerWindow::patternSelectionChanged(GtkTreeSelection *selection, GtkWidget *patternText = GTK_WIDGET(gtk_builder_get_object(me->_builder, "entry_pattern_text")); - gtk_entry_set_text(GTK_ENTRY(patternText), text); + gtk_editable_set_text(GTK_EDITABLE(patternText), text); g_free(dopatt); g_free(what); g_free(text); @@ -453,9 +445,7 @@ void RGFilterManagerWindow::patternSelectionChanged(GtkTreeSelection *selection, } } -gint RGFilterManagerWindow::deleteEventAction(GtkWidget *widget, - GdkEvent *event, - gpointer data) +gint RGFilterManagerWindow::deleteEventAction(GtkWidget *widget, gpointer data) { RGFilterManagerWindow *me = (RGFilterManagerWindow *)data; me->cancelAction(widget, data); @@ -493,7 +483,7 @@ void RGFilterManagerWindow::readFilters() editFilter(); } - gtk_entry_set_text(GTK_ENTRY(_filterEntry), ""); + gtk_editable_set_text(GTK_EDITABLE(_filterEntry), ""); // cleanup previous saved filters for (vector::const_iterator I = _saveFilters.begin(); @@ -536,7 +526,7 @@ void RGFilterManagerWindow::selectAction(GtkTreeSelection *selection, // endl; me->_selectedFilter = filter; // cout << "You selected" << filter << endl; - gtk_entry_set_text(GTK_ENTRY(me->_filterEntry), filtername); + gtk_editable_set_text(GTK_EDITABLE(me->_filterEntry), filtername); g_free(filtername); me->editFilter(); @@ -611,12 +601,12 @@ void RGFilterManagerWindow::setSectionFilter(RSectionPackageFilter &f) GtkWidget *_exclGB = GTK_WIDGET(gtk_builder_get_object(_builder, "radiobutton_excl")); assert(_exclGB); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(_inclGB), FALSE); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(_exclGB), FALSE); + gtk_check_button_set_active(GTK_CHECK_BUTTON(_inclGB), FALSE); + gtk_check_button_set_active(GTK_CHECK_BUTTON(_exclGB), FALSE); if (f.inclusive()) { - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(_inclGB), TRUE); + gtk_check_button_set_active(GTK_CHECK_BUTTON(_inclGB), TRUE); } else { - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(_exclGB), TRUE); + gtk_check_button_set_active(GTK_CHECK_BUTTON(_exclGB), TRUE); } } @@ -628,8 +618,8 @@ void RGFilterManagerWindow::setStatusFilter(RStatusPackageFilter &f) int type = f.status(); for (i = 0; i < NrOfStatusBits; i++) { - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(_statusB[i]), - (type & StatusMasks[i]) ? TRUE : FALSE); + gtk_check_button_set_active(GTK_CHECK_BUTTON(_statusB[i]), + (type & StatusMasks[i]) ? TRUE : FALSE); } } @@ -693,13 +683,13 @@ void RGFilterManagerWindow::setPatternFilter(RPatternPackageFilter &f) } if (f.getAndMode()) { - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object( - _builder, "radiobutton_properties_and")), - TRUE); + gtk_check_button_set_active(GTK_CHECK_BUTTON(gtk_builder_get_object( + _builder, "radiobutton_properties_and")), + TRUE); } else { - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object( - _builder, "radiobutton_properties_or")), - TRUE); + gtk_check_button_set_active(GTK_CHECK_BUTTON(gtk_builder_get_object( + _builder, "radiobutton_properties_or")), + TRUE); } } @@ -714,7 +704,7 @@ void RGFilterManagerWindow::getSectionFilter(RSectionPackageFilter &f) GtkWidget *w = GTK_WIDGET(gtk_builder_get_object(_builder, "radiobutton_incl")); assert(w); - int inclusive = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)); + int inclusive = gtk_check_button_get_active(GTK_CHECK_BUTTON(w)); f.setInclusive(inclusive == TRUE); f.clear(); @@ -747,7 +737,7 @@ void RGFilterManagerWindow::getStatusFilter(RStatusPackageFilter &f) int i; int type = 0; for (i = 0; i < NrOfStatusBits; i++) { - if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(_statusB[i]))) + if (gtk_check_button_get_active(GTK_CHECK_BUTTON(_statusB[i]))) type |= StatusMasks[i]; } f.setStatus(type); @@ -798,7 +788,7 @@ void RGFilterManagerWindow::getPatternFilter(RPatternPackageFilter &f) g_free(text); } - f.setAndMode(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( + f.setAndMode(gtk_check_button_get_active(GTK_CHECK_BUTTON( gtk_builder_get_object(_builder, "radiobutton_properties_and")))); } diff --git a/gtk/rgfiltermanager.h b/gtk/rgfiltermanager.h index 82bddcc1a..5aee0529a 100644 --- a/gtk/rgfiltermanager.h +++ b/gtk/rgfiltermanager.h @@ -31,9 +31,6 @@ #include "rpackagefilter.h" #include -#include -#include -#include #include #include #include @@ -97,9 +94,7 @@ class RGFilterManagerWindow : public RGGtkBuilderWindow static void includeTagAction(GtkWidget *self, void *data); static void excludeTagAction(GtkWidget *self, void *data); - static gint deleteEventAction(GtkWidget *widget, - GdkEvent *event, - gpointer data); + static gint deleteEventAction(GtkWidget *widget, gpointer data); static void selectAction(GtkTreeSelection *selection, gpointer data); @@ -123,7 +118,6 @@ class RGFilterManagerWindow : public RGGtkBuilderWindow GtkWidget *_comboPatternWhat; GtkWidget *_comboPatternDo; GtkWidget *_filterEntry; /* GtkEntry */ - GdkCursor *_busyCursor; GtkWidget *_filterDetailsBox; // detail box @@ -133,15 +127,8 @@ class RGFilterManagerWindow : public RGGtkBuilderWindow GtkTreePath *_selectedPath; RFilter *_selectedFilter; enum { NAME_COLUMN, FILTER_COLUMN, N_COLUMNS }; - - // the section list - GtkWidget *_sectionList; /* GtkTreeView */ - GtkListStore *_sectionListStore; enum { SECTION_COLUMN, SECTION_N_COLUMNS }; - // status filter buttons - GtkWidget *_statusB[NrOfStatusBits]; - // the pattern list enum { PATTERN_DO_COLUMN, @@ -149,6 +136,13 @@ class RGFilterManagerWindow : public RGGtkBuilderWindow PATTERN_TEXT_COLUMN, PATTERN_N_COLUMNS }; + + // the section list + GtkWidget *_sectionList; /* GtkTreeView */ + GtkListStore *_sectionListStore; + + // status filter buttons + GtkWidget *_statusB[NrOfStatusBits]; GtkWidget *_patternList; /* GtkTreeView */ GtkListStore *_patternListStore; bool setPatternRow(int row, diff --git a/gtk/rgfindwindow.cc b/gtk/rgfindwindow.cc index 80cdf6902..563314cf6 100644 --- a/gtk/rgfindwindow.cc +++ b/gtk/rgfindwindow.cc @@ -30,9 +30,6 @@ #include #include #include -#include -#include -#include #include #include @@ -147,5 +144,4 @@ RGFindWindow::RGFindWindow(RGWindow *win) gtk_widget_set_sensitive(_findB, FALSE); setTitle(_("Find")); - skipTaskbar(true); } diff --git a/gtk/rgfindwindow.h b/gtk/rgfindwindow.h index 3e18149a8..8922e91b4 100644 --- a/gtk/rgfindwindow.h +++ b/gtk/rgfindwindow.h @@ -28,7 +28,6 @@ #include "rggtkbuilderwindow.h" #include -#include #include class RGFindWindow; diff --git a/gtk/rggtkbuilderwindow.cc b/gtk/rggtkbuilderwindow.cc index 679fc4034..ee6d4a3c1 100644 --- a/gtk/rggtkbuilderwindow.cc +++ b/gtk/rggtkbuilderwindow.cc @@ -45,8 +45,6 @@ RGGtkBuilderWindow::RGGtkBuilderWindow(RGWindow *parent, string name, string mainName) { - _busyCursor = - gdk_cursor_new_for_display(gdk_display_get_default(), GDK_WATCH); _builder = gtk_builder_new(); gchar *main_widget = NULL; @@ -73,7 +71,6 @@ RGGtkBuilderWindow::RGGtkBuilderWindow(RGWindow *parent, gtk_window_set_transient_for(GTK_WINDOW(_win), GTK_WINDOW(parent->window())); - gtk_window_set_position(GTK_WINDOW(_win), GTK_WIN_POS_CENTER_ON_PARENT); gtk_window_set_icon_name(GTK_WINDOW(_win), "synaptic"); g_free(main_widget); @@ -210,7 +207,7 @@ bool RGGtkBuilderWindow::setPixmap(const char *widget_name, const char *value) cout << "textview == NULL with: " << widget_name << endl; return false; } - gtk_image_set_from_icon_name(GTK_IMAGE(pix), value, GTK_ICON_SIZE_BUTTON); + gtk_image_set_from_icon_name(GTK_IMAGE(pix), value); return true; } @@ -219,9 +216,9 @@ void RGGtkBuilderWindow::setBusyCursor(bool flag) { if (flag) { if (gtk_widget_get_visible(_win)) - gdk_window_set_cursor(gtk_widget_get_window(window()), _busyCursor); + gtk_widget_set_cursor_from_name(window(), "watch"); } else { if (gtk_widget_get_visible(_win)) - gdk_window_set_cursor(gtk_widget_get_window(window()), NULL); + gtk_widget_set_cursor(window(), NULL); } } diff --git a/gtk/rggtkbuilderwindow.h b/gtk/rggtkbuilderwindow.h index 6fb402752..d4f9c383d 100644 --- a/gtk/rggtkbuilderwindow.h +++ b/gtk/rggtkbuilderwindow.h @@ -34,18 +34,12 @@ class RGGtkBuilderWindow : public RGWindow { protected: GtkBuilder *_builder; - GdkCursor *_busyCursor; public: RGGtkBuilderWindow(RGWindow *parent, std::string name, std::string main_widget = ""); - void skipTaskbar(bool value) - { - gtk_window_set_skip_taskbar_hint(GTK_WINDOW(_win), value); - } - // show busy cursor over main window void setBusyCursor(bool flag = true); diff --git a/gtk/rgiconlegend.cc b/gtk/rgiconlegend.cc index 51b93950c..ac21e06c5 100644 --- a/gtk/rgiconlegend.cc +++ b/gtk/rgiconlegend.cc @@ -31,10 +31,6 @@ #include #include #include -#include -#include -#include -#include #include #include @@ -55,36 +51,30 @@ RGIconLegendPanel::RGIconLegendPanel(RGWindow *parent) "clicked", G_CALLBACK(closeWindow), this); - GtkWidget *vbox = GTK_WIDGET(gtk_builder_get_object(_builder, "vbox_main")); - assert(vbox); + GtkGrid *grid = GTK_GRID(gtk_builder_get_object(_builder, "grid_main")); + assert(grid); - GtkWidget *hbox, *label, *pix; - - for (int i = 0; i < RGPackageStatus::N_STATUS_COUNT; i++) { - hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12); + GtkWidget *label, *pix; + int i; + for (i = 0; i < RGPackageStatus::N_STATUS_COUNT; i++) { pix = gtk_image_new_from_icon_name( - RGPackageStatus::pkgStatus.getIconName(i), GTK_ICON_SIZE_BUTTON); - gtk_box_pack_start(GTK_BOX(hbox), pix, FALSE, FALSE, 0); + RGPackageStatus::pkgStatus.getIconName(i)); + gtk_grid_attach(grid, pix, 0, i + 1, 1, 1); label = gtk_label_new(RGPackageStatus::pkgStatus.getLongStatusString(i)); - gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); - - gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + gtk_label_set_xalign(GTK_LABEL(label), 0.0); + gtk_grid_attach(grid, label, 1, i + 1, 1, 1); } // package support status - hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12); - pix = - gtk_image_new_from_icon_name("package-supported", GTK_ICON_SIZE_BUTTON); - gtk_box_pack_start(GTK_BOX(hbox), pix, FALSE, FALSE, 0); + pix = gtk_image_new_from_icon_name("package-supported"); + gtk_grid_attach(grid, pix, 0, i + 1, 1, 1); label = gtk_label_new( _config->Find("Synaptic::supported-text", _("Package is supported")) .c_str()); - gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); - gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + gtk_label_set_xalign(GTK_LABEL(label), 0.0); + gtk_grid_attach(grid, label, 1, i + 1, 1, 1); - gtk_widget_show_all(vbox); - // skipTaskbar(true); show(); } diff --git a/gtk/rginstallprogress.cc b/gtk/rginstallprogress.cc index c09a3b941..2777083fc 100644 --- a/gtk/rginstallprogress.cc +++ b/gtk/rginstallprogress.cc @@ -38,9 +38,7 @@ #include #include #include -#include #include -#include #include class RGWindow; @@ -64,15 +62,13 @@ RGInstallProgressMsgs::RGInstallProgressMsgs(RGWindow *win) gtk_css_provider_load_from_data( _cssProvider, "TextView { font-family: helvetica; font-size: 10pt; }", - -1, - NULL); + -1); gtk_style_context_add_provider(styleContext, GTK_STYLE_PROVIDER(_cssProvider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); PangoFontDescription *font; font = pango_font_description_from_string("helvetica bold 10"); gtk_text_buffer_create_tag(_textBuffer, "bold", "font-desc", font, NULL); - skipTaskbar(true); } RGInstallProgressMsgs::~RGInstallProgressMsgs() @@ -82,13 +78,13 @@ RGInstallProgressMsgs::~RGInstallProgressMsgs() void RGInstallProgressMsgs::onCloseClicked(GtkWidget *self, void *data) { - // RGInstallProgressMsgs *me = (RGInstallProgressMsgs*)data; - gtk_main_quit(); + RGInstallProgressMsgs *me = (RGInstallProgressMsgs *)data; + me->close(); } void RGInstallProgressMsgs::close() { - gtk_main_quit(); + gtk_window_close(GTK_WINDOW(_win)); } void RGInstallProgressMsgs::addText(const char *text, bool bold) @@ -129,20 +125,20 @@ bool RGInstallProgressMsgs::empty() return gtk_text_buffer_get_char_count(_textBuffer) == 0; } -void RGInstallProgressMsgs::run() +task RGInstallProgressMsgs::run() { show(); - gtk_main(); + co_await co_run_window(GTK_WINDOW(_win)); hide(); } -void RGInstallProgress::startUpdate() +task RGInstallProgress::startUpdate() { show(); - RGFlushInterface(); + co_await RGFlushInterface(); } -void RGInstallProgress::finishUpdate() +task RGInstallProgress::finishUpdate() { char buf[1024]; memset(buf, 0, 1024); @@ -154,8 +150,8 @@ void RGInstallProgress::finishUpdate() GTK_BUTTONS_OK, _("APT system reports:\n%s"), utf8(buf)); - gtk_dialog_run(GTK_DIALOG(dia)); - gtk_widget_destroy(dia); + co_await co_run_dialog(GTK_DIALOG(dia)); + gtk_window_destroy(GTK_WINDOW(dia)); } if (_startCounting) { @@ -165,16 +161,16 @@ void RGInstallProgress::finishUpdate() if (_msgs.empty() == false && _config->FindB("Synaptic::IgnorePMOutput", false) == false) - _msgs.run(); + co_await _msgs.run(); - RGFlushInterface(); + co_await RGFlushInterface(); hide(); } void RGInstallProgress::prepare(RPackageLister *lister) { - for (unsigned int row = 0; row < lister->packagesSize(); row++) { + for (int row = 0; row < lister->packagesSize(); row++) { RPackage *elem = lister->getPackage(row); // Is it going to be seen? @@ -191,7 +187,7 @@ void RGInstallProgress::prepare(RPackageLister *lister) } } -void RGInstallProgress::updateInterface() +task RGInstallProgress::updateInterface() { char buf[2]; static char line[1024] = ""; @@ -248,15 +244,12 @@ void RGInstallProgress::updateInterface() } } - if (gtk_events_pending()) { - while (gtk_events_pending()) - gtk_main_iteration(); - } else { - usleep(5000); - if (_startCounting == false) { - gtk_progress_bar_pulse(GTK_PROGRESS_BAR(_pbar)); - gtk_progress_bar_pulse(GTK_PROGRESS_BAR(_pbarTotal)); - } + co_await RGFlushInterface(); + co_await sleep_ms{5}; + + if (_startCounting == false) { + gtk_progress_bar_pulse(GTK_PROGRESS_BAR(_pbar)); + gtk_progress_bar_pulse(GTK_PROGRESS_BAR(_pbarTotal)); } } @@ -345,8 +338,7 @@ RGInstallProgress::RGInstallProgress(RGMainWindow *main, RPackageLister *lister) gtk_css_provider_load_from_data( _cssProviderBold, "Label { font-family: helvetica; font-size: 10pt; font-weight: bold; }", - -1, - NULL); + -1); gtk_style_context_add_provider(styleContext, GTK_STYLE_PROVIDER(_cssProviderBold), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); @@ -354,10 +346,7 @@ RGInstallProgress::RGInstallProgress(RGMainWindow *main, RPackageLister *lister) _cssProvider = gtk_css_provider_new(); styleContext = gtk_widget_get_style_context(_labelSummary); gtk_css_provider_load_from_data( - _cssProvider, - "Label { font-family: helvetica; font-size: 10pt; }", - -1, - NULL); + _cssProvider, "Label { font-family: helvetica; font-size: 10pt; }", -1); gtk_style_context_add_provider(styleContext, GTK_STYLE_PROVIDER(_cssProvider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); @@ -368,8 +357,6 @@ RGInstallProgress::RGInstallProgress(RGMainWindow *main, RPackageLister *lister) gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(_pbar), 0.01); gtk_progress_bar_pulse(GTK_PROGRESS_BAR(_pbarTotal)); gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(_pbarTotal), 0.01); - - skipTaskbar(true); } RGInstallProgress::~RGInstallProgress() diff --git a/gtk/rginstallprogress.h b/gtk/rginstallprogress.h index 90b8f9d36..5f9190d90 100644 --- a/gtk/rginstallprogress.h +++ b/gtk/rginstallprogress.h @@ -55,7 +55,7 @@ class RGInstallProgressMsgs : public RGGtkBuilderWindow virtual void addLine(const char *line); virtual bool empty(); - virtual void run(); + [[nodiscard]] virtual task run(); virtual void close() override; RGInstallProgressMsgs(RGWindow *win); @@ -91,7 +91,7 @@ class RGInstallProgress : public RInstallProgress, public RGGtkBuilderWindow RGInstallProgress(RGMainWindow *main, RPackageLister *lister); ~RGInstallProgress(); - virtual void startUpdate() override; - virtual void updateInterface() override; - virtual void finishUpdate() override; + [[nodiscard]] virtual task startUpdate() override; + [[nodiscard]] virtual task updateInterface() override; + [[nodiscard]] virtual task finishUpdate() override; }; diff --git a/gtk/rglogview.cc b/gtk/rglogview.cc index f79e9ff55..551e2c3c0 100644 --- a/gtk/rglogview.cc +++ b/gtk/rglogview.cc @@ -36,9 +36,6 @@ #include #include #include -#include -#include -#include #include #include #include @@ -272,7 +269,7 @@ void RGLogView::cbButtonFind(GtkWidget *self, void *data) me->clearLogBuf(); - me->findStr = gtk_entry_get_text(GTK_ENTRY(me->_entryFind)); + me->findStr = gtk_editable_get_text(GTK_EDITABLE(me->_entryFind)); // reset to old model if (strlen(me->findStr) == 0) { me->findStr = NULL; @@ -309,16 +306,13 @@ void RGLogView::cbButtonFind(GtkWidget *self, void *data) void RGLogView::show() { clearLogBuf(); - gtk_entry_set_text(GTK_ENTRY(_entryFind), ""); + gtk_editable_set_text(GTK_EDITABLE(_entryFind), ""); RGWindow::show(); } RGLogView::RGLogView(RGWindow *parent) : RGGtkBuilderWindow(parent, "logview"), findStr(NULL) { - GtkWidget *vbox = GTK_WIDGET(gtk_builder_get_object(_builder, "vbox_main")); - assert(vbox); - _entryFind = GTK_WIDGET(gtk_builder_get_object(_builder, "entry_find")); assert(_entryFind); @@ -344,6 +338,7 @@ RGLogView::RGLogView(RGWindow *parent) G_CALLBACK(cbCloseClicked), this); + hideByEscape(); /* Setup the selection handler */ GtkTreeSelection *select; diff --git a/gtk/rglogview.h b/gtk/rglogview.h index aa5593334..1b9df0d84 100644 --- a/gtk/rglogview.h +++ b/gtk/rglogview.h @@ -26,7 +26,6 @@ #include "rggtkbuilderwindow.h" -#include #include #include diff --git a/gtk/rgmainwindow.cc b/gtk/rgmainwindow.cc index 59391df8a..51164eba2 100644 --- a/gtk/rgmainwindow.cc +++ b/gtk/rgmainwindow.cc @@ -88,9 +88,6 @@ #include #include -// include it here because depcache.h hates us if we have it before -#include - using namespace std; const char *relOptions[] = {N_("Dependencies"), @@ -117,7 +114,7 @@ enum { GtkCssProvider *RGMainWindow::_fastSearchCssProvider = NULL; -void RGMainWindow::changeView(int view, string subView) +task RGMainWindow::changeView(int view, string subView) { if (_config->FindB("Debug::Synaptic::View", false)) ioprintf(clog, @@ -148,7 +145,7 @@ void RGMainWindow::changeView(int view, string subView) GtkTreeSelection *selection; setBusyCursor(true); - setInterfaceLocked(TRUE); + co_await setInterfaceLocked(TRUE); GtkWidget *tview = GTK_WIDGET(gtk_builder_get_object(_builder, "treeview_subviews")); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tview)); @@ -173,7 +170,7 @@ void RGMainWindow::changeView(int view, string subView) } _lister->setSubView(subView); refreshTable(pkg, false); - setInterfaceLocked(FALSE); + co_await setInterfaceLocked(FALSE); setBusyCursor(false); _blockActions = FALSE; setStatusText(); @@ -284,9 +281,9 @@ string RGMainWindow::selectedSubView() return ret; } -bool RGMainWindow::showErrors() +task RGMainWindow::showErrors() { - return _userDialog->showErrors(); + co_return co_await _userDialog->showErrors(); } void RGMainWindow::notifyChange(RPackage *pkg) @@ -321,7 +318,7 @@ void RGMainWindow::refreshTable(RPackage *selectedPkg, bool setAdjustment) selectedPkg != NULL ? selectedPkg->name() : "(no pkg)", setAdjustment); - const gchar *str = gtk_entry_get_text(GTK_ENTRY(_entry_fast_search)); + const gchar *str = gtk_editable_get_text(GTK_EDITABLE(_entry_fast_search)); if (str != NULL && strlen(str) > 1) { if (_config->FindB("Debug::Synaptic::View", false)) cerr << "RGMainWindow::refreshTable: rerun limitBySearch" << endl; @@ -521,16 +518,25 @@ void RGMainWindow::cbMenuAutoInstalledClicked(GSimpleAction *action, g_variant_unref(state); g_simple_action_set_state(action, g_variant_new_boolean(active)); + start_task( + [me, active]() -> task { co_await me->autoInstalled(active); }); +} + +task RGMainWindow::autoInstalled(bool active) +{ + if (_blockActions) + co_return; + GtkTreeSelection *selection; GtkTreeIter iter; GList *list, *li; RPackage *pkg; - selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(me->_treeView)); - list = li = gtk_tree_selection_get_selected_rows(selection, &me->_pkgList); + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(_treeView)); + li = gtk_tree_selection_get_selected_rows(selection, &_pkgList); while (li != NULL) { - gtk_tree_model_get_iter(me->_pkgList, &iter, (GtkTreePath *)(li->data)); - gtk_tree_model_get(me->_pkgList, &iter, PKG_COLUMN, &pkg, -1); + gtk_tree_model_get_iter(_pkgList, &iter, (GtkTreePath *)(li->data)); + gtk_tree_model_get(_pkgList, &iter, PKG_COLUMN, &pkg, -1); if (pkg == NULL) { li = g_list_next(li); continue; @@ -544,23 +550,23 @@ void RGMainWindow::cbMenuAutoInstalledClicked(GSimpleAction *action, // write it GtkWidget *progress = - GTK_WIDGET(gtk_builder_get_object(me->_builder, "progressbar_main")); + GTK_WIDGET(gtk_builder_get_object(_builder, "progressbar_main")); GtkWidget *label = - GTK_WIDGET(gtk_builder_get_object(me->_builder, "label_status")); + GTK_WIDGET(gtk_builder_get_object(_builder, "label_status")); RGCacheProgress cacheProgress(progress, label); - me->_lister->getCache()->deps()->writeStateFile(&cacheProgress, true); + _lister->getCache()->deps()->writeStateFile(&cacheProgress, true); // refresh - me->setInterfaceLocked(TRUE); - me->_lister->unregisterObserver(me); + co_await setInterfaceLocked(TRUE); + _lister->unregisterObserver(this); - me->_lister->getCache()->deps()->MarkAndSweep(); - me->_lister->refreshView(); + _lister->getCache()->deps()->MarkAndSweep(); + _lister->refreshView(); - me->_lister->registerObserver(me); - me->refreshTable(); - me->refreshSubViewList(); - me->setInterfaceLocked(FALSE); + _lister->registerObserver(this); + refreshTable(); + refreshSubViewList(); + co_await setInterfaceLocked(FALSE); } // install a specific version @@ -571,11 +577,17 @@ void RGMainWindow::cbInstallFromVersion(GSimpleAction *action, // cout << "RGMainWindow::cbInstallFromVersion()" << endl; RGMainWindow *me = (RGMainWindow *)data; - RPackage *pkg = me->selectedPackage(); + start_task([me]() -> task { co_await me->installFromVersion(); }); +} + +// install a specific version +task RGMainWindow::installFromVersion() +{ + RPackage *pkg = selectedPackage(); if (pkg == NULL) - return; + co_return; - RGGtkBuilderUserDialog dia(me, "change_version"); + RGGtkBuilderUserDialog dia(this, "change_version"); GtkWidget *label = GTK_WIDGET(gtk_builder_get_object(dia.getGtkBuilder(), "label_text")); @@ -609,9 +621,9 @@ void RGMainWindow::cbInstallFromVersion(GSimpleAction *action, } gtk_combo_box_set_active(GTK_COMBO_BOX(available_versions_combo), canidateNr); - if (!dia.run()) { + if (!co_await dia.co_run()) { // cout << "cancel" << endl; - return; // user clicked cancel + co_return; // user clicked cancel } int nr = gtk_combo_box_get_active(GTK_COMBO_BOX(available_versions_combo)); @@ -619,7 +631,7 @@ void RGMainWindow::cbInstallFromVersion(GSimpleAction *action, pkg->setNotify(false); // nr-1 here as we add a "do not override" to the option menu pkg->setVersion(versions[nr].first.c_str()); - me->pkgAction(PKG_INSTALL_FROM_VERSION); + co_await pkgAction(PKG_INSTALL_FROM_VERSION); if (!(pkg->getFlags() & RPackage::FInstall)) @@ -628,8 +640,8 @@ void RGMainWindow::cbInstallFromVersion(GSimpleAction *action, pkg->setNotify(true); } -bool RGMainWindow::askStateChange(RPackageLister::pkgState state, - const vector &exclude) +task RGMainWindow::askStateChange(RPackageLister::pkgState state, + const vector &exclude) { vector toKeep; vector toInstall; @@ -661,7 +673,7 @@ bool RGMainWindow::askStateChange(RPackageLister::pkgState state, toRemove, toDowngrade, notAuthenticated); - int res = gtk_dialog_run(GTK_DIALOG(changes.window())); + int res = co_await co_run_dialog(GTK_DIALOG(changes.window())); if (res != GTK_RESPONSE_OK) { // canceled operation _lister->restoreState(state); @@ -672,16 +684,16 @@ bool RGMainWindow::askStateChange(RPackageLister::pkgState state, } } - return changed; + co_return changed; } -void RGMainWindow::pkgAction(RGPkgAction action) +task RGMainWindow::pkgAction(RGPkgAction action) { GtkTreeSelection *selection; GtkTreeIter iter; GList *li, *list; - setInterfaceLocked(TRUE); + co_await setInterfaceLocked(TRUE); _blockActions = TRUE; // get list of selected pkgs @@ -748,17 +760,17 @@ void RGMainWindow::pkgAction(RGPkgAction action) break; case PKG_DELETE: // delete if (flags & RPackage::FInstalled) - pkgRemoveHelper(pkg); + co_await pkgRemoveHelper(pkg); break; case PKG_PURGE: // purge if (flags & RPackage::FInstalled || flags & RPackage::FResidualConfig) - pkgRemoveHelper(pkg, true); + co_await pkgRemoveHelper(pkg, true); break; case PKG_DELETE_WITH_DEPS: if (flags & RPackage::FInstalled || flags & RPackage::FResidualConfig) - pkgRemoveHelper(pkg, true, true); + co_await pkgRemoveHelper(pkg, true, true); break; default: cout << "uh oh!!!!!!!!!" << endl; @@ -776,14 +788,14 @@ void RGMainWindow::pkgAction(RGPkgAction action) _lister->notifyCachePostChange(); - bool changed = askStateChange(state, exclude); + bool changed = co_await askStateChange(state, exclude); if (changed) { bool failed = false; // check for failed installs, if a installs fails, restore old state // as the Fixer may do wired thinks when trying to resolve the problem if (action == PKG_INSTALL) { - failed = checkForFailedInst(instPkgs); + failed = co_await checkForFailedInst(instPkgs); if (failed) _lister->restoreState(state); } @@ -800,11 +812,11 @@ void RGMainWindow::pkgAction(RGPkgAction action) refreshSubViewList(); _blockActions = FALSE; - setInterfaceLocked(FALSE); + co_await setInterfaceLocked(FALSE); refreshTable(pkg); } -bool RGMainWindow::checkForFailedInst(vector instPkgs) +task RGMainWindow::checkForFailedInst(vector instPkgs) { string failedReason; bool failed = false; @@ -828,45 +840,27 @@ bool RGMainWindow::checkForFailedInst(vector instPkgs) GTK_WIDGET(gtk_builder_get_object(dia.getGtkBuilder(), "textview")); GtkTextBuffer *tb = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tv)); gtk_text_buffer_set_text(tb, utf8(failedReason.c_str()), -1); - dia.run(); + co_await dia.co_run(); // we informaed the user about the problem, we can clear the // apt error stack // CHECKME: is this discard here really needed? _error->Discard(); } - return failed; -} - -struct ActionClosure -{ - RGMainWindow *me; - const char *action_name; -}; - -static void acceleratorCallback(gpointer data) -{ - ActionClosure *c = (ActionClosure *)data; - c->me->activateAction(c->action_name, nullptr); + co_return failed; } static void setActionShortcut(RGMainWindow *me, - GtkAccelGroup *accel_group, + GtkShortcutController *shortcuts, const char *action_name, guint key, GdkModifierType mods) { - ActionClosure *closure = g_new0(ActionClosure, 1); - closure->me = me; - closure->action_name = action_name; - - gtk_accel_group_connect(accel_group, - key, - mods, - GTK_ACCEL_VISIBLE, - g_cclosure_new_swap(G_CALLBACK(acceleratorCallback), - closure, - (GClosureNotify)g_free)); + gtk_shortcut_controller_add_shortcut( + shortcuts, + gtk_shortcut_new( + gtk_keyval_trigger_new(key, mods), + gtk_named_action_new((std::string("win.") + action_name).c_str()))); } RGMainWindow::RGMainWindow(GtkApplication *app, @@ -874,7 +868,7 @@ RGMainWindow::RGMainWindow(GtkApplication *app, string name) : RGGtkBuilderWindow(NULL, name), _lister(packLister), _pkgList(0), _treeView(0), _tasksWin(0), _iconLegendPanel(0), _pkgDetails(0), - _logView(0), _installProgress(0), _fetchProgress(0), _fastSearchEventID(-1) + _logView(0), _fetchProgress(0), _installProgress(0), _fastSearchEventID(-1) { assert(_win); gtk_application_add_window(GTK_APPLICATION(app), GTK_WINDOW(_win)); @@ -927,38 +921,37 @@ RGMainWindow::RGMainWindow(GtkApplication *app, {"icon-legend", cbShowIconLegendPanel}, {"about", cbShowAboutPanel}}; - GSimpleActionGroup *group = g_simple_action_group_new(); + win_actions = G_ACTION_GROUP(g_simple_action_group_new()); g_action_map_add_action_entries( - G_ACTION_MAP(group), entries, G_N_ELEMENTS(entries), this); + G_ACTION_MAP(win_actions), entries, G_N_ELEMENTS(entries), this); gtk_widget_insert_action_group( - GTK_WIDGET(_win), "win", G_ACTION_GROUP(group)); - - GtkAccelGroup *accel_group = gtk_accel_group_new(); - gtk_window_add_accel_group(GTK_WINDOW(_win), accel_group); - setActionShortcut(this, accel_group, "quit", GDK_KEY_Q, GDK_CONTROL_MASK); - setActionShortcut(this, accel_group, "undo", GDK_KEY_Z, GDK_CONTROL_MASK); - setActionShortcut(this, accel_group, "redo", GDK_KEY_Z, GDK_SHIFT_MASK); - setActionShortcut(this, accel_group, "search", GDK_KEY_F, GDK_CONTROL_MASK); - setActionShortcut(this, accel_group, "reload", GDK_KEY_R, GDK_CONTROL_MASK); + GTK_WIDGET(_win), "win", G_ACTION_GROUP(win_actions)); + + GtkShortcutController *shortcuts = getShortcutController(); + setActionShortcut(this, shortcuts, "quit", GDK_KEY_Q, GDK_CONTROL_MASK); + setActionShortcut(this, shortcuts, "undo", GDK_KEY_Z, GDK_CONTROL_MASK); + setActionShortcut(this, shortcuts, "redo", GDK_KEY_Z, GDK_SHIFT_MASK); + setActionShortcut(this, shortcuts, "search", GDK_KEY_F, GDK_CONTROL_MASK); + setActionShortcut(this, shortcuts, "reload", GDK_KEY_R, GDK_CONTROL_MASK); setActionShortcut( - this, accel_group, "mark-all-upgrades", GDK_KEY_G, GDK_CONTROL_MASK); - setActionShortcut(this, accel_group, "apply", GDK_KEY_P, GDK_CONTROL_MASK); - setActionShortcut(this, accel_group, "unmark", GDK_KEY_N, GDK_CONTROL_MASK); + this, shortcuts, "mark-all-upgrades", GDK_KEY_G, GDK_CONTROL_MASK); + setActionShortcut(this, shortcuts, "apply", GDK_KEY_P, GDK_CONTROL_MASK); + setActionShortcut(this, shortcuts, "unmark", GDK_KEY_N, GDK_CONTROL_MASK); setActionShortcut( - this, accel_group, "mark-install", GDK_KEY_I, GDK_CONTROL_MASK); + this, shortcuts, "mark-install", GDK_KEY_I, GDK_CONTROL_MASK); setActionShortcut( - this, accel_group, "mark-upgrade", GDK_KEY_U, GDK_CONTROL_MASK); + this, shortcuts, "mark-upgrade", GDK_KEY_U, GDK_CONTROL_MASK); setActionShortcut( - this, accel_group, "mark-delete", GDK_KEY_Delete, (GdkModifierType)0); + this, shortcuts, "mark-delete", GDK_KEY_Delete, (GdkModifierType)0); setActionShortcut( - this, accel_group, "mark-purge", GDK_KEY_Delete, GDK_SHIFT_MASK); + this, shortcuts, "mark-purge", GDK_KEY_Delete, GDK_SHIFT_MASK); setActionShortcut( - this, accel_group, "override-version", GDK_KEY_E, GDK_CONTROL_MASK); + this, shortcuts, "override-version", GDK_KEY_E, GDK_CONTROL_MASK); setActionShortcut( - this, accel_group, "download-changelog", GDK_KEY_L, GDK_CONTROL_MASK); + this, shortcuts, "download-changelog", GDK_KEY_L, GDK_CONTROL_MASK); setActionShortcut( - this, accel_group, "package-properties", GDK_KEY_Return, GDK_MOD1_MASK); - setActionShortcut(this, accel_group, "help", GDK_KEY_F1, (GdkModifierType)0); + this, shortcuts, "package-properties", GDK_KEY_Return, GDK_ALT_MASK); + setActionShortcut(this, shortcuts, "help", GDK_KEY_F1, (GdkModifierType)0); _blockActions = false; _unsavedChanges = false; @@ -1127,23 +1120,19 @@ void RGMainWindow::buildInterface() gtk_window_set_icon_name(GTK_WINDOW(_win), "synaptic"); - gtk_window_resize(GTK_WINDOW(_win), - _config->FindI("Synaptic::windowWidth", 640), - _config->FindI("Synaptic::windowHeight", 480)); - gtk_window_move(GTK_WINDOW(_win), - _config->FindI("Synaptic::windowX", 100), - _config->FindI("Synaptic::windowY", 100)); + gtk_window_set_default_size(GTK_WINDOW(_win), + _config->FindI("Synaptic::windowWidth", 640), + _config->FindI("Synaptic::windowHeight", 480)); + if (_config->FindB("Synaptic::Maximized", false)) gtk_window_maximize(GTK_WINDOW(_win)); - RGFlushInterface(); if (_fastSearchCssProvider == NULL) { _fastSearchCssProvider = gtk_css_provider_new(); gtk_css_provider_load_from_data( _fastSearchCssProvider, "GtkEntry:not(:selected) { background: #F7F7BE; }", - -1, - NULL); + -1); } if (getuid() != 0) { @@ -1151,12 +1140,6 @@ void RGMainWindow::buildInterface() GTK_WIDGET(gtk_builder_get_object(_builder, "no_root_info"))); } - gtk_menu_shell_bind_model( - GTK_MENU_SHELL(gtk_builder_get_object(_builder, "menubar1")), - G_MENU_MODEL(gtk_builder_get_object(_builder, "main_menu")), - nullptr, - false); - g_signal_connect(gtk_builder_get_object(_builder, "entry_fast_search"), "changed", G_CALLBACK(cbSearchEntryChanged), @@ -1246,7 +1229,7 @@ void RGMainWindow::buildInterface() // not restored in the same place as it was. if (!_config->FindB("Volatile::HideMainwindow", false)) show(); - RGFlushInterface(); + gtk_paned_set_position(GTK_PANED(vpaned), _config->FindI("Synaptic::vpanedPos", 140)); gtk_paned_set_position(GTK_PANED(hpaned), @@ -1256,10 +1239,12 @@ void RGMainWindow::buildInterface() // build the treeview buildTreeView(); - g_signal_connect(G_OBJECT(_treeView), - "button-press-event", - (GCallback)cbPackageListClicked, - this); + GtkGesture *click_controller = gtk_gesture_click_new(); + gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(click_controller), 0); + g_signal_connect( + click_controller, "pressed", (GCallback)cbPackageListClicked, this); + gtk_widget_add_controller(GTK_WIDGET(_treeView), + GTK_EVENT_CONTROLLER(click_controller)); GtkTreeSelection *select; select = gtk_tree_view_get_selection(GTK_TREE_VIEW(_treeView)); @@ -1339,9 +1324,9 @@ void RGMainWindow::buildInterface() g_signal_connect( G_OBJECT(select), "changed", G_CALLBACK(cbChangedSubView), this); - GtkBindingSet *binding_set = gtk_binding_set_find("GtkTreeView"); - gtk_binding_entry_add_signal( - binding_set, GDK_KEY_s, GDK_CONTROL_MASK, "start_interactive_search", 0); + // GtkBindingSet *binding_set = gtk_binding_set_find("GtkTreeView"); + // gtk_binding_entry_add_signal(binding_set, GDK_KEY_s, GDK_CONTROL_MASK, + // "start_interactive_search", 0); _entry_fast_search = GTK_WIDGET(gtk_builder_get_object(_builder, "entry_fast_search")); @@ -1369,13 +1354,11 @@ void RGMainWindow::buildInterface() bool RGMainWindow::isActionEnabled(const char *action_name) { - GActionGroup *win_actions = gtk_widget_get_action_group(_win, "win"); return g_action_group_get_action_enabled(win_actions, action_name); } void RGMainWindow::setActionEnabled(const char *action_name, bool enabled) { - GActionGroup *win_actions = gtk_widget_get_action_group(_win, "win"); GAction *action = g_action_map_lookup_action(G_ACTION_MAP(win_actions), action_name); g_simple_action_set_enabled(G_SIMPLE_ACTION(action), enabled); @@ -1383,7 +1366,6 @@ void RGMainWindow::setActionEnabled(const char *action_name, bool enabled) void RGMainWindow::setActionState(const char *action_name, GVariant *value) { - GActionGroup *win_actions = gtk_widget_get_action_group(_win, "win"); GAction *action = g_action_map_lookup_action(G_ACTION_MAP(win_actions), action_name); g_simple_action_set_state(G_SIMPLE_ACTION(action), value); @@ -1391,7 +1373,6 @@ void RGMainWindow::setActionState(const char *action_name, GVariant *value) void RGMainWindow::activateAction(const char *action_name, GVariant *value) { - GActionGroup *win_actions = gtk_widget_get_action_group(_win, "win"); g_action_group_activate_action(win_actions, action_name, value); } @@ -1410,7 +1391,9 @@ void RGMainWindow::pkgInstallHelper(RPackage *pkg, _lister->fixBroken(); } -void RGMainWindow::pkgRemoveHelper(RPackage *pkg, bool purge, bool withDeps) +task RGMainWindow::pkgRemoveHelper(RPackage *pkg, + bool purge, + bool withDeps) { if (pkg->getFlags() & RPackage::FImportant) { gchar *warning = @@ -1418,10 +1401,10 @@ void RGMainWindow::pkgRemoveHelper(RPackage *pkg, bool purge, bool withDeps) "system unusable.\n" "Are you sure you want to do that?"), pkg->name()); - bool confirmed = _userDialog->confirm(warning, false); + bool confirmed = co_await _userDialog->confirm(warning, false); g_free(warning); if (!confirmed) { - return; + co_return; } } if (!withDeps) @@ -1501,10 +1484,10 @@ void RGMainWindow::setStatusText(char *text) } -void RGMainWindow::saveState() +task RGMainWindow::saveState() { if (_config->FindB("Volatile::NoStateSaving", false) == true) - return; + co_return; GtkWidget *vpaned = GTK_WIDGET(gtk_builder_get_object(_builder, "vpaned_main")); @@ -1515,30 +1498,21 @@ void RGMainWindow::saveState() _config->Set("Synaptic::hpanedPos", gtk_paned_get_position(GTK_PANED(hpaned))); - GtkAllocation allocation; - gtk_widget_get_allocation(_win, &allocation); - _config->Set("Synaptic::windowWidth", allocation.width); - _config->Set("Synaptic::windowHeight", allocation.height); - gint x, y; - gtk_window_get_position(GTK_WINDOW(_win), &x, &y); - _config->Set("Synaptic::windowX", x); - _config->Set("Synaptic::windowY", y); + _config->Set("Synaptic::windowWidth", gtk_widget_get_width(_win)); + _config->Set("Synaptic::windowHeight", gtk_widget_get_height(_win)); _config->Set("Synaptic::ToolbarState", (int)_toolbarStyle); - if (gdk_window_get_state(gtk_widget_get_window(_win)) & - GDK_WINDOW_STATE_MAXIMIZED) - _config->Set("Synaptic::Maximized", true); - else - _config->Set("Synaptic::Maximized", false); + _config->Set("Synaptic::Maximized", + gtk_window_is_maximized(GTK_WINDOW(_win))); if (!RWriteConfigFile(*_config)) { _error->Error(_("An error occurred while saving configurations.")); - _userDialog->showErrors(); + co_await _userDialog->showErrors(); } if (!_roptions->store()) cerr << "Internal Error: error storing raptoptions" << endl; } -bool RGMainWindow::restoreState() +task RGMainWindow::restoreState() { // see if we have broken packages (might be better in some @@ -1554,13 +1528,13 @@ bool RGMainWindow::restoreState() "Use the \"Broken\" filter to locate them.", broken); msg = g_strdup_printf(msg, broken); - _userDialog->warning(msg); + co_await _userDialog->warning(msg); g_free(msg); } if (!_config->FindB("Volatile::Upgrade-Mode", false)) { int viewNr = _config->FindI("Synaptic::ViewMode", 0); - changeView(viewNr); + co_await changeView(viewNr); // we auto set to "All" on startup when we have gtk2.4 (without // the list is too slow) @@ -1574,7 +1548,7 @@ bool RGMainWindow::restoreState() gtk_tree_selection_select_iter(selection, &iter); } updatePackageInfo(NULL); - return true; + co_return true; } @@ -1583,41 +1557,42 @@ void RGMainWindow::close() if (_interfaceLocked > 0) return; - RGGtkBuilderUserDialog dia(this); - if (_unsavedChanges == false || dia.run("quit")) { - _error->Discard(); - saveState(); - showErrors(); - exit(0); - } + start_task([this]() -> task { + RGGtkBuilderUserDialog dia(this); + if (_unsavedChanges == false || co_await dia.co_run("quit")) { + _error->Discard(); + co_await saveState(); + co_await showErrors(); + exit(0); + } + }); } -void RGMainWindow::setInterfaceLocked(bool flag) +task RGMainWindow::setInterfaceLocked(bool flag) { if (flag) { _interfaceLocked++; if (_interfaceLocked > 1) - return; + co_return; gtk_widget_set_sensitive(_win, FALSE); if (gtk_widget_get_visible(_win)) - gdk_window_set_cursor(gtk_widget_get_window(_win), _busyCursor); + gtk_widget_set_cursor_from_name(GTK_WIDGET(_win), "watch"); } else { assert(_interfaceLocked > 0); _interfaceLocked--; if (_interfaceLocked > 0) - return; + co_return; gtk_widget_set_sensitive(_win, TRUE); if (gtk_widget_get_visible(_win)) - gdk_window_set_cursor(gtk_widget_get_window(_win), NULL); + gtk_widget_set_cursor_from_name(GTK_WIDGET(_win), NULL); } // fast enough with the new fixed-height mode - while (gtk_events_pending()) - gtk_main_iteration(); + co_await RGFlushInterface(); } void RGMainWindow::setTreeLocked(bool flag) @@ -1643,7 +1618,7 @@ void RGMainWindow::cbPkgAction(RGPkgAction action) if (gtk_widget_has_focus(entry) && action == PKG_DELETE) { return; } - pkgAction(action); + start_task([this, action]() -> task { co_await pkgAction(action); }); } void RGMainWindow::cbPkgActionUnmark(GSimpleAction *action, @@ -1712,30 +1687,38 @@ void RGMainWindow::cbPkgActionDefault(GSimpleAction *action, } -gboolean RGMainWindow::cbPackageListClicked(GtkWidget *treeview, - GdkEventButton *event, - gpointer data) +void RGMainWindow::cbPackageListClicked(GtkGestureClick *gesture, + int n_press, + double x, + double y, + gpointer data) { // cout << "RGMainWindow::cbPackageListClicked()" << endl; + int button = + gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(gesture)); + GdkModifierType state = gdk_event_get_modifier_state( + gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(gesture))); + RGMainWindow *me = (RGMainWindow *)data; RPackage *pkg = NULL; GtkTreePath *path; GtkTreeViewColumn *column; /* Single clicks only */ - if (event->type == GDK_BUTTON_PRESS) { + if (n_press == 1) { GtkTreeSelection *selection; GtkTreeIter iter; - if (!(event->window == - gtk_tree_view_get_bin_window(GTK_TREE_VIEW(treeview)))) - return false; + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(me->_treeView)); + + int bx, by; + gtk_tree_view_convert_widget_to_bin_window_coords( + GTK_TREE_VIEW(me->_treeView), (int)x, (int)y, &bx, &by); - selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)); - if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview), - (int)event->x, - (int)event->y, + if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(me->_treeView), + bx, + by, &path, &column, NULL, @@ -1743,18 +1726,19 @@ gboolean RGMainWindow::cbPackageListClicked(GtkWidget *treeview, /* Check if it's either a right-button click, or a left-button * click on the status column. */ - if (!(event->button == 3 || - (event->button == 1 && + if (!(button == 3 || + (button == 1 && strcmp(gtk_tree_view_column_get_title(column), "S") == 0))) - return false; + return; vector selected_pkgs; GList *li = NULL; // Treat click with CONTROL as additional selection - if ((event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK && - !gtk_tree_selection_path_is_selected(selection, path)) + if ((state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK && + !gtk_tree_selection_path_is_selected(selection, path)) { gtk_tree_selection_unselect_all(selection); + } gtk_tree_selection_select_path(selection, path); li = gtk_tree_selection_get_selected_rows(selection, &me->_pkgList); @@ -1767,12 +1751,10 @@ gboolean RGMainWindow::cbPackageListClicked(GtkWidget *treeview, selected_pkgs.push_back(pkg); } - cbTreeviewPopupMenu(treeview, event, me, selected_pkgs); - return true; + me->cbTreeviewPopupMenu(button, bx, by, selected_pkgs); + return; } } - - return false; } void RGMainWindow::cbChangelogDialog(GSimpleAction *action, @@ -1781,13 +1763,14 @@ void RGMainWindow::cbChangelogDialog(GSimpleAction *action, { RGMainWindow *me = (RGMainWindow *)data; - RPackage *pkg = me->selectedPackage(); - if (pkg == NULL) - return; - - me->setInterfaceLocked(TRUE); - ShowChangelogDialog(me, pkg); - me->setInterfaceLocked(FALSE); + start_task([me]() -> task { + RPackage *pkg = me->selectedPackage(); + if (pkg != NULL) { + co_await me->setInterfaceLocked(TRUE); + co_await ShowChangelogDialog(me, pkg); + co_await me->setInterfaceLocked(FALSE); + } + }); } void RGMainWindow::cbPackageListRowActivated(GtkTreeView *treeview, @@ -1796,49 +1779,55 @@ void RGMainWindow::cbPackageListRowActivated(GtkTreeView *treeview, gpointer data) { // cout << "RGMainWindow::cbPackageListRowActivated()" << endl; - RGMainWindow *me = (RGMainWindow *)data; + start_task([me, treeview, path] -> task { + co_await me->packageListRowActivated(treeview, path); + }); +} + +task RGMainWindow::packageListRowActivated(GtkTreeView *treeview, + GtkTreePath *path) +{ GtkTreeIter iter; RPackage *pkg = NULL; - if (!gtk_tree_model_get_iter(me->_pkgList, &iter, path)) - return; + if (!gtk_tree_model_get_iter(_pkgList, &iter, path)) + co_return; - gtk_tree_model_get(me->_pkgList, &iter, PKG_COLUMN, &pkg, -1); + gtk_tree_model_get(_pkgList, &iter, PKG_COLUMN, &pkg, -1); assert(pkg); int flags = pkg->getFlags(); if (flags & RPackage::FPinned) - return; + co_return; if (!(flags & RPackage::FInstalled)) { if (flags & RPackage::FKeep) - me->pkgAction(PKG_INSTALL); + co_await pkgAction(PKG_INSTALL); else if (flags & RPackage::FInstall) - me->pkgAction(PKG_KEEP); + co_await pkgAction(PKG_KEEP); } else if (flags & RPackage::FOutdated) { if (flags & RPackage::FKeep) - me->pkgAction(PKG_INSTALL); + co_await pkgAction(PKG_INSTALL); else if (flags & RPackage::FUpgrade) - me->pkgAction(PKG_KEEP); + co_await pkgAction(PKG_KEEP); } // make sure we do not lose the keyboard focus (this happens in // pkgAction otherwise) - gtk_widget_grab_focus(GTK_WIDGET(treeview)); - gtk_tree_view_set_cursor(GTK_TREE_VIEW(treeview), path, NULL, false); + // gtk_widget_grab_focus (GTK_WIDGET(treeview)); + // gtk_tree_view_set_cursor(GTK_TREE_VIEW(treeview), path, NULL, false); - GtkTreePath *start = gtk_tree_path_new(); - bool ok = - gtk_tree_view_get_visible_range(GTK_TREE_VIEW(treeview), &start, NULL); - if (ok && gtk_tree_model_get_iter_first(me->_pkgList, &iter)) { - gtk_tree_view_scroll_to_cell( - GTK_TREE_VIEW(treeview), start, NULL, true, 0.0, 0.0); - } - gtk_tree_path_free(start); + // GtkTreePath *start = gtk_tree_path_new(); + // bool ok = gtk_tree_view_get_visible_range(GTK_TREE_VIEW(treeview), &start, + // NULL); if (ok && gtk_tree_model_get_iter_first(_pkgList, &iter)) { + // gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(treeview), start, NULL, + // true, 0.0, 0.0); + // } + // gtk_tree_path_free(start); - me->setStatusText(); + setStatusText(); } void RGMainWindow::cbAddCDROM(GSimpleAction *action, @@ -1846,32 +1835,37 @@ void RGMainWindow::cbAddCDROM(GSimpleAction *action, gpointer data) { RGMainWindow *me = (RGMainWindow *)data; - RGCDScanner scan(me, me->_userDialog); - me->setInterfaceLocked(TRUE); + start_task([me]() -> task { co_await me->addCDROM(); }); +} + +task RGMainWindow::addCDROM() +{ + RGCDScanner scan(this, _userDialog); + co_await setInterfaceLocked(TRUE); bool updateCache = false; bool dontStop = true; while (dontStop) { - if (scan.run() == false) - me->showErrors(); + if (co_await scan.run() == false) + co_await showErrors(); else updateCache = true; if (_config->FindB("APT::CDROM::NoMount", false)) dontStop = false; else - dontStop = - me->_userDialog->confirm(_("Do you want to add another CD-ROM?")); + dontStop = co_await _userDialog->confirm( + _("Do you want to add another CD-ROM?")); } scan.hide(); if (updateCache) { - me->setTreeLocked(TRUE); - if (!me->_lister->openCache()) { - me->showErrors(); + setTreeLocked(TRUE); + if (!_lister->openCache()) { + co_await showErrors(); exit(1); } - me->setTreeLocked(FALSE); - me->refreshTable(me->selectedPackage()); + setTreeLocked(FALSE); + refreshTable(selectedPackage()); } - me->setInterfaceLocked(FALSE); + co_await setInterfaceLocked(FALSE); } void RGMainWindow::cbTasksClicked(GSimpleAction *action, @@ -1879,15 +1873,20 @@ void RGMainWindow::cbTasksClicked(GSimpleAction *action, gpointer data) { RGMainWindow *me = (RGMainWindow *)data; + start_task([me]() -> task { co_await me->tasksClicked(); }); +} - me->setBusyCursor(true); +task RGMainWindow::tasksClicked() +{ + setBusyCursor(true); - if (me->_tasksWin == NULL) { - me->_tasksWin = new RGTasksWin(me); + if (_tasksWin == NULL) { + _tasksWin = new RGTasksWin(this); } - me->_tasksWin->show(); + auto packages = co_await _tasksWin->selectTasks(); + co_await selectToInstall(packages); - me->setBusyCursor(false); + setBusyCursor(false); } void RGMainWindow::cbOpenClicked(GSimpleAction *action, @@ -1896,47 +1895,56 @@ void RGMainWindow::cbOpenClicked(GSimpleAction *action, { // std::cout << "RGMainWindow::openClicked()" << endl; RGMainWindow *me = (RGMainWindow *)data; + start_task([me]() -> task { co_await me->openClicked(); }); +} +task RGMainWindow::openClicked() +{ GtkWidget *filesel; filesel = gtk_file_chooser_dialog_new(_("Open changes"), - GTK_WINDOW(me->window()), + GTK_WINDOW(window()), GTK_FILE_CHOOSER_ACTION_OPEN, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Open"), GTK_RESPONSE_ACCEPT, NULL); - if (gtk_dialog_run(GTK_DIALOG(filesel)) == GTK_RESPONSE_ACCEPT) { - me->setInterfaceLocked(TRUE); + if (co_await co_run_dialog(GTK_DIALOG(filesel)) == GTK_RESPONSE_ACCEPT) { + co_await setInterfaceLocked(TRUE); gtk_widget_hide(filesel); - RGFlushInterface(); + co_await RGFlushInterface(); RPackageLister::pkgState state; - me->_lister->saveState(state); + _lister->saveState(state); - const char *file; - file = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filesel)); - me->selectionsFilename = file; + GFile *file = gtk_file_chooser_get_file(GTK_FILE_CHOOSER(filesel)); + char *filename = g_file_get_path(file); + g_object_unref(file); - ifstream in(file); + selectionsFilename = filename; + ifstream in(filename); if (!in != 0) { - _error->Error(_("Can't read %s"), file); - me->_userDialog->showErrors(); - return; + _error->Error(_("Can't read %s"), filename); + co_await _userDialog->showErrors(); + g_free(filename); + co_return; } - me->_lister->unregisterObserver(me); + + g_free(filename); + + _lister->unregisterObserver(this); // read the selections from the file - me->_lister->readSelections(in); - me->askStateChange(state); + _lister->readSelections(in); + co_await askStateChange(state); // refresh to ensure that broken dependencies are displayed - me->_lister->registerObserver(me); - me->refreshTable(); - me->refreshSubViewList(); - me->setStatusText(); - me->setInterfaceLocked(FALSE); + _lister->registerObserver(this); + refreshTable(); + refreshSubViewList(); + setStatusText(); + co_await setInterfaceLocked(FALSE); } - gtk_widget_destroy(filesel); + gtk_window_destroy(GTK_WINDOW(filesel)); } void RGMainWindow::cbSaveClicked(GSimpleAction *action, @@ -1945,23 +1953,27 @@ void RGMainWindow::cbSaveClicked(GSimpleAction *action, { // std::cout << "RGMainWindow::saveClicked()" << endl; RGMainWindow *me = (RGMainWindow *)data; + start_task([me]() -> task { co_await me->saveClicked(); }); +} - if (me->selectionsFilename == "") { - me->cbSaveAsClicked(nullptr, nullptr, data); - return; +task RGMainWindow::saveClicked() +{ + if (selectionsFilename == "") { + co_await saveAsClicked(); + co_return; } - ofstream out(me->selectionsFilename.c_str()); + ofstream out(selectionsFilename.c_str()); if (!out != 0) { - _error->Error(_("Can't write %s"), me->selectionsFilename.c_str()); - me->_userDialog->showErrors(); - return; + _error->Error(_("Can't write %s"), selectionsFilename.c_str()); + co_await _userDialog->showErrors(); + co_return; } - me->_lister->unregisterObserver(me); - me->_lister->writeSelections(out, me->saveFullState); - me->_lister->registerObserver(me); - me->setStatusText(); + _lister->unregisterObserver(this); + _lister->writeSelections(out, saveFullState); + _lister->registerObserver(this); + setStatusText(); } @@ -1971,30 +1983,39 @@ void RGMainWindow::cbSaveAsClicked(GSimpleAction *action, { // std::cout << "RGMainWindow::saveAsClicked()" << endl; RGMainWindow *me = (RGMainWindow *)data; + start_task([me]() -> task { co_await me->saveAsClicked(); }); +} +task RGMainWindow::saveAsClicked() +{ GtkWidget *filesel; filesel = gtk_file_chooser_dialog_new(_("Save changes"), - GTK_WINDOW(me->window()), + GTK_WINDOW(window()), GTK_FILE_CHOOSER_ACTION_SAVE, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Save"), GTK_RESPONSE_ACCEPT, NULL); - GtkWidget *checkButton = - gtk_check_button_new_with_label(_("Save full state, not only changes")); - gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(filesel), checkButton); - - if (gtk_dialog_run(GTK_DIALOG(filesel)) == GTK_RESPONSE_ACCEPT) { - const char *file; - file = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filesel)); - me->selectionsFilename = file; - me->saveFullState = - gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkButton)); + gtk_file_chooser_add_choice(GTK_FILE_CHOOSER(filesel), + "full", + _("Save full state, not only changes"), + NULL, + NULL); + + if (co_await co_run_dialog(GTK_DIALOG(filesel)) == GTK_RESPONSE_ACCEPT) { + GFile *file = gtk_file_chooser_get_file(GTK_FILE_CHOOSER(filesel)); + char *filename = g_file_get_path(file); + selectionsFilename = filename; + g_free(filename); + g_object_unref(file); + saveFullState = g_strcmp0("true", + gtk_file_chooser_get_choice( + GTK_FILE_CHOOSER(filesel), "full")) == 0; // now call save for the actual saving - me->cbSaveClicked(nullptr, nullptr, me); + co_await saveClicked(); } - gtk_widget_destroy(filesel); + gtk_window_destroy(GTK_WINDOW(filesel)); } @@ -2046,7 +2067,11 @@ void RGMainWindow::cbShowSourcesWindow(GSimpleAction *action, gpointer data) { RGMainWindow *me = (RGMainWindow *)data; + start_task([me]() -> task { co_await me->showSourcesWindow(); }); +} +task RGMainWindow::showSourcesWindow() +{ // FIXME: make this all go into the repository window bool Changed = false; bool ForceReload = _config->FindB("Synaptic::UpdateAfterSrcChange", false); @@ -2054,20 +2079,18 @@ void RGMainWindow::cbShowSourcesWindow(GSimpleAction *action, if (!g_file_test("/usr/bin/software-properties-gtk", G_FILE_TEST_IS_EXECUTABLE) || _config->FindB("Synaptic::dontUseGnomeSoftwareProperties", false)) { - RGRepositoryEditor w(me); - Changed = w.Run(); + RGRepositoryEditor w(this); + Changed = co_await w.Run(); } else { // use gnome-software-properties window - me->setInterfaceLocked(TRUE); + co_await setInterfaceLocked(TRUE); GPid pid; int status; - const char *argv[5]; + const char *argv[4]; argv[0] = "/usr/bin/software-properties-gtk"; argv[1] = "-n"; argv[2] = "-t"; - argv[3] = g_strdup_printf( - "%lu", GDK_WINDOW_XID(gtk_widget_get_window(me->_win))); - argv[4] = NULL; + argv[3] = NULL; g_spawn_async(NULL, const_cast(argv), NULL, @@ -2079,22 +2102,22 @@ void RGMainWindow::cbShowSourcesWindow(GSimpleAction *action, // kill the child if the window is deleted while (waitpid(pid, &status, WNOHANG) == 0) { usleep(50000); - RGFlushInterface(); + co_await RGFlushInterface(); } Changed = WEXITSTATUS(status); - me->setInterfaceLocked(FALSE); + co_await setInterfaceLocked(FALSE); } - RGFlushInterface(); + co_await RGFlushInterface(); // auto update after repostitory change if (Changed == true && ForceReload) { - me->cbUpdateClicked(nullptr, nullptr, data); + co_await updateClicked(); } else if (Changed == true && _config->FindB("Synaptic::AskForUpdateAfterSrcChange", true)) { // ask for update after repo change GtkWidget *cb, *dialog; - dialog = gtk_message_dialog_new(GTK_WINDOW(me->window()), + dialog = gtk_message_dialog_new(GTK_WINDOW(window()), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_INFO, GTK_BUTTONS_NONE, @@ -2116,26 +2139,21 @@ void RGMainWindow::cbShowSourcesWindow(GSimpleAction *action, NULL); GtkWidget *reload_button = gtk_dialog_get_widget_for_response( GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT); - GtkWidget *refresh_image = - gtk_image_new_from_icon_name("view-refresh", GTK_ICON_SIZE_BUTTON); - gtk_button_set_image(GTK_BUTTON(reload_button), refresh_image); + GtkWidget *refresh_image = gtk_image_new_from_icon_name("view-refresh"); + gtk_button_set_child(GTK_BUTTON(reload_button), refresh_image); cb = gtk_check_button_new_with_label(_("Never show this message again")); - gtk_box_pack_start( - GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), - cb, - true, - true, - 0); + gtk_box_append(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), + cb); gtk_widget_show(cb); - gint response = gtk_dialog_run(GTK_DIALOG(dialog)); + gint response = co_await co_run_dialog(GTK_DIALOG(dialog)); gtk_widget_hide(dialog); if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb))) { _config->Set("Synaptic::AskForUpdateAfterSrcChange", false); } if (response == GTK_RESPONSE_ACCEPT) { - me->cbUpdateClicked(nullptr, nullptr, data); + co_await updateClicked(); } - gtk_widget_destroy(dialog); + gtk_window_destroy(GTK_WINDOW(dialog)); } } @@ -2143,28 +2161,28 @@ static void traverseToolbarButtons( GtkWidget *toolbar, std::function cb) { - if (!GTK_IS_CONTAINER(toolbar)) + if (!GTK_IS_WIDGET(toolbar)) return; - GList *children = gtk_container_get_children(GTK_CONTAINER(toolbar)); - for (GList *iter = children; iter != NULL; iter = iter->next) { - if (GTK_IS_BUTTON(iter->data)) { - GtkWidget *box = gtk_bin_get_child(GTK_BIN(iter->data)); + + for (GtkWidget *child = gtk_widget_get_first_child(toolbar); child != NULL; + child = gtk_widget_get_next_sibling(child)) { + if (GTK_IS_BUTTON(child)) { + GtkWidget *box = gtk_button_get_child(GTK_BUTTON(child)); GtkWidget *image = nullptr; GtkWidget *label = nullptr; - GList *box_children = gtk_container_get_children(GTK_CONTAINER(box)); - for (GList *iter2 = box_children; iter2 != NULL; iter2 = iter2->next) { - if (GTK_IS_IMAGE(iter2->data)) - image = GTK_WIDGET(iter2->data); - else if (GTK_IS_LABEL(iter2->data)) - label = GTK_WIDGET(iter2->data); + for (GtkWidget *box_child = gtk_widget_get_first_child(box); + box_child != NULL; + box_child = gtk_widget_get_next_sibling(box_child)) { + if (GTK_IS_IMAGE(box_child)) + image = GTK_WIDGET(box_child); + else if (GTK_IS_LABEL(box_child)) + label = GTK_WIDGET(box_child); } - g_list_free(box_children); cb(box, image, label); } } - g_list_free(children); } void RGMainWindow::cbMenuToolbarClicked(GSimpleAction *action, @@ -2228,41 +2246,44 @@ void RGMainWindow::cbFindToolClicked(GSimpleAction *action, gpointer data) { RGMainWindow *me = (RGMainWindow *)data; + start_task([me]() -> task { co_await me->findTool(); }); +} - if (me->_findWin == NULL) { - me->_findWin = new RGFindWindow(me); +task RGMainWindow::findTool() +{ + if (_findWin == NULL) { + _findWin = new RGFindWindow(this); } - me->_findWin->selectText(); - int res = gtk_dialog_run(GTK_DIALOG(me->_findWin->window())); + _findWin->selectText(); + int res = co_await co_run_dialog(GTK_DIALOG(_findWin->window())); if (res == GTK_RESPONSE_OK) { - // clear the quick search, otherwise both apply and that is // confusing - gtk_entry_set_text(GTK_ENTRY(me->_entry_fast_search), ""); + gtk_editable_set_text(GTK_EDITABLE(_entry_fast_search), ""); - string str = me->_findWin->getFindString(); - me->setBusyCursor(true); + string str = _findWin->getFindString(); + setBusyCursor(true); // we need to convert here as the DDTP project does not use utf-8 const char *locale_str = utf8_to_locale(str.c_str()); if (locale_str == NULL) // invalid utf-8 locale_str = str.c_str(); - int type = me->_findWin->getSearchType(); + int type = _findWin->getSearchType(); GtkWidget *progress = - GTK_WIDGET(gtk_builder_get_object(me->_builder, "progressbar_main")); + GTK_WIDGET(gtk_builder_get_object(_builder, "progressbar_main")); GtkWidget *label = - GTK_WIDGET(gtk_builder_get_object(me->_builder, "label_status")); + GTK_WIDGET(gtk_builder_get_object(_builder, "label_status")); RGCacheProgress searchProgress(progress, label); - int found = me->_lister->searchView()->setSearch( + int found = _lister->searchView()->setSearch( str, type, locale_str, searchProgress); - me->changeView(PACKAGE_VIEW_SEARCH, str); + co_await changeView(PACKAGE_VIEW_SEARCH, str); - me->setBusyCursor(false); + setBusyCursor(false); gchar *statusstr = g_strdup_printf(_("Found %i packages"), found); - me->setStatusText(statusstr); - me->updatePackageInfo(NULL); + setStatusText(statusstr); + updatePackageInfo(NULL); g_free(statusstr); } } @@ -2334,8 +2355,12 @@ void RGMainWindow::cbHelpAction(GSimpleAction *action, gpointer data) { RGMainWindow *me = (RGMainWindow *)data; + start_task([me]() -> task { co_await me->helpAction(); }); +} - me->setStatusText(_("Starting help viewer...")); +task RGMainWindow::helpAction() +{ + setStatusText(_("Starting help viewer...")); // FIXME: move this into rgutils as well (or rgspawn.cc) vector cmd; @@ -2348,57 +2373,44 @@ void RGMainWindow::cbHelpAction(GSimpleAction *action, } if (cmd.empty()) { - me->_userDialog->error(_("No help viewer is installed!\n\n" - "You need either the GNOME help viewer 'yelp', " - "or any browser setup to use xdg-open " - "to view the synaptic manual.\n\n" - "Alternatively you can open the man page " - "with 'man synaptic' from the " - "command line or view the html version located " - "in the 'synaptic/html' folder.")); - return; + co_await _userDialog->error( + _("No help viewer is installed!\n\n" + "You need either the GNOME help viewer 'yelp', " + "or any browser setup to use xdg-open " + "to view the synaptic manual.\n\n" + "Alternatively you can open the man page " + "with 'man synaptic' from the " + "command line or view the html version located " + "in the 'synaptic/html' folder.")); + co_return; } RunAsSudoUserCommand(cmd); } -void RGMainWindow::cbCloseFilterManagerAction(void *self, bool okcancel) -{ - RGMainWindow *me = (RGMainWindow *)self; - - // FIXME: only do all this if the user didn't click "cancel" in the dialog - - me->setInterfaceLocked(TRUE); - - me->_lister->filterView()->refreshFilters(); - me->refreshTable(); - me->refreshSubViewList(); - - me->setInterfaceLocked(FALSE); -} - - void RGMainWindow::cbShowFilterManagerWindow(GSimpleAction *action, GVariant *parameter, gpointer data) { - RGMainWindow *me = (RGMainWindow *)data; + start_task([me]() -> task { co_await me->showFilterManagerWindow(); }); +} - if (me->_fmanagerWin == NULL) { - me->_fmanagerWin = - new RGFilterManagerWindow(me, me->_lister->filterView()); +task RGMainWindow::showFilterManagerWindow() +{ + if (_fmanagerWin == NULL) { + _fmanagerWin = new RGFilterManagerWindow(this, _lister->filterView()); } - me->_fmanagerWin->readFilters(); - int res = gtk_dialog_run(GTK_DIALOG(me->_fmanagerWin->window())); + _fmanagerWin->readFilters(); + int res = co_await co_run_dialog(GTK_DIALOG(_fmanagerWin->window())); if (res == GTK_RESPONSE_OK) { - me->setInterfaceLocked(TRUE); + co_await setInterfaceLocked(TRUE); - me->_lister->filterView()->refreshFilters(); - me->refreshTable(); - me->refreshSubViewList(); + _lister->filterView()->refreshFilters(); + refreshTable(); + refreshSubViewList(); - me->setInterfaceLocked(FALSE); + co_await setInterfaceLocked(FALSE); } } @@ -2444,22 +2456,27 @@ void RGMainWindow::cbClearAllChangesClicked(GSimpleAction *action, { // cout << "clearAllChangesClicked" << endl; RGMainWindow *me = (RGMainWindow *)data; - me->setInterfaceLocked(TRUE); - me->_lister->unregisterObserver(me); - me->setTreeLocked(TRUE); + start_task([me]() -> task { co_await me->clearAllChanges(); }); +} + +task RGMainWindow::clearAllChanges() +{ + co_await setInterfaceLocked(TRUE); + _lister->unregisterObserver(this); + setTreeLocked(TRUE); // reset - if (!me->_lister->openCache()) { - me->showErrors(); + if (!_lister->openCache()) { + co_await showErrors(); exit(1); } - me->_lister->registerObserver(me); - me->setTreeLocked(FALSE); - me->refreshTable(); - me->refreshSubViewList(); - me->setInterfaceLocked(FALSE); - me->setStatusText(); + _lister->registerObserver(this); + setTreeLocked(FALSE); + refreshTable(); + refreshSubViewList(); + co_await setInterfaceLocked(FALSE); + setStatusText(); } @@ -2469,16 +2486,21 @@ void RGMainWindow::cbUndoClicked(GSimpleAction *action, { // cout << "undoClicked" << endl; RGMainWindow *me = (RGMainWindow *)data; - me->setInterfaceLocked(TRUE); + start_task([me]() -> task { co_await me->undo(); }); +} - me->_lister->unregisterObserver(me); +task RGMainWindow::undo() +{ + co_await setInterfaceLocked(TRUE); + + _lister->unregisterObserver(this); // undo - me->_lister->undo(); + _lister->undo(); - me->_lister->registerObserver(me); - me->refreshTable(); - me->setInterfaceLocked(FALSE); + _lister->registerObserver(this); + refreshTable(); + co_await setInterfaceLocked(FALSE); } void RGMainWindow::cbRedoClicked(GSimpleAction *action, @@ -2487,16 +2509,21 @@ void RGMainWindow::cbRedoClicked(GSimpleAction *action, { // cout << "redoClicked" << endl; RGMainWindow *me = (RGMainWindow *)data; - me->setInterfaceLocked(TRUE); + start_task([me]() -> task { co_await me->redo(); }); +} - me->_lister->unregisterObserver(me); +task RGMainWindow::redo() +{ + co_await setInterfaceLocked(TRUE); + + _lister->unregisterObserver(this); // redo - me->_lister->redo(); + _lister->redo(); - me->_lister->registerObserver(me); - me->refreshTable(); - me->setInterfaceLocked(FALSE); + _lister->registerObserver(this); + refreshTable(); + co_await setInterfaceLocked(FALSE); } void RGMainWindow::cbPkgReconfigureClicked(GSimpleAction *action, @@ -2505,24 +2532,26 @@ void RGMainWindow::cbPkgReconfigureClicked(GSimpleAction *action, { RGMainWindow *me = (RGMainWindow *)data; // cout << "RGMainWindow::pkgReconfigureClicked()" << endl; + start_task([me]() -> task { co_await me->pkgReconfigureClicked(); }); +} - if (me->selectedPackage() == NULL) - return; +task RGMainWindow::pkgReconfigureClicked() +{ + if (selectedPackage() == NULL) + co_return; RPackage *pkg = NULL; - pkg = me->_lister->getPackage("libgnome2-perl"); + pkg = _lister->getPackage("libgnome2-perl"); if (pkg && pkg->installedVersion() == NULL) { - me->_userDialog->error(_("Cannot start configuration tool!\n" - "You have to install the required package " - "'libgnome2-perl'.")); - return; + co_await _userDialog->error(_("Cannot start configuration tool!\n" + "You have to install the required package " + "'libgnome2-perl'.")); + co_return; } - me->setStatusText(_("Starting package configuration tool...")); - const gchar *cmd[] = {"/usr/sbin/dpkg-reconfigure", - "-fgnome", - me->selectedPackage()->name(), - NULL}; + setStatusText(_("Starting package configuration tool...")); + const gchar *cmd[] = { + "/usr/sbin/dpkg-reconfigure", "-fgnome", selectedPackage()->name(), NULL}; GError *error = NULL; g_spawn_async("/", const_cast(cmd), @@ -2543,12 +2572,16 @@ void RGMainWindow::cbPkgHelpClicked(GSimpleAction *action, gpointer data) { RGMainWindow *me = (RGMainWindow *)data; + start_task([me]() -> task { co_await me->pkgHelpClicked(); }); +} - if (me->selectedPackage() == NULL) - return; +task RGMainWindow::pkgHelpClicked() +{ + if (selectedPackage() == NULL) + co_return; // cout << "RGMainWindow::pkgHelpClicked()" << endl; - me->setStatusText(_("Starting package documentation viewer...")); + setStatusText(_("Starting package documentation viewer...")); // mozilla eats bookmarks when run under sudo (because it does not // change $HOME) so we better play safe here @@ -2560,7 +2593,7 @@ void RGMainWindow::cbPkgHelpClicked(GSimpleAction *action, if (is_binary_in_path("dwww")) { const gchar *cmd[5]; cmd[0] = "dwww"; - cmd[1] = me->selectedPackage()->name(); + cmd[1] = selectedPackage()->name(); cmd[2] = NULL; g_spawn_async("/tmp", const_cast(cmd), @@ -2571,8 +2604,9 @@ void RGMainWindow::cbPkgHelpClicked(GSimpleAction *action, NULL, NULL); } else { - me->_userDialog->error(_("You have to install the package \"dwww\" " - "to browse the documentation of a package")); + co_await _userDialog->error( + _("You have to install the package \"dwww\" " + "to browse the documentation of a package")); } } @@ -2580,14 +2614,16 @@ void RGMainWindow::cbPkgHelpClicked(GSimpleAction *action, void RGMainWindow::cbChangedView(GtkWidget *self, void *data) { RGMainWindow *me = (RGMainWindow *)data; + start_task([self, me]() -> task { + // only act on the active buttons + if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(self)) || + me->_blockActions == TRUE) { + co_return; + } - // only act on the active buttons - if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(self)) || - me->_blockActions == TRUE) - return; - - long view = (long)g_object_get_data(G_OBJECT(self), "index"); - me->changeView(view); + long view = (long)g_object_get_data(G_OBJECT(self), "index"); + co_await me->changeView(view); + }); } void RGMainWindow::cbChangedSubView(GtkTreeSelection *selection, gpointer data) @@ -2636,48 +2672,52 @@ void RGMainWindow::cbProceedClicked(GSimpleAction *action, gpointer data) { RGMainWindow *me = (RGMainWindow *)data; + start_task([me]() -> task { co_await me->applyChanges(); }); +} +task RGMainWindow::applyChanges() +{ // nothing to do - int installed, broken; + int listed, installed, broken; int toInstall, toRemove; double size; - me->_lister->getStats(installed, broken, toInstall, toRemove, size); + _lister->getStats(installed, broken, toInstall, toRemove, size); if ((toInstall + toRemove) == 0) - return; + co_return; // check whether we can really do it - if (!me->_lister->check()) { - me->_userDialog->error(_("Could not apply changes!\n" - "Fix broken packages first.")); - return; + if (!_lister->check()) { + co_await _userDialog->error(_("Could not apply changes!\n" + "Fix broken packages first.")); + co_return; } int a, b, c, d, e, f, g, h, unAuthenticated; double s; - me->_lister->getSummary(a, b, c, d, e, f, g, h, unAuthenticated, s); + _lister->getSummary(a, b, c, d, e, f, g, h, unAuthenticated, s); if (unAuthenticated || _config->FindB("Volatile::Non-Interactive", false) == false) { // show a summary of what's gonna happen - RGSummaryWindow summ(me, me->_lister); - if (!summ.showAndConfirm()) { + RGSummaryWindow summ(this, _lister); + if (!co_await summ.showAndConfirm()) { // canceled operation - return; + co_return; } } - me->setInterfaceLocked(TRUE); - me->updatePackageInfo(NULL); + co_await setInterfaceLocked(TRUE); + updatePackageInfo(NULL); - me->setStatusText(_("Applying marked changes. This may take a while...")); + setStatusText(_("Applying marked changes. This may take a while...")); // fetch packages - RGFetchProgress *fprogress = me->_fetchProgress = new RGFetchProgress(me); + RGFetchProgress *fprogress = _fetchProgress = new RGFetchProgress(this); fprogress->setDescription(_("Downloading Package Files"), ""); // _("The package files will be cached locally for // installation.")); // Do not let the treeview access the cache during the update. - me->setTreeLocked(TRUE); + setTreeLocked(TRUE); // save selections to temporary file const gchar *file = @@ -2685,10 +2725,10 @@ void RGMainWindow::cbProceedClicked(GSimpleAction *action, ofstream out(file); if (!out != 0) { _error->Error(_("Can't write %s"), file); - me->_userDialog->showErrors(); - return; + co_await _userDialog->showErrors(); + co_return; } - me->_lister->writeSelections(out, false); + _lister->writeSelections(out, false); RInstallProgress *iprogress; @@ -2704,52 +2744,51 @@ void RGMainWindow::cbProceedClicked(GSimpleAction *action, # endif // DPKG # endif // HAVE_RPM if (_config->FindB("Synaptic::UseTerminal", UseTerminal) == true) - iprogress = new RGTermInstallProgress(me); + iprogress = new RGTermInstallProgress(this); else #endif // HAVE_TERMINAL #ifdef HAVE_RPM - iprogress = new RGInstallProgress(me, me->_lister); + iprogress = new RGInstallProgress(this, _lister); #else # ifdef WITH_DPKG_STATUSFD - iprogress = new RGDebInstallProgress(me, me->_lister); + iprogress = new RGDebInstallProgress(this, _lister); # else iprogress = new RGDummyInstallProgress(); # endif // WITH_DPKG_STATUSFD #endif // HAVE_RPM - me->_installProgress = dynamic_cast(iprogress); + _installProgress = dynamic_cast(iprogress); - // bool result = me->_lister->commitChanges(fprogress, iprogress); - me->_lister->commitChanges(fprogress, iprogress); + co_await _lister->commitChanges(fprogress, iprogress); iprogress->finish(); delete fprogress; - me->_fetchProgress = NULL; + _fetchProgress = NULL; delete iprogress; - me->_installProgress = NULL; + _installProgress = NULL; if (_config->FindB("Synaptic::IgnorePMOutput", false) == false) { - me->showErrors(); + co_await showErrors(); } else { _error->Discard(); } if (_config->FindB("Volatile::Non-Interactive", false) == true) { - return; + co_return; } if (_config->FindB("Synaptic::AskQuitOnProceed", false) == true && - me->_userDialog->confirm(_("Do you want to quit Synaptic?"))) { + co_await _userDialog->confirm(_("Do you want to quit Synaptic?"))) { _error->Discard(); - me->saveState(); - me->showErrors(); + co_await saveState(); + co_await showErrors(); exit(0); } if (_config->FindB("Volatile::Download-Only", false) == false) { // reset the cache - if (!me->_lister->openCache()) { - me->showErrors(); + if (!_lister->openCache()) { + co_await showErrors(); exit(1); } } @@ -2757,19 +2796,19 @@ void RGMainWindow::cbProceedClicked(GSimpleAction *action, ifstream in(file); if (!in != 0) { _error->Error(_("Can't read %s"), file); - me->_userDialog->showErrors(); - return; + co_await _userDialog->showErrors(); + co_return; } - me->_lister->readSelections(in); + _lister->readSelections(in); unlink(file); g_free((void *)file); - me->setTreeLocked(FALSE); - me->refreshTable(); - me->refreshSubViewList(); - me->setInterfaceLocked(FALSE); - me->updatePackageInfo(NULL); + setTreeLocked(FALSE); + refreshTable(); + refreshSubViewList(); + co_await setInterfaceLocked(FALSE); + updatePackageInfo(NULL); } void RGMainWindow::cbShowWelcomeDialog(GSimpleAction *action, @@ -2777,52 +2816,59 @@ void RGMainWindow::cbShowWelcomeDialog(GSimpleAction *action, gpointer data) { RGMainWindow *me = (RGMainWindow *)data; - RGGtkBuilderUserDialog dia(me); - dia.run("welcome"); + start_task([me]() -> task { co_await me->showWelcomeDialog(); }); +} + +task RGMainWindow::showWelcomeDialog() +{ + RGGtkBuilderUserDialog dia(this); + co_await dia.co_run("welcome"); GtkWidget *cb = GTK_WIDGET( gtk_builder_get_object(dia.getGtkBuilder(), "checkbutton_show_again")); assert(cb); _config->Set("Synaptic::showWelcomeDialog", - gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb))); + gtk_check_button_get_active(GTK_CHECK_BUTTON(cb))); } -gboolean RGMainWindow::xapianDoSearch(void *data) +void RGMainWindow::xapianDoSearch(void *data) { RGMainWindow *me = (RGMainWindow *)data; - const gchar *str = gtk_entry_get_text(GTK_ENTRY(me->_entry_fast_search)); - GtkStyleContext *styleContext = - gtk_widget_get_style_context(me->_entry_fast_search); + start_task([me]() -> task { + const gchar *str = + gtk_editable_get_text(GTK_EDITABLE(me->_entry_fast_search)); + GtkStyleContext *styleContext = + gtk_widget_get_style_context(me->_entry_fast_search); - me->_fastSearchEventID = -1; - me->setBusyCursor(true); - RGFlushInterface(); - if (str == NULL || strlen(str) <= 1) { - // reset the color - gtk_style_context_remove_provider( - styleContext, GTK_STYLE_PROVIDER(_fastSearchCssProvider)); - // if the user has cleared the search, refresh the view - // Gtk-CRITICAL **: gtk_tree_view_unref_tree_helper: assertion `node != - // NULL' failed at us, see LP: #38397 for more information - gtk_tree_view_set_model(GTK_TREE_VIEW(me->_treeView), NULL); - me->_lister->reapplyFilter(); - me->refreshTable(); - me->setBusyCursor(false); - } else if (strlen(str) > 1) { - // only search when there is more than one char entered, single - // char searches tend to be very slow + me->_fastSearchEventID = -1; me->setBusyCursor(true); - RGFlushInterface(); - gtk_tree_view_set_model(GTK_TREE_VIEW(me->_treeView), NULL); - me->refreshTable(); - // set color to a light yellow to make it more obvious that a search - // is performed - gtk_style_context_add_provider(styleContext, - GTK_STYLE_PROVIDER(_fastSearchCssProvider), - GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); - } - me->setBusyCursor(false); - - return FALSE; + co_await RGFlushInterface(); + if (str == NULL || strlen(str) <= 1) { + // reset the color + gtk_style_context_remove_provider( + styleContext, GTK_STYLE_PROVIDER(_fastSearchCssProvider)); + // if the user has cleared the search, refresh the view + // Gtk-CRITICAL **: gtk_tree_view_unref_tree_helper: assertion `node != + // NULL' failed at us, see LP: #38397 for more information + gtk_tree_view_set_model(GTK_TREE_VIEW(me->_treeView), NULL); + me->_lister->reapplyFilter(); + me->refreshTable(); + me->setBusyCursor(false); + } else if (strlen(str) > 1) { + // only search when there is more than one char entered, single + // char searches tend to be very slow + me->setBusyCursor(true); + co_await RGFlushInterface(); + gtk_tree_view_set_model(GTK_TREE_VIEW(me->_treeView), NULL); + me->refreshTable(); + // set color to a light yellow to make it more obvious that a search + // is performed + gtk_style_context_add_provider( + styleContext, + GTK_STYLE_PROVIDER(_fastSearchCssProvider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + } + me->setBusyCursor(false); + }); } void RGMainWindow::cbSearchEntryChanged(GtkWidget *edit, void *data) @@ -2833,7 +2879,7 @@ void RGMainWindow::cbSearchEntryChanged(GtkWidget *edit, void *data) g_source_remove(me->_fastSearchEventID); me->_fastSearchEventID = -1; } - me->_fastSearchEventID = g_timeout_add(500, xapianDoSearch, me); + me->_fastSearchEventID = g_timeout_add_once(500, xapianDoSearch, me); } void RGMainWindow::cbUpdateClicked(GSimpleAction *action, @@ -2841,23 +2887,27 @@ void RGMainWindow::cbUpdateClicked(GSimpleAction *action, gpointer data) { RGMainWindow *me = (RGMainWindow *)data; + start_task([me]() -> task { co_await me->updateClicked(); }); +} +task RGMainWindow::updateClicked() +{ // need to delete dialogs, as they might have data pointing // to old stuff // xxx delete me->_fmanagerWin; - me->_fmanagerWin = NULL; + _fmanagerWin = NULL; - RGFetchProgress *progress = me->_fetchProgress = new RGFetchProgress(me); + RGFetchProgress *progress = _fetchProgress = new RGFetchProgress(this); progress->setDescription( _("Downloading Package Information"), _("The repositories will be checked for new, removed " "or upgraded software packages.")); - me->setStatusText(_("Reloading package information...")); + setStatusText(_("Reloading package information...")); - me->setInterfaceLocked(TRUE); - me->setTreeLocked(TRUE); - me->_lister->unregisterObserver(me); + co_await setInterfaceLocked(TRUE); + setTreeLocked(TRUE); + _lister->unregisterObserver(this); // save to temporary file const gchar *file = @@ -2865,54 +2915,59 @@ void RGMainWindow::cbUpdateClicked(GSimpleAction *action, ofstream out(file); if (!out != 0) { _error->Error(_("Can't write %s"), file); - me->_userDialog->showErrors(); - return; + co_await _userDialog->showErrors(); + co_return; } - me->_lister->writeSelections(out, false); + _lister->writeSelections(out, false); // update cache and forget about the previous new packages // (only if no error occurred) string error; - if (!me->_lister->updateCache(progress, error)) { - RGGtkBuilderUserDialog dia(me, "update_failed"); + bool updateCacheResult = co_await runWithStatusAsync( + [this, &error](pkgAcquireStatus &progress) -> bool { + return _lister->updateCache(&progress, error); + }, + progress); + if (!updateCacheResult) { + RGGtkBuilderUserDialog dia(this, "update_failed"); GtkWidget *tv = GTK_WIDGET(gtk_builder_get_object(dia.getGtkBuilder(), "textview")); GtkTextBuffer *tb = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tv)); gtk_text_buffer_set_text(tb, utf8(error.c_str()), -1); - dia.run(); + co_await dia.co_run(); } else { - me->forgetNewPackages(); + forgetNewPackages(); _config->Set("Synaptic::update::last", time(NULL)); } delete progress; - me->_fetchProgress = NULL; + _fetchProgress = NULL; // show errors and warnings (like the gpg failures for the package list) - me->showErrors(); + co_await showErrors(); - if (!me->_lister->openCache()) { - me->showErrors(); + if (!_lister->openCache()) { + co_await showErrors(); exit(1); } // reread saved selections ifstream in(file); if (!in != 0) { _error->Error(_("Can't read %s"), file); - me->_userDialog->showErrors(); - return; + co_await _userDialog->showErrors(); + co_return; } - me->_lister->readSelections(in); + _lister->readSelections(in); unlink(file); g_free((void *)file); // check if the index needs to be rebuild - me->xapianDoIndexUpdate(me); + xapianDoIndexUpdate(this); - me->setTreeLocked(FALSE); - me->refreshTable(); - me->refreshSubViewList(); - me->setInterfaceLocked(FALSE); - me->setStatusText(); + setTreeLocked(FALSE); + refreshTable(); + refreshSubViewList(); + co_await setInterfaceLocked(FALSE); + setStatusText(); } void RGMainWindow::cbFixBrokenClicked(GSimpleAction *action, @@ -2920,19 +2975,24 @@ void RGMainWindow::cbFixBrokenClicked(GSimpleAction *action, gpointer data) { RGMainWindow *me = (RGMainWindow *)data; - RPackage *pkg = me->selectedPackage(); + start_task([me]() -> task { co_await me->fixBroken(); }); +} + +task RGMainWindow::fixBroken() +{ + RPackage *pkg = selectedPackage(); - bool res = me->_lister->fixBroken(); - me->setInterfaceLocked(TRUE); - me->refreshTable(pkg); + bool res = _lister->fixBroken(); + co_await setInterfaceLocked(TRUE); + refreshTable(pkg); if (!res) - me->setStatusText(_("Failed to resolve dependency problems!")); + setStatusText(_("Failed to resolve dependency problems!")); else - me->setStatusText(_("Successfully fixed dependency problems")); + setStatusText(_("Successfully fixed dependency problems")); - me->setInterfaceLocked(FALSE); - me->showErrors(); + co_await setInterfaceLocked(FALSE); + co_await showErrors(); } @@ -2941,38 +3001,43 @@ void RGMainWindow::cbUpgradeClicked(GSimpleAction *action, gpointer data) { RGMainWindow *me = (RGMainWindow *)data; - RPackage *pkg = me->selectedPackage(); + start_task([me]() -> task { co_await me->upgrade(); }); +} + +task RGMainWindow::upgrade() +{ + RPackage *pkg = selectedPackage(); bool dist_upgrade; int res; - if (!me->_lister->check()) { - me->_userDialog->error(_("Could not upgrade the system!\n" - "Fix broken packages first.")); - return; + if (!_lister->check()) { + co_await _userDialog->error(_("Could not upgrade the system!\n" + "Fix broken packages first.")); + co_return; } // check if we have saved upgrade type UpgradeType upgrade = (UpgradeType)_config->FindI("Synaptic::UpgradeType", UPGRADE_DIST); // special case for non-interactive upgrades - if (_config->FindB("Volatile::Non-Interactive", false)) + if (_config->FindB("Volatile::Non-Interactive", false)) { if (_config->FindB("Volatile::Upgrade-Mode", false)) upgrade = UPGRADE_NORMAL; else if (_config->FindB("Volatile::DistUpgrade-Mode", false)) upgrade = UPGRADE_DIST; - + } if (upgrade == UPGRADE_ASK) { // ask what type of upgrade the user wants GtkBuilder *builder; GtkWidget *button; - RGGtkBuilderUserDialog dia(me); - res = dia.run("upgrade", true); + RGGtkBuilderUserDialog dia(this); + res = co_await dia.co_run("upgrade", true); switch (res) { case GTK_RESPONSE_CANCEL: case GTK_RESPONSE_DELETE_EVENT: - return; + co_return; case GTK_RESPONSE_YES: dist_upgrade = true; break; @@ -2995,33 +3060,33 @@ void RGMainWindow::cbUpgradeClicked(GSimpleAction *action, } // do the work - me->setInterfaceLocked(TRUE); - me->setStatusText(_("Marking all available upgrades...")); + co_await setInterfaceLocked(TRUE); + setStatusText(_("Marking all available upgrades...")); - me->_lister->saveUndoState(); + _lister->saveUndoState(); RPackageLister::pkgState state; - me->_lister->saveState(state); + _lister->saveState(state); if (dist_upgrade) - res = me->_lister->distUpgrade(); + res = _lister->distUpgrade(); else - res = me->_lister->upgrade(); + res = _lister->upgrade(); - if (me->askStateChange(state)) { - me->refreshTable(pkg); + if (co_await askStateChange(state)) { + refreshTable(pkg); if (res) - me->setStatusText(_("Successfully marked available upgrades")); + setStatusText(_("Successfully marked available upgrades")); else - me->setStatusText(_("Failed to mark all available upgrades!")); + setStatusText(_("Failed to mark all available upgrades!")); } else { // if the user canceled the action, just show the default message - me->setStatusText(); + setStatusText(); } - me->setInterfaceLocked(FALSE); - me->showErrors(); + co_await setInterfaceLocked(FALSE); + co_await showErrors(); } void RGMainWindow::cbMenuPinClicked(GSimpleAction *action, @@ -3030,10 +3095,6 @@ void RGMainWindow::cbMenuPinClicked(GSimpleAction *action, { RGMainWindow *me = (RGMainWindow *)data; - GtkTreeSelection *selection; - GtkTreeIter iter; - RPackage *pkg; - if (me->_blockActions) return; @@ -3044,15 +3105,27 @@ void RGMainWindow::cbMenuPinClicked(GSimpleAction *action, g_variant_unref(state); g_simple_action_set_state(action, g_variant_new_boolean(active)); - selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(me->_treeView)); + start_task([me, active]() -> task { co_await me->pin(active); }); +} + +task RGMainWindow::pin(bool active) +{ + if (_blockActions) + co_return; + + GtkTreeSelection *selection; + GtkTreeIter iter; + RPackage *pkg; + + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(_treeView)); GList *li, *list; - list = li = gtk_tree_selection_get_selected_rows(selection, &me->_pkgList); + list = li = gtk_tree_selection_get_selected_rows(selection, &_pkgList); if (li == NULL) - return; + co_return; - me->setInterfaceLocked(TRUE); - me->_lister->unregisterObserver(me); + co_await setInterfaceLocked(TRUE); + _lister->unregisterObserver(this); // save to temporary file const gchar *file = @@ -3060,14 +3133,14 @@ void RGMainWindow::cbMenuPinClicked(GSimpleAction *action, ofstream out(file); if (!out != 0) { _error->Error(_("Can't write %s"), file); - me->_userDialog->showErrors(); - return; + co_await _userDialog->showErrors(); + co_return; } - me->_lister->writeSelections(out, false); + _lister->writeSelections(out, false); while (li != NULL) { - gtk_tree_model_get_iter(me->_pkgList, &iter, (GtkTreePath *)(li->data)); - gtk_tree_model_get(me->_pkgList, &iter, PKG_COLUMN, &pkg, -1); + gtk_tree_model_get_iter(_pkgList, &iter, (GtkTreePath *)(li->data)); + gtk_tree_model_get(_pkgList, &iter, PKG_COLUMN, &pkg, -1); if (pkg == NULL) { li = g_list_next(li); continue; @@ -3077,9 +3150,9 @@ void RGMainWindow::cbMenuPinClicked(GSimpleAction *action, _roptions->setPackageLock(pkg->name(), active); li = g_list_next(li); } - me->setTreeLocked(TRUE); - if (!me->_lister->openCache()) { - me->showErrors(); + setTreeLocked(TRUE); + if (!_lister->openCache()) { + co_await showErrors(); exit(1); } @@ -3087,10 +3160,10 @@ void RGMainWindow::cbMenuPinClicked(GSimpleAction *action, ifstream in(file); if (!in != 0) { _error->Error(_("Can't read %s"), file); - me->_userDialog->showErrors(); - return; + co_await _userDialog->showErrors(); + co_return; } - me->_lister->readSelections(in); + _lister->readSelections(in); unlink(file); g_free((void *)file); @@ -3098,17 +3171,17 @@ void RGMainWindow::cbMenuPinClicked(GSimpleAction *action, g_list_foreach(list, (GFunc)gtk_tree_path_free, NULL); g_list_free(list); - me->_lister->registerObserver(me); - me->setTreeLocked(FALSE); - me->refreshTable(); - me->refreshSubViewList(); - me->refreshTable(); - me->setInterfaceLocked(FALSE); + _lister->registerObserver(this); + setTreeLocked(FALSE); + refreshTable(); + refreshSubViewList(); + refreshTable(); + co_await setInterfaceLocked(FALSE); } -void RGMainWindow::cbTreeviewPopupMenu(GtkWidget *treeview, - GdkEventButton *event, - RGMainWindow *me, +void RGMainWindow::cbTreeviewPopupMenu(int button, + double x, + double y, vector selected_pkgs) { // Nothing selected, shouldn't happen, but we play safely. @@ -3124,9 +3197,9 @@ void RGMainWindow::cbTreeviewPopupMenu(GtkWidget *treeview, if (flags & RPackage::FPinned) return; - if (event->button == 1 && + if (button == 1 && _config->FindB("Synaptic::OneClickOnStatusActions", false) == true) { - me->activateAction("mark-default", nullptr); + activateAction("mark-default", nullptr); return; } @@ -3160,7 +3233,7 @@ void RGMainWindow::cbTreeviewPopupMenu(GtkWidget *treeview, GMenu *recommendsSection = g_menu_new(); if (GMenu *recommendedSubmenu = - me->buildWeakDependsMenu(pkg, pkgCache::Dep::Recommends)) + buildWeakDependsMenu(pkg, pkgCache::Dep::Recommends)) g_menu_append_submenu(recommendsSection, _("Mark Recommended for Installation"), G_MENU_MODEL(recommendedSubmenu)); @@ -3169,7 +3242,7 @@ void RGMainWindow::cbTreeviewPopupMenu(GtkWidget *treeview, recommendsSection, _("Mark Recommended for Installation"), nullptr); if (GMenu *suggestedSubmenu = - me->buildWeakDependsMenu(pkg, pkgCache::Dep::Suggests)) + buildWeakDependsMenu(pkg, pkgCache::Dep::Suggests)) g_menu_append_submenu(recommendsSection, _("Mark Suggested for Installation"), G_MENU_MODEL(suggestedSubmenu)); @@ -3184,11 +3257,13 @@ void RGMainWindow::cbTreeviewPopupMenu(GtkWidget *treeview, GdkRectangle rect{0, 0, 0, 0}; gtk_tree_view_convert_bin_window_to_widget_coords( - GTK_TREE_VIEW(treeview), (int)event->x, (int)event->y, &rect.x, &rect.y); + GTK_TREE_VIEW(_treeView), (int)x, (int)y, &rect.x, &rect.y); GtkWidget *popupMenu = - gtk_popover_new_from_model(treeview, G_MENU_MODEL(popupMenuModel)); + gtk_popover_menu_new_from_model(G_MENU_MODEL(popupMenuModel)); + gtk_widget_set_parent(popupMenu, _treeView); gtk_popover_set_pointing_to(GTK_POPOVER(popupMenu), &rect); + gtk_popover_present(GTK_POPOVER(popupMenu)); gtk_popover_popup(GTK_POPOVER(popupMenu)); } @@ -3239,20 +3314,18 @@ GMenu *RGMainWindow::buildWeakDependsMenu(RPackage *pkg, return NULL; } -void RGMainWindow::selectToInstall(vector packagenames) +task RGMainWindow::selectToInstall(vector packagenames) { - RGMainWindow *me = this; - RPackageLister::pkgState state; vector exclude; vector instPkgs; // we always save the state (for undo) - me->_lister->saveState(state); - me->_lister->notifyCachePreChange(); + _lister->saveState(state); + _lister->notifyCachePreChange(); for (unsigned int i = 0; i < packagenames.size(); i++) { - RPackage *newpkg = (RPackage *)me->_lister->getPackage(packagenames[i]); + RPackage *newpkg = (RPackage *)_lister->getPackage(packagenames[i]); if (newpkg) { // only install the package if it is not already installed or if // it is outdated @@ -3260,7 +3333,7 @@ void RGMainWindow::selectToInstall(vector packagenames) (newpkg->getFlags() & RPackage::FOutdated)) { // actual action newpkg->setNotify(false); - me->pkgInstallHelper(newpkg); + pkgInstallHelper(newpkg); newpkg->setNotify(true); // exclude.push_back(newpkg); instPkgs.push_back(newpkg); @@ -3269,19 +3342,19 @@ void RGMainWindow::selectToInstall(vector packagenames) } // ask for additional changes - me->setBusyCursor(true); - if (me->askStateChange(state, exclude)) { - me->_lister->saveUndoState(state); - if (me->checkForFailedInst(instPkgs)) - me->_lister->restoreState(state); + setBusyCursor(true); + if (co_await askStateChange(state, exclude)) { + _lister->saveUndoState(state); + if (co_await checkForFailedInst(instPkgs)) + _lister->restoreState(state); } - me->setBusyCursor(false); - me->_lister->notifyPostChange(NULL); - me->_lister->notifyCachePostChange(); + setBusyCursor(false); + _lister->notifyPostChange(NULL); + _lister->notifyCachePostChange(); - RPackage *pkg = me->selectedPackage(); - me->refreshTable(pkg); - me->updatePackageInfo(pkg); + RPackage *pkg = selectedPackage(); + refreshTable(pkg); + updatePackageInfo(pkg); } void RGMainWindow::pkgInstallByNameHelper(GSimpleAction *action, @@ -3292,37 +3365,42 @@ void RGMainWindow::pkgInstallByNameHelper(GSimpleAction *action, // cout << "pkgInstallByNameHelper: " << name << endl; RGMainWindow *me = (RGMainWindow *)data; + start_task( + [me, name]() -> task { co_await me->pkgInstallByName(name); }); +} - RPackage *newpkg = (RPackage *)me->_lister->getPackage(name); +task RGMainWindow::pkgInstallByName(const char *name) +{ + RPackage *newpkg = (RPackage *)_lister->getPackage(name); if (newpkg) { RPackageLister::pkgState state; vector exclude; vector instPkgs; // we always save the state (for undo) - me->_lister->saveState(state); - me->_lister->notifyCachePreChange(); + _lister->saveState(state); + _lister->notifyCachePreChange(); // actual action newpkg->setNotify(false); - me->pkgInstallHelper(newpkg); + pkgInstallHelper(newpkg); newpkg->setNotify(true); exclude.push_back(newpkg); instPkgs.push_back(newpkg); // ask for additional changes - if (me->askStateChange(state, exclude)) { - me->_lister->saveUndoState(state); - if (me->checkForFailedInst(instPkgs)) - me->_lister->restoreState(state); + if (co_await askStateChange(state, exclude)) { + _lister->saveUndoState(state); + if (co_await checkForFailedInst(instPkgs)) + _lister->restoreState(state); } - me->_lister->notifyPostChange(NULL); - me->_lister->notifyCachePostChange(); + _lister->notifyPostChange(NULL); + _lister->notifyCachePostChange(); - RPackage *pkg = me->selectedPackage(); - me->refreshTable(pkg); - me->updatePackageInfo(pkg); + RPackage *pkg = selectedPackage(); + refreshTable(pkg); + updatePackageInfo(pkg); } } @@ -3332,43 +3410,54 @@ void RGMainWindow::cbGenerateDownloadScriptClicked(GSimpleAction *action, { // cout << "cbGenerateDownloadScriptClicked()" << endl; RGMainWindow *me = (RGMainWindow *)data; + start_task([me]() -> task { co_await me->generateDownloadScript(); }); +} +task RGMainWindow::generateDownloadScript() +{ int installed, broken, toInstall, toRemove; double sizeChange; - me->_lister->getStats(installed, broken, toInstall, toRemove, sizeChange); + _lister->getStats(installed, broken, toInstall, toRemove, sizeChange); if (toInstall == 0) { - me->_userDialog->message("Nothing to install/upgrade\n\n" - "Please select the \"Mark all Upgrades\" " - "button or some packages to install/upgrade."); - return; + co_await _userDialog->message( + "Nothing to install/upgrade\n\n" + "Please select the \"Mark all Upgrades\" " + "button or some packages to install/upgrade."); + co_return; } vector uris; - if (!me->_lister->getDownloadUris(uris)) - return; + if (!_lister->getDownloadUris(uris)) + co_return; GtkWidget *filesel; filesel = gtk_file_chooser_dialog_new(_("Save script"), - GTK_WINDOW(me->window()), + GTK_WINDOW(window()), GTK_FILE_CHOOSER_ACTION_SAVE, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Save"), GTK_RESPONSE_ACCEPT, NULL); - int res = gtk_dialog_run(GTK_DIALOG(filesel)); - const char *file = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filesel)); - gtk_widget_destroy(filesel); - if (res != GTK_RESPONSE_ACCEPT) - return; + int res = co_await co_run_dialog(GTK_DIALOG(filesel)); + GFile *selected_file = gtk_file_chooser_get_file(GTK_FILE_CHOOSER(filesel)); + gtk_window_destroy(GTK_WINDOW(filesel)); + if (res != GTK_RESPONSE_ACCEPT) { + g_object_unref(selected_file); + co_return; + } + + char *file = g_file_get_path(selected_file); + g_object_unref(selected_file); // FIXME: this is prototype code, hardcoding wget here suckx ofstream out(file); out << "#!/bin/sh" << endl; - for (int i = 0; i < uris.size(); i++) { + for (size_t i = 0; i < uris.size(); i++) { out << "wget -c " << uris[i] << endl; } chmod(file, 0755); + g_free(file); } void RGMainWindow::cbAddDownloadedFilesClicked(GSimpleAction *action, @@ -3376,57 +3465,70 @@ void RGMainWindow::cbAddDownloadedFilesClicked(GSimpleAction *action, gpointer data) { RGMainWindow *me = (RGMainWindow *)data; + start_task([me]() -> task { co_await me->addDownloadedFiles(); }); +} + +task RGMainWindow::addDownloadedFiles() +{ #ifndef HAVE_RPM - // cout << "cbAddDownloadedFilesClicked()" << endl; GtkWidget *filesel; filesel = gtk_file_chooser_dialog_new(_("Select directory"), - GTK_WINDOW(me->window()), + GTK_WINDOW(window()), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Open"), GTK_RESPONSE_ACCEPT, NULL); - int res = gtk_dialog_run(GTK_DIALOG(filesel)); - const char *path = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filesel)); - gtk_widget_destroy(filesel); - if (res != GTK_RESPONSE_ACCEPT) - return; - if (!g_file_test(path, G_FILE_TEST_IS_DIR)) { - me->_userDialog->error(_("Please select a directory")); - return; + int res = co_await co_run_dialog(GTK_DIALOG(filesel)); + GFile *selected_file = gtk_file_chooser_get_file(GTK_FILE_CHOOSER(filesel)); + gtk_window_destroy(GTK_WINDOW(filesel)); + if (res != GTK_RESPONSE_ACCEPT) { + g_object_unref(selected_file); + co_return; } + GFileType file_type = g_file_query_file_type( + selected_file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL); + if (file_type != G_FILE_TYPE_DIRECTORY) { + co_await _userDialog->error(_("Please select a directory")); + g_object_unref(selected_file); + co_return; + } + // now read the dir for debs + char *path = g_file_get_path(selected_file); const gchar *file; string pkgname; stringstream pkgs; GDir *dir = g_dir_open(path, 0, NULL); while ((file = g_dir_read_name(dir)) != NULL) { if (g_pattern_match_simple("*_*.deb", file)) { - if (me->_lister->addArchiveToCache(string(path) + "/" + string(file), - pkgname)) + if (_lister->addArchiveToCache(string(path) + "/" + string(file), + pkgname)) pkgs << pkgname << "\t install" << endl; } } g_dir_close(dir); + g_free(path); + g_object_unref(selected_file); // and set what we found as selection pkgs.seekg(0); if (pkgs.str() == "") - return; + co_return; - me->_lister->unregisterObserver(me); - me->_lister->readSelections(pkgs); - me->_lister->registerObserver(me); - me->refreshTable(); + _lister->unregisterObserver(this); + _lister->readSelections(pkgs); + _lister->registerObserver(this); + refreshTable(); // show any errors - me->_userDialog->showErrors(); + co_await _userDialog->showErrors(); // click proceed - me->cbProceedClicked(nullptr, nullptr, me); - + co_await applyChanges(); #else - me->_userDialog->error("Sorry, not implemented for rpm, patches welcome"); + co_await _userDialog->error( + "Sorry, not implemented for rpm, patches welcome"); #endif } diff --git a/gtk/rgmainwindow.h b/gtk/rgmainwindow.h index 9d7e3bc58..704d81158 100644 --- a/gtk/rgmainwindow.h +++ b/gtk/rgmainwindow.h @@ -30,16 +30,12 @@ #include "rggtkbuilderwindow.h" #include "rpackagelister.h" #include "rpackageview.h" +#include "coroutines.h" #include #include -#include -#include -#include #include -#include #include -#include #include #include @@ -130,6 +126,8 @@ class RGMainWindow : public RGGtkBuilderWindow, public RPackageObserver // the buttons for the various views GtkWidget *_viewButtons[N_PACKAGE_VIEWS]; + GActionGroup *win_actions; + // init stuff void buildInterface(); void buildTreeView(); @@ -169,32 +167,35 @@ class RGMainWindow : public RGGtkBuilderWindow, public RPackageObserver std::string selectedSubView(); // helpers - void pkgAction(RGPkgAction action); - bool askStateChange( + [[nodiscard]] task pkgAction(RGPkgAction action); + [[nodiscard]] task askStateChange( RPackageLister::pkgState, const std::vector &exclude = std::vector()); - bool checkForFailedInst(std::vector instPkgs); + [[nodiscard]] task checkForFailedInst( + std::vector instPkgs); void pkgInstallHelper(RPackage *pkg, bool fixBroken = true, bool reInstall = false); - void pkgRemoveHelper(RPackage *pkg, - bool purge = false, - bool withDeps = false); + [[nodiscard]] task pkgRemoveHelper(RPackage *pkg, + bool purge = false, + bool withDeps = false); void pkgKeepHelper(RPackage *pkg); // helper for recommends/suggests static void pkgInstallByNameHelper(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task pkgInstallByName(const char *name); // install a non-standard version static void cbInstallFromVersion(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task installFromVersion(); // helpers for search-as-you-type static void cbSearchEntryChanged(GtkWidget *editable, void *data); static void xapianIndexUpdateFinished(GPid pid, gint status, void *data); - static gboolean xapianDoSearch(void *data); + static void xapianDoSearch(void *data); static gboolean xapianDoIndexUpdate(void *data); // RPackageObserver @@ -210,12 +211,13 @@ class RGMainWindow : public RGGtkBuilderWindow, public RPackageObserver void refreshTable(RPackage *selectedPkg = NULL, bool setAdjustments = true); - void changeView(int view, std::string subView = ""); + [[nodiscard]] task changeView(int view, std::string subView = ""); // install the list of packagenames and display a changes window - void selectToInstall(std::vector packagenames); + [[nodiscard]] task selectToInstall( + std::vector packagenames); - void setInterfaceLocked(bool flag); + [[nodiscard]] task setInterfaceLocked(bool flag); void setTreeLocked(bool flag); void rebuildTreeView() { @@ -227,10 +229,10 @@ class RGMainWindow : public RGGtkBuilderWindow, public RPackageObserver // this helper will bring the current active window to the foreground void activeWindowToForeground(); - void saveState(); - bool restoreState(); + [[nodiscard]] task saveState(); + [[nodiscard]] task restoreState(); - bool showErrors(); + [[nodiscard]] task showErrors(); GMenu *buildWeakDependsMenu(RPackage *pkg, pkgCache::Dep::DepType); @@ -264,14 +266,16 @@ class RGMainWindow : public RGGtkBuilderWindow, public RPackageObserver GVariant *parameter, gpointer data); - static gboolean cbPackageListClicked(GtkWidget *treeview, - GdkEventButton *event, - gpointer data); + static void cbPackageListClicked(GtkGestureClick *gesture, + gint n_press, + double x, + double y, + gpointer data); - static void cbTreeviewPopupMenu(GtkWidget *treeview, - GdkEventButton *event, - RGMainWindow *me, - std::vector selected_pkgs); + void cbTreeviewPopupMenu(int button, + double x, + double y, + std::vector selected_pkgs); static void cbChangelogDialog(GSimpleAction *action, GVariant *parameter, @@ -282,6 +286,8 @@ class RGMainWindow : public RGGtkBuilderWindow, public RPackageObserver GtkTreePath *arg1, GtkTreeViewColumn *arg2, gpointer user_data); + [[nodiscard]] task packageListRowActivated(GtkTreeView *treeview, + GtkTreePath *path); static void cbChangedView(GtkWidget *self, void *); static void cbChangedSubView(GtkTreeSelection *selection, gpointer data); @@ -294,23 +300,29 @@ class RGMainWindow : public RGGtkBuilderWindow, public RPackageObserver static void cbTasksClicked(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task tasksClicked(); static void cbOpenClicked(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task openClicked(); static void cbSaveClicked(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task saveClicked(); static void cbSaveAsClicked(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task saveAsClicked(); std::string selectionsFilename; bool saveFullState; static void cbGenerateDownloadScriptClicked(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task generateDownloadScript(); static void cbAddDownloadedFilesClicked(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task addDownloadedFiles(); static void cbViewLogClicked(GSimpleAction *action, GVariant *parameter, gpointer data); @@ -319,48 +331,59 @@ class RGMainWindow : public RGGtkBuilderWindow, public RPackageObserver static void cbUndoClicked(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task undo(); static void cbRedoClicked(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task redo(); static void cbClearAllChangesClicked(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task clearAllChanges(); static void cbUpdateClicked(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task updateClicked(); static void cbAddCDROM(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task addCDROM(); static void cbFixBrokenClicked(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task fixBroken(); static void cbUpgradeClicked(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task upgrade(); static void cbProceedClicked(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task applyChanges(); // packages menu static void cbMenuPinClicked(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task pin(bool active); static void cbMenuAutoInstalledClicked(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task autoInstalled(bool active); // filter menu static void cbShowFilterManagerWindow(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task showFilterManagerWindow(); static void cbSaveFilterAction(void *self, RGFilterWindow *rwin); static void cbCloseFilterAction(void *self, RGFilterWindow *rwin); - static void cbCloseFilterManagerAction(void *self, bool okcancel); // search menu static void cbFindToolClicked(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task findTool(); // preferences menu static void cbShowConfigWindow(GSimpleAction *action, @@ -372,6 +395,7 @@ class RGMainWindow : public RGGtkBuilderWindow, public RPackageObserver static void cbShowSourcesWindow(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task showSourcesWindow(); static void cbMenuToolbarClicked(GSimpleAction *action, GVariant *parameter, gpointer data); @@ -380,6 +404,7 @@ class RGMainWindow : public RGGtkBuilderWindow, public RPackageObserver static void cbHelpAction(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task helpAction(); static void cbShowIconLegendPanel(GSimpleAction *action, GVariant *parameter, gpointer data); @@ -389,12 +414,15 @@ class RGMainWindow : public RGGtkBuilderWindow, public RPackageObserver static void cbShowWelcomeDialog(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task showWelcomeDialog(); // the buttons static void cbPkgHelpClicked(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task pkgHelpClicked(); static void cbPkgReconfigureClicked(GSimpleAction *action, GVariant *parameter, gpointer data); + [[nodiscard]] task pkgReconfigureClicked(); }; diff --git a/gtk/rgpkgcdrom.cc b/gtk/rgpkgcdrom.cc index eb104b589..9b4daeb2b 100644 --- a/gtk/rgpkgcdrom.cc +++ b/gtk/rgpkgcdrom.cc @@ -35,7 +35,6 @@ # include "rgwindow.h" # include "ruserdialog.h" -# include # include # include # include @@ -54,7 +53,7 @@ class RGDiscName : public RGGtkBuilderWindow public: RGDiscName(RGWindow *wwin, const string defaultName); - bool run(string &name); + [[nodiscard]] task run(string &name); }; void RGCDScanner::Update(string text, int current) @@ -66,24 +65,23 @@ void RGCDScanner::Update(string text, int current) gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(_pbar), ((float)current) / totalSteps); show(); - RGFlushInterface(); } bool RGCDScanner::ChangeCdrom() { - return _userDialog->proceed(_("Please insert a disc in the drive.")); + // TODO + // co_return co_await _userDialog->proceed(_("Please insert a disc in the + // drive.")); + return false; } bool RGCDScanner::AskCdromName(string &name) { // cout << "askCdromName()" << endl; RGDiscName discName(this, name); - - if (!discName.run(name)) { - return false; - } - - return true; + // TODO + // co_return co_await discName.run(name); + return false; } RGCDScanner::RGCDScanner(RGMainWindow *main, RUserDialog *userDialog) @@ -96,30 +94,31 @@ RGCDScanner::RGCDScanner(RGMainWindow *main, RUserDialog *userDialog) gtk_window_set_default_size(GTK_WINDOW(_win), 300, 120); GtkWidget *topBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - gtk_container_add(GTK_CONTAINER(_win), topBox); + gtk_window_set_child(GTK_WINDOW(_win), topBox); gtk_widget_set_margin_top(topBox, 12); gtk_widget_set_margin_bottom(topBox, 12); gtk_widget_set_margin_start(topBox, 12); gtk_widget_set_margin_end(topBox, 12); + gtk_box_set_spacing(GTK_BOX(topBox), 12); _label = gtk_label_new("\n\n"); - gtk_box_pack_start(GTK_BOX(topBox), _label, TRUE, TRUE, 10); + gtk_widget_set_hexpand(_label, true); + gtk_widget_set_vexpand(_label, true); + gtk_box_append(GTK_BOX(topBox), _label); _pbar = gtk_progress_bar_new(); gtk_widget_set_size_request(_pbar, -1, 25); - gtk_box_pack_start(GTK_BOX(topBox), _pbar, FALSE, TRUE, 0); - - gtk_widget_show_all(topBox); + gtk_box_append(GTK_BOX(topBox), _pbar); gtk_window_set_transient_for(GTK_WINDOW(_win), GTK_WINDOW(main->window())); - gtk_window_set_position(GTK_WINDOW(_win), GTK_WIN_POS_CENTER_ON_PARENT); } -bool RGCDScanner::run() +task RGCDScanner::run() { pkgCdrom scanner; - return scanner.Add(this); + bool result = scanner.Add(this); + co_return result; } RGDiscName::RGDiscName(RGWindow *wwin, const string defaultName) @@ -127,7 +126,7 @@ RGDiscName::RGDiscName(RGWindow *wwin, const string defaultName) { setTitle(_("Disc Label")); _textEntry = GTK_WIDGET(gtk_builder_get_object(_builder, "text_entry")); - gtk_entry_set_text(GTK_ENTRY(_textEntry), defaultName.c_str()); + gtk_editable_set_text(GTK_EDITABLE(_textEntry), defaultName.c_str()); g_signal_connect(gtk_builder_get_object(_builder, "ok"), "clicked", @@ -137,30 +136,28 @@ RGDiscName::RGDiscName(RGWindow *wwin, const string defaultName) "clicked", G_CALLBACK(onCancelClicked), this); - gtk_window_set_skip_taskbar_hint(GTK_WINDOW(_win), TRUE); gtk_window_set_transient_for(GTK_WINDOW(_win), GTK_WINDOW(wwin->window())); - gtk_window_set_position(GTK_WINDOW(_win), GTK_WIN_POS_CENTER_ON_PARENT); } void RGDiscName::onOkClicked(GtkWidget *self, void *data) { RGDiscName *me = (RGDiscName *)data; me->_userConfirmed = true; - gtk_main_quit(); + gtk_window_close(GTK_WINDOW(me->_win)); } void RGDiscName::onCancelClicked(GtkWidget *self, void *data) { - gtk_main_quit(); + RGDiscName *me = (RGDiscName *)data; + gtk_window_close(GTK_WINDOW(me->_win)); } -bool RGDiscName::run(string &discName) +task RGDiscName::run(string &discName) { _userConfirmed = false; - show(); - gtk_main(); - discName = gtk_entry_get_text(GTK_ENTRY(_textEntry)); - return _userConfirmed; + co_await co_run_window(GTK_WINDOW(_win)); + discName = gtk_editable_get_text(GTK_EDITABLE(_textEntry)); + co_return _userConfirmed; } #endif diff --git a/gtk/rgpkgcdrom.h b/gtk/rgpkgcdrom.h index 253a1a9a2..62f5615cc 100644 --- a/gtk/rgpkgcdrom.h +++ b/gtk/rgpkgcdrom.h @@ -29,6 +29,7 @@ #ifdef HAVE_APTPKG_CDROM # include "rgwindow.h" +# include "coroutines.h" # include # include @@ -52,7 +53,7 @@ class RGCDScanner : public pkgCdromStatus, public RGWindow bool AskCdromName(std::string &defaultName); void Update(std::string text, int current); - bool run(); + [[nodiscard]] task run(); }; #endif diff --git a/gtk/rgpkgdetails.cc b/gtk/rgpkgdetails.cc index b7bc775fe..7c2a6a927 100644 --- a/gtk/rgpkgdetails.cc +++ b/gtk/rgpkgdetails.cc @@ -26,6 +26,7 @@ #include "rgpkgdetails.h" #include "i18n.h" +#include "racquireasync.h" #include "rgchangelogdialog.h" #include "rgfetchprogress.h" #include "rggtkbuilderwindow.h" @@ -39,13 +40,7 @@ #include #include #include -#include -#include -#include -#include -#include #include -#include #include #include #include @@ -146,68 +141,70 @@ vector RGPkgDetailsWindow::formatDepInformation( return depStrings; } -void RGPkgDetailsWindow::cbShowBigScreenshot(GtkWidget *box, - GdkEventButton *event, - void *data) +void RGPkgDetailsWindow::cbShowBigScreenshot(GtkGestureClick *gesture, + gint n_press, + gdouble x, + gdouble y, + gpointer data) { // cerr << "cbShowBigScreenshot" << endl; RPackage *pkg = (RPackage *)data; - - doShowBigScreenshot(pkg); + start_task([pkg] -> task { co_await doShowBigScreenshot(pkg); }); } -void RGPkgDetailsWindow::doShowBigScreenshot(RPackage *pkg) +task RGPkgDetailsWindow::doShowBigScreenshot(RPackage *pkg) { RGFetchProgress *status = new RGFetchProgress(NULL); - ; - pkgAcquire fetcher(status); - string filename = pkg->getScreenshotFile(&fetcher, false); - GtkWidget *img = gtk_image_new_from_file(filename.c_str()); + + pkgAcquire fetcher; + string filename = co_await pkg->getScreenshotFile(&fetcher, status, false); + GtkWidget *img = gtk_picture_new_for_filename(filename.c_str()); GtkWidget *win = gtk_dialog_new(); gtk_window_set_default_size(GTK_WINDOW(win), 500, 400); gtk_dialog_add_button(GTK_DIALOG(win), _("_Close"), GTK_RESPONSE_CLOSE); - gtk_widget_show(img); GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(win)); - gtk_container_add(GTK_CONTAINER(content_area), img); - gtk_dialog_run(GTK_DIALOG(win)); - gtk_widget_destroy(win); + gtk_widget_set_vexpand(img, TRUE); + gtk_box_append(GTK_BOX(content_area), img); + g_signal_connect(win, "response", G_CALLBACK(gtk_window_destroy), nullptr); + gtk_window_present(GTK_WINDOW(win)); } void RGPkgDetailsWindow::cbShowScreenshot(GtkWidget *button, void *data) { struct screenshot_info *si = (struct screenshot_info *)data; - - if (_config->FindB("Synaptic::InlineScreenshots") == false) { - doShowBigScreenshot(si->pkg); - return; - } else { - - // hide button - gtk_widget_hide(button); - - // get screenshot - RGFetchProgress *status = new RGFetchProgress(NULL); - ; - pkgAcquire fetcher(status); - string filename = si->pkg->getScreenshotFile(&fetcher); - GtkWidget *event = gtk_event_box_new(); - GtkWidget *img = gtk_image_new_from_file(filename.c_str()); - gtk_container_add(GTK_CONTAINER(event), img); - g_signal_connect(G_OBJECT(event), - "button_press_event", - G_CALLBACK(cbShowBigScreenshot), - (void *)si->pkg); - gtk_text_view_add_child_at_anchor( - GTK_TEXT_VIEW(si->textview), GTK_WIDGET(event), si->anchor); - gtk_widget_show_all(event); - } + start_task([si, button]() -> task { + if (_config->FindB("Synaptic::InlineScreenshots") == false) { + co_await doShowBigScreenshot(si->pkg); + } else { + // hide button + gtk_widget_hide(button); + + // get screenshot + RGFetchProgress *status = new RGFetchProgress(NULL); + + pkgAcquire fetcher; + string filename = + co_await si->pkg->getScreenshotFile(&fetcher, status); + GtkWidget *img = gtk_picture_new_for_filename(filename.c_str()); + GtkGesture *click_controller = gtk_gesture_click_new(); + g_signal_connect(G_OBJECT(click_controller), + "pressed", + G_CALLBACK(cbShowBigScreenshot), + (void *)si->pkg); + gtk_widget_add_controller(img, GTK_EVENT_CONTROLLER(click_controller)); + gtk_text_view_add_child_at_anchor( + GTK_TEXT_VIEW(si->textview), GTK_WIDGET(img), si->anchor); + } + }); } void RGPkgDetailsWindow::cbShowChangelog(GtkWidget *button, void *data) { RPackage *pkg = (RPackage *)data; RGWindow *parent = (RGWindow *)g_object_get_data(G_OBJECT(button), "me"); - ShowChangelogDialog(parent, pkg); + start_task([parent, pkg]() -> task { + co_await ShowChangelogDialog(parent, pkg); + }); } gboolean RGPkgDetailsWindow::cbOpenLink(GtkWidget *label, @@ -314,18 +311,16 @@ void RGPkgDetailsWindow::fillInValues(RGGtkBuilderWindow *me, // insert space gtk_text_buffer_insert(buf, &it, " ", 1); // make image - emblem = gtk_image_new_from_icon_name(icon_name, GTK_ICON_SIZE_BUTTON); + emblem = gtk_image_new_from_icon_name(icon_name); gtk_image_set_pixel_size(GTK_IMAGE(emblem), 16); - // set eventbox and tooltip - GtkWidget *event = gtk_event_box_new(); - gtk_container_add(GTK_CONTAINER(event), emblem); + // set tooltip gtk_widget_set_tooltip_text( - event, _("This application is supported by the distribution")); + emblem, _("This application is supported by the distribution")); // create anchor GtkTextChildAnchor *anchor = gtk_text_buffer_create_child_anchor(buf, &it); - gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(textview), event, anchor); - gtk_widget_show_all(event); + gtk_text_view_add_child_at_anchor( + GTK_TEXT_VIEW(textview), emblem, anchor); } // add button to get screenshot @@ -401,7 +396,7 @@ void RGPkgDetailsWindow::fillInValues(RGGtkBuilderWindow *me, gchar *str; vector list; vector> versions = pkg->getAvailableVersions(); - for (int i = 0; i < versions.size(); i++) { + for (size_t i = 0; i < versions.size(); i++) { // TRANSLATORS: this the format of the available versions in // the "Properties/Available versions" window // e.g. "0.56 (unstable)" diff --git a/gtk/rgpkgdetails.h b/gtk/rgpkgdetails.h index dfeb30b7a..316cd46bb 100644 --- a/gtk/rgpkgdetails.h +++ b/gtk/rgpkgdetails.h @@ -28,8 +28,6 @@ #include "rggtkbuilderwindow.h" #include "rpackage.h" -#include -#include #include #include #include @@ -54,13 +52,15 @@ class RGPkgDetailsWindow : public RGGtkBuilderWindow static void cbDependsMenuChanged(GtkWidget *self, void *data); static void cbCloseClicked(GtkWidget *self, void *data); static void cbShowScreenshot(GtkWidget *button, void *data); - static void cbShowBigScreenshot(GtkWidget *button, - GdkEventButton *event, - void *data); + static void cbShowBigScreenshot(GtkGestureClick *gesture, + gint n_press, + gdouble x, + gdouble y, + gpointer data); static void cbShowChangelog(GtkWidget *button, void *data); static gboolean cbOpenHomepage(GtkWidget *button, void *data); static gboolean cbOpenLink(GtkWidget *button, gchar *uri, void *data); - static void doShowBigScreenshot(RPackage *pkg); + [[nodiscard]] static task doShowBigScreenshot(RPackage *pkg); public: RGPkgDetailsWindow(RGWindow *parent); diff --git a/gtk/rgpreferenceswindow.cc b/gtk/rgpreferenceswindow.cc index 584675886..4316c21b7 100644 --- a/gtk/rgpreferenceswindow.cc +++ b/gtk/rgpreferenceswindow.cc @@ -41,11 +41,6 @@ #include #include #include -#include -#include -#include -#include -#include #include #include #include @@ -101,11 +96,11 @@ const char *RGPreferencesWindow::upgrade_method[] = {N_("Always Ask"), void RGPreferencesWindow::cbHttpProxyEntryChanged(GtkWidget *self, void *data) { // this function strips http:// from a entred proxy url - const gchar *text = gtk_entry_get_text(GTK_ENTRY(self)); + const gchar *text = gtk_editable_get_text(GTK_EDITABLE(self)); gchar *new_text = NULL; if (g_str_has_prefix(text, "http://")) { new_text = g_strdup_printf("%s", &text[strlen("http://")]); - gtk_entry_set_text(GTK_ENTRY(self), new_text); + gtk_editable_set_text(GTK_EDITABLE(self), new_text); } if (new_text != NULL) g_free(new_text); @@ -135,8 +130,7 @@ void RGPreferencesWindow::cbRadioDistributionChanged(GtkWidget *self, RGPreferencesWindow *me = (RGPreferencesWindow *)data; // we are only interested in the active one - if (me->_blockAction || - !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(self))) + if (me->_blockAction || !gtk_check_button_get_active(GTK_CHECK_BUTTON(self))) return; gchar *defaultDistro = @@ -233,8 +227,8 @@ void RGPreferencesWindow::saveGeneral() int i; // show package properties in main window - newval = gtk_toggle_button_get_active( - GTK_TOGGLE_BUTTON(_optionShowAllPkgInfoInMain)); + newval = gtk_check_button_get_active( + GTK_CHECK_BUTTON(_optionShowAllPkgInfoInMain)); _config->Set("Synaptic::ShowAllPkgInfoInMain", newval ? "true" : "false"); // apply the changes GtkWidget *notebook = GTK_WIDGET( @@ -255,15 +249,15 @@ void RGPreferencesWindow::saveGeneral() } // Ask to confirm changes also affecting other packages - newval = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(_optionAskRelated)); + newval = gtk_check_button_get_active(GTK_CHECK_BUTTON(_optionAskRelated)); _config->Set("Synaptic::AskRelated", newval ? "true" : "false"); // Consider recommended packages as dependencies - newval = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(_optionCheckRecom)); + newval = gtk_check_button_get_active(GTK_CHECK_BUTTON(_optionCheckRecom)); _config->Set("APT::Install-Recommends", newval ? "true" : "false"); // Clicking on the status icon marks the most likely action - newval = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(_optionOneClick)); + newval = gtk_check_button_get_active(GTK_CHECK_BUTTON(_optionOneClick)); _config->Set("Synaptic::OneClickOnStatusActions", newval ? "true" : "false"); // Removal of packages: @@ -288,11 +282,11 @@ void RGPreferencesWindow::saveGeneral() _config->Set("Synaptic::undoStackSize", maxUndo); // Apply changes in a terminal window - newval = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(_optionUseTerminal)); + newval = gtk_check_button_get_active(GTK_CHECK_BUTTON(_optionUseTerminal)); _config->Set("Synaptic::UseTerminal", newval ? "true" : "false"); // Ask to quit after the changes have been applied successfully - newval = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(_optionAskQuit)); + newval = gtk_check_button_get_active(GTK_CHECK_BUTTON(_optionAskQuit)); _config->Set("Synaptic::AskQuitOnProceed", newval ? "true" : "false"); } @@ -301,7 +295,7 @@ void RGPreferencesWindow::saveColumnsAndFonts() bool newval; // Use custom application font - newval = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( + newval = gtk_check_button_get_active(GTK_CHECK_BUTTON( gtk_builder_get_object(_builder, "checkbutton_user_font"))); _config->Set("Synaptic::useUserFont", newval); @@ -321,7 +315,7 @@ void RGPreferencesWindow::saveColumnsAndFonts() } g_value_unset(&value); - newval = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( + newval = gtk_check_button_get_active(GTK_CHECK_BUTTON( gtk_builder_get_object(_builder, "checkbutton_user_terminal_font"))); _config->Set("Synaptic::useUserTerminalFont", newval); @@ -368,7 +362,7 @@ void RGPreferencesWindow::saveColors() // save the colors RGPackageStatus::pkgStatus.saveColors(); newval = - gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(_optionUseStatusColors)); + gtk_check_button_get_active(GTK_CHECK_BUTTON(_optionUseStatusColors)); _config->Set("Synaptic::UseStatusColors", newval ? "true" : "false"); } @@ -377,13 +371,13 @@ void RGPreferencesWindow::saveFiles() bool newval; // cache - newval = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(_cacheClean)); + newval = gtk_check_button_get_active(GTK_CHECK_BUTTON(_cacheClean)); _config->Set("Synaptic::CleanCache", newval ? "true" : "false"); - newval = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(_cacheAutoClean)); + newval = gtk_check_button_get_active(GTK_CHECK_BUTTON(_cacheAutoClean)); _config->Set("Synaptic::AutoCleanCache", newval ? "true" : "false"); // history - newval = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(_delHistory)); + newval = gtk_check_button_get_active(GTK_CHECK_BUTTON(_delHistory)); if (!newval) { _config->Set("Synaptic::delHistory", -1); return; @@ -400,24 +394,24 @@ void RGPreferencesWindow::saveNetwork() const gchar *http, *ftp, *noProxy; int httpPort, ftpPort; - useProxy = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(_useProxy)); + useProxy = gtk_check_button_get_active(GTK_CHECK_BUTTON(_useProxy)); _config->Set("Synaptic::useProxy", useProxy); // http - http = gtk_entry_get_text( - GTK_ENTRY(gtk_builder_get_object(_builder, "entry_http_proxy"))); + http = gtk_editable_get_text( + GTK_EDITABLE(gtk_builder_get_object(_builder, "entry_http_proxy"))); _config->Set("Synaptic::httpProxy", http); httpPort = (int)gtk_spin_button_get_value(GTK_SPIN_BUTTON( gtk_builder_get_object(_builder, "spinbutton_http_port"))); _config->Set("Synaptic::httpProxyPort", httpPort); // ftp - ftp = gtk_entry_get_text( - GTK_ENTRY(gtk_builder_get_object(_builder, "entry_ftp_proxy"))); + ftp = gtk_editable_get_text( + GTK_EDITABLE(gtk_builder_get_object(_builder, "entry_ftp_proxy"))); _config->Set("Synaptic::ftpProxy", ftp); ftpPort = (int)gtk_spin_button_get_value( GTK_SPIN_BUTTON(gtk_builder_get_object(_builder, "spinbutton_ftp_port"))); _config->Set("Synaptic::ftpProxyPort", ftpPort); - noProxy = gtk_entry_get_text( - GTK_ENTRY(gtk_builder_get_object(_builder, "entry_no_proxy"))); + noProxy = gtk_editable_get_text( + GTK_EDITABLE(gtk_builder_get_object(_builder, "entry_no_proxy"))); _config->Set("Synaptic::noProxy", noProxy); applyProxySettings(); @@ -435,21 +429,26 @@ void RGPreferencesWindow::saveDistribution() } -void RGPreferencesWindow::saveAction(GtkWidget *self, void *data) +void RGPreferencesWindow::cbSaveAction(GtkWidget *self, void *data) { RGPreferencesWindow *me = (RGPreferencesWindow *)data; + start_task([me]() -> task { co_await me->saveAction(); }); +} + - me->saveGeneral(); - me->saveColumnsAndFonts(); - me->saveColors(); - me->saveFiles(); - me->saveNetwork(); - me->saveDistribution(); +task RGPreferencesWindow::saveAction() +{ + saveGeneral(); + saveColumnsAndFonts(); + saveColors(); + saveFiles(); + saveNetwork(); + saveDistribution(); if (!RWriteConfigFile(*_config)) { _error->Error(_("An error occurred while saving configurations.")); - RGUserDialog userDialog(me); - userDialog.showErrors(); + RGUserDialog userDialog(this); + co_await userDialog.showErrors(); } } @@ -457,64 +456,68 @@ void RGPreferencesWindow::saveAction(GtkWidget *self, void *data) void RGPreferencesWindow::closeAction(GtkWidget *self, void *data) { RGPreferencesWindow *me = (RGPreferencesWindow *)data; - me->close(); + me->hide(); } -void RGPreferencesWindow::doneAction(GtkWidget *self, void *data) +void RGPreferencesWindow::cbDoneAction(GtkWidget *self, void *data) { RGPreferencesWindow *me = (RGPreferencesWindow *)data; - me->saveAction(self, data); - if (me->distroChanged) { - me->hide(); - me->_lister->unregisterObserver(me->_mainWin); - me->_mainWin->setTreeLocked(TRUE); - if (!me->_lister->openCache()) { - me->_mainWin->showErrors(); + start_task([me]() -> task { co_await me->doneAction(); }); +} + +task RGPreferencesWindow::doneAction() +{ + co_await saveAction(); + if (distroChanged) { + hide(); + _lister->unregisterObserver(_mainWin); + _mainWin->setTreeLocked(TRUE); + if (!_lister->openCache()) { + co_await _mainWin->showErrors(); exit(1); } - me->_mainWin->setTreeLocked(FALSE); - me->_lister->registerObserver(me->_mainWin); - me->_mainWin->refreshTable(); + _mainWin->setTreeLocked(FALSE); + _lister->registerObserver(_mainWin); + _mainWin->refreshTable(); } - me->closeAction(self, data); + closeAction(nullptr, this); } -void RGPreferencesWindow::changeFontAction(GtkWidget *self, void *data) +void RGPreferencesWindow::changeDefaultFontAction(GtkWidget *self, void *data) { - const char *fontName, *propName; - - switch (GPOINTER_TO_INT(data)) { - case FONT_DEFAULT: - propName = "Synaptic::FontName"; - fontName = "sans 10"; - break; - case FONT_TERMINAL: - propName = "Synaptic::TerminalFontName"; - fontName = "monospace 10"; - break; - default: - cerr << "changeFontAction called with unknown argument" << endl; - return; - } + auto me = static_cast(data); + start_task([me]() -> task { + co_await me->changeFont("Synaptic::FontName", "sans 10"); + }); +} + +void RGPreferencesWindow::changeTerminalFontAction(GtkWidget *self, void *data) +{ + auto me = static_cast(data); + start_task([me]() -> task { + co_await me->changeFont("Synaptic::TerminalFontName", "monospace 10"); + }); +} +task RGPreferencesWindow::changeFont(const char *propName, + const char *defaultValue) +{ GtkWidget *fontsel = gtk_font_chooser_dialog_new( - _("Choose font"), GTK_WINDOW(gtk_widget_get_toplevel(self))); + _("Choose font"), GTK_WINDOW(gtk_widget_get_root(_win))); + gtk_window_set_modal(GTK_WINDOW(fontsel), true); gtk_font_chooser_set_font(GTK_FONT_CHOOSER(fontsel), - _config->Find(propName, fontName).c_str()); - - gint result = gtk_dialog_run(GTK_DIALOG(fontsel)); - if (result != GTK_RESPONSE_OK) { - gtk_widget_destroy(fontsel); - return; - } + _config->Find(propName, defaultValue).c_str()); - fontName = gtk_font_chooser_get_font(GTK_FONT_CHOOSER(fontsel)); - // cout << "fontname: " << fontName << endl; + int result = co_await co_run_dialog(GTK_DIALOG(fontsel)); + if (result == GTK_RESPONSE_OK) { + auto fontName = gtk_font_chooser_get_font(GTK_FONT_CHOOSER(fontsel)); + // cout << "fontname: " << fontName << endl; - _config->Set(propName, fontName); + _config->Set(propName, fontName); + } - gtk_widget_destroy(fontsel); + gtk_window_destroy(GTK_WINDOW(fontsel)); } void RGPreferencesWindow::clearCacheAction(GtkWidget *self, void *data) @@ -527,22 +530,22 @@ void RGPreferencesWindow::clearCacheAction(GtkWidget *self, void *data) void RGPreferencesWindow::readGeneral() { // Allow regular expressions in searches and filters - gtk_toggle_button_set_active( - GTK_TOGGLE_BUTTON(_optionShowAllPkgInfoInMain), + gtk_check_button_set_active( + GTK_CHECK_BUTTON(_optionShowAllPkgInfoInMain), _config->FindB("Synaptic::ShowAllPkgInfoInMain", false)); // Ask to confirm changes also affecting other packages - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(_optionAskRelated), - _config->FindB("Synaptic::AskRelated", true)); + gtk_check_button_set_active(GTK_CHECK_BUTTON(_optionAskRelated), + _config->FindB("Synaptic::AskRelated", true)); // Consider recommended packages as dependencies - gtk_toggle_button_set_active( - GTK_TOGGLE_BUTTON(_optionCheckRecom), + gtk_check_button_set_active( + GTK_CHECK_BUTTON(_optionCheckRecom), _config->FindB("APT::Install-Recommends", false)); // Clicking on the status icon marks the most likely action - gtk_toggle_button_set_active( - GTK_TOGGLE_BUTTON(_optionOneClick), + gtk_check_button_set_active( + GTK_CHECK_BUTTON(_optionOneClick), _config->FindB("Synaptic::OneClickOnStatusActions", false)); // Removal of packages: @@ -586,13 +589,13 @@ void RGPreferencesWindow::readGeneral() # endif # endif #endif - gtk_toggle_button_set_active( - GTK_TOGGLE_BUTTON(_optionUseTerminal), + gtk_check_button_set_active( + GTK_CHECK_BUTTON(_optionUseTerminal), _config->FindB("Synaptic::UseTerminal", UseTerminal)); // Ask to quit after the changes have been applied successfully - gtk_toggle_button_set_active( - GTK_TOGGLE_BUTTON(_optionAskQuit), + gtk_check_button_set_active( + GTK_CHECK_BUTTON(_optionAskQuit), _config->FindB("Synaptic::AskQuitOnProceed", false)); } @@ -600,13 +603,13 @@ void RGPreferencesWindow::readColumnsAndFonts() { // font stuff bool b = _config->FindB("Synaptic::useUserFont", false); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object( - _builder, "checkbutton_user_font")), - b); + gtk_check_button_set_active(GTK_CHECK_BUTTON(gtk_builder_get_object( + _builder, "checkbutton_user_font")), + b); b = _config->FindB("Synaptic::useUserTerminalFont", false); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object( - _builder, "checkbutton_user_terminal_font")), - b); + gtk_check_button_set_active(GTK_CHECK_BUTTON(gtk_builder_get_object( + _builder, "checkbutton_user_terminal_font")), + b); readTreeViewValues(); } @@ -618,8 +621,8 @@ void RGPreferencesWindow::readColors() GtkWidget *button = NULL; // Color packages by their status - gtk_toggle_button_set_active( - GTK_TOGGLE_BUTTON(_optionUseStatusColors), + gtk_check_button_set_active( + GTK_CHECK_BUTTON(_optionUseStatusColors), _config->FindB("Synaptic::UseStatusColors", true)); // color buttons @@ -646,7 +649,7 @@ void RGPreferencesWindow::readColors() color_string); g_free(color_string); } - gtk_css_provider_load_from_data(_css_provider, custom_css->str, -1, NULL); + gtk_css_provider_load_from_data(_css_provider, custom_css->str, -1); g_free(color_button); } g_string_free(custom_css, TRUE); @@ -658,23 +661,19 @@ void RGPreferencesWindow::readFiles() bool postClean = _config->FindB("Synaptic::CleanCache", false); bool postAutoClean = _config->FindB("Synaptic::AutoCleanCache", true); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(_cacheClean), postClean); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(_cacheAutoClean), - postAutoClean); - if (postClean) - gtk_button_clicked(GTK_BUTTON(_cacheClean)); + gtk_check_button_set_active(GTK_CHECK_BUTTON(_cacheClean), true); else if (postAutoClean) - gtk_button_clicked(GTK_BUTTON(_cacheAutoClean)); + gtk_check_button_set_active(GTK_CHECK_BUTTON(_cacheAutoClean), true); else - gtk_button_clicked(GTK_BUTTON(_cacheLeave)); + gtk_check_button_set_active(GTK_CHECK_BUTTON(_cacheLeave), true); // history int delHistory = _config->FindI("Synaptic::delHistory", -1); if (delHistory < 0) - gtk_button_clicked(GTK_BUTTON(_keepHistory)); + gtk_check_button_set_active(GTK_CHECK_BUTTON(_keepHistory), true); else { - gtk_button_clicked(GTK_BUTTON(_delHistory)); + gtk_check_button_set_active(GTK_CHECK_BUTTON(_delHistory), true); gtk_spin_button_set_value(GTK_SPIN_BUTTON(_spinDelHistory), delHistory); } } @@ -683,14 +682,14 @@ void RGPreferencesWindow::readNetwork() { // proxy stuff bool useProxy = _config->FindB("Synaptic::useProxy", false); - gtk_toggle_button_set_active( - GTK_TOGGLE_BUTTON(gtk_builder_get_object(_builder, "radio_no_proxy")), + gtk_check_button_set_active( + GTK_CHECK_BUTTON(gtk_builder_get_object(_builder, "radio_no_proxy")), !useProxy); gtk_widget_set_sensitive( GTK_WIDGET(gtk_builder_get_object(_builder, "table_proxy")), useProxy); string str = _config->Find("Synaptic::httpProxy", ""); - gtk_entry_set_text( - GTK_ENTRY(gtk_builder_get_object(_builder, "entry_http_proxy")), + gtk_editable_set_text( + GTK_EDITABLE(gtk_builder_get_object(_builder, "entry_http_proxy")), str.c_str()); int i = _config->FindI("Synaptic::httpProxyPort", 3128); gtk_spin_button_set_value( @@ -698,16 +697,16 @@ void RGPreferencesWindow::readNetwork() i); str = _config->Find("Synaptic::ftpProxy", ""); - gtk_entry_set_text( - GTK_ENTRY(gtk_builder_get_object(_builder, "entry_ftp_proxy")), + gtk_editable_set_text( + GTK_EDITABLE(gtk_builder_get_object(_builder, "entry_ftp_proxy")), str.c_str()); i = _config->FindI("Synaptic::ftpProxyPort", 3128); gtk_spin_button_set_value( GTK_SPIN_BUTTON(gtk_builder_get_object(_builder, "spinbutton_ftp_port")), i); str = _config->Find("Synaptic::noProxy", ""); - gtk_entry_set_text( - GTK_ENTRY(gtk_builder_get_object(_builder, "entry_no_proxy")), + gtk_editable_set_text( + GTK_EDITABLE(gtk_builder_get_object(_builder, "entry_no_proxy")), str.c_str()); } @@ -758,7 +757,7 @@ void RGPreferencesWindow::readDistribution() gtk_widget_set_sensitive(GTK_WIDGET(_comboDefaultDistro), TRUE); } assert(button); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE); + gtk_check_button_set_active(GTK_CHECK_BUTTON(button), TRUE); // set up combo-box @@ -946,31 +945,40 @@ void RGPreferencesWindow::cbToggleColumn(GtkWidget *self, } -void RGPreferencesWindow::colorClicked(GtkWidget *self, void *data) +void RGPreferencesWindow::cbColorClicked(GtkWidget *self, void *data) +{ + RGPreferencesWindow *me = + (RGPreferencesWindow *)g_object_get_data(G_OBJECT(self), "me"); + int status = GPOINTER_TO_INT(data); + + start_task( + [me, status]() -> task { co_await me->colorClicked(status); }); +} + +task RGPreferencesWindow::colorClicked(int status) { GtkWidget *color_dialog; - RGPreferencesWindow *me; - me = (RGPreferencesWindow *)g_object_get_data(G_OBJECT(self), "me"); - color_dialog = gtk_color_chooser_dialog_new( - _("Color selection"), - GTK_WINDOW(gtk_builder_get_object(me->_builder, "window_preferences"))); + color_dialog = + gtk_color_chooser_dialog_new(_("Color selection"), GTK_WINDOW(window())); + gtk_window_set_modal(GTK_WINDOW(color_dialog), true); gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(color_dialog), false); GdkRGBA *color = NULL; - color = RGPackageStatus::pkgStatus.getColor(GPOINTER_TO_INT(data)); + color = RGPackageStatus::pkgStatus.getColor(status); if (color != NULL) gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(color_dialog), color); - if (gtk_dialog_run(GTK_DIALOG(color_dialog)) == GTK_RESPONSE_OK) { + int response_id = co_await co_run_dialog(GTK_DIALOG(color_dialog)); + if (response_id == GTK_RESPONSE_OK) { GdkRGBA current_color; gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(color_dialog), ¤t_color); - RGPackageStatus::pkgStatus.setColor(GPOINTER_TO_INT(data), + RGPackageStatus::pkgStatus.setColor(status, gdk_rgba_copy(¤t_color)); - me->readColors(); + readColors(); } - gtk_widget_destroy(color_dialog); + gtk_window_destroy(GTK_WINDOW(color_dialog)); } void RGPreferencesWindow::useProxyToggled(GtkWidget *self, void *data) @@ -979,7 +987,7 @@ void RGPreferencesWindow::useProxyToggled(GtkWidget *self, void *data) bool useProxy; RGPreferencesWindow *me = (RGPreferencesWindow *)data; - useProxy = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(me->_useProxy)); + useProxy = gtk_check_button_get_active(GTK_CHECK_BUTTON(me->_useProxy)); gtk_widget_set_sensitive( GTK_WIDGET(gtk_builder_get_object(me->_builder, "table_proxy")), useProxy); @@ -995,7 +1003,7 @@ void RGPreferencesWindow::checkbuttonUserFontToggled(GtkWidget *self, GtkWidget *check = GTK_WIDGET(gtk_builder_get_object(me->_builder, "checkbutton_user_font")); gtk_widget_set_sensitive( - button, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check))); + button, gtk_check_button_get_active(GTK_CHECK_BUTTON(check))); } void RGPreferencesWindow::checkbuttonUserTerminalFontToggled(GtkWidget *self, @@ -1008,7 +1016,7 @@ void RGPreferencesWindow::checkbuttonUserTerminalFontToggled(GtkWidget *self, GtkWidget *check = GTK_WIDGET( gtk_builder_get_object(me->_builder, "checkbutton_user_terminal_font")); gtk_widget_set_sensitive( - button, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check))); + button, gtk_check_button_get_active(GTK_CHECK_BUTTON(check))); } @@ -1189,10 +1197,10 @@ RGPreferencesWindow::RGPreferencesWindow(RGWindow *win, RPackageLister *lister) G_CALLBACK(closeAction), this); g_signal_connect(gtk_builder_get_object(_builder, "apply"), "clicked", - G_CALLBACK(saveAction), this); + G_CALLBACK(cbSaveAction), this); g_signal_connect(gtk_builder_get_object(_builder, "ok"), "clicked", - G_CALLBACK(doneAction), this); + G_CALLBACK(cbDoneAction), this); g_signal_connect(gtk_builder_get_object(_builder, "button_clean_cache"), "clicked", @@ -1208,7 +1216,7 @@ RGPreferencesWindow::RGPreferencesWindow(RGWindow *win, RPackageLister *lister) g_signal_connect(gtk_builder_get_object(_builder, "button_default_font"), "clicked", - G_CALLBACK(changeFontAction),GINT_TO_POINTER(FONT_DEFAULT)); + G_CALLBACK(changeDefaultFontAction), this); g_signal_connect(gtk_builder_get_object(_builder, "checkbutton_user_terminal_font"), "toggled", @@ -1219,8 +1227,8 @@ RGPreferencesWindow::RGPreferencesWindow(RGWindow *win, RPackageLister *lister) g_signal_connect(gtk_builder_get_object(_builder, "button_terminal_font"), "clicked", - G_CALLBACK(changeFontAction), - GINT_TO_POINTER(FONT_TERMINAL)); + G_CALLBACK(changeTerminalFontAction), + this); checkbuttonUserTerminalFontToggled(NULL, this); checkbuttonUserFontToggled(NULL, this); @@ -1240,12 +1248,11 @@ RGPreferencesWindow::RGPreferencesWindow(RGWindow *win, RPackageLister *lister) g_object_set_data(G_OBJECT(button), "me", this); g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(colorClicked), + G_CALLBACK(cbColorClicked), GINT_TO_POINTER(i)); g_free(color_button); } - skipTaskbar(true); setTitle(_("Preferences")); } @@ -1258,32 +1265,33 @@ void RGPreferencesWindow::buttonAuthenticationClicked(GtkWidget *self, void *data) { RGPreferencesWindow *me = (RGPreferencesWindow *)data; - - RGGtkBuilderUserDialog dia(me, "authentication"); - GtkBuilder *dia_xml = dia.getGtkBuilder(); - GtkWidget *entry_user = - GTK_WIDGET(gtk_builder_get_object(dia_xml, "entry_username")); - GtkWidget *entry_pass = - GTK_WIDGET(gtk_builder_get_object(dia_xml, "entry_password")); - - // now set the values - string now_user = _config->Find("Synaptic::httpProxyUser", ""); - gtk_entry_set_text(GTK_ENTRY(entry_user), now_user.c_str()); - string now_pass = _config->Find("Synaptic::httpProxyPass", ""); - gtk_entry_set_text(GTK_ENTRY(entry_pass), now_pass.c_str()); - - int res = dia.run(); - - if (!res) - return; - - // get the entered data - const gchar *user = gtk_entry_get_text(GTK_ENTRY(entry_user)); - const gchar *pass = gtk_entry_get_text(GTK_ENTRY(entry_pass)); - - // write out the configuration - _config->Set("Synaptic::httpProxyUser", user); - _config->Set("Synaptic::httpProxyPass", pass); + start_task([me]() -> task { + RGGtkBuilderUserDialog dia(me, "authentication"); + GtkBuilder *dia_xml = dia.getGtkBuilder(); + GtkWidget *entry_user = + GTK_WIDGET(gtk_builder_get_object(dia_xml, "entry_username")); + GtkWidget *entry_pass = + GTK_WIDGET(gtk_builder_get_object(dia_xml, "entry_password")); + + // now set the values + string now_user = _config->Find("Synaptic::httpProxyUser", ""); + gtk_editable_set_text(GTK_EDITABLE(entry_user), now_user.c_str()); + string now_pass = _config->Find("Synaptic::httpProxyPass", ""); + gtk_editable_set_text(GTK_EDITABLE(entry_pass), now_pass.c_str()); + + int res = co_await dia.co_run(); + + if (!res) + co_return; + + // get the entered data + const gchar *user = gtk_editable_get_text(GTK_EDITABLE(entry_user)); + const gchar *pass = gtk_editable_get_text(GTK_EDITABLE(entry_pass)); + + // write out the configuration + _config->Set("Synaptic::httpProxyUser", user); + _config->Set("Synaptic::httpProxyPass", pass); + }); } // vim:ts=3:sw=3:et diff --git a/gtk/rgpreferenceswindow.h b/gtk/rgpreferenceswindow.h index b7f7251f0..853303715 100644 --- a/gtk/rgpreferenceswindow.h +++ b/gtk/rgpreferenceswindow.h @@ -25,6 +25,7 @@ #include "config.h" // IWYU pragma: associated #include "rggtkbuilderwindow.h" +#include "coroutines.h" #include "gtk/gtkcssprovider.h" #include @@ -111,11 +112,15 @@ class RGPreferencesWindow : public RGGtkBuilderWindow static void cbToggleColumn(GtkWidget *self, char *path, void *data); // callbacks - static void changeFontAction(GtkWidget *self, void *data); + static void changeDefaultFontAction(GtkWidget *self, void *data); + static void changeTerminalFontAction(GtkWidget *self, void *data); + [[nodiscard]] task changeFont(const char *propName, + const char *defaultValue); static void checkbuttonUserFontToggled(GtkWidget *self, void *data); static void checkbuttonUserTerminalFontToggled(GtkWidget *self, void *data); - static void saveAction(GtkWidget *self, void *data); + static void cbSaveAction(GtkWidget *self, void *data); + [[nodiscard]] task saveAction(); void saveGeneral(); void saveColumnsAndFonts(); void saveColors(); @@ -131,10 +136,12 @@ class RGPreferencesWindow : public RGGtkBuilderWindow void readDistribution(); static void closeAction(GtkWidget *self, void *data); - static void doneAction(GtkWidget *self, void *data); + static void cbDoneAction(GtkWidget *self, void *data); + [[nodiscard]] task doneAction(); static void clearCacheAction(GtkWidget *self, void *data); - static void colorClicked(GtkWidget *self, void *data); + static void cbColorClicked(GtkWidget *self, void *data); + [[nodiscard]] task colorClicked(int status); static void buttonAuthenticationClicked(GtkWidget *self, void *data); static void useProxyToggled(GtkWidget *self, void *data); diff --git a/gtk/rgrepositorywin.cc b/gtk/rgrepositorywin.cc index ca45e8a40..5e1ab4f61 100644 --- a/gtk/rgrepositorywin.cc +++ b/gtk/rgrepositorywin.cc @@ -37,11 +37,6 @@ #include #include #include -#include -#include -#include -#include -#include #include #include @@ -97,41 +92,49 @@ void RGRepositoryEditor::item_toggled(GtkCellRendererToggle *cell, RGRepositoryEditor *me = (RGRepositoryEditor *)g_object_get_data(G_OBJECT(cell), "me"); GtkTreeModel *model = (GtkTreeModel *)data; - GtkTreePath *path = gtk_tree_path_new_from_string(path_str); - GtkTreeIter iter; - gboolean toggle_item; - gchar *section = NULL; + start_task([me, path_str, model]() -> task { + GtkTreePath *path = gtk_tree_path_new_from_string(path_str); + GtkTreeIter iter; + gboolean toggle_item; + gchar *section = NULL; + + /* get toggled iter */ + gtk_tree_model_get_iter(model, &iter, path); + gtk_tree_model_get(model, + &iter, + STATUS_COLUMN, + &toggle_item, + SECTIONS_COLUMN, + §ion, + -1); - /* get toggled iter */ - gtk_tree_model_get_iter(model, &iter, path); - gtk_tree_model_get( - model, &iter, STATUS_COLUMN, &toggle_item, SECTIONS_COLUMN, §ion, -1); - - /* do something with the value */ - toggle_item ^= 1; - - // special warty check - if (toggle_item && section && g_strrstr(section, "universe")) { - gchar *msg = _("You are adding the \"universe\" component.\n\n " - "Packages in this component are not supported. " - "Are you sure?"); - if (!me->_userDialog->message( - msg, RUserDialog::DialogWarning, RUserDialog::ButtonsOkCancel)) { - gtk_tree_path_free(path); - g_free(section); - return; + /* do something with the value */ + toggle_item ^= 1; + + // special warty check + if (toggle_item && section && g_strrstr(section, "universe")) { + gchar *msg = _("You are adding the \"universe\" component.\n\n " + "Packages in this component are not supported. " + "Are you sure?"); + if (!co_await me->_userDialog->message(msg, + RUserDialog::DialogWarning, + RUserDialog::ButtonsOkCancel)) { + gtk_tree_path_free(path); + g_free(section); + co_return; + } } - } - /* set new value */ - gtk_list_store_set( - GTK_LIST_STORE(model), &iter, STATUS_COLUMN, toggle_item, -1); + /* set new value */ + gtk_list_store_set( + GTK_LIST_STORE(model), &iter, STATUS_COLUMN, toggle_item, -1); - me->_dirty = true; + me->_dirty = true; - /* clean up */ - g_free(section); - gtk_tree_path_free(path); + /* clean up */ + g_free(section); + gtk_tree_path_free(path); + }); } @@ -240,7 +243,7 @@ RGRepositoryEditor::RGRepositoryEditor(RGWindow *parent) select = gtk_tree_view_get_selection(GTK_TREE_VIEW(_sourcesListView)); gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE); g_signal_connect( - G_OBJECT(select), "changed", G_CALLBACK(SelectionChanged), this); + G_OBJECT(select), "changed", G_CALLBACK(cbSelectionChanged), this); //_cbEnabled = GTK_WIDGET(gtk_builder_get_object(_builder, //"checkbutton_enabled")); @@ -344,7 +347,7 @@ RGRepositoryEditor::RGRepositoryEditor(RGWindow *parent) g_signal_connect(GTK_WIDGET(gtk_builder_get_object(_builder, "button_ok")), "clicked", - G_CALLBACK(DoOK), + G_CALLBACK(cbDoOK), this); g_signal_connect( @@ -393,8 +396,8 @@ RGRepositoryEditor::RGRepositoryEditor(RGWindow *parent) assert(_editTable); gtk_widget_set_sensitive(_editTable, FALSE); - gtk_window_resize(GTK_WINDOW(_win), 620, 400); - skipTaskbar(true); + gtk_widget_set_size_request(GTK_WIDGET(_win), 620, 400); + show(); } @@ -406,10 +409,10 @@ RGRepositoryEditor::~RGRepositoryEditor() } -bool RGRepositoryEditor::Run() +task RGRepositoryEditor::Run() { if (_lst.ReadSources() == false) { - _userDialog->warning( + co_await _userDialog->warning( _("Ignoring invalid record(s) in sources.list file!")); // return false; } @@ -418,8 +421,8 @@ bool RGRepositoryEditor::Run() if (_lst.ReadVendors() == false) { _error->Error(_("Cannot read vendors.list file")); - _userDialog->showErrors(); - return false; + co_await _userDialog->showErrors(); + co_return false; } GtkTreeIter iter; @@ -459,8 +462,8 @@ bool RGRepositoryEditor::Run() UpdateVendorMenu(); - gtk_main(); - return _applied; + co_await co_run_window(GTK_WINDOW(_win)); + co_return _applied; } void RGRepositoryEditor::UpdateVendorMenu() @@ -505,9 +508,9 @@ void RGRepositoryEditor::DoClear(GtkWidget *, gpointer data) gtk_combo_box_set_active(GTK_COMBO_BOX(me->_optType), 0); gtk_combo_box_set_active(GTK_COMBO_BOX(me->_optVendor), 0); - gtk_entry_set_text(GTK_ENTRY(me->_entryURI), ""); - gtk_entry_set_text(GTK_ENTRY(me->_entryDist), ""); - gtk_entry_set_text(GTK_ENTRY(me->_entrySect), ""); + gtk_editable_set_text(GTK_EDITABLE(me->_entryURI), ""); + gtk_editable_set_text(GTK_EDITABLE(me->_entryDist), ""); + gtk_editable_set_text(GTK_EDITABLE(me->_entrySect), ""); } void RGRepositoryEditor::DoAdd(GtkWidget *, gpointer data) @@ -555,7 +558,7 @@ void RGRepositoryEditor::DoAdd(GtkWidget *, gpointer data) me->_dirty = true; } -void RGRepositoryEditor::doEdit() +task RGRepositoryEditor::doEdit() { // cout << "RGRepositoryEditor::doEdit()"<Type |= SourcesList::RepomdSrc; break; default: - _userDialog->error(_("Unknown source type")); - return; + co_await _userDialog->error(_("Unknown source type")); + co_return; } #if 0 // PORTME, no vendor id support right now @@ -624,19 +627,19 @@ void RGRepositoryEditor::doEdit() -1); rec->VendorID = type; #endif - rec->URI = gtk_entry_get_text(GTK_ENTRY(_entryURI)); - rec->Dist = gtk_entry_get_text(GTK_ENTRY(_entryDist)); + rec->URI = gtk_editable_get_text(GTK_EDITABLE(_entryURI)); + rec->Dist = gtk_editable_get_text(GTK_EDITABLE(_entryDist)); delete[] rec->Sections; rec->NumSections = 0; - const char *Section = gtk_entry_get_text(GTK_ENTRY(_entrySect)); + const char *Section = gtk_editable_get_text(GTK_EDITABLE(_entrySect)); if (Section != 0 && Section[0] != 0) rec->NumSections++; rec->Sections = new string[rec->NumSections]; rec->NumSections = 0; - Section = gtk_entry_get_text(GTK_ENTRY(_entrySect)); + Section = gtk_editable_get_text(GTK_EDITABLE(_entrySect)); if (Section != 0 && Section[0] != 0) rec->Sections[rec->NumSections++] = Section; @@ -694,51 +697,62 @@ void RGRepositoryEditor::DoRemove(GtkWidget *, gpointer data) me->_dirty = true; } -void RGRepositoryEditor::DoOK(GtkWidget *, gpointer data) +void RGRepositoryEditor::cbDoOK(GtkWidget *, gpointer data) { RGRepositoryEditor *me = (RGRepositoryEditor *)data; + start_task([me]() -> task { co_await me->doOK(); }); +} - me->doEdit(); - me->_lst.UpdateSources(); +task RGRepositoryEditor::doOK() +{ + co_await doEdit(); + _lst.UpdateSources(); // check if we actually can parse the sources.list pkgSourceList List; if (!List.ReadMainList()) { - me->_userDialog->showErrors(); - me->_savedList.UpdateSources(); - return; + co_await _userDialog->showErrors(); + _savedList.UpdateSources(); + co_return; } - gtk_main_quit(); - me->_applied = me->_dirty; + _applied = _dirty; + gtk_window_close(GTK_WINDOW(_win)); } void RGRepositoryEditor::DoCancel(GtkWidget *, gpointer data) { - // RGRepositoryEditor *me = (RGRepositoryEditor *)data; - gtk_main_quit(); + RGRepositoryEditor *me = (RGRepositoryEditor *)data; + + gtk_window_close(GTK_WINDOW(me->_win)); } -void RGRepositoryEditor::SelectionChanged(GtkTreeSelection *selection, - gpointer data) +void RGRepositoryEditor::cbSelectionChanged(GtkTreeSelection *selection, + gpointer data) { RGRepositoryEditor *me = (RGRepositoryEditor *)data; // cout << "RGRepositoryEditor::SelectionChanged()"< task { + co_await me->selectionChanged(selection); + }); +} +task RGRepositoryEditor::selectionChanged(GtkTreeSelection *selection) +{ GtkTreeIter iter; GtkTreeModel *model; - gtk_widget_set_sensitive(me->_editTable, TRUE); + gtk_widget_set_sensitive(_editTable, TRUE); - gtk_widget_set_sensitive(me->_upBut, TRUE); - gtk_widget_set_sensitive(me->_downBut, TRUE); - gtk_widget_set_sensitive(me->_deleteBut, TRUE); + gtk_widget_set_sensitive(_upBut, TRUE); + gtk_widget_set_sensitive(_downBut, TRUE); + gtk_widget_set_sensitive(_deleteBut, TRUE); if (gtk_tree_selection_get_selected(selection, &model, &iter)) { - me->doEdit(); // save the old row - if (me->_lastIter != NULL) - gtk_tree_iter_free(me->_lastIter); - me->_lastIter = gtk_tree_iter_copy(&iter); + co_await doEdit(); // save the old row + if (_lastIter != NULL) + gtk_tree_iter_free(_lastIter); + _lastIter = gtk_tree_iter_copy(&iter); const SourcesList::SourceRecord *rec; gtk_tree_model_get(model, &iter, RECORD_COLUMN, &rec, -1); @@ -758,30 +772,28 @@ void RGRepositoryEditor::SelectionChanged(GtkTreeSelection *selection, id = ITEM_TYPE_REPOMD; else if (rec->Type & SourcesList::RepomdSrc) id = ITEM_TYPE_REPOMDSRC; - gtk_combo_box_set_active(GTK_COMBO_BOX(me->_optType), id); + gtk_combo_box_set_active(GTK_COMBO_BOX(_optType), id); - gtk_combo_box_set_active(GTK_COMBO_BOX(me->_optVendor), - me->VendorMenuIndex(rec->VendorID)); + gtk_combo_box_set_active(GTK_COMBO_BOX(_optVendor), + VendorMenuIndex(rec->VendorID)); - gtk_entry_set_text(GTK_ENTRY(me->_entryURI), utf8(rec->URI.c_str())); - gtk_entry_set_text(GTK_ENTRY(me->_entryDist), utf8(rec->Dist.c_str())); - gtk_entry_set_text(GTK_ENTRY(me->_entrySect), ""); + gtk_editable_set_text(GTK_EDITABLE(_entryURI), utf8(rec->URI.c_str())); + gtk_editable_set_text(GTK_EDITABLE(_entryDist), utf8(rec->Dist.c_str())); + gtk_editable_set_text(GTK_EDITABLE(_entrySect), ""); for (unsigned int I = 0; I < rec->NumSections; I++) { - int pos = gtk_editable_get_position(GTK_EDITABLE(me->_entrySect)); - gtk_editable_insert_text(GTK_EDITABLE(me->_entrySect), - utf8(rec->Sections[I].c_str()), - -1, - &pos); - gtk_editable_insert_text(GTK_EDITABLE(me->_entrySect), " ", -1, &pos); + int pos = gtk_editable_get_position(GTK_EDITABLE(_entrySect)); + gtk_editable_insert_text( + GTK_EDITABLE(_entrySect), utf8(rec->Sections[I].c_str()), -1, &pos); + gtk_editable_insert_text(GTK_EDITABLE(_entrySect), " ", -1, &pos); } } else { // cout << "no selection" << endl; - gtk_widget_set_sensitive(me->_editTable, FALSE); + gtk_widget_set_sensitive(_editTable, FALSE); - gtk_widget_set_sensitive(me->_upBut, FALSE); - gtk_widget_set_sensitive(me->_downBut, FALSE); - gtk_widget_set_sensitive(me->_deleteBut, FALSE); + gtk_widget_set_sensitive(_upBut, FALSE); + gtk_widget_set_sensitive(_downBut, FALSE); + gtk_widget_set_sensitive(_deleteBut, FALSE); } } diff --git a/gtk/rgrepositorywin.h b/gtk/rgrepositorywin.h index 05da19656..89415b7fc 100644 --- a/gtk/rgrepositorywin.h +++ b/gtk/rgrepositorywin.h @@ -29,9 +29,8 @@ #include "rggtkbuilderwindow.h" #include "rsources.h" +#include "coroutines.h" -#include -#include #include #include #include @@ -42,7 +41,7 @@ class RGWindow; typedef std::list::iterator SourcesListIter; typedef std::list::iterator VendorsListIter; -class RGRepositoryEditor : RGGtkBuilderWindow +class RGRepositoryEditor : public RGGtkBuilderWindow { SourcesList _lst, _savedList; @@ -81,22 +80,24 @@ class RGRepositoryEditor : RGGtkBuilderWindow static void DoAdd(GtkWidget *, gpointer); static void DoUpDown(GtkWidget *, gpointer); static void DoRemove(GtkWidget *, gpointer); - static void DoOK(GtkWidget *, gpointer); + static void cbDoOK(GtkWidget *, gpointer); + [[nodiscard]] task doOK(); static void DoCancel(GtkWidget *, gpointer); static void VendorsWindow(GtkWidget *, gpointer); - static void SelectionChanged(GtkTreeSelection *selection, gpointer data); + static void cbSelectionChanged(GtkTreeSelection *selection, gpointer data); + [[nodiscard]] task selectionChanged(GtkTreeSelection *selection); // treeview item toggled static void item_toggled(GtkCellRendererToggle *cell, gchar *path_str, gpointer data); // get values - void doEdit(); + [[nodiscard]] task doEdit(); public: RGRepositoryEditor(RGWindow *parent); ~RGRepositoryEditor(); - bool Run(); + [[nodiscard]] task Run(); }; diff --git a/gtk/rgsetoptwindow.cc b/gtk/rgsetoptwindow.cc index 41fd0281f..619a33427 100644 --- a/gtk/rgsetoptwindow.cc +++ b/gtk/rgsetoptwindow.cc @@ -27,9 +27,6 @@ #include "rggtkbuilderwindow.h" #include -#include -#include -#include #include #include @@ -44,8 +41,8 @@ void RGSetOptWindow::DoApply(GtkWindow *widget, void *data) GtkWidget *entry_value = GTK_WIDGET(gtk_builder_get_object(me->_builder, "entry_value")); - const gchar *name = gtk_entry_get_text(GTK_ENTRY(entry_name)); - const gchar *value = gtk_entry_get_text(GTK_ENTRY(entry_value)); + const gchar *name = gtk_editable_get_text(GTK_EDITABLE(entry_name)); + const gchar *value = gtk_editable_get_text(GTK_EDITABLE(entry_value)); _config->Set(name, value); } @@ -73,5 +70,4 @@ RGSetOptWindow::RGSetOptWindow(RGWindow *win) this); setTitle(""); - skipTaskbar(true); } diff --git a/gtk/rgsummarywindow.cc b/gtk/rgsummarywindow.cc index f681a542b..0754f62f8 100644 --- a/gtk/rgsummarywindow.cc +++ b/gtk/rgsummarywindow.cc @@ -36,15 +36,13 @@ #include #include #include -#include -#include -#include #include #include #include #include class RGWindow; +#include "rgutils.h" using namespace std; @@ -405,7 +403,7 @@ RGSummaryWindow::RGSummaryWindow(RGWindow *wwin, RPackageLister *lister) assert(_checkSigsB); #ifdef HAVE_RPM bool check = _config->FindB("RPM::GPG-Check", true); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(_checkSigsB), check); + gtk_check_button_set_active(GTK_CHECK_BUTTON(_checkSigsB), check); gtk_widget_show(_checkSigsB); #endif @@ -514,37 +512,33 @@ RGSummaryWindow::RGSummaryWindow(RGWindow *wwin, RPackageLister *lister) gtk_label_set_markup(GTK_LABEL(_summarySpaceL), msg_space->str); g_string_free(msg, TRUE); g_string_free(msg_space, TRUE); - if (!_config->FindB("Volatile::HideMainwindow", false)) - skipTaskbar(true); - else - skipTaskbar(false); } -bool RGSummaryWindow::showAndConfirm() +task RGSummaryWindow::showAndConfirm() { - gtk_toggle_button_set_active( - GTK_TOGGLE_BUTTON(_dlonlyB), + gtk_check_button_set_active( + GTK_CHECK_BUTTON(_dlonlyB), _config->FindB("Volatile::Download-Only", false)); - int res = gtk_dialog_run(GTK_DIALOG(_win)); + int res = co_await co_run_dialog(GTK_DIALOG(_win)); bool confirmed = (res == GTK_RESPONSE_APPLY); RGUserDialog userDialog(this); if (confirmed && _potentialBreak && - userDialog.warning(_("Essential packages will be removed.\n" - "This may render your system unusable!\n"), - false) == false) - return false; + co_await userDialog.warning(_("Essential packages will be removed.\n" + "This may render your system unusable!\n"), + false) == false) + co_return false; _config->Set("Volatile::Download-Only", - gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(_dlonlyB)) + gtk_check_button_get_active(GTK_CHECK_BUTTON(_dlonlyB)) ? "true" : "false"); #ifdef HAVE_RPM _config->Set("RPM::GPG-Check", - gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(_checkSigsB)) + gtk_check_button_get_active(GTK_CHECK_BUTTON(_checkSigsB)) ? "true" : "false"); #endif - return confirmed; + co_return confirmed; } diff --git a/gtk/rgsummarywindow.h b/gtk/rgsummarywindow.h index 3f8245557..a0e829489 100644 --- a/gtk/rgsummarywindow.h +++ b/gtk/rgsummarywindow.h @@ -26,6 +26,7 @@ #include "config.h" // IWYU pragma: associated +#include "coroutines.h" #include "rggtkbuilderwindow.h" #include @@ -55,5 +56,5 @@ class RGSummaryWindow : public RGGtkBuilderWindow public: RGSummaryWindow(RGWindow *win, RPackageLister *lister); - bool showAndConfirm(); + [[nodiscard]] task showAndConfirm(); }; diff --git a/gtk/rgtaskswin.cc b/gtk/rgtaskswin.cc index 3a6b0c37b..3f7d59986 100644 --- a/gtk/rgtaskswin.cc +++ b/gtk/rgtaskswin.cc @@ -33,9 +33,6 @@ #include #include #include -#include -#include -#include #include #include #include @@ -92,13 +89,13 @@ void RGTasksWin::cbButtonOkClicked(GtkWidget *self, void *data) // cout << "cmd: " << cmd << endl; - vector packages; + me->packages.clear(); // only act if at least one task was selected if (act) { char buf[255]; FILE *f = popen(cmd.c_str(), "r"); while (fgets(buf, 254, f) != NULL) { - packages.push_back(string(g_strstrip(buf))); + me->packages.push_back(string(g_strstrip(buf))); } pclose(f); @@ -112,8 +109,6 @@ void RGTasksWin::cbButtonOkClicked(GtkWidget *self, void *data) me->setBusyCursor(false); me->hide(); - - me->_mainWin->selectToInstall(packages); } void RGTasksWin::cbButtonCancelClicked(GtkWidget *self, void *data) @@ -127,49 +122,51 @@ void RGTasksWin::cbButtonDetailsClicked(GtkWidget *self, void *data) { // cout << "cbButtonDetailsClicked(GtkWidget *self, void *data)"< task { + me->setBusyCursor(true); - me->setBusyCursor(true); - - // get selected task-name - GtkTreeIter iter; - GtkTreeSelection *selection; - selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(me->_taskView)); - if (!gtk_tree_selection_get_selected( - selection, (GtkTreeModel **)(&me->_store), &iter)) { - return; - } - gchar *str; - gtk_tree_model_get( - GTK_TREE_MODEL(me->_store), &iter, TASK_NAME_COLUMN, &str, -1); - - // ask tasksel about the selected task - string cmd = _config->Find("Synaptic::taskHelperProg", "/usr/bin/tasksel") + - " --task-desc " + string(str); - string taskDescr; - char buf[255]; - FILE *f = popen(cmd.c_str(), "r"); - while (fgets(buf, 254, f) != NULL) { - taskDescr += string(buf); - } + // get selected task-name + GtkTreeIter iter; + GtkTreeSelection *selection; + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(me->_taskView)); + if (!gtk_tree_selection_get_selected( + selection, (GtkTreeModel **)(&me->_store), &iter)) { + co_return; + } + gchar *str; + gtk_tree_model_get( + GTK_TREE_MODEL(me->_store), &iter, TASK_NAME_COLUMN, &str, -1); + + // ask tasksel about the selected task + string cmd = + _config->Find("Synaptic::taskHelperProg", "/usr/bin/tasksel") + + " --task-desc " + string(str); + string taskDescr; + char buf[255]; + FILE *f = popen(cmd.c_str(), "r"); + while (fgets(buf, 254, f) != NULL) { + taskDescr += string(buf); + } - // display the result in a nice dialog - RGGtkBuilderUserDialog dia(me, "task_descr"); + // display the result in a nice dialog + RGGtkBuilderUserDialog dia(me, "task_descr"); - // TRANSLATORS: Title of the task window - %s is the task (e.g. "desktop" or - // "mail server") - gchar *title = g_strdup_printf(_("Description %s"), str); - dia.setTitle(title); + // TRANSLATORS: Title of the task window - %s is the task (e.g. "desktop" + // or "mail server") + gchar *title = g_strdup_printf(_("Description %s"), str); + dia.setTitle(title); - GtkWidget *tv = - GTK_WIDGET(gtk_builder_get_object(dia.getGtkBuilder(), "textview")); - GtkTextBuffer *tb = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tv)); - gtk_text_buffer_set_text(tb, utf8(taskDescr.c_str()), -1); + GtkWidget *tv = + GTK_WIDGET(gtk_builder_get_object(dia.getGtkBuilder(), "textview")); + GtkTextBuffer *tb = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tv)); + gtk_text_buffer_set_text(tb, utf8(taskDescr.c_str()), -1); - dia.run(); + co_await dia.co_run(); - g_free(str); - g_free(title); - me->setBusyCursor(false); + g_free(str); + g_free(title); + me->setBusyCursor(false); + }); } void RGTasksWin::cell_toggled_callback(GtkCellRendererToggle *cell, @@ -321,3 +318,11 @@ RGTasksWin::RGTasksWin(RGWindow *parent) : RGGtkBuilderWindow(parent, "tasks") (GCallback)cbButtonDetailsClicked, this); }; + +task> RGTasksWin::selectTasks() +{ + packages.clear(); + co_await co_run_window(GTK_WINDOW(window())); + + co_return std::move(packages); +} diff --git a/gtk/rgtaskswin.h b/gtk/rgtaskswin.h index 226ce7894..e0ff976f8 100644 --- a/gtk/rgtaskswin.h +++ b/gtk/rgtaskswin.h @@ -25,8 +25,8 @@ #include "config.h" // IWYU pragma: associated #include "rggtkbuilderwindow.h" +#include "rgutils.h" -#include #include class RGMainWindow; @@ -50,6 +50,10 @@ class RGTasksWin : public RGGtkBuilderWindow gpointer user_data); public: + std::vector packages; + RGTasksWin(RGWindow *parent); virtual ~RGTasksWin() {}; + + [[nodiscard]] task> selectTasks(); }; diff --git a/gtk/rgterminstallprogress.cc b/gtk/rgterminstallprogress.cc index 440b8c386..c108a4cdf 100644 --- a/gtk/rgterminstallprogress.cc +++ b/gtk/rgterminstallprogress.cc @@ -81,16 +81,16 @@ RGTermInstallProgress::RGTermInstallProgress(RGMainWindow *main) pango_font_description_free(fontdesc); GtkWidget *box = GTK_WIDGET(gtk_builder_get_object(_builder, "hbox_vte")); - gtk_box_pack_start(GTK_BOX(box), _term, TRUE, TRUE, 0); - gtk_box_pack_end(GTK_BOX(box), _scrollbar, FALSE, FALSE, 0); - gtk_widget_show(_term); - gtk_widget_show(_scrollbar); + gtk_widget_set_hexpand(_term, TRUE); + gtk_widget_set_vexpand(_term, TRUE); + gtk_box_append(GTK_BOX(box), _term); + gtk_box_append(GTK_BOX(box), _scrollbar); _closeOnF = GTK_WIDGET( gtk_builder_get_object(_builder, "checkbutton_close_after_pm")); assert(_closeOnF); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(_closeOnF), - _config->FindB("Synaptic::closeZvt", false)); + gtk_check_button_set_active(GTK_CHECK_BUTTON(_closeOnF), + _config->FindB("Synaptic::closeZvt", false)); _statusL = GTK_WIDGET(gtk_builder_get_object(_builder, "label_status")); _closeB = GTK_WIDGET(gtk_builder_get_object(_builder, "button_close")); @@ -112,11 +112,10 @@ void RGTermInstallProgress::child_exited(VteTerminal *vteterminal, me->child_has_exited = true; } -void RGTermInstallProgress::startUpdate() +task RGTermInstallProgress::startUpdate() { GtkWidget *win = GTK_WIDGET(gtk_builder_get_object(_builder, "window_zvtinstallprogress")); - gtk_widget_show_all(win); child_has_exited = false; g_signal_connect( @@ -126,32 +125,31 @@ void RGTermInstallProgress::startUpdate() gtk_label_set_markup(GTK_LABEL(_statusL), _("Running...")); gtk_widget_set_sensitive(_closeB, false); - RGFlushInterface(); + co_await RGFlushInterface(); } -void RGTermInstallProgress::finishUpdate() +task RGTermInstallProgress::finishUpdate() { gtk_widget_set_sensitive(_closeB, true); - RGFlushInterface(); + co_await RGFlushInterface(); _updateFinished = true; _config->Set("Synaptic::closeZvt", - gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(_closeOnF)) + gtk_check_button_get_active(GTK_CHECK_BUTTON(_closeOnF)) ? "true" : "false"); if (!RWriteConfigFile(*_config)) { _error->Error(_("An error occurred while saving configurations.")); RGUserDialog userDialog(this); - userDialog.showErrors(); + co_await userDialog.showErrors(); } - if (res == 0 && - (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(_closeOnF)) || - _config->FindB("Volatile::Non-Interactive", false))) { + if (res == 0 && (gtk_check_button_get_active(GTK_CHECK_BUTTON(_closeOnF)) || + _config->FindB("Volatile::Non-Interactive", false))) { hide(); - return; + co_return; } const char *msg = _(getResultStr(res)); @@ -164,15 +162,16 @@ void RGTermInstallProgress::finishUpdate() void RGTermInstallProgress::stopShell(GtkWidget *self, void *data) { RGTermInstallProgress *me = (RGTermInstallProgress *)data; - - if (!me->_updateFinished) { - gtk_label_set_markup(GTK_LABEL(me->_statusL), - _("Can't close while running")); - return; - } - - RGFlushInterface(); - me->hide(); + start_task([me]() -> task { + if (!me->_updateFinished) { + gtk_label_set_markup(GTK_LABEL(me->_statusL), + _("Can't close while running")); + co_return; + } + + co_await RGFlushInterface(); + me->hide(); + }); } void RGTermInstallProgress::close() @@ -240,22 +239,16 @@ std::optional RGTermInstallProgress::poll() return res; } -void RGTermInstallProgress::updateInterface() +task RGTermInstallProgress::updateInterface() { - if (gtk_events_pending()) { - while (gtk_events_pending()) - gtk_main_iteration(); - } else { - // 0.1 secs - usleep(10000); - } + co_await RGFlushInterface(); } -void RGTermInstallProgress::finish() +task RGTermInstallProgress::finish() { while (gtk_widget_get_visible(GTK_WIDGET(window()))) { - RGFlushInterface(); - usleep(100000); + co_await RGFlushInterface(); + co_await sleep_ms{100}; } } diff --git a/gtk/rgterminstallprogress.h b/gtk/rgterminstallprogress.h index f4da00988..73f2a09ed 100644 --- a/gtk/rgterminstallprogress.h +++ b/gtk/rgterminstallprogress.h @@ -63,11 +63,11 @@ class RGTermInstallProgress : public RInstallProgress, public RGGtkBuilderWindow int numPackages = 0, int totalPackages = 0) override; virtual std::optional poll() override; - virtual void finish() override; + [[nodiscard]] virtual task finish() override; - virtual void startUpdate() override; - virtual void updateInterface() override; - virtual void finishUpdate() override; + [[nodiscard]] virtual task startUpdate() override; + [[nodiscard]] virtual task updateInterface() override; + [[nodiscard]] virtual task finishUpdate() override; }; #endif /* HAVT_TERMINAL */ diff --git a/gtk/rguserdialog.cc b/gtk/rguserdialog.cc index 3c95936e8..f692e74ba 100644 --- a/gtk/rguserdialog.cc +++ b/gtk/rguserdialog.cc @@ -39,19 +39,13 @@ using namespace std; -static void actionResponse(GtkDialog *dialog, gint id, gpointer user_data) -{ - GtkResponseType *res = (GtkResponseType *)user_data; - *res = (GtkResponseType)id; -} - -bool RGUserDialog::showErrors() +task RGUserDialog::showErrors() { GtkWidget *dia; std::string err, warn; if (_error->empty()) - return false; + co_return false; while (!_error->empty()) { string message; @@ -90,8 +84,14 @@ bool RGUserDialog::showErrors() gtk_widget_set_size_request(dia, 500, 300); gtk_window_set_resizable(GTK_WINDOW(dia), TRUE); - gtk_container_set_border_width(GTK_CONTAINER(dia), 6); - GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL); + + GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dia)); + gtk_widget_set_margin_top(content_area, 6); + gtk_widget_set_margin_bottom(content_area, 6); + gtk_widget_set_margin_start(content_area, 6); + gtk_widget_set_margin_end(content_area, 6); + + GtkWidget *scroll = gtk_scrolled_window_new(); GtkWidget *textview = gtk_text_view_new(); GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview)); gtk_text_buffer_set_text(GTK_TEXT_BUFFER(buffer), utf8(msg.c_str()), -1); @@ -99,34 +99,31 @@ bool RGUserDialog::showErrors() gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview), 3); gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(textview), FALSE); gtk_text_view_set_editable(GTK_TEXT_VIEW(textview), FALSE); - gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), - GTK_SHADOW_IN); + gtk_scrolled_window_set_has_frame(GTK_SCROLLED_WINDOW(scroll), TRUE); gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); - gtk_container_set_border_width(GTK_CONTAINER(scroll), 6); - gtk_container_add(GTK_CONTAINER(scroll), textview); - gtk_widget_show_all(scroll); - gtk_container_add_with_properties( - GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dia))), - scroll, - "expand", - TRUE, - NULL); - - gtk_dialog_run(GTK_DIALOG(dia)); - gtk_widget_destroy(dia); - - return true; + gtk_widget_set_margin_top(scroll, 6); + gtk_widget_set_margin_bottom(scroll, 6); + gtk_widget_set_margin_start(scroll, 6); + gtk_widget_set_margin_end(scroll, 6); + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), textview); + gtk_widget_set_hexpand(scroll, TRUE); + gtk_widget_set_vexpand(scroll, TRUE); + gtk_box_append(GTK_BOX(content_area), scroll); + + co_await co_run_dialog(GTK_DIALOG(dia)); + gtk_window_destroy(GTK_WINDOW(dia)); + + co_return true; } -bool RGUserDialog::message(const char *msg, - RUserDialog::DialogType dialog, - RUserDialog::ButtonsType buttons, - bool defaultResponse) +task RGUserDialog::message(const char *msg, + RUserDialog::DialogType dialog, + RUserDialog::ButtonsType buttons, + bool defaultResponse) { GtkWidget *dia; - GtkResponseType res; GtkMessageType gtkmessage; GtkButtonsType gtkbuttons; @@ -171,7 +168,6 @@ bool RGUserDialog::message(const char *msg, gtk_window_set_icon_name(GTK_WINDOW(dia), "synaptic"); gtk_message_dialog_set_markup(GTK_MESSAGE_DIALOG(dia), utf8(msg)); - gtk_container_set_border_width(GTK_CONTAINER(dia), 6); switch (buttons) { case RUserDialog::ButtonsOkCancel: { @@ -191,13 +187,10 @@ bool RGUserDialog::message(const char *msg, } } - g_signal_connect( - G_OBJECT(dia), "response", G_CALLBACK(actionResponse), (gpointer)&res); - - gtk_dialog_run(GTK_DIALOG(dia)); - gtk_widget_destroy(dia); - return (res == GTK_RESPONSE_OK) || (res == GTK_RESPONSE_YES) || - (res == GTK_RESPONSE_CLOSE); + auto res = co_await co_run_dialog(GTK_DIALOG(dia)); + gtk_window_destroy(GTK_WINDOW(dia)); + co_return(res == GTK_RESPONSE_OK) || (res == GTK_RESPONSE_YES) || + (res == GTK_RESPONSE_CLOSE); } // RGGtkBuilderUserDialog @@ -232,25 +225,23 @@ void RGGtkBuilderUserDialog::init(const char *name) assert(_dialog); gtk_window_set_icon_name(GTK_WINDOW(_dialog), "synaptic"); - gtk_window_set_position(GTK_WINDOW(_dialog), GTK_WIN_POS_CENTER_ON_PARENT); - if (gtk_window_get_modal(GTK_WINDOW(_dialog))) - gtk_window_set_skip_taskbar_hint(GTK_WINDOW(_dialog), TRUE); gtk_window_set_transient_for(GTK_WINDOW(_dialog), GTK_WINDOW(_parentWindow)); g_free(main_widget); } -int RGGtkBuilderUserDialog::run(const char *name, bool return_gtk_response) +task RGGtkBuilderUserDialog::co_run(const char *name, + bool return_gtk_response) { if (name != NULL) init(name); - res = (GtkResponseType)gtk_dialog_run(GTK_DIALOG(_dialog)); + res = (GtkResponseType)co_await co_run_dialog(GTK_DIALOG(_dialog)); gtk_widget_hide(_dialog); if (return_gtk_response) - return res; + co_return res; else - return (res == GTK_RESPONSE_OK) || (res == GTK_RESPONSE_YES) || - (res == GTK_RESPONSE_CLOSE); + co_return(res == GTK_RESPONSE_OK) || (res == GTK_RESPONSE_YES) || + (res == GTK_RESPONSE_CLOSE); } diff --git a/gtk/rguserdialog.h b/gtk/rguserdialog.h index 3310a8fda..8ed10ad91 100644 --- a/gtk/rguserdialog.h +++ b/gtk/rguserdialog.h @@ -27,6 +27,7 @@ #include "config.h" // IWYU pragma: associated #include "rgwindow.h" +#include "rgutils.h" #include "ruserdialog.h" #include @@ -43,9 +44,9 @@ class RGUserDialog : public RUserDialog RGUserDialog(RGWindow *parent) : _parentWindow(parent->window()) {}; RGUserDialog(GtkWidget *parent) : _parentWindow(parent) {}; - virtual bool showErrors(); + [[nodiscard]] virtual task showErrors(); - virtual bool message( + [[nodiscard]] virtual task message( const char *msg, RUserDialog::DialogType dialog = RUserDialog::DialogInfo, RUserDialog::ButtonsType buttons = RUserDialog::ButtonsOk, @@ -84,7 +85,7 @@ class RGGtkBuilderUserDialog : public RGUserDialog RGGtkBuilderUserDialog(RGWindow *parent, const char *name); virtual ~RGGtkBuilderUserDialog() { - gtk_widget_destroy(_dialog); + gtk_widget_hide(_dialog); }; void setTitle(std::string title) @@ -92,7 +93,8 @@ class RGGtkBuilderUserDialog : public RGUserDialog gtk_window_set_title(GTK_WINDOW(_dialog), title.c_str()); } - int run(const char *name = NULL, bool return_gtk_response = false); + [[nodiscard]] task co_run(const char *name = NULL, + bool return_gtk_response = false); GtkBuilder *getGtkBuilder() { return builder; diff --git a/gtk/rgutils.cc b/gtk/rgutils.cc index ac44d8d02..c3d09ed76 100644 --- a/gtk/rgutils.cc +++ b/gtk/rgutils.cc @@ -34,11 +34,9 @@ #include #include -void RGFlushInterface() +task RGFlushInterface() { - while (gtk_events_pending()) { - gtk_main_iteration(); - } + co_await glib_idle{}; } static void AddRun0Environment(std::vector &prefix) @@ -308,4 +306,63 @@ std::string MarkupUnescapeString(std::string escaped) } return escaped; } + +struct DialogResponseClosure +{ + int *response; + std::coroutine_handle<> h; +}; + +static void dialog_response_callback(GtkDialog *dialog, + int response_id, + gpointer data) +{ + g_signal_handlers_disconnect_by_data(dialog, data); + + auto *closure = static_cast(data); + *closure->response = response_id; + if (closure->h) { + closure->h.resume(); + closure->h = {}; + } +} + +void dialog_response::await_suspend(std::coroutine_handle<> h) +{ + g_signal_connect_data( + dialog, + "response", + G_CALLBACK(dialog_response_callback), + new DialogResponseClosure{response, h}, + [](gpointer data, GClosure *) { + delete static_cast(data); + }, + G_CONNECT_DEFAULT); +} + +dialog_response co_run_dialog(GtkDialog *dialog) +{ + gtk_window_present(GTK_WINDOW(dialog)); + return dialog_response{dialog}; +} + +static void window_hide_callback(GtkWindow *window, gpointer data) +{ + g_signal_handlers_disconnect_by_data(window, data); + if (auto h = std::coroutine_handle<>::from_address(data)) + h.resume(); +} + +void window_hide::await_suspend(std::coroutine_handle<> h) +{ + g_signal_connect( + window, "hide", G_CALLBACK(window_hide_callback), h.address()); +} + +window_hide co_run_window(GtkWindow *window) +{ + gtk_window_present(window); + return window_hide{window}; +} + // vim:ts=3:sw=3:et diff --git a/gtk/rgutils.h b/gtk/rgutils.h index b5069ac40..dcead05fc 100644 --- a/gtk/rgutils.h +++ b/gtk/rgutils.h @@ -27,6 +27,13 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include "coroutines.h" enum { PIXMAP_COLUMN, @@ -44,7 +51,7 @@ enum { N_COLUMNS }; -void RGFlushInterface(); +[[nodiscard]] task RGFlushInterface(); bool is_binary_in_path(const char *program); @@ -59,3 +66,194 @@ bool RunAsSudoUserCommand(std::vector cmd); std::string MarkupEscapeString(std::string str); std::string MarkupUnescapeString(std::string str); + +struct [[nodiscard]] sleep_ms +{ + int ms; + + bool await_ready() const noexcept + { + return false; + } + + void await_suspend(std::coroutine_handle<> h) + { + g_timeout_add_once( + ms, + [](gpointer data) { + auto h = std::coroutine_handle<>::from_address(data); + h.resume(); + }, + h.address()); + } + + void await_resume() const noexcept + {} +}; + +struct [[nodiscard]] glib_idle +{ + bool await_ready() const noexcept + { + return false; + } + + void await_suspend(std::coroutine_handle<> h) + { + g_idle_add_once( + [](gpointer data) { + auto h = std::coroutine_handle<>::from_address(data); + h.resume(); + }, + h.address()); + } + + void await_resume() const noexcept + {} +}; + +struct [[nodiscard]] dialog_response +{ + GtkDialog *dialog; + int *response; + + explicit dialog_response(GtkDialog *dialog) + : dialog(dialog), response(new int{-1}) + {} + + bool await_ready() const + { + return false; + } + void await_suspend(std::coroutine_handle<> h); + int await_resume() const noexcept + { + return *response; + } +}; + +[[nodiscard]] dialog_response co_run_dialog(GtkDialog *dialog); + +struct [[nodiscard]] window_hide +{ + GtkWindow *window; + + bool await_ready() const + { + return false; + } + void await_suspend(std::coroutine_handle<> h); + void await_resume() const noexcept + {} +}; + +[[nodiscard]] window_hide co_run_window(GtkWindow *window); + +struct [[nodiscard]] glib_scheduler +{ + bool await_ready() const noexcept + { + return false; + } + + void await_suspend(std::coroutine_handle<> h) + { + g_main_context_invoke( + g_main_context_default(), + [](gpointer data) -> gboolean { + auto h = std::coroutine_handle<>::from_address(data); + h.resume(); + return G_SOURCE_REMOVE; + }, + h.address()); + } + + void await_resume() const noexcept + {} +}; + +template void destroy_later(T *&obj) noexcept +{ + if (!obj) + return; + T *ptr = obj; + obj = nullptr; + g_idle_add_once([](gpointer data) { delete static_cast(data); }, ptr); +} + +struct DetachedTaskOperationBase +{ + virtual ~DetachedTaskOperationBase() = default; +}; + +struct DetachedTaskReceiver +{ + using receiver_concept = beman::execution::receiver_tag; + + DetachedTaskOperationBase *operation = nullptr; + + struct Env + { + auto query(beman::execution::get_scheduler_t) const noexcept + { + return beman::execution::inline_scheduler{}; + } + }; + + auto get_env() const noexcept + { + return Env{}; + } + + void set_value() && noexcept + { + destroy_later(operation); + } + + template void set_error(Error &&) && noexcept + { + destroy_later(operation); + } + + void set_stopped() && noexcept + { + destroy_later(operation); + } +}; + +template +struct DetachedTaskOperation : DetachedTaskOperationBase +{ + std::move_only_function()> *func; + Operation operation; + + explicit DetachedTaskOperation(std::move_only_function()> *func) + : func(func), + operation( + beman::execution::connect((*func)(), DetachedTaskReceiver{this})) + {} + + ~DetachedTaskOperation() + { + delete func; + } + + void start() noexcept + { + beman::execution::start(operation); + } +}; + +template void start_task(F &&f) +{ + auto *ff = new std::move_only_function()>{ + [fn = std::forward(f)]() -> task { + co_await glib_scheduler{}; + co_await fn(); + }}; + + using Operation = decltype(beman::execution::connect( + std::move((*ff)()), DetachedTaskReceiver{})); + auto *operation = new DetachedTaskOperation(ff); + operation->start(); +} diff --git a/gtk/rgwindow.cc b/gtk/rgwindow.cc index d2e0c5dfe..c3de6983d 100644 --- a/gtk/rgwindow.cc +++ b/gtk/rgwindow.cc @@ -29,9 +29,7 @@ #include #include -gboolean RGWindow::windowCloseCallback(GtkWidget *window, - GdkEvent *event, - gpointer data) +gboolean RGWindow::windowCloseCallback(GtkWidget *window, gpointer data) { ((RGWindow *)data)->close(); return TRUE; @@ -42,23 +40,22 @@ void RGWindow::init() g_object_set_data(G_OBJECT(_win), "me", this); g_signal_connect( - G_OBJECT(_win), "delete-event", G_CALLBACK(windowCloseCallback), this); + G_OBJECT(_win), "close-request", G_CALLBACK(windowCloseCallback), this); } RGWindow::RGWindow(RGWindow *parent, std::string name) { - _win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + _win = gtk_window_new(); gtk_window_set_title(GTK_WINDOW(_win), (char *)name.c_str()); init(); - gtk_window_set_transient_for(GTK_WINDOW(_win), GTK_WINDOW(parent->window())); } RGWindow::~RGWindow() { - gtk_widget_destroy(_win); + gtk_widget_hide(_win); } void RGWindow::setTitle(std::string title) @@ -66,6 +63,33 @@ void RGWindow::setTitle(std::string title) gtk_window_set_title(GTK_WINDOW(_win), (char *)title.c_str()); } +GtkShortcutController *RGWindow::getShortcutController() +{ + if (!_shortcutController) { + _shortcutController = + GTK_SHORTCUT_CONTROLLER(gtk_shortcut_controller_new()); + gtk_widget_add_controller(GTK_WIDGET(_win), + GTK_EVENT_CONTROLLER(_shortcutController)); + } + return _shortcutController; +} + +void RGWindow::hideByEscape() +{ + gtk_shortcut_controller_add_shortcut( + getShortcutController(), + gtk_shortcut_new( + gtk_keyval_trigger_new(GDK_KEY_Escape, GDK_NO_MODIFIER_MASK), + gtk_callback_action_new( + [](GtkWidget *, GVariant *, gpointer data) -> gboolean { + auto me = static_cast(data); + gtk_widget_hide(me->_win); + return TRUE; + }, + this, + nullptr))); +} + void RGWindow::close() { hide(); diff --git a/gtk/rgwindow.h b/gtk/rgwindow.h index 9049b3ae8..4c1e70476 100644 --- a/gtk/rgwindow.h +++ b/gtk/rgwindow.h @@ -31,19 +31,23 @@ class RGWindow { + private: + GtkShortcutController *_shortcutController; + protected: GtkWidget *_win; - static gboolean windowCloseCallback(GtkWidget *widget, - GdkEvent *event, - gpointer data); + static gboolean windowCloseCallback(GtkWidget *widget, gpointer data); virtual void close(); void init(); - RGWindow() : _win(nullptr) + RGWindow() : _win(nullptr), _shortcutController(nullptr) {} + GtkShortcutController *getShortcutController(); + void hideByEscape(); + public: inline virtual GtkWidget *window() { @@ -54,12 +58,12 @@ class RGWindow inline virtual void hide() { - gtk_widget_hide(_win); + gtk_widget_set_visible(_win, false); } inline virtual void show() { - gtk_widget_show(_win); + gtk_window_present(GTK_WINDOW(_win)); } explicit RGWindow(RGWindow *parent, std::string name); diff --git a/meson.build b/meson.build index ee47bd42e..bc40c4ba7 100644 --- a/meson.build +++ b/meson.build @@ -4,12 +4,15 @@ project( version: '0.91.7', default_options: [ 'warning_level=1', - 'cpp_std=gnu++17', + 'cpp_std=c++23', ], ) +add_project_arguments('-fcoroutines', language: ['cpp']) + i18n = import('i18n') gnome = import('gnome') +cmake = import('cmake') prefix = get_option('prefix') bindir = prefix / get_option('bindir') @@ -26,9 +29,14 @@ root_inc = include_directories('.') common_inc = include_directories('common') gtk_inc = include_directories('gtk') +task_options = cmake.subproject_options() +task_options.add_cmake_defines({'BEMAN_USE_MODULES': 'OFF'}) +task = cmake.subproject('task', options: task_options) +task_dep = task.get_variable('beman_task_dep') + threads_dep = dependency('threads') -gtk_dep = dependency('gtk+-3.0', version: '>= 3.4.0') -pango_dep = dependency('pango', version: '>= 1.0.0') +gtk_dep = dependency('gtk4', version: '>= 4.20.0') +tbb_dep = dependency('tbb') glib_dep = dependency('glib-2.0') apt_pkg_dep = dependency('apt-pkg', required: true) x11_dep = cpp.find_library('X11', required: true) @@ -69,7 +77,7 @@ endif have_apt_auth = cpp.has_header('apt-pkg/metaindex.h', dependencies: apt_pkg_dep) have_aptpkg_cdrom = cpp.has_header('apt-pkg/cdrom.h', dependencies: apt_pkg_dep) -vte_dep = dependency('vte-2.91', version: '>= 0.5', required: false) +vte_dep = dependency('vte-2.91-gtk4', version: '>= 0.5', required: false) have_vte = vte_dep.found() if get_option('dpkg_progress').enabled() and not have_vte @@ -153,7 +161,7 @@ gtk_cpp_args = [ '-DGDK_PIXBUF_DISABLE_DEPRECATED', ] -common_deps = [apt_pkg_dep] +common_deps = [apt_pkg_dep, task_dep] common_deps += rpm_deps if apt_inst_dep.found() common_deps += [apt_inst_dep] @@ -163,8 +171,9 @@ if have_xapian endif gui_deps = [ + task_dep, + tbb_dep, gtk_dep, - pango_dep, glib_dep, apt_pkg_dep, x11_dep, diff --git a/subprojects/.wraplock b/subprojects/.wraplock new file mode 100644 index 000000000..e69de29bb diff --git a/subprojects/task.wrap b/subprojects/task.wrap new file mode 100644 index 000000000..02aad0c23 --- /dev/null +++ b/subprojects/task.wrap @@ -0,0 +1,9 @@ +[wrap-git] +directory = task +url = https://github.com/bemanproject/task.git +revision = 167918522e1cc4bc44c04f97b204f46c1735d7a2 +depth = 1 +method = cmake + +[provide] +task = task_dep diff --git a/subprojects/task/.clang-format b/subprojects/task/.clang-format new file mode 100644 index 000000000..1fbd05eff --- /dev/null +++ b/subprojects/task/.clang-format @@ -0,0 +1,251 @@ +--- +BasedOnStyle: LLVM +Language: Json +IndentWidth: 2 +UseTab: Never +--- +Language: JavaScript +IndentWidth: 4 +UseTab: Never +--- +Language: Cpp +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: true +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveShortCaseStatements: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCaseColons: false +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: + - __capability +BinPackArguments: false +BinPackParameters: false +BitFieldColonSpacing: Both +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterExternBlock: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAfterAttributes: Never +BreakAfterJavaFieldAnnotations: false +BreakArrays: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Custom +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +# Please update .markdownlint.yaml if this line is to be updated +ColumnLimit: 119 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: false +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertBraces: false +InsertNewlineAtEOF: false +InsertTrailingCommas: None +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +KeepEmptyLinesAtEOF: false +LambdaBodyIndentation: Signature +LineEnding: DeriveLF +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PackConstructorInitializers: NextLine +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Left +PPIndentWidth: -1 +QualifierAlignment: Custom +QualifierOrder: + - inline + - static + - constexpr + - const + - volatile + - type +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +RemoveParentheses: Leave +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: Never +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeJsonColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInContainerLiterals: true +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParens: Never +SpacesInParensOptions: + InCStyleCasts: false + InConditionalStatements: false + InEmptyParentheses: false + Other: false +SpacesInSquareBrackets: false +Standard: Auto +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseTab: Never +VerilogBreakBetweenInstancePorts: true +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME +... diff --git a/subprojects/task/.clang-format-ignore b/subprojects/task/.clang-format-ignore new file mode 100644 index 000000000..a6c57f5fb --- /dev/null +++ b/subprojects/task/.clang-format-ignore @@ -0,0 +1 @@ +*.json diff --git a/subprojects/task/.clang-tidy b/subprojects/task/.clang-tidy new file mode 100644 index 000000000..d85365769 --- /dev/null +++ b/subprojects/task/.clang-tidy @@ -0,0 +1,55 @@ +Checks: + -*, + boost-*, + bugprone-*, + cert-*, + -cert-dcl58-cpp, + clang-analyzer-*, + concurrency-*, + -cppcoreguidelines-*, + -google-*, + hicpp-*, + misc-*, + -misc-const-correctness, + -misc-include-cleaner, + -misc-non-private-member-variables-in-classes, + -modernize-*, + -modernize-use-nodiscard, + performance-*, + portability-*, + -readability-*, + -readability-identifier-*, + -readability-implicit-bool-conversion, + -readability-magic-numbers, + -*-named-parameter, + -*-uppercase-literal-suffix, + -*-use-equals-default, + -*-braces-around-statements +HeaderFilterRegex: '.*/execution/(include|src|example|tests)/.*\.(hpp)$' +WarningsAsErrors: 'clang*' +FormatStyle: file + +CheckOptions: + - { key: readability-identifier-naming.NamespaceCase, value: CamelCase } + - { key: readability-identifier-naming.ClassCase, value: CamelCase } + - { key: readability-identifier-naming.EnumCase, value: CamelCase } + - { key: readability-identifier-naming.MemberCase, value: camelBack } + - { key: readability-identifier-naming.MemberPrefix, value: m_ } + - { key: readability-identifier-naming.StructCase, value: lower_case } + - { key: readability-identifier-naming.UnionCase, value: lower_case } + - { key: readability-identifier-naming.TypedefCase, value: lower_case } + - { key: readability-identifier-naming.TypedefSuffix, value: _type } + - { key: readability-identifier-naming.FunctionCase, value: camelBack } + - { key: readability-identifier-naming.VariableCase, value: camelBack } + - { key: readability-identifier-naming.ParameterCase, value: camelBack } + - { key: readability-identifier-naming.LocalVariableCase, value: camelBack } + - { key: readability-identifier-naming.ConstexprFunctionCase, value: camelBack } + - { key: readability-identifier-naming.ConstexprMethodCase, value: camelBack } + - { key: readability-identifier-naming.ConstexprVariableCase, value: UPPER_CASE } + - { key: readability-identifier-naming.ClassConstantCase, value: UPPER_CASE } + - { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE } + - { key: readability-identifier-naming.GlobalConstantCase, value: UPPER_CASE } + - { key: readability-identifier-naming.GlobalConstantPointerCase, value: UPPER_CASE } + - { key: readability-identifier-naming.LocalConstantPointerCase, value: UPPER_CASE } + - { key: readability-identifier-naming.ScopedEnumConstantCase, value: UPPER_CASE } + - { key: readability-identifier-naming.StaticConstantCase, value: UPPER_CASE } diff --git a/subprojects/task/.codespellignore b/subprojects/task/.codespellignore new file mode 100644 index 000000000..3f49838ef --- /dev/null +++ b/subprojects/task/.codespellignore @@ -0,0 +1,7 @@ +que +cancelled +copyable +pullrequest +snd +statics +Claus diff --git a/subprojects/task/.codespellrc b/subprojects/task/.codespellrc new file mode 100644 index 000000000..31a9163ab --- /dev/null +++ b/subprojects/task/.codespellrc @@ -0,0 +1,6 @@ +[codespell] +builtin = clear,rare,en-GB_to_en-US,names,informal,code +check-hidden = +skip = ./.git,./build/*,./stagedir/*,./docs/html/*,./docs/latex/*,*.log,.*.swp,*~,*.bak,Makefile +quiet-level = 2 +ignore-words = .codespellignore diff --git a/subprojects/task/.devcontainer/Dockerfile b/subprojects/task/.devcontainer/Dockerfile new file mode 100644 index 000000000..584eb2dbc --- /dev/null +++ b/subprojects/task/.devcontainer/Dockerfile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +FROM mcr.microsoft.com/devcontainers/cpp:1-ubuntu-22.04 + +USER vscode + +# Install latest cmake +RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | sudo tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null +RUN echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ jammy main' | sudo tee /etc/apt/sources.list.d/kitware.list >/dev/null +RUN sudo apt-get update && sudo apt-get install -y cmake + +# Install pre-commit +RUN sudo apt-get install -y python3-pip && pip3 install pre-commit + +# Avoid ASAN Stalling +# See: https://github.com/google/sanitizers/issues/1614 +# Alternative is to update to above clang-18 and gcc-13.2 +# Maybe crashing codespace??? +RUN sudo sysctl -w vm.mmap_rnd_bits=28 diff --git a/subprojects/task/.devcontainer/devcontainer.json b/subprojects/task/.devcontainer/devcontainer.json new file mode 100644 index 000000000..e0cbb9cf5 --- /dev/null +++ b/subprojects/task/.devcontainer/devcontainer.json @@ -0,0 +1,15 @@ +{ + "name": "Beman Project Generic Devcontainer", + "build": { + "dockerfile": "Dockerfile" + }, + "postCreateCommand": "bash .devcontainer/postcreate.sh", + "customizations": { + "vscode": { + "extensions": [ + "ms-vscode.cpptools", + "ms-vscode.cmake-tools" + ] + } + } +} diff --git a/subprojects/task/.devcontainer/postcreate.sh b/subprojects/task/.devcontainer/postcreate.sh new file mode 100644 index 000000000..4293f7e4d --- /dev/null +++ b/subprojects/task/.devcontainer/postcreate.sh @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# Setup pre-commit +pre-commit +pre-commit install diff --git a/subprojects/task/.github/CODEOWNERS b/subprojects/task/.github/CODEOWNERS new file mode 100644 index 000000000..0fc077b9c --- /dev/null +++ b/subprojects/task/.github/CODEOWNERS @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# Codeowners for reviews on PRs + +# Github Actions: +# .github/workflows/ @wusatosi # Add other project owners here +# +# Devcontainer: +# .devcontainer/ @wusatosi # Add other project owners here +# +# Pre-commit: +# .pre-commit-config.yaml @wusatosi # Add other project owners here +# .markdownlint.yaml @wusatosi # Add other project owners here + +* @bretbrownjr @dietmarkuehl @steve-downey @wusatosi diff --git a/subprojects/task/.github/workflows/ci_tests.yml b/subprojects/task/.github/workflows/ci_tests.yml new file mode 100644 index 000000000..df6ed072c --- /dev/null +++ b/subprojects/task/.github/workflows/ci_tests.yml @@ -0,0 +1,113 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +name: Continuous Integration Tests + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + schedule: + - cron: '30 15 * * *' + +jobs: + beman-submodule-check: + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-submodule-check.yml@1.3.0 + + preset-test: + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-preset-test.yml@1.3.0 + with: + matrix_config: > + [ + {"preset": "gcc-debug", "image": "ghcr.io/bemanproject/infra-containers-gcc:latest"}, + {"preset": "gcc-release", "image": "ghcr.io/bemanproject/infra-containers-gcc:latest"}, + {"preset": "llvm-debug", "image": "ghcr.io/bemanproject/infra-containers-clang:21"}, + {"preset": "llvm-release", "image": "ghcr.io/bemanproject/infra-containers-clang:21"}, + {"preset": "msvc-debug", "runner": "windows-latest"}, + {"preset": "msvc-release", "runner": "windows-latest"} + ] + + build-and-test: + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-build-and-test.yml@1.3.0 + with: + matrix_config: > + { + "gcc": [ + { "versions": ["15"], + "tests": [ + { "cxxversions": ["c++26"], + "tests": [ + { "stdlibs": ["libstdc++"], + "tests": [ + "Debug.Default", "Release.Default", "Release.MaxSan", + "Debug.Coverage", "Debug.Werror", + "Debug.Dynamic", "Release.Dynamic" + ] + } + ] + }, + { "cxxversions": ["c++23"], + "tests": [{ "stdlibs": ["libstdc++"], "tests": ["Release.Default"]}] + } + ] + }, + { "versions": ["14"], + "tests": [ + { "cxxversions": ["c++26", "c++23"], + "tests": [{ "stdlibs": ["libstdc++"], "tests": ["Release.Default"]}] + } + ] + } + ], + "clang": [ + { "versions": ["21"], + "tests": [ + {"cxxversions": ["c++26"], + "tests": [ + { "stdlibs": ["libc++"], + "tests": [ + "Debug.Default", "Release.Default", "Release.MaxSan", + "Debug.Dynamic", "Release.Dynamic" + ] + } + ] + }, + { "cxxversions": ["c++23"], + "tests": [ + {"stdlibs": ["libc++"], "tests": ["Release.Default"]} + ] + } + ] + }, + { "versions": ["20", "19"], + "tests": [ + { "cxxversions": ["c++26", "c++23"], + "tests": [ + {"stdlibs": ["libc++"], "tests": ["Release.Default"]} + ] + } + ] + } + ], + "msvc": [ + { "versions": ["latest"], + "tests": [ + { "cxxversions": ["c++23"], + "tests": [ + { "stdlibs": ["stl"], + "tests": ["Debug.Default", "Release.Default", "Release.MaxSan", + "Debug.Dynamic", "Release.Dynamic" + ] + } + ] + } + ] + } + ] + } + + create-issue-when-fault: + needs: [preset-test, build-and-test] + if: failure() && github.event_name == 'schedule' + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-create-issue-when-fault.yml@1.3.0 diff --git a/subprojects/task/.github/workflows/pre-commit-check.yml b/subprojects/task/.github/workflows/pre-commit-check.yml new file mode 100644 index 000000000..2f9110380 --- /dev/null +++ b/subprojects/task/.github/workflows/pre-commit-check.yml @@ -0,0 +1,13 @@ +name: Lint Check (pre-commit) + +on: + # We have to use pull_request_target here as pull_request does not grant + # enough permission for reviewdog + pull_request_target: + push: + branches: + - main + +jobs: + pre-commit: + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-pre-commit.yml@1.3.0 diff --git a/subprojects/task/.github/workflows/pre-commit-update.yml b/subprojects/task/.github/workflows/pre-commit-update.yml new file mode 100644 index 000000000..930b750c1 --- /dev/null +++ b/subprojects/task/.github/workflows/pre-commit-update.yml @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +name: Weekly pre-commit autoupdate + +on: + workflow_dispatch: + schedule: + - cron: "0 16 * * 0" + +jobs: + auto-update-pre-commit: + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-update-pre-commit.yml@1.3.0 + secrets: + APP_ID: ${{ secrets.AUTO_PR_BOT_APP_ID }} + PRIVATE_KEY: ${{ secrets.AUTO_PR_BOT_PRIVATE_KEY }} diff --git a/subprojects/task/.github/workflows/pre-commit.yml b/subprojects/task/.github/workflows/pre-commit.yml new file mode 100644 index 000000000..2409d2f44 --- /dev/null +++ b/subprojects/task/.github/workflows/pre-commit.yml @@ -0,0 +1,13 @@ +name: Lint Check (pre-commit) + +on: + # We have to use pull_request_target here as pull_request does not grant + # enough permission for reviewdog + pull_request_target: + push: + branches: + - main + +jobs: + pre-commit: + uses: ./.github/workflows/reusable-beman-pre-commit.yml diff --git a/subprojects/task/.gitignore b/subprojects/task/.gitignore new file mode 100644 index 000000000..9c3b70cc6 --- /dev/null +++ b/subprojects/task/.gitignore @@ -0,0 +1,5 @@ +/.cache +/compile_commands.json +build/ +/docs/generated +/docs/wg21 diff --git a/subprojects/task/.meson-subproject-wrap-hash.txt b/subprojects/task/.meson-subproject-wrap-hash.txt new file mode 100644 index 000000000..1c8612e0e --- /dev/null +++ b/subprojects/task/.meson-subproject-wrap-hash.txt @@ -0,0 +1 @@ +ff4fd74fe71e4a2cbd49e972d7a3501849a474cfb410f9f06a29ecb1a6589196 diff --git a/subprojects/task/.pre-commit-config.yaml b/subprojects/task/.pre-commit-config.yaml new file mode 100644 index 000000000..5d28bd462 --- /dev/null +++ b/subprojects/task/.pre-commit-config.yaml @@ -0,0 +1,46 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-json + - id: check-yaml + exclude: ^\.clang-(format|tidy)$ + - id: check-added-large-files + + # Config file: .codespellrc + - repo: https://github.com/codespell-project/codespell + rev: v2.4.2 + hooks: + - id: codespell + files: ^.*\.(cmake|cpp|cppm|hpp|txt|md|mds|json|js|in|yaml|yml|py|toml)$ + args: ["--write", "--ignore-words", ".codespellignore" ] + + - repo: https://github.com/bemanproject/beman-tidy + rev: v0.4.0 + hooks: + - id: beman-tidy + + # Clang-format for C++ + # This brings in a portable version of clang-format. + # See also: https://github.com/ssciwr/clang-format-wheel + # Config file: .clang-format + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: v22.1.4 + hooks: + - id: clang-format + types_or: [c++, c, json, javascript] + exclude: docs/TODO.json + + # CMake linting and formatting + - repo: https://github.com/BlankSpruce/gersemi-pre-commit + rev: 0.27.2 + hooks: + - id: gersemi + name: CMake linting + exclude: ^.*/tests/.*/data/ # Exclude test data directories + +exclude: 'infra/' diff --git a/subprojects/task/CMakeLists.txt b/subprojects/task/CMakeLists.txt new file mode 100644 index 000000000..2c3f3c414 --- /dev/null +++ b/subprojects/task/CMakeLists.txt @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +cmake_minimum_required(VERSION 3.30...4.3) + +project( + beman.task # CMake Project Name, which is also the name of the top-level + # targets (e.g., library, executable, etc.). + DESCRIPTION "A Beman library task (coroutine task)" + LANGUAGES CXX + VERSION 0.2.0 +) + +# [CMAKE.SKIP_TESTS] +option( + BEMAN_TASK_BUILD_TESTS + "Enable building tests and test infrastructure. Default: ON. Values: { ON, OFF }." + ${PROJECT_IS_TOP_LEVEL} +) + +# [CMAKE.SKIP_EXAMPLES] +option( + BEMAN_TASK_BUILD_EXAMPLES + "Enable building examples. Default: ON. Values: { ON, OFF }." + ${PROJECT_IS_TOP_LEVEL} +) + +include(FetchContent) + +FetchContent_Declare( + execution + # FETCHCONTENT_SOURCE_DIR_EXECUTION ${CMAKE_SOURCE_DIR}/../execution + GIT_REPOSITORY https://github.com/bemanproject/execution + GIT_TAG 7df0f75 + SYSTEM + FIND_PACKAGE_ARGS + 0.2.0 + EXACT + NAMES + beman.execution + COMPONENTS + execution_headers +) +FetchContent_MakeAvailable(execution) + +add_subdirectory(src/beman/task) + +#=============================================================================== +# NOTE: this must be done before tests! CK +include(infra/cmake/beman-install-library.cmake) +beman_install_library(${PROJECT_NAME} TARGETS ${PROJECT_NAME} beman.task_headers + DEPENDENCIES [===[beman.execution 0.2.0 EXACT]===] +) +#=============================================================================== + +enable_testing() + +if(BEMAN_TASK_BUILD_TESTS) + add_subdirectory(tests/beman/task) +endif() + +if(BEMAN_TASK_BUILD_EXAMPLES) + add_subdirectory(examples) +endif() diff --git a/subprojects/task/CMakePresets.json b/subprojects/task/CMakePresets.json new file mode 100644 index 000000000..5f09777fa --- /dev/null +++ b/subprojects/task/CMakePresets.json @@ -0,0 +1,413 @@ +{ + "version": 6, + "configurePresets": [ + { + "name": "_root-config", + "hidden": true, + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/${presetName}", + "cacheVariables": { + "BEMAN_USE_MODULES": false, + "BEMAN_USE_STD_MODULE": false, + "CMAKE_CXX_STANDARD": "23", + "CMAKE_CXX_EXTENSIONS": true, + "CMAKE_CXX_STANDARD_REQUIRED": true, + "CMAKE_EXPORT_COMPILE_COMMANDS": true, + "CMAKE_INSTALL_MESSAGE": "LAZY", + "CMAKE_SKIP_TEST_ALL_DEPENDENCY": false, + "CMAKE_PROJECT_TOP_LEVEL_INCLUDES": "infra/cmake/use-fetch-content.cmake" + } + }, + { + "name": "_debug-base", + "hidden": true, + "warnings": { + "dev": true, + "deprecated": true, + "uninitialized": true, + "unusedCli": true, + "systemVars": false + }, + "errors": { + "dev": false, + "deprecated": false + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "_release-base", + "hidden": true, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } + }, + { + "name": "gcc-debug", + "displayName": "GCC Debug Build", + "inherits": [ + "_root-config", + "_debug-base" + ], + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "infra/cmake/gnu-toolchain.cmake" + } + }, + { + "name": "gcc-release", + "displayName": "GCC Release Build", + "inherits": [ + "_root-config", + "_release-base" + ], + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "infra/cmake/gnu-toolchain.cmake" + } + }, + { + "name": "llvm-debug", + "displayName": "Clang Debug Build", + "inherits": [ + "_root-config", + "_debug-base" + ], + "cacheVariables": { + "BEMAN_USE_STD_MODULE": false, + "CMAKE_TOOLCHAIN_FILE": "infra/cmake/llvm-libc++-toolchain.cmake" + }, + "environment": { + "CXX": "clang++", + "CMAKE_CXX_FLAGS": "-stdlib=libc++" + } + }, + { + "name": "llvm-release", + "displayName": "Clang Release Build", + "inherits": [ + "_root-config", + "_release-base" + ], + "cacheVariables": { + "BEMAN_USE_STD_MODULE": false, + "CMAKE_TOOLCHAIN_FILE": "infra/cmake/llvm-libc++-toolchain.cmake" + }, + "environment": { + "CXX": "clang++", + "CMAKE_CXX_FLAGS": "-stdlib=libc++" + } + }, + { + "name": "appleclang-debug", + "displayName": "Appleclang Debug Build", + "inherits": [ + "_root-config", + "_debug-base" + ], + "cacheVariables": { + "BEMAN_USE_STD_MODULE": false, + "BEMAN_USE_MODULES": false, + "CMAKE_TOOLCHAIN_FILE": "infra/cmake/appleclang-toolchain.cmake" + } + }, + { + "name": "appleclang-release", + "displayName": "Appleclang Release Build", + "inherits": [ + "_root-config", + "_release-base" + ], + "cacheVariables": { + "BEMAN_USE_STD_MODULE": false, + "BEMAN_USE_MODULES": false, + "CMAKE_TOOLCHAIN_FILE": "infra/cmake/appleclang-toolchain.cmake" + } + }, + { + "name": "msvc-debug", + "displayName": "MSVC Debug Build", + "inherits": [ + "_root-config", + "_debug-base" + ], + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "infra/cmake/msvc-toolchain.cmake" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "msvc-release", + "displayName": "MSVC Release Build", + "inherits": [ + "_root-config", + "_release-base" + ], + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "infra/cmake/msvc-toolchain.cmake" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + } + ], + "buildPresets": [ + { + "name": "_root-build", + "hidden": true, + "jobs": 0 + }, + { + "name": "gcc-debug", + "configurePreset": "gcc-debug", + "inherits": [ + "_root-build" + ] + }, + { + "name": "gcc-release", + "configurePreset": "gcc-release", + "inherits": [ + "_root-build" + ] + }, + { + "name": "llvm-debug", + "configurePreset": "llvm-debug", + "inherits": [ + "_root-build" + ] + }, + { + "name": "llvm-release", + "configurePreset": "llvm-release", + "inherits": [ + "_root-build" + ] + }, + { + "name": "appleclang-debug", + "configurePreset": "appleclang-debug", + "inherits": [ + "_root-build" + ] + }, + { + "name": "appleclang-release", + "configurePreset": "appleclang-release", + "inherits": [ + "_root-build" + ] + }, + { + "name": "msvc-debug", + "configurePreset": "msvc-debug", + "inherits": [ + "_root-build" + ] + }, + { + "name": "msvc-release", + "configurePreset": "msvc-release", + "inherits": [ + "_root-build" + ] + } + ], + "testPresets": [ + { + "name": "_test_base", + "hidden": true, + "output": { + "outputOnFailure": true + }, + "execution": { + "noTestsAction": "error", + "stopOnFailure": true + } + }, + { + "name": "gcc-debug", + "inherits": "_test_base", + "configurePreset": "gcc-debug" + }, + { + "name": "gcc-release", + "inherits": "_test_base", + "configurePreset": "gcc-release" + }, + { + "name": "llvm-debug", + "inherits": "_test_base", + "configurePreset": "llvm-debug" + }, + { + "name": "llvm-release", + "inherits": "_test_base", + "configurePreset": "llvm-release" + }, + { + "name": "appleclang-debug", + "inherits": "_test_base", + "configurePreset": "appleclang-debug" + }, + { + "name": "appleclang-release", + "inherits": "_test_base", + "configurePreset": "appleclang-release" + }, + { + "name": "msvc-debug", + "inherits": "_test_base", + "configurePreset": "msvc-debug" + }, + { + "name": "msvc-release", + "inherits": "_test_base", + "configurePreset": "msvc-release" + } + ], + "workflowPresets": [ + { + "name": "gcc-debug", + "steps": [ + { + "type": "configure", + "name": "gcc-debug" + }, + { + "type": "build", + "name": "gcc-debug" + }, + { + "type": "test", + "name": "gcc-debug" + } + ] + }, + { + "name": "gcc-release", + "steps": [ + { + "type": "configure", + "name": "gcc-release" + }, + { + "type": "build", + "name": "gcc-release" + }, + { + "type": "test", + "name": "gcc-release" + } + ] + }, + { + "name": "llvm-debug", + "steps": [ + { + "type": "configure", + "name": "llvm-debug" + }, + { + "type": "build", + "name": "llvm-debug" + }, + { + "type": "test", + "name": "llvm-debug" + } + ] + }, + { + "name": "llvm-release", + "steps": [ + { + "type": "configure", + "name": "llvm-release" + }, + { + "type": "build", + "name": "llvm-release" + }, + { + "type": "test", + "name": "llvm-release" + } + ] + }, + { + "name": "appleclang-debug", + "steps": [ + { + "type": "configure", + "name": "appleclang-debug" + }, + { + "type": "build", + "name": "appleclang-debug" + }, + { + "type": "test", + "name": "appleclang-debug" + } + ] + }, + { + "name": "appleclang-release", + "steps": [ + { + "type": "configure", + "name": "appleclang-release" + }, + { + "type": "build", + "name": "appleclang-release" + }, + { + "type": "test", + "name": "appleclang-release" + } + ] + }, + { + "name": "msvc-debug", + "steps": [ + { + "type": "configure", + "name": "msvc-debug" + }, + { + "type": "build", + "name": "msvc-debug" + }, + { + "type": "test", + "name": "msvc-debug" + } + ] + }, + { + "name": "msvc-release", + "steps": [ + { + "type": "configure", + "name": "msvc-release" + }, + { + "type": "build", + "name": "msvc-release" + }, + { + "type": "test", + "name": "msvc-release" + } + ] + } + ] +} diff --git a/subprojects/task/LICENSE b/subprojects/task/LICENSE new file mode 100644 index 000000000..f6db814c2 --- /dev/null +++ b/subprojects/task/LICENSE @@ -0,0 +1,219 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +---- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. diff --git a/subprojects/task/Makefile b/subprojects/task/Makefile new file mode 100644 index 000000000..3ee617d03 --- /dev/null +++ b/subprojects/task/Makefile @@ -0,0 +1,74 @@ +# Standard stuff + +.SUFFIXES: + +MAKEFLAGS+= --no-builtin-rules # Disable the built-in implicit rules. +# MAKEFLAGS+= --warn-undefined-variables # Warn when an undefined variable is referenced. +# MAKEFLAGS+= --include-dir=$(CURDIR)/conan # Search DIRECTORY for included makefiles (*.mk). + +#-dk: note to self: PATH=/opt/llvm-19.1.6/bin:$PATH LDFLAGS=-fuse-ld=lld + +.PHONY: config test default install examples clean distclean doc docs html pdf format clang-format tidy + +export CXX := g++ + +BUILDDIR = build +PRESET = gcc-release +UNAME = $(shell uname -s) +ifeq ($(UNAME),Darwin) + # PRESET = appleclang-release + ifeq ($(shell uname -m),arm64) + PRESET = appleclang-release + else + PRESET = gcc-release + endif +endif +BUILD = $(BUILDDIR)/$(PRESET) + +default: $(BUILD)/compile_commands.json + cmake --workflow --preset=$(PRESET) + cmake --install $(BUILD) --prefix=$(BUILDDIR)/statedir + +examples: default + cmake -S $@ -G Ninja -B build/examples -D CMAKE_BUILD_TYPE=RelWithDebInfo \ + -D CMAKE_PREFIX_PATH=$(CURDIR)/$(BUILDDIR)/statedir \ + --fresh --log-level=VERBOSE + ninja -C build/examples all + ninja -C build/examples test + +docs doc: + cd docs; $(MAKE) + +pdf html: + cd docs; $(MAKE) doc-$@ + +$(BUILD)/compile_commands.json: Makefile CMakePresets.json + cmake --preset=$(PRESET) --log-level=VERBOSE + ln -sf $(@) . + +list: + cmake --workflow --list-presets + +format: + pre-commit run --all + +clang-format: + git clang-format main + +$(BUILDDIR)/tidy/compile_commands.json: Makefile CMakeLists.txt + CC=$(CXX) cmake -G Ninja -S . -B $(BUILDDIR)/tidy \ + -D CMAKE_EXPORT_COMPILE_COMMANDS=1 \ + --fresh --log-level=VERBOSE + ln -sf $(@) . + +tidy: $(BUILDDIR)/tidy/compile_commands.json + run-clang-tidy -p $(BUILDDIR)/tidy tests examples + +test: $(BUILDDIR) + cd $(BUILDDIR); ctest + +clean: + cmake --build $(BUILD) --target clean + +distclean: + $(RM) -r $(BUILD) $(BUILDDIR) diff --git a/subprojects/task/README.md b/subprojects/task/README.md new file mode 100644 index 000000000..3dd2efff4 --- /dev/null +++ b/subprojects/task/README.md @@ -0,0 +1,124 @@ +# beman.task: Beman Library Implementation of `task` (P3552) + + + +![Continuous Integration Tests](https://github.com/bemanproject/task/actions/workflows/ci_tests.yml/badge.svg) +![Standard Target](https://github.com/bemanproject/beman/blob/main/images/badges/cpp26.svg) +![Coverage](https://coveralls.io/repos/github/bemanproject/task/badge.svg?branch=main) +![Library Status](https://raw.githubusercontent.com/bemanproject/beman/refs/heads/main/images/badges/beman_badge-beman_library_under_development.svg) + +`beman::execution::task` is a class template which +is used as the the type of coroutine tasks. The corresponding objects +are senders. The first template argument (`T`) defines the result +type which becomes a `set_value_t(T)` completion signatures. The +second template argument (`Context`) provides a way to configure +the behavior of the coroutine. By default it can be left alone. + +**Implements**: `std::execution::task` proposed in [Add a Coroutine Lazy Type (P3552)](https://wg21.link/P3552). + +**Status**: [Under development and not yet ready for production use.](https://github.com/bemanproject/beman/blob/main/docs/beman_library_maturity_model.md#under-development-and-not-yet-ready-for-production-use) + +## License + +`beman.task` is licensed under the Apache License v2.0 with LLVM Exceptions. + +## Usage + +The following code snippet shows a basic use of `beman::task::task` +using sender/receiver facilities to implement version of `hello, +world`: + +```cpp +#include +#include +#include + +namespace ex = beman::execution; +namespace ly = beman::task; + +int main() { + return std::get<0>(*ex::sync_wait([]->ex::task { + std::cout << "Hello, world!\n"; + co_return co_await ex::just(0); + }())); +} +``` + +Full runnable examples can be found in [`examples/`](`./example`) (e.g., [`./examples/hello.cpp`](./examples/hello.cpp)). For some explanation see [`./docs/examples.md`](./docs/examples.md). + +## Help Welcome + +There are plenty of things which need to be done. See the +[contributions page](https://github.com/bemanproject/task/blob/main/docs/contributing.md) +for some ideas how to contribute. The [resources page](https://github.com/bemanproject/task/blob/main/docs/resources.md) +contains some links for general information about coroutines. + +## Building beman.task + +### Dependencies + +This project depends on +[`beman::execution`](https://github.com/bemanproject/execution) (which +will be automatically obtained using `cmake`'s `FetchContent*`). + +Build-time dependencies: + +- `cmake` +- `ninja`, `make`, or another CMake-supported build system + - CMake defaults to "Unix Makefiles" on POSIX systems + +### Supported Platforms + +| Compiler | Version | C++ Standards | Standard Library | +|----------|---------|---------------|------------------| +| GCC | 15-14 | C++26, C++23 | libstdc++ | +| Clang | 21-19 | C++26, C++23 | libc++ | +| MSVC | latest | C++23 | MSVC STL | + +### How to build beman.task + + + +This project strives to be as normal and simple a CMake project as +possible. This build workflow in particular will work, producing +a static `libbeman.task.a` library, ready to package with its +headers: + +```shell +# Build beman.task. +$ cmake --workflow --preset gcc-debug +$ cmake --workflow --preset gcc-release + +# Install beman.task into your system. +$ cmake --install build/gcc-release/ --prefix /opt/beman.task/ +-- Install configuration: "RelWithDebInfo" +-- Installing: /opt/beman.task/lib/libbeman.task.a +-- Installing: /opt/beman.task/include/beman/task/task.hpp +[...] + +$ tree /opt/beman.task/ +/opt/beman.task/ +├── include +│   └── beman +│   └── task +│   ├── detail +│   │   ├── affine_on.hpp +│   │   ├── ... +│   │   ├── task.hpp +│   │   └── with_error.hpp +│   └── task.hpp +└── lib + ├── cmake + │   └── beman + │   └── task + │   └── BemanTaskConfig.cmake + └── libbeman.task.a + +9 directories, 26 files +``` + +## Contributing + +Please do! Issues and pull requests are appreciated. diff --git a/subprojects/task/docs/Makefile b/subprojects/task/docs/Makefile new file mode 100644 index 000000000..36b9db487 --- /dev/null +++ b/subprojects/task/docs/Makefile @@ -0,0 +1,18 @@ +.PHONY: default doc doc-html doc-pdf + +default: doc + +doc: doc-html + +doc-html: P3552-task.html P3796-task-issues.html P3941-affinity.html P3980-allocation.html + +doc-pdf: P3552-task.pdf P3796-task-issues.pdf + +wg21/Makefile: + git clone https://github.com/mpark/wg21.git + +distclean: clean + $(RM) -r generated + $(RM) mkerr olderr + +include wg21/Makefile diff --git a/subprojects/task/docs/P3552-task.md b/subprojects/task/docs/P3552-task.md new file mode 100644 index 000000000..2e3263466 --- /dev/null +++ b/subprojects/task/docs/P3552-task.md @@ -0,0 +1,2108 @@ +--- +title: Add a Coroutine Task Type +document: P3552R3 +date: 2025-06-20 +audience: + - Concurrency Working Group (SG1) + - Library Evolution Working Group (LEWG) + - Library Working Group (LWG) +author: + - name: Dietmar Kühl (Bloomberg) + email: + - name: Maikel Nadolski + email: +source: + - https://github.com/bemanproject/task/doc/P3552-task.md +toc: false +--- + +C++20 added support for coroutines that can improve the experience +writing asynchronous code. C++26 added the sender/receiver model +for a general interface to asynchronous operations. The expectation +is that users would use the framework using some coroutine type. To +support that, a suitable class needs to be defined and this proposal +is providing such a definition. + +Just to get an idea what this proposal is about: here is a simple +`Hello, world` written using the proposed coroutine type: + +```c++ +#include +#include +#include + +namespace ex = std::execution; + +int main() { + return std::get<0>(*ex::sync_wait([]->ex::task { + std::cout << "Hello, world!\n"; + co_return co_await ex::just(0); + }())); +} +``` + +# Change History + +## R0 Initial Revision + +## R1 Hagenberg Feedback + +- Changed the name from `lazy` to `task` based on SG1 feedback and + dropped the section on why `lazy` was chosen. +- Changed the name of `any_scheduler` to `task_scheduler`. +- Added wording for the `task` specification. + +## R2 LEWG Feedback + +- Removed the use `decay_t` from the specification. +- Changed the wording to avoid `exception_ptr` when unavailable. +- Renamed `Context` to `Environment` to better reflect the argument's use. +- Made exposition-only "macros" use italics. +- Added a feature test macro. +- Fixed some typos. + +## R3 LWG Feedback + +# Prior Work + +This proposal isn't the first to propose a coroutine type. Prior proposals +didn't see any recent (post introduction of sender/receiver) update, although +corresponding proposals were discussed informally on multiple occasions. +There are also implementations of coroutine types based on a sender/receiver +model in active use. This section provides an overview of this prior work, +and where relevant, of corresponding discussions. This section is primarily +for motivating requirements and describing some points in the design space. + +## [P1056](https://wg21.link/P1056): Add lazy coroutine (coroutine task) type + +The paper describes a `task`/`lazy` type (in +[P1056r0](https://wg21.link/P1056r0) the name was `task`; the primary +change for [P1056r1](https://wg21.link/P1056r1) is changing the +name to `lazy`). The fundamental idea is to have a coroutine type +which can be `co_await`ed: the interface of `lazy` consists of move +constructor, deliberately no move assignment, a destructor, and +`operator co_await()`. The proposals don't go into much detail +on how to eventually use a coroutine, but it mentions that there could +be functions like `sync_await(task)` to await completion of a +task (similar to +[`execution::sync_wait(sender)`](https://eel.is/c++draft/exec.sync.wait)) +or a few variations of that. + +A fair part of the paper argues why `future.then()` is _not_ a good +approach to model coroutines and their results. Using `future` +requires allocation, synchronization, reference counting, and +scheduling which can all be avoided when using coroutines in a +structured way. + +The paper also mentions support for [symmetric +transfer](https://wg21.link/P0913) and allocator support. Both of these +are details on how the coroutine is implemented. + +[Discussion for P1056r0 in SG1](https://wiki.edg.com/bin/view/Wg21rapperswil2018/P1056R0) + +- The task doesn't really have anything to do with concurrency. +- Decomposing a task cheaply is fundamental. The + [HALO Optimizations](https://wg21.link/P0981R0) help. +- The `task` isn't move assignable because there are better approaches + than using containers to hold them. It is move constructible as there + are no issues with overwriting a potentially live task. +- Resuming where things complete is unsafe but the task didn't want to + impose any overhead on everybody. +- There can be more than one `task` type for different needs. +- Holding a mutex lock while `co_await`ing which may resume on a different + thread is hazardous. Static analyzers should be able to detect these cases. +- Votes confirmed the no move assignment and forwarding to LEWG assuming + the name is not `task`. +- Votes against deal with associated executors and a request to have + strong language about transfer between threads. + +## [P2506](https://wg21.link/P2506): std::lazy: a coroutine for deferred execution + +This paper is effectively restating what [P1056](https://wg21.link/P1056) +said with the primary change being more complete proposed wording. +Although sender/receiver were discussed when the paper was written +but [`std::execution`](https://wg21.link/P2300) hadn't made it into +the working paper, the proposal did _not_ take a sender/receiver +interface into account. + +Although there were mails seemingly scheduling a discussion in LEWG, +we didn't manage to actually locate any discussion notes. + +## [cppcoro](https://github.com/lewissbaker/cppcoro) + +This library contains multiple coroutine types, algorithms, and +some facilities for asynchronous work. For the purpose of this +discussion only the task types are of interest. There are two task +types +[`cppcoro::task`](https://github.com/lewissbaker/cppcoro?tab=readme-ov-file#taskt) +and +[`cppcoro::shared_task`](https://github.com/lewissbaker/cppcoro?tab=readme-ov-file#shared_taskt). +The key difference between `task` and `shared_task` is that the +latter can be copied and awaited by multiple other coroutines. As +a result `shared_task` always produces an lvalue and may have +slightly higher costs due to the need to maintain a reference count. + +The types and algorithms are pre-sender/receiver and operate entirely +in terms for awaiters/awaitables. The interface of both task types +is a bit richer than that from +[P1056](https://wg21.link/P1056)/[P2506](https://wg21.link/P2506). Below +`t` is either a `cppcoro::task` or a `cppcoro::shared_task`: + +- The task objects can be move constructed and move assigned; `shared_task` + object can also be copy constructed and copy assigned. +- Using `t.is_ready()` it can be queried if `t` has completed. +- Using `co_await t` awaits completion of `t`, yielding the result. The result + may be throwing an exception if the coroutine completed by throwing. +- Using `co_await t.when_ready()` allows synchronizing with the completion of + `t` without actually getting the result. This form of synchronization won't + throw any exception. +- `cpproro::shared_task` also supports equality comparisons. + +In both cases, the task starts suspended and is resumed when it is +`co_await`ed. This way a continuation is known when the task is +resumed, which is similar to `start(op)`ing an operation state `op`. +The coroutine body needs to use `co_await` or `co_return`. `co_await` +expects an awaitable or an awaiter as argument. Using `co_yield` +is not supported. The implementation supports symmetric transfer but +doesn't mention allocators. + +The `shared_task` is similar to +[`split(sender)`](https://eel.is/c++draft/exec.split): in both cases, +the same result is produced for multiple consumers. Correspondingly, +there isn't a need to support a separate `shared_task` in a +sender/receiver world. Likewise, throwing of results can be avoid +by suitably rewriting the result of the `set_error` channel avoiding +the need for an operation akin to `when_ready()`. + +## [libunifex](https://github.com/facebookexperimental/libunifex) + +`unifex` is an earlier implementation of the sender/receiver +ideas. Compared to `std::execution` it is lacking some of the +flexibilities. For example, it doesn't have a concept of environments +or domains. However, the fundamental idea of three completion +channels for success, failure, and cancellation and the general +shape of how these are used is present (even using the same names +for `set_value` and `set_error`; the equivalent of `set_stopped` +is called `set_done`). `unifex` is in production use in multiple +places. The implementation includes a +[`unifex::task`](https://github.com/facebookexperimental/libunifex/blob/main/include/unifex/task.hpp). + +As `unifex` is sender/receiver-based, its `unifex::task` is +implemented such that `co_await` can deal with senders in addition +to awaitables or awaiters. Also, `unifex::task` is _scheduler +affine_: the coroutine code resumes on the same scheduler even if a +sender completed on a different scheduler. The task's scheduler +is taken from the receiver it is `connect`ed to. The exception for +rescheduling on the task's scheduler is explicitly awaiting the +result of `schedule(sched)` for some scheduler `sched`: the operation +changes the task's scheduler to be `sched`. The relevant treatment +is in the promise type's `await_transform()`: + +- If a sender `sndr` which is the result of `schedule(sched)` is + `co_await`ed, the corresponding `sched` is installed as the task's + scheduler and the task resumes on the context completing `sndr`. + Feedback from people working with unifex suggests that this choice + for changing the scheduler is too subtle. While it is considered + important to be able to explicitly change the scheduler a task + executes on, doing so should be more explicit. +- For both senders and awaiters being awaited, the coroutine + will be resumed on the task's current scheduler when the task is + scheduler affine. In general that is done by continuing with the + senders result on the task's scheduler, similar to `continues_on(sender, + scheduler)`. The rescheduling is avoided when the sender is tagged + as not changing scheduler (using a `static constexpr` member named + `blocking` which is initialized to `blocking_kind::always_inline`). +- If a sender is `co_await`ed it gets `connect`ed to a receiver + provided by the task to form an awaiter holding an operation + state. The operation state gets `start`ed by the awaiter's + `await_suspend`. The receiver arranges for a `set_value` completion + to become a value returned from `await_resume`, a `set_error` + completion to become an exception, and a `set_done` completion + to resume a special "on done" coroutine handle rather than resuming + the task itself effectively behaving like an uncatchable exception + (all relevant state is properly destroyed and the coroutine is + never resumed). + +When `co_await`ing a sender `sndr` there can be at most one `set_value` +completion: if there are more than one `set_value` completions the +promise type's `await_transform` will just return `sndr` and the +result cannot be `co_await`ed (unless it is also given an awaitable +interface). The result type of `co_await sndr` depends on the number +of arguments to `set_value`: + +- If there are no arguments for `set_value` then the type of `co_await sndr` + will be `void`. +- If there is exactly one argument of type `T` for `set_value` + then the type of `co_await sndr` will be `T`. +- If there are more than one arguments for `set_value` then + the type of `co_await sndr` will be `std::tuple` + with the corresponding argument types. + +If a receiver doesn't have a scheduler, it can't be `connect()`ed +to a `unifex::task`. In particular, when using a `unifex::async_scope +scope` it isn't possible to directly call `scope.spawn(task)` with +a `unifex::task task` as the `unifex::async_scope` doesn't provide +a scheduler. The `unifex::async_scope` provides a few variations +of `spawn()` which take a scheduler as argument. + +`unifex` provides some sender algorithms to transform the sender +result into something which may be more suitable to be `co_await`ed. +For example, `unifex::done_as_optional(sender)` turns a successful +completion for a type `T` into an `std::optional` and the +cancellation completion `set_done` into a `set_value` completion +with a disengaged `std::optional`. + +The `unifex::task` is itself a sender and can be used correspondingly. +To deal with scheduler affinity a type erased scheduler +`unifex::any_scheduler` is used. + +The `unifex::task` doesn't have allocator support. When creating +a task multiple objects are allocated on the heap: it seems there +is a total of 6 allocations for each `unifex::task` being created. +After that, it seems the different `co_await`s don't use a separate +allocation. + +The `unifex::task` doesn't directly guard against stack overflow. +Due to rescheduling continuations on a scheduler when the completion +isn't always inline, the issue only arises when `co_await`ing many +senders with `blocking_kind::always_inline` or when the scheduler +resumes inline. + +## [stdexec](https://github.com/NVIDIA/stdexec) + +The +[`exec::task`](https://github.com/NVIDIA/stdexec/blob/main/include/exec/task.hpp) +in stdexec is somewhat similar to the unifex task with some choices +being different, though: + +- The `exec::task` is also scheduler affine. The chosen scheduler + is unconditionally used for every `co_await`, i.e., there is no + attempt made to avoid scheduling, e.g., when the `co_await`ed sender + completes inline. +- Unlike unifex, it is OK if the receiver's environment doesn't provide + a scheduler. In that case an inline scheduler is used. If an inline + scheduler is used there is the possibility of stack overflow. +- It is possible to `co_await just_error(e)` and `co_await just_stopped()`, + i.e., the sender isn't required to have a `set_value_t` completion. + +The `exec::task` also provides a _context_ `C`. An object of +this type becomes the environment for receivers `connect()`ed to +`co_await`ed senders. The default context provides access to the +task's scheduler. In addition an `in_place_stop_token` is provides +which forwards the stop requests from the environment of the receiver +which is connected to the task. + +Like the unifex task `exec::task` doesn't provide any allocator +support. When creating a task there are two allocations. + +# Objectives + +Also see [sender/receiver issue 241](https://github.com/cplusplus/sender-receiver/issues/241). + +Based on the prior work and discussions around corresponding coroutine +support there is a number of required or desired features (listed in +no particular order): + +1. A coroutine task needs to be awaiter/awaitable friendly, i.e., it + should be possibly to `co_await` awaitables which includes both + library provided and user provided ones. While that seems + obvious, it is possible to create an `await_transform` which + is deleted for awaiters and that should be prohibited. +2. When composing sender algorithms without using a coroutine it + is common to adapt the results using suitable algorithms and + the completions for sender algorithms are designed accordingly. + On the other hand, when awaiting senders in a coroutine it may + be considered annoying having to transform the result into a + shape which is friendly to a coroutine use. Thus, it may be + reasonable to support rewriting certain shapes of completion + signatures into something different to make the use of senders + easier in a coroutine task. See the section on the + [result type for `co_await`](#result-type-for-co_await) for a + discussion. + +3. A coroutine task needs to be sender friendly: it is expected that + asynchronous code is often written using coroutines awaiting + senders. However, depending on how senders are treated by a + coroutine some senders may not be awaitable. For example neither + unifex nor stdexec support `co_await`ing senders with more than + one `set_value` completion. +4. It is possibly confusing and problematic if coroutines resume on + a different execution context than the one they were suspended + on: the textual similarity to normal functions makes it look + as if things are executed sequentially. Experience also indicates + that continuing a coroutine on whatever context a `co_await`ed + operation completes frequently leads to issues. Senders could, + however, complete on an entirely different scheduler than where + they started. When composing senders (not using coroutines) + changing contexts is probably OK because it is done deliberately, + e.g., using `continues_on`, and the way to express things is + new with fewer attached expectations. + + To bring these two views + together a coroutine task should be scheduler affine by default, + i.e., it should normally resume on the same scheduler. There + should probably also be an explicit way to opt out of scheduler + affinity when the implications are well understood. + + Note that scheduler affinity does _not_ mean that a task is + always continuing on the same thread: a scheduler may refer to + a thread pool and the task will continue on one of the threads + (which also means that thread local storage cannot be used to + propagate contexts implicitly; see the [discussion on environments](#environment-support) + below). +5. When using coroutines there will probably be an allocation at + least for the coroutine frame (the [HALO optimizations](https://wg21.link/P0981R0) + can't always work). To support the use in environments + where memory allocations using `new`/`delete` aren't supported + the coroutine task should support allocations using allocators. +6. Receivers have associated environments which can support an open + set of queries. Normally, queries on an environment can be + forwarded to the environment of a `connect()`ed receiver. + Since the coroutine types are determined before the coroutine's + receiver is known and the queries themselves don't specify a + result type that isn't possible when a coroutine provides a + receiver to a sender in a `co_await` expression. It should still + be possible to provide a user-customisable environment from the + receiver used by `co_await` expressions. One aspect of this + environment is to forward stop requests to `co_await`ed child + operations. Another is possibly changing the scheduler to be + used when a child operation queries `get_scheduler` from the + receiver's environment. Also, in non-asynchronous code it is + quite common to pass some form of context implicitly using + thread local storage. In an asynchronous world such contexts + could be forwarded using the environment. +7. The coroutine should be able to indicate that it was canceled, + i.e., to get `set_stopped()` called on the task's receiver. + `std::execution::with_awaitable_senders` already provided this + ability senders being `co_await`ed but that doesn't necessarily + extend to the coroutine implementation. +8. Similar to indicating that a task got canceled it would be good + if a task could indicate that an error occurred without throwing + an exception which escapes from the coroutine. +9. In general a task has to assume that an exception escapes the + coroutine implementation. As a result, the task's completion + signatures need to include `set_error_t(std::exception_ptr)`. + If it can be indicated to the task that no exception will escape + the coroutine, this completion signature can be avoided. +10. When many `co_await`ed operations complete synchronously, there + is a chance for stack overflow. It may be reasonable to have + the implementation prevent stack overflow by using a suitable + scheduler sometimes. +11. In some situations it can be useful to somehow schedule an + asynchronous clean-up operation which is triggered upon + coroutine exit. See the section on [asynchronous clean-up](#asynchronous-clean-up) + below for more discussing +12. The `task` coroutine provided by the standard library may + not always fit user's needs although they may need/want various + of the facilities. To avoid having users implement all functionality + from scratch `task` should use specified components which can + be used by users when building their own coroutine. The components + `as_awaitable` and `with_awaitable_sender` are two parts of + achieving this objective but there are likely others. + + The algorithm `std::execution::as_awaitable` does turn a sender + into an awaitable and is expected to be used by custom written + coroutines. Likewise, it is intended that custom coroutines use + the CRTP class template `std::execution::with_awaitable_senders`. + It may be reasonable to adjust the functionality of these + components instead of defining the functionality specific to a + `task<...>` coroutine task. + +It is important to note that different coroutine task implementations +can live side by side: not all functionality has to be implemented +by the same coroutine task. The objective for this proposal is to +select a set of features which provides a coroutine task suitable +for most uses. It may also be reasonable to provide some variations +as different names. A future revision of the standard or third party +libraries can also provide additional variations. + +# Design + +This section discusses various design options for achieving the +listed objectives. Most of the designs are independent of each other +and can be left out if the consensus is that it shouldn't be used +for whatever reason. + +## Template Declaration for `task` + +Coroutines can use `co_return` to produce a value. The value returned can +reasonably provide the argument for the `set_value_t` completion +of the coroutines. As the type of a coroutine is defined even before +the coroutine body is given, there is no way to deduce the result +type. The result type is probably the primary customization and +should be the first template parameter which gets defaulted to +`void` for coroutines not producing any value. For example: + +```c++ +int main() { + ex::sync_wait([]->ex::task<>{ + int result = co_await []->ex::task { co_return 42; }(); + assert(result == 42); + }()); +} +``` + +The inner coroutines completes with `set_value_t(int)` which gets +translated to the value returned from `co_await` (see [`co_await` +result type below](#result-type-for-co_await) for more details). +The outer coroutine completes with `set_value_t()`. + +Beyond the result type there are a number of features for a coroutine +task which benefit from customization or for which it may be desirable +to disable them because they introduce a cost. As many template +parameters become unwieldy, it makes sense to combine these into a +[defaulted] context parameter. The aspects which benefit from +customization are at least: + +- [Customizing the environment](#environment-support) for child + operations. The context itself can actually become part of + the environment. +- Disable [scheduler affinity](#scheduler-affinity) and/or configure + the strategy for obtaining the coroutine's scheduler. +- Configure [allocator awareness](#allocator-support). +- Indicate that the coroutine should be [noexcept](#error-reporting). +- Define [additional error types](#error-reporting). + +The default context should be used such that any empty type provides +the default behavior instead of requiring a lot of boilerplate just +to configure a particular aspect. For example, it should be possible +to selectively enable [allocator support](#allocator-support) using +something like this: + +```c++ +struct allocator_aware_context { + using allocator_type = std::pmr::polymorphic_allocator; +}; +template +using my_task = ex::task; +``` + +Using various different types for task coroutines isn't a problem +as the corresponding objects normally don't show up in containers. +Tasks are mostly `co_await`ed by other tasks, used as child senders +when composing work graphs, or maintained until completed using +something like a [`counting_scope`](https://wg21.link/p3149). When +they are used in a container, e.g., to process data using a range +of coroutines, they are likely to use the same result type and +context types for configurations. + +## `task` Completion Signatures + +The discussion above established that `task` can have a +successful completion using `set_value_t(T)`. The coroutine completes +accordingly when it is exited using a matching `co_return`. When +`T` is `void` the coroutine also completes successfully using +`set_value()` when floating off the end of the coroutine or when +using a `co_return` without an expression. + +If a coroutine exits with an exception completing the corresponding +operation with `set_error(std::exception_ptr)` is an obvious choice. +Note that a `co_await` expression results in throwing an exception +when the awaited operation completes with `set_error(E)` +([see below](#result-type-for-co_await)), i.e., the coroutine itself +doesn't necessarily need to `throw` an exception itself. + +Finally, a `co_await` expression completing with `set_stoppped()` +results in aborting the coroutine immediately +([see below](#result-type-for-co_await)) and causing the coroutine +itself to also complete with `set_stopped()`. + +The coroutine implementation cannot inspect the coroutine body to +determine how the different asynchronous operations may complete. As +a result, the default completion signatures for `task` are + +```c++ +ex::completion_signatures< + ex::set_value_t(T), // or ex::set_value_t() if T == void + ex::set_error_t(std::exception_ptr), + ex:set_stopped_t() +>; +``` + +Support for [reporting an error without exception](#error-reporting) +may modify the completion signatures. + +## `task` constructors and assignments + +Coroutines are created via a factory function which returns the +coroutine type and whose body uses one of the `co_*` function, e.g. + +```c++ +task<> nothing(){ co_return; } +``` + +The actual object is created via the promise type's `get_return_object` +function and it is between the promise and coroutine types how +that actually works: this constructor is an implementation detail. +To be valid senders the coroutine type needs to be destructible and it +needs to have a move constructor. Other than that, constructors and +assignments either don't make sense or enable dangerous practices: + +1. Copy constructor and copy assignment don't make sense because there + is no way to copy the actual coroutine state. +2. Move assignment is rather questionable because it makes it easy to + transport the coroutine away from referenced entities. + + Previous papers [P1056](https://wg21.link/p1056) and + [P2506](https://wg21.link/p2506) also argued against a move + assignment. However, one of the arguments doesn't apply to the + `task` proposed here: There is no need to deal with cancellation + when assigning or destroying a `task` object. Upon `start()` + of `task` the coroutine handle is transferred to an operation + state and the original coroutine object doesn't have any + reference to the object anymore. +3. If there is no assignment, a default constructed object doesn't make + much sense, i.e., `task` also doesn't have a default constructor. + +Based on experience with [Folly](https://github.com/facebook/folly) +the suggestion was even stronger: `task` shouldn't even have move +construction! That would mean that `task` can't be a sender or that +there would need to be some internal interface enabling the necessary +transfer. That direction isn't pursued by this proposal. + +The lack of move assignment doesn't mean that `task` can't be held +in a container: it is perfectly fine to `push_back` objects of this +type into a container, e.g.: + +```c++ +std::vector> cont; +cont.emplace_back([]->ex::task<> { co_return; }()); +cont.push_back([]->ex::task<> { co_return; }()); +``` + +The expectation is that most of the time coroutines don't end up +in normal containers. Instead, they'd be managed by a +[`counting_scope`](https://wg21.link/p3149) or hold on to by objects +in a work graph composed of senders. + +Technically there isn't a problem adding a default constructor, move +assignment, and a `swap()` function. Based on experience with similar +components it seems `task` is better off not having them. + +## Result Type For `co_await` + +When `co_await`ing a sender `sndr` in a coroutine, `sndr` needs to +be transformed to an awaitable. The existing approach is to use +`execution::as_waitable(sndr)` [[exex.as.awaitable]](https://eel.is/c++draft/exec.as.awaitable) +in the promise type's `await_transform` and `task` uses that approach. +The awaitable returned from `as_awaitable(sndr)` has the following +behavior (`rcvr` is the receiver the sender `sndr` is connected to): + +1. When `sndr` completes with `set_stopped(std::move(rcvr))` the function `unhandled_stopped()` + on the promise type is called and the awaiting coroutine is never + resumed. The `unhandled_stopped()` results in `task` itself + also completing with `set_stopped_t()`. +2. When `sndr` completes with `set_error(std::move(rcvr), error)` the coroutine is resumed + and the `co_await sndr` expression results in `error` being thrown as an + exceptions (with special treatment for `std::error_code`). +3. When `sndr` completes with `set_value(std::move(rcvr), a...)` the expression `co_await sndr` + produces a result corresponding the arguments to `set_value`: + + 1. If the argument list is empty, the result of `co_await sndr` is `void`. + 2. Otherwise, if the argument list contains exactly one element the result of `co_await sndr` is `a...`. + 3. Otherwise, the result of `co_await sndr` is `std::tuple(a...)`. + +Note that the sender `sndr` is allowed to have no `set_value_t` +completion signatures. In this case the result type of the awaitable +returned from `as_awaitable(sndr)` is declared to be `void` but +`co_await sndr` would never return normally: the only ways to +complete without a `set_value_t` completion is to complete with +`set_stopped(std::move(rcvr)` or with `set_error(std::move(rcvr), +error)`, i.e., the expression either results in the coroutine to +be never resumed or an exception being thrown. + +Here is an example which summarizes the different supported result types: + +```c++ +task<> fun() { + co_await ex::just(); // void + auto v = co_await ex::just(0); // int + auto[i, b, c] = co_await ex::just(0, true, 'c'); // tuple + try { co_await ex::just_error(0); } catch (int) {} // exception + co_await ex::just_stopped(); // cancel: never resumed +} +``` + +The sender `sndr` can have at most one `set_value_t` completion +signature: if there are more than one `set_value_t` completion +signatures `as_awaitable(sndr)` is invalid and fails to compile: users +who want to `co_await` a sender with more than one `set_value_t` +completions need to use `co_await into_variant(s)` (or similar) to +transform the completion signatures appropriately. It would be possible +to move this transformation into `as_awaitable(sndr)`. + +Using effectively `into_variant(s)` isn't the only possible +transformation if there are multiple `set_value_t` transformations. +To avoid creating a fairly hard to use result object, `as_awaitable(sndr)` +could detect certain usage patterns and rather create a result which +is easier to use when being `co_await`ed. An example for this +situation is the `queue.async_pop()` operation for [concurrent +queues](https://wg21.link/P0260): this operation can complete +successfully in two ways: + +1. When an object was extracted the operation completes with `set_value(std::move(rcvr), value)`. +2. When the queue was closed the operation completes with `set_value(std::move(rcvr))`. + +Turning the result of `queue.async_pop()` into an awaitable +using the current `as_awaitable(queue.async_pop())` +([[exec.as.awaitable](https://eel.is/c++draft/exec#as.awaitable)]) +fails because the function accepts only senders with at most +one `set_value_t` completion. Thus, it is necessary to use something +like the below: + +```c++ +task<> pop_demo(auto& queue) { + // auto value = co_await queue.async_pop(); // doesn't work + std::optional v0 = co_await (queue.async_pop() | into_optional); + std::optional v1 = co_await into_optional(queue.async_pop()); +} +``` + +The algorithm `into_optional(sndr)` would determine that there is +exactly one `set_value_t` completion with arguments and produce an +`std::optional` if there is just one parameter of type `T` and +produce a `std::optional>` if there are more than +one parameter with types `T...`. It would be possible to apply this +transformation when a corresponding set of completions is detected. +The proposal [optional variants in sender/receiver](https://wg21.link/P3570) +goes into this direction. + +This proposal currently doesn't propose a change to `as_awaitable` +([[exec.as.awaitable](https://eel.is/c++draft/exec#as.awaitable)]). +The primary reason is that there are likely many different shapes +of completions each with a different desirable transformation. If +these are all absorbed into `as_awaitable` it is likely fairly hard +to reason what exact result is returned. Also, there are likely +different options of how a result could be transformed: `into_optional` +is just one example. It could be preferable to turn the two results +into an `std::expected` instead. However, there should probably be +some transformation algorithms like `into_optional`, `into_expected`, +etc. similar to `into_variant`. + +## Scheduler Affinity + +Coroutines look very similar to synchronous code with a few +`co`-keywords sprinkled over the code. When reading such code the +expectation is typically that all code executes on the same context +despite some `co_await` expressions using senders which may explicitly +change the scheduler. There are various issues when using `co_await` +naïvely: + +- Users may expect that work continues on the same context where it + was started. If the coroutine simply resumes when the `co_await`ed + senders calls a completion function code may execute some lengthy + operation on a context which is expected to keep a UI responsive + or which is meant to deal with I/O. +- Conversely, running a loop `co_await`ing some work may be seen as + unproblematic but may actually easily cause a stack overflow if + `co_await`ed work immediately completes (also + [see below](#avoiding-stack-overflow)). +- When `co_await`ing some work completes on a different context and + later a blocking call is made from the coroutine which also + ends up `co_await`ing some work from the same resource there + can be a dead lock. + +Thus, the execution should normally be scheduled on the original +scheduler: doing so can avoid the problems mentioned above (assuming +a scheduler is used which doesn't immediately complete without +actually scheduling anything). This transfer of the execution with +a coroutine is referred to as _scheduler affinity_. Note: a scheduler +may execute on multiple threads, e.g., for a pool scheduler: execution +would get to any of these threads, i.e., thread local storage is +_not_ guaranteed to access the same data even with scheduler affinity. +Also, scheduling work has some cost even if this cost can often be +fairly small. + +The basic idea for scheduler affinity consists of a few parts: + +1. A scheduler is determined when `start`ing an operation state + which resulted from `connect`ing a coroutine to a receiver. + This scheduler is used to resume execution of the coroutine. + The scheduler is determined based on the receiver `rcvr`'s + environment. + + ```c++ + auto scheduler = get_scheduler(get_env(rcvr)); + ``` + +2. The type of `scheduler` is unknown when the coroutine is created. + Thus, the coroutine implementation needs to operate in terms + of a scheduler with a known type which can be constructed from + `scheduler`. The used scheduler type is determined based on the + context parameter `C` of the coroutine type `task` using + `typename C::scheduler_type` and defaults to `task_scheduler` + if this type isn't defined. `task_scheduler` uses type-erasure + to deal with arbitrary schedulers (and small object optimizations + to avoid allocations). The used scheduler type can be parameterized + to allow use of `task` contexts where the scheduler type is + known, e.g., to avoid the costs of type erasure. + + Originally `task_scheduler` was called `any_scheduler` but there + was feedback from SG1 suggesting that a general `any_scheduler` + may need to cover various additional properties. To avoid dealing + with generalizing the facility a different name is used. The + name remains specified as it is still a useful component, at + least until an `any_scheduler` is defined by the standard + library. If necessary, the type erased scheduler type used by + `task` can be unspecified. + +3. When an operation which is `co_await`ed completes the execution + is transferred to the held scheduler using `continues_on`. + Injecting this operation into the graph can be done in the + promise type's `await_transform`: + + ```c++ + template + auto await_transform(Sender&& sndr) noexcept { + return ex::as_awaitable_sender( + ex::continues_on(std::forward(sndr), + this->scheduler); + ); + } + ``` + +There are a few immediate issues with the basic idea: + +1. What should happen if there is no scheduler, i.e., + `get_scheduler(get_env(rcvr))` doesn't exist? +2. What should happen if the obtained `scheduler` is incompatible with + the coroutine's scheduler? +3. Scheduling isn't free and despite the potential problems it should + be possible to use `task` without scheduler affinity. +4. When operations are known to complete inline the scheduler isn't + actually changed and the scheduling operation should be avoided. +5. It should be possible to explicitly change the scheduler used by + a coroutine from within this coroutine. + +All of these issues can be addressed although there are different +choices in some of these cases. + +In many cases the receiver can provide access to a scheduler via +the environment query. An example where no scheduler is available +is when starting a task on a [`counting_scope`](https://wg21.link/p3149). +The scope doesn't know about any schedulers and, thus, the receiver +used by `counting_scope` when `connect`ing to a sender doesn't +support the `get_scheduler` query, i.e., this example doesn't work: + +```c++ +ex::spawn([]->ex::task { co_await ex::just(); }(), token); +``` + +Using `spawn()` with coroutines doing the actual work is expected +to be quite common, i.e., it isn't just a theoretical possibility that +`task` is used together with `counting_scope`. The approach used by +[`unifex`](https://github.com/facebookexperimental/libunifex) is +to fail compilation when trying to `connect` a `Task` to a receiver +without a scheduler. The approach taken by +[`stdexec`](https://github.com/NVIDIA/stdexec) is to keep executing +inline in that case. Based on the experience that silently changing +contexts within a coroutine frequently causes bugs it seems failing +to compile is preferable. + +Failing to construct the scheduler used by a coroutine with the +`scheduler` obtained from the receiver is likely an error and should +be addressed by the user appropriately. Failing to compile is seems +to be a reasonable approach in that case, too. + +It should be possible to avoid scheduler affinity explicitly to +avoid the cost of scheduling. Users should be very careful when +pursuing this direction but it can be a valid option. One way to +achieve that is to create an "inline scheduler" which immediately +completes when it is `start()`ed and using this type for the +coroutine. Explicitly providing a type `inline_scheduler` implementing +this logic could allow creating suitable warnings. It would also +allow detecting that type in `await_transform` and avoiding the use +of `continues_on` entirely. + +When operations actually don't change the scheduler there shouldn't +be a need to schedule them again. In these cases it would be great +if the `continues_on` could be avoided. At the moment there is no +way to tell whether a sender will complete inline. Using a sender +query which determines whether a sender always completes inline could +avoid the rescheduling. Something like that is implemented for +[`unifex`](https://github.com/facebookexperimental/libunifex): +senders define a property `blocking` which can have the value +`blocking_kind::always_inline`. The proposal [A sender query for +completion behavior](https://wg21.link/P3206) proposes a +`get_completion_behaviour(sndr, env)` customization point to address +this need. The result can indicate that the `sndr` returns synchronously +(using `completion_behaviour::synchronous` or +`completion_behaviour::inline_completion`). If `sndr` returns synchronously +there isn't a need to reschedule it. + +In some situations it is desirable to explicitly switch to a different +scheduler from within the coroutine and from then on carry on using +this scheduler. +[`unifex`](https://github.com/facebookexperimental/libunifex) detects +the use of `co_await schedule(scheduler);` for this purpose. That is, +however, somewhat subtle. It may be reasonable to use a dedicated +awaiter for this purpose and use, e.g. + +```c++ +auto previous = co_await co_continue_on(new_scheduler); +``` + +Using this statement replaces the coroutine's scheduler with the +`new_scheduler`. When the `co_await` completes it is on `new_scheduler` +and further `co_await` operations complete on `new_scheduler`. The +result of `co_await`ing `co_continue_on` is the previously used +scheduler to allow transfer back to this scheduler. In +[stdexec](https://github.com/NVIDIA/stdexec) the corresponding +operation is called `reschedule_coroutine`. + +Another advantage of scheduling the operations on a scheduler instead +of immediately continuing on the context where the operation completed +is that it helps with stack overflows: when scheduling on a non-inline +scheduler the call stack is unwound. Without that it may be necessary +to inject scheduling just for the purpose of avoiding stack overflow +when too many operations complete inline. + +## Allocator Support + +When using coroutines at least the coroutine frame may end up being +allocated on the heap: the [HALO](https://wg21.link/P0981) optimizations +aren't always possible, e.g., when a coroutine becomes a child of +another sender. To control how this allocation is done and to support +environments where allocations aren't possible `task` should have +allocator support. The idea is to pick up on a pair of arguments of +type `std::allocator_arg_t` and an allocator type being passed and use +the corresponding allocator if present. For example: + +```c++ +struct allocator_aware_context { + using allocator_type = std::pmr::polymorphic_allocator; +}; + +template +ex::task fun(int value, A&&...) { + co_return value; +} + +int main() { + // Use the coroutine without passing an allocator: + ex::sync_wait(fun(17)); + + // Use the coroutine with passing an allocator: + using allocator_type = std::pmr::polymorphic_alloctor; + ex::sync_wait(fun(17, std::allocator_arg, allocator_type())); +} +``` + +The arguments passed when creating the coroutine are made available +to an `operator new` of the promise type, i.e., this operator can +extract the allocator, if any, from the list of parameters and use +that for the purpose of allocation. The matching `operator delete` +gets passed only the pointer to release and the originally requested +`size`. To have access to the correct allocator in `operator delete` +the allocator either needs to be stateless or a copy needs to be accessible +via the pointer passed to `operator delete`, e.g., stored at the +offset `size`. + +To avoid any cost introduced by type erasing an allocator +type as part of the `task` definition the expected allocator type +is obtained from the context argument `C` of `task`: + +```c++ +using allocator_type = ex::allocator_of_t; +``` + +This `using` alias uses `typename C::allocator_type` if present or +defaults to `std::allocator` otherwise. This +`allocator_type` has to be for the type `std::byte` (if necessary +it is possible to relax that constraint). + +The allocator used for the coroutine frame should also be used for +any other allocators needed for the coroutine itself, e.g., when +type erasing something needed for its operation (although in most +cases a small object optimization would be preferable and sufficient). +Also, the allocator should be made available to child operations +via the respective receiver's environment using the `get_allocator` +query. The arguments passed to the coroutine are also available to +the constructor of the promise type (if there is a matching on) and +the allocator can be obtained from there: + +```c++ +struct allocator_aware_context { + using allocator_type = pmr::polymorphic_allocator; +}; +fixed_resource<2048> resource; + +ex::sync_wait([](auto&&, auto* resource) + -> ex::task { + auto alloc = co_await ex::read_env(ex::get_allocator); + use(alloc); +}(allocator_arg, &resource)); +``` + +## Environment Support + +When `co_await`ing child operations these may want to access an +environment. Ideally, the coroutine would expose the environment +from the receiver it gets `connect`ed to. Doing so isn't directly +possible because the coroutine types doesn't know about the receiver +type which in turn determines the environment type. Also, the +queries don't know the type they are going to return. Thus, some +extra mechanisms are needed to provide an environment. + +A basic environment can be provided by some entities already known +to the coroutine, though: + +- The `get_scheduler` query should provide the scheduler maintained + for [scheduler affinity](#scheduler-affinity) whose type is + determined based on the coroutine's context using + `ex::scheduler_of_t`. +- The `get_allocator` query should provide the + [coroutine's allocator](#allocator-support) whose type is + determined based on the coroutine's context using `ex::allocator_of_t`. + The allocator gets initialized when constructing the promise type. +- The `get_stop_token` query should provide a stop token + from a stop source which is linked to the stop token obtained + from the receiver's environment. The type of the stop source + is determined from the coroutine's context using + `ex::stop_source_of_t` and defaults to `ex::inplace_stop_source`. + Linking the stop source can be delayed until the first stop + token is requested or omitted entirely if `stop_possible()` + returns `false` or if the stop token type of the coroutine's + receiver matches that of `ex::stop_source_of_t`. + +For any other environment query the context `C` of `task` can +be used. The coroutine can maintain an instance of type `C`. In many +cases queries from the environment of the coroutine's `receiver` need +to be forwarded. Let `env` be `get_env(receiver)` and `Env` +be the type of `env`. `C` gets optionally constructed +with access to the environment: + + 1. If `C::env_type` is a valid type the coroutine + state will contain an object `own_env` of this type which is + constructed with `env`. The object `own_env` will live at least + as long as the `C` object maintained and `C` is constructed + with a reference to `own_env`, allowing `C` to reference type-erased + representations for query results it needs to forward. + 2. Otherwise, if `C(env)` is valid the `C` object is constructed + with the result of `get_env(receiver)`. Constructing the context + with the receiver's environment provides the opportunity to + store whatever data is needed from the environment to later + respond to queries as well. + 3. Otherwise, `C` is default constructed. This option typically + applies if `C` doesn't need to provide any environment queries. + +Any query which isn't provided by the coroutine but is available +from the context `C` is forwarded. Any other query shouldn't be +part of the overload set. + +For example: + +```c++ +struct context { + int value{}; + int query(get_value_t const&) const noexcept { return this->value; } + context(auto const& env): value(get_value(env)) {} +}; + +int main() { + ex::sync_wait( + ex::write_env( + []->demo::task { + auto sched(co_await ex::read_env(get_scheduler)); + auto value(co_await ex::read_env(get_value)); + std::cout << "value=" << value << "\n"; + // ... + }(), + ex::make_env(get_value, 42) + ) + ); +} +``` + +## Support For Requesting Cancellation/Stopped + +When a coroutine task executes the actual work it may listen to +a stop token to recognize that it got canceled. Once it recognizes +that its work should be stopped it should also complete with +`set_stopped(rcvr)`. There is no special syntax needed as that is the +result of using `just_stopped()`: + +```c++ +co_await ex::just_stopped(); +``` + +The sender `just_stopped()` completes with `set_stopped()` causing +the coroutine to be canceled. Any other sender completing with +`set_stopped()` can also be used. + +## Error Reporting + +The sender/receiver approach to error reporting is for operations +to complete with a call to `set_error(rcvr, err)` for some receiver +object `rcvr` and an error value `err`. The details of the completions +are used by algorithms to decide how to proceed. For example, if +any of the senders of `when_all(sndr...)` fails with a `set_error_t` +completion the other senders are stopped and the overall operation +fails itself forwarding the first error. Thus, it should be possible +for coroutines to complete with a `set_error_t` completion. Using a +`set_value_t` completion using an error value isn't quite the same +as these are not detected as errors by algorithms. + +The error reporting used for +[`unifex`](https://github.com/facebookexperimental/libunifex) and +[stdexec](https://github.com/NVIDIA/stdexec) is to turn an exception +escaping from the coroutine into a `set_error_t(std::exception_ptr)` +completion: when `unhandled_exception()` is called on the promise +type the coroutine is suspended and the function can just call +`set_value(r, std::get_current_exception())`. There are a few +limitations with this approach: + +1. The only supported error completion is `set_error_t(std::exception_ptr)`. + While the thrown exception can represent any error type and + `set_error_t` completions from `co_await`ed operations resulting + in the corresponding error being thrown it is better if the + other error types can be reported, too. +2. To report an error an exception needs to be thrown. In some + environments it is preferred to not throw exception or exceptions + may even be entirely banned or disabled which means that there + isn't a way to report errors from coroutines unless a different + mechanism is provided. +3. To extract the actual error information from `std::exception_ptr` + the exception has to be rethrown. +4. The completion signatures for `task` necessarily contain + `set_error_t(std::exception_ptr)` which is problematic when + exceptions are unavailable: `std::exception_ptr` may also be + unavailable. Also, without exception as it is impossible to + decode the error. It can be desirable to have coroutine which + don't declare such a completion signature. + +Before going into details on how errors can be reported it is +necessary to provide a way for `task` to control the error +completion signatures. Similar to the return type the error types +cannot be deduced from the coroutine body. Instead, they can be +declared using the context type `C`: + +- If present, `typename C::error_signatures` is used to declare the + error types. This type needs to be a specialization of + `completion_signatures` listing the valid `set_error_t` + completions. +- If this nested type is not present, + `completion_signatures` is + used as a default. + +The name can be adjusted and it would be possible to use a different +type list template and listing the error types. The basic idea would +remain the same, i.e., the possible error types are declared via the +context type. + +Reporting an error by having an exception escape the coroutine is +still possible but it doesn't necessarily result in a `set_error_t`: +If an exception escapes the coroutine and `set_error_t(std::exception_ptr)` +isn't one of the supported the `set_error_t` completions, +`std::terminate()` is called. If an error is explicitly reported +somehow, e.g., using one of the approaches described below, +and the error type isn't supported by the context's `error_signatures`, the program is ill-formed. + +The discussion below assumes the use of the class template `with_error` +to indicate that the coroutine completed with an error. It can be as +simple as + +```c++ +template struct with_error{ E error; }; +``` + +The name can be different although it shouldn't collide with already +use names (like `error_code` or `upon_error`). Also, in some cases +there isn't really a need to wrap the error into a recognizable +class template. Using a marker type probably helps with readability +and avoiding ambiguities in other cases. + +Besides exceptions there are three possible ways how a coroutine +can be exited: + +1. The coroutine is exited when using `co_return`, optionally + with an argument. Flowing off the end of a coroutine is equivalent + to explicitly using `co_return;` instead of flowing off. It + would be possible to turn the use of + + ```c++ + co_return with_error{err}; + ``` + + into a `set_error(std::move(rcvr), err)` completion. + + One restriction with this approach is that for a + `task` the body can't contain `co_return with_error{e};`: the + `void` result requires that the promise type contains a function + `return_void()` and if that is present it isn't possible to + also have a `return_value(T)`. + +2. When a coroutine uses `co_await a;` the coroutine is in a suspended + state when `await_suspend(...)` of some awaiter is entered. + While the coroutine is suspended it can be safely destroyed. It + is possible to complete the coroutine in that state and have the + coroutine be cleaned up. This approach is used when the awaited + operation completes with `set_stopped()`. It is possible to + call `set_error(std::move(rcvr), err)` for some receiver `rcvr` + and error `err` obtained via the awaitable `a`. Thus, using + + ```c++ + co_await with_error{err}; + ``` + + could complete with `set_error(std::move(rcvr), err)`. + + Using the same notation for awaiting outstanding operations and + returning results from a coroutine is, however, somewhat + surprising. The name of the awaiter may need to become more + explicit like `exist_coroutine_with_error` if this approach should + be supported. + +3. When a coroutine uses `co_yield v;` the promise member + `yield_value(T)` is called which can return an awaiter `a`. + When `a`'s `await_suspend()` is called, the coroutine is suspended + and the operation can complete accordingly. Thus, using + + ```c++ + co_yield with_error{err}; + ``` + + could complete with `set_error(std::move(rcvr), err)`. Using + `co_yield` for the purpose of returning from a coroutine with + a specific result seems more expected than using `co_await`. + +There are technically viable options for returning an error from a +coroutine without requiring exceptions. Whether any of them is +considered suitable from a readability point of view is a separate +question. + +One concern which was raised with just not resuming the coroutine +is that the time of destruction of variables used by the coroutine +is different. The promise object can be destroyed before completing +which might address the concern. + +Using `co_await` or `co_yield` to propagate error results out of +the coroutine has a possibly interesting variation: in both of these +case the error result may be conditionally produced, i.e., it is +possible to complete with an error sometimes and to produce a value +at other times. That could allow a pattern (using `co_yield` for the +potential error return): + +```c++ +auto value = co_yield when_error(co_await into_expected(sender)); +``` + +The subexpression `into_expected(sender)` could turn the `set_value_t` +and `set_error_t` into a suitable `std::expected>` +always reported using a `set_value_t` completion (so the `co_await` +doesn't throw). The corresponding `std::expected` becomes the result +of the `co_await`. Using `co_yield` with `when_error(exp)` where `exp` +is an expected can then either produce `exp.value()` as the result +of the `co_yield` expression or it can result in the coroutine +completing with the error from `exp.error()`. Using this approach +produces a fairly compact approach to propagating the error retaining +the type and without using exceptions. + +## Avoiding Stack Overflow + +It is easy to use a coroutine to accidentally create a stack overflow +because loops don't really execute like loops. For example, a +coroutine like this can easily result in a stack overflow: + +```c++ +ex::sync_wait(ex::write_env( + []() -> ex::task { + for (int i{}; i < 1000000; ++i) + co_await ex::just(i); + }(), + ex::make_env(ex::get_scheduler, ex::inline_scheduler{}) +)); +``` + +The reason this innocent looking code creates a stack overflow is +that the use of `co_await` results in some function calls to suspend +the coroutine and then further function calls to resume the coroutine +(for a proper explanation see, e.g., Lewis Baker's +[Understanding Symmetric Transfer](https://lewissbaker.github.io/2020/05/11/understanding_symmetric_transfer)). +As a result, the stack grows with each iteration of the loop until +it eventually overflows. + +With senders it is also not possible to use symmetric transfer to +combat the problem: to achieve the full generality and composing +senders, there are still multiple function calls used, e.g., when +producing the completion signal. Using `get_completion_behaviour` +from the proposal [A sender query for completion behavior](https://wg21.link/P3206) +could allow detecting senders which complete synchronously. In these +cases the stack overflow could be avoided relying on symmetric transfer. + +When using scheduler affinity the transfer of control via a scheduler +which doesn't complete immediately does avoid the risk of stack +overflow: even when the `co_await`ed work immediately completes as +part of the `await_suspend` call of the created awaiter the coroutine +isn't immediately resumed. Instead, the work is scheduled and the +coroutine is suspended. The thread unwinds its stack until it +reaches its own scheduling and picks up the next entity to execute. + +When using `sync_wait(sndr)` the `run_loop`'s scheduler is used and +it may very well just resume the just suspended coroutine: when +there is scheduling happening as part of scheduler affinity it +doesn't mean that work gets scheduled on a different thread! + +The problem with stack overflows does remain when the work resumes +immediately despite using scheduler affinity. That may be the case +when using an inline scheduler, i.e., a scheduler with an operation +state whose `start()` immediately completes: the scheduled work gets +executed as soon as `set_value(std::move(rcvr))` is called. + +Another potential for stack overflows is when optimizing the behavior +for work which is known to not move to another scheduler: in that case +there isn't really any need to use `continue_on` to get back to the +scheduler where the operation was started! The execution remained +on that scheduler all along. However, not rescheduling the work +means that the stack isn't unwound. + +Since `task` uses scheduler affinity by default, stack overflow +shouldn't be a problem and there is no separate provision required +to combat stack overflow. If the implementation chooses to avoid +rescheduling work it will need to make sure that doing so doesn't +cause any problems, e.g., by rescheduling the work sometimes. When +using an inline scheduler the user will need to be very careful to +not overflow the stack or cause any of the various other problems +with executing immediately. + +## Asynchronous Clean-Up + +Asynchronous clean-up of objects is an important facility. Both +[`unifex`](https://github.com/facebookexperimental/libunifex) and +[stdexec](https://github.com/NVIDIA/stdexec) provide some facilities +for asynchronous clean-up in their respective coroutine task. Based +on the experience the recommendation is to do something different! + +The recommended direction is to support asynchronous resources +independent of a coroutine task. For example the +[async-object](https://wg21.link/p2849) proposal is in this direction. +There is similar work ongoing in the context of +[Folly](https://github.com/facebook/folly). Thus, there is currently +no plan to support asynchronous clean-up as part of the `task` +implementation. Instead, it can be composed based on other facilities. + +# Caveats + +The use of coroutines introduces some issues which are entirely +independent of how specific coroutines are defined. Some of these +were brought up on prior discussions but they aren't anything which +can be solved as part of any particular coroutine implementation. +In particular: + +1. As `co_await`ing the result of an operation (or `co_yield`ing a + value) may suspend a coroutine, there is a potential to + introduce problems when resources which are meant to be held temporarily + are held when suspending. For example, holding a lock to a mutex + while suspending a coroutine can result in a different thread + trying to release the lock when the coroutine is resumed + (scheduler affinity will move the resumed coroutine + to the same scheduler but not to the same thread). +2. Destroying a coroutine is only safe when it is suspended. For + the task implementation that means that it shall only call a + completion handler once the coroutine is suspended. That part + is under the control of the coroutine implementation. However, + there is no way to guard against users explicitly destroying a + coroutine from within its implementation or from another thread + while it is not suspended: that's akin to destroying an object + while it being used. +3. Debugging asynchronous code doesn't work with the normal approaches: + there is generally no suitable stack as work gets resumed from + some run loop which doesn't tell what set up the original work. + To improve on this situation, _async stack traces_ linking + different pieces of outstanding work together can help. At + [CppCon 2024](https://cppcon.org/cppcon-2024-timeline/) Ian + Petersen and Jessica Wong presented how that may work ([watch + the video](https://youtu.be/nHy2cA9ZDbw?si=RDFty43InNoJxJNN)). + Implementations should consider adding corresponding support + and enhance tooling, e.g., debuggers, to pick up on async stack + traces. However, async stack support itself isn't really something + which one coroutine implementation can enable. + +While these issues are important this proposal isn't the right place +to discuss them. Discussion of these issues should be delegated +to suitable proposals wanting to improve this situation in some +form. + +# Questions + +This section lists questions based on the design discussion +above. Each one has a recommendation and a vote is only needed +if there opinions deviating from the recommendation. + +- Result type: expand `as_awaitable(sndr)` to support more than + one `set_value_t(T...)` completion? Recommendation: no. +- Result type: add transformation algorithms like `into_optional`, + `into_expected`? Recommendation: no, different proposals. +- Scheduler affinity: should `task` support scheduler affinity? + Recommendation: yes. +- Scheduler affinity: require a `get_scheduler()` query on the + receiver's environments? Recommendation: yes. +- Scheduler affinity: add a definition for `inline_scheduler` + (using whatever name) to support disabling scheduler affinity? + Recommendation: yes. +- Allocator support: should `task` support allocators (default + `std::allocator`)? Recommendation: yes. +- Error reporting: should it be possible to return an error + without throwing an exception? Recommendation: yes. +- Error reporting: how should errors be reported? Recommendation: + using `co_yield with_error(e). +- Error reporting: should `co_yield when_error(expected)` be + supported? Recommendation: yes (although weakly). +- Clean-up: should asynchronous clean-up be supported? Recommendation: + no. + +# Implementation + +An implementation of `task` as proposed in this document is available +from [`beman::task`](https://github.com/bemanproject/task). This +implementation hasn't received much use, yet, as it is fairly new. It +is setup to be buildable and provides some examples as a starting +point for experimentation. + +Coroutine tasks very similar although not identical to the one +proposed are used in multiple projects. In particular, there are +three implementations in wide use: + +- [`Folly::Task`](https://github.com/facebook/folly/blob/main/folly/coro/Task.h) +- [`unifex::Task`](https://github.com/facebookexperimental/libunifex/blob/main/include/unifex/task.hpp) +- [`stdexec::task`](https://github.com/NVIDIA/stdexec/blob/main/include/exec/task.hpp) + +The first one +([`Folly::Task`](https://github.com/facebook/folly/blob/main/folly/coro/Task.h)) +isn't based on sender/receiver. Usage experience from all three +have influenced the design of `task`. + +# Acknowledgments + +We would like to thank Ian Petersen, Alexey Spiridonov, and Lee +Howes for comments on drafts of this proposal and general guidance. + +# Proposed Wording + +In [version.syn], add a row + +:::add + +```c++ +#define __cpp_lib_task YYYMML // @_also in_@ +``` + +::: + +In [except.terminate] paragraph 1 add this bullet at the end of Note 1: + +:::note +These situations are + +- ... + +- when unhandled_stopped is called on a with_awaitable_senders object ([exec.with.awaitable.senders]) whose continuation is not a handle to a coroutine whose promise type has an unhandled_stopped member function[.]{.rm}[, or]{.add} + +:::add + +- when an exception is thrown from a coroutine `std::execution::task` + which doesn't support a `std::execution::set_error_t(std::execption_ptr)` completion. + +::: + +::: + +In [execution.syn]{.sref} add declarations for the new classes: + +```c++ +namespace std::execution { + ... + // [exec.with.awaitable.senders] + template<@_class-type_@ Promise> + struct with_awaitable_senders; + +``` + +:::add + +```c++ + // @[exec.affine.on]{.sref}@ + struct affine_on_t { @_unspecified_@ }; + inline constexpr affine_on_t affine_on; + + // @[exec.inline.scheduler]{.sref}@ + class inline_scheduler; + + // @[exec.task.scheduler]{.sref}@ + class task_scheduler; + + // @[exec.task]{.sref}@ + template + class task; +``` + +::: + +```c++ +} +``` + +Add new subsections for the different classes at the end of [exec]{.sref}: + +::: draftnote +Everything below is text meant to go to the end of the [exec]{.sref} +section without any color highlight of what it being added. +::: + +## `execution::affine_on` [exec.affine.on] + +[1]{.pnum} `affine_on` adapts a sender into one that completes on + the specified scheduler. If the algorithm determines that the + adapted sender already completes on the correct scheduler it + can avoid any scheduling operation. + +[2]{.pnum} The name `affine_on` denotes a pipeable sender adaptor + object. For subexpressions `sch` and `sndr`, if `decltype((sch))` + does not satisfy `scheduler`, or `decltype((sndr))` does not + satisfy `sender`, `affine_on(sndr, sch)` is ill-formed. + +[3]{.pnum} Otherwise, the expression `affine_on(sndr, sch)` is + expression-equivalent to: + +```c++ + transform_sender(@_get-domain-early_@(sndr), @_make-sender_@(affine_on, sch, sndr)) +``` + +except that `sndr` is evaluated only once. + +[4]{.pnum} The exposition-only class template `@_impls-for_@` is specialized + for `affine_on_t` as follows: + +```c++ + namespace std::execution { + template <> + struct @_impls-for_@: @_default-impls_@ { + static constexpr auto @_get-attrs_@ = + [](const auto& data, const auto& child) noexcept -> decltype(auto) { + return @_JOIN-ENV_@(@_SCHED-ATTRS_@(data), @_FWD-ENV_@(get_env(child))); + }; + }; + } +``` + +[5]{.pnum} Let `out_sndr` be a subexpression denoting a sender returned + from `affine_on(sndr, sch)` or one equal to such, and let + `OutSndr` be the type `decltype((out_sndr))`. Let `out_rcvr` + be a subexpression denoting a receiver that has an environment + of type `Env` such that `sender_in` is `true`. + Let `op` be an lvalue referring to the operation state that + results from connecting `out_sndr` to `out_rcvr`. Calling + `start(op)` will start `sndr` on the current execution agent + and execute completion operations on `out_rcvr` on an execution + agent of the execution resource associated with `sch`. If the + current execution resource is the same as the execution resource + associated with `sch`, the completion operation on `out_rcvr` + may be called before `start(op)` completes. If scheduling onto + `sch` fails, an error completion on `out_rcvr` shall be executed + on an unspecified execution agent. + +## `execution::inline_scheduler` [exec.inline.scheduler] + +```c++ +namespace std::execution { + class inline_scheduler { + class @_inline-sender_@; // exposition only + template + class @_inline-state_@; // exposition only + + public: + using scheduler_concept = scheduler_t; + + constexpr @_inline-sender_@ schedule() noexcept { return {}; } + constexpr bool operator== (const inline_scheduler&) const noexcept = default; + }; +} +``` + +[1]{.pnum} `inline_scheduler` is a class that models `scheduler` +[exec.scheduler]{.sref}. All objects of type `inline_scheduler` are equal. + +[2]{.pnum} `@_inline-sender_@` is an exposition-only type that satisfies +`sender`. The type
+`completion_signatures_of_t<@_inline-sender_@>` +is `completion_signatures`. + +[3]{.pnum} Let `@_sndr_@` be an expression of type `@_inline-sender_@`, let `@_rcvr_@` +be an expression such that `receiver_of` is `true` where +`CS` is `completion_signatures`, then: + +- [3.1]{.pnum} the expression `connect(@_sndr_@, @_rcvr_@)` has + type `@_inline-state_@>` and is potentially-throwing if and + only if + `((void)@_sndr_@, auto(@_rcvr_@))` is potentially-throwing, and +- [3.2]{.pnum} the expression `get_completion_scheduler(get_env(@_sndr_@))` + has type `inline_scheduler` and is potentially-throwing if and only if `get_env(@_sndr_@)` is potentially-throwing. + +[4]{.pnum} Let `@_o_@` be a non-`const` lvalue of type `@_inline-state_@`, and + let `REC(@_o_@)` be a non-`const` lvalue reference to an object of type `Rcvr` + that was initialized with the expression `@_rcvr_@` passed to an invocation + of `connect` that returned `@_o_@`, then: + +- [4.1]{.pnum} the object to which `REC(@_o_@)` refers remains valid for the lifetime + of the object to which `@_o_@` refers, and +- [4.2]{.pnum} the expression `start(@_o_@)` is equivalent to `set_value(std::move(REC(@_o_@)))`. + +## `execution::task_scheduler` [exec.task.scheduler] + +```c++ +namespace std::execution { + class task_scheduler { + class @_sender_@; // exposition only + template + class @_state_@; // exposition only + + public: + using scheduler_concept = scheduler_t; + + template > + requires (!same_as>) + && scheduler + explicit task_scheduler(Sch&& sch, Allocator alloc = {}); + + @_sender_@ schedule(); + + friend bool operator== (const task_scheduler& lhs, const task_scheduler& rhs) + noexcept; + template + requires (!same_as) + && scheduler + friend bool operator== (const task_scheduler& lhs, const Sch& rhs) noexcept; + + private: + shared_ptr @_sch_@_; // @_exposition only_@ + }; +} +``` + +[1]{.pnum} `task_scheduler` is a class that models `scheduler` + [exec.scheduler]{.sref}. + Given on object `s` of type `task_scheduler`, let `@_SCHED_@(s)` be the object owned + by `s.@_sch_@_`. + +```c++ +template > + requires(!same_as>) && scheduler +explicit task_scheduler(Sch&& sch, Allocator alloc = {}); +``` + +[2]{.pnum} _Effects_: Initialize `@_sch_@_` with + `allocate_shared>(alloc, std::forward(sch))`. + +[3]{.pnum} _Recommended practice_: Implementations should avoid the + use of dynamically allocated memory for small scheduler objects. + +[4]{.pnum} _Remarks_: + Any allocations performed by construction of `@_sender_@` or `@_state_@` + objects resulting from calls on `*this` are performed using a copy + of `alloc`. + +```c++ +@_sender_@ schedule(); +``` + +[5]{.pnum} _Effects_: Returns an object of type `@_sender_@` containing + a sender initialized with `schedule(@_SCHED_@(*this))`. + +```c++ +bool operator== (const task_scheduler& lhs, const task_scheduler& rhs) noexcept; +``` + +[6]{.pnum} _Effects_: Equivalent to: `return lhs == @_SCHED_@(rhs);` + +```c++ +template + requires (!same_as) + && scheduler +bool operator== (const task_scheduler& lhs, const Sch& rhs) noexcept; +``` + +[7]{.pnum} _Returns_: `false` if type of `@_SCHED_@(lhs)` is not +`Sch`, otherwise `@_SCHED_@(lhs) == rhs;` + +```c++ +class task_scheduler::@_sender_@ { // @_exposition only_@ +public: + using sender_concept = sender_t; + + template + @_state_@ connect(Rcvr&& rcvr); +}; +``` + +[8]{.pnum} `@_sender_@` is an exposition-only class that models + `sender` [exec.sender]{.sref} and for which + `completion_signatures_of_t<@_sender_@>` denotes: + +```c++ +completion_signatures< + set_value_t(), + set_error_t(error_code), + set_error_t(exception_ptr), + set_stopped_t()> +``` + +[9]{.pnum} Let `sch` be an object of type `task_scheduler` and + let `sndr` be an object of type `@_sender_@` obtained from + `schedule(sch)`. Then + `get_completion_scheduler(get_env(sndr)) == sch` + is `true`. The object `@_SENDER_@(sndr)` is the sender object + contained by `sndr` or an object move constructed from it. + +```c++ +template +@_state_@ connect(Rcvr&& rcvr); +``` + +[10]{.pnum} _Effects_: Let `r` be an object of a type that models +`receiver` and whose completion handlers result in invoking the +corresponding completion handlers of `rcvr` or copy thereof. +Returns an object of type `@_state_@` containing an operation +state object initialized with + `connect(@_SENDER_@(*this), std::move(r))`. + +```c++ +template +class task_scheduler::@_state_@ { // @_exposition only_@ +public: + using operation_state_concept = operation_state_t; + + void start() & noexcept; +}; +``` + +[11]{.pnum} `@_state_@` is an exposition-only class template whose specializations + model `operation_state` [exec.opstate]{.sref}. + +```c++ +void start() & noexcept; +``` + +[12]{.pnum} _Effects_: Equivalent to `start(st)` where `st` is + the operation state object contained by `*this`. + +## `execution::task` [exec.task] + +### `task` Overview [task.overview] + +[1]{.pnum} The `task` class template represents a sender that can +be used as the return type of coroutines. The first template parameter +`T` defines the type value completion datum ([exec.async.ops]{.sref}) +if `T` is not `void`. Otherwise, there are no value completion +datums. Inside coroutines returning `task` the operand of +`co_return` (if any) becomes the argument of `set_value`. The +second template parameter `Environment` is used to customize the +behavior of `task`. + +### Class template task [task.class] + +```c++ +namespace std::execution { + template + class task { + // [task.state] + template + class @_state_@; // @_exposition only_@ + + public: + using sender_concept = sender_t; + using completion_signatures = @_see below_@; + using allocator_type = @_see below_@; + using scheduler_type = @_see below_@; + using stop_source_type = @_see below_@; + using stop_token_type = decltype(declval().get_token()); + using error_types = @_see below_@; + + // [task.promise] + class promise_type; + + task(task&&) noexcept; + ~task(); + + template + @_state_@ connect(Rcvr&& rcvr); + + private: + coroutine_handle @_handle_@; // @_exposition only_@ + }; +} +``` + +[1]{.pnum} `task` models `sender` [exec.snd]{.sref} if `T` is `void`, a reference type, or an cv-unqualified non-array object type and `E` is class type. Otherwise a program that instantiates the definition of `task` is ill-formed. + +[2]{.pnum} The nested types of `task` template specializations +are determined based on the `Environment` parameter: + +- [2.1]{.pnum} `allocator_type` is `Environment::allocator_type` +if that qualified-id is valid and denotes a type, +`allocator` otherwise. +- [2.2]{.pnum} `scheduler_type` is `Environment::scheduler_type` if that qualified-id is valid and denotes a type, `task_scheduler` +otherwise. +- [2.3]{.pnum} `stop_source_type` is `Environment::stop_source_type` +if that qualified-id is valid and denotes a type, `inplace_stop_source` +otherwise. +- [2.4]{.pnum} `error_types` is `Environment::error_types` + if that qualified-id is valid and denotes a type, + `completion_signatures` otherwise. + +[3]{.pnum} A program is ill-formed if + `error_types` is not a + specialization of `completion_signatures` or + `ErrorSigs` contains an element which is not of the form `set_error_t(E)` + for some type `E`. + +[4]{.pnum} The type alias `completion_signatures` + is a specialization of `execution::completion_signatures` with + the template arguments (in unspecified order): + +- [4.1]{.pnum} `set_value_t()` if `T` is `void`, and `set_value_t(T)` otherwise; +- [4.2]{.pnum} template arguments of the specialization of `execution::completion_signatures` denoted by `error_types`; and +- [4.3]{.pnum} `set_stopped_t()`. + +[5]{.pnum} `allocator_type` shall meet the _Cpp17Allocator_ requirements. + +### Task Members [task.members] + +```c++ +task(task&& other) noexcept; +``` + +[1]{.pnum} _Effects:_ Initializes `@_handle_@` with `exchange(other.@_handle_@, {})`. + +```c++ +~task(); +``` + +[2]{.pnum} _Effects:_ Equivalent to: + +```c++ + if (@_handle_@) + @_handle_@.destroy(); +``` + +```c++ +template +@_state_@ connect(R&& recv); +``` + +[3]{.pnum} _Preconditions:_ `bool(@_handle_@)` is `true`. + +[4]{.pnum} _Effects:_ Equivalent to: `return @_state_@(exchange(@_handle_@, {}), std::forward(recv));` + +### Class template task::state [task.state] + +```c++ +namespace std::execution { + template + template + class task::@_state_@ { // @_exposition only_@ + public: + using operation_state_concept = operation_state_t; + + template + @_state_@(coroutine_handle h, Rcvr&& rr); + ~@_state_@(); + void start() & noexcept; + +private: + using @_own-env-t_@ = @_see below_@; // @_exposition only_@ + coroutine_handle @_handle_@; // @_exposition only_@ + remove_cvref_t @_rcvr_@; // @_exposition only_@ + @_own-env-t_@ @_own-env_@; // @_exposition only_@ + Environment @_environment_@; // @_exposition only_@ + }; +} +``` + +[1]{.pnum} The type `@_own-env-t_@` is + `Environment::template env_type()))>` + if that qualified-id is valid and denotes a type, + `env<>` otherwise. + +```c++ +template +@_state_@(coroutine_handle h, Rcvr&& rr); +``` + +[2]{.pnum} _Effects:_ Initializes + +- [2.1]{.pnum} `@_handle_@` with `std::move(h)`; +- [2.2]{.pnum} `@_rcvr_@` with `std::forward(rr)`; +- [2.3]{.pnum} `@_own-env_@` + with `@_own-env-t_@(get_env(@_rcvr_@))` if that expression + is valid and `@_own-env-t_@()` otherwise; + If neither of these expressions is valid, the program is ill-formed. +- [2.4]{.pnum} `@_environment_@` with `Environment(@_own-env_@)` if that expression is + valid, otherwise `Environment(get_env(@_rcvr_@))` + if this expression is valid, otherwise `Environment()`. + If neither of these expressions is valid, the program is ill-formed. + +```c++ +~@_state_@(); +``` + +[3]{.pnum} _Effects:_ Equivalent to: + +```c++ + if (@_handle_@) + @_handle_@.destroy(); +``` + +```c++ +void start() & noexcept; +``` + +[4]{.pnum} _Effects:_ Let `prom` be the object `@_handle_@.promise()`. +Associates `@_STATE_@(prom)`, `@_RCVR_@(prom)`, and `@_SCHED_@(prom)` with `*this` +as follows: + +- [4.1]{.pnum} `@_STATE_@(prom)` is `*this`. +- [4.2]{.pnum} `@_RCVR_@(prom)` is `@_rcvr_@`. +- [4.3]{.pnum} `@_SCHED_@(prom)` is the object initialized with + `scheduler_type(get_scheduler(get_env(@_rcvr_@)))` if that expression is + valid and `scheduler_type()` otherwise. If neither of these + expressions is valid, the program is ill-formed. + +Let `st` be `get_stop_token(get_env(@_rcvr_@))`. Initializes `prom.@_token_@` + and `prom.@_source_@` such that + +- [4.4]{.pnum} `prom.@_token_@.stop_requested()` returns `st.stop_requested()`; +- [4.5]{.pnum} `prom.@_token_@.stop_possible()` returns `st.stop_possible()`; and +- [4.6]{.pnum} for types `Fn` and `Init` such that both `invocable` and `constructible_from` are modeled, `stop_token_type::callback_type` models `@_stoppable-callback-for_@`. + +After that invokes `@_handle_@.resume()`. + +### Class task::promise_type [task.promise] + +```c++ +namespace std::execution { + template + struct with_error { + using type = remove_cvref_t; + type error; + }; + template + with_error(E) -> with_error; + + template + struct change_coroutine_scheduler { + using type = remove_cvref_t; + type scheduler; + }; + template + change_coroutine_scheduler(Sch) -> change_coroutine_scheduler; + + template + class task::promise_type { + public: + template + promise_type(const Args&... args); + + task get_return_object() noexcept; + + auto initial_suspend() noexcept; + auto final_suspend() noexcept; + + void uncaught_exception(); + coroutine_handle<> unhandled_stopped(); + + void return_void(); // present only if is_void_v is true; + template + void return_value(V&& value); // present only if is_void_v is false; + + template + @_unspecified_@ yield_value(with_error error); + + template + auto await_transform(A&& a); + template + auto await_transform(change_coroutine_scheduler sch); + + @_unspecified_@ get_env() const noexcept; + + template + void* operator new(size_t size, Args&&... args); + + void operator delete(void* pointer, size_t size) noexcept; + + private: + using @_error-variant_@ = @_see below_@; // @_exposition only_@ + + allocator_type @_alloc_@; // @_exposition only_@ + stop_source_type @_source_@; // @_exposition only_@ + stop_token_type @_token_@; // @_exposition only_@ + optional @_result_@; // @_exposition only_@; present only if is_void_v is false; + @_error-variant_@ @_errors_@; // @_exposition only_@ + }; +} +``` + +[1]{.pnum} Let `prom` be an object of `promise_type` and let `tsk` + be the `task` object created by `prom.get_return_object()`. The + description below refers to objects `@_STATE_@(prom)`, + `@_RCVR_@(prom)`, and `@_SCHED_@(prom)` associated with `tsk` + during evaluation of `task::@_state_@::start` for some + receiver `Rcvr` [task.state]{.sref}. + +[2]{.pnum} `@_error-variant_@` is a `variant...>`, with duplicate +types removed, where `E...` are template arguments of the +specialization of `execution::completion_signatures` denoted by +`error_types`. + +```c++ +template +promise_type(const Args&... args); +``` + +[3]{.pnum} _Mandates:_ The first parameter of type `allocator_arg_t` (if any) is not the last parameter. + +[4]{.pnum} _Effects:_ If `Args` contains an element of type + `allocator_arg_t` then `@_alloc_@` is initialized with the + corresponding next element of `args`. Otherwise, `@_alloc_@` + is initialized with `allocator_type()`. + +```c++ +task get_return_object() noexcept; +``` + +[5]{.pnum} _Returns:_ A `task` object whose member `@_handle_@` is + `coroutine_handle::from_promise(*this)`. + +```c++ +auto initial_suspend() noexcept; +``` + +[6]{.pnum} _Returns:_ An awaitable object of unspecified type + ([expr.await]) whose member functions arrange for + +- [6.1]{.pnum} the calling coroutine to be suspended, +- [6.2]{.pnum} the coroutine to be resumed on an execution agent of the execution resource associated with `@_SCHED_@(*this)`. + +```c++ +auto final_suspend() noexcept; +``` + +[7]{.pnum} _Returns:_ An awaitable object of unspecified type + ([expr.await]) whose member functions arrange for the completion + of the asynchronous operation associated with `@_STATE_@(*this)` by invoking: + +- [7.1]{.pnum} + `set_error(std::move(@_RCVR_@(*this)),` `std::move(e))` + if `@_errors_@.index()` is greater than zero and + `e` is the value held by `@_errors_@`, otherwise +- [7.2]{.pnum} + `set_value(std::move(@_RCVR_@(*this)))` if `is_void` is `true`, + and otherwise +- [7.3]{.pnum} + `set_value(std::move(@_RCVR_@(*this)), *@_result_@)`. + +```c++ +template +auto yield_value(with_error err); +``` + +[8]{.pnum} _Mandates_ `std::move(err.error)` is convertible to + exactly one of the `set_error_t` argument types of `error_types`. Let `Cerr` + be that type. + +[9]{.pnum} _Returns:_ An awaitable object of unspecified type + ([expr.await]) whose member functions arrange for the calling + coroutine to be suspended and then completes + the asynchronous operation associated with `@_STATE_@(*this)` by invoking + `set_error(std::move(@_RCVR_@(*this)), Cerr(std::move(err.error)))`. + +```c++ +template +auto await_transform(Sender&& sndr) noexcept; +``` + +[10]{.pnum} _Returns_: If `same_as` is + `true` returns `as_awaitable(std::forward(sndr),` `*this)`; + otherwise returns + `as_awaitable(affine_on(std::forward(sndr), @_SCHED_@(*this)), *this)`. + +```c++ +template +auto await_transform(change_coroutine_scheduler sch) noexcept; +``` + +[11]{.pnum} _Effects:_ Equivalent to: `returns await_transform(just(exchange(@_SCHED_@(*this), scheduler_type(sch.scheduler))), *this);` + +```c++ +void uncaught_exception(); +``` + +[12]{.pnum} _Effects:_ If the signature `set_error_t(exception_ptr)` is + not an element of `error_types`, calls `terminate()` ([except.terminate]{.sref}). Otherwise, stores + `current_exception()` into `@_errors_@`. + +```c++ +coroutine_handle<> unhandled_stopped(); +``` + +[13]{.pnum} _Effects:_ Completes the asynchronous operation + associated with `@_STATE_@(*this)` by invoking + `set_stopped(std::move(@_RCVR_@(*this)))`. + +[14]{.pnum} _Returns:_ `noop_coroutine()`. + +```c++ +@_unspecified_@ get_env() const noexcept; +``` + +[15]{.pnum} _Returns:_ An object `env` + such that queries are forwarded as follows: + +- [15.1]{.pnum} `env.query(get_scheduler)` returns `scheduler_type(@_SCHED_@(*this))`. +- [15.2]{.pnum} `env.query(get_allocator)` returns `@_alloc_@`. +- [15.3]{.pnum} `env.query(get_stop_token)` returns `@_token_@`. +- [15.4]{.pnum} For any other query `q` and arguments `a...` a + call to `env.query(q, a...)` returns + `@_STATE_@(*this).environment.query(q, a...)` if this expression + is well-formed and `forwarding_query(q)` is well-formed and is `true`. Otherwise + `env.query(q, a...)` is ill-formed. + +```c++ +template +void* operator new(size_t size, const Args&... args); +``` + +[16]{.pnum} If there is no parameter with type `allocator_arg_t` + then let `alloc` be `Allocator()`. Let `arg_next` be the + parameter following the first `allocator_arg_t` parameter (if + any) and let `alloc` be `Allocator(arg_next)`. Then `PAlloc` + is `allocator_traits::template rebind_alloc` where + `U` is an unspecified type whose size and alignment are both + `__STDCPP_DEFAULT_NEW_ALIGNMENT__`. + +[17]{.pnum} _Mandates:_ + +- [17.1]{.pnum} The first parameter of type `allocator_arg_t` (if any) is not the last parameter. +- [17.2]{.pnum} `Allocator(arg_next)` is a valid expression if there is a parameter + of type `allocator_arg_t`. +- [17.3]{.pnum} `allocator_traits::pointer` is a pointer type. + +[18]{.pnum} _Effects:_ Initializes an allocator `palloc` of type `PAlloc` + with `alloc`. Uses `palloc` to allocate storage for the smallest + array of `U` sufficient to provide storage for a coroutine state + of size `size`, and unspecified additional state necessary to + ensure that `operator delete` can later deallocate this memory + block with an allocator equal to `palloc`. + +[19]{.pnum} _Returns:_ A pointer to the allocated storage. + +```c++ +void operator delete(void* pointer, size_t size) noexcept; +``` + +[20]{.pnum} _Preconditions:_ `pointer` was returned from an invocation + of the above overload of `operator new` with a size argument + equal to `size`. + +[21]{.pnum} _Effects:_ Deallocates the storage pointed to by `pointer` + using an allocator equal to that used to allocate it. diff --git a/subprojects/task/docs/P3796-task-issues.md b/subprojects/task/docs/P3796-task-issues.md new file mode 100644 index 000000000..df53c939c --- /dev/null +++ b/subprojects/task/docs/P3796-task-issues.md @@ -0,0 +1,1560 @@ +--- +title: Coroutine Task Issues +document: P3796R1 +date: 2025-08-14 +audience: + - Concurrency Working Group (SG1) + - Library Evolution Working Group (LEWG) + - Library Working Group (LWG) +author: + - name: Dietmar Kühl (Bloomberg) + email: +source: + - https://github.com/bemanproject/task/doc/issues.md +toc: true +--- + +After the [task proposal](https://wg21.link/p3552) was voted to be +forwarded to plenary for inclusion into C++26 a number of issues +were brought up. The issues were reported using various means. This +paper describes the issues and proposes potential changes to address +them. + +# Change History + +## R0 Initial Revision + +## R1: added more issues and discussion + +- Added the proposal to support `co_return { ... };` ([link](#consider-supporting-co_return-args)) +- Added a response to [P3801r0](https://wg21.link/P3801r0) "Concerns about the design of std::execution::task" ([link](#response-to-concerns-about-the-design-of-stdexecutiontask)) +- Added a concrete reason why `co_yield with_error(e)` is "clunky" ([link](#response-to-concerns-about-the-design-of-stdexecutiontask)) +- Added a discussion of `change_coroutine_scheduler` requiring the scheduler to be assignable ([link](#co_await-change_coroutine_schedulersched-requires-assignable-scheduler)) +- Added a discussion of adding `operator co_await` ([link](#sender-unaware-coroutines-should-be-able-to-co_await-a-task)) +- Added a discussion of `bulk` vs. `task_scheduler` ([link](#bulk-vs.-task_scheduler)) +- Added discussion of missing rvalue qualification ([link](#missing-rvalue-qualification)) +- Added issue about missing specification for `return_value` and `return_void` ([link](#return_value-and-return_void-have-no-specification)) +- Various minor fixes + +# General + +After LWG voted to forward the [task +proposal](https://wg21.link/p3552r3) +to be forwarded to plenary for inclusion into C++26 some issues +were brought up. The concerns range from accidental omissions (e.g., +`unhandled_stopped` lacking `noexcept`) via wording issues and +performance concerns to some design questions. In particular the +behavior of the algorithm `affine_on` used to implement scheduler +affinity for `task` received some questions which may lead to some +design changes and/or clarifications. This document discusses the +various issues raised and proposes ways to address some of them. + +One statement from the plenary was that `task` is the obvious name +for a coroutine task and we should get it right. There are certainly +improvements which can be applied. Although I'm not aware of anything +concrete beyond the raised issues there is probably potential for +improvements. If the primary concern is that there may be better +task interfaces in the future and a better task should get the name +`task`, it seems reasonable to rename the component. The original +name used for the component was `lazy` and feedback from Hagenberg +was to rename it `task`. In principle, any name could work, e.g., +`affine_task`. It seems reasonable to keep `task` in the name somehow +if `task` is to be renamed. + +Specifically for `task` it is worth pointing out that multiple +coroutine task components can coexist. Due to the design of coroutines +and sender/receiver different coroutine tasks can interoperate. + +# The Concerns + +Some concerns were posted to the LWG chat on Mattermost (they were +originally raised on a Discord chat where multiple of the authors +of sender/receiver components coordinate the work). Other concerns +were raised separately (and the phrasing isn't exactly that of the +originally, e.g., because it was stated in a different language). I +became aware of all but one of these concerns after the poll by LWG +to forward the proposal for inclusion into C++26. The rest of this +section discusses these concerns: + +## `affine_on` Concerns + +This section covers different concerns around the specification, +or rather lack thereof, of `affine_on`. The algorithm `affine_on` +is at the core of the scheduler affinity implementation: this +algorithm is used to establish the invariant that a `task` executes +on the currently installed scheduler. Originally, the `task` proposal +used `continue_on` in the specification but during the SG1 discussion +it was suggested that a differently named algorithm is used. The +original idea was that `affine_on(@_sndr_@, @_sch_@)` behaves like +`continues_on(@_sndr_@, @_sch_@)` but it is customized to avoid +actual scheduling when it knows that `@_sndr_@` completes on +`@_sch_@`. When further exploring the direction of using a different +algorithm than `continues_on` some additional potential for changed +semantics emerged (see below). + +The name `affine_on` was _not_ discussed by SG1. The +direction was "come up with a name". The current name just concentrates +on the primary objective of implementing scheduler affinity for +`task`. It can certainly use a different name. For example the +algorithm could be called `continues_inline_or_on` or +`affine_continues_on`. + +To some extend `affine_on`'s specification is deliberately vague +because currently the `execution` specification is lacking some +tools which would allow omission of scheduling, e.g., for `just` +which completes immediately with a result. While users can't +necessarily tap into the customisations, yet, implementation could +use tools like those proposed by [P3206](https://wg21.link/p3206) +"A sender query for completion behavior" using a suitable hidden +name while the proposal isn't adopted. + +### `affine_on` Default Implementation Lacks a Specification + +The wording of [`affine_on`](https://eel.is/c++draft/exec#affine.on) +doesn't have a specification for the default implementation. For +other algorithms the default implementation is specified. To resolve +this concern add a new paragraph in +[[exec.affine.on]](https://eel.is/c++draft/exec#affine.on) to provide +a specification for the default implementation (probably in front of +the current paragraph 5): + +::: add +> [5]{.pnum} Let `sndr` and `env` be subexpressions such that `Sndr` +> is `decltype((sndr))`. If `@_sender-for_@` is +> `false`, then the expression `affine_on.transform_sender(sndr, env)` is +> ill-formed; otherwise, it is equivalent to: +> +> ``` +> auto [_, sch, child] = sndr; +> return transform_sender( +> @_query-with-default_@(get_domain, sch, default_domain()), +> continues_on(std::move(child), std::move(sch))); +> ``` +> +> except that `sch` is only evaluated once. +::: + +The intention for `affine_on` was to all optimization/customization +in a way reducing the necessary scheduling: if the implementation +can determine if a sender completed already on the correct execution +agent it should be allowed to avoid scheduling. That may be achieved +by using `get_completion_scheduler` of the sender, +using (for now implementation specific) queries like those proposed +by +[P3206 "A sender query for completion behavior"](https://wg21.link/P3206), +or some other means. Unfortunately, the specification proposed above +seems to disallow implementation specific techniques to avoid +scheduling. Future revisions of the standard could require some of +the techniques to avoid scheduling assuming the necessary infrastructure +gets standardized. + +### `affine_on` Semantics Are Not Clear + +The wording in +[`affine_on` p5](https://eel.is/c++draft/exec#affine.on-5) says: + +> ... Calling `start(op)` will start `sndr` on the current execution +> agent and execution completion operations on `out_rcvr` on an +> execution agent of the execution resource associated with `sch`. +> If the current execution resource is the same as the execution +> resource associated with `sch`, the completion operation on +> `out_rcvr` may be invoked on the same thread of execution before +> `start(op)` completes. If scheduling onto `sch` fails, an error +> completion on `out_rcvr` shall be executed on an unspecified +> execution agent. + +The sentence "If the current execution resource is the same as the +execution resource associated with `sch` is not clear to which +execution resource is the "current execution resource". It could +be the "current execution agent" that was used to call `start(op)`, +or it could be the execution agent that the predecessor completes +on. + +The intended meaning for "current execution resource" was to refer +to the execution resource of the execution agent that was used to +call `start(op)`. The specification could be clarified by changing +the wording to become: + +> ... Calling `start(op)` will start `sndr` on the current execution +> agent and execution completion operations on `out_rcvr` on an +> execution agent of the execution resource associated with `sch`. +> If the [current]{.rm} execution resource [of the execution agent +> that was used to invoke `start(op)` ]{.add} is the same as the +> execution resource associated with `sch`, the completion operation +> on `out_rcvr` may be called before `start(op)` completes. If +> scheduling onto `sch` fails, an error completion on `out_rcvr` +> shall be executed on an unspecified execution agent. + +It is also not clear what the actual difference in semantics +between `continues_on` and `affine_on` is. The `continues_on` +semantics already requires that the resulting sender completes on +the specified schedulers execution agent. It does not specify that +it must evaluate a `schedule()` (although that is what the default +implementation does), and so in theory it already permits an +implementation/customization to skip the schedule (e.g. if the child +senders completion scheduler was equal to the target scheduler). + +The key semantic that we want here is to specify one of two possible +semantics that differ from `continues_on`: + +1. That the completion of an `affine_on` sender will occur on the + same scheduler that the operation started on. This is a slightly + stronger requirement than that of `continues_on`, in that it + puts a requirement on the caller of `affine_on` to ensure that + the operation is started on the scheduler passed to `affine_on`, + but then also grants permission for the operation to complete + inline if it completes synchronously. +2. That the completion of an `affine_on` sender will either complete + inline on the execution agent that it was started on, or it + will complete asynchronously on an execution agent associated + with the provided scheduler. This is a slightly more permissive + than option 1. in that it permits the caller to start on any + context, but also is no longer able to definitively advertise + a completion context, since it might now complete on one of two + possible contexts (even if in many cases those two contexts + might be the same). This weaker semantic can be used in conjunction + with knowledge by the caller that they will start the operation + on a context associated with the same scheduler passed to + `affine_on` to ensure that the operation will complete on the + given scheduler. + +The description in the paper at the Hagenberg meeting assumed that +`task` uses `continues_on` directly to achieve scheduler affinity. +During the SG1 discussion it was requested that the approach to +scheduler affinity doesn't use `continues_on` directly but rather +uses a different algorithm which can be customized separately. This +is the algorithm now named `affine_on`. The intention was that +`affine_on` can avoid scheduling in more cases than `continues_on`. + +It is worth noting that an implementation can't determine the +execution resource of the execution agent which invoked `start(op)` +directly. If `rcvr` is the receiver used to create `op` it can be +queried for `get_scheduler(get_env(rcvr))` which is intended to +yield this execution resource. However, there is no guarantee that +this is the case [yet?]. + +The intended direction here is to pursue the second possibility +mentioned above. Compared to `continues_on` that would effectively +relax the requirement that `affine_on` completes on `sch` if `sender` +doesn't actually change schedulers and completes inline: if `start(op)` +gets invoked on an execution agents which matches `sch`'s execution +resources the guarantee holds but `affine_on` would be allowed to +complete on the execution agent `start(op)` was invoked on. It is upon +the user to invoke `start(op)` on the correct execution agent. + +Another difference to `continues_on` is that `affine_on` can be +separately customized. + +### `affine_on`'s Shape May Not Be Correct + +The `affine_on` algorithm defined in +[[exec.affine.on]](https://eel.is/c++draft/exec#affine.on) takes two arguments; a +[`sender`](https://eel.is/c++draft/exec.snd.concepts) and a +[`scheduler`](https://eel.is/c++draft/exec.sched). + +As mentioned above, the semantic that we really want for the purpose +in coroutines is that the operation completes on the same execution +context that it started on. This way, we can ensure, by induction, +that the coroutine which starts on the right context, and resumes +on the same context after each suspend-point, will itself complete +on the same context. + +This then also begs the question: Could we just take the scheduler +that the operation will be started on from the +[`get_scheduler`](https://eel.is/c++draft/exec.get.scheduler) query +on the receivers environment and avoid having to explicitly pass +the scheduler as an argument? + +To this end, we should consider potentially simplifying the `affine_on` +algorithm to just take an input sender and to pick up the scheduler +that it will be started on from the receivers environment and promise +to complete on that context. + +For example, the +[`await_transform`](https://eel.is/c++draft/exec#task.promise-10) +function could potentially be changed to return: +`as_awaitable(affine_on(std::forward(sndr)))`. Then we could +define the `affine_on.transform_sender(sndr, env)` expression (which +provides the default implementation) to be equivalent to +`continues_on(sndr, get_scheduler(env))`. + +Such an approach would also require a change to the semantic +requirements of `get_scheduler(get_env(rcvr))` to require that +`start(op)` is called on that context. + +This change isn't strictly necessary, though. The interface of +`affine_on` as is can be used. Making this change does improve the +design. Nothing outside of `task` currently uses `affine_on` +and as it is an algorithm tailored for `task`'s needs it seems +reasonable to make it fit this use case exactly. This change would +also align with the direction that the execution agent used to +invoke `start(op)` matches the execution resource of +`get_scheduler(get_env(rcvr))`. + +### `affine_on` Shouldn't Forward Stop Requests To Scheduling Operations + +The `affine_on` algorithm is used by the `task` coroutine to ensure +that the coroutine always resumes back on its associated scheduler +by applying the `affine_on` algorithm to each awaited value in a +`co_await` expression. + +In cases where the awaited operation completes asynchronously, +resumption of the coroutine will be scheduled using the coroutines +associated scheduler via a +[`schedule`](https://eel.is/c++draft/exec.schedule) operation. + +If that schedule operation completes with `set_value` then the +coroutine successfully resumes on its associated execution context. +However, if it completes with `set_error` or `set_stopped` then resuming +the coroutine on the execution context of the completion is not +going to preserve the invariant that the coroutine is always going +to resume on its associated context. + +For some cases this may not be an issue, but for other cases, +resuming on the right execution context may be important for +correctness, even during exception unwind or due to cancellation. +For example, destructors may require running on a UI thread in order +to release UI resources. Or the associated scheduler may be a strand +(which runs all tasks scheduled to it sequentially) in order to +synchronize access to shared resources used by destructors. + +Thus, if a stop-request has been sent to the coroutine, that +stop-request should be propagated to child operations so that the +child operation it is waiting on can be cancelled if necessary, but +should probably not be propagated to any schedule operation created +by the implicit `affine_on` algorithm as this is needed to complete +successfully in order to ensure the coroutine resumes on its +associated context. + +One option to work around this with the status-quo would be to +define a scheduler adapter that adapted the underlying `schedule()` +operation to prevent passing through stop-requests from the parent +environment (e.g. applying the +[`unstoppable`](https://eel.is/c++draft/exec.unstoppable) adapter). +If failing to reschedule onto the associated context was a fatal +error, you could also apply a `terminate_on_error` adaptor as well. + +Then the user could apply this adapter to the scheduler before +passing it to the `task`. + +For example: + +``` +template +struct infallible_scheduler { + using scheduler_concept = std::execution::scheduler_t; + S scheduler; + auto schedule() { + return unstoppable(terminate_on_error(std::execution::schedule(scheduler))); + } + bool operator==(const infallible_scheduler&) const noexcept = default; +}; +template +infallible_scheduler(S) -> infallible_scheduler; + +std::execution::task> example() { + co_await some_cancellable_op(); +} + +std::execution::task> caller() { + std::execution::scheduler auto sched = co_await std::execution::read_env(get_scheduler); + co_await std::execution::on(infallible_scheduler{sched}, example()); +} +``` + +However, this approach has the downside that this scheduler behavior +now also applies to all other uses of the scheduler - not just the +uses required to ensure the coroutines invariant of always resuming +on the associated context. + +Other ways this could be tackled include: + +- Making it the default behavior of `affine_on` to suppress stop + requests for the scheduling operations. That would mean that + `affine_on` won't delegate to [`continues_on`](). +- Somehow making the behavior a policy decision specified via the + `Environment` template parameter of the `task`. +- Somehow using domain-based customization to allow the coroutine + to customize the behavior of `affine_on`. +- Making the `task::promise_type::await_transform` apply this adapter + to the scheduler passed to `affine_on`. i.e. it calls + `affine_on(std::forward(sndr), infallible_scheduler{SCHED(*sched)})`. + Taking this route would mean that the shape of `affine_on` should + not be changed. + +### `affine_on` Customization For Other Senders + +Assuming the the `affine_on` algorithm semantics are changed to +just require that it completes either inline or on the context of +the receiver environments `get_scheduler` query, then there are +probably some other algorithms that could either make use of +this, or provide customisations for it that short-circuit the need +to schedule unnecessarily. + +For example: + +- `affine_on(just(args...))` could be simplified to `just(args...)` +- `affine_on(on(sch, sndr))` can be simplified to `on(sch, sndr)` + as on already provides `affine_on`-like semantics +- The [`counting_scope::join`](https://eel.is/c++draft/exec.counting.scopes.general) sender currently already provides + `affine_on`-like semantics. + - We could potentially simplify this sender to just complete + inline unless the join-sender is wrapped in `affine_on`, in + which case the resulting `affine_on(scope.join())` sender would + have the semantics that `scope.join()` has today. + - Alternatively, we could just customize `affine_on(scope.join())` + to be equivalent to `scope.join()`. +- Other similar senders like those returned from + `bounded_queue::async_push` and `bounded_queue::async_pop` which + are defined to return a sender that will resume on the original + scheduler. + +The intended use of `affine_on` is to avoid scheduling where the +algorithm already resumes on a suitable execution agent. However, +as the proposal was late it didn't require potential optimizations. +The intend was to leave the specification of the default implementation +vague enough to let implementations avoid scheduling where they +know it isn't needed. Making these a requirement is intended for +future revisions of the standard. + +It is also a bit unclear how algorithm customization is actually +implemented in practice. Algorithms can advertise a domain via the +`get_domain` query which can then be used to transform algorithms: +`transform_sender(dom, sender, env...)` +[[exec.snd.transform]](https://eel.is/c++draft/exec#snd.transform) +uses `dom.transform_sender(sender, env...)` to transform the sender +if this expression is valid (otherwise the transformation from +`default_domain` is used which doesn't transform the sender). One +way to allow special transformations for `affine_on` is to defined +the `get_domain` query for `affine_on` (well, the environment +obtained by `get_env(a)` from an `affine_on` sender `a`) to yield a +custom domain `affine_on_domain` which delegates transformations +of `affine_on(sndr, sch)` the sender `sndr` or the scheduler `sch`: + +Let `affine_on_domain.transform_sender(affsndr, env...)` (where +`affsndr` is the result of `affine_on(sch, sndr)`) be + +- the result of the expression `sndr.affine_on(sch, env...)` if it + is valid, otherwise +- the result of the expression `sch.affine_on(sndr, env...)` if it + is valid, otherwise +- not defined. + +A similar approach would be used for other algorithms which can be +customized. Currently, no algorithm defines the exact ways it can +be customized in an open form and the intended design for customisations +may be different. The above outlines one possible way. + +## Task Operation + +This section groups three concerns relating to the way `task` gets +started and stopped: + +1. `task` shouldn't reschedule upon `start()`. +2. `task`s `co_await`ing `task`s shouldn't reschedule. +3. `task` doesn't support symmetric Transfer. + +### Starting A `task` Should Not Unconditionally Reschedule + +In +[[task.promise] p6](https://eel.is/c++draft/exec#task.promise-6), +the wording for `initial_suspend` says that it unconditionally +suspends and reschedules onto the associated scheduler. The intent +of this wording seems to be that the coroutine ensures execution +initially starts on the associated scheduler by executing a schedule +operation and then resuming the coroutine from the initial suspend +point inside the `set_value` completion handler of the schedule +operation. The effect of this would be that every call to a coroutine +would have to round-trip through the scheduler, which, depending +on the behavior of the schedule operation, might relegate it to the +back of the schedulers queue, greatly increasing latency of the +call. + +The specification of `initial_suspend()` (assuming it is rephrased +to not resume the coroutine immediately) doesn't really say it +unconditionally reschedules. It merely states that an awaiter is +returned which arranges for the coroutine to get resumed on the +correct scheduler. In general, i.e., when the coroutine gets resumed +via `start(os)` on an operation state `os` obtained from `connect(tsk, +rcvr)` it will need to reschedule. However, an implementation can +pass additional context information, e.g., via implementation +specific properties on the receiver's environment: while the +`get_scheduler` query may not provide the necessary guarantee that +the returned scheduler is the scheduler the operation was started +on a different, a non-forwardable query could provide this guarantee. + +A possible alternative is to require that `start(op)`, where `op` +is the result of `connect(sndr, rcvr)`, is invoked on an execution +agent associated with `get_scheduler(get_env(rcvr))`, at least when +`sndr` is a `task<...>`. Such a requirement could be desirable more +general but it would also be part of the general sender/receiver +contract. The current specification in +[[exec.async.ops](https://eel.is/c++draft/exec.async.ops)] doesn't +have a corresponding constraint. `task<...>` is a sender and +where it can be used with standard library algorithms this constraint +would hold. + +A different way to approach the issue is to use a `task`-specific +awaitable when a `task` is `co_await`ed by another `task`. This use +of an awaiter shouldn't be observable but it may be better to +explicitly spell out that `task` has a domain with custom transformation +of the `affine_on` algorithm and `as_awaitable` operation. + +### Resuming After A `task` Should Not Reschedule + +Similar to the previous issue, a `task` needs to resume on the +expected scheduler. The definition of `await_transform` which is used +for a `task` is defined in [[task.promise] p10](https://eel.is/c++draft/exec#task.promise-10) +(the `co_await`ed `task` would be the `sndr` parameter): + +``` +as_awaitable(affine_on(std::forward(sndr), SCHED(*this)), *this) +``` + +As defined there is no additional information about the scheduler +on which the `sndr` completes. Thus, it is likely that a scheduling +operation is needed after a `co_await`ed `task` completes when the +outer `task` resumes, even if the `co_await`ed `task` completed on +the same execution agent. + +As `task` is scheduler affine, it is likely that it completes on +the same scheduler it was started on, i.e., there shouldn't be a +need to reschedule (if the scheduler used by the `task` was changed +using `co_await change_coroutine_scheduler(sch)` it still needs to +be rescheduled). The implementation can provide a query on the state +used to execute the `task` which identifies the scheduler on which +it completed and the `affine_on` implementation can use this +information to decide whether it is necessary to reschedule after +the `task`'s completion. It would be preferable if a corresponding +query were specified by the standard library to have user-defined +senders provide similar information but currently there is no such +query. + +A different approach is to transform the `affine_on(task, sch)` +operation into a `task`-specific implementation which arranges to +always complete on `sch` using `task` specific information. It may +be necessary to explicit specify or, at least, allow that `task` +provides a domain with a custom transformation for `affine_on`. + +### No Support For Symmetric Transfer + +The specification doesn't mention any use of symmetric transfer. +Further, the `task` gets adapted by `affine_on` in `await_transform` +([[task.promise] p10](https://eel.is/c++draft/exec#task.promise-10) +) which produces a different sender than `task` which needs +special treatment to use symmetric transfer. With symmetric transfer +stack overflow can be avoided when operation complete immediately, e.g. + +```c++ +template +task test() { + for (std::size_t i{}; i < 1000000; ++i) { + co_await std::invoke([]()->task> { + co_return; + }); + } +} +``` + +When using a scheduler which actually schedules the work (rather +than immediately completing when a corresponding sender gets started) +there isn't a stack overflow but the scheduling may be slow. With +symmetric transfer the it can be possible to avoid both the expensive +scheduling operation and the stack overflow, at least in some cases. +When the inner `task` actually `co_await`s any work which synchronously +completes, e.g., `co_await just()`, the code could still result in +a stack overflow despite using symmetric transfer. There is a general +issue that stack size needs to be bounded when operations complete +synchronously. The general idea is to use a trampoline scheduler +which bounds stack size and reschedules when the stack size or the +recursion depth becomes too big. + +Except for the presence or absence of stack overflows it shouldn't +be observable whether an implementation invokes the nested coroutine +directly through an awaiter interface or using the default +implementations of `as_awaitable` and `affine_on`. To address the +different scheduling problems (schedule on `start`, schedule on +completion, and symmetric transfer) it may be reasonable to mandate +that `task` customizes `affine_on` and that the result of this +customization also customizes `as_awaitable`. + +## Allocation + +### Unusual Allocator Customization + +The allocator customization mechanism is inconsistent with the +design of `generator` allocator customization: with `generator`, +when you don't specify an allocator in the template arg, then you +can use any `allocator_arg` type. With `task` if no allocator is +specified in the `Environment` the allocator type defaults to +`std::allocator` and using an allocator with an incompatible +type results in an ill-formed program. + +For example: + +```c++ +struct default_env {}; +struct allocator_env { + using allocator_type = std::pmr::polymorphic_allocator<>; +}; + +template +ex::task test(int i, auto&&...) { + co_return co_await ex::just(i); +} +``` + +Depending on how the coroutine is invoked this may fail to compile: + +- `test(17)`: OK - use default allocator +- `test(17, std::allocator_arg, std::allocator())`: OK - allocator is convertible +- `test(17, std::allocator_arg, std::pmr::polymorphic_allocator<>())`: compile-time error +- `test(17, std::allocator_arg, std::pmr::polymorphic_allocator<>())`: OK + +The main motivator for always having an `allocator_type` is it to +support the `get_allocator` query on the receiver's environments +when `co_await`ing another sender. The immediate use of the allocator +is the allocation of the coroutine frame and these two needs can be +considered separate. + +Instead of using the `allocator_type` both for the environment and +the coroutine frame, the coroutine frame could be allocated with +any allocator specified as an argument (i.e., as the argument +following an `std::allocator_arg` argument). The cost of doing so +is that the allocator stored with the coroutine frame would need +to be type-erased which is, however, fairly cheap as only the +`deallocate(ptr, size)` operation needs to be known and certainly +no extra allocation is needed to do so. If no `allocator_type` is +specified in the `Environment` argument to `task`, the +`get_allocator` query would not be available from the environment +of received used with `co_await`. + +### Issue: Flexible Allocator Position + +For `task` the position of an `std::allocator_arg` can be +anywhere in the argument list except it can't be the last argument +(as it needs to be followed by an allocator). For `generator` the +`std::allocator_arg` argument needs to be the first argument. That +is consistent with other uses of `std::allocator_arg`. `task` should +also require the `std::allocator_arg` argument to be the first +argument. + +The reason why `task` deviates from the approach normally taken is +that the consistency with non-coroutines is questionable: the primary +reason why the `std::allocator_arg` is needed in the first position +is that allocation is delegated to `std::uses_allocator` construction. +This approach, however, doesn't apply to coroutines at all: the +coroutine frame is allocated from an `operator new()` overloaded +for the `promise_type`. In the context of coroutines the question +is rather how to make it easy to optionally support allocators. + +Any coroutine which wants to support allocators for the allocation +of the coroutine frame needs to allow that allocators show up on +the parameter list. As coroutines normally have other parameters, +too, requiring that the optional `std::allocator_arg`/allocator +pair of arguments comes first effectively means that a forwarding +function is provided, e.g., for a coroutine taking an `int` parameter +for its work (`Env` is assumed to be a environment type with a +suitable definition of `allocator_type`): + +``` +task work(std::allocator_arg_t, auto, int value) { + co_await just(value); +} +task work(int value) { + return work(std::allocator_arg, std::allocator()); +} +``` + +If the `allocator_arg` argument can be at an arbitrary location of +the parameter list, defining a coroutine with optional allocator +support amounts to adding a suitable trailing list of parameters, +e.g.: + +``` +task work(int value, auto&&...) { + co_await just(value); +} +``` + +Constraining `task` to match the use of `generator` is entirely +possible. It seems more reasonable to rather consider relaxing the +requirement on `generator` in a future revision of the C++ standard. + +### Shadowing The Environment Allocator Is Questionable + +The `get_allocator` query on the environment of the receiver passed to +`co_await`ed senders always returns the allocator determined when +the coroutine frame is created. The allocator provided by the +environment of the receiver the `task` is `connect`ed to is hidden. + +Creating a coroutine (calling the coroutine function) chooses the +allocator for the coroutine frame, either explicitly specified or +implicitly determined. Either way the coroutine frame is allocated +when the function is called. At this point the coroutine isn't +`connect`ed to a receiver, yet. The fundamental idea of the allocator +model is that children of an entity use the same allocator as their +parent unless an allocator is explicitly specified for a child. +The `co_await`ed entities are children of the coroutine and should +share the coroutine's allocator. + +In addition, to forward the allocator from the receiver's environment +in an environment, its static type needs to be convertible to the +coroutine's allocator type: the coroutine's environment types are +determined when the coroutine is created at which point the receiver +isn't known, yet. Whether the allocator from the receiver's environment +can be used with the statically determined allocator type can't be +determined. It may be possible to use a type-erase allocator which +could be created in the operation state when the `task` is `connect`ed. + +On the flip side, the receiver's environment can contain the +configuration from the user of a work-graph which is likely better +informed about the best allocator to use. The allocator from the +receiver's environment could be forwarded if the `task`'s allocator +can be initialized with it, e.g., because the `task` use +`std::pmr::polymorphic_allocator<>`. It isn't clear what should +happen if the receiver's environment has an allocator which can't +be converted to the allocator based `task`'s environment: at least +ignoring a mismatching allocator or producing a compile time error +are options. It is likely possible to come up with a way to configure +the desired behavior using the environment. + +## Stop Token Management + +### A Stop Source Always Needs To Be Created + +The specification of the `promise_type` contains exposition-only +members for a stop source and a stop token. It is expected that in +may situations the upstream stop token will be an `inplace_stop_token` +and this is also the token type exposed downstream. In these cases +the `promise_type` should store neither a stop source nor a stop +token: instead the stop token should be obtained from the upstream +environment. Necessarily storing a stop source and a stop +token would increase the size of the promise type by multiple +pointers. On top of that, to forward the state of an upstream stop +token via a new stop source requires registration/deregistration +of a stop callback which requires dealing with synchronization. + +The expected implementation is, indeed, to get the stop token from +the operation state: when the operation state is created, it is +known whether the upstream stop token is compatible with the +statically determined stop token exposed by `task` to `co_await`ed +operations via the respective receiver's environment. If the type +is compatible there is no need to store anything beyond the receiver +from which a stop token can be obtained using the `get_stop_token` +query when needed. Otherwise, the operation state can store an +optional stop source which gets initialized and connected to the +upstream stop token via a stop callback when the first stop token +is requested. + +The exposition-only members are not meant to imply that a corresponding +object is actually stored or where they are. Instead, they are +merely meant to talk about the corresponding entities where the +behavior of the environment is described. If the current wording +is considered to imply that these entities actually exist or it can +be misunderstood to imply that, the wording may need some massaging +possibly using specification macros to refer to the respective +entities in the operation state. + +### The Wording Implies The Stop Token Is Default Constructible + +Using an exposition-only member for the stop token implies that the +stop token type is default constructible. The stop token types are +generally not default constructible and are, instead, created via +the stop source and always refer to the corresponding stop source. + +The intent here is, indeed, that the stop token type isn't actually +stored at all: the operation state either stores a stop source which +is used to get the stop token or, probably in the comment case, the +stop token is obtained from the upstream receiver's environment by +querying `get_stop_token`. The rewording for the previous concern +should also address this concern. + +## Miscellaneous Concerns + +The remaining concerns aren't as coupled to other concerns and +discussed separately. + +### Task Is Not Actually Lazily Started + +The wording for `task<...>::promise_type::initial_suspend` in +[[task.promise] p6](https://eel.is/c++draft/exec#task.promise-6) +may imply that a `task` is eagerly started: + +> `auto initial_suspend() noexcept;` +> +> _Returns:_ An awaitable object of unspecified type ([expr.await]) +> whose member functions arrange for +> +> - the calling coroutine to be suspended, +> - the coroutine to be resumed on an execution agent of the execution +> resource associated with `SCHED(*this)`. + +In particular the second bullet can be interpreted to mean that the +task gets resumed immediately. That wouldn't actually work because +`SCHED(*this)` only gets initialized when the `task` gets `connect`ed +to a suitable receiver. The intention of the current specification +is to establish the invariant that the coroutine is running on the +correct scheduler when the coroutine is resumed (see discussion of +`affine_on` below). The mechanisms used to achieve that are not +detailed to avoid requiring that it gets scheduled. The formulation +should, at least, be improved to clarify that the coroutine isn't +resumed immediately, possibly changing the text like this: + +> - the coroutine [to be resumed]{.rm}[resuming]{.add} on an execution +> agent of the execution resource associated with `SCHED(*this)` +> [when it gets resumed]{.add}. + +The proposed fix for the issue is to specify that `initial_suspend()` +always returns `suspend_always{}` and require that `start(...)` +calls `handle.resume()` to resume the coroutine on the appropriate +scheduler after `SCHED(*this)` has been initialized. The corresponding +change could be + +Change [[task.promise] p6](https://eel.is/c++draft/exec#task.promise-6): + +> `auto initial_suspend() noexcept;` +> +::: rm +> _Returns:_ An awaitable object of unspecified type ([expr.await]) +> whose member functions arrange for: +> +> - the calling coroutine to be suspended, +> - the coroutine to be resumed on an execution agent of the execution +> resource associated with `SCHED(*this)`. +::: +::: add +> _Returns:_ `suspend_always{}`. +::: + +The suggestion is to ensure that the task gets resumed on the +correct associated context via added requirements on the receiver's +`get_scheduler()` (see below). + +In separate discussions it was suggested to relax the specification +of `initial_suspend()` to allow returning an awaiter which does +semantically what `suspend_always` does but isn't necessary +`suspend_always`. The proposal was to copy the wording of the +`suspend_always` specification +[[coroutine.trivial.awaitables]](https://eel.is/c++draft/coroutine.trivial.awaitables#lib:suspend_always). +This specification just shows the class completion definition. + +### The Coroutine Frame Is Destroyed Too Late + +When the `task` completes from a suspended coroutine without ever +reaching `final_suspend` the coroutine frame lingers until the +operation state object is destroyed. This happens when the `task` +isn't resumed because a `co_await`ed sender completed with +`set_stopped()` or when the `task` completes using `co_yield when_error(e)`. +In these cases the order of destruction of objects may be unexpected: +objects held by the coroutine frame may be destroyed only long after +the respective completion function was called. + +This behavior is an oversight in the specification and not intentional +at all. Instead, there should be a statement that the coroutine +frame is destroyed before any of the completion functions is invoked. +The implication is that the results can't be stored in the promise +type but that isn't a huge constraint as the best place store them +is in the operation state. + +### `task` Has No Default Arguments + +The current specification of `task` doesn't have any default arguments +in its first declaration in [[execution.syn]](https://eel.is/c++draft/execution.syn). The intent was to default the +type `T` to `void` and the environment `E` to `env<>`. That is, this change +should be applied to [[execution.syn]](https://eel.is/c++draft/execution.syn): + +> ``` +> ... +> // [exec.task] +> template ]{.add}@> +> class task; +> ... +> ``` + +It isn't catastrophic if that change isn't made but it seems to +improve usability without any drawbacks. + +### `bulk` vs. `task_scheduler` + +Normally, the scheduler type used by an operation can be deduced +when a sender is `connect`ed to a receiver from the receiver's +environment. The body of a coroutine cannot know about the `receiver` +the `task` sender gets `connect`ed to. The implication is that the +type of the scheduler used by the coroutine needs to be known when +the `task` is created. To still allow custom schedulers used when +`connect`ing, the type-erase scheduler `task_scheduler` is used. +However, that leads to surprises when algorithms are customized +for a scheduler as is, e.g., the case for `bulk` when used with a +`parallel_scheduler`: if `bulk` is `co_await`ed within a coroutine +using `task_scheduler` it will use the default implementation of +`bulk` which sequentially executes the work, even if the `task_scheduler` +was initialized with a `parallel_scheduler` (the exact invocation may +actually be slightly different or need to use `bulk_chunked` or +`bulk_unchunked` but that isn't the point being made): + +``` +struct env { + auto query(ex::get_scheduler_t) const noexcept { return ex::parallel_scheduler(); } +}; +struct work { + auto operator()(std::size_t s){ /*...*/ }; +}; + +ex::sync_wait( + ex::write_env(ex::bulk(ex::just(), 16u, work{}), + env{} +)); +ex::sync_wait(ex::write_env( + []()->ex::task> { co_await ex::bulk(ex::just(), 16u, work{}); }(), + env{} +)); +``` + +The two invocations should probably both execute the work in parallel +but the coroutine version doesn't: it uses the `task_scheduler` +which doesn't have a specialized version of `bulk` to potentially +delegate in a type-erased form to the underlying scheduler. It is +straight forward to move the `write_env` wrapper inside the coroutine +which fixes the problem in this case but this need introduces the +potential for a subtle performance bug. The problem is sadly not +limited to a particular scheduler or a particular algorithm: any +scheduler/algorithm combination which may get specialized can suffer +from the specialized algorithm not being picked up. + +There are a few ways this problem can be addressed (this list of +options is almost certainly incomplete): + +1. Accept the situation as is and advise users to be careful about +customized algorithms like `bulk` when using `task_scheduler`. +2. Extend the interface of `task_scheduler` to deal with a set of +algorithms for which it provides a type-erased interface. The +interface would likely be more constrained and it would use virtual +dispatch at run-time. However, the set of covered algorithms would +necessarily be limited in some form. +3. To avoid the trap, make the use of known algorithms incompatible +with the use of `task_scheduler`, i.e., "customize" these algorithms +for `task_scheduler` such that a compile-time error is produced. + +A user who knows that the main purpose of a coroutine is to executed +an algorithm customized for a certain scheduler can use `task` with an environment `E` specifying exactly that scheduler type. +However, this use may be nested within some sender being `co_await`ed +and users need to be aware that the customization wouldn't be picked +up. Any approach I'm currently aware of will have the problem that +customized versions of an algorithm are not used for algorithms we +are currently unaware of. + +### `unhandled_stopped()` Isn't `noexcept` + +The `unhandled_stopped()` member function of `task::promise_type` +[[task.promise]](https://eel.is/c++draft/exec#task.promise) +is not currently marked as `noexcept`. + +As this method is generally called from the `set_stopped` +completion-handler of a receiver (such as in [[exec.as.awaitable] p4.3](https://eel.is/c++draft/exec.as.awaitable#4.3)) +and is invoked without handler from a `noexcept` function, we should +probably require that this function is marked `noexcept` as well. + +The equivalent method defined as part of the `with_awaitable_senders` +base-class +([[exec.with.awaitable.senders]](https://eel.is/c++draft/exec.with.awaitable.senders#1) +p1) is also marked `noexcept`. + +This change should be applied to [[task.promise]](https://eel.is/c++draft/exec#task.promise): + +> ``` +> ... +> void uncaught_exception(); +> coroutine_handle<> unhandled_stopped()@[ noexcept]{.add}@; +> +> void return_void(); // present only if is_void_v is true; +> ... +> ``` +> +> ... +> +> ``` +> coroutine_handle<> unhandled_stopped()@[ noexcept]{.add}@; +> ``` +> +> [13]{.pnum} _Effects_: Completes the asynchronous operation associated with `STATE(*this)` by invoking `set_stopped(std::move(RCVR(*this)))`. + +### The Environment Design May Be Inefficient + +The environment returned from `get_env(rcvr)` for the receiver used +to `connect` to `co_await`ed sender is described in terms of members +of the `promise_type::@_state_@` in +[[task.promise] p15](https://eel.is/c++draft/exec#task.promise-15). +In particular the `@_state_@` contains a member `@_own-env_@` which +gets initialized via the upstream receiver's environment (if the +corresponding expression is valid). As the environment `@_own-env_@` +is initialized with a temporary, `@_own-env_@` will need to create +a copy of whatever it is holding. + +As the environment exposed to `co_await`ed senders via the +`get_env(rcvr)` is statically determined when the `task` is created, +not when the `task` is `connect`ed to an upstream receiver, the +environment needs to be type erase. Some of the queries (`get_scheduler`, +`get_stop_token`, and `get_allocator`) are known and already receive +treatment by the `task` itself. However, the `task` cannot know about +user-defined properties which may need to be type-erased as well. To +allow the `Environment` to possibly use type-erase properties from the +upstream receiver, an `own-env-t` object is created and a reference to +the object is passed to the `Environment` constructor (if there are +suitable constructors). Thus, the entire use of `own-env-t` is about +enabling storage of whatever is needed to type-erase the result of +user-defined queries. Ideally, this functionality isn't needed and +the `Environment` parameter doesn't specify an `env_type`. If it is +needed the type-erasure will likely need to store some data. + +### No Completion Scheduler + +The concern raised is that `task` doesn't define a `get_env` that +returns an environment with a `get_completion_scheduler` query. +As a result, it will be necessary to reschedule in some situations although +the `task` already completes on the correct scheduler. + +The query `get_completion_scheduler(env)` operates on the sender's +environment, i.e., it would operate on the `task`'s environment. +However, when the `task` is created it has no information about the +scheduler, yet, it is going to use. The entire information the +`task` has is what is passed to the coroutine [factory] function. +The information about any scheduling gets provided when the `task` +is `connect`ed to a receiver: the receiver provides the queries on +where the task is started (`get_scheduler`). The `task` may complete +on this scheduler assuming the scheduler isn't changed (using +`co_await change_coroutine_scheduler(sch)`). + +The only place where a `task` actually knows its completion scheduler +is once it has completed. However, there is no query or environment +defined for completed operation states. Thus, it seems there is no +way where a `get_completion_scheduler` would be useful. A future +evolution of the sender/receiver interface may change that in which +case `task` should be revisited. + +### Awaitable non-`sender`s Are Not Supported + +The overload of `await_transform` described in +[[task.promise]](https://eel.is/c++draft/exec#task.promise-10) +is constrained to require arguments to satisfy the +[`sender`](https://eel.is/c++draft/exec.snd.concepts) concept. +However, this precludes awaiting types that implement the +[`as_awaitable()`](https://eel.is/c++draft/exec.as.awaitable) +customization point but that do not satisfy the +[`sender`](https://eel.is/c++draft/exec.snd.concepts) concept from +being able to be awaited within a `task` coroutine. + +This is inconsistent with the behavior of the [`with_awaitable_senders`]() +base class defined in +[[exec.with.awaitable.senders]](https://eel.is/c++draft/exec.with.awaitable.senders), +which only requires that the awaited value supports the +[`as_awaitable()`](https://eel.is/c++draft/exec.as.awaitable) +operation. + +The rationale for this is that the argument needs to be passed to +the +[`affine_on`](https://eel.is/c++draft/exec#exec.affine.on) +algorithm which currently requires its argument to model +[`sender`](https://eel.is/c++draft/exec.snd.concepts). This requirement in turn is +there because it is unclear how to guarantee scheduler affinity with an awaitable-only +interface without wrapping a coroutine around the awaitable. + +Do we want to consider relaxing this constraint to be consistent with the constraints +on +[[exec.with.awaitable.senders]](https://eel.is/c++draft/exec.with.awaitable.senders)? + +This would require either: + +- relaxing the [`sender`](https://eel.is/c++draft/exec.snd.concepts) + constraint on the `affine_on` algorithm to also allow an argument + that has only an + [`as_awaitable()`](https://eel.is/c++draft/exec.as.awaitable) but + that did not satisfy the + [`sender`](https://eel.is/c++draft/exec.snd.concepts) concept. +- extending the [`sender`](https://eel.is/c++draft/exec.snd.concepts) + concept to match types that provide the `.as_awaitable()` + member-function similar to how it supports types with `operator + co_await()` (see [[exec.connect]](https://eel.is/c++draft/exec.connect)). + +### Should `task::promise_type` Use `with_awaitable_senders`? + +The existing [[exec]](https://eel.is/c++draft/exec) wording added +the +[`with_awaitable_senders`](https://eel.is/c++draft/exec#with.awaitable.senders) +helper class with the intention that it be usable as the base-class +for asynchronous coroutine promise-types to provide the ability to +await senders. It does this by providing the necessary `await_transform` +overload and also the `unhandled_stopped` member-function necessary +to support the `as_awaitable()` adaptor for senders. + +However, the current specification of `task::promise_types` does not +use this facility for a couple of reasons: + +- It needs to apply the `affine_on` adaptor to awaited senders. +- It needs to provide a custom implementation of `unhandled_stopped` + that reschedules onto the original scheduler in the case that it + is different from the current scheduler (e.g. due to + `co_await change_coroutine_scheduler{other}`). + +This raises a couple of questions: + +- Should there also be a `with_affine_awaitable_senders` that + implements the scheduler affinity logic? +- Is `with_awaitable_senders` actually the right design if the first + coroutine type we add to the standard library doesn't end up using it? + +These are valid questions. It seems the `with_awaitable_senders` class +was designed at time when there was no consensus that a `task` coroutine +should be scheduler affine by default. However, these questions don't +really affect the design of `task` and they can be answered in a future +revision of the standard. The `task` specification also uses a few other +tools which could be useful for users who want to implement their own +custom `task`-like coroutine. Such tools should probably be proposed for +inclusion into a future revision of the C++ standard, too. + +However, what is relevant to the `task` specification is that the +wording for `unhandled_stopped` in +[[task.promise]](https://eel.is/c++draft/exec#task.promise) +paragraph 13 doesn't spell out that the `set_stopped` completion +is invoked on the correct scheduler. + +### A Future Coroutine Feature Could Avoid `co_yield` For Errors + +The mechanism to complete with an error without using an exception +uses [`co_yield +with_error(x)`](https://eel.is/c++draft/exec#task.promise-9). This +use may surprise users as `co_yield`ing is normally assumed not to +be a final operation. A future language change may provide a better +way to achieve the objective of reporting errors without using +exceptions. + +Using not, yet, proposed features which may become part of a future +revision of the C++ standard may always provide facilities which +result in a better design for pretty much anything. There is no +current `task` implementation and certainly none which promises ABI +stability. Assuming a corresponding language change gets proposed +reasonably early for the next cycle, implementations can take it into +account for possibly improving the interface without the need to +break ABI. The shape of the envisioned language change is unknown +but most likely it is an extension which hopefully integrates +with the existing design. The functionality to use `co_yield` +would, however, continue to exist until it eventually gets +deprecated and removed (assuming a better alternative emerges). + +### There Is No Hook To Capture/Restore TLS + +This concern is the only one I was aware of for a few weeks prior +to the Sofia meeting. I believe the necessary functionality can be +implemented although it is possibly harder to do than with more +direct support. The concern raised is that existing code accesses +thread local storage (TLS) to store context information. When a +`co_await` actually suspends it is possible that a different task +running on the same thread replaces used TLS data or the task gets +resumed on a different thread of a thread pool. In that case it is +necessary to capture the TLS variables prior to suspending and +restoring them prior to resuming the coroutine. There is currently +no special way to actually do that. + +The sender/receiver approach to propagating context information +isn't using TLS but rather the environments accessed via the receiver. +However, existing software does use TLS and at least for a migration +period corresponding support may be necessary. One way to implement +this functionality is using a scheduler adapter with a customized +`affine_on` algorithm: + +- When the `affine_on` algorithm is started, it captures the relevant + TLS data into the operation state and then starts `affine_on` + for the adapted scheduler with a receiver observing the + completions. +- When the adapted scheduler invokes any of the completion signals + the TLS data is restored before invoke the algorithm's completion + signal. + +Support for optionally capturing some state before starting a child +operation and restoring the captured started before resuming could +be added to the `task`, avoiding the need to use custom scheduling. +Doing so would yield a nicer and easier to use interface. In +environments where TLS is currently used and developers move to an +asynchronous model, failure to capture and restore data in TLS is a +likely source of errors. However, it will be necessary to specify +what data needs to be stored, i.e., the problems can't be automatically +avoided. + +### `return_value` And `return_void` Have No Specification + +The functions `return_value` and `return_void` declared for the +`task<...>::promise_type` in +[[task.promise]](https://eel.is/c++draft/task.promise) (only one +of them is present in a given `promise_type`) are entirely missing +from the specification. To fix this problem the specifications need +to be added: + +::: add +``` +void return_void(); // present only if is_void_v is true; +``` + +[x]{.pnum} _Effects_: does nothing. + +``` +template + void return_value(V&& value); // present only if is_void_v is false; +``` + +[x+1]{.pnum} _Effects_: Equivalent to _result_.emplace(std::forward(value)); + +::: + + +### Consider Supporting co_return { args... }; + +The current [declaration of `return_value`](https://eel.is/c++draft/task.promise) in the +`promise_type` specifies the function without a default type for +the template parameter: + +``` +template +void return_value(V&& value); // present only if is_void is false +``` + +As a result it isn't possible to use aggregate initialization without +mentioning the type. For example, the following code isn't valid: + +``` +struct aggregate { int value{0} }; +[]() -> task> { co_return {}; }; +[]() -> task> { co_return { 42 }; }; +``` + +To better match the normal functions, the template +parameter should be defaulted which would make the code above valid: + +``` +template +void return_value(V&& value); // present only if is_void is false +``` + +There isn't anything broken with the specification but adding the +default type improves usability. If necessary, this change can be +applied in a future revision of the standard. It would be nice to +fix it for C++26. + +### `co_await change_coroutine_scheduler(sched)` Requires Assignable Scheduler + +The specification of `change_coroutine_scheduler(sched)` uses `std::exchange` to +put the scheduler into place (in [[task.promise] p11](https://eel.is/c++draft/exec#task.promise-11)): + +> ``` +> auto await_transform(change_coroutine_scheduler sch) noexcept;` +> ``` +> [11]{.pnum} Effects: Equivalent to: +> ``` +> return await_transform(just(exchange(SCHED(*this), scheduler_type(sch.scheduler))), *this); +> ``` + +The problem is that `std::exchange(x, v)` expects `x` to be assignable from `v` +but there is no requirement for scheduler to be assignable. It would be possible to +specify the operation in a way avoiding the assignment: + +> ``` +> @[`return await_transform(just(exchange(SCHED(*this), scheduler_type(sch.scheduler))), *this);`]{.rm}@ +> @[`auto* s{address_of(SCHED(*this))};`]{.add}@ +> @[`auto rc{std::move(*s)};`]{.add}@ +> @[`s->~scheduler_type();`]{.add}@ +> @[`new(s) scheduler_type(std::move(sch));`]{.add}@ +> @[`return std::move(rc);`]{.add}@ +> ``` + +An alternative is to consider the presence of the assignment to be +an indicator of whether the scheduler can be replaced: the possibility +of changing the scheduler used for scheduler affinity means that +the body of the coroutine may actually complete on a different +scheduler than the scheduler it was originally started on. It can +be determined statically whether `co_await change_coroutine_scheduler(s)` +was used. As a result, when a `task` awaits another `task` it is +potentially necessary to scheduler the continuation for the outer +`task`. This possibility and the corresponding check may have a +cost. However, when the scheduler isn't assignable, it couldn't +be replaced and that feature is statically checkable, i.e., any +cost associated with the potential of the inner `task` changing the +scheduler is avoided. + +It should be possible to relax the requirements for replacing +schedulers in a future revision of the standard, i.e., this issues +doesn't necessarily need to be resolved one way or the other for +C++26. + +### Sender Unaware Coroutines Should Be Able To `co_await` A `task` + +The request here is to add an `operator co_await()` to `task` +which returns an awaiter used to start the `task`. On the surface +doing so should allow any coroutine to `co_await` a `task`. Unfortunately, +here are a few complications: + +1. If the `task` is used with an environment `E` whose +`scheduler_type` can't be default constructed the environment +associated with the promise type `P` from the `std::coroutine_handle

` +passed to `await_suspend` needs to have a `get_env` providing an +environment with a query for `get_scheduler`. The default used +(`task_scheduler`) is not default constructible because by +default `task` should be scheduler affine and to implement +that the scheduler needs to be known. The implication is that the +`operator co_await` can only be used when either the `task` isn't scheduler affine or the `co_await`ing coroutine knows at +least about the `get_scheduler` query, i.e., it isn't entirely +sender agnostic. + +2. When `co_await`ing a sender within a `task` it is +always possible that a sender completes with `set_stopped()`, i.e., +the coroutine gets cancelled. However, to actually implement that, +the `co_await`ing coroutine needs to have a hook which can be invoked +to notify it about this cancellation. When using `as_awaitable(s)` +this hook is to call `unhandled_stopped()` on the promise object. +Thus, a sender agnostic coroutine trying to `co_await` a `st::task` would need to know about this protocol or only support `task` objects for which the `co_await`s cannot result in completing +with `set_stopped()`. + +3. Other senders are not `co_await`able by sender agnostic coroutines. +If a `task` wants to support `co_await`ing senders it needs to provide +an `await_transform` which turns a sender into an awaitable, e.g., using +`as_awaitable`. Why should that be different for `task`? As `task` +is already a coroutine it seems this is a fairly weak argument, though. + +Given these constraints it is unclear whether it is worth adding +an `operator co_await()` to `task`. If a user really wants to +`co_await` a `task`, it should be possible by adapting +the object to provide a `get_scheduler` query (e.g., using +`std::write_env`), to deal with the potential of a `set_stopped()` completion +(e.g., using `upon_stopped`), and then doing something similar +to `as_awaitable` with the result, just without the need to have +an `unhandled_stopped`. As there are various choices of what +schedulers should be provided and how to deal with the `set_stopped()` +completion it seems reasonable to not add support for `operator +co_await()` at this time. + +### Missing Rvalue Qualification + +The nested sender of a `task_scheduler` isn't necessary copyable. +As the operation is type erased, the +task_scheduler::_ts-sender_::connect should be rvalue +qualified, i.e., in [[exec.task.scheduler] +p8](https://eel.is/c++draft/exec#task.scheduler-8) add rvalue +qualification to `connect`: + +``` +namespace execution { + class task_scheduler::@_ts-sender_@ { // @_exposition only_@ + public: + using sender_concept = sender_t; + + template + state connect(R&& rcvr)@[` &&`]{.add}@; + }; +} +``` + +In [[exec.task.scheduler] p10](https://eel.is/c++draft/exec#task.scheduler-10) add +rvalue qualification to `connect`: + +``` +template + state connect(Rcvr&& rcvr)@[` &&`]{.add}@; +``` + +Coroutines aren't copyable. When using objects holding a +`coroutine_handle

` such that the underlying `coroutine_handle

` +gets transferred to a different object, the operation should be +rvalue qualified and not all such operations currently are rvalue +qualified. The `task::connect` function should be rvalue +qualified, i.e., change [[task.class] p1](https://eel.is/c++draft/exec#task.class): + +``` +namespace std::execution { + template + class task { + ... + template + state connect(R&& recv)@[` &&`]{.add}@; + ... + }; +} +``` + +Change [[task.members] p3](https://eel.is/c++draft/exec#task.members-3): + +``` +template +state connect(R&& recv)@[` &&`]{.add}@; +``` + +If an `as_awaitable` member is added to `task` (see [this issue](#starting-a-task-should-not-unconditionally-reschedule)) this member +should also be rvalue qualified. + +# Response to ["Concerns about the design of std::execution::task"](https://wg21.link/P3801r0) + +The paper ["Concerns about the design of std::execution::task"](P3801r0) +raises three major points and three minor points. The three major points +are: + +1. Synchronous completions can result in stack overflow. This problem + was discussed in the proposal ["Add a Coroutine Task + Type"](https://wg21.link/p3552). If a coroutine uses an actually + scheduling scheduler there is no issue with stack overflow + unless an implementation "optimizes" `affine_on` to complete + synchronously in some cases and they don't guard against stack + overflow, e.g., using a "trampoline scheduler". Also, the danger + of stack overflow isn't actually specific to `task` but applies + to any synchronous completion, especially if it happens from a + loop-like construct. In that sense the issue exists without + `task` although using a `task` may make it more likely that + users encounter this issue. + + The concern also mentioned that there is no support for symmetric + transfer between tasks. This point is already discussed above + in section [No Support For Symmetric + Transfer](#no-support-for-symmetric-transfer). + +2. One concern addresses that the life-time of variables local to + coroutines is surprising: there is no requirement to destroy + the coroutine frame before completing the operation. This issue + is discussed in [The Coroutine Frame Is Destroyed Too + Late](#the-coroutine-frame-is-destroyed-too-late) and the fix + is to explicitly specify that the coroutine frame has to be + destroyed before completing the operation. The only implication + is that the result of the operation needs to be stored in the + operation state rather than the promise type which is, however, + a reasonable approach anyway. + +3. It is raised as a concern that that `task` doesn't defined against + capturing reference which may be out of scope before the `task` + is executed. Here is a minimal example demonstrating the problem + (the example in the paper uses `co` instead of `t` and omitted + the `move` but it is clear what is intended): + + task> g() { + auto t = [](const int& v) -> task> { co_return v; }(42); + auto v = co_await std::move(t); + } + + The subtle problem with this code is the argument `42` is turned + into a temporary `int` which is captured by `const int&` in the + coroutine frame. Once the expression completes this temporary + goes out of scope and is destroyed. When `t` gets `co_await`ed + the body of the function accesses an already destroyed object. + It is worth noting that the relevant references can be hidden, + i.e., they don't necessarily show up in the signature of the + coroutine (preventing `const&` parameters isn't a solution). + + This problem and an approach to avoid it is explained at length + in Aaron Jacobs's C++Now 2024 talk ["Coroutines at + Scale"](https://youtu.be/k-A12dpMYHo?si=oZUownmZrbolDfJS). The + proposed solution is to return an object which can only be + `co_await`ed immediately. While having such a coroutines would + certainly be beneficial and it could be the recommended coroutine + for use from within another coroutine, it isn't a viable + replacement for `task` in all contexts: + + 1. It is intended that `task`s can be used as arguments to sender + algorithms, e.g., to `when_all` to execute multiple asynchronous + operations potentially concurrently, e.g.: + + task> do_work(std::string value) { /* work */ co_return; } + task> execute_all() { + co_await when_all( + do_work("arguments 1"), + do_work("arguments 2") + ); + }; + + 2. It is intended that `task`s can be used with `counting_scope` + (although to do so it is necessary to provide an environment + with a scheduler). There is no scoping relationship between + the `counting_scope` and the argument to `spawn`. + + 3. It is intended that `task` is used to encapsulate + the definition of an asynchronous operation into a function + which can be implemented in a separate translation unit and + that corresponding objects are returned from functions. + + While the idea of limiting the scope of `task` was considered + (admittedly that isn't reflected in the proposal paper), I don't + think there is a way to incorporate the proposed safety mechanism + into `task`. It can be incorporated into a different coroutine + type which can be `co_await`ed by `task`, though. + +The three minor points are: + +1. The use of `co_yield with_error(e)` is "clunky". There is no +disagreement here. A concrete reason why `co_yield with_error(e)` +isn't ideal is that `co_yield` cannot be used within a `catch` +block. In a future revision of the C++ standard it may become +possible that `co_return with_error(e)` can be supported (currently +that isn't possible in general because a promise type cannot provide +both `return_value` and `return_void` functions). However, using +`co_return` for both errors and normal returns introduces a small +problem when returning an object of type `with_error` as normal a +result (this is, however, a rather weird case): + + struct error_env { + using error_types = completion_signatures; + }; + []() -> task, error_env> { + co_return with_error(17); // error or success? + } + +2. The use of `co_await scheduler(sched);` does not result in the +coroutine being resumed on `sched`: due to scheduler affinity the +coroutine is resumed on the coroutine's scheduler, not on `sched` +(unless, of course, these two scheduler are identical). To change +a scheduler something else, specifically `co_await change_coroutine_scheduler(sched)`, +needs to be used. The use of `co_await scheduler(sched);` was used +by a sender/receiver library to replace the coroutine's scheudler +and the recommendation from this experience was to not use this +exact approach. The hinted at proposal is to use `co_yield sched;` +to change the scheduler: just because `co_yield` is used for returning +errors using `with_error(e)` doesn't mean it can't be used for other +uses and `co_yield x;` where `x` is a scheduler could change the +scheduler. However, it is quite intentional to use an explicitly +visible type like `change_coroutine_scheduler` to make changing the +scheduler visible. I have no objection to using `co_yield +change_coroutine_scheduler(sched);` instead of using `co_await` if +that is the preference. + +3. Coroutine cancellation is ad-hoc: the +[`std::execution`](https://wg21.link/p2300) proposal introduced the +use of `unhandled_stopped` to deal with cancellation. This use +wasn't part of the [`task` proposal](https://wg21.link/P3552). I +don't think it is reasonable to not have a coroutine `task` because +there could be a nicer language level approach in the future. + +I think I understand the concerns raised. I don't think that any +of them warrants removing the `task` from the standard document. +The argument that we should only standardize perfect design would +actually lead to hardly anything getting standardized because pretty +much everything is imperfect on some level. + +# Conclusion + +There are some parts of the specification which can be misunderstood. +These should be clarified correspondingly. In most of these cases +the change only affects the wording and doesn't affect the design. + +For the issues around `affine_on` there are some potential design +changes. In particular with respect to the exact semantics of +`affine_on` the design was possibly unclear. Likewise, explicitly +specifying that `task` customizes `affine_on` and provides an +`as_awaitable` function is somewhat in design space. The intention +was that doing so would be possible with the current specification +and it just didn't spell out how `co_await` a `task` from a `task` +actually works. + +However, some fixes of the specification are needed. + +# Acknowledgment + +The issue descriptions are largely based on [this +draft](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org) +written by Lewis Baker. Lewis Baker and Tomasz Kamiński contributed +to the discussions towards addressing the issues. diff --git a/subprojects/task/docs/P3941-affinity.md b/subprojects/task/docs/P3941-affinity.md new file mode 100644 index 000000000..7e9d72f3a --- /dev/null +++ b/subprojects/task/docs/P3941-affinity.md @@ -0,0 +1,1401 @@ +--- +title: Scheduler Affinity +document: P3941R4 +date: 2026-03-26 +audience: + - Concurrency Working Group (SG1) + - Library Evolution Working Group (LEWG) + - Library Working Group (LWG) +author: + - name: Dietmar Kühl (Bloomberg) + email: +source: + - https://github.com/bemanproject/task/doc/P3941-affinity.md +toc: true +--- + +

+One important design of `std::execution::task` is that a coroutine +resumes after a `co_await` on the same scheduler as the one it was +executing on prior to the `co_await`. To achieve this, `task` +transforms the awaited object `@_obj_@` using +`affine_on(@_obj_@, @_sched_@)` where `@_sched_@` is the corresponding +scheduler. There were multiple concerns raised against the specification +of `affine_on` and discussed as part of +[P3796R1](https://wg21.link/P3796R1). This proposal is intended +to specifically address the concerns raised relating to `task`'s +scheduler affinity and in particular `affine_on`. The gist of this +proposal is impose constraints on `affine_on` to guarantee it can +meet its objective at run-time. +

+ +# Change History + +## R4 + +- remove the alternative wording using `get_scheduler` +- remove fallback `get_scheduler` for `get_start_scheduler` +- use `get_start_scheduler` instead of `get_scheduler` to refer to + the scheduler an operation got started on (either for queries or + for defining environments) +- use `start_scheduler_type` instead of `scheduler_type` in `task` +- give implementations permission to define `@_sndr_@.affine_on()` + and `@_sndr_@.as_awaitable(@_p_@)` + +## R3 + +- rebase changes on the customization changes [P3826r3](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p3826r3.html) +- use `transform_sender` in `as_awaitable` to locate possible customization of nested + senders in [[exec.as.awaitable](https://wg21.link/exec.as.awaitable#7)] + +## R2 + +- added requirement on `get_scheduler`/`get_start_scheduler` +- fixed typo in the wording for `affine_on::transform_sender` +- fixed various typos in the text + +## R1 + +- added wording + +## R0 + +- initial revision + +# Overview of Changes + +

+There are a few NB comments raised about the way `affine_on` works: +

+
    +
  • [US 232-366](https://github.com/cplusplus/nbballot/issues/941): specify customization of `affine_on` when the scheduler doesn't change.
  • +
  • [US 233-365](https://github.com/cplusplus/nbballot/issues/940): clarify `affine_on` vs. `continues_on`.
  • +
  • [US 234-364](https://github.com/cplusplus/nbballot/issues/939): remove scheduler parameter from `affine_on`.
  • +
  • [US 235-363](https://github.com/cplusplus/nbballot/issues/938): `affine_on` should not forward the stop token to the scheduling operation.
  • +
  • [US 236-362](https://github.com/cplusplus/nbballot/issues/937): specify default implementation of `affine_on`.
  • +
+

+The discussion on `affine_on` revealed some aspects which were not +quite clear previously and taking these into account points towards +a better design than was previously specified: +

+
    +
  1. + To implement scheduler affinity the algorithm needs to know the + scheduler on which it was started itself. The correct scheduler + may actually be hard to determine while building the work graph. + However, this scheduler can be communicated using + `get_scheduler(get_env(@_rcvr_@))` when an algorithm + is `start`ed. This requirement is more general than just + `affine_on` and is introduced by + [P3718R0](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3718r0.html): + with this guarantee in place, `affine_on` only needs one + parameter, i.e., the sender for the work to be executed. + [P3718R0](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3718r0.html) + is, however, discontinued (for unrelated reasons) and adding the guarantee + to get the current scheduler from a receiver query is proposed + here. +
  2. +
  3. + The scheduler `@_sched_@` on which the work needs + to resume has to guarantee that it is possible to resume on the + correct execution agent. The implication is that scheduling work needs + to be infallible, i.e., the completion signatures of + `scheduler(@_sched_@)` cannot contain a + `set_error_t(E)` completion signature. This requirement should + be checked statically. +
  4. +
  5. + The work needs to be resumed on the correct scheduler even when + the work is stopped, i.e., the scheduling operation shall be + `connect`ed to a receiver whose environment's `get_stop_token` + query yields an `unstoppable_token`. In addition, the + scheduling operation shall not have a `set_stopped_t()` completion + signature if the environment's `get_stop_token` query yields + an `unstoppable_token`. This requirement should also be checked + statically. +
  6. +
  7. + When a sender knows that it will complete on the scheduler it + was started on, it should be possible to customize the `affine_on` + algorithm to avoid rescheduling. This customization can be + achieved by `connect`ing to the result of an `affine_on` member + function called on the child sender, if such a member function + is present, when `connect`ing an `affine_on` sender. +
  8. +
+ +

+None of these changes really contradict any earlier design: the +shape and behavior of the `affine_on` algorithm wasn't fully fleshed +out. Tightening the behavior of scheduler affinity and the `affine_on` +algorithm has some implications on some other components: +

+
    +
  1. + If `affine_on` requires an infallible scheduler at least + `inline_scheduler`, `task_scheduler`, and `run_loop::scheduler` + should be infallible (i.e., they always complete successfully + with `set_value()`). `parallel_scheduler` can probably not be + made infallible. +
  2. +
  3. + The scheduling semantics when changing a `task`'s scheduler + using `co_await change_coroutine_scheduler(@_sch_@)` + become somewhat unclear and this functionality should be removed. + Similar semantics are better modeled using + `co_await on(@_sch_@, @_nested-task_@)`. +
  4. +
  5. + The name `affine_on` isn't particular good and wasn't designed. + It may be worth renaming the algorithms to something different. +
  6. +
+ +# Discussion of Changes + +## `affine_on` Shape + +

+The original proposal for `task` used `continues_on` to schedule +the work back on the original scheduler. This algorithm takes the +work to be executed and the scheduler on which to continue as +arguments. When SG1 requested that a similar but different algorithms +is to be used to implement scheduler affinity, `continues_on` was +just replaced by `affine_on` with the same shape but the potential +to get customized differently. +

+

+The scheduler used for affinity is the scheduler communicated via +the `get_scheduler` query on the receiver's environment: the scheduler +argument passed to the `affine_on` algorithm would need to match +the scheduler obtained from `get_scheduler` query. In the context +of the `task` coroutine this scheduler can be obtained via the +promise type but in general it is actually not straight forward to +get hold of this scheduler because the receiver and hence its +associated scheduler is only provided by `connect`. It is much more +reasonable to have `affine_on` only take the work, i.e., a sender, +as argument and determine the scheduler to resume on from the +receiver's +environment in `connect`. +

+

+Thus, instead of using +```c++ +affine_on(@_sndr_@, @_sch_@) +``` +the algorithm is used just with the sender: +```c++ +affine_on(@_sndr_@) +``` +

+

+Note that this change implies that an operation state resulting +from `connect`ing `affine_on` to a receiver `@_rcvr_@` +is `start`ed on the execution agent associated with the scheduler obtained +from `get_scheduler(get_env(@_rcvr_@))`. The same +requirement is also assumed to be met when `start`ing the operation +state resulting from `connect`ing a `task`. While it is possible to +statically detect whether the query is valid and provides a scheduler +it cannot be detected if the scheduler matches the execution agent on which +`start` was called. +[P3718r0](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3718r0.html) +proposed to add this exact requirement to +[[exec.get.scheduler]](https://wg21.link/exec.get.scheduler) which is no moved +to this proposal. +

+

+This change addresses [US 234-364](https://github.com/cplusplus/nbballot/issues/939) ([LWG4331](https://cplusplus.github.io/LWG/issue4331)). +

+ +## Scheduler An Operation Was Started On + +

+The `on` and `affine_on` algorithms both use the scheduler returned +from the `get_scheduler` query on the receiver to determine where +to resume execution after some other work completed. When using +these algorithms it is not always possible to determine which +scheduler an operation gets started on when creating a work graph. +Thus, the algorithms determine the scheduler from context. To +actually do that, it needs to be required that the scheduler is +made accessible when using these algorithms. +[P3718r0](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3718r0.html) +proposed that `get_scheduler` should be required to yield the +scheduler an operation was started on. + +

+ +

+It was pointed out that the meaning for the `get_scheduler` query isn't clear +and it is used for two separate purposes: +

+
    +
  1. The `get_scheduler` query is used to get a scheduler to schedule new work on. +
  2. The `get_scheduler` query is used to get the scheduler an operation was `start`ed on. +
+

+This dual use doesn't necessary align. It would be reasonable to +add a new query, e.g., `get_start_scheduler` which is used to +determine the scheduler an operation was `start`ed on. The requirement +for getting the scheduler an operation was started on would be on +the `get_start_scheduler` query rather than on the `get_scheduler` +query. At the same time the `get_start_scheduler` query could be +defaulted to use the `get_scheduler` query. +

+ +## Infallible Schedulers +

+The objective of `affine_on(@_sndr_@)` is to execute `@_sndr_@` and +to complete on the execution agent on which the operation was +`start`ed. Let `sch` be the scheduler obtained from +`get_scheduler(get_env(@_rcvr_@))` where `@_rcvr_@` is the receiver +used when `connect`ing `affine_on(@_sndr_@)` (the discussion in +this section also applies if the scheduler would be taken as a +parameter, i.e., if the [previous change](#affine_on-shape) isn't +applied this discussion still applies). If `connect`ing the result +of `schedule(@_sch_@)` fails (i.e., `connect(schedule(@_sch_@), +@_rcvr_@)` throws where `@_rcvr_@` is a suitable receiver), `affine_on` +can avoid `start`ing the main work and fail on the execution agent +where it was `start`ed. Otherwise, if it obtained an operation +state `@_os_@` from `connect(scheduler(@_sch_@), @_rcvr_@)`, +`affine_on` would `start` its main work and would `start(@_os_@)` +on the execution agent where the main work completed. If `start(@_os_@)` +is always successful, `affine_on` can achieve its objective. However, +if this scheduling operation fails, i.e., it completes with +`set_error(@_e_@)`, or if it gets cancelled, i.e., it completes +with `set_stopped()`, the execution agent on which the scheduling +operation resumes is unclear and `affine_on` cannot guarantee its +promise. Thus, it seems reasonable to require that a scheduler used +with `affine_on` is infallible, at least when used appropriately +(i.e., when providing a receiver whose associated stop token is an +`unstoppable_token`). + +

+

+The current working draft specifies 4 schedulers: +

+
    +
  1. + [`inline_scheduler`](https://wg21.link/exec.inline.scheduler) which + just completes with `set_value()` when `start()`ed, i.e., this + scheduler is already infallible. +
  2. +
  3. + [`task_scheduler`](https://wg21.link/exec.task.scheduler) is a + type-erased scheduler delegating to another scheduler. If the + underlying scheduler is infallible, the only error case for + `task_scheduler` is potential memory allocation during `connect` + of its `@_ts-sender_@`. If `affine_on` creates an operation state + for the scheduling operation during `connect`, it can guarantee + that any necessary scheduling operation succeeds. Thus, this + scheduler can be made infallible. +
  4. +
  5. + The [`run_loop::@_run-loop-scheduler_@`](https://wg21.link/exec.run.loop) + is used by [`run_loop`](https://wg21.link/exec.run.loop). The + current specification allows the scheduling operation to fail + with `set_error_t(std::exception_ptr)`. This permission allows + an implementation to use [`std::mutex`](https://wg21.link/thread.mutex) + and [`std::condition_variable`](https://wg21.link/thread.condition) + whose operations may throw. It is possible to implement the logic + using atomic operations which can't throw. The `set_stopped()` + completion is only used when the receiver's stop token, i.e. the + result of `get_stop_token(get_env(@_rcvr_@))`, was stopped. This + receiver is controlled by `affine_on`, i.e., it can provide a + [`never_stoptoken`](https://wg21.link/stoptoken.never) and this + scheduler won't complete with `set_stopped()`. If the + [`get_completion_signatures`](https://wg21.link/exec.getcomplsigs) for + the corresponding sender takes the environment into account, this + scheduler can also be made infallible. +
  6. +
  7. + The [`parallel_scheduler`](https://wg21.link/exec.par.scheduler) + provides an interface to a replaceable implementation of a thread + pool. The current interface allows + [`parallel_scheduler`](https://wg21.link/exec.par.scheduler) to + complete with `set_error_t(std::exception_ptr)` as well as with + `set_stopped_t()`. It seems unlikely that this interface can be + constrained to make it infallible. +
  8. +
+

+In general it seems unlikely that all schedulers can be constrained +to be infallible. As a result `affine_on` and, by extension, `task` +won't be usable with all schedulers if `affine_on` insists on using +only infallible schedulers. If there are fallible schedulers, there +aren't any good options for using them with a `task`. Note that +`affine_on` can fail and get cancelled (due to the main work failing +or getting cancelled) but `affine_on` can still guarantee that +execution resumes on the expect execution agent when it uses an +infallible scheduler. +

+

+This change addresses +[US 235-363](https://github.com/cplusplus/nbballot/issues/938) +([LWG4332](https://cplusplus.github.io/LWG/issue4332)). This change +goes beyond the actual issue and clarifies that the scheduling +operation used be `affine_on` needs to be always successful. +

+ +### Require Infallible Schedulers For `affine_on` + +

+If `affine_on` promises in all cases that it resumes on the +original scheduler it can only work with infallible schedulers. +If a users wants to use a fallible scheduler with `affine_on` or +`task` the scheduler will need to be adapted. The adapted scheduler +can define what it means when the underlying scheduler fails. There +are conceptually only two options (the exact details may vary) on how +to deal with a failed scheduling operation: +

+
    +
  1. +The user can transform the scheduling failure into a call to +`std::terminate`. +
  2. +
  3. +The user can consider resuming on an execution agent where the +adapting scheduler can schedule to infallibly (e.g., the execution +agent on which operation completed) but which is different from +execution agent associated with the adapted scheduler to be suitable +to continue running. In that case the scheduling operation would +just succeed without necessarily running on the correct execution +agent. However, there is no indication that scheduling to the adapted +scheduler failed and the scheduler affinity may be impacted in this +failure case. +
  4. +
+ +The standard library doesn't provide a way to adapt schedulers +easily. However, it can certainly be done. + +### Allow Fallible Schedulers For `affine_on` + +

+If the scheduler used with `affine_on` is allowed to fail, `affine_on` +can't guarantee that it completes on the correct scheduler in case of +an error completion. It could be specified that `affine_on` completes +with `set_error(@_rcvr_@, scheduling_error{@_e_@})` when the scheduling +operation completes with `set_error(@_r_@, @_e_@)` to make it detectable +that it didn't complete on the correct scheduler. This situation is +certainly not ideal but, at least, only affects the error completion and +it can be made detectable. +

+

+A use of `affine_on` which always needs to complete on a specific scheduler +is still possible: in that case the user will need to make sure that the +used scheduler is infallible. The main issue here is that there is no +automatic static checking whether that is the case. +

+ +### Considerations On Infallible Schedulers + +In an ideal world, all schedulers would be infallible. It is unclear +if that is achievable. If schedulers need to be allowed to be fallible, +it may be viable to require that all standard library schedulers +are infallible. As outlined above that should be doable for all current +schedulers except, possibly, `parallel_scheduler`. So, the proposed +change is to require schedulers to be infallible when being used with +`affine_on` (and, thus, being used by `task`) and to change as many of +the standard C++ libraries to be infallible as possible. + +If constraining `affine_on` to only infallible schedulers turns out +to be too strong, the constraint can be relaxed in a future revision +of the standard by explicitly opting out of that constraints, e.g., +using an additional argument. For `task` to make use of it, it too +would need an explicit mechanisms to indicate that its `affine_on` +use should opt out of the constraint, e.g., by adding a suitable +`static` member to the environment template argument. + +## `affine_on` Customization + +Senders which don't cause the execution agent to be changed like +`just` or the various queries should be able to customize `affine_on` +to avoid unnecessary scheduling. Sadly, a proposal +([P3206](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3206r0.pdf)) +to standardize properties which could be used to determine how a +sender completes didn't make much progress, yet. An implementation +can make use of similar techniques using an implementation-specific +protocol. If a future standard defines a standard approach to +determine the necessary properties the implementation can pick up +on those. + +The idea is to have `affine_on` define a `transform_sender(s)` +member function which determines what sender should be returned. +By default the argument is returned but if the child sender indicates +that it doesn't actually change the execution agent the function +would return the child sender. There are a number of senders for +which this can be done: + +- `just`, `just_error`, and `just_stopped` +- `read_env` and `write_env` +- `then`, `upon_error`, and `upon_stopped` if the child sender + doesn't change the execution agent + +The proposal is to define a `transform_sender` member which uses +an implementation-specific property to determine that a sender +completes on the same execution agent as the one it was started on. +In addition, it is recommended that this property gets defined by +the various standard library senders where it can make a difference. + +This change addresses +[US 232-366](https://github.com/cplusplus/nbballot/issues/941) +([LWG4329](https://cplusplus.github.io/LWG/issue4329)), although +not in a way allowing application code to plug into this mechanism. +Such an approach can be designed in a future revision of the standard. + +## Removing `change_coroutine_scheduler` + +The current working paper specifies `change_coroutine_scheduler` to change +the scheduler used by the coroutine for scheduler affinity. It turns out that +this use is somewhat problematic in two ways: + +1. Changing the scheduler affects the coroutine until the end of + the coroutine or until `change_coroutine_scheduler` is `co_await`ed + again. It doesn't automatically reset. Thus, local variables + constructed before `change_coroutine_scheduler(s)` was + `co_await`ed were constructed on the original scheduler and are + destroyed on the replaced scheduler. +2. The `task`'s execution may finish on a different than the original + scheduler. To allow symmetric transfer between two `task`s each + `task` needs to complete on the correct scheduler. Thus, the + `task` needs to be prepared to change to the original scheduler + before actually completing. To do so, it is necessary to know + the original scheduler and also to have storage for the state + needed to change to a different scheduler. It can't be statically + detected whether `change_coroutine_scheduler(s)` is `co_await`ed + in the body of a coroutine and, thus, the necessary storage and + checks are needed even for `task`s which don't use + `change_coroutine_scheduler`. + +If there were no way to change the scheduler it would still be possible +to execute using a different scheduler, although not as direct: +instead of using `co_await change_coroutine_scheduler(s)` to change +the scheduler used for affinity to `s` a nested `task` executing on `s` +could be `co_await`ed: + +```c++ +co_await ex::starts_on(s, [](@_parameters_@)->task<@_T_@, @_E_@> { @_logic_@ }(@_arguments_@)); +``` + +Using this approach the use of the scheduler `s` is clearly limited +to the nested coroutine. The scheduler affinity is fully taken care +of by the use of `affine_on` when `co_await`ing work. There is no +need to provide storage or checks needed for the potential of +having a `task` return to the original scheduler if the scheduler +isn't actually changed by a `task`. + +The proposal is remove `change_coroutine_scheduler` and the possibility +of changing the scheduler within a `task`. The alternative to +controlling the scheduler used for affinity from within a `task` +is a bit verbose. This need under the control of the coroutine is +likely relatively rare. Replacing the used scheduler for an existing +`task` by nesting it within `on(s, t)` or `starts_on(s, t)` is +fairly straightforward. + +This functionality was originally included because it is present +for, at least, one of the existing libraries, although in a form +which was recommended against. The existing use changes the scheduler +of a coroutine when `co_await`ing the result of `schedule(s)`; this +exact approach was found to be fragile and surprising and the +recommendation was to provide the functionality more explicit. + +This change is not associated with any national body comment. +However, it is still important to do! It isn't adding any new +functionality but removes a problematic way to achieve something +which can be better achieved differently. If this change is not +made the inherent cost of having the possibility of having +`change_routine_scheduler` can't be removed later without breaking +existing code. + +## `affine_on` Default Implementation + +Using the previous discussion leads to a definition of `affine_on` which +is quite different from effectively just using `continues_on`: + +1. The class `affine_on` + should define a `transform_sender` member function which returns the + child sender if this child sender indicates via an implementation + specific way that it doesn't change the execution agent. It + should be recommended that some of the standard library sender + algorithms (see above) to indicate that they don't change the + execution agent. +2. The `affine_on` algorithm should only allow to get `connect`ed to a + receiver `r` whose scheduler `sched` obtained by + `get_scheduler(get_env(r))` is infallible, i.e., + `get_completion_signatures(schedule(sched), e)` with an environment + `e` where `get_stop_token(e)` yields `never_stop_token` returns + `completion_signatures`. +3. When `affine_on` gets `connect`ed, the scheduling operation state needs + to be created by `connect`ing the scheduler's sender to a suitable receiver to guarantee + that the completion can be scheduled on the execution agent. + The stop token `get_stop_token(get_env(r))` for the receiver + `r` used for this `connect` shall be an `unstoppable_token`. + The child sender also needs to be `connect`ed with a receiver + which will capture the respective result upon completion and + start the scheduling operation. +4. When the result operation state gets `start`ed it `start`s the + operation state from the child operation. +5. Upon completion of the child operation the kind of completion and + the parameters, if any, are stored. If this operation throws, + the storage is set up to be as if `set_error(current_exception)` + were called. Once the parameters are stored, the scheduling + operation is started. +6. Upon completion of the scheduling operation, the appropriate + completion function with the respective arguments is invoked. + +This behavior is similar to `continues_on` but is subtly different +with respect to when the scheduling operation state needs to be +created and that any stop token from the receiver doesn't get +forwarded. In addition `affine_on` is more constrained with respect +to the schedulers it supports and the shape of the algorithm is +different: `affine_on` gets the scheduler to execute on from the +receiver it gets `connect`ed to. + +This change addresses +[US 233-365](https://github.com/cplusplus/nbballot/issues/940) +([LWG4330](https://cplusplus.github.io/LWG/issue4330)) and +[US 236-362](https://github.com/cplusplus/nbballot/issues/937) +([LWG](https://cplusplus.github.io/LWG/issue4344); the proposed +resolution in this issue is incomplete). + +## Name Change + +The name `affine_on` isn't great. It may be worth giving the +algorithm a better name. + +## Use get_start_scheduler Properly + +During the LEWG discussion on 2026-03-24 it was brought up that +`get_start_scheduler` shouldn't fallback to `get_scheduler`. To +make proper use of `get_start_scheduler` without that fallback, +algorithms current using `get_scheduler` also need to use +`get_start_scheduler` and advertise it appropriately: + +- `let*` pass the first operation's `completion_scheduler` + as the `get_start_scheduler` for the second operation +- `starts_on` should advertise `get_start_scheduler` +- `task` should propagate `get_start_scheduler` instead of + `get_scheduler`. Since it names the type, the type is also + renamed `start_scheduler_type`. +- `sync_wait` advertises its scheduler using both `get_scheduler` + and `get_start_scheduler`. + +# Wording Changes + +::: ednote +This wording is relative to [N5032](https://wg21.link/N5032) with +the changes from +[P3826r3](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p3826r3.html) +applied. +::: + +::: ednote + +Add an exposition only concept `@_infallible-scheduler_@` to +[[exec.sched](https://wg21.link/exec.sched)]: + +::: + +... + +[7]{.pnum} A scheduler type's destructor shall not block pending completion of any receivers connected to the sender objects returned from `schedule`. + +::: add +[8]{.pnum} The exposition-only `@_infallible-scheduler_@` concept defines +the requirements of a scheduler type whose `schedule` asynchronous operation +can only complete with `set_value` unless stop can be requested: + +``` +template +concept @_infallible-scheduler_@ = + scheduler && + (same_as, + completion_signatures_of_t())), Env> + > || + (!unstoppable_token> && ( + same_as, + completion_signatures_of_t())), Env> + > || + same_as, + completion_signatures_of_t())), Env> + >) + ) + ); +``` + +::: + + +::: ednote +Add `get_start_scheduler` to the synopsis in [execution.syn] after +`get_scheduler` as follows: +::: + +``` +... +namespace std::execution { + // [exec.queries], queries + struct get_domain_t { @_unspecified_@ }; + struct get_scheduler_t { @_unspecified_@ }; + @[struct]{.add}@ @[get_start_scheduler_t]{.add}@ @@[{ @_unspecified_@ };]{.add}@@ + struct get_delegation_scheduler_t { @_unspecified_@ }; + struct get_forward_progress_guarantee_t { @_unspecified_@ }; + template + struct get_completion_scheduler_t { @_unspecified_@ }; + template + struct get_completion_domain_t { @_unspecified_@ }; + struct get_await_completion_adaptor_t { @_unspecified_@ }; + + inline constexpr get_domain_t get_domain{}; + inline constexpr get_scheduler_t get_scheduler{}; + @[inline]{.add}@ @[constexpr]{.add}@ @[get_start_scheduler_t]{.add}@ @[get_start_scheduler{};]{.add}@ + inline constexpr get_delegation_scheduler_t get_delegation_scheduler{}; + enum class forward_progress_guarantee; + inline constexpr get_forward_progress_guarantee_t get_forward_progress_guarantee{}; + template + constexpr get_completion_scheduler_t get_completion_scheduler{}; + template + constexpr get_completion_domain_t get_completion_domain{}; + inline constexpr get_await_completion_adaptor_t get_await_completion_adaptor{}; + ... +} +``` + +::: ednote +Add a new section after [exec.get.scheduler] as follows: +::: + +:::add + +### execution::get_start_scheduler [exec.get.start.scheduler] + +[1]{.pnum} `get_start_scheduler` asks a queryable object for the scheduler +an operation will be or was started on. + +[2]{.pnum} The name `get_start_scheduler` denotes a query object. For a +subexpression `env`, `get_start_scheduler(env)` is expression-equivalent to +MANDATE-NOTHROW(AS-CONST(env).query(get_start_scheduler)). + +Mandates: If the expression above is well-formed, its type satisfies `scheduler`. + +[3]{.pnum} `forwarding_query(execution::get_start_scheduler)` is a core +constant expression and has value true. + +[4]{.pnum} Given subexpressions `sndr` and `rcvr` such that +`sender_to` is `true` and the +expression `get_start_scheduler(get_env(rcvr))` is well-formed, an operation +state that is the result of calling `connect(sndr, rcvr)` shall, if +it is started, be started on an execution agent associated with the +scheduler `get_start_scheduler(get_env(rcvr))`. +::: + +::: ednote + +Add two paragraphs at the start of [exec.snd.expos] giving +implementations permission to add members `affine_on` and `as_awaitable` +to standard library sender types: + +::: + +::: add + +[?]{.pnum} Given an expression `sndr`, whose type is any sender +type defined in the standard library, it is unspecified whether the +expression `sndr.affine_on()` is well-formed. If that expression +is well-formed, then the evaluation thereof meets the +semantic requirements of the `affine_on` [exec.affine.on] algorithm. + +[?]{.pnum} Given an expression `sndr`, whose type is any sender type +defined in the standard library, and an expression `p`, whose type is a +promise type, it is unspecified whether the expression +`sndr.as_awaitable(p)` is well-formed. If that expression is +well-formed, then the evaluation thereof meets the semantic +requirements of the `as_awaitable` [exec.as.awaitable] algorithm. + +::: + +::: ednote + +Change [[exec.snd.expos](https://wg21.link/exec.snd.expos)] paragraph +8 to have SCHED-ENV use `get_start_scheduler` +instead of `get_scheduler`: + +::: + +[8]{.pnum} SCHED-ENV(sch) is an expression `o2` whose type +satisfies queryable such that `o2.query(@[get_scheduler]{.rm}@@[get_start_scheduler]{.add}@)` +is a prvalue with the same type and value as `sch`, and such that +`o2.query(get_domain)` is expression-equivalent to `sch.query(get_domain)`. + +::: ednote +The specification of `on` [[exec.on](https://wg21.link/exec.on)] +shouldn't use `write_env` as it does, i.e., this change removes +these (not removing them was an oversight in +[P3826](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p3826r3.html)). + +In addition, if `get_start_scheduler` is introduced change how `on` +gets its scheduler in [[exec.on](https://wg21.link/exec.on)], i.e., +change the use from `get_scheduler` to use `get_start_scheduler`: + +::: + +

+... +

+ +

+[8]{.pnum} Otherwise, the expression `on.transform_sender(set_value, out_sndr, env)` has effects equivalent to: +

+ +``` +auto&& [_, data, child] = out_sndr; +if constexpr (scheduler) { + auto orig_sch = + @_call-with-default_@(@[get_scheduler]{.rm}@@[get_start_scheduler]{.add}@, @_not-a-scheduler_@(), env); + + return continues_on( + starts_on(std::forward_like(data), std::forward_like(child)), + std::move(orig_sch)); +} else { + auto& [sch, closure] = data; + auto orig_sch = @_call-with-default_@( + get_completion_scheduler, @_not-a-scheduler_@(), get_env(child), env); + + return continues_on( + std::forward_like(closure)( + continues_on( + std::forward_like(child), + sch)), + orig_sch); +} +``` +

+[9]{.pnum} +Let `out_sndr` be a subexpression denoting a sender returned from +`on(sch, sndr)` or one equal to such, and let `OutSndr` be the type +`decltype((out_sndr))`. Let `out_rcvr` be a subexpression denoting a +receiver that has an environment of type `Env` such that +`sender_in` is `true`. Let `op` be an lvalue referring +to the operation state that results from connecting `out_sndr` with +`out_rcvr`. Calling `start(op)` shall +

    +
  • [9.1]{.pnum} remember the current +scheduler[,]{.rm}[ which is obtained by]{.add} `@[get_scheduler]{.rm}@@[get_start_scheduler]{.add}@(get_env(rcvr))`;
  • +
  • [9.2]{.pnum} start `sndr` on an execution agent belonging to `sch`'s associated +execution resource;
  • +
  • [9.3]{.pnum} +upon `sndr`'s completion, transfer execution back to the execution resource associated with the scheduler remembered in step 1; and
  • +
  • [9.4]{.pnum} +forward `sndr`'s async result to `out_rcvr`.
  • +
+If any scheduling operation fails, an error completion on `out_rcvr` shall be executed on an unspecified execution agent. +

+ +::: ednote + +Change [[exec.sync.wait](https://wg21.link/exec.sync.wait#2)] p2 to also provide +a `get_start_scheduler` query: + +::: + +Let `@_sync-wait-env_@` be the following exposition-only class type: + +``` +namespace std::this_thread { + struct sync-wait-env { + execution::run_loop* loop; // @_exposition only_@ + + auto query(execution::get_scheduler_t) const noexcept { + return loop->get_scheduler(); + } + + @[auto]{.add}@ @[query(execution::get_start_scheduler_t)]{.add}@ @[const]{.add}@ @[noexcept]{.add}@ @[{]{.add}@ + @[return]{.add}@ @[loop->get_scheduler();]{.add}@ + @[}]{.add}@ + + auto query(execution::get_delegation_scheduler_t) const noexcept { + return loop->get_scheduler(); + } + }; +} +``` + +::: ednote + +Change [[exec.as.awaitable](https://wg21.link/exec.as.awaitable#7)] +paragraph 7 such that it tries to locate a customization for +`as_awaitable` on the transformed nested sender. + +::: + +[7]{.pnum} `as_awaitable` is a customization point object. For +subexpressions `expr` and `p` where `p` is an lvalue, `Expr` names +the type `decltype((expr))` and `Promise` names the type +`decay_t, as_awaitable(expr, p)` is expression-equivalent +to, except that the evaluations of `expr` and `p` are indeterminately +sequenced: + +
    +
  • +

    [7.1]{.pnum} +`expr.as_awaitable(p)` if that expression is well-formed. +

    +

    +Mandates: `@_is-awaitable_@` is `true`, where `A` is +the type of the expression above. +

    +
  • +
  • + +[7.?]{.pnum} + +[Otherwise, +`@_adapt-for-await-completion_@(transform_sender(expr, +get_env(p))).as_awaitable(p)` if this expression is well-formed, +`sender_in>` is `true`, and +`@_single-sender-value-type_@>` is well-formed, +except that `p` is only evaluated once.]{.add} + +
  • +
  • +[7.2]{.pnum} Otherwise, `(void(p), expr)` if +`decltype(@_GET-AWAITER_@(expr))` satisfies `@_is-awaiter_@`. +
  • +
  • +

    +[7.3]{.pnum} [Otherwise, `@_sender-awaitable_@{@_adapted-expr_@, p}` if]{.rm} +

    +

    [`@_has-queryable-await-completion-adaptor_@`]{.rm}

    +

    [and]{.rm}

    +

    [`@_awaitable-sender_@`]{.rm}

    +

    +[are both satisfied, where `@_adapted-expr_@` is +`get_await_completion_adaptor(get_env(expr))(expr)`, except that +`expr` is evaluated only once.]{.rm} +

    +
  • +
  • +[7.4]{.pnum} Otherwise, [`@_sender-awaitable_@{expr, p}` if +`@_awaitable-sender_@` is `true`.]{.rm} +[`@_sender-awaitable_@{@_adapt-for-await-completion_@(transform_sender(expr, get_env(p))), p}` if `sender_in>` is `true` and `@_single-sender-value-type_@>` is well-formed], except that `p` is only evaluated once.{.add} + +
  • +
  • [7.5]{.pnum} Otherwise, `(void(p), expr)`. +
  • +
+ +::: add + +[8]{.pnum} `@_adapt-for-await-completion_@(s)` is expression-equivalent +to +
    +
  • `get_await_completion_adaptor(get_env(s))(s)` if that is well-formed, except that `s` is evaluated only once,
  • +
  • otherwise, `s`.
  • +
+ +::: + +::: ednote +Change [exec.affine.on] to use only one parameter, require an +infallible scheduler from the receiver, and add a default implementation +which allows customization of `affine_on` for child senders. +The algorithm `affine_on` should use +`get_start_scheduler` to get the start scheduler: +::: + +[1]{.pnum} +`affine_on` adapts a sender into one that completes on a [specified +scheduler]{.rm}[receiver's scheduler]{.add}. If the algorithm +determines that the adapted sender already completes on the correct +scheduler it can avoid any scheduling operation. + +[2]{.pnum} +The name `affine_on` denotes a pipeable sender adaptor object. For +[a ]{.add} subexpression[s sch and]{.rm} `sndr`, if [`decltype((sch))` +does not satisfy scheduler, or]{.rm} `decltype((sndr))` does not +satisfy sender, affine_on(sndr[, sch]{.rm}) is ill-formed. + +[3]{.pnum} +Otherwise, the expression affine_on(sndr[, sch]{.rm}) +is expression-equivalent to +`@_make-sender_@(affine_on, @[sch]{.rm}@@[env<>()]{.add}@, sndr)`. + +:::{.add} +[?]{.pnum} For a subexpression `sch` whose type models `scheduler`, +let `@_UNSTOPPABLE-SCHEDULER_@(sch)` be an expression `e` whose type +models `scheduler` such that: + +
    +
  • [?.1]{.pnum} `schedule(e)` is expression-equivalent to `unstoppable(schedule(sch))`.
  • +
  • [?.2]{.pnum} For any query object `q` and pack of subexpressions `args...`, `e.query(q, args...)` +is expression-equivalent to `sch.query(q, args...)`.
  • +
  • [?.3]{.pnum} The expression `e == @_UNSTOPPABLE-SCHEDULER_@(other)` +is expression-equivalent to `sch == other`.
  • +
+ +[?]{.pnum} +Let `sndr` and `ev` be subexpressions such that `Sndr` is +`decltype((sndr))`. If sender-for<Sndr, +affine_on_t> is `false`, then the expression +`affine_on.transform_sender(sndr, ev)` is ill-formed; otherwise, +it is equal to: + +``` +auto&[_, _, child] = sndr; +if constexpr (requires{ std::forward_like(child).affine_on(); }) + return std::forward_like(child).affine_on(); +else + return continues_on(std::forward_like(child), @_UNSTOPPABLE-SCHEDULER_@(get_start_scheduler(ev))); +``` + +[?]{.pnum} +_Recommended Practice_: Implementations should provide `affine_on` +member functions for senders that are known to resume on the +scheduler where they were started. Example senders for which that +is the case are `just`, `just_error`, `just_stopped`, `read_env`, +and `write_env`. + +::: + +[5]{.pnum} +Let _out_sndr_ be a subexpression denoting a sender +returned from affine_on(sndr[, sch]{.rm}) or one equal +to such, and let _OutSndr_ be the type +decltype((_out_sndr_)). Let out_rcvr +be a subexpression denoting a receiver that has an environment of +type `Env` [such that sender_in<_OutSndr_, Env> +is `true`]{.rm}. [If get_start_scheduler(get_env(out_rcvr)) is ill-formed +or does not satisfy `@_infallible-scheduler_@` then evaluation of the +expression `get_completion_signatures<@_OutSndr_@, Env>()` exits with +an exception. +]{.add} Let `op` be an lvalue referring to the operation +state that results from connecting _out_sndr_ to +_out_rcvr_. Calling start(_op_) will +start `sndr` on the current execution agent and execute completion +operations on _out_rcvr_ on an execution agent of the +execution resource associated with [`sch`]{.rm}[_sch_]{.add}. +If the current execution resource is the same as the execution +resource associated with [`sch`]{.rm}[_sch_]{.add}, +the completion operation on _out_rcvr_ may be called +before start(_op_) completes. [If scheduling onto `sch` +fails, an error completion on _out_rcvr_ shall be +executed on an unspecified execution agent.]{.rm} + +::: ednote +Remove `change_coroutine_scheduler` from [execution.syn]: +::: + +``` +namespace std::execution { + ... + // [exec.task.scheduler], task scheduler + class task_scheduler; + + template + struct with_error { + using type = remove_cvref_t; + type error; + }; + template + with_error(E) -> with_error; +``` +::: rm +``` + template + struct change_coroutine_scheduler { + using type = remove_cvref_t; + type scheduler; + }; + template + change_coroutine_scheduler(Sch) -> change_coroutine_scheduler; +``` +::: +``` + // [exec.task], class template task + template + class task; + ... +} +``` + +::: ednot + +In [[task.class](https://wg21.link/task.class)] change `scheduler_type` to +`start_scheduler_type`: + +::: + +``` +namespace std::execution { + template> + class task { + // [task.state] + template + class @_state_@; // @_exposition only_@ + + public: + using sender_concept = sender_t; + using completion_signatures = @_see below_@; + using allocator_type = @_see below_@; + using @[scheduler_type]{.rm}@@[start_scheduler_type]{.add}@ = @_see below_@; + using stop_source_type = @_see below_@; + using stop_token_type = decltype(declval().get_token()); + using error_types = @_see below_@; + + // [task.promise] + class promise_type; + + task(task&&) noexcept; + ~task(); + + template + @_state_@ connect(Rcvr&& rcvr) &&; + + private: + coroutine_handle handle; // @_exposition only_@ + }; +} +``` + +[1]{.pnum} +`task` models sender ([exec.snd]) if `T` is `void`, a reference +type, or a _cv_-unqualified non-array object type and `E` is a class +type. Otherwise a program that instantiates the definition of +`task` is ill-formed. + +[2]{.pnum} +The nested types of task template specializations are determined based on the `Environment` parameter: +
    +
  • [2.1]{.pnum} +`allocator_type` is `Environment::allocator_type` if that _qualified-id_ +is valid and denotes a type, `allocator` otherwise.
  • +
  • [2.2]{.pnum} +`@[scheduler_type]{.rm}@@[start_scheduler_type]{.add}@` is `Environment::@[scheduler_type]{.rm}@@[start_scheduler_type]{.add}@` if that _qualified-id_ + is valid and denotes a type, `task_scheduler` otherwise.
  • +
  • [2.3]{.pnum} +`stop_source_type` is `Environment::stop_source_type` if that +_qualified-id_ is valid and denotes a type, `inplace_stop_source` +otherwise.
  • +
  • [2.4]{.pnum} +`error_types` is `Environment::error_types` if that _qualified-id_ +is valid and denotes a type, +`completion_signatures` otherwise.
  • +
+ +[3]{.pnum} + +A program is ill-formed if `error_types` is not a specialization +of `execution::completion_signatures` or if the template arguments +of that specialization contain an element which is not of the form +`set_error_t(E)` for some type `E`. + +[4]{.pnum} +The type alias `completion_signatures` is a specialization of +`execution::completion_signatures` with the template arguments (in +unspecified order): + +
    +
  • [4.1]{.pnum} +`set_value_t()` if `T` is `void`, and `set_value_t(T)` otherwise;
  • +
  • [4.2]{.pnum} +template arguments of the specialization of +`execution::completion_signatures` denoted by `error_types`; and
  • +
  • [4.3]{.pnum} `set_stopped_t()`.
  • +
+[5]{.pnum} `allocator_type` shall meet the _Cpp17Allocator_ requirements. + +::: ednote +Change the scheduler propagation to `start` to use `get_start_scheduler` in [[task.state](https://wg21.link/task.state#4)] p4: +::: + +``` +void start() & noexcept; +``` + +[4]{.pnum} _Effects_: Let `@_prom_@` be the object `@_handle_@.promise()`. Associates `@_STATE_@(@_prom_@)`, `@_RCVR_@(@_prom_@)`, and `@_SCHED_@(@_prom_@)` with `*this` as follows: + +
    +
  • [4.1]{.pnum} `@_STATE_@(prom)` is `*this`.
  • +
  • [4.2]{.pnum} `@_RCVR_@(@_prom_@)` is `@_rcvr_@`.
  • +
  • [4.3]{.pnum} `@_SCHED_@(@_prom_@)` is the object initialized with +`@[scheduler_type]{.rm}@@[start_scheduler_type]{.add}@(@[get_scheduler]{.rm}@@[get_start_scheduler]{.add}@(get_env(@_rcvr_@)))` if that expression is +valid and `@[scheduler_type]{.rm}@@[start_scheduler_type]{.add}@()` otherwise. If neither of these expressions +is valid, the program is ill-formed.
  • +
+ +Let `@_st_@` be `get_stop_token(get_env(@_rcvr_@))`. Initializes `@_prom_@.@_token_@` +and `@_prom.source_@` such that + +
    +
  • [4.4]{.pnum} +`@_prom_@.@_token_@.stop_requested()` returns `@_st_@.stop_requested()`;
  • +
  • [4.5]{.pnum} +`@_prom_@.@_token_@.stop_possible()` returns `@_st_@.stop_possible()`; and
  • +
  • [4.6]{.pnum} +for types `Fn` and `Init` such that both `invocable` and +`constructible_from` are modeled, +`stop_token_type::callback_type` models +`@_stoppable-callback-for_@`.
  • +
+ +After that invokes `@_handle_@.resume()`. + +::: ednote +Use `get_start_scheduler` instead of `get_scheduler` when providing +an environment to the `co_await`ed sender in +[[task.state](https://wg21.link/task.state#16)] p16: +::: + +``` +@_unspecified_@ get_env() const noexcept; +``` +[16]{.pnum} + +_Returns_: An object `env` such that queries are forwarded as follows: +
    +
  • [16.1]{.pnum} +`env.query(@[get_scheduler]{.rm}@@[get_start_scheduler]{.add}@)` returns `@[scheduler_type]{.rm}@@[start_scheduler_type]{.add}@(@_SCHED_@(*this))`.
  • +
  • [16.2]{.pnum} +`env.query(get_allocator)` returns `@_alloc_@`.
  • +
  • [16.3]{.pnum} +`env.query(get_stop_token)` returns `@_token_@`.
  • +
  • [16.4]{.pnum} For any other query `q` and arguments `a...` a call +to `env.query(q, a...)` returns `STATE(*this).environment.query(q, +a...)` if this expression is well-formed and `forwarding_query(q)` is +well-formed and is `true`. Otherwise `env.query(q, a...)` is ill-formed.
  • +
+ +::: ednote +Adjust the use of `affine_on` and remove `change_coroutine_scheduler` from [task.promise]: +::: + +``` +namespace std::execution { + template + class task::promise_type { + public: + ... + + template + auto await_transform(A&& a); +``` +::: rm +``` + template + auto await_transform(change_coroutine_scheduler sch); +``` +::: +``` + + @_unspecified_@ get_env() const noexcept; + + ... + } +}; +``` +... + +``` +template + auto await_transform(Sender&& sndr) noexcept; +``` +[9]{.pnum} +_Returns_: If `same_as` is `true` returns `as_awaitable(​std​::​​forward(sndr), *this);` otherwise returns `as_awaitable(affine_on(​std​::​​forward(sndr)@[, SCHED(*this)]{.rm}@), *this)`. + +::: rm +``` +template + auto await_transform(change_coroutine_scheduler sch) noexcept; +``` +[10]{.pnum} +_Effects_: Equivalent to: +``` +return await_transform(just(exchange(SCHED(*this), scheduler_type(sch.scheduler))), *this); +``` +::: + +``` +void unhandled_exception(); +``` +[11]{.pnum} +_Effects_: If the signature `set_error_t(exception_ptr)` is not an element of `error_types`, calls `terminate()` ([except.terminate]). Otherwise, stores `current_exception()` into _errors_. + +... + +::: ednote +In [exec.task.scheduler] change the constructor of `task_scheduler` to require that the scheduler passed +is infallible +::: + +``` +template> + requires(!same_as>) && scheduler +explicit task_scheduler(Sch&& sch, Allocator alloc = {}); +``` + +::: add +[?]{.pnum} +_Mandates_: `Sch` satisfies `@_infallible-scheduler_@>`. + +::: + +[2]{.pnum} +_Effects_: Initialize sch_ with `allocate_shared>(alloc,​ std​::​forward​(sch))`. + +[3]{.pnum} +_Recommended practice_: Implementations should avoid the use of +dynamically allocated memory for small scheduler objects. + +[4]{.pnum} +_Remarks_: Any allocations performed by construction of +_ts-sender_ or _state_ objects resulting +from calls on `*this` are performed using a copy of `alloc`. + +::: ednote +In [exec.task.scheduler] change the ts-sender completion signatures +to indicate that `task_scheduler` is infallible: +::: + +[8]{.pnum} +``` +namespace std::execution { + class task_scheduler::@_ts-sender_@ { // @_exposition only_@ + public: + using sender_concept = sender_t; + + template + @_state_@ connect(Rcvr&& rcvr) &&; + }; +} +``` + +ts-sender is an exposition-only class that +models `sender` ([exec.snd]) and for which +completion_signatures_of_t<ts-sender[, E]{.add}> +denotes[:]{.rm}[ `completion_signatures` if `unstoppable_token>` is `true`, and +otherwise `completion_signatures`.]{.add} + +::: rm +``` +completion_signatures< + set_value_t(), + set_error_t(error_code), + set_error_t(exception_ptr), + set_stopped_t()> +``` +::: + +::: ednote +In [exec.run.loop.types] change the paragraph defining the completion signatures: +::: + +... + +``` +class run-loop-sender; +``` + +[6]{.pnum} +run-loop-sender is an exposition-only type that satisfies `sender`. +[Let `E` be the type of an environment. If `unstoppable_token>` is `true`, +then ]{.add} completion_signatures_of_t<run-loop-sender[, E]{.add}> is + +::: rm +``` + completion_signatures +``` +::: + +::: add +``` + completion_signatures +``` +Otherwise it is +``` + completion_signatures +``` +::: + +[7]{.pnum} An instance of run-loop-sender remains +valid until the end of the lifetime of its associated `run_loop` +instance. + +... + +::: ednote +Change +[[exec.counting.scopes.general](https://wg21.link/exec.counting.scopes.general#4)] +p4 to use `get_start_scheduler` instead of `get_scheduler`: +::: + +[4]{.pnum} The exposition-only class template `@_impls-for_@` ([exec.snd.expos]) is specialized for `@_scope-join-t_@` as follows: + +``` +namespace std::execution { + template<> + struct @_impls-for_@<@_scope-join-t_@> : @_default-impls_@ { + template + struct state { // @_exposition only_@ + struct @_rcvr-t_@ { // @_exposition only_@ + using receiver_concept = receiver_t; + + Rcvr& @_rcvr_@; // @_exposition only_@ + + void set_value() && noexcept { + execution::set_value(std::move(@_rcvr_@)); + } + + template + void set_error(E&& e) && noexcept { + execution::set_error(std::move(@_rcvr_@), std::forward(e)); + } + + void set_stopped() && noexcept { + execution::set_stopped(std::move(@_rcvr_@)); + } + + decltype(auto) get_env() const noexcept { + return execution::get_env(@_rcvr_@); + } + }; + + using @_sched-sender_@ = // @_exposition only_@ + decltype(schedule(@[get_scheduler]{.rm}@@[get_start_scheduler]{.add}@(get_env(declval())))); + using @_op-t_@ = // @_exposition only_@ + connect_result_t<@_sched-sender_@, @_rcvr-t_@>; + + Scope* @_scope_@; // @_exposition only_@ + Rcvr& @_receiver_@; // @_exposition only_@ + @_op-t_@ @_op_@; // @_exposition only_@ + + @_state_@(Scope* scope, Rcvr& rcvr) // @_exposition only_@ + noexcept(@_nothrow-callable_@) + : @_scope_@(scope), + @_receiver_@(rcvr), + @_op_@(connect(schedule(@[get_scheduler]{.rm}@@[get_start_scheduler]{.add}@(get_env(rcvr))), @_rcvr-t_@(rcvr))) {} + + void @_complete_@() noexcept { // @_exposition only_@ + start(@_op_@); + } + + void @_complete-inline_@() noexcept { // @_exposition only_@ + set_value(std::move(@_receiver_@)); + } + }; + + static constexpr auto @_get-state_@ = // @_exposition only_@ + [](auto&& sender, Rcvr& receiver) + noexcept(is_nothrow_constructible_v<@_state_@, @_data-type_@, Rcvr&>) { + auto[_, self] = sender; + return @_state_@(self, receiver); + }; + + static constexpr auto @_start_@ = // @_exposition only_@ + [](auto& s, auto&) noexcept { + if (s.@_scope_@->@_start-join-sender_@(s)) + s.@_complete-inline_@(); + }; + }; +} +``` diff --git a/subprojects/task/docs/P3980-allocation.md b/subprojects/task/docs/P3980-allocation.md new file mode 100644 index 000000000..0a6e9d3ba --- /dev/null +++ b/subprojects/task/docs/P3980-allocation.md @@ -0,0 +1,388 @@ +--- +title: Task's Allocator Use +document: P3980R1 +date: 2026-03-24 +audience: + - Library Evolution Working Group (LEWG) + - Library Working Group (LWG) +author: + - name: Dietmar Kühl (Bloomberg) + email: +source: + - https://github.com/bemanproject/task/doc/P3980-allocation.md +toc: true +--- + +

+There are different uses of allocators for `task`. The obvious use +is that the coroutine frame needs to be allocated and using an +allocator control where this coroutine frame gets allocated. In +addition, the environment used when `connect`ing a sender can provide +access to an allocator via the `get_allocator` query. The current +specification uses the same allocator for coroutine frame and the +child environments. At the Kona meeting the room preferred if these +were separated and the allocator for the environment were taken +from the environment of the receiver it gets `connect`ed to. In +doing so, the allocator for the coroutine frame becomes more flexible +and it should be brought more in line with `generator`'s allocator +use. This paper addresses +[US 254-385](https://github.com/cplusplus/nbballot/issues/960), +[US 253-386](https://github.com/cplusplus/nbballot/issues/961), +[US 255-384](https://github.com/cplusplus/nbballot/issues/959), +and [US 261-391](https://github.com/cplusplus/nbballot/issues/966). +

+ +# Change History + +## R0 Initial Revision + +# Overview of Changes + +

+There are a few NB comments about `task`'s use of allocators: +

+
    +
  • [US 254-385](https://github.com/cplusplus/nbballot/issues/960): Constraint `allocator_arg` argument to be the first argument
  • +
  • [US 253-386](https://github.com/cplusplus/nbballot/issues/961): Allow use of arbitrary allocators for coroutine frame
  • +
  • [US 255-384](https://github.com/cplusplus/nbballot/issues/959): Use allocator from receiver's environment
  • +
  • [US 261-391](https://github.com/cplusplus/nbballot/issues/966): Bad specification of parameter type
  • +
+

+The first issue +([US 254-385](https://github.com/cplusplus/nbballot/issues/960)) is +about where an allocator argument for the coroutine frame may go +on the coroutine function. The options are a fixed location (which +would fit first for consistency with existing use) and anywhere. The +status quo is anywhere, and the request is to require that it goes +first. However, to support optionally passing an allocator, having +it go anywhere is easier to do. +

+

+The allocator constraints for allocating the coroutine frame are +due to the use of the same allocator for the environment of child +senders. If the allocator for the environment of child senders uses +the allocator from the receiver's environment, these constraints +can be relaxed. Instead, there may be requirements on the result +of the `get_allocator` query from the receiver's environment. The +discussion in Kona favored this direction. This change can address +the second ([US 253-386](https://github.com/cplusplus/nbballot/issues/961)) +and the third +([US 255-384](https://github.com/cplusplus/nbballot/issues/959)) issues. +

+

+The fourth issue ([US +261-391](https://github.com/cplusplus/nbballot/issues/966)) is +primarily a wording issue. However, some of the problematic paragraphs +will need some modifications to address the other issues, i.e., fixing +these wording issues in isolation isn't reasonable. +

+ +# Allocator Argument Position + +

+The combination of using `allocator_arg` followed by an allocator +object when invoking a function or a constructor is used in various +places throughout the standard C++ library. The `allocator_arg` +argument normally needs to be the first argument when present. The +definition of `task` makes the position of the `allocator_arg` more +flexible to allow easier support for optionally passing an allocator. +

+

+For coroutines, the arguments to the coroutine [factory] function +show up in three separate places: +

+
    +
  1. The parameters to the coroutine [factory] functions.
  2. +
  3. The constructor of the `promise_type` if there is a suitable matching overload.
  4. +
  5. the `operator new()` of the `promise_type` if there is a suitable matching overload.
  6. +
+

+This added flexibility doesn't introduce any constraints on how the coroutine +function is defined. It rather allows passing an `allocator_arg`/allocator +pair without requiring a specific location. The main benefit is that support +of an optional allocator can be supported by having a trailing `, auto&&...` +on the parameter list. Note that the allocator used for the coroutine frame +is normally not used in the body of the allocator. If it is needed, it can +in all cases be put into the first location. +

+::: cmptable + +### First +``` +task<> none(int x) +{ ... } +``` + +### Flexible +``` +task<> none(int x) +{ ... } +``` + +--- + +``` +task<> comes_first(allocator_arg_t, auto a, int x) +{ ... } +``` + +``` +task<> comes_first(allocator_arg_t, auto a, int x) +{ ... } +``` + +--- + +``` +task<> optional(allocator_arg_t, auto, int x) +{ ... } +task<> optional(int x) +{ return optional(allocator_arg, allocator(), x); } +``` + +``` +task<> optional(int x, auto&&...) +{ ... } +``` + +::: + +

+The comparison table above shows three separate cases the author of +a coroutine function may want to support: +

+
    +
  1. No allocator support (`none`): the use identical and just doesn't mention any allocator.
  2. +
  3. Mandate that the allocator is the first argument (`comes_first`): the use is identical.
  4. +
  5. + Support optionally passing an `allocator_arg`/allocator pair + (`optional`): the use can be identical but it can also be simplified + taking advantage of the flexible location. +
  6. +
+ +Below are three variations of the wording changes, only one can be picked: + +
    +
  1. Only support `allocator_arg` as the first argument and use the receiver's allocator for the environment.
  2. +
  3. Flexible position of the allocator arg and use allocator for the environment.
  4. +
  5. Flexible position of the allocator arg and use the receiver's allocator for the environment.
  6. +
+ +At the LEWG meeting on 2026-02-03 the first approach (putting the `allocator_arg` first, Wording Change A) +was preferred ([notes](https://wiki.isocpp.org/2026-02-03_LEWG_Telecon)). It was identified that the original wording change did not support member functions returning a `task` (the wording was fixed accordingly). + +## Wording Change A: `allocator_arg` must be first argument + +::: ednote +Change the synopsis of `promise` type in [task.promise], modifying +the overloads of `operator new`: +::: + +``` +namespace std::execution { + template + class task::promise_type { + public: + ... + unspecified get_env() const noexcept; + + @[void*]{.add}@ @[operator]{.add}@ @[new(size_t]{.add}@ @[size);]{.add}@ + template<@[class Alloc,]{.add}@ class... Args> + void* operator new(size_t size, @[allocator_arg_t,]{.add}@ @[Alloc]{.add}@ @[alloc, ]{.add}@Args&&... @[args]{.rm}@); + @[template]{.add}@ + @[void*]{.add}@ @[operator]{.add}@ @[new(size_t]{.add}@ @[size,]{.add}@ @[const]{.add}@ @[This&,]{.add}@ @[allocator_arg_t,]{.add}@ @[Alloc]{.add}@ @[alloc,]{.add}@ @[Args&&...);]{.add}@ + + void operator delete(void* pointer, size_t size) noexcept; + + private: + ... + }; +} +``` + +::: ednote +Change [task.promise] paragraphs 17 and 18: +::: + +::: add + +``` +void* operator new(size_t size); +``` + +[??]{.pnum} _Effects_: Equivalent to `return operator new(size, allocator_arg, allocator_type());` + +::: + +``` +template<@[class Alloc,]{.add}@ class... Args> + void* operator new(size_t size, @[allocator_arg_t,]{.add}@ @[Alloc]{.add}@ @[alloc, ]{.add}@Args&&... @[args]{.rm}@); +@[template]{.add}@ + @[void*]{.add}@ @[operator]{.add}@ @[new(size_t]{.add}@ @[size,]{.add}@ @[const]{.add}@ @[This&,]{.add}@ @[allocator_arg_t,]{.add}@ @[Alloc]{.add}@ @[alloc,]{.add}@ @[Args&&...);]{.add}@ +``` + +[17]{.pnum} [If there is no parameter with type `allocator_arg_t` +then let `alloc` be `allocator_type()`. Otherwise, let `arg_next` +be the parameter following the first `allocator_arg_t` parameter, +and let `alloc` be `allocator_type(arg_next)`]{.rm}. Let `PAlloc` +be `allocator_traits<@[allocator_type]{.rm}@@[Alloc]{.add}@>::template +rebind_alloc`, where `U` is an unspecified type whose size and +alignment are both `__STDCPP_DEFAULT_NEW_ALIGNMENT__`. + +::: rm + +[18]{.pnum} +_Mandates_: + +
    +
  • [18.1]{.pnum} The first parameter of type `allocator_arg_t` (if any) is not the last parameter.
  • +
  • [18.2]{.pnum} `allocator_type(arg_next)` is a valid expression if there is a parameter of type `allocator_arg_t`.
  • +
  • [18.3]{.pnum} `allocator_traits​::​pointer` is a pointer type.
  • +
+ +::: + +::: add + +[18]{.pnum} +_Mandates_: `allocator_traits​::​pointer` is a pointer type. + +::: + +[19]{.pnum} _Effects_: Initializes an allocator `palloc` of type +`PAlloc` with `alloc`. Uses `palloc` to allocate storage for the +smallest array of `U` sufficient to provide storage for a coroutine +state of size `size`, and unspecified additional state necessary to +ensure that `operator delete` can later deallocate this memory block +with an allocator equal to `palloc`. + +[20]{.pnum} _Returns_: A pointer to the allocated storage. + +# Use Allocator From Environment + +

+During the discussion at Kona the conclusion was that the allocator +forwarded by `task`'s environment to child senders should be the +allocator from `get_allocator` on the receiver `task` gets `connect`ed +to. Let `rcvr` be the receiver a `task` got `connect`ed to and let `ev` +be the result of `get_env(rcvr)`. The implication is that the `task`'s +`allocator_type` is compatible with the allocator of `ev`: +

+
    +
  • + If `get_allocator(ev)` is not defined, `allocator_type` has to + be default constructible. +
  • +
  • + Otherwise `allocator_type(get_allocator(ev))` has to be well-formed. +
  • +
  • + There is no need to store an alloc in the + `promise_type`: it can be obtained when requested from `ev` which, + in turn, can be obtained from `rcvr`. Thus, the ctor for `promise_type` + isn't needed. +
  • +
  • + The definition of `get_env` needs to be changed to get the allocator + when needed. +
  • +
+ +## Wording Changes + +::: ednote + +In [[task.members](https://wg21.link/task.members#3)] add a _Mandates_ to `connect`: + +::: + +``` +template + state connect(Rcvr&& recv) &&; +``` + +::: add + +[?]{.pnum} _Mandates_: At least one of the expressions `allocator_type(get_allocator(get_env(rcvr)))` and `allocator_type()` is well-formed. + +::: + +[3]{.pnum} _Preconditions_: bool(handle) is `true`. + +[4]{.pnum} _Effects_: Equivalent to: +``` +return state(exchange(handle, {}), std::forward(recv)); +``` + +::: ednote + +In [[task.promise]](https://wg21.link/task.promise) in the synopsis remove the `promise_type` constructor and +the alloc exposition-only member. + +::: + +``` +namespace std::execution { + template + class task::promise_type { + public: + @[template]{.rm}@ + @[promise_type(const]{.rm}@ @[Args&...]{.rm}@ @[args);]{.rm}@ + + task get_return_object() noexcept; + + ... + private: + using error-variant = see below; // @_exposition only_@ + + @[allocator_type]{.rm}@ @[_alloc_;]{.rm}@ @[//]{.rm}@ @[_exposition_]{.rm}@ @[_only_]{.rm}@ + stop_source_type @_source_@; // @_exposition only_@ + stop_token_type @_token_@; // @_exposition only_@ + optional @_result_@; // @_exposition only_@; present only if is_void_v is false + error-variant @_errors_@; // @_exposition only_@ + }; +} +``` + +::: ednote + +Remove the ctor for `promise_type`, i.e., [[task.promise]](https://wg21.link/task.promise) paragraph [3](https://wg21.link/task.promise#3) and [4](https://wg21.link/task.promise#4): + +::: + +::: rm + +``` +template + promise_type(const Args&... args); +``` + +[3]{.pnum} _Mandates_: The first parameter of type `allocator_arg_t` +(if any) is not the last parameter. + +[4]{.pnum} _Effects_: If `Args` contains an element of type +`allocator_arg_t` then alloc is initialized +with the corresponding next element of `args`. Otherwise, +alloc is initialized with `allocator_type()`. + +::: + +::: ednote + +Change `get_env` to get the allocator from the receiver when needed +in [[task.promise] p16](https://wg21.link/task.promise#16): + +::: + +``` +@_unspecified_@ get_env() const noexcept; +``` + +[16]{.pnum} _Returns_: An object `env` such that queries are forwarded as follows: +
    +
  • [16.1]{.pnum} `env.query(get_scheduler)` returns scheduler_type(SCHED(*this)).
  • +
  • [16.2]{.pnum} `env.query(get_allocator)` returns [alloc]{.rm}[allocator_type(get_allocator(get_env(RCVR(*this)))) if this expression is well-formed and `allocator_type()` otherwise]{.add}.
  • +
  • [16.3]{.pnum} `env.query(get_stop_token)` returns token.
  • +
  • [16.4]{.pnum} For any other query `q` and arguments `a...` a call to `env.query(q, a...)` returns STATE(*this).environment.query(q, a...) if this expression is well-formed and `forwarding_query(q)` is well-formed and is `true`. Otherwise `env.query(q, a...)` is ill-formed.
  • +
diff --git a/subprojects/task/docs/c++now.md b/subprojects/task/docs/c++now.md new file mode 100644 index 000000000..17f5e0f93 --- /dev/null +++ b/subprojects/task/docs/c++now.md @@ -0,0 +1,46 @@ +# Getting The Lazy Task Done + +## Description + +The C++26 specificatiom includes the sender/receiver framework for +dealing with asynchronous operations. It defines a vocualary and +various algorithms and utilities using this vocabulary. The current +specification doesn't include matching coroutine task class template +although it is expected that most users would use coroutines to +actually use the facilities. This presentation discusses various +aspects for creating a coroutine task suitable for many needs: + +- The basic operation of a sender/receiver aware task. +- Scheduler affinity and why it is important. +- Allocator support for the coroutine frame. +- Providing an environment to child operations. +- Propagating errors without exceptions. + +## Outline + +The presentation starts off with motivating why coroutines are a +reasonable approach to implementing asynchronous algorithms and +show some basic usage. That will include some motivation of why an +environment is important as a replacement for thread local variables +and why executing from common scheduler is normally desirable. + +The most contentious aspect is probably scheduler affinity and some +time is spent on showing how it is implement, motivating why it is +important, and why it should be the default. There are some use +cases where scheduler affinity may be undesirable and it is also +shown how it can be avoided. + +The coroutine frame typically can't live on the stack and needs to +be allocated while the HALO operations are not always applicable. It +is shown how allocation of the coroutine frame can be customized if +it is desired using an allocator. + +Any use allocation would likely be part of an environment exposed to +child operations. It should also be possible to customize the available +environment with user-specified properties. It is showing how to get +access to environment properties and how they can be customized. + +Error handling and cancellation are important aspects which are directly +supported by the sender/receiver framework. There are a few ways how a +coroutine can interact with error handling and cancellation and the +different options are discussed. diff --git a/subprojects/task/docs/compiler-explorer.ico b/subprojects/task/docs/compiler-explorer.ico new file mode 100644 index 000000000..0b0380f39 Binary files /dev/null and b/subprojects/task/docs/compiler-explorer.ico differ diff --git a/subprojects/task/docs/contributing.md b/subprojects/task/docs/contributing.md new file mode 100644 index 000000000..0c617363c --- /dev/null +++ b/subprojects/task/docs/contributing.md @@ -0,0 +1,36 @@ + + +# Contributing to `beman::task` + +We welcome all constructive contributions! You will have something you +can contribute independent of your level of expertise! + +There is a huge range of contributions needed and welcome! Here is an +incomplete list of starting points for contributions: + +* Some documentation is unclear. +* There is some bug somewhere. That can be a typo in some documentation, + a logic error in the implementation, a deviation from the standard + specification, etc. +* Something reference moved or the reference is out of date. +* When compiling the code on your system warnings or even errors are + created. +* The [issues page](https://github.com/bemanproject/task/issues) lists + known issues. +* The implementation of a component may be missing. +* The layout of some pages related to the project can be improved. +* Some behavior of a component isn't tested or documented. +* You found something which should be linked from the + [resources](https://github.com/bemanproject/task/blob/main/docs/resources.md) page. +* There are [pull requests](https://github.com/bemanproject/task/pulls) + which could be reviewed. +* This very page or list is lacking something. + +Whatever it is, you can improve the project! Ideally, create a pull +request with your proposed change. If you think you can't do that +for whatever reason, create an issue (if possible checking if the +issue isn't reported, yet) explaining what should be done. You can +possibly use a pull request or an issue to ask for help with something +you want to contribute. You can also send a message to one of the +contributors with your suggestion, e.g. to +[Dietmar Kühl](mailto:dietmar.kuehl@me.com). diff --git a/subprojects/task/docs/examples.md b/subprojects/task/docs/examples.md new file mode 100644 index 000000000..c3c1f9080 --- /dev/null +++ b/subprojects/task/docs/examples.md @@ -0,0 +1,246 @@ +# Examples + +## Code used to prepare Dietmar's C++Now 2025 presentation + +
+ +c++now-affinity.cpp +: +demo scheduler affinity + + +The example program +[`c++now-affinity.cpp`](https://github.com/bemanproject/task/blob/main/examples/c%2B%2Bnow-affinity.cpp) +uses [`demo::thread_loop`](../examples/demo-thread_loop.hpp) to demonstrate +the behavior of _scheduler affinity_: the idea is that scheduler +affinity causes the coroutine to resume on the same scheduler as +the one the coroutine was started on. The program implements three +coroutines which have most of their behavior in common: + +1. Each coroutine is executed from `main` using sync_wait(_fun_(loop.get_scheduler())). +2. Each coroutine prints the id of the thread it is executing on prior to any `co_await` and after all `co_await` expression. +3. Each coroutine `co_await`s the result of `scheduler(sched) | then([]{ ... })` where the function passed to `then` just prints the thread id. +4. `work2` additionally changes the coroutines scheduler to be an `inline_scheduler` and later restores the original scehduler using `change_coroutine_scheduler`. +5. While `work1` and `work2` use the default scheduler (`task_scheduler` a +type-erased scheduler which gets initialized from the receiver's environment's `get_scheduler`), `work3` sets the coroutine's scheduler up to be `inline_scheduler`, effectively causing the coroutine to resume wherever +the `co_await`'s expression resumed. + +The output of the program is something like the below: + +``` +before id=0x1fd635f00 +then id =0x16b64b000 +after id =0x1fd635f00 + +before id=0x1fd635f00 +then id =0x16b64b000 +after1 id=0x16b64b000 +then id =0x16b64b000 +after2 id=0x1fd635f00 + +before id=0x1fd635f00 +then id =0x16b64b000 +after id =0x16b64b000 +``` + +It shows that: + +1. The thread on which the `then`'s function is executed is always the +same and different from the thread each of the coroutines started on. +2. For `work1` the `co_await` resumes on the same thread as the one the +coroutine was started on. +3. For `work2` the first `co_await` after `schedule(sched)` resumes on +the thread used by `sched`. After restoring the original scheduler the +`co_await` resumes on the original thread. +4. For `work3` the `co_await` resumes on the thread used by `sched` as +the `inline_scheduler` doesn't do any actual scheduling. + +
+ +
+ +c++now-allocator.cpp +: +demo allocator use + + +This demo shows how to configure `task`'s environment argument to +use a different allocator than the default `std::allocator`. +To do so it defines an environment type `with_allocator` which +defines a nested type alias `allocator_type` to be +`std::pmr::polymorphic_allocator`. + +The coroutine `coro` shows how to use `read_env` to extract the +used allocator object to potentially use it for any allocation +purposes within the coroutine. There are two uses of `coro`, the +first one using the default which just uses +`std::pmr::polymorphic_allocator()` to allocate memory. +The second use explicitly specifies the memory resource +`std::pmr::new_delete_resource()` to initialized the use +`std::pmr::polymorphic_allocator`. + +
+ +
+ +c++now-basic.cpp +: +demo basic task use + + +The example +c++now-basic.cpp +shows some basic use of a `task`: + +1. The coroutine `basic` just `co_await`s the awaiter `std::suspend_never{}` which immediately completes. + This use demonstrates that any awaiter can be `co_await`ed by a `task<...>`. +2. The coroutine `await_sender` demonstrates the results of `co_await`ing various senders. It uses variations of + `just*` to show the different results: + - `co_await`ing a sender completing with `set_value_t()`, e.g., `just()`, produces an expression with type `void`. + - `co_await`ing a sender completing with `set_value_t(T)`, e.g., `just(1)`, produces an expression with type `T`. + - `co_await`ing a sender completing with set_value_t(T0, ..., Tn), e.g., `just(1, true)`, produces an expression with type tuple<T0, ..., Tn>. + - `co_await`ing a sender completing with `set_error_t(E)`, e.g., `just_error(1)`, results in an exception of type `E` being thrown. + - `co_await`ing a sender completing with `set_stopped_t()`, e.g., `just_stopped()`, results in the corouting never getting resumed although all local objects are properly destroyed. +
+ +
+ +c++now-cancel.cpp +: +demo how a task can actively cancel the work + + +The example +c++now-cancel.cpp +shows a coroutine `co_await`ing `just_stopped()` which results in the coroutine getting cancelled. The coroutine will +complete with `set_stopped()`. +
+ +
+ +c++now-errors.cpp +: +demo how to handle errors within task + + +The example +c++now-errors.cpp +shows examples of how to handle errors within a coroutine: + +- The coroutine `error_result` simply `co_await`s a sender producing an error (`just_error(17)`). When + a `co_await`ed sender completes with `set_error_t(T)` an exception of type `T` is thrown and the error + needs to be handled with a `try`/`catch` block. Otherwise the coroutine itself completes with `set_error_t(exception_ptr)` + where the `exception_ptr` hold the thrown exception object. +- The coroutine `expected` uses a sender algorithm `as_expected` which is implemented at the top of the example + to turn the result of the `co_await`ed sender into an object of type `expected`, avoiding an exception + from being thrown. + +
+ +
+ +c++now-query.cpp +: +demo passing a custom environment into a task + + +The example +c++now-query.cpp +shows how to define and use a custom environment element. + +1. The coroutine `with_env` uses a simple environment named `context` which just defines a custom query for `get_value` + to obtain a value. The value itself gets initialized from the environment of the receiver used with the `task`. +2. The coroutine `with_fancy_env` uses an environment which embed a an object depending the type of the environment + of the receiver used with the `task`. While the type accessed from within the `task` needs to be type-erased, + the actually stored value can depend on the environment of the upstream receiver. +
+ +
+ +c++now-result-types.cpp +: +demo the result type of co_await expressions. + + +The example +c++now-result-types.cpp +shows the result types of successful senders using variatons of `just`: + +- `co_await just()` doesn't produce a value, i.e., the type of the expression is `void`. +- `co_await just(1)` produces an `int`. +- `co_await just(1, true)` produces a `tuple`. + +
+ +
+ +c++now-return.cpp +: +shows the different normal values returned + + +The example +c++now-return.cpp +shows various ways of returning normally (without an error) for a `task`. Some of the coroutines are set up to produce +specific error results although none of them are actually use: + +- `default_return` shows that the default return type for `task<>` is `void`. +- `void_return` explicitly specifies a `void` return type. +- `int_return` specifies the return type as `int` and returns an `int` value. +- `error_return` specifies the return type as `int` and also specifies custom error results. +- `no_error_return` specifies the return type as `int` and also specifies that the coroutine can't produce any error. +
+ +
+ +c++now-stop_token.cpp +: +demo how to get and use a stop token in a `task` + + +The example +c++now-stop_token.cpp +shows how to get a stop token in side a `task` and how to use it to cancel active work. It doesn't actually complete +with a `set_stopped()` but completes with `set_value()`. + +- In the coroutine `co_await read_env(get_stop_token)` is used to get a stop token. +- In the loop the value of `token.stop_requested()` is checked to determine if the loop should continue. +- In `main` an `inplace_stop_source` is used to have something which can be stopped. +- When running the coroutine `stopping` on a separate thread, the environment is changed using `write_env` to use stop token from `main`'s stop source in the environment. +- After sleeping for a bit, `source.request_stop()` is called to trigger cancellation of the coroutine. +
+ +
+ +c++now-with_error.cpp +: +demo exiting a task with an error + + +The example +c++now-with_error.cpp +shows how a coroutine can be exited reporting an error without throwing an exception. To do so, the coroutine +uses `co_yield with_error(e)`. By default the `task` only declares `set_error_t(exception_ptr)`. To return +other errors, an environment declaring a suitable `set_error_t(E)` completion using the `error_types` alias is used. +
+ +## Tools Used By The Examples + +
+ +demo::thread_loop is a run_loop whose run() is called from a std::thread. + + +Technically [`demo::thread_loop`](../examples/demo-thread_loop.hpp) +is a class `public`ly derived from `execution::run_loop` which is +also owning a `std::thread`. The `std::thread` is constructed with +a function object calling `run()` on the +[`demo::thread_loop`](../examples/demo-thread_loop.hpp) object. +Destroying the object calls `finish()` and then `join()`s the +`std::thread`: the destructor will block until the `execution::run_loop`'s +`run()` returns. + +The important bit is that work executed on the +[`demo::thread_loop`](../examples/demo-thread_loop.hpp)'s `scheduler` +will be executed on a corresponding `std::thread`. +
diff --git a/subprojects/task/docs/issues-overview.md b/subprojects/task/docs/issues-overview.md new file mode 100644 index 000000000..f154adfd5 --- /dev/null +++ b/subprojects/task/docs/issues-overview.md @@ -0,0 +1,274 @@ +# Task Issues Overview + +A brief overview of the issues based on two lists (there is some +overlap between the two lists; the initials are used to reference +the respective issue): + +- Lewis's list [LB](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org) +- Dietmar's list [DK](https://github.com/bemanproject/task/blob/issues/docs/issues.md) (build nearly complete from Mattermost feedback) + +## Task is eager + +[LB](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#task-is-not-actually-lazily-started) +[DK](https://github.com/bemanproject/task/blob/issues/docs/issues.md#task-is-not-actually-lazily-started) + +- the current spec implies eager start +- the intent is to establish task's invariant (scheduler affinity) +- fix: `initial_suspend() -> suspend_always` and constraint start +- comment: rephrase to clarify it is resumed later + +=> say it always suspend (steal wording from suspend_always) but don't nail down suspend_always + change op_state.start(): calls handle.resume() on an execution agent associated with SCHED(prom) + +- we may want to require that the awaiter operations are not potentially throwing/noexcept + +## Initial resume shouldn't reschedule + +[LB](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#task-should-not-unconditionally-reschedule-when-control-enters-the-coroutine) +[DK](https://github.com/bemanproject/task/blob/issues/docs/issues.md#task-should-not-unconditionally-reschedule-when-control-enters-the-coroutine) +[DK](https://github.com/bemanproject/task/blob/issues/docs/issues.md#starting-a-task-reschedules) + +- `initial_suspend()`'s spec implies reschedule +- fix: constrain `start(rcvr)` to be called on `get_scheduler(get_env(rcvr))` instead +- comment: the description from `initial_suspend()` merely lays out what needs to hold => that's OK + +=> Change op_state.start() to handle.resume() on the correct execution agent. + +- this change isn't really task but could be a different paper +- we are not saying anything about schedule - we just say where we are executing +- maybe add a recommended practice about task constructed from task +- co_await on a task should never call schedule + +## Shouldn't reschedule after `co_await`ing task + +[LB](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#task-awaiting-another-task-should-not-reschedule-on-resumption) + +- `await_transform` uses `affine_on` without enough context information => reschedule +- fix: specify a custom `affine_on` for `task` and arrange for various things +- comment: how can a user tell what exactly happened? the `co_await`ing `task` and the `co_await`ed task are from the implementation + +=> define affine_on such that it start on the receiver's get_scheduler +- LB: I'm imagining +- it turns out that task doesn't have any default parameters! +- inline_scheduler should have a customization of affine_on and we drop the special case +- affine_on on the task could be specialized via the task's domain: it just returns the task + +- get_scheduler is where the operation is started +- affine_on should just take one argument and use get_scheduler but that requires that start is called on get_scheduler's scheduler + +[unrelated: +- get_scheduler: where we got started +- get_delegation_scheduler: where to start new work +- get_completion_scheduler: where things complete +] + +## No symmetric transfer + +[LB](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#task-coroutine-awaiting-another-task-does-not-use-symmetric-transfer) +[DK](https://github.com/bemanproject/task/blob/issues/docs/issues.md#no-support-for-symmetric-transfer) + +- `co_await tsk;` doesn't use symmetric transfer +- fix: require symmetric transfer +- comment: recommended practice? the `task`s can communicate appropriately + +## no support for non-senders + +[LB](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#minor-task-does-not-accept-awaiting-types-that-provide-as_awaitable-but-that-do-not-satisfy-sender-concept) + +- `as_awaitable` isn't used +- fix: use `as_awaitable` +- comment: how to guarantee affinity? seems to require an additional coroutine +- don't do? + +## `affine_on` has no default spec + +[LB](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#affine_on-is-missing-a-specification-for-a-default-implementation) +[DK](https://github.com/bemanproject/task/blob/issues/docs/issues.md#affine_on-underspecified) + +- there is no default specification of what `affine_on` does. +- fix: say something specific +- comment: we don't have the tools to say what implementations can do for known senders + +=> get a continues_on p5-like paragraph which calls continues_on + +## `affine_on` semantics unclear + +[LB](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#affine_on-semantics-are-not-clear) + +- the wording is unclear +- fix: two versions +- comment: I think I had the 2nd in mind + +=> allow affine_on to complete inline regardless that's the difference to continues_on + in addition it can be customized separately + +## `affine_on` shape + +[LB](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#affine_on-might-not-have-the-right-shape) + +- `affine_on` should just depend on the receiver's `get_scheduler()`. +- comment: agreed; that is design, though + +=> starts on get_scheduler, complete on the passed scheduler (which may get dropped) + +## `affine_on` schedule vs. stop token + +[LB](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#affine_on-should-probably-not-forward-stop-requests-to-reschedule-operation) + +- non-success completions don't guarantee affinity invariant +- fix: suppress stop token +- comment: in case of `set_stopped()` the coroutine doesn't resume; for `set_error(e)` other reasons may exist +- comment: however, I don't object to this direction + +=> this requirement needs to align with what continues_on does +- the actual conclusion is: affine_on may need to do what continues_on does +- unless continues_on's schedule operation should also not be cancelable +- alternatively we could call continues_on with an adapted scheduler + +## `affine_on` vs. other algorithms + +[LB](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#we-should-probably-define-customsiations-for-affine_on-for-some-other-senders) + +- `affine_on(sch, sndr)` should behave special for some `sndr` +- fix: specify some of the interactions +- comment: I think an implementation can do these; once we have better tools we may want to require some + +## promise_type vs with_awaitable_sender + +[LB](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#minor-taskpromise_type-doesnt-use-with_awaitable_senders---should-it) + +- should it use `with_awaitable_sender`? +- potential fix: have affinity aware `with_awaitable_sender` variant? +- comment: there are multiple tools which may be useful + +## unhandled_stopped should be noexcept + +[LB](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#taskpromise_typeunhandled_stopped-should-be-marked-noexcept) + +- fix: do it! +- comment: for consistency it should be `noexcept` + +## allocator type not specified + +[LB](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#behaviour-when-the-tasks-environment-type-does-not-specify-an-allocator_type) +[DK](https://github.com/bemanproject/task/blob/issues/docs/issues.md#unusual-allocator-customisation) + +- generator uses the allocator anyway +- fix: use type-erased allocator in that case like generator +- comment: that's an oversight; I didn't realize generator does that + +=> write up the pros and cons +- we could construct environment with the allocator so the user can choose which allocator to use +- use the coroutine_traits to determine the promise_type based on the allocator? + +## allocator_arg more permissive + +[LB](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#handling-of-allocator_arg-is-more-permissive-than-for-stdgenerator) +[DK](https://github.com/bemanproject/task/blob/issues/docs/issues.md#issue-flexible-allocator-position) + +- generator requires allocator in first position +- fix: should generator be generalized? +- comment: yes but not for C++26 + +=> coroutines can forward - write design discussion + +## hiding parent's allocator + +[LB](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#minor-task-environments-allocator_type-overrides-the-parent-environments-get_allocator) +[DK](https://github.com/bemanproject/task/blob/issues/docs/issues.md#shadowing-the-environment-allocator-is-questionable) + +- task's allocator (from ctor) hides receiver's allocator +- fix: allow receiver's allocator through +- comment: that misaligns with my understanding of the allocator model + +## no stop_source in promise + +[LB](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#taskpromise_type-should-not-contain-a-stop-source) +[DK](https://github.com/bemanproject/task/blob/issues/docs/issues.md#a-stop-source-always-needs-to-be-created) + +- the stop_source is only need for misaligned stop tokens +- fix: have the stop source in promise and only when needed +- comment: I think that is already allowed; maybe should be clarified, recommended practice? + +## `stop_token` default constructible + +[LB](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#taskpromise_type-wording-assumes-that-stop-token-is-default-constructible) + +- the exposition-only member for `stop_token` require default constructible +- fix: don't have it: get it from env or stop source +- comment: I think that is already allowed + +## coro frame destroyed too late + +[LB](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#task-coroutine-state-is-not-destroyed-early-enough-after-completing) + +- frame should be destroyed before completing +- fix: result in state, destroy before completing +- comment: that is what I had hoped to say; may need wording fix + +## inefficient env + +[LB](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#taskpromise_typeget_env-seems-to-require-an-inefficient-implementation) +[DK](https://github.com/bemanproject/task/blob/issues/docs/issues.md#the-environment-design-is-odd) + +- `own_env` must copy lots of state +- comment: `own_env` just allows a potential type-erase entity to be workable, primarily it is env + +## no completion scheduler + +[DK](https://github.com/bemanproject/task/blob/issues/docs/issues.md#no-completion-scheduler) + +- `task` doesn't expose a completion scheduler +- comment: it can't have one +- fix: redesign get_completion_scheduler(sender, env) or get_completion_scheduler(os) + +## future coro may not need `co_yield error` + +[DK](https://github.com/bemanproject/task/blob/issues/docs/issues.md#a-future-coroutine-feature-could-avoid-co_yield-for-errors) + +- sure + +## no hook to capture/restore TLS + +[DK](https://github.com/bemanproject/task/blob/issues/docs/issues.md#there-is-no-hook-to-capturerestore-tls) + +- legacy APIs may need TLS to be set appropriately +- objective: capture TLS before async, restore after +- fix: use custom `affine_on` with wrapped scheduler, delegating + +# Outline + +## Major + +- [x] `affine_on` - require more detailed specification - Dietmar will have a paper - get_scheduler semantics (Telecon during August, vote during Kona) - NB comment + - [x] [`affine_on` is missing a specification for a default implementation](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#affine_on-is-missing-a-specification-for-a-default-implementation) + - [x] [`affine_on` semantics are not clear](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#affine_on-semantics-are-not-clear) + - [x] [`affine_on` might not have the right shape](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#affine_on-might-not-have-the-right-shape) + - [x] [`affine_on` should probably not forward stop-requests to reschedule operation](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#affine_on-should-probably-not-forward-stop-requests-to-reschedule-operation) + - [x] [`affine_on` customization for other senders](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#we-should-probably-define-customsiations-for-affine_on-for-some-other-senders) +- [x] [`task` is not actually lazily started](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#task-is-not-actually-lazily-started) - wording fix (should suspend always) (Dietmar’s paper) +- [x] [`task` coroutine reschedules too often](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#task-coroutine-reschedules-too-often): - apply “known design” to optimize / fix (Dietmar’s paper) + - [x] [`task` should not unconditionally reschedule when control enters the coroutine](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#task-should-not-unconditionally-reschedule-when-control-enters-the-coroutine) + - [x] [`task` awaiting another task should not reschedule on resumption](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#task-awaiting-another-task-should-not-reschedule-on-resumption) + - [x] [`task` coroutine awaiting another task does not use symmetric-transfer](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#task-coroutine-awaiting-another-task-does-not-use-symmetric-transfer) - (Dietmar’s paper) +- [x] [`task` allocation strategy](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#task-allocator-customisation-behaviour-is-inconsistent-with-generator) + - [x] [`task` allocator customization is more permissive](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#handling-of-allocator_arg-is-more-permissive-than-for-stdgenerator) + - [x] [`task` environment’s `allocator_type` hides the parent environment’s `get_allocator`](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#minor-task-environments-allocator_type-overrides-the-parent-environments-get_allocator) + - [x] [for `generator` an `allocator_arg, allocator` can always be used](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#behaviour-when-the-tasks-environment-type-does-not-specify-an-allocator_type) + +## Medium + +- [x] [`task::promise_type` should not contain a stop-source](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#taskpromise_type-should-not-contain-a-stop-source) - (Dietmar’s paper) +- [x] [`task::promise_type` wording assumes that stop-token is default constructible](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#taskpromise_type-wording-assumes-that-stop-token-is-default-constructible) - (Dietmar’s paper) +- [x] [`task` coroutine-state is not destroyed early enough after completing](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#task-coroutine-state-is-not-destroyed-early-enough-after-completing) (optimization?) - (Dietmar’s paper) +- [x] [`task::promise_type::get_env` seems to require an inefficient implementation](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#taskpromise_typeget_env-seems-to-require-an-inefficient-implementation) (optimization?) - (Dietmar’s paper) + +## Minor issues + +- [x] [`task` does not accept awaiting types that provide `as_awaitable`](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#minor-task-does-not-accept-awaiting-types-that-provide-as_awaitable-but-that-do-not-satisfy-sender-concept) but that do not satisfy sender concept +- [x] [`task::promise_type` doesn’t use `with_awaitable_senders` - should it?](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#minor-taskpromise_type-doesnt-use-with_awaitable_senders---should-it) +- [x] `task` Has No Default Arguments +- [x] [`task::promise_type::unhandled_stopped` should be marked `noexcept`](https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org#taskpromise_typeunhandled_stopped-should-be-marked-noexcept) +- [x] No Completion Scheduler +- [x] Awaitable non-`sender`s Are Not Supported +- [x] Future Coroutine Features Could avoid co_yield with_error. +- [x] No hook to capture/restore TLS diff --git a/subprojects/task/docs/resources.md b/subprojects/task/docs/resources.md new file mode 100644 index 000000000..181ac50f2 --- /dev/null +++ b/subprojects/task/docs/resources.md @@ -0,0 +1,12 @@ + + +# Resources Related to `beman::task` + +This page has links to some resources related to this repository: + +* Lewis Baker's [Asymmetric Transfer blog](https://lewissbaker.github.io/) +* Dietmar Kühl's + [co_await All The Things](https://youtu.be/Npiw4cYElng?si=J0EYX6IgBf_u-e6z) + from [ACCU 2023](https://accu.org/conf-previous/2023/sessions/) implementing + a simple task live +* The C++ standard proposal [Add a Coroutine Lazy Type](https://wg21.link/P3552). diff --git a/subprojects/task/docs/task-issues.md b/subprojects/task/docs/task-issues.md new file mode 100644 index 000000000..9da8013e7 --- /dev/null +++ b/subprojects/task/docs/task-issues.md @@ -0,0 +1,28 @@ +# C++ task related issues + +NB Comment | LWG Issue | State | Section | Title +-----------|-----------|-------|---------|------ +[US 232-366](https://github.com/cplusplus/nbballot/issues/941) | [LWG4329](https://cplusplus.github.io/LWG/issue4329) [P3941](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p3941r1.html) | open/SG1 | [33.13.3](https://wg21.link/exec.affine.on) | Specify customizations for `affine_on` when sender does not change scheduler +[US 233-365](https://github.com/cplusplus/nbballot/issues/940) | [LWG4330](https://cplusplus.github.io/LWG/issue4330) [P3941](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p3941r1.html) | open/SG1 | [33.13.3](https://wg21.link/exec.affine.on) | Clarify `affine_on` vs. `continues_on` +[US 234-364](https://github.com/cplusplus/nbballot/issues/939) | [LWG4331](https://cplusplus.github.io/LWG/issue4331) [P3941](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p3941r1.html) | open/LEWG | [33.13.3](https://wg21.link/exec.affine.on) | Remove scheduler parameter from `affine_on` +[US 235-363](https://github.com/cplusplus/nbballot/issues/938) | [LWG4332](https://cplusplus.github.io/LWG/issue4332) [P3941](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p3941r1.html) | open/SG1 | [33.13.3](https://wg21.link/exec.affine.on) | `affine_on` should not forward the stop token to the scheduling operation +[US 236-362](https://github.com/cplusplus/nbballot/issues/937) | [LWG4344](https://cplusplus.github.io/LWG/issue4344) [P3941](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p3941r1.html) | open/SG1 | [33.13.3](https://wg21.link/exec.affine.on) | Specify default implementation of `affine_on` +[US 237-369](https://github.com/cplusplus/nbballot/issues/944) | [LWG4342](https://cplusplus.github.io/LWG/issue4342) | closed | [33.13.5](https://wg21.link/exec.task.scheduler) | Add rvalue reference qualification to task_scheduler::ts-sender::connect +[US 238-368](https://github.com/cplusplus/nbballot/issues/943) | [LWG4336](https://cplusplus.github.io/LWG/issue4336) [P3927](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p3927r0.html) | open/LEWG | [33.13.5](https://wg21.link/exec.task.scheduler) | Allow customizations when using `task_scheduler` +[US 239-367](https://github.com/cplusplus/nbballot/issues/942) | | closed | [33.13.5](https://wg21.link/exec.task.scheduler) | sch_ must not be in moved-from state +[US 242-372](https://github.com/cplusplus/nbballot/issues/947) | [LWG4339](https://cplusplus.github.io/LWG/issue4339) | open/LWG | [33.13.6](https://wg21.link/exec.task) | Timing of destruction of coroutine frame +[US 243-376](https://github.com/cplusplus/nbballot/issues/951) | | closed | [33.13.6.2](https://wg21.link/task.class) | Add default template arguments for `task` +[US 244-375](https://github.com/cplusplus/nbballot/issues/950) | [LWG4341](https://cplusplus.github.io/LWG/issue4341) | closed | [33.13.6.2](https://wg21.link/task.class) | Add rvalue reference qualifier for `task::connect()` +[US 245-374](https://github.com/cplusplus/nbballot/issues/949) | [LWG4338](https://cplusplus.github.io/LWG/issue4338) | rejected | [33.13.6.2](https://wg21.link/task.class) | Add `operator co_await` +[US 246-373](https://github.com/cplusplus/nbballot/issues/948) | [LWG4348](https://cplusplus.github.io/LWG/issue4348) | ⚠️open/SG1 | [33.13.6.2](https://wg21.link/task.class) | Support symmetric transfer +[US 249-379](https://github.com/cplusplus/nbballot/issues/954) | | ⚠️open/LWG | [33.13.6.4p4](https://wg21.link/task.state#4) | Move specification for `stop_token_type` +[US 250-389](https://github.com/cplusplus/nbballot/issues/964) | [LWG4346](https://cplusplus.github.io/LWG/issue4346) | closed | [33.13.6.5](https://wg21.link/task.promise) | Add specification for `return_void` and `return_value` +[US 251-388](https://github.com/cplusplus/nbballot/issues/963) | [LWG4345](https://cplusplus.github.io/LWG/issue4345) | closed | [33.13.6.5](https://wg21.link/task.promise) | Add default template argument for `return_value` +[US 252-387](https://github.com/cplusplus/nbballot/issues/962) | [LWG4340](https://cplusplus.github.io/LWG/issue4340) | closed | [33.13.6.5](https://wg21.link/task.promise) | `task::promise_type::unhandled_stopped()` should be `noexcept` +[US 253-386](https://github.com/cplusplus/nbballot/issues/961) | [LWG4333](https://cplusplus.github.io/LWG/issue4333) [D3980](https://isocpp.org/files/papers/D3980R0.html) | ✓open/LEWG | [33.13.6.5](https://wg21.link/task.promise) | Allow use of arbitrary allocators for coroutine frame +[US 254-385](https://github.com/cplusplus/nbballot/issues/960) | [LWG4334](https://cplusplus.github.io/LWG/issue4334) [D3980](https://isocpp.org/files/papers/D3980R0.html) | ✓open/LEWG | [33.13.6.5](https://wg21.link/task.promise) | Constraint `allocator_arg` argument to be the first argument +[US 255-384](https://github.com/cplusplus/nbballot/issues/959) | [LWG4335](https://cplusplus.github.io/LWG/issue4335) [D3980](https://isocpp.org/files/papers/D3980R0.html) | ✓open/LEWG | [33.13.6.5](https://wg21.link/task.promise) | Use allocator from receiver's environment +[US 256-383](https://github.com/cplusplus/nbballot/issues/958) | [LWG4337](https://cplusplus.github.io/LWG/issue4337) | rejected | [33.13.6.5](https://wg21.link/task.promise) | Scheduler need not be assignable +[US 257-382](https://github.com/cplusplus/nbballot/issues/957) | [LWG4347](https://cplusplus.github.io/LWG/issue4347) | ⚠️open/LWG | [33.13.6.5](https://wg21.link/task.promise) | Respecify source and token members +[US 258-381](https://github.com/cplusplus/nbballot/issues/956) | [LWG4349](https://cplusplus.github.io/LWG/issue4349) | closed | [33.13.6.5](https://wg21.link/task.promise) | Clarify `task` is not eagerly started +[US 261-391](https://github.com/cplusplus/nbballot/issues/966) | [D3980](https://isocpp.org/files/papers/D3980R0.html) | ✓open/LEWG | [33.13.6.5](https://wg21.link/task.promise) [p3](https://wg21.link/task.promise#3), [p16](https://wg21.link/task.promise#16), [p17](https://wg21.link/task.promise#17) | Bad specification of parameter type diff --git a/subprojects/task/examples/CMakeLists.txt b/subprojects/task/examples/CMakeLists.txt new file mode 100644 index 000000000..8dda33eef --- /dev/null +++ b/subprojects/task/examples/CMakeLists.txt @@ -0,0 +1,79 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +cmake_minimum_required(VERSION 3.30...4.3) + +# include(../cmake/prelude.cmake) + +project(beman_task.example LANGUAGES CXX) + +# TODO(CK): cmake -S . -G Ninja -B build -D CMAKE_BUILD_TYPE=RelWithDebInfo --fresh +if(PROJECT_IS_TOP_LEVEL) + # include(../cmake/cxx-modules-rules.cmake) + set(BEMAN_USE_MODULES OFF) + set(BEMAN_USE_STD_MODULE OFF) + set(CMAKE_CXX_STANDARD 23) + + find_package(beman.task 0.2.0 EXACT REQUIRED) + + enable_testing() +endif() + +set(TODO tls-scheduler into_optional issue-start-reschedules loop) + +set(ALL_EXAMPLES + task_scheduler + rvalue-task + customize + issue-symmetric-transfer + container +) + +if(NOT MSVC) + list( + APPEND ALL_EXAMPLES + task-sender + alloc-1 + alloc-2 + c++now-allocator + c++now-cancel + c++now-errors + alloc + affinity + odd-return + dangling-references + aggregate-return + issue-affine_on + c++now-affinity + c++now-basic + # FIXME: c++now-query + c++now-result-types + c++now-return + # FIXME: c++now-stop_token + c++now-with_error + co_await-result + co_await-task + error + escaped-exception + friendly + hello + issue-frame-allocator + # FIXME: query + result_example + # FIXME: stop + ) +endif() + +set(xALL_EXAMPLES issue-symmetric-transfer) +set(xALL_EXAMPLES customize) + +message(STATUS "Examples to be built: ${ALL_EXAMPLES}") + +foreach(example ${ALL_EXAMPLES}) + add_executable(beman.task.examples.${example}) + target_sources(beman.task.examples.${example} PRIVATE ${example}.cpp) + target_link_libraries(beman.task.examples.${example} beman::task_headers) + add_test( + NAME beman.task.examples.${example} + COMMAND $ + ) +endforeach() diff --git a/subprojects/task/examples/affinity.cpp b/subprojects/task/examples/affinity.cpp new file mode 100644 index 000000000..9f5605865 --- /dev/null +++ b/subprojects/task/examples/affinity.cpp @@ -0,0 +1,63 @@ +// examples/affinity.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include "demo-thread_loop.hpp" +#include +#include + +namespace ex = beman::execution; + +// ---------------------------------------------------------------------------- + +namespace { +struct test_receiver { + using receiver_concept = ex::receiver_tag; + + auto set_value(auto&&...) && noexcept {} + auto set_error(auto&&) && noexcept {} + auto set_stopped() && noexcept {} +}; +static_assert(ex::receiver); + +std::ostream& fmt_id(std::ostream& out) { return out << std::this_thread::get_id(); } + +struct non_affine { + using scheduler_type = ex::inline_scheduler; +}; +} // namespace + +int main() { + std::cout << std::unitbuf; + demo::thread_loop loop; + ex::sync_wait(ex::just() | ex::then([]() noexcept { std::cout << "main:" << fmt_id << "\n"; })); + ex::sync_wait(ex::schedule(loop.get_scheduler()) | + ex::then([]() noexcept { std::cout << "loop:" << fmt_id << "\n"; })); + ex::sync_wait(ex::schedule(ex::task_scheduler(loop.get_scheduler())) | + ex::then([]() noexcept { std::cout << "any: " << fmt_id << "\n"; })); + ex::sync_wait([]() -> ex::task { + std::cout << "coro:" << fmt_id << "\n"; + co_return; + }()); + std::cout << "scheduler affine:\n"; + ex::sync_wait([](auto& pl) -> ex::task { + std::cout << "cor1:" << fmt_id << "\n"; + co_await (ex::schedule(pl.get_scheduler()) | ex::then([] { std::cout << "then:" << fmt_id << "\n"; })); + std::cout << "cor2:" << fmt_id << "\n"; + }(loop)); + + std::cout << "not scheduler affine:\n"; + ex::sync_wait([](auto& pl) -> ex::task { + std::cout << "cor1:" << fmt_id << "\n"; + co_await (ex::schedule(pl.get_scheduler()) | ex::then([] { std::cout << "then:" << fmt_id << "\n"; })); + std::cout << "cor2:" << fmt_id << "\n"; + }(loop)); + + std::cout << "use inline_scheduler:\n"; + ex::sync_wait(ex::starts_on(ex::inline_scheduler{}, [](auto& pl) -> ex::task { + std::cout << "cor1:" << fmt_id << "\n"; + co_await (ex::schedule(pl.get_scheduler()) | ex::then([] { std::cout << "then:" << fmt_id << "\n"; })); + std::cout << "cor2:" << fmt_id << "\n"; + }(loop))); +} diff --git a/subprojects/task/examples/aggregate-return.cpp b/subprojects/task/examples/aggregate-return.cpp new file mode 100644 index 000000000..bebcb9fd1 --- /dev/null +++ b/subprojects/task/examples/aggregate-return.cpp @@ -0,0 +1,20 @@ +// examples/aggregate-return.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include + +namespace ex = beman::execution; + +// ---------------------------------------------------------------------------- + +int main() { + struct aggregate { + int value = 0; + }; + + ex::sync_wait([]() -> ex::task { co_return aggregate{42}; }()); + ex::sync_wait([]() -> ex::task { co_return aggregate{}; }()); + ex::sync_wait([]() -> ex::task { co_return {42}; }()); + ex::sync_wait([]() -> ex::task { co_return {}; }()); +} diff --git a/subprojects/task/examples/alloc-1.cpp b/subprojects/task/examples/alloc-1.cpp new file mode 100644 index 000000000..129c2a30f --- /dev/null +++ b/subprojects/task/examples/alloc-1.cpp @@ -0,0 +1,124 @@ +// examples/alloc-1.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ex = beman::execution; + +// ---------------------------------------------------------------------------- +// --- defer_frame turns yields a sender calling a coroutine upon start() --- + +template +struct defer_frame { + Mem mem; + Self self; + template + auto operator()(Arg&&... arg) const { + return ex::let_value(ex::read_env(ex::get_allocator), + [mem = this->mem, self = this->self, ... a = std::forward(arg)](auto alloc) { + return std::invoke(mem, self, std::allocator_arg, alloc, std::move(a)...); + }); + } + template + auto operator()(::std::allocator_arg_t, Alloc alloc, Arg&&... arg) const { + return ex::let_value(ex::just(alloc), + [&mem = this->mem, self = this->self, ... a = std::forward(arg)](auto alloc) { + return std::invoke(mem, self, std::allocator_arg, alloc, std::move(a)...); + }); + } + auto operator()(::std::allocator_arg_t) const = delete; +}; + +template +struct defer_frame { + Task task; + template + auto operator()(Arg&&... arg) const { + return ex::let_value(ex::read_env(ex::get_allocator), + [task = this->task, ... a = std::forward(arg)](auto alloc) { + return std::invoke(task, std::allocator_arg, alloc, std::move(a)...); + }); + } + template + auto operator()(::std::allocator_arg_t, Alloc alloc, Arg&&... arg) const { + return ex::let_value(ex::just(alloc), [&task = this->task, ... a = std::forward(arg)](auto alloc) { + return std::invoke(task, std::allocator_arg, alloc, std::move(a)...); + }); + } + auto operator()(::std::allocator_arg_t) const = delete; +}; + +// ---------------------------------------------------------------------------- + +void* operator new(std::size_t n) { + auto p = std::malloc(n); + std::cout << " global new(" << n << ")->" << p << "\n"; + return p; +} +void operator delete(void* ptr) noexcept { + std::cout << " global operator delete(" << ptr << ")\n"; + std::free(ptr); +} +void operator delete(void* ptr, std::size_t size) noexcept { + std::cout << " global operator delete(" << ptr << ", " << size << ")\n"; + std::free(ptr); +} + +struct resource : std::pmr::memory_resource { + void* do_allocate(std::size_t n, std::size_t) override { + auto p{std::malloc(n)}; + std::cout << " resource::allocate(" << n << ")->" << p << "\n"; + return p; + } + void do_deallocate(void* p, std::size_t n, std::size_t) override { + std::cout << " resource::deallocate(" << p << ", " << n << ")\n"; + std::free(p); + } + bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override { return this == &other; } +}; + +// ---------------------------------------------------------------------------- + +using allocator_type = std::pmr::polymorphic_allocator; +struct alloc_env { + using allocator_type = ::allocator_type; +}; +template +using a_task = ex::task; + +a_task hidden_async_fun(std::allocator_arg_t, ::allocator_type, int value) { co_return value; } +auto async_fun(int value) { return defer_frame(&hidden_async_fun)(value); } + +int main() { + std::cout << std::unitbuf; + resource res{}; + allocator_type alloc(&res); + + std::cout << "not setting up an allocator:\n"; + ex::sync_wait([]() -> a_task<> { + auto result{co_await async_fun(17)}; + std::cout << " result=" << result << "\n"; + }()); + + std::cout << "setting up an allocator:\n"; + ex::sync_wait(ex::write_env( + []() -> a_task<> { + auto result{co_await async_fun(17)}; + std::cout << " result=" << result << "\n"; + }(), + ex::env{ex::prop{ex::get_allocator, alloc}})); + + std::cout << "setting up an allocator and using defer_frame:\n"; + ex::sync_wait(ex::write_env(defer_frame([](auto, auto) -> a_task<> { + auto result{co_await async_fun(17)}; + std::cout << " result=" << result << "\n"; + })(), + ex::env{ex::prop{ex::get_allocator, alloc}})); +} diff --git a/subprojects/task/examples/alloc-2.cpp b/subprojects/task/examples/alloc-2.cpp new file mode 100644 index 000000000..18776618c --- /dev/null +++ b/subprojects/task/examples/alloc-2.cpp @@ -0,0 +1,82 @@ +// examples/alloc-1.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ex = beman::execution; + +// ---------------------------------------------------------------------------- + +struct tls_allocator : std::pmr::polymorphic_allocator { + thread_local static std::pmr::memory_resource* alloc; + + tls_allocator() : std::pmr::polymorphic_allocator(alloc) {} + + static void set(std::pmr::memory_resource* a) { alloc = a; } +}; +thread_local std::pmr::memory_resource* tls_allocator::alloc{std::pmr::new_delete_resource()}; + +// ---------------------------------------------------------------------------- + +void* operator new(std::size_t n) { + auto p = std::malloc(n); + std::cout << " global new(" << n << ")->" << p << "\n"; + return p; +} +void operator delete(void* ptr) noexcept { + std::cout << " global operator delete(" << ptr << ")\n"; + std::free(ptr); +} +void operator delete(void* ptr, std::size_t size) noexcept { + std::cout << " global operator delete(" << ptr << ", " << size << ")\n"; + std::free(ptr); +} + +struct resource : std::pmr::memory_resource { + void* do_allocate(std::size_t n, std::size_t) override { + auto p{std::malloc(n)}; + std::cout << " resource::allocate(" << n << ")->" << p << "\n"; + return p; + } + void do_deallocate(void* p, std::size_t n, std::size_t) override { + std::cout << " resource::deallocate(" << p << ", " << n << ")\n"; + std::free(p); + } + bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override { return this == &other; } +}; + +// ---------------------------------------------------------------------------- + +using allocator_type = tls_allocator; +struct alloc_env { + using allocator_type = ::allocator_type; +}; +template +using a_task = ex::task; + +a_task async_fun(int value) { co_return value; } + +int main(int ac, char*[]) { + resource res{}; + + std::cout << "not setting up an allocator:\n"; + ex::sync_wait([ac]() -> a_task<> { + auto result{co_await async_fun(ac)}; + std::cout << " result=" << result << "\n"; + }()); + + std::cout << "setting up an allocator:\n"; + tls_allocator::set(&res); + ex::sync_wait([ac]() -> a_task<> { + auto result{co_await async_fun(ac)}; + std::cout << " result=" << result << "\n"; + }()); +} diff --git a/subprojects/task/examples/alloc.cpp b/subprojects/task/examples/alloc.cpp new file mode 100644 index 000000000..5d01db575 --- /dev/null +++ b/subprojects/task/examples/alloc.cpp @@ -0,0 +1,118 @@ +// examples/alloc.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ex = beman::execution; + +#if defined(__has_feature) +#if __has_feature(thread_sanitizer) +#define TSAN_ENABLED +#endif +#endif +#if !defined(TSAN_ENABLED) +void* operator new(std::size_t size) { // NOLINT(misc-no-recursion) + void* pointer(std::malloc(size)); // NOLINT(hicpp-no-malloc) + std::cout << "global new(" << size << ")->" << pointer << "\n"; + return pointer; +} +void operator delete(void* pointer) noexcept { + std::cout << "global delete(" << pointer << ")\n"; + return std::free(pointer); // NOLINT(hicpp-no-malloc) +} +void operator delete(void* pointer, std::size_t size) noexcept { + std::cout << "global delete(" << pointer << ", " << size << ")\n"; + return std::free(pointer); // NOLINT(hicpp-no-malloc) +} +#endif + +template +class fixed_resource : public std::pmr::memory_resource { + std::array buffer{}; + std::byte* free{this->buffer.data()}; + + void* do_allocate(std::size_t size, std::size_t) override { + if (size <= std::size_t(buffer.data() + Size - free)) { + auto ptr{this->free}; + this->free += size; + std::cout << "resource alloc(" << size << ")->" << ptr << "\n"; + return ptr; + } else { + return nullptr; + } + } + void do_deallocate(void* ptr, std::size_t size, std::size_t) override { + std::cout << "resource dealloc(" << size << "+" << sizeof(std::pmr::polymorphic_allocator) << ")->" + << ptr << "\n"; + } + bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override { return &other == this; } +}; + +struct alloc_aware { + using allocator_type = std::pmr::polymorphic_allocator; +}; + +struct test_base { + template + void* operator new(std::size_t size, Args&&...) { + void* ptr{::operator new(size)}; + std::cout << "custom alloc(" << size << ") -> " << ptr << "\n"; + return ptr; + } + void operator delete(void* ptr, std::size_t size) { + std::cout << "custom dealloc(" << ptr << ", " << size << ")\n"; + ::operator delete(ptr, size); + } +}; +struct test : test_base {}; + +int main() { + std::cout << "running just\n"; + ex::sync_wait(ex::just(0)); + std::cout << "just done\n\n"; + + std::cout << "running ex::task\n"; + ex::sync_wait([]() -> ex::task { + co_await ex::just(0); + co_await ex::just(0); + }()); + std::cout << "ex::task done\n\n"; + + fixed_resource<2048> resource; + if constexpr (true) { + std::cout << "running ex::task with alloc\n"; + ex::sync_wait([](auto&&...) -> ex::task { + co_await ex::just(0); + co_await ex::just(0); + }(std::allocator_arg, &resource)); + std::cout << "ex::task with alloc done\n\n"; + } + + if constexpr (false) { + std::cout << "running ex::task with alloc\n"; + ex::sync_wait([](auto&&...) -> ex::task { + co_await ex::just(0); + co_await ex::just(0); + }(std::allocator_arg, &resource)); + std::cout << "ex::task with alloc done\n\n"; + } + + if constexpr (false) { + std::cout << "running ex::task extracting alloc\n"; + ex::sync_wait([](auto&&, [[maybe_unused]] auto* res) -> ex::task { + auto alloc = co_await ex::read_env(ex::get_allocator); + static_assert(std::same_as, decltype(alloc)>); + assert(alloc == std::pmr::polymorphic_allocator(res)); + }(std::allocator_arg, &resource)); + std::cout << "ex::task extracting alloc done\n\n"; + } + + delete new test{}; +} diff --git a/subprojects/task/examples/async-lock.cpp b/subprojects/task/examples/async-lock.cpp new file mode 100644 index 000000000..47bc7a117 --- /dev/null +++ b/subprojects/task/examples/async-lock.cpp @@ -0,0 +1,116 @@ +// examples/async-lock -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ex = beman::execution; + +namespace { +struct queue { + struct notify { + notify() = default; + notify(const notify&) = delete; + notify(notify&&) = delete; + virtual ~notify() = default; + notify& operator=(const notify&) = delete; + notify& operator=(notify&&) = delete; + virtual void complete(int) = 0; + }; + + std::mutex mutex; + std::condition_variable condition; + std::queue> work; + + void push(notify* n, int w) { + { + std::lock_guard cerberos{this->mutex}; + work.emplace(n, w); + } + condition.notify_one(); + } + std::tuple pop() { + std::unique_lock cerberos{this->mutex}; + condition.wait(cerberos, [this] { return not work.empty(); }); + auto result{work.front()}; + work.pop(); + return result; + } +}; + +struct request { + using sender_concept = ex::sender_tag; + using completion_signatures = ex::completion_signatures; + + template + struct state : queue::notify { + using operation_state_concept = ex::operation_state_tag; + Receiver receiver; + int value; + queue& que; + + template + state(R&& r, int val, queue& q) : receiver(std::forward(r)), value(val), que(q) {} + + void start() & noexcept { + try { + this->que.push(this, value); + } catch (...) { + std::terminate(); + } + } + void complete(int result) override { ex::set_value(std::move(this->receiver), result); } + }; + + int value; + queue& que; + + template + auto connect(Receiver&& receiver) { + return state>(std::forward(receiver), this->value, this->que); + } +}; + +void stop(queue& q) { ex::sync_wait(request{0, q}); } +} // namespace + +int main(int ac, char* av[]) { + try { + queue q{}; + + std::thread process([&q] { + while (true) { + auto [completion, request] = q.pop(); + completion->complete(2 * request); + if (request == 0) { + break; + } + } + }); + + auto work{[](queue& que) -> ex::task { + std::cout << std::this_thread::get_id() << " start\n" << std::flush; + auto result = co_await request{17, que}; + std::cout << std::this_thread::get_id() << " result=" << result << "\n" << std::flush; + stop(que); + }}; + + if (1 < ac && av[1] == std::string_view("run-it")) { + ex::sync_wait( + ex::detail::write_env(work(q), ex::detail::make_env(ex::get_scheduler, ex::inline_scheduler{}))); + } else { + ex::sync_wait(work(q)); + } + process.join(); + std::cout << "done\n"; + } catch (...) { + std::cout << "ERROR: unexpected exception\n"; + } +} diff --git a/subprojects/task/examples/bulk.cpp b/subprojects/task/examples/bulk.cpp new file mode 100644 index 000000000..aabb167ba --- /dev/null +++ b/subprojects/task/examples/bulk.cpp @@ -0,0 +1,27 @@ +// examples/bulk.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include + +namespace ex = beman::execution; +namespace beman::execution { +using parallel_scheduler = inline_scheduler; +} // namespace beman::execution + +// ---------------------------------------------------------------------------- + +int main() { + struct env { + auto query(ex::get_scheduler_t) const noexcept { return ex::parallel_scheduler(); } + }; + struct work { + auto operator()(std::size_t) { /*...*/ }; + }; + + ex::sync_wait(ex::write_env(ex::bulk(ex::just(), 16u, work{}), env{})); + + ex::sync_wait( + ex::write_env([]() -> ex::task> { co_await ex::bulk(ex::just(), 16u, work{}); }(), env{})); +} diff --git a/subprojects/task/examples/c++now-affinity.cpp b/subprojects/task/examples/c++now-affinity.cpp new file mode 100644 index 000000000..642fde8bf --- /dev/null +++ b/subprojects/task/examples/c++now-affinity.cpp @@ -0,0 +1,56 @@ +// examples/c++now-affinity.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include "demo-thread_loop.hpp" +#include +#include +#include +#include +#include +#include + +namespace ex = beman::execution; + +namespace { + +ex::task<> work1(auto sched) { + std::cout << "before id=" << std::this_thread::get_id() << "\n"; + co_await (ex::schedule(sched) | ex::then([] { std::cout << "then id =" << std::this_thread::get_id() << "\n"; })); + std::cout << "after id =" << std::this_thread::get_id() << "\n\n"; +} + +ex::task<> work2(auto sched) { + std::cout << "before id=" << std::this_thread::get_id() << "\n"; + auto s = co_await ex::change_coroutine_scheduler(ex::inline_scheduler()); + co_await (ex::schedule(sched) | ex::then([] { std::cout << "then id =" << std::this_thread::get_id() << "\n"; })); + std::cout << "after1 id=" << std::this_thread::get_id() << "\n"; + + co_await ex::change_coroutine_scheduler(s); + co_await (ex::schedule(sched) | ex::then([] { std::cout << "then id =" << std::this_thread::get_id() << "\n"; })); + std::cout << "after2 id=" << std::this_thread::get_id() << "\n\n"; +} + +struct inline_context { + using scheduler_type = ex::inline_scheduler; +}; + +ex::task work3(auto sched) { + std::cout << "before id=" << std::this_thread::get_id() << "\n"; + co_await (ex::schedule(sched) | ex::then([] { std::cout << "then id =" << std::this_thread::get_id() << "\n"; })); + std::cout << "after id =" << std::this_thread::get_id() << "\n\n"; +} +} // namespace + +int main() { + try { + demo::thread_loop loop; + ex::sync_wait(work1(loop.get_scheduler())); + ex::sync_wait(work2(loop.get_scheduler())); + ex::sync_wait(work3(loop.get_scheduler())); + } catch (...) { + std::cout << "ERROR: unexpected exception\n"; + } +} diff --git a/subprojects/task/examples/c++now-allocator.cpp b/subprojects/task/examples/c++now-allocator.cpp new file mode 100644 index 000000000..ce7d40ef8 --- /dev/null +++ b/subprojects/task/examples/c++now-allocator.cpp @@ -0,0 +1,28 @@ +// examples/c++now-alloctor.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include + +namespace ex = beman::execution; + +// ---------------------------------------------------------------------------- + +namespace { + +struct with_allocator { + using allocator_type = std::pmr::polymorphic_allocator; +}; + +ex::task coro(int value, auto&&...) { + co_await ex::just(value); + [[maybe_unused]] auto alloc = co_await ex::read_env(ex::get_allocator); +} +} // namespace + +int main() { + ex::sync_wait(coro(0)); + ex::sync_wait(coro(1, std::allocator_arg, std::pmr::new_delete_resource())); +} diff --git a/subprojects/task/examples/c++now-basic.cpp b/subprojects/task/examples/c++now-basic.cpp new file mode 100644 index 000000000..b53586fa3 --- /dev/null +++ b/subprojects/task/examples/c++now-basic.cpp @@ -0,0 +1,34 @@ +// examples/c++now-basic.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include + +namespace ex = beman::execution; + +// ---------------------------------------------------------------------------- + +namespace { +ex::task<> basic() { co_await std::suspend_never{}; } + +ex::task<> await_sender() { + co_await ex::just(); + [[maybe_unused]] int n = co_await ex::just(1); + [[maybe_unused]] auto [m, b] = co_await ex::just(1, true); + try { + co_await ex::just_error(1); + } catch (int e) { + std::cout << "caught " << e << '\n'; + } + co_await ex::just_stopped(); +} + +} // namespace + +int main() { + ex::sync_wait(std::suspend_never{}); + ex::sync_wait(basic()); + ex::sync_wait(await_sender()); +} diff --git a/subprojects/task/examples/c++now-cancel.cpp b/subprojects/task/examples/c++now-cancel.cpp new file mode 100644 index 000000000..60cf6fd8b --- /dev/null +++ b/subprojects/task/examples/c++now-cancel.cpp @@ -0,0 +1,15 @@ +// examples/c++now-cancel.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// ---------------------------------------------------------------------------- + +#include +#include + +namespace ex = beman::execution; + +// ---------------------------------------------------------------------------- +namespace { +ex::task<> cancel() { co_await ex::just_stopped(); } +} // namespace + +int main() { ex::sync_wait(cancel()); } diff --git a/subprojects/task/examples/c++now-errors.cpp b/subprojects/task/examples/c++now-errors.cpp new file mode 100644 index 000000000..f4fb468fd --- /dev/null +++ b/subprojects/task/examples/c++now-errors.cpp @@ -0,0 +1,84 @@ +// examples/c++now-errors.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include + +namespace ex = beman::execution; + +// ---------------------------------------------------------------------------- + +namespace { +struct none_t {}; +std::ostream& operator<<(std::ostream& out, none_t) { return out << ""; } + +template +struct identity_or_none; +template <> +struct identity_or_none<> { + using type = none_t; +}; +template <> +struct identity_or_none { + using type = none_t; +}; +template +struct identity_or_none { + using type = T; +}; +template +using identity_or_none_t = typename identity_or_none::type; + +#if 202202 <= __cpp_lib_expected +template +auto as_expected(Sender&& sndr) { + using value_type = ex::value_types_of_t, std::tuple, identity_or_none_t>; + using error_type = ex::error_types_of_t, identity_or_none_t>; + using result_type = std::expected; + + return std::forward(sndr) | + ex::then([](T&& x) noexcept { return result_type(std::forward(x)); }) | + ex::upon_error([](T&& x) noexcept { return result_type(std::unexpected(std::forward(x))); }); +} +#endif + +void print_expected(const char* msg, const auto& e) { + if (e) { + if constexpr (std::same_as>) + std::cout << msg << " (value)\n"; + else + std::cout << msg << " (value)" << std::get<0>(e.value()) << "\n"; + } else + std::cout << msg << " (error)" << e.error() << "\n"; +} + +// ---------------------------------------------------------------------------- + +ex::task<> error_result() { + try { + co_await ex::just_error(17); + } catch (int n) { + std::cout << "Error: " << n << "\n"; + } +} + +#if 202202 <= __cpp_lib_expected +ex::task<> expected() { + auto e = co_await as_expected(ex::just(17)); + print_expected("expected with value=", e); + auto u = co_await as_expected(ex::just_error(17)); + print_expected("expected without value=", u); +} +#endif +} // namespace + +int main() { + ex::sync_wait(error_result()); +#if 202202 <= __cpp_lib_expected + ex::sync_wait(expected()); +#endif +} diff --git a/subprojects/task/examples/c++now-query.cpp b/subprojects/task/examples/c++now-query.cpp new file mode 100644 index 000000000..369786ba6 --- /dev/null +++ b/subprojects/task/examples/c++now-query.cpp @@ -0,0 +1,66 @@ +// examples/c++now-query.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include + +namespace ex = beman::execution; +namespace exd = beman::execution::detail; + +// ---------------------------------------------------------------------------- + +namespace { + +constexpr struct get_value_t { + template + requires requires(const get_value_t& self, const Env& e) { e.query(self); } + decltype(auto) operator()(const Env& e) const { + return e.query(*this); + } + constexpr auto query(const ex::forwarding_query_t&) const noexcept -> bool { return true; } +} get_value{}; + +struct context { + int value{}; + explicit context(const auto& e) : value(get_value(e)) {} + int query(const get_value_t&) const { return this->value; } +}; + +ex::task with_env() { + decltype(auto) v = co_await ex::read_env(get_value); + std::cout << "with_env: v=" << v << "\n"; +} + +struct fancy { + struct env_base { + virtual int get() const = 0; + + env_base() = default; + env_base(const env_base&) = delete; + env_base(env_base&&) = delete; + virtual ~env_base() = default; + env_base& operator=(const env_base&) = delete; + env_base& operator=(env_base&&) = delete; + }; + template + struct env_type final : env_base { + explicit env_type(const Env& e) : env(e) {} + Env env; + int get() const override { return get_value(env); } + }; + int query(const get_value_t&) const { return this->env.get(); } + explicit fancy(const env_base& own) : env(own) {} + const env_base& env; +}; + +ex::task with_fancy_env() { + decltype(auto) v = co_await ex::read_env(get_value); + std::cout << "v=" << v << "\n"; +} +} // namespace + +int main() { + ex::sync_wait(exd::write_env(with_env(), exd::make_env(get_value, 17))); + ex::sync_wait(exd::write_env(with_fancy_env(), exd::make_env(get_value, 17))); +} diff --git a/subprojects/task/examples/c++now-result-types.cpp b/subprojects/task/examples/c++now-result-types.cpp new file mode 100644 index 000000000..bb1634290 --- /dev/null +++ b/subprojects/task/examples/c++now-result-types.cpp @@ -0,0 +1,20 @@ +// examples/c++now-result-types.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include + +namespace ex = beman::execution; + +// ---------------------------------------------------------------------------- + +namespace { +ex::task<> result_type() { + co_await ex::just(); + [[maybe_unused]] int n = co_await ex::just(1); + [[maybe_unused]] std::tuple p = co_await ex::just(1, true); +} +} // namespace + +int main() { ex::sync_wait(result_type()); } diff --git a/subprojects/task/examples/c++now-return.cpp b/subprojects/task/examples/c++now-return.cpp new file mode 100644 index 000000000..def18e211 --- /dev/null +++ b/subprojects/task/examples/c++now-return.cpp @@ -0,0 +1,45 @@ +// examples/c++now-return.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include + +namespace ex = beman::execution; + +// ---------------------------------------------------------------------------- + +namespace { + +struct E {}; +struct F {}; + +struct context { + using error_types = ex::completion_signatures; +}; + +struct empty_errors_context { + using error_types = ex::completion_signatures<>; +}; + +// ---------------------------------------------------------------------------- + +ex::task<> default_return() { co_return; } + +ex::task void_return() { co_return; } + +ex::task int_return() { co_return 17; } + +ex::task error_return() { co_return 17; } + +ex::task no_error_return() { co_return 17; } +} // namespace + +int main() { + ex::sync_wait(default_return()); + ex::sync_wait(void_return()); + [[maybe_unused]] auto [n] = ex::sync_wait(int_return()).value_or(std::tuple(0)); + std::cout << "n=" << n << "\n"; + [[maybe_unused]] auto [e] = ex::sync_wait(error_return()).value_or(std::tuple(0)); + [[maybe_unused]] auto [x] = ex::sync_wait(no_error_return()).value_or(std::tuple(0)); +} diff --git a/subprojects/task/examples/c++now-stop_token.cpp b/subprojects/task/examples/c++now-stop_token.cpp new file mode 100644 index 000000000..1957917de --- /dev/null +++ b/subprojects/task/examples/c++now-stop_token.cpp @@ -0,0 +1,39 @@ +// examples/c++now-stop_token.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include + +namespace ex = beman::execution; + +// ---------------------------------------------------------------------------- + +namespace { +ex::task<> stopping() { + auto token = co_await ex::read_env(ex::get_stop_token); + std::size_t count{}; + while (!token.stop_requested()) { + ++count; + } + std::cout << "count=" << count << "\n"; +} + +} // namespace + +int main() { + using namespace std::chrono_literals; + + ex::inplace_stop_source source; + std::thread thread([&] { + ex::sync_wait(ex::detail::write_env(stopping(), ex::detail::make_env(ex::get_stop_token, source.get_token()))); + }); + + std::this_thread::sleep_for(100ms); + source.request_stop(); + + thread.join(); +} diff --git a/subprojects/task/examples/c++now-with_error.cpp b/subprojects/task/examples/c++now-with_error.cpp new file mode 100644 index 000000000..8e672f4db --- /dev/null +++ b/subprojects/task/examples/c++now-with_error.cpp @@ -0,0 +1,63 @@ +// examples/c++now-with_error.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include + +namespace ex = beman::execution; + +// ---------------------------------------------------------------------------- + +namespace { +void unreachable([[maybe_unused]] const char* msg) { assert(nullptr == msg); } + +struct E { + int value; +}; + +struct context { + using error_types = ex::completion_signatures; +}; + +// ---------------------------------------------------------------------------- + +ex::task error_return(int arg) noexcept { + try { + if (arg == 1) + co_yield ex::with_error{E{arg}}; + co_return arg * 17; + } catch (...) { + unreachable("no error should escape co_yield with_error{E{0}}"); + std::terminate(); + } +} + +struct ctxt { + using error_types = ex::completion_signatures; +}; + +ex::task call(int v) { + if (v == 1) + co_yield ex::with_error{-1}; + co_return 2 * v; +} +} // namespace + +int main(int ac, char*[]) { + for (int i{}; i != 3; ++i) { + auto [r] = ex::sync_wait(error_return(i) | ex::upon_error([](auto x) { + std::cout << "error: " << x.value << "\n"; + return -1; + })) + .value_or(std::tuple(0)); + std::cout << i << ": r=" << r << "\n"; + } + + [[maybe_unused]] auto [n] = ex::sync_wait(call(ac) | ex::upon_error([](int e) { + std::cout << "error(" << e << "\n"; + return -1; + })) + .value_or(std::tuple(0)); +} diff --git a/subprojects/task/examples/co_await-result.cpp b/subprojects/task/examples/co_await-result.cpp new file mode 100644 index 000000000..6fa7b0a0c --- /dev/null +++ b/subprojects/task/examples/co_await-result.cpp @@ -0,0 +1,40 @@ +// examples/co_await-result.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include + +namespace ex = beman::execution; + +namespace { +void unreachable([[maybe_unused]] const char* msg) { assert(nullptr == msg); } +} // namespace +// ---------------------------------------------------------------------------- + +int main() { + [[maybe_unused]] auto o = ex::sync_wait([]() -> ex::task<> { + co_await ex::just(); // void + std::cout << "after co_await ex::just()\n"; + [[maybe_unused]] auto v = co_await ex::just(42); // int + assert(v == 42); + [[maybe_unused]] auto [i, b, c] = co_await ex::just(17, true, 'c'); // tuple + assert(i == 17 && b == true && c == 'c'); + try { + co_await ex::just_error(-1); // exception + unreachable("co_await just_error(...) result in an exception"); + } catch ([[maybe_unused]] int e) { + assert(e == -1); + } + std::cout << "about to cancel\n"; + try { + co_await ex::just_stopped(); + } catch (...) { + unreachable("co_await just_stopped(...) doesn't result in an exception"); + } // cancel: never resumed + unreachable("co_await just_stopped(...) result in the coroutine not to be resumed"); + }()); + assert(not o); + std::cout << "done\n"; +} diff --git a/subprojects/task/examples/co_await-task.cpp b/subprojects/task/examples/co_await-task.cpp new file mode 100644 index 000000000..6a451d0eb --- /dev/null +++ b/subprojects/task/examples/co_await-task.cpp @@ -0,0 +1,24 @@ +// examples/co_await-task.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include + +namespace ex = beman::execution; + +// ---------------------------------------------------------------------------- + +auto inner() -> ex::task<> { co_return; } + +auto outer() -> ex::task<> { + for (int i{}; i < 10; ++i) { + co_await inner(); + } + co_return; +} + +auto main() -> int { + std::cout << std::unitbuf; + ex::sync_wait(outer()); +} diff --git a/subprojects/task/examples/container.cpp b/subprojects/task/examples/container.cpp new file mode 100644 index 000000000..7a538f228 --- /dev/null +++ b/subprojects/task/examples/container.cpp @@ -0,0 +1,26 @@ +// examples/container.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +namespace ex = beman::execution; + +// ---------------------------------------------------------------------------- + +namespace { +void unreachable([[maybe_unused]] const char* msg) { assert(nullptr != msg); } +} // namespace + +int main() { + try { + std::vector> cont; + cont.emplace_back([]() -> ex::task<> { co_return; }()); + cont.push_back([]() -> ex::task<> { co_return; }()); + } catch (...) { + unreachable("no exception should escape to main"); + } +} diff --git a/subprojects/task/examples/customize.cpp b/subprojects/task/examples/customize.cpp new file mode 100644 index 000000000..0a5e1631c --- /dev/null +++ b/subprojects/task/examples/customize.cpp @@ -0,0 +1,31 @@ +// examples/customize.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +namespace ex = beman::execution; + +namespace { +struct empty {}; +} // namespace + +int main() { +#if 0 + auto task = []()->ex::task { co_return; }(); + static_assert(ex::sender); + auto env = ex::get_env(task); + static_assert(std::same_as); + auto dom = ex::get_domain(env); + static_assert(std::same_as); + auto edom = ex::detail::get_domain_early(task); + static_assert(std::same_as); + std::cout << "---\n"; + transform_sender(edom, ex::detail::make_sender(ex::affine_on, ex::inline_scheduler{}, std::move(task))); + std::cout << "---\n"; + auto affine = ex::affine_on(std::move(task), ex::inline_scheduler{}); +#endif +} diff --git a/subprojects/task/examples/dangling-references.cpp b/subprojects/task/examples/dangling-references.cpp new file mode 100644 index 000000000..501ba6745 --- /dev/null +++ b/subprojects/task/examples/dangling-references.cpp @@ -0,0 +1,34 @@ +// examples/dangling-references.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +namespace ex = beman::execution; + +// ---------------------------------------------------------------------------- + +namespace { +ex::task> do_work(std::string) { /* work */ co_return 0; }; +ex::task> execute_all() { + co_await ex::when_all(do_work("arguments 1"), do_work("arguments 2")); + co_return; +} + +struct error_env { + using error_types = ex::completion_signatures; +}; +} // namespace + +int main() { + ex::sync_wait([]() -> ex::task, error_env> { co_return ex::with_error{42}; }()); + + ex::sync_wait(execute_all()); + ex::sync_wait([]() -> ex::task> { + auto t = [](const int /* this would be added: &*/ v) -> ex::task> { co_return v; }(42); + [[maybe_unused]] auto v = co_await std::move(t); + }()); +} diff --git a/subprojects/task/examples/demo-scope.hpp b/subprojects/task/examples/demo-scope.hpp new file mode 100644 index 000000000..d1c2efb6c --- /dev/null +++ b/subprojects/task/examples/demo-scope.hpp @@ -0,0 +1,91 @@ +// examples/demo_scope.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_EXAMPLES_DEMO_SCOPE +#define INCLUDED_EXAMPLES_DEMO_SCOPE + +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace demo { +namespace ex = ::beman::net::detail::ex; + +class scope { + private: + static constexpr bool log_completions{false}; + struct env { + scope* self; + + auto query(ex::get_stop_token_t) const noexcept { return this->self->source.get_token(); } + }; + + struct job_base { + virtual ~job_base() = default; + }; + + struct receiver { + using receiver_concept = ex::receiver_tag; + scope* self; + job_base* state{}; + + auto set_error(auto&&) noexcept -> void { + ::std::cerr << "ERROR: demo::scope::job in scope completed with error!\n"; + this->complete(); + } + auto set_value() && noexcept -> void { + if (log_completions) + std::cout << "demo::scope::set_value()\n"; + this->complete(); + } + auto set_stopped() && noexcept -> void { + if (log_completions) + std::cout << "demo::scope::set_stopped()\n"; + this->complete(); + } + auto complete() -> void { + scope* slf{this->self}; + delete this->state; + if (0u == --slf->count) { + slf->complete(); + } + } + auto get_env() const noexcept -> env { return {this->self}; } + }; + + template + struct job : job_base { + using state_t = decltype(ex::connect(std::declval(), std::declval())); + state_t state; + template + job(scope* self, S&& sender) : state(ex::connect(::std::forward(sender), receiver{self, this})) { + ex::start(this->state); + } + }; + + std::atomic count{}; + ex::inplace_stop_source source; + + auto complete() -> void {} + + public: + ~scope() { + if (0u < this->count) + std::cerr << "ERROR: scope destroyed with live jobs: " << this->count << "\n"; + } + template + auto spawn(Sender&& sender) { + ++this->count; + new job(this, std::forward(sender)); + } + auto stop() -> void { this->source.request_stop(); } + auto empty() const -> bool { return 0u == this->count; } +}; +} // namespace demo + +// ---------------------------------------------------------------------------- + +#endif diff --git a/subprojects/task/examples/demo-thread_loop.hpp b/subprojects/task/examples/demo-thread_loop.hpp new file mode 100644 index 000000000..bedbe9d9d --- /dev/null +++ b/subprojects/task/examples/demo-thread_loop.hpp @@ -0,0 +1,30 @@ +// examples/demo-thread_loop.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_EXAMPLES_DEMO_THREAD_LOOP +#define INCLUDED_EXAMPLES_DEMO_THREAD_LOOP + +// ---------------------------------------------------------------------------- + +#include +#include +#include + +namespace demo { + +class thread_loop : public ::beman::execution::run_loop { + private: + std::thread thread{std::bind(&thread_loop::run, this)}; + + public: + ~thread_loop() { + this->finish(); + this->thread.join(); + } +}; + +} // namespace demo + +// ---------------------------------------------------------------------------- + +#endif diff --git a/subprojects/task/examples/demo-tls_scheduler.hpp b/subprojects/task/examples/demo-tls_scheduler.hpp new file mode 100644 index 000000000..772b54faf --- /dev/null +++ b/subprojects/task/examples/demo-tls_scheduler.hpp @@ -0,0 +1,171 @@ +// examples/demo-tls_scheduler.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_EXAMPLES_DEMO_TLS_SCHEDULER +#define INCLUDED_EXAMPLES_DEMO_TLS_SCHEDULER + +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace demo { +struct tls_domain { + template <::beman::execution::receiver Rcvr, typename Data> + struct affine_state_base { + std::remove_cvref_t rcvr; + Data data{}; + + auto complete() { + std::cout << "affine_state::complete\n"; + this->data.restore(); + ::beman::execution::set_value(::std::move(this->rcvr)); + } + }; + + template <::beman::execution::receiver Rcvr, typename Data> + struct affine_receiver { + using receiver_concept = ::beman::execution::receiver_tag; + struct env_t { + affine_state_base* st; + template + requires requires(affine_state_base* self, Q&& q, A&&... a) { + std::forward(q)(::beman::execution::get_env(self->st->rcvr), ::std::forward(a)...); + } + auto query(Q&& q, A&&... a) const noexcept { + std::forward(q)(::beman::execution::get_env(this->st->rcvr, ::std::forward(a)...)); + } + }; + affine_state_base* st; + + template + auto set_error(E&& e) && noexcept -> void { + ::beman::execution::set_error(::std::move(this->st->rcvr), ::std::forward(e)); + } + auto set_stopped() && noexcept -> void { ::beman::execution::set_stopped(::std::move(this->st->rcvr)); } + auto set_value() && noexcept -> void { this->st->complete(); } + + auto get_env() const noexcept -> env_t { return {this->st}; } + }; + + template <::beman::execution::sender Sndr, ::beman::execution::scheduler Sch, ::beman::execution::receiver Rcvr> + struct affine_state : affine_state_base { + using operation_state_concept = ::beman::execution::operation_state_tag; + using env_t = decltype(::beman::execution::get_env(std::declval())); + using base_t = affine_state_base; + using data_t = typename Sch::type; + using state_t = decltype(::beman::execution::connect( + ::beman::execution::affine_on(::std::declval(), ::std::declval()), + ::std::declval>())); + + state_t state; + + template <::beman::execution::sender S, ::beman::execution::scheduler SC, ::beman::execution::receiver R> + affine_state(S&& s, SC&& sc, R&& r) + : base_t{::std::forward(r)}, + state(::beman::execution::connect( + ::beman::execution::affine_on(::std::forward(s), ::std::forward(sc)), + affine_receiver{this})) {} + auto start() & noexcept -> void { + std::cout << "affine_state::start\n"; + this->data.save(); + ::beman::execution::start(this->state); + } + }; + template <::beman::execution::sender Sndr, ::beman::execution::scheduler Sch> + struct affine_sender { + using sender_concept = ::beman::execution::sender_tag; + + std::remove_cvref_t sndr; + std::remove_cvref_t sch; + + template + auto get_completion_signatures(const Env& env) const noexcept { + return ::beman::execution::get_completion_signatures(this->sndr, env); + } + template <::beman::execution::receiver Rcvr> + auto connect(Rcvr&& rcvr) const& { + return affine_state(sndr, sch, std::forward(rcvr)); + } + template <::beman::execution::receiver Rcvr> + auto connect(Rcvr&& rcvr) && { + return affine_state(std::move(sndr), std::move(sch), std::forward(rcvr)); + } + }; + template <::beman::execution::sender Sndr, typename... Env> + requires std::same_as<::beman::execution::tag_of_t<::std::remove_cvref_t>, + ::beman::execution::affine_on_t> + auto transform_sender(Sndr&& s, const Env&...) const noexcept { + auto [tag, sch, sndr] = s; + return affine_sender{::beman::execution::detail::forward_like(sndr), + ::beman::execution::detail::forward_like(sch)}; + } +}; + +template +struct tls_scheduler { + using scheduler_concept = ::beman::execution::scheduler_tag; + using type = Data; + struct env { + Scheduler sched; + template + auto query(const ::beman::execution::get_completion_scheduler_t&) const noexcept -> tls_scheduler { + return {this->sched}; + } + }; + template <::beman::execution::receiver Receiver> + struct state { + using operation_state_concept = ::beman::execution::operation_state_tag; + using upstream_state_t = decltype(::beman::execution::connect( + ::beman::execution::schedule(std::declval()), std::declval>())); + + upstream_state_t upstream; + + template <::beman::execution::sender Sndr, ::beman::execution::receiver Rcvr> + state(Sndr&& sndr, Rcvr&& rcvr) + : upstream(::beman::execution::connect(std::forward(sndr), std::forward(rcvr))) {} + auto start() & noexcept -> void { ::beman::execution::start(this->upstream); } + }; + struct tls_sender { + using sender_concept = ::beman::execution::sender_tag; + using sender_type = decltype(::beman::execution::schedule(std::declval())); + + Scheduler sched; + + template + auto get_completion_signatures(const Env&... env) const noexcept { + return ::beman::execution::get_completion_signatures(::beman::execution::schedule(Scheduler(this->sched)), + env...); + } + auto get_env() const noexcept -> env { return {this->sched}; } + + template <::beman::execution::receiver Receiver> + auto connect(Receiver&& rcvr) -> state { + return state(::beman::execution::schedule(this->sched), std::forward(rcvr)); + } + }; + + Scheduler sched; + template + requires(!std::same_as>) + tls_scheduler(Sch&& sch) : sched(sch) {} + tls_scheduler(const tls_scheduler&) = default; + tls_scheduler(tls_scheduler&&) = default; + tls_scheduler& operator=(const tls_scheduler&) = default; + tls_scheduler& operator=(tls_scheduler&&) = default; + + auto schedule() -> tls_sender { return {this->sched}; } + + auto query(const ::beman::execution::get_domain_t&) const noexcept -> tls_domain { return {}; } + + auto operator==(const tls_scheduler&) const -> bool = default; +}; +static_assert(::beman::execution::scheduler< + tls_scheduler().get_scheduler())>>); + +} // namespace demo + +// ---------------------------------------------------------------------------- + +#endif diff --git a/subprojects/task/examples/environment.cpp b/subprojects/task/examples/environment.cpp new file mode 100644 index 000000000..808046aa5 --- /dev/null +++ b/subprojects/task/examples/environment.cpp @@ -0,0 +1,159 @@ +// examples/environment.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef _MSC_VER +#include +#include +#include +#include +#include +#include +#include +#include +#include "demo-scope.hpp" +#include "demo-thread_loop.hpp" + +namespace ex = beman::execution; +namespace net = beman::net; +using namespace std::chrono_literals; + +// ---------------------------------------------------------------------------- + +template +void spawn(Sched&& sched, demo::scope& scope, Sender&& sender) { + scope.spawn(ex::detail::write_env(std::forward(sender), + ex::detail::make_env(ex::get_scheduler, std::forward(sched)))); +} + +// ---------------------------------------------------------------------------- + +class environment { + static thread_local std::string name; + + public: + static auto get() -> std::string { return name; } + static auto set(const std::string& n) -> void { name = n; } +}; +thread_local std::string environment::name{""}; + +struct env_scheduler { + using scheduler_concept = ex::scheduler_tag; + + std::string name; + ex::task_scheduler scheduler; + + template + env_scheduler(std::string n, Sched&& sched) : name(std::move(n)), scheduler(std::forward(sched)) {} + + template + struct receiver { + using receiver_concept = ex::receiver_tag; + + std::remove_cvref_t rcvr; + std::string name; + + receiver(Rcvr&& r, std::string n) : rcvr(std::forward(r)), name(std::move(n)) {} + auto get_env() const noexcept { return ex::get_env(this->rcvr); } + auto set_value() && noexcept { + environment::set(std::move(this->name)); + ex::set_value(std::move(this->rcvr)); + } + template + auto set_error(E&& e) && noexcept { + environment::set(std::move(this->name)); + ex::set_error(std::move(this->rcvr), std::forward(e)); + } + auto set_stopped() && noexcept { + environment::set(std::move(this->name)); + ex::set_stopped(std::move(this->rcvr)); + } + }; + + struct env { + std::string name; + ex::task_scheduler scheduler; + template + auto query(const ex::get_completion_scheduler_t&) const noexcept { + return env_scheduler(this->name, this->scheduler); + } + }; + + struct sender { + using sender_concept = ex::sender_tag; + using task_sender = decltype(ex::schedule(std::declval())); + template + auto get_completion_signatures(const E& e) const noexcept { + return ex::get_completion_signatures(this->sender, e); + } + + std::string name; + task_sender sender; + + auto get_env() const noexcept -> env { + return env{this->name, ex::get_completion_scheduler(ex::get_env(this->sender))}; + } + + template + auto connect(Rcvr&& rcvr) && { + return ex::connect(std::move(this->sender), + receiver(std::forward(rcvr), std::move(this->name))); + } + }; + + auto schedule() -> sender { return sender{this->name, ex::schedule(this->scheduler)}; } + bool operator==(const env_scheduler&) const = default; +}; + +struct with_env { + using scheduler_type = env_scheduler; +}; + +// ---------------------------------------------------------------------------- + +std::ostream& print_env(std::ostream& out) { + return out << "tid=" << std::this_thread::get_id() << " " + << "env=" << environment::get(); +} + +ex::task run(auto scheduler, auto duration) { + std::cout << print_env << " duration=" << duration << " start\n" << std::flush; + for (int i = 0; i != 4; ++i) { + co_await net::resume_after(scheduler, duration); + std::cout << print_env << " duration=" << duration << "\n" << std::flush; + } + std::cout << print_env << " duration=" << duration << " done\n" << std::flush; +} + +const std::string text("####"); +[[maybe_unused]] const std::string black("\x1b[30m" + text + "\x1b[0m:"); +[[maybe_unused]] const std::string red("\x1b[31m" + text + "\x1b[0m:"); +[[maybe_unused]] const std::string green("\x1b[32m" + text + "\x1b[0m:"); +[[maybe_unused]] const std::string yellow("\x1b[33m" + text + "\x1b[0m:"); +[[maybe_unused]] const std::string blue("\x1b[34m" + text + "\x1b[0m:"); +[[maybe_unused]] const std::string magenta("\x1b[35m" + text + "\x1b[0m:"); +[[maybe_unused]] const std::string cyan("\x1b[36m" + text + "\x1b[0m:"); +[[maybe_unused]] const std::string white("\x1b[37m" + text + "\x1b[0m:"); + +int main() { + demo::thread_loop loop1; + demo::thread_loop loop2; + net::io_context context; + demo::scope scope; + + environment::set("main"); + + ex::sync_wait(ex::schedule(loop1.get_scheduler()) | ex::then([] { environment::set("thread1"); }) | + ex::then([] { std::cout << print_env << "\n"; })); + std::cout << print_env << "\n"; + + spawn(env_scheduler(magenta, loop1.get_scheduler()), scope, run(context.get_scheduler(), 100ms)); + spawn(env_scheduler(green, loop1.get_scheduler()), scope, run(context.get_scheduler(), 150ms)); + spawn(env_scheduler(blue, loop1.get_scheduler()), scope, run(context.get_scheduler(), 250ms)); + + while (!scope.empty()) { + context.run(); + } +} +#else +int main() {} +#endif diff --git a/subprojects/task/examples/error.cpp b/subprojects/task/examples/error.cpp new file mode 100644 index 000000000..ca4c14b28 --- /dev/null +++ b/subprojects/task/examples/error.cpp @@ -0,0 +1,64 @@ +// examples/error.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include + +namespace ex = beman::execution; + +namespace { +struct error_context { + using error_types = + ex::completion_signatures; +}; + +ex::task fun(int i) { + using on_exit = std::unique_ptr; + on_exit msg("> "); + std::cout << "<"; + + switch (i) { + default: + break; + case 0: + // successful execution: a later `co_return 0;` completes + co_await ex::just(0); + break; + case 1: + // the co_awaited work fails and an exception is thrown + co_await ex::just_error(0); + break; + case 2: + // the co_awaited work is stopped and the coroutine is stopped itself + co_await ex::just_stopped(); + break; + case 3: + // successful return of a value + co_return 17; + case 4: + co_yield ex::with_error{std::make_error_code(std::errc::io_error)}; + break; + } + + co_return 0; +} + +template +void print(const char* what, const E& e) { + if constexpr (std::same_as) + std::cout << what << "(exception_ptr)\n"; + else + std::cout << what << "(" << e << ")\n"; +} +} // namespace + +int main() { + for (int i{}; i != 5; ++i) { + std::cout << "i=" << i << " "; + ex::sync_wait(fun(i) | ex::then([](auto e) { print("then", e); }) | + ex::upon_error([](const auto& e) { print("upon_error", e); }) | + ex::upon_stopped([] { std::cout << "stopped\n"; })); + } +} diff --git a/subprojects/task/examples/escaped-exception.cpp b/subprojects/task/examples/escaped-exception.cpp new file mode 100644 index 000000000..5d815e691 --- /dev/null +++ b/subprojects/task/examples/escaped-exception.cpp @@ -0,0 +1,30 @@ +// examples/escaped-exception.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include + +namespace ex = beman::execution; + +struct my_exception : std::exception { + const char* what() const noexcept override { return "my_exception"; } +}; + +int main() { + try { + ex::sync_wait([]() -> ex::task { +#ifndef __clang__ + //-dk:TODO determine what's up with clang + throw my_exception{}; +#endif + co_return 0; + }()); + std::cout << "not reached!\n"; + } catch (const std::exception& ex) { + std::cout << "ERROR: " << ex.what() << "\n"; + } catch (...) { + std::cout << "ERROR: unknown exception\n"; + } +} diff --git a/subprojects/task/examples/friendly.cpp b/subprojects/task/examples/friendly.cpp new file mode 100644 index 000000000..adb921393 --- /dev/null +++ b/subprojects/task/examples/friendly.cpp @@ -0,0 +1,17 @@ +// examples/friendly.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include + +namespace ex = beman::execution; + +int main() { + ex::sync_wait([]() -> ex::task { co_await ex::just(); }()); + ex::sync_wait([]() -> ex::task { co_await std::suspend_never(); }()); +} diff --git a/subprojects/task/examples/hello.cpp b/subprojects/task/examples/hello.cpp new file mode 100644 index 000000000..bf98974f9 --- /dev/null +++ b/subprojects/task/examples/hello.cpp @@ -0,0 +1,16 @@ +// examples/hello.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include + +namespace ex = beman::execution; + +int main() { + return std::get<0>(ex::sync_wait([]() -> ex::task { + std::cout << "Hello, world!\n"; + co_return co_await ex::just(0); + }()) + .value_or(std::tuple(-1))); +} diff --git a/subprojects/task/examples/http-server.cpp b/subprojects/task/examples/http-server.cpp new file mode 100644 index 000000000..c91f57bb9 --- /dev/null +++ b/subprojects/task/examples/http-server.cpp @@ -0,0 +1,37 @@ +// examples/http-server.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include + +namespace ex = beman::execution; +namespace net = beman::net; + +// ---------------------------------------------------------------------------- + +namespace { +auto sched_spawn(auto&& scheduler, auto&& sender, auto&& token) { + return ex::spawn( + ex::write_env(std::forward(sender) | + ex::upon_error([](auto&&) noexcept { std::cout << "ERROR!\n"; }), + ex::detail::make_env(ex::get_scheduler, std::forward(scheduler))), + std::forward(token)); +} +} // namespace + +int main() { + net::io_context context{}; + ex::counting_scope scope{}; + + sched_spawn( + context.get_scheduler(), ex::just() | ex::then([]() noexcept { std::cout << "hello\n"; }), scope.get_token()); + sched_spawn( + context.get_scheduler(), + []() -> ex::task<> { + std::cout << "hello\n"; + co_return; + }(), + scope.get_token()); + ex::sync_wait(scope.join()); +} diff --git a/subprojects/task/examples/into_optional.cpp b/subprojects/task/examples/into_optional.cpp new file mode 100644 index 000000000..badfd2c7c --- /dev/null +++ b/subprojects/task/examples/into_optional.cpp @@ -0,0 +1,76 @@ +// examples/into_optional.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include + +namespace ex = beman::execution; + +namespace { +template +struct multi_sender { + using sender_concept = ex::sender_tag; + using completion_signatures = ex::completion_signatures; + + template + auto connect(Receiver&& receiver) const { + return ex::connect(ex::just(), std::forward(receiver)); + } +}; + +template +struct queue { + multi_sender async_pop() { return {}; } +}; + +template +auto to_optional(V& v) { + using result_type = decltype([] { + if constexpr (sizeof...(T) == 1u) + return std::optional{}; + else + return std::optional>{}; + }()); + return std::visit( + [](std::tuple& a) { + if constexpr (sizeof...(X) == 0u) + return result_type{}; + else if constexpr (sizeof...(X) == 1u) + return result_type(std::move(std::get<0>(a))); + else + return result_type(std::move(a)); + }, + v); +} + +template +auto my_into_optional(S&& s) { + return ex::into_variant(s) | ex::then([](auto&&...) { return std::optional(); }) +#if 0 + //-dk:TODO see if this code is needed or can be removed + | ex::then([]( + std::variant, std::tuple> r) { + if constexpr (sizeof...(A) != 0u) { + return to_optional(r); + } + else { + return to_optional(r); + } + }) +#endif + ; +} +} // namespace + +int main() { + queue que; + ex::sync_wait([](auto& q) -> ex::task<> { + // auto x = co_await ex::just(true) | into_optional; + [[maybe_unused]] std::optional x = co_await (q.async_pop() | ex::into_optional); + [[maybe_unused]] std::optional y = co_await ex::into_optional(q.async_pop()); + }(que)); +} diff --git a/subprojects/task/examples/issue-affine_on.cpp b/subprojects/task/examples/issue-affine_on.cpp new file mode 100644 index 000000000..b5894dd1d --- /dev/null +++ b/subprojects/task/examples/issue-affine_on.cpp @@ -0,0 +1,21 @@ +// examples/issue-affine_on.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include + +namespace ex = beman::execution; + +template +ex::task<> test(Sender&& sender) { + co_await std::move(sender); +} + +int main() { + ex::sync_wait(test(ex::just())); + ex::sync_wait(test(ex::read_env(ex::get_scheduler))); + ex::sync_wait(test(ex::read_env(ex::get_scheduler) | ex::let_value([](auto) noexcept { return ex::just(); }))); + ex::sync_wait(test(ex::read_env(ex::get_scheduler) | + ex::let_value([](auto sched) { return ex::starts_on(sched, ex::just()); }))); +} diff --git a/subprojects/task/examples/issue-frame-allocator.cpp b/subprojects/task/examples/issue-frame-allocator.cpp new file mode 100644 index 000000000..0c92b2cd6 --- /dev/null +++ b/subprojects/task/examples/issue-frame-allocator.cpp @@ -0,0 +1,26 @@ +// examples/issue-frame-allocator.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +namespace ex = beman::execution; + +template +ex::task test(int i, auto&&...) { + co_return co_await ex::just(i); +} +struct default_env {}; +struct allocator_env { + using allocator_type = std::pmr::polymorphic_allocator<>; +}; + +int main() { + ex::sync_wait(test(17)); // OK: no allocator + ex::sync_wait(test(17, std::allocator_arg, std::allocator())); // OK: allocator is convertible + // ex::sync_wait(test(17, std::allocator_arg, std::pmr::polymorphic_allocator<>())); // error + ex::sync_wait(test(17, std::allocator_arg, std::pmr::polymorphic_allocator<>())); // OK but unusual +} diff --git a/subprojects/task/examples/issue-start-reschedules.cpp b/subprojects/task/examples/issue-start-reschedules.cpp new file mode 100644 index 000000000..4d2a1e9ae --- /dev/null +++ b/subprojects/task/examples/issue-start-reschedules.cpp @@ -0,0 +1,36 @@ +// examples/issue-start-reschedules.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include "demo-thread_loop.hpp" +#include +#include + +namespace ex = beman::execution; + +ex::task<> test(auto sched) { + std::cout << "init =" << std::this_thread::get_id() << "\n"; + co_await ex::starts_on(sched, ex::just()); + // static_assert(std::same_as{}))>); + co_await ex::just(); + std::cout << "final=" << std::this_thread::get_id() << "\n"; +} + +int main() { + demo::thread_loop loop1; + demo::thread_loop loop2; + std::cout << "main =" << std::this_thread::get_id() << "\n"; + ex::sync_wait(ex::schedule(loop1.get_scheduler()) | + ex::then([] { std::cout << "loop1=" << std::this_thread::get_id() << "\n"; })); + ex::sync_wait(ex::schedule(loop2.get_scheduler()) | + ex::then([] { std::cout << "loop2=" << std::this_thread::get_id() << "\n"; })); + try { + std::cout << "--- use 1 ---\n"; + ex::sync_wait(test(loop2.get_scheduler())); + std::cout << "--- use 2 ---\n"; + // ex::sync_wait(ex::starts_on(loop1.get_scheduler(), test(loop2.get_scheduler()))); + } catch (...) { + } +} diff --git a/subprojects/task/examples/issue-symmetric-transfer.cpp b/subprojects/task/examples/issue-symmetric-transfer.cpp new file mode 100644 index 000000000..f6455cfeb --- /dev/null +++ b/subprojects/task/examples/issue-symmetric-transfer.cpp @@ -0,0 +1,29 @@ +// examples/issue-symmetric-transfer.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include + +namespace ex = beman::execution; + +template +ex::task test() { + for (std::size_t i{}; i < 1000000; ++i) { + co_await std::invoke([]() -> ex::task<> { co_await ex::when_all(ex::just()); }); + } +} + +struct affine_env {}; +struct inline_env { + using scheduler_type = ex::inline_scheduler; +}; + +int main() { + [[maybe_unused]] affine_env ae{}; + [[maybe_unused]] inline_env ie{}; +#ifndef _MSC_VER + ex::sync_wait(test()); // OK +#endif + // ex::sync_wait(test()); // error: stack overflow without symmetric transfer +} diff --git a/subprojects/task/examples/loop.cpp b/subprojects/task/examples/loop.cpp new file mode 100644 index 000000000..766e7af42 --- /dev/null +++ b/subprojects/task/examples/loop.cpp @@ -0,0 +1,97 @@ +// examples/loop.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +namespace ex = beman::execution; + +namespace { +struct run_loop_env { + using scheduler_type = decltype(std::declval().get_scheduler()); +}; + +template +struct log_scheduler { + using scheduler_concept = ex::scheduler_tag; + + std::remove_cvref_t scheduler; + + log_scheduler(auto&& sched) : scheduler(std::forward(sched)) {} + + struct sender { + using sender_concept = ex::sender_tag; + using completion_signatures = ex::completion_signatures; + using up_sender = decltype(ex::schedule(std::declval())); + + up_sender _sender; + + template + struct state { + using operation_state_concept = ex::operation_state_tag; + using up_state_t = decltype(ex::connect(std::declval(), std::declval())); + + up_state_t _state; + + template + state(S&& sender, R&& receiver) + : _state(ex::connect(std::forward(sender), std::forward(receiver))) {} + + auto start() & noexcept -> void { + std::cout << "start scheduler: " << &this->_state << "\n"; + ex::start(this->_state); + } + }; + + struct env { + log_scheduler _sched; + auto query(const ex::get_completion_scheduler_t&) const noexcept -> log_scheduler { + return this->_sched; + } + }; + auto get_env() const noexcept -> env { + return {ex::get_completion_scheduler(ex::get_env(this->_sender))}; + } + template + auto connect(Receiver&& receiver) noexcept -> state> { + return {this->_sender, std::forward(receiver)}; + } + }; + + auto schedule() noexcept { return sender{ex::schedule(this->scheduler)}; } + auto operator==(const log_scheduler&) const noexcept -> bool = default; +}; +template +log_scheduler(Scheduler&&) -> log_scheduler>; + +static_assert(ex::scheduler>); +static_assert(ex::scheduler>); +} // namespace + +namespace { +[[maybe_unused]] ex::task loop(auto count) { + for (decltype(count) i{}; i < count; ++i) + // co_await ex::just(i); + co_await [](int x) -> ex::task<> { + std::cout << "before co_await: " << &x << ": " << x << "\n"; + co_await (ex::just(x) | ex::then([](int v) { std::cout << &v << ": " << v << "\n"; })); + std::cout << "after co_await: " << &x << ": " << x << "\n"; + }(i); +} +} // namespace + +int main(int ac, char* av[]) { + auto count = 1 < ac && av[1] == std::string_view("run-it") ? 1000000 : 1000; + ex::sync_wait(loop(count)); + ex::sync_wait([](std::size_t count) -> ex::task { + co_await ex::write_env( + loop(count), + ex::detail::make_env(ex::get_scheduler, log_scheduler(co_await ex::read_env(ex::get_scheduler)))); + }(count)); +#if 0 + ex::sync_wait(ex::detail::write_env(loop(count), ex::detail::make_env(ex::get_scheduler, ex::inline_scheduler{}))); +#endif +} diff --git a/subprojects/task/examples/odd-completions.cpp b/subprojects/task/examples/odd-completions.cpp new file mode 100644 index 000000000..efa196a94 --- /dev/null +++ b/subprojects/task/examples/odd-completions.cpp @@ -0,0 +1,28 @@ +// examples/hello.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include + +namespace ex = beman::execution; + +int main() { + return std::get<0>(ex::sync_wait([]() -> ex::task { + std::cout << "Hello, world!\n"; + co_return co_await ex::just(0); + }()) + .value_or(std::tuple(-1))); + ex::sync_wait([](int value) -> ex::task { + switch (value) { + default: + co_return value; + case -1: + co_yield ex::with_error(std::make_exception_ptr(value)); + case 2: + throw value; + case 0: + co_await ex::just_stopped(); + } + }(0)); +} diff --git a/subprojects/task/examples/odd-return.cpp b/subprojects/task/examples/odd-return.cpp new file mode 100644 index 000000000..d3c2c2741 --- /dev/null +++ b/subprojects/task/examples/odd-return.cpp @@ -0,0 +1,20 @@ +// examples/odd-return.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +namespace ex = beman::execution; + +// ---------------------------------------------------------------------------- + +int main() { + auto mono = ex::sync_wait([]() -> ex::task { co_return std::monostate{}; }()); + assert(mono); + + auto exp = ex::sync_wait([]() -> ex::task { co_return std::make_exception_ptr(17); }()); + assert(exp); +} diff --git a/subprojects/task/examples/query.cpp b/subprojects/task/examples/query.cpp new file mode 100644 index 000000000..d0db8e188 --- /dev/null +++ b/subprojects/task/examples/query.cpp @@ -0,0 +1,64 @@ +// examples/query.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include + +namespace ex = beman::execution; + +constexpr struct get_value_t { + template + requires requires(const get_value_t& self, const Env& e) { e.query(self); } + decltype(auto) operator()(const Env& e) const { + return e.query(*this); + } + constexpr auto query(const ::beman::execution::forwarding_query_t&) const noexcept -> bool { return true; } +} get_value{}; + +struct simple_context { + int value{}; + int query(const get_value_t&) const noexcept { return this->value; } + explicit simple_context(auto&& env) + requires(not std::same_as>) + : value(get_value(env)) {} +}; + +struct context { + struct env_base { + env_base() = default; + env_base(const env_base&) = delete; + env_base(env_base&&) = delete; + virtual ~env_base() = default; + env_base& operator=(const env_base&) = delete; + env_base& operator=(env_base&&) = delete; + virtual int do_get_value() const = 0; + }; + template + struct env_type : env_base { + Env env; + explicit env_type(const Env& e) : env(e) {} + int do_get_value() const override { return get_value(env) + 3; } + }; + const env_base& env; + int query(const get_value_t&) const noexcept { return this->env.do_get_value(); } + explicit context(const env_base& own) : env(own) {} +}; + +int main() { + ex::sync_wait(ex::detail::write_env( + []() -> ex::task { + auto value(co_await ex::read_env(get_value)); + std::cout << "value=" << value << "\n"; + }(), + ex::detail::make_env(get_value, 42))); + ex::sync_wait(ex::detail::write_env( + []() -> ex::task { + auto value(co_await ex::read_env(get_value)); + std::cout << "value=" << value << "\n"; + }(), + ex::detail::make_env(get_value, 42))); +} diff --git a/subprojects/task/examples/result_example.cpp b/subprojects/task/examples/result_example.cpp new file mode 100644 index 000000000..fb52c7370 --- /dev/null +++ b/subprojects/task/examples/result_example.cpp @@ -0,0 +1,17 @@ +// examples/result_example.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include + +namespace ex = beman::execution; + +// ---------------------------------------------------------------------------- + +int main() { + ex::sync_wait([]() -> ex::task<> { + [[maybe_unused]] int result = co_await []() -> ex::task { co_return 42; }(); + assert(result == 42); + }()); +} diff --git a/subprojects/task/examples/rvalue-task.cpp b/subprojects/task/examples/rvalue-task.cpp new file mode 100644 index 000000000..47bd0678a --- /dev/null +++ b/subprojects/task/examples/rvalue-task.cpp @@ -0,0 +1,38 @@ +// examples/rvalue-task.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include + +namespace ex = beman::execution; + +// ---------------------------------------------------------------------------- + +namespace { +struct receiver { + using receiver_concept = ex::receiver_tag; + void set_value() && noexcept { std::cout << "set_value called\n"; } + void set_error(std::exception_ptr) && noexcept { std::cout << "set_error called\n"; } + void set_stopped() && noexcept { std::cout << "set_stopped called\n"; } + + struct env { + auto query(const ex::get_scheduler_t&) const noexcept -> ex::inline_scheduler { + return ex::inline_scheduler{}; + } + }; + env get_env() const noexcept { return {}; } +}; + +template +void test(T&& task) { + if (requires { ex::connect(std::forward(task), receiver{}); }) { + //[[maybe_unused]] auto op_state = ex::connect(std::forward(task), std::move(receiver{})); + } +} +} // namespace + +int main() { + auto task = []() -> ex::task> { co_return; }(); + test(std::move(task)); + // test(task); +} diff --git a/subprojects/task/examples/stop.cpp b/subprojects/task/examples/stop.cpp new file mode 100644 index 000000000..bc5d18378 --- /dev/null +++ b/subprojects/task/examples/stop.cpp @@ -0,0 +1,53 @@ +// examples/stop.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ex = beman::execution; + +namespace { +void unreachable([[maybe_unused]] const char* msg) { assert(nullptr == msg); } +} // namespace + +int main() { + try { + ex::stop_source source; + std::thread t([&source] { + using namespace std::chrono_literals; + std::this_thread::sleep_for(100ms); + source.request_stop(); + }); + + struct context { + struct xstop_source_type { // remove the x to disable the stop token + static constexpr bool stop_possible() { return false; } + static constexpr void request_stop() {} + static constexpr beman::execution::never_stop_token get_token() { return {}; } + }; + }; + + auto [result] = ex::sync_wait(ex::detail::write_env( + []() -> ex::task { + auto token(co_await ex::read_env(ex::get_stop_token)); + std::uint64_t count{}; + while (!token.stop_requested() && count != 200'000'000u) { + ++count; + } + co_return count; + }(), + ex::detail::make_env(ex::get_stop_token, source.get_token()))) + .value_or(std::uint64_t()); + std::cout << "result=" << result << "\n"; + + t.join(); + } catch (...) { + unreachable("an unexpected exception escaped to main"); + } +} diff --git a/subprojects/task/examples/task-sender.cpp b/subprojects/task/examples/task-sender.cpp new file mode 100644 index 000000000..f763bed6b --- /dev/null +++ b/subprojects/task/examples/task-sender.cpp @@ -0,0 +1,160 @@ +// examples/task_sender.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ex = beman::execution; + +void* operator new(std::size_t n) { + auto p = std::malloc(n); + std::cout << "global new(" << n << ")->" << p << "\n"; + return p; +} +void operator delete(void* ptr) noexcept { + std::cout << " global operator delete(" << ptr << ")\n"; + std::free(ptr); +} +void operator delete(void* ptr, std::size_t size) noexcept { + std::cout << " global operator delete(" << ptr << ", " << size << ")\n"; + std::free(ptr); +} + +template +struct defer_frame { + Mem mem; + Self self; + template + auto operator()(Arg&&... arg) const { + return ex::let_value(ex::read_env(ex::get_allocator), + [mem = this->mem, self = this->self, ... a = std::forward(arg)](auto alloc) { + return std::invoke(mem, self, std::allocator_arg, alloc, std::move(a)...); + }); + } + template + auto operator()(::std::allocator_arg_t, Alloc alloc, Arg&&... arg) const { + return ex::let_value(ex::just(alloc), + [&mem = this->mem, self = this->self, ... a = std::forward(arg)](auto alloc) { + return std::invoke(mem, self, std::allocator_arg, alloc, std::move(a)...); + }); + } + auto operator()(::std::allocator_arg_t) const = delete; +}; + +template +struct defer_frame { + Task task; + template + auto operator()(Arg&&... arg) const { + return ex::let_value(ex::read_env(ex::get_allocator), + [&task = this->task, ... a = std::forward(arg)](auto alloc) { + return std::invoke(task, std::allocator_arg, alloc, std::move(a)...); + }); + } + template + auto operator()(::std::allocator_arg_t, Alloc alloc, Arg&&... arg) const { + return ex::let_value(ex::just(alloc), [&task = this->task, ... a = std::forward(arg)](auto alloc) { + return std::invoke(task, std::allocator_arg, alloc, std::move(a)...); + }); + } + auto operator()(::std::allocator_arg_t) const = delete; +}; + +struct env { + using allocator_type = std::pmr::polymorphic_allocator; +}; + +auto lambda{[](int i, auto&&...) -> ex::task { + auto alloc = co_await ex::read_env(ex::get_allocator); + alloc.deallocate(alloc.allocate(1), 1); + std::cout << "lambda(" << i << ")\n"; + co_return; +}}; + +class example { + ex::task member_(std::allocator_arg_t, std::pmr::polymorphic_allocator, int); + ex::task const_member_(std::allocator_arg_t, std::pmr::polymorphic_allocator, int) const; + + public: + auto member(int i) { return defer_frame(&example::member_, this)(i); } + auto const_member(int i) { return defer_frame(&example::const_member_, this)(i); } +}; + +inline ex::task example::member_(std::allocator_arg_t, std::pmr::polymorphic_allocator, int i) { + std::cout << "example::member(" << i << ")\n"; + co_return; +} +inline ex::task +example::const_member_(std::allocator_arg_t, std::pmr::polymorphic_allocator, int i) const { + std::cout << "example::const member(" << i << ")\n"; + co_return; +} + +struct resource : std::pmr::memory_resource { + void* do_allocate(std::size_t n, std::size_t) override { + auto p{std::malloc(n)}; + std::cout << " resource::allocate(" << n << ")->" << p << "\n"; + return p; + } + void do_deallocate(void* p, std::size_t n, std::size_t) override { + std::cout << " resource::deallocate(" << p << ", " << n << ")\n"; + std::free(p); + } + bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override { return this == &other; } +}; + +int main() { + resource res{}; + std::pmr::polymorphic_allocator alloc(&res); + std::cout << "direct allocator use:\n"; + alloc.deallocate(alloc.allocate(1), 1); + std::cout << "write_env/just/then use:\n"; + ex::sync_wait(ex::write_env(ex::just(alloc) | ex::then([](auto a) { a.deallocate(a.allocate(1), 1); }), + ex::env{ex::prop{ex::get_allocator, alloc}})); + std::cout << "write_env/read_env/then use:\n"; + ex::sync_wait( + ex::write_env(ex::read_env(ex::get_allocator) | ex::then([](auto a) { a.deallocate(a.allocate(1), 1); }), + ex::env{ex::prop{ex::get_allocator, alloc}})); + std::cout << "write_env/let_value/then use:\n"; + ex::sync_wait(ex::write_env(ex::just() | ex::let_value([] { + return ex::read_env(ex::get_allocator) | + ex::then([](auto a) { a.deallocate(a.allocate(1), 1); }); + }), + ex::env{ex::prop{ex::get_allocator, alloc}})); + std::cout << "write_env/task<>:\n"; + ex::sync_wait(ex::write_env( + []() -> ex::task<> { + auto a = co_await ex::read_env(ex::get_allocator); + a.deallocate(a.allocate(1), 1); + }(), + ex::env{ex::prop{ex::get_allocator, alloc}})); + std::cout << "write_env/task:\n"; + ex::sync_wait(ex::write_env( + [](auto&&...) -> ex::task { + auto a = co_await ex::read_env(ex::get_allocator); + a.deallocate(a.allocate(1), 1); + }(std::allocator_arg, alloc), + ex::env{ex::prop{ex::get_allocator, alloc}})); + std::cout << "write_env/defer_frame>:\n"; + static constexpr defer_frame t0([](auto, auto, int i) -> ex::task { + std::cout << " i=" << i << "\n"; + auto a = co_await ex::read_env(ex::get_allocator); + a.deallocate(a.allocate(1), 1); + }); + ex::sync_wait(ex::write_env(t0(17), ex::env{ex::prop{ex::get_allocator, alloc}})); + std::cout << "write_env/temporary defer_frame>:\n"; + ex::sync_wait(ex::write_env(defer_frame([](auto, auto, auto i) -> ex::task { + std::cout << " i=" << i << "\n"; + co_await std::suspend_never{}; + auto a = co_await ex::read_env(ex::get_allocator); + a.deallocate(a.allocate(1), 1); + })(42), + ex::env{ex::prop{ex::get_allocator, alloc}})); + std::cout << "done\n"; +} diff --git a/subprojects/task/examples/task_scheduler.cpp b/subprojects/task/examples/task_scheduler.cpp new file mode 100644 index 000000000..c02f5ced1 --- /dev/null +++ b/subprojects/task/examples/task_scheduler.cpp @@ -0,0 +1,21 @@ +// examples/task_scheduler.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include + +namespace ex = beman::execution; + +int main() { + ex::inline_scheduler isched; + const ex::inline_scheduler cisched; + ex::task_scheduler asched(isched); + const ex::task_scheduler casched(cisched); + + return asched == isched && isched == asched && asched == cisched && cisched == casched && !(cisched != casched) && + !(asched != isched) + ? EXIT_SUCCESS + : EXIT_FAILURE; +} diff --git a/subprojects/task/examples/tls-scheduler.cpp b/subprojects/task/examples/tls-scheduler.cpp new file mode 100644 index 000000000..eb8f5a2aa --- /dev/null +++ b/subprojects/task/examples/tls-scheduler.cpp @@ -0,0 +1,60 @@ +// examples/hello.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "demo-tls_scheduler.hpp" +#include +#include +#include +#include + +namespace ex = beman::execution; + +// ---------------------------------------------------------------------------- + +class tls_data { + static thread_local std::string value; + + public: + static std::string get() { return tls_data::value; } + static void set(std::string_view v) { tls_data::value = v; } +}; + +thread_local std::string tls_data::value{}; + +struct tls_save { + std::optional value; + void save() { this->value = tls_data::get(); } + void restore() { tls_data::set(*this->value); } +}; + +struct tls_env { + using scheduler_type = demo::tls_scheduler; + // using scheduler_type = ex::inline_scheduler; +}; + +ex::task run_timer(std::string name) { + tls_data::set(name); + + for (int i = 0; i != 3; ++i) { + co_await ex::just(); + std::cout << "data=" << tls_data::get() << "\n"; + } +} + +int main() { + ex::counting_scope scope; + ex::sync_wait([](auto& scope) -> ex::task { + auto scheduler = co_await ex::read_env(ex::get_scheduler); + + ex::spawn(ex::write_env(run_timer("timer 1") | ex::upon_error([](auto&&) noexcept {}) | + ex::then([]() noexcept { std::cout << "loop done\n"; }), + ex::detail::make_env(ex::get_scheduler, scheduler)), + scope.get_token()); + ex::spawn(ex::write_env(run_timer("timer 2") | ex::upon_error([](auto&&) noexcept {}) | + ex::then([]() noexcept { std::cout << "loop done\n"; }), + ex::detail::make_env(ex::get_scheduler, scheduler)), + scope.get_token()); + co_return; + }(scope)); + ex::sync_wait(scope.join()); +} diff --git a/subprojects/task/include/beman/execution/task.hpp b/subprojects/task/include/beman/execution/task.hpp new file mode 100644 index 000000000..8a8f49027 --- /dev/null +++ b/subprojects/task/include/beman/execution/task.hpp @@ -0,0 +1,13 @@ +// include/beman/execution/task.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_INCLUDE_BEMAN_EXECUTION_TASK +#define INCLUDED_INCLUDE_BEMAN_EXECUTION_TASK + +// ---------------------------------------------------------------------------- + +#include + +// ---------------------------------------------------------------------------- + +#endif diff --git a/subprojects/task/include/beman/lazy/lazy.hpp b/subprojects/task/include/beman/lazy/lazy.hpp new file mode 100644 index 000000000..9a89462ad --- /dev/null +++ b/subprojects/task/include/beman/lazy/lazy.hpp @@ -0,0 +1,24 @@ +// include/beman/lazy/lazy.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_INCLUDE_BEMAN_LAZY_LAZY +#define INCLUDED_INCLUDE_BEMAN_LAZY_LAZY + +#include + +// ---------------------------------------------------------------------------- + +namespace beman::lazy { +template +using lazy = ::beman::task::detail::task; +} + +namespace beman::execution { +template +using lazy [[deprecated("beman::execution::lazy has been renamed to beman::execution::task")]] = + ::beman::execution::task; +} + +// ---------------------------------------------------------------------------- + +#endif diff --git a/subprojects/task/include/beman/task/detail/allocator_of.hpp b/subprojects/task/include/beman/task/detail/allocator_of.hpp new file mode 100644 index 000000000..e319c18ea --- /dev/null +++ b/subprojects/task/include/beman/task/detail/allocator_of.hpp @@ -0,0 +1,38 @@ +// include/beman/task/detail/allocator_of.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_ALLOCATOR_OF +#define INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_ALLOCATOR_OF + +#include +#include + +// ---------------------------------------------------------------------------- + +namespace beman::task::detail { +/*! + * \brief Utility to get an allocator type from a context + * \headerfile beman/task/task.hpp + * \internal + */ +template +struct allocator_of { + using type = std::allocator; +}; +template + requires requires { typename Context::allocator_type; } +struct allocator_of { + using type = typename Context::allocator_type; + static_assert( + requires(type& a, std::size_t s, std::byte* ptr) { + { a.allocate(s) } -> std::same_as; + a.deallocate(ptr, s); + }, "The allocator_type needs to be an allocator of std::byte"); +}; +template +using allocator_of_t = typename allocator_of::type; +} // namespace beman::task::detail + +// ---------------------------------------------------------------------------- + +#endif diff --git a/subprojects/task/include/beman/task/detail/allocator_support.hpp b/subprojects/task/include/beman/task/detail/allocator_support.hpp new file mode 100644 index 000000000..a3d397992 --- /dev/null +++ b/subprojects/task/include/beman/task/detail/allocator_support.hpp @@ -0,0 +1,87 @@ +// include/beman/task/detail/allocator_support.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_BEMAN_TASK_DETAIL_ALLOCATOR_SUPPORT +#define INCLUDED_BEMAN_TASK_DETAIL_ALLOCATOR_SUPPORT + +#include +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace beman::task::detail { +/*! + * \brief Utility adding allocator support to type by embedding the allocator + * \headerfile beman/task/task.hpp + * + * To add allocator support using this class just publicly inherit from + * allocator_support. This utility is probably + * only useful for coroutine promise types. + * + * This struct is a massive hack, primarily support allocators for coroutines. + * The memory for coroutines is implicitly managed and there isn't a way to + * provide the memory directly. Instead, the promise_type can overload an + * operator new and somehow determine an allocator based on the arguments + * passed to the coroutine. Even worse, the operator delete only gets passed + * a pointer to delete and a size. To determine the correct allocator the + * operator delete needs to located it based on this information. Putting + * the allocator after actually used memory causes the address sanitizer to + * object! So, the current strategy is to embed space for the allocator + * into the object and pull it out from there. + */ +template +struct allocator_support { + using allocator_traits = std::allocator_traits; + + static std::size_t offset(std::size_t size) { + return (size + alignof(Allocator) - 1u) & ~(alignof(Allocator) - 1u); + } + static Allocator* get_allocator(void* ptr, std::size_t size) { + ptr = static_cast(ptr) + offset(size); + return ::std::launder(reinterpret_cast(ptr)); + } + + template + static void* operator new(std::size_t size, [[maybe_unused]] A&&... a) { + if constexpr (::std::same_as>) { + Allocator alloc{}; + return allocator_traits::allocate(alloc, size); + } else { + Allocator alloc{::beman::task::detail::find_allocator(a...)}; + void* ptr{allocator_traits::allocate(alloc, allocator_support::offset(size) + sizeof(Allocator))}; + try { + new (allocator_support::get_allocator(ptr, size)) Allocator(alloc); + } catch (...) { + allocator_traits::deallocate( + alloc, static_cast(ptr), allocator_support::offset(size) + sizeof(Allocator)); + throw; + } + return ptr; + } + } + template + static void operator delete(void* ptr, std::size_t size, const A&...) { + allocator_support::operator delete(ptr, size); + } + static void operator delete(void* ptr, std::size_t size) { + if constexpr (::std::same_as>) { + Allocator alloc{}; + allocator_traits::deallocate(alloc, static_cast(ptr), size); + } else { + Allocator* aptr{allocator_support::get_allocator(ptr, size)}; + Allocator alloc{*aptr}; + aptr->~Allocator(); + allocator_traits::deallocate( + alloc, static_cast(ptr), allocator_support::offset(size) + sizeof(Allocator)); + } + } +}; +} // namespace beman::task::detail + +// ---------------------------------------------------------------------------- + +#endif diff --git a/subprojects/task/include/beman/task/detail/awaiter.hpp b/subprojects/task/include/beman/task/detail/awaiter.hpp new file mode 100644 index 000000000..d69efae9b --- /dev/null +++ b/subprojects/task/include/beman/task/detail/awaiter.hpp @@ -0,0 +1,116 @@ +// include/beman/task/detail/awaiter.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_AWAITER +#define INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_AWAITER + +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace beman::task::detail { +template +struct awaiter_scheduler_receiver { + using receiver_concept = ::beman::execution::receiver_tag; + Awaiter* aw; + auto set_value(auto&&...) noexcept { this->aw->actual_complete().resume(); } + auto set_error(auto&&) noexcept { this->aw->actual_complete().resume(); } + auto set_stopped() noexcept { this->aw->actual_complete().resume(); } +}; + +template +struct awaiter_op_t { + using state_type = + decltype(::beman::execution::connect(::beman::execution::schedule(::beman::execution::get_scheduler( + ::beman::execution::get_env(::std::declval()))), + ::std::declval>())); + + awaiter_op_t(const ParentPromise& p, Awaiter* aw) + : state(::beman::execution::connect( + ::beman::execution::schedule(beman::execution::get_scheduler(::beman::execution::get_env(p))), + awaiter_scheduler_receiver{aw})) {} + state_type state; + auto start() noexcept -> void { ::beman::execution::start(this->state); } +}; +template +struct awaiter_op_t { + awaiter_op_t(const ParentPromise&, Awaiter*) noexcept {} + auto start() noexcept -> void {} +}; + +template +class awaiter : public ::beman::task::detail::state_base { + public: + using allocator_type = typename ::beman::task::detail::state_base::allocator_type; + using stop_token_type = typename ::beman::task::detail::state_base::stop_token_type; + using scheduler_type = typename ::beman::task::detail::state_base::scheduler_type; + + explicit awaiter(::beman::task::detail::handle h) : handle(std::move(h)) {} + constexpr auto await_ready() const noexcept -> bool { return false; } + struct env_receiver { + ParentPromise* parent; + auto get_env() const noexcept { return parent->get_env(); } + }; + auto await_suspend(::std::coroutine_handle parent) noexcept { + this->state_rep.emplace(env_receiver{&parent.promise()}); + this->scheduler.emplace( + this->template from_env(::beman::execution::get_env(parent.promise()))); + this->parent = ::std::move(parent); + return this->handle.start(this); + } + auto await_resume() { return this->result_resume(); } + + private: + friend struct awaiter_scheduler_receiver; + auto do_complete() -> std::coroutine_handle<> override { + assert(this->parent); + assert(this->scheduler); + if constexpr (requires { + *this->scheduler != + ::beman::execution::get_scheduler(::beman::execution::get_env(this->parent.promise())); + }) { + if (*this->scheduler != + ::beman::execution::get_scheduler(::beman::execution::get_env(this->parent.promise()))) { + this->reschedule.emplace(this->parent.promise(), this); + this->reschedule->start(); + return ::std::noop_coroutine(); + } + } + return this->actual_complete(); + } + auto actual_complete() -> std::coroutine_handle<> { + return this->no_completion_set() ? this->parent.promise().unhandled_stopped() : ::std::move(this->parent); + } + auto do_get_allocator() -> allocator_type override { + if constexpr (requires { + ::beman::execution::get_allocator(::beman::execution::get_env(this->parent.promise())); + }) + return ::beman::execution::get_allocator(::beman::execution::get_env(this->parent.promise())); + else + return allocator_type{}; + } + auto do_get_scheduler() -> scheduler_type override { return *this->scheduler; } + auto do_set_scheduler(scheduler_type other) -> scheduler_type override { + return ::std::exchange(*this->scheduler, other); + } + auto do_get_stop_token() -> stop_token_type override { return {}; } + auto do_get_environment() -> Env& override { return this->state_rep->context; } + + ::beman::task::detail::handle handle; + ::std::optional<::beman::task::detail::state_rep> state_rep; + ::std::optional scheduler; + ::std::coroutine_handle parent{}; + ::std::optional> reschedule{}; +}; +} // namespace beman::task::detail + +// ---------------------------------------------------------------------------- + +#endif diff --git a/subprojects/task/include/beman/task/detail/change_coroutine_scheduler.hpp b/subprojects/task/include/beman/task/detail/change_coroutine_scheduler.hpp new file mode 100644 index 000000000..ad35b6fa3 --- /dev/null +++ b/subprojects/task/include/beman/task/detail/change_coroutine_scheduler.hpp @@ -0,0 +1,37 @@ +// include/beman/task/detail/change_coroutine_scheduler.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_CHANGE_COROUTINE_SCHEDULER +#define INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_CHANGE_COROUTINE_SCHEDULER + +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace beman::task::detail { +template <::beman::execution::scheduler Scheduler> +struct change_coroutine_scheduler { + using type = Scheduler; + type scheduler; + + template <::beman::execution::scheduler S> + change_coroutine_scheduler(S&& s) : scheduler(::std::forward(s)) {} + + constexpr bool await_ready() const { return false; } + template + bool await_suspend(::std::coroutine_handle

h) { + this->scheduler = h.promise().change_scheduler(std::move(this->scheduler)); + return false; + } + type await_resume() { return this->scheduler; } +}; +template <::beman::execution::scheduler S> +change_coroutine_scheduler(S&&) -> change_coroutine_scheduler<::beman::task::detail::task_scheduler>; +} // namespace beman::task::detail + +// ---------------------------------------------------------------------------- + +#endif diff --git a/subprojects/task/include/beman/task/detail/completion.hpp b/subprojects/task/include/beman/task/detail/completion.hpp new file mode 100644 index 000000000..28340e83e --- /dev/null +++ b/subprojects/task/include/beman/task/detail/completion.hpp @@ -0,0 +1,28 @@ +// include/beman/task/detail/completion.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_COMPLETION +#define INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_COMPLETION + +#include + +// ---------------------------------------------------------------------------- + +namespace beman::task::detail { +template +struct completion { + using type = ::beman::execution::set_value_t(R); +}; +template <> +struct completion { + using type = ::beman::execution::set_value_t(); +}; + +template +using completion_t = typename beman::task::detail::completion::type; + +} // namespace beman::task::detail + +// ---------------------------------------------------------------------------- + +#endif diff --git a/subprojects/task/include/beman/task/detail/error_types_of.hpp b/subprojects/task/include/beman/task/detail/error_types_of.hpp new file mode 100644 index 000000000..b2fb585b6 --- /dev/null +++ b/subprojects/task/include/beman/task/detail/error_types_of.hpp @@ -0,0 +1,28 @@ +// include/beman/task/detail/error_types_of.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_ERROR_TYPES_OF +#define INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_ERROR_TYPES_OF + +#include +#include + +// ---------------------------------------------------------------------------- + +namespace beman::task::detail { +template +struct error_types_of { + using type = ::beman::execution::completion_signatures<::beman::execution::set_error_t(::std::exception_ptr)>; +}; +template + requires requires { typename Context::error_types; } +struct error_types_of { + using type = typename Context::error_types; +}; +template +using error_types_of_t = typename error_types_of::type; +} // namespace beman::task::detail + +// ---------------------------------------------------------------------------- + +#endif diff --git a/subprojects/task/include/beman/task/detail/final_awaiter.hpp b/subprojects/task/include/beman/task/detail/final_awaiter.hpp new file mode 100644 index 000000000..861ed933f --- /dev/null +++ b/subprojects/task/include/beman/task/detail/final_awaiter.hpp @@ -0,0 +1,27 @@ +// include/beman/task/detail/final_awaiter.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_FINAL_AWAITER +#define INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_FINAL_AWAITER + +#include +#include + +// ---------------------------------------------------------------------------- + +namespace beman::task::detail { + +struct final_awaiter { + static constexpr auto await_ready() noexcept -> bool { return false; } + template + static auto await_suspend(std::coroutine_handle handle) noexcept { + return handle.promise().notify_complete(); + } + static constexpr void await_resume() noexcept {} +}; + +} // namespace beman::task::detail + +// ---------------------------------------------------------------------------- + +#endif diff --git a/subprojects/task/include/beman/task/detail/find_allocator.hpp b/subprojects/task/include/beman/task/detail/find_allocator.hpp new file mode 100644 index 000000000..0b2120857 --- /dev/null +++ b/subprojects/task/include/beman/task/detail/find_allocator.hpp @@ -0,0 +1,46 @@ +// include/beman/task/detail/find_allocator.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_FIND_ALLOCATOR +#define INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_FIND_ALLOCATOR + +#include + +// ---------------------------------------------------------------------------- + +namespace beman::task::detail { +/*! + * \brief Utility locating an allocator_arg/allocator pair + * \headerfile beman/task/task.hpp + * \internal + */ +template +Allocator find_allocator() { + return Allocator(); +} +template +Allocator find_allocator(const std::allocator_arg_t&) { + static_assert( + requires { + { Allocator() } -> std::same_as; + }, "There needs to be an allocator argument following std::allocator_arg"); + return Allocator(); +} + +template +Allocator find_allocator(const std::allocator_arg_t&, const Alloc& alloc, const A&...) { + static_assert( + requires(const Alloc& a) { Allocator(a); }, + "The allocator needs to be constructible from the argument following std::allocator"); + return Allocator(alloc); +} +template +Allocator find_allocator(A0 const&, const A&... a) { + return ::beman::task::detail::find_allocator(a...); +} + +} // namespace beman::task::detail + +// ---------------------------------------------------------------------------- + +#endif diff --git a/subprojects/task/include/beman/task/detail/handle.hpp b/subprojects/task/include/beman/task/detail/handle.hpp new file mode 100644 index 000000000..b1d9471c7 --- /dev/null +++ b/subprojects/task/include/beman/task/detail/handle.hpp @@ -0,0 +1,48 @@ +// include/beman/task/detail/handle.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_HANDLE +#define INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_HANDLE + +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace beman::task::detail { +template +class handle { + private: + struct deleter { + auto operator()(P* p) noexcept -> void { + if (p) { + std::coroutine_handle

::from_promise(*p).destroy(); + } + } + }; + std::unique_ptr h; + + public: + explicit handle(P* p) : h(p) {} + auto reset() -> void { this->h.reset(); } + template + auto start(A&&... a) noexcept -> auto { + return this->h->start(::std::forward(a)...); + } + auto release() -> ::std::coroutine_handle

{ + return ::std::coroutine_handle

::from_promise(*this->h.release()); + } + P* get() const noexcept { return this->h.get(); } + auto get_env() const noexcept { + assert(this->h.get()); + return ::beman::execution::get_env(*this->h); + } +}; + +} // namespace beman::task::detail + +// ---------------------------------------------------------------------------- + +#endif diff --git a/subprojects/task/include/beman/task/detail/infallible_scheduler.hpp b/subprojects/task/include/beman/task/detail/infallible_scheduler.hpp new file mode 100644 index 000000000..40c171642 --- /dev/null +++ b/subprojects/task/include/beman/task/detail/infallible_scheduler.hpp @@ -0,0 +1,33 @@ +// include/beman/task/detail/infallible_scheduler.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_INFALLIBLE_SCHEDULER +#define INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_INFALLIBLE_SCHEDULER + +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace beman::task::detail { +template +concept completes_with = + ::std::same_as<::beman::execution::completion_signatures, + ::beman::execution:: + completion_signatures_of_t())), Env>>; + +template +concept infallible_scheduler = + (::beman::execution::scheduler) && + (::beman::task::detail::completes_with || + (!::beman::execution::unstoppable_token<::beman::execution::stop_token_of_t> && + (::beman::task::detail:: + completes_with || + ::beman::task::detail:: + completes_with))); +} // namespace beman::task::detail + +// ---------------------------------------------------------------------------- + +#endif diff --git a/subprojects/task/include/beman/task/detail/into_optional.hpp b/subprojects/task/include/beman/task/detail/into_optional.hpp new file mode 100644 index 000000000..7bef831ca --- /dev/null +++ b/subprojects/task/include/beman/task/detail/into_optional.hpp @@ -0,0 +1,100 @@ +// include/beman/task/detail/into_optional.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_BEMAN_TASK_DETAIL_INTO_OPTIONAL +#define INCLUDED_BEMAN_TASK_DETAIL_INTO_OPTIONAL + +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace beman::task::detail { + +inline constexpr struct into_optional_t : beman::execution::sender_adaptor_closure { + template <::beman::execution::sender Upstream> + struct sender { + using upstream_t = std::remove_cvref_t; + using sender_concept = ::beman::execution::sender_tag; + upstream_t upstream; + + template + struct type_list {}; + + template + static auto find_type(type_list>) { + return std::optional{}; + } + template + static auto find_type(type_list, type_list<>>) { + return std::optional{}; + } + template + static auto find_type(type_list, type_list>) { + return std::optional{}; + } + template + static auto find_type(type_list>) { + return std::optional>{}; + } + template + static auto find_type(type_list, type_list<>>) { + return std::optional>{}; + } + template + static auto find_type(type_list, type_list>) { + return std::optional>{}; + } + + template + static auto get_type(Env&&) { + return find_type( + ::beman::execution::value_types_of_t, type_list, type_list>()); + } + + template + constexpr auto make_signatures(auto&& env, type_list, type_list) const { + return ::beman::execution::completion_signatures<::beman::execution::set_value_t( + decltype(this->get_type(env))), + ::beman::execution::set_error_t(E)..., + S...>(); + } + template + auto get_completion_signatures(Env&& env) const { + return make_signatures( + env, + ::beman::execution::error_types_of_t, type_list>{}, + std::conditional_t<::beman::execution::sends_stopped>, + type_list<::beman::execution::set_stopped_t()>, + type_list<>>{}); + } + template + struct make_object { + template + auto operator()(A&&... a) const + -> decltype(get_type(::beman::execution::get_env(std::declval()))) { + if constexpr (sizeof...(A) == 0u) + return {}; + else + return {std::forward(a)...}; + } + }; + template + auto connect(Receiver&& receiver) && { + return ::beman::execution::connect( + ::beman::execution::then(std::move(this->upstream), make_object{}), + std::forward(receiver)); + } + }; + + template + sender operator()(Upstream&& upstream) const { + return {std::forward(upstream)}; + } +} into_optional{}; +} // namespace beman::task::detail + +// ---------------------------------------------------------------------------- + +#endif diff --git a/subprojects/task/include/beman/task/detail/logger.hpp b/subprojects/task/include/beman/task/detail/logger.hpp new file mode 100644 index 000000000..465494088 --- /dev/null +++ b/subprojects/task/include/beman/task/detail/logger.hpp @@ -0,0 +1,40 @@ +// include/beman/task/detail/logger.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_LOGGER +#define INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_LOGGER + +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace beman::task::detail { +struct logger { + static auto level(int i) -> int { + static int rc{}; + return rc += i; + } + ::std::ostream& log(const char* pre, const char* msg) { + ::std::fill_n(::std::ostreambuf_iterator(std::cout.rdbuf()), level(0), ' '); + std::cout << pre; + return std::cout << msg << "\n"; + } + ::std::ostream& log(const char* msg) { return log("| ", msg); } + const char* msg; + explicit logger(const char* m) : msg(m) { + level(1); + log("\\ ", this->msg); + } + logger(logger&&) = delete; + ~logger() { + log("/ ", this->msg); + level(-1); + } +}; +} // namespace beman::task::detail + +// ---------------------------------------------------------------------------- + +#endif diff --git a/subprojects/task/include/beman/task/detail/poly.hpp b/subprojects/task/include/beman/task/detail/poly.hpp new file mode 100644 index 000000000..a48f92f75 --- /dev/null +++ b/subprojects/task/include/beman/task/detail/poly.hpp @@ -0,0 +1,78 @@ +// include/beman/task/detail/poly.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_BEMAN_TASK_DETAIL_POLY +#define INCLUDED_BEMAN_TASK_DETAIL_POLY + +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace beman::task::detail { +/*! + * \brief Utility providing small object optimization and type erasure. + * \headerfile beman/task/task.hpp + * \internal + */ +template +class alignas(sizeof(double)) poly { + private: + std::array buf{}; + + Base* pointer() { return static_cast(static_cast(buf.data())); } + const Base* pointer() const { return static_cast(static_cast(buf.data())); } + + public: + template + requires(sizeof(T) <= Size) + poly(T*, Args&&... args) { + new (this->buf.data()) T(::std::forward(args)...); + static_assert(sizeof(T) <= Size); + } + poly(poly&& other) + requires requires(Base* b, void* t) { b->move(t); } + { + other.pointer()->move(this->buf.data()); + } + poly& operator=(poly&& other) + requires requires(Base* b, void* t) { b->move(t); } + { + if (this != &other) { + this->pointer()->~Base(); + other.pointer()->move(this->buf.data()); + } + return *this; + } + poly& operator=(const poly& other) + requires requires(Base* b, void* t) { b->clone(t); } + { + if (this != &other) { + this->pointer()->~Base(); + other.pointer()->clone(this->buf.data()); + } + return *this; + } + poly(const poly& other) + requires requires(Base* b, void* t) { b->clone(t); } + { + other.pointer()->clone(this->buf.data()); + } + ~poly() { this->pointer()->~Base(); } + bool operator==(const poly& other) const + requires requires(const Base& b) { + { b.equals(&b) } -> std::same_as; + } + { + return other.pointer()->equals(this->pointer()); + } + Base* operator->() { return this->pointer(); } + const Base* operator->() const { return this->pointer(); } +}; +} // namespace beman::task::detail + +// ---------------------------------------------------------------------------- + +#endif diff --git a/subprojects/task/include/beman/task/detail/promise_base.hpp b/subprojects/task/include/beman/task/detail/promise_base.hpp new file mode 100644 index 000000000..787577bec --- /dev/null +++ b/subprojects/task/include/beman/task/detail/promise_base.hpp @@ -0,0 +1,61 @@ +// include/beman/task/detail/promise_base.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_PROMISE_BASE +#define INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_PROMISE_BASE + +#include +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace beman::task::detail { +/* + * \brief Helper base class dealing with void vs. value results. + * \headerfile beman/task/task.hpp + * \internal + */ +template <::beman::task::detail::stoppable Stop, typename Value, typename Environment> +class promise_base { + public: + /* + * \brief Set the value result. + * \internal + */ + template + void return_value(T&& value) { + this->get_state()->set_value(::std::forward(value)); + } + + public: + auto set_state(::beman::task::detail::state_base* s) noexcept -> void { this->state_ = s; } + auto get_state() const noexcept -> ::beman::task::detail::state_base* { return this->state_; } + + private: + ::beman::task::detail::state_base* state_{}; +}; + +template +class promise_base { + public: + /* + * \brief Set the value result although without any value. + */ + void return_void() { this->get_state()->set_value(void_type{}); } + + public: + auto set_state(::beman::task::detail::state_base* s) noexcept -> void { this->state_ = s; } + auto get_state() const noexcept -> ::beman::task::detail::state_base* { return this->state_; } + + private: + ::beman::task::detail::state_base* state_{}; +}; +} // namespace beman::task::detail + +// ---------------------------------------------------------------------------- + +#endif diff --git a/subprojects/task/include/beman/task/detail/promise_env.hpp b/subprojects/task/include/beman/task/detail/promise_env.hpp new file mode 100644 index 000000000..41f8b6870 --- /dev/null +++ b/subprojects/task/include/beman/task/detail/promise_env.hpp @@ -0,0 +1,40 @@ +// include/beman/task/detail/promise_env.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_PROMISE_ENV +#define INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_PROMISE_ENV + +#include +#include + +// ---------------------------------------------------------------------------- + +namespace beman::task::detail { +template +struct promise_env { + const Promise* promise; + + auto query(const ::beman::execution::get_scheduler_t&) const noexcept -> typename Promise::scheduler_type { + return this->promise->get_scheduler(); + } + auto query(const ::beman::execution::get_allocator_t&) const noexcept -> typename Promise::allocator_type { + return this->promise->get_allocator(); + } + auto query(const ::beman::execution::get_stop_token_t&) const noexcept -> typename Promise::stop_token_type { + return this->promise->get_stop_token(); + } + + template + requires requires(const Promise* p, Q q, A&&... a) { + ::beman::execution::forwarding_query(q); + q(p->get_environment(), std::forward(a)...); + } + auto query(Q q, A&&... a) const noexcept { + return q(promise->get_environment(), std::forward(a)...); + } +}; +} // namespace beman::task::detail + +// ---------------------------------------------------------------------------- + +#endif diff --git a/subprojects/task/include/beman/task/detail/promise_type.hpp b/subprojects/task/include/beman/task/detail/promise_type.hpp new file mode 100644 index 000000000..987e22f06 --- /dev/null +++ b/subprojects/task/include/beman/task/detail/promise_type.hpp @@ -0,0 +1,122 @@ +// include/beman/task/detail/promise_type.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_PROMISE_TYPE +#define INCLUDED_INCLUDE_BEMAN_TASK_DETAIL_PROMISE_TYPE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace beman::task::detail { +namespace meta { +template +struct list_contains; +template