mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
bd796581dc
Backed out changeset 9b97e2aa2ed9 (bug 1165515) Backed out changeset 150606c022a2 (bug 1165515) Backed out changeset 4e875a488349 (bug 1165515) Backed out changeset 467e7feeb546 (bug 1165515) Backed out changeset d6b6cc373197 (bug 1165515) Backed out changeset 0615265b593c (bug 1165515) Backed out changeset fafd1dce9f08 (bug 1165515) Backed out changeset d1df869245f9 (bug 1165515) Backed out changeset 6876a7c63611 (bug 1165515) Backed out changeset b7841c94a9a3 (bug 1165515) Backed out changeset e5e3617f7c73 (bug 1165515) Backed out changeset 39be3db95978 (bug 1165515) Backed out changeset 0ec74176f8de (bug 1165515) Backed out changeset 5b928dd10d71 (bug 1165515)
316 lines
9.5 KiB
C++
316 lines
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
|