diff --git a/doomsday/apps/client/src/network/serverlink.cpp b/doomsday/apps/client/src/network/serverlink.cpp
index ebc9abf1eb..a8c0a006fa 100644
--- a/doomsday/apps/client/src/network/serverlink.cpp
+++ b/doomsday/apps/client/src/network/serverlink.cpp
@@ -13,7 +13,7 @@
* 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, see:
- * http://www.gnu.org/licenses
+ * http://www.gnu.org/licenses
*/
#include "de_platform.h"
@@ -49,7 +49,6 @@ enum LinkState
};
DENG2_PIMPL(ServerLink)
-, DENG2_OBSERVES(Loop, Iteration)
{
shell::ServerFinder finder; ///< Finding local servers.
LinkState state;
@@ -57,6 +56,7 @@ DENG2_PIMPL(ServerLink)
typedef QMap
Servers;
Servers discovered;
Servers fromMaster;
+ LoopCallback mainCall;
Instance(Public *i)
: Base(i)
@@ -64,11 +64,6 @@ DENG2_PIMPL(ServerLink)
, fetching(false)
{}
- ~Instance()
- {
- Loop::get().audienceForIteration() -= this;
- }
-
void notifyDiscoveryUpdate()
{
DENG2_FOR_PUBLIC_AUDIENCE(DiscoveryUpdate, i) i->linkDiscoveryUpdate(self);
@@ -183,17 +178,16 @@ DENG2_PIMPL(ServerLink)
fetching = true;
N_MAPost(MAC_REQUEST);
N_MAPost(MAC_WAIT);
- Loop::get().audienceForIteration() += this;
+ mainCall.enqueue([this] () { checkMasterReply(); });
}
- void loopIteration()
+ void checkMasterReply()
{
DENG2_ASSERT(fetching);
if(N_MADone())
{
fetching = false;
- Loop::get().audienceForIteration() -= this;
fromMaster.clear();
int const count = N_MasterGet(0, 0);
@@ -206,6 +200,11 @@ DENG2_PIMPL(ServerLink)
notifyDiscoveryUpdate();
}
+ else
+ {
+ // Check again later.
+ mainCall.enqueue([this] () { checkMasterReply(); });
+ }
}
Servers allFound(FoundMask const &mask) const
diff --git a/doomsday/apps/client/src/resource/resourcesystem.cpp b/doomsday/apps/client/src/resource/resourcesystem.cpp
index 05c843bf55..cb23d4d920 100644
--- a/doomsday/apps/client/src/resource/resourcesystem.cpp
+++ b/doomsday/apps/client/src/resource/resourcesystem.cpp
@@ -2196,6 +2196,8 @@ DENG2_PIMPL(ResourceSystem)
void loopIteration()
{
+ /// @todo Refactor: TaskPool has a signal (or audience) when all tasks are complete.
+ /// No need to check on every loop iteration.
if(convertSavegameTasks.isDone())
{
LOG_AS("ResourceSystem");
diff --git a/doomsday/apps/client/src/ui/widgets/savedsessionmenuwidget.cpp b/doomsday/apps/client/src/ui/widgets/savedsessionmenuwidget.cpp
index 5a5ff78acb..0523dea166 100644
--- a/doomsday/apps/client/src/ui/widgets/savedsessionmenuwidget.cpp
+++ b/doomsday/apps/client/src/ui/widgets/savedsessionmenuwidget.cpp
@@ -38,7 +38,6 @@ using de::game::SavedSession;
DENG_GUI_PIMPL(SavedSessionMenuWidget)
, DENG2_OBSERVES(Games, Readiness)
, DENG2_OBSERVES(Session::SavedIndex, AvailabilityUpdate)
-, DENG2_OBSERVES(Loop, Iteration) // deferred refresh
{
/**
* Action for loading a saved session.
@@ -170,6 +169,8 @@ DENG_GUI_PIMPL(SavedSessionMenuWidget)
}
};
+ LoopCallback mainCall;
+
Instance(Public *i) : Base(i)
{
App_Games().audienceForReadiness() += this;
@@ -178,7 +179,6 @@ DENG_GUI_PIMPL(SavedSessionMenuWidget)
~Instance()
{
- Loop::get().audienceForIteration() -= this;
App_Games().audienceForReadiness() -= this;
game::Session::savedIndex().audienceForAvailabilityUpdate() -= this;
}
@@ -277,24 +277,7 @@ DENG_GUI_PIMPL(SavedSessionMenuWidget)
void savedIndexAvailabilityUpdate(Session::SavedIndex const &)
{
- if(!App::inMainThread())
- {
- // We'll have to defer the update for now.
- deferUpdate();
- return;
- }
- updateItemsFromSavedIndex();
- }
-
- void deferUpdate()
- {
- Loop::get().audienceForIteration() += this;
- }
-
- void loopIteration()
- {
- Loop::get().audienceForIteration() -= this;
- updateItemsFromSavedIndex();
+ mainCall.enqueue([this] () { updateItemsFromSavedIndex(); });
}
};
diff --git a/doomsday/apps/client/src/ui/widgets/singleplayersessionmenuwidget.cpp b/doomsday/apps/client/src/ui/widgets/singleplayersessionmenuwidget.cpp
index 2ede73e869..cad94179f0 100644
--- a/doomsday/apps/client/src/ui/widgets/singleplayersessionmenuwidget.cpp
+++ b/doomsday/apps/client/src/ui/widgets/singleplayersessionmenuwidget.cpp
@@ -30,7 +30,6 @@ using namespace de;
DENG_GUI_PIMPL(SingleplayerSessionMenuWidget)
, DENG2_OBSERVES(Games, Addition)
, DENG2_OBSERVES(Games, Readiness)
-, DENG2_OBSERVES(Loop, Iteration) // deferred updates
, DENG2_OBSERVES(App, GameChange)
{
/// ActionItem with a Game member, for loading a particular game.
@@ -66,6 +65,7 @@ DENG_GUI_PIMPL(SingleplayerSessionMenuWidget)
Mode mode;
FIFO pendingGames;
+ LoopCallback mainCall;
Instance(Public *i) : Base(i)
{
@@ -76,8 +76,6 @@ DENG_GUI_PIMPL(SingleplayerSessionMenuWidget)
~Instance()
{
- Loop::get().audienceForIteration() -= this;
-
App_Games().audienceForAddition() -= this;
App_Games().audienceForReadiness() -= this;
App::app().audienceForGameChange() -= this;
@@ -89,7 +87,10 @@ DENG_GUI_PIMPL(SingleplayerSessionMenuWidget)
pendingGames.put(&game);
// Update from main thread later.
- Loop::get().audienceForIteration() += this;
+ mainCall.enqueue([this] () {
+ addPendingGames();
+ updateGameAvailability();
+ });
}
void addExistingGames()
@@ -107,13 +108,6 @@ DENG_GUI_PIMPL(SingleplayerSessionMenuWidget)
(mode == ShowGamesWithMissingResources && !isReady));
}
- void loopIteration()
- {
- Loop::get().audienceForIteration() -= this;
- addPendingGames();
- updateGameAvailability();
- }
-
void addPendingGames()
{
if(pendingGames.isEmpty()) return;
@@ -178,7 +172,7 @@ DENG_GUI_PIMPL(SingleplayerSessionMenuWidget)
void currentGameChanged(game::Game const &)
{
- Loop::get().audienceForIteration() += this;
+ mainCall.enqueue([this] () { updateGameAvailability(); });
}
};
diff --git a/doomsday/sdk/libcore/include/de/core/loop.h b/doomsday/sdk/libcore/include/de/core/loop.h
index 5c01f4d479..685d6dbb6a 100644
--- a/doomsday/sdk/libcore/include/de/core/loop.h
+++ b/doomsday/sdk/libcore/include/de/core/loop.h
@@ -13,16 +13,19 @@
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details. You should have received a copy of
* the GNU Lesser General Public License along with this program; if not, see:
- * http://www.gnu.org/licenses
+ * http://www.gnu.org/licenses
*/
#ifndef LIBDENG2_LOOP_H
#define LIBDENG2_LOOP_H
#include
+#include
#include
#include
+#include
+
namespace de {
/**
@@ -88,6 +91,24 @@ public slots:
DENG2_PRIVATE(d)
};
+/**
+ * Utility for deferring callbacks via the Loop.
+ */
+class DENG2_PUBLIC LoopCallback : public Lockable, DENG2_OBSERVES(Loop, Iteration)
+{
+public:
+ typedef std::function Callback;
+
+ LoopCallback();
+ ~LoopCallback();
+
+ void enqueue(Callback func);
+ void loopIteration();
+
+private:
+ QList _funcs;
+};
+
} // namespace de
#endif // LIBDENG2_LOOP_H
diff --git a/doomsday/sdk/libcore/src/core/loop.cpp b/doomsday/sdk/libcore/src/core/loop.cpp
index da2ad335ca..f19fa38b70 100644
--- a/doomsday/sdk/libcore/src/core/loop.cpp
+++ b/doomsday/sdk/libcore/src/core/loop.cpp
@@ -13,7 +13,7 @@
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details. You should have received a copy of
* the GNU Lesser General Public License along with this program; if not, see:
- * http://www.gnu.org/licenses
+ * http://www.gnu.org/licenses
*/
#include "de/Loop"
@@ -119,4 +119,35 @@ void Loop::nextLoopIteration()
}
}
+LoopCallback::LoopCallback()
+{}
+
+LoopCallback::~LoopCallback()
+{
+ Loop::get().audienceForIteration() -= this;
+}
+
+void LoopCallback::enqueue(Callback func)
+{
+ DENG2_GUARD(this);
+
+ _funcs << func;
+ Loop::get().audienceForIteration() += this;
+}
+
+void LoopCallback::loopIteration()
+{
+ DENG2_GUARD(this);
+
+ Loop::get().audienceForIteration() -= this;
+
+ // Make a copy of the list if new callbacks get enqueued in the callback.
+ QList const funcs = _funcs;
+ _funcs.clear();
+ for(Callback const &cb : funcs)
+ {
+ cb();
+ }
+}
+
} // namespace de
diff --git a/doomsday/sdk/libcore/src/data/bank.cpp b/doomsday/sdk/libcore/src/data/bank.cpp
index db5fc574c9..322e8b0b8a 100644
--- a/doomsday/sdk/libcore/src/data/bank.cpp
+++ b/doomsday/sdk/libcore/src/data/bank.cpp
@@ -111,8 +111,7 @@ class Cache : public Lockable
} // namespace internal
-DENG2_PIMPL(Bank),
-DENG2_OBSERVES(Loop, Iteration) // notifications from other threads sent via main Loop
+DENG2_PIMPL(Bank)
{
/**
* Data item. Has ownership of the in-memory cached data and the source
@@ -535,6 +534,7 @@ DENG2_OBSERVES(Loop, Iteration) // notifications from other threads sent via mai
DataTree items;
TaskPool jobs;
NotifyQueue notifications;
+ LoopCallback mainCall;
Instance(Public *i, char const *name, Flags const &flg)
: Base(i)
@@ -550,7 +550,6 @@ DENG2_OBSERVES(Loop, Iteration) // notifications from other threads sent via mai
~Instance()
{
- Loop::get().audienceForIteration() -= this;
destroySerialCache();
}
@@ -674,16 +673,10 @@ DENG2_OBSERVES(Loop, Iteration) // notifications from other threads sent via mai
notifications.put(new Notification(notif));
if(isThreaded())
{
- Loop::get().audienceForIteration() += this;
+ mainCall.enqueue([this] () { performNotifications(); });
}
}
- void loopIteration()
- {
- Loop::get().audienceForIteration() -= this;
- performNotifications();
- }
-
void performNotifications()
{
forever