blob: 12fb35a092987f6802ff02e02a85597b7c8186a5 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SERVICES_MEDIA_FRAMEWORK_ENGINE_H_
#define SERVICES_MEDIA_FRAMEWORK_ENGINE_H_
#include <list>
#include <queue>
#include <stack>
#include <unordered_map>
#include "base/synchronization/lock.h"
#include "services/media/framework/refs.h"
#include "services/media/framework/stages/stage.h"
namespace mojo {
namespace media {
//
// DESIGN
//
// Engine uses a 'work list' algorithm to operate the graph. The
// engine has a backlog of stages that need to be updated. To advance the
// operation of the graph, the engine removes a stage from the backlog and calls
// the stage's Update method. The Stage::Update may cause stages to be added
// synchronously to the the backlog. This procedure continues until the backlog
// is empty.
//
// Stage::Update is the stage's opportunity to react to the supply of new media
// via its inputs and the signalling of new demand via its outputs. During
// Update, the stage does whatever work it can with the current supply and
// demand, possibly supplying media downstream through its outputs and/or
// signalling new demand via its inputs. When a stage supplies media through
// an output, the downstream stage is added to the backlog. When a stage updates
// its demand through an input, the upstream stage is added to the backlog.
//
// The process starts when a stage invokes an update callback supplied by the
// engine. Stages that implement synchronous models never do this. Other stages
// do this as directed by the parts they host in accordance with their
// respective models. When a stage is ready to supply media or update demand
// due to external events, it calls the update callback. The engine responds by
// adding the stage to the backlog and then burning down the backlog. The stage
// that called back is updated first, and then all the work that can be done
// synchronously as a result of the external event is completed. In this way,
// the operation of the graph is driven by external events signalled through
// update callbacks.
//
// Currently, Engine uses an opportunistic threading model that only allows
// one thread to drive the backlog processing at any given time. The engine
// runs the processing on whatever thread enters it via an update callback.
// An engine employs a single lock that protects manipulation of the graph and
// processing of the backlog. Stage update methods are invoked with that lock
// taken. This arrangement implies the following constraints:
//
// 1) An update callback cannot be called synchronously with a Stage::Update
// call, because the lock is taken for the duration of Update, and the
// callback will take the lock.
// 2) A stage cannot update supply/demand on its inputs/outputs except during
// Update. When an external event occurs, the stage and/or its hosted part
// should update its internal state as required and invoke the callback.
// During the subsequent Update, the stage and/or part can then update
// supply and/or demand.
// 3) Threads used to call update callbacks must be suitable for operating the
// engine. There is currently no affordance for processing other tasks on
// the thread while the callback is running. A callback may run for a long
// time, depending on how much work needs to be done.
// 4) Parts cannot rely on being called back on the same thread on which they
// invoke update callbacks. This may require additional synchronization and
// thread transitions inside the part.
// 5) If a part takes a lock of its own during Update, it should not also hold
// that lock when calling the update callback. Doing so will result in
// deadlock.
//
// NOTE: Allocators, not otherwise discussed here, are required to be thread-
// safe so that packets may be cleaned up on any thread.
//
// In the future, the threading model will be enhanced. Intended features
// include:
// 1) Support for multiple threads.
// 2) Marshalling update callbacks to a different thread.
//
// Manages operation of a Graph.
class Engine {
public:
Engine();
~Engine();
// Prepares the input and the subgraph upstream of it.
void PrepareInput(const InputRef& input_ref);
// Unprepares the input and the subgraph upstream of it.
void UnprepareInput(const InputRef& input_ref);
// Flushes the output and the subgraph downstream of it.
void FlushOutput(const OutputRef& output_ref);
// Queues the stage for update and winds down the backlog.
void RequestUpdate(Stage* stage);
// Pushes the stage to the supply backlog if it isn't already there.
void PushToSupplyBacklog(Stage* stage);
// Pushes the stage to the demand backlog if it isn't already there.
void PushToDemandBacklog(Stage* stage);
private:
using UpstreamVisitor =
std::function<void(const InputRef& input,
const OutputRef& output,
const Stage::UpstreamCallback& callback)>;
using DownstreamVisitor =
std::function<void(const OutputRef& output,
const InputRef& input,
const Stage::DownstreamCallback& callback)>;
void VisitUpstream(const InputRef& input, const UpstreamVisitor& vistor);
void VisitDownstream(const OutputRef& output,
const DownstreamVisitor& vistor);
// Processes the entire backlog.
void Update();
// Performs processing for a single stage, updating the backlog accordingly.
void Update(Stage* stage);
// Pops a stage from the supply backlog and returns it or returns nullptr if
// the supply backlog is empty.
Stage* PopFromSupplyBacklog();
// Pops a stage from the demand backlog and returns it or returns nullptr if
// the demand backlog is empty.
Stage* PopFromDemandBacklog();
mutable base::Lock lock_;
// supply_backlog_ contains pointers to all the stages that have been supplied
// (packets or frames) but have not been updated since. demand_backlog_ does
// the same for demand. The use of queue vs stack here is a guess as to what
// will yield the best results. It's possible that only a single backlog is
// required.
// TODO(dalesat): Determine the best ordering and implement it.
std::queue<Stage*> supply_backlog_;
std::stack<Stage*> demand_backlog_;
bool packets_produced_;
};
} // namespace media
} // namespace mojo
#endif // SERVICES_MEDIA_FRAMEWORK_ENGINE_H_