зеркало из https://github.com/mozilla/gecko-dev.git
316 строки
9.5 KiB
C++
316 строки
9.5 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
|
/* 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 "AbstractThread.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 PRLogModuleInfo* gStateWatchingLog;
|
|
|
|
#define WATCH_LOG(x, ...) \
|
|
MOZ_ASSERT(gStateWatchingLog); \
|
|
MOZ_LOG(gStateWatchingLog, PR_LOG_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<nsRefPtr<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) {}
|
|
|
|
operator const T&() const { return mValue; }
|
|
Watchable& operator=(const T& aNewValue)
|
|
{
|
|
if (aNewValue != mValue) {
|
|
mValue = aNewValue;
|
|
NotifyWatchers();
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
private:
|
|
Watchable(const Watchable& aOther); // Not implemented
|
|
Watchable& operator=(const Watchable& aOther); // Not implemented
|
|
|
|
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 (size_t i = 0; i < mWatchers.Length(); ++i) {
|
|
mWatchers[i]->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 (mStrongRef) {
|
|
// We've already got a notification job in the pipe.
|
|
return;
|
|
}
|
|
mStrongRef = mOwner; // Hold the owner alive while notifying.
|
|
|
|
// Queue up our notification jobs to run in a stable state.
|
|
nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(this, &PerCallbackWatcher::DoNotify);
|
|
mOwnerThread->TailDispatcher().AddDirectTask(r.forget());
|
|
}
|
|
|
|
bool CallbackMethodIs(CallbackMethod aMethod) const
|
|
{
|
|
return mCallbackMethod == aMethod;
|
|
}
|
|
|
|
private:
|
|
~PerCallbackWatcher() {}
|
|
|
|
void DoNotify()
|
|
{
|
|
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
|
|
MOZ_ASSERT(mStrongRef);
|
|
nsRefPtr<OwnerType> ref = mStrongRef.forget();
|
|
((*ref).*mCallbackMethod)();
|
|
}
|
|
|
|
OwnerType* mOwner; // Never null.
|
|
nsRefPtr<OwnerType> mStrongRef; // Only non-null when notifying.
|
|
nsRefPtr<AbstractThread> mOwnerThread;
|
|
CallbackMethod mCallbackMethod;
|
|
};
|
|
|
|
PerCallbackWatcher* GetWatcher(CallbackMethod aMethod)
|
|
{
|
|
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
|
|
for (size_t i = 0; i < mWatchers.Length(); ++i) {
|
|
if (mWatchers[i]->CallbackMethodIs(aMethod)) {
|
|
return mWatchers[i];
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
PerCallbackWatcher& EnsureWatcher(CallbackMethod aMethod)
|
|
{
|
|
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
|
|
PerCallbackWatcher* watcher = GetWatcher(aMethod);
|
|
if (watcher) {
|
|
return *watcher;
|
|
}
|
|
watcher = mWatchers.AppendElement(new PerCallbackWatcher(mOwner, mOwnerThread, aMethod))->get();
|
|
return *watcher;
|
|
}
|
|
|
|
nsTArray<nsRefPtr<PerCallbackWatcher>> mWatchers;
|
|
OwnerType* mOwner;
|
|
nsRefPtr<AbstractThread> mOwnerThread;
|
|
};
|
|
|
|
#undef WATCH_LOG
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif
|