зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1144486 - Implement state watching machinery. r=jww
This commit is contained in:
Родитель
8d8e6e7fae
Коммит
08da945616
|
@ -35,6 +35,7 @@ extern PRLogModuleInfo* gMediaDecoderLog;
|
|||
|
||||
|
||||
PRLogModuleInfo* gMediaPromiseLog;
|
||||
PRLogModuleInfo* gStateWatchingLog;
|
||||
|
||||
void
|
||||
EnsureMediaPromiseLog()
|
||||
|
@ -44,6 +45,14 @@ EnsureMediaPromiseLog()
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
EnsureStateWatchingLog()
|
||||
{
|
||||
if (!gStateWatchingLog) {
|
||||
gStateWatchingLog = PR_NewLogModule("StateWatching");
|
||||
}
|
||||
}
|
||||
|
||||
class VideoQueueMemoryFunctor : public nsDequeFunctor {
|
||||
public:
|
||||
VideoQueueMemoryFunctor() : mSize(0) {}
|
||||
|
@ -88,6 +97,7 @@ MediaDecoderReader::MediaDecoderReader(AbstractMediaDecoder* aDecoder)
|
|||
{
|
||||
MOZ_COUNT_CTOR(MediaDecoderReader);
|
||||
EnsureMediaPromiseLog();
|
||||
EnsureStateWatchingLog();
|
||||
}
|
||||
|
||||
MediaDecoderReader::~MediaDecoderReader()
|
||||
|
|
|
@ -0,0 +1,262 @@
|
|||
/* -*- 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. Note that some watchers may themselves be watched,
|
||||
* allowing watchers to be composed together.
|
||||
*
|
||||
* 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;
|
||||
|
||||
void EnsureStateWatchingLog();
|
||||
|
||||
#define WATCH_LOG(x, ...) \
|
||||
MOZ_ASSERT(gStateWatchingLog); \
|
||||
PR_LOG(gStateWatchingLog, PR_LOG_DEBUG, (x, ##__VA_ARGS__))
|
||||
|
||||
/*
|
||||
* AbstractWatcher is a superclass from which all watchers must inherit.
|
||||
*/
|
||||
class AbstractWatcher
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(AbstractWatcher)
|
||||
AbstractWatcher() : mDestroyed(false) {}
|
||||
bool IsDestroyed() { return mDestroyed; }
|
||||
virtual void Notify() = 0;
|
||||
|
||||
protected:
|
||||
virtual ~AbstractWatcher() {}
|
||||
virtual void CustomDestroy() {}
|
||||
|
||||
private:
|
||||
// Only the holder is allowed to invoke Destroy().
|
||||
friend class WatcherHolder;
|
||||
void Destroy()
|
||||
{
|
||||
mDestroyed = true;
|
||||
CustomDestroy();
|
||||
}
|
||||
|
||||
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);
|
||||
aWatcher->Notify();
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
};
|
||||
|
||||
/*
|
||||
* Watcher is the concrete AbstractWatcher implementation. It registers itself with
|
||||
* various WatchTargets, and accepts any number of callbacks that will be
|
||||
* invoked whenever any WatchTarget notifies of a change. It may also be watched
|
||||
* by other watchers.
|
||||
*
|
||||
* When a notification is received, a runnable is passed as "direct" to the
|
||||
* thread's tail dispatcher, which run directly (rather than via dispatch) when
|
||||
* the tail dispatcher fires. All subsequent notifications are ignored until the
|
||||
* runnable executes, triggering the updates and resetting the flags.
|
||||
*/
|
||||
class Watcher : public AbstractWatcher, public WatchTarget
|
||||
{
|
||||
public:
|
||||
explicit Watcher(const char* aName)
|
||||
: WatchTarget(aName), mNotifying(false) {}
|
||||
|
||||
void Notify() override
|
||||
{
|
||||
if (mNotifying) {
|
||||
return;
|
||||
}
|
||||
mNotifying = true;
|
||||
|
||||
// Queue up our notification jobs to run in a stable state.
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(this, &Watcher::DoNotify);
|
||||
AbstractThread::GetCurrent()->TailDispatcher().AddDirectTask(r.forget());
|
||||
}
|
||||
|
||||
void Watch(WatchTarget& aTarget) { aTarget.AddWatcher(this); }
|
||||
void Unwatch(WatchTarget& aTarget) { aTarget.RemoveWatcher(this); }
|
||||
|
||||
template<typename ThisType>
|
||||
void AddWeakCallback(ThisType* aThisVal, void(ThisType::*aMethod)())
|
||||
{
|
||||
mCallbacks.AppendElement(NS_NewNonOwningRunnableMethod(aThisVal, aMethod));
|
||||
}
|
||||
|
||||
protected:
|
||||
void CustomDestroy() override { mCallbacks.Clear(); }
|
||||
|
||||
void DoNotify()
|
||||
{
|
||||
MOZ_ASSERT(mNotifying);
|
||||
mNotifying = false;
|
||||
|
||||
// Notify dependent watchers.
|
||||
NotifyWatchers();
|
||||
|
||||
for (size_t i = 0; i < mCallbacks.Length(); ++i) {
|
||||
mCallbacks[i]->Run();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
nsTArray<nsCOMPtr<nsIRunnable>> mCallbacks;
|
||||
|
||||
bool mNotifying;
|
||||
};
|
||||
|
||||
/*
|
||||
* WatcherHolder encapsulates a Watcher and handles lifetime issues. Use it to
|
||||
* holder watcher members.
|
||||
*/
|
||||
class WatcherHolder
|
||||
{
|
||||
public:
|
||||
explicit WatcherHolder(const char* aName) { mWatcher = new Watcher(aName); }
|
||||
operator Watcher*() { return mWatcher; }
|
||||
Watcher* operator->() { return mWatcher; }
|
||||
|
||||
~WatcherHolder()
|
||||
{
|
||||
mWatcher->Destroy();
|
||||
mWatcher = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<Watcher> mWatcher;
|
||||
};
|
||||
|
||||
|
||||
#undef WATCH_LOG
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
|
@ -137,6 +137,7 @@ EXPORTS += [
|
|||
'SelfRef.h',
|
||||
'SharedBuffer.h',
|
||||
'SharedThreadPool.h',
|
||||
'StateWatching.h',
|
||||
'StreamBuffer.h',
|
||||
'TaskDispatcher.h',
|
||||
'ThreadPoolCOMListener.h',
|
||||
|
|
Загрузка…
Ссылка в новой задаче