зеркало из https://github.com/mozilla/gecko-dev.git
303 строки
9.8 KiB
C++
303 строки
9.8 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#if !defined(StateWatching_h_)
|
|
# define StateWatching_h_
|
|
|
|
# include "mozilla/AbstractThread.h"
|
|
# include "mozilla/Logging.h"
|
|
# include "mozilla/TaskDispatcher.h"
|
|
# include "mozilla/UniquePtr.h"
|
|
# include "mozilla/Unused.h"
|
|
|
|
# include "nsISupportsImpl.h"
|
|
|
|
/*
|
|
* The state-watching machinery automates the process of responding to changes
|
|
* in various pieces of state.
|
|
*
|
|
* A standard programming pattern is as follows:
|
|
*
|
|
* mFoo = ...;
|
|
* NotifyStuffChanged();
|
|
* ...
|
|
* mBar = ...;
|
|
* NotifyStuffChanged();
|
|
*
|
|
* This pattern is error-prone and difficult to audit because it requires the
|
|
* programmer to manually trigger the update routine. This can be especially
|
|
* problematic when the update routine depends on numerous pieces of state, and
|
|
* when that state is modified across a variety of helper methods. In these
|
|
* cases the responsibility for invoking the routine is often unclear, causing
|
|
* developers to scatter calls to it like pixie dust. This can result in
|
|
* duplicate invocations (which is wasteful) and missing invocations in corner-
|
|
* cases (which is a source of bugs).
|
|
*
|
|
* This file provides a set of primitives that automatically handle updates and
|
|
* allow the programmers to explicitly construct a graph of state dependencies.
|
|
* When used correctly, it eliminates the guess-work and wasted cycles described
|
|
* above.
|
|
*
|
|
* There are two basic pieces:
|
|
* (1) Objects that can be watched for updates. These inherit WatchTarget.
|
|
* (2) Objects that receive objects and trigger processing. These inherit
|
|
* AbstractWatcher. In the current machinery, these exist only internally
|
|
* within the WatchManager, though that could change.
|
|
*
|
|
* Note that none of this machinery is thread-safe - it must all happen on the
|
|
* same owning thread. To solve multi-threaded use-cases, use state mirroring
|
|
* and watch the mirrored value.
|
|
*
|
|
* Given that semantics may change and comments tend to go out of date, we
|
|
* deliberately don't provide usage examples here. Grep around to find them.
|
|
*/
|
|
|
|
namespace mozilla {
|
|
|
|
extern LazyLogModule gStateWatchingLog;
|
|
|
|
# define WATCH_LOG(x, ...) \
|
|
MOZ_LOG(gStateWatchingLog, LogLevel::Debug, (x, ##__VA_ARGS__))
|
|
|
|
/*
|
|
* AbstractWatcher is a superclass from which all watchers must inherit.
|
|
*/
|
|
class AbstractWatcher {
|
|
public:
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractWatcher)
|
|
AbstractWatcher() : mDestroyed(false) {}
|
|
bool IsDestroyed() { return mDestroyed; }
|
|
virtual void Notify() = 0;
|
|
|
|
protected:
|
|
virtual ~AbstractWatcher() { MOZ_ASSERT(mDestroyed); }
|
|
bool mDestroyed;
|
|
};
|
|
|
|
/*
|
|
* WatchTarget is a superclass from which all watchable things must inherit.
|
|
* Unlike AbstractWatcher, it is a fully-implemented Mix-in, and the subclass
|
|
* needs only to invoke NotifyWatchers when something changes.
|
|
*
|
|
* The functionality that this class provides is not threadsafe, and should only
|
|
* be used on the thread that owns that WatchTarget.
|
|
*/
|
|
class WatchTarget {
|
|
public:
|
|
explicit WatchTarget(const char* aName) : mName(aName) {}
|
|
|
|
void AddWatcher(AbstractWatcher* aWatcher) {
|
|
MOZ_ASSERT(!mWatchers.Contains(aWatcher));
|
|
mWatchers.AppendElement(aWatcher);
|
|
}
|
|
|
|
void RemoveWatcher(AbstractWatcher* aWatcher) {
|
|
MOZ_ASSERT(mWatchers.Contains(aWatcher));
|
|
mWatchers.RemoveElement(aWatcher);
|
|
}
|
|
|
|
protected:
|
|
void NotifyWatchers() {
|
|
WATCH_LOG("%s[%p] notifying watchers\n", mName, this);
|
|
PruneWatchers();
|
|
for (size_t i = 0; i < mWatchers.Length(); ++i) {
|
|
mWatchers[i]->Notify();
|
|
}
|
|
}
|
|
|
|
private:
|
|
// We don't have Watchers explicitly unregister themselves when they die,
|
|
// because then they'd need back-references to all the WatchTargets they're
|
|
// subscribed to, and WatchTargets aren't reference-counted. So instead we
|
|
// just prune dead ones at appropriate times, which works just fine.
|
|
void PruneWatchers() {
|
|
for (int i = mWatchers.Length() - 1; i >= 0; --i) {
|
|
if (mWatchers[i]->IsDestroyed()) {
|
|
mWatchers.RemoveElementAt(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
nsTArray<RefPtr<AbstractWatcher>> mWatchers;
|
|
|
|
protected:
|
|
const char* mName;
|
|
};
|
|
|
|
/*
|
|
* Watchable is a wrapper class that turns any primitive into a WatchTarget.
|
|
*/
|
|
template <typename T>
|
|
class Watchable : public WatchTarget {
|
|
public:
|
|
Watchable(const T& aInitialValue, const char* aName)
|
|
: WatchTarget(aName), mValue(aInitialValue) {}
|
|
|
|
const T& Ref() const { return mValue; }
|
|
operator const T&() const { return Ref(); }
|
|
Watchable& operator=(const T& aNewValue) {
|
|
if (aNewValue != mValue) {
|
|
mValue = aNewValue;
|
|
NotifyWatchers();
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
private:
|
|
Watchable(const Watchable& aOther) = delete;
|
|
Watchable& operator=(const Watchable& aOther) = delete;
|
|
|
|
T mValue;
|
|
};
|
|
|
|
// Manager class for state-watching. Declare one of these in any class for which
|
|
// you want to invoke method callbacks.
|
|
//
|
|
// Internally, WatchManager maintains one AbstractWatcher per callback method.
|
|
// Consumers invoke Watch/Unwatch on a particular (WatchTarget, Callback) tuple.
|
|
// This causes an AbstractWatcher for |Callback| to be instantiated if it
|
|
// doesn't already exist, and registers it with |WatchTarget|.
|
|
//
|
|
// Using Direct Tasks on the TailDispatcher, WatchManager ensures that we fire
|
|
// watch callbacks no more than once per task, once all other operations for
|
|
// that task have been completed.
|
|
//
|
|
// WatchManager<OwnerType> is intended to be declared as a member of |OwnerType|
|
|
// objects. Given that, it and its owned objects can't hold permanent strong
|
|
// refs to the owner, since that would keep the owner alive indefinitely.
|
|
// Instead, it _only_ holds strong refs while waiting for Direct Tasks to fire.
|
|
// This ensures that everything is kept alive just long enough.
|
|
template <typename OwnerType>
|
|
class WatchManager {
|
|
public:
|
|
typedef void (OwnerType::*CallbackMethod)();
|
|
explicit WatchManager(OwnerType* aOwner, AbstractThread* aOwnerThread)
|
|
: mOwner(aOwner), mOwnerThread(aOwnerThread) {}
|
|
|
|
~WatchManager() {
|
|
if (!IsShutdown()) {
|
|
Shutdown();
|
|
}
|
|
}
|
|
|
|
bool IsShutdown() const { return !mOwner; }
|
|
|
|
// Shutdown needs to happen on mOwnerThread. If the WatchManager will be
|
|
// destroyed on a different thread, Shutdown() must be called manually.
|
|
void Shutdown() {
|
|
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
|
|
for (auto& watcher : mWatchers) {
|
|
watcher->Destroy();
|
|
}
|
|
mWatchers.Clear();
|
|
mOwner = nullptr;
|
|
}
|
|
|
|
void Watch(WatchTarget& aTarget, CallbackMethod aMethod) {
|
|
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
|
|
aTarget.AddWatcher(&EnsureWatcher(aMethod));
|
|
}
|
|
|
|
void Unwatch(WatchTarget& aTarget, CallbackMethod aMethod) {
|
|
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
|
|
PerCallbackWatcher* watcher = GetWatcher(aMethod);
|
|
MOZ_ASSERT(watcher);
|
|
aTarget.RemoveWatcher(watcher);
|
|
}
|
|
|
|
void ManualNotify(CallbackMethod aMethod) {
|
|
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
|
|
PerCallbackWatcher* watcher = GetWatcher(aMethod);
|
|
MOZ_ASSERT(watcher);
|
|
watcher->Notify();
|
|
}
|
|
|
|
private:
|
|
class PerCallbackWatcher : public AbstractWatcher {
|
|
public:
|
|
PerCallbackWatcher(OwnerType* aOwner, AbstractThread* aOwnerThread,
|
|
CallbackMethod aMethod)
|
|
: mOwner(aOwner),
|
|
mOwnerThread(aOwnerThread),
|
|
mCallbackMethod(aMethod) {}
|
|
|
|
void Destroy() {
|
|
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
|
|
mDestroyed = true;
|
|
mOwner = nullptr;
|
|
}
|
|
|
|
void Notify() override {
|
|
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
|
|
MOZ_DIAGNOSTIC_ASSERT(mOwner,
|
|
"mOwner is only null after destruction, "
|
|
"at which point we shouldn't be notified");
|
|
if (mNotificationPending) {
|
|
// We've already got a notification job in the pipe.
|
|
return;
|
|
}
|
|
mNotificationPending = true;
|
|
|
|
// Queue up our notification jobs to run in a stable state.
|
|
mOwnerThread->TailDispatcher().AddDirectTask(
|
|
NS_NewRunnableFunction("WatchManager::PerCallbackWatcher::Notify",
|
|
[self = RefPtr<PerCallbackWatcher>(this),
|
|
owner = RefPtr<OwnerType>(mOwner)]() {
|
|
if (!self->mDestroyed) {
|
|
((*owner).*(self->mCallbackMethod))();
|
|
}
|
|
self->mNotificationPending = false;
|
|
}));
|
|
}
|
|
|
|
bool CallbackMethodIs(CallbackMethod aMethod) const {
|
|
return mCallbackMethod == aMethod;
|
|
}
|
|
|
|
private:
|
|
~PerCallbackWatcher() = default;
|
|
|
|
OwnerType* mOwner; // Never null.
|
|
bool mNotificationPending = false;
|
|
RefPtr<AbstractThread> mOwnerThread;
|
|
CallbackMethod mCallbackMethod;
|
|
};
|
|
|
|
PerCallbackWatcher* GetWatcher(CallbackMethod aMethod) {
|
|
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
|
|
for (auto& watcher : mWatchers) {
|
|
if (watcher->CallbackMethodIs(aMethod)) {
|
|
return watcher;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
PerCallbackWatcher& EnsureWatcher(CallbackMethod aMethod) {
|
|
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
|
|
PerCallbackWatcher* watcher = GetWatcher(aMethod);
|
|
if (watcher) {
|
|
return *watcher;
|
|
}
|
|
watcher = mWatchers
|
|
.AppendElement(MakeAndAddRef<PerCallbackWatcher>(
|
|
mOwner, mOwnerThread, aMethod))
|
|
->get();
|
|
return *watcher;
|
|
}
|
|
|
|
nsTArray<RefPtr<PerCallbackWatcher>> mWatchers;
|
|
OwnerType* mOwner;
|
|
RefPtr<AbstractThread> mOwnerThread;
|
|
};
|
|
|
|
# undef WATCH_LOG
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif
|