Project import generated by Copybara.

PiperOrigin-RevId: 253489161
This commit is contained in:
MediaPipe Team
2019-06-16 16:06:57 -07:00
committed by jqtang
commit d68f5e4169
844 changed files with 134997 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+421
View File
@@ -0,0 +1,421 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Forked from mediapipe/framework/calculator.proto.
// The forked proto must remain identical to the original proto and should be
// ONLY used by mediapipe open source project.
syntax = "proto3";
package mediapipe;
import public "mediapipe/framework/calculator_options.proto";
import "google/protobuf/any.proto";
import "mediapipe/framework/mediapipe_options.proto";
import "mediapipe/framework/packet_factory.proto";
import "mediapipe/framework/packet_generator.proto";
import "mediapipe/framework/status_handler.proto";
import "mediapipe/framework/stream_handler.proto";
option java_package = "com.google.mediapipe.proto";
option java_outer_classname = "CalculatorProto";
// Describes a MediaPipe Executor.
message ExecutorConfig {
// The name of the executor (used by a CalculatorGraphConfig::Node or
// PacketGeneratorConfig to specify which executor it will execute on).
// This field must be unique within a CalculatorGraphConfig. If this field
// is omitted or is an empty string, the ExecutorConfig describes the
// default executor.
//
// NOTE: The names "default" and "gpu" are reserved and must not be used.
string name = 1;
// The registered type of the executor. For example: "ThreadPoolExecutor".
// The framework will create an executor of this type (with the options in
// the options field) for the CalculatorGraph.
//
// The ExecutorConfig for the default executor may omit this field and let
// the framework choose an appropriate executor type. Note: If the options
// field is used in this case, it should contain the
// ThreadPoolExecutorOptions.
//
// If the ExecutorConfig for an additional (non-default) executor omits this
// field, the executor must be created outside the CalculatorGraph and
// passed to the CalculatorGraph for use.
string type = 2;
// The options passed to the Executor. The extension in the options field
// must match the type field. For example, if the type field is
// "ThreadPoolExecutor", then the options field should contain the
// ThreadPoolExecutorOptions.
MediaPipeOptions options = 3;
}
// A collection of input data to a CalculatorGraph.
message InputCollection {
// The name of the input collection. Name must match [a-z_][a-z0-9_]*
string name = 1;
// The names of each side packet. The number of side_packet_name
// must match the number of packets generated by the input file.
repeated string side_packet_name = 2;
// DEPRECATED: old way of referring to side_packet_name.
repeated string external_input_name = 1002;
// The input can be specified in several ways.
enum InputType {
// An invalid default value. This value is guaranteed to be the
// lowest enum value (i.e. don't add negative enum values).
UNKNOWN = 0;
// A recordio where each record is a serialized PacketManagerConfig.
// Each PacketManagerConfig must have the same number of packet
// factories in it as the number of side packet names. Furthermore,
// the output side packet name field in each PacketFactoryConfig
// must not be set. This is the most general input, and allows
// multiple side packet values to be set in arbitrarily complicated
// ways before each run.
RECORDIO = 1;
// A recordio where each record is a serialized packet payload.
// For example a recordio of serialized OmniaFeature protos dumped
// from Omnia.
FOREIGN_RECORDIO = 2;
// A text file where each line is a comma separated list. The number
// of elements for each csv string must be the same as the number
// of side_packet_name (and the order must match). Each line must
// be less than 1MiB in size. Lines comprising of only whitespace
// or only whitespace and a pound comment will be skipped.
FOREIGN_CSV_TEXT = 3;
// This and all higher values are invalid. Update this value to
// always be larger than any other enum values you add.
INVALID_UPPER_BOUND = 4;
}
// Sets the source of the input collection data.
// The default value is UNKNOWN.
InputType input_type = 3;
// A file name pointing to the data. The format of the data is
// specified by the "input_type" field. Multiple shards may be
// specified using @N or glob expressions.
string file_name = 4;
}
// A convenient way to specify a number of InputCollections.
message InputCollectionSet {
repeated InputCollection input_collection = 1;
}
// Additional information about an input stream.
message InputStreamInfo {
// A description of the input stream.
// This description uses the Calculator visible specification of
// a stream. The format is a tag, then an index with both being
// optional. If the tag is missing it is assumed to be "" and if
// the index is missing then it is assumed to be 0. If the index
// is provided then a colon (':') must be used.
// Examples:
// "TAG" -> tag "TAG", index 0
// "" -> tag "", index 0
// ":0" -> tag "", index 0
// ":3" -> tag "", index 3
// "VIDEO:0" -> tag "VIDEO", index 0
// "VIDEO:2" -> tag "VIDEO", index 2
string tag_index = 1;
// Whether the input stream is a back edge.
// By default, MediaPipe requires graphs to be acyclic and treats cycles in a
// graph as errors. To allow MediaPipe to accept a cyclic graph, set the
// back_edge fields of the input streams that are back edges to true. A
// cyclic graph usually has an obvious forward direction, and a back edge
// goes in the opposite direction. For a formal definition of a back edge,
// please see https://en.wikipedia.org/wiki/Depth-first_search.
bool back_edge = 2;
}
// Configs for the profiler for a calculator. Not applicable to subgraphs.
message ProfilerConfig {
// Size of the runtimes histogram intervals (in microseconds) to generate the
// histogram of the Process() time. The last interval extends to +inf.
// If not specified, the interval is 1000000 usec = 1 sec.
int64 histogram_interval_size_usec = 1;
// Number of intervals to generate the histogram of the Process() runtime.
// If not specified, one interval is used.
int64 num_histogram_intervals = 2;
// TODO: clean up after migration to MediaPipeProfiler.
// DEPRECATED: If true, the profiler also profiles the input output latency.
// Should be true only if the packet timestamps corresponds to the
// microseconds wall time from epoch.
bool enable_input_output_latency = 3 [deprecated = true];
// If true, the profiler starts profiling when graph is initialized.
bool enable_profiler = 4;
// If true, the profiler also profiles the stream latency and input-output
// latency.
// No-op if enable_profiler is false.
bool enable_stream_latency = 5;
// If true, the profiler uses packet timestamp (as production time and source
// production time) for packets added by calling
// CalculatorGraph::AddPacketToInputStream().
// If false, uses profiler's clock.
bool use_packet_timestamp_for_added_packet = 6;
// The maximum number of trace events buffered in memory.
int64 trace_log_capacity = 7;
// Trace event types that are not logged.
repeated int32 trace_event_types_disabled = 8;
// The output directory and base-name prefix for trace log files.
// Log files are written to: StrCat(trace_log_path, index, ".binarypb")
string trace_log_path = 9;
// The number of trace log files retained.
// The trace log files are named "trace_0.log" through "trace_k.log".
// The default value specifies 2 output files retained.
int32 trace_log_count = 10;
// The interval in microseconds between trace log output.
// The value -1 specifies output only when the graph is closed.
// The default value specifies trace log output once every 1 sec.
int64 trace_log_interval_usec = 11;
// The interval in microseconds between TimeNow and the highest times
// included in trace log output. This margin allows time for events
// to be appended to the TraceBuffer.
int64 trace_log_margin_usec = 12;
// True specifies an event for each calculator invocation.
// False specifies a separate event for each start and finish time.
bool trace_log_duration_events = 13;
// The number of trace log intervals per file. The total log duration is:
// trace_log_interval_usec * trace_log_file_count * trace_log_interval_count.
// The default value specifies 10 intervals per file.
int32 trace_log_interval_count = 14;
// An option to turn ON/OFF writing trace files to disk. Saving trace files to
// disk is enabled by default.
bool trace_log_disabled = 15;
}
// Describes the topology and function of a MediaPipe Graph. The graph of
// Nodes must be a Directed Acyclic Graph (DAG) except as annotated by
// "back_edge" in InputStreamInfo. Use a mediapipe::CalculatorGraph object to
// run the graph.
message CalculatorGraphConfig {
// A single node in the DAG.
message Node {
// The name of the node. This field is optional and doesn't generally
// need to be specified, but does improve error messaging.
string name = 1;
// The registered type of a calculator (provided via REGISTER_CALCULATOR),
// or of a subgraph (via REGISTER_MEDIAPIPE_GRAPH).
string calculator = 2;
// A Calculator can choose to access its input streams, output
// streams, and input side packets either by tag or by index. If the
// calculator chooses indexes then it will receive the streams or side
// packets in the same order as they are specified in this proto.
// If the calculator chooses to use tags then it must specify a
// tag along with each name. The field is given as "TAG:name".
// Meaning a tag name followed by a colon followed by the name.
// Tags use only upper case letters, numbers, and underscores, whereas
// names use only lower case letters, numbers, and underscores.
// Example:
// Node {
// calculator: "SomeAudioVideoCalculator"
// # This calculator accesses its inputs by index (no tag needed).
// input_stream: "combined_input"
// # This calculator accesses its outputs by tags, so all
// # output_streams must specify a tag.
// output_stream: "AUDIO:audio_stream"
// output_stream: "VIDEO:video_stream"
// # This calculator accesses its input side packets by tag.
// input_side_packet: "MODEL:model_01"
// }
// String(s) representing "TAG:name" of the stream(s) from which the current
// node will get its inputs. "TAG:" part is optional, see above.
// A calculator with no input stream is a source.
repeated string input_stream = 3;
// String(s) representing "TAG:name" of the stream(s) produced by this node.
// "TAG:" part is optional, see above. These must be different from any
// other output_streams specified for other nodes in the graph.
repeated string output_stream = 4;
// String(s) representing "TAG:name" of the input side packet(s).
// "TAG:" part is optional, see above.
repeated string input_side_packet = 5;
// String(s) representing "TAG:name" of the output side packet(s). Only
// used by subgraphs.
// "TAG:" part is optional, see above.
repeated string output_side_packet = 6;
// The options passed to the Calculator, in proto2 syntax.
CalculatorOptions options = 7;
// The options passed to the Calculator, in proto3 syntax.
// Each node_options message must have a different message type.
// If the same message type is specified in |options| and |node_options|,
// only the message in |options| is used.
repeated google.protobuf.Any node_options = 8;
// Note: the following fields are only applicable to calculators, not
// subgraphs.
// For a Source Calculator (i.e. a calculator with no inputs),
// this is the "layer" on which the calculator is executed. For a
// non-source calculator (i.e. a calculator with one or more input
// streams) this field has no effect. The sources on each layer
// are completely exhausted before Process() is called on any source
// calculator on a higher numbered layer.
// Example:
// Decoder -> Median Frame (requires all frames) -> Image Subtraction
// --------------------------------------->
// The entire video will be buffered on the edge from the decoder
// to the Image subtraction. To fix this problem, layers can be used.
// Decoder (layer 0) -> Median Frame -> Image Subtraction
// Decoder (layer 1) ----------------->
// The frames from layer 0 will no longer be buffered, but the video
// will be decoded again instead. Note, that different options can
// be used in the second decoder.
int32 source_layer = 9;
// Optional parameter that allows the user to indicate to the scheduler that
// this node has a buffering behavior (i.e. waits for a bunch of packets
// before emitting any) and specify the size of the buffer that is built up.
// The scheduler will then try to keep the maximum size of any input queues
// in the graph to remain below the maximum of all buffer_size_hints and
// max_queue_size (if specified). The ideal value is typically something
// larger than the actual number of buffered packets to maintain pipelining.
// The default value 0 indicates that the node has no buffering behavior.
int32 buffer_size_hint = 10;
// Config for this node's InputStreamHandler.
// If unspecified, the graph-level input stream handler will be used.
InputStreamHandlerConfig input_stream_handler = 11;
// Config for this node's OutputStreamHandler.
// If unspecified, the graph-level output stream handler will be used.
OutputStreamHandlerConfig output_stream_handler = 12;
// Additional information about an input stream. The |name| field of the
// InputStreamInfo must match an input_stream.
repeated InputStreamInfo input_stream_info = 13;
// Set the executor which the calculator will execute on.
string executor = 14;
// TODO: Remove from Node when switched to Profiler.
// DEPRECATED: Configs for the profiler.
ProfilerConfig profiler_config = 15 [deprecated = true];
// The maximum number of invocations that can be executed in parallel.
// If not specified, the limit is one invocation.
int32 max_in_flight = 16;
// DEPRECATED: For backwards compatibility we allow users to
// specify the old name for "input_side_packet" in proto configs.
// These are automatically converted to input_side_packets during
// config canonicalization.
repeated string external_input = 1005;
}
// The nodes.
repeated Node node = 1;
// Create a side packet using a PacketFactory. This side packet is
// created as close to the worker that does the work as possible. A
// PacketFactory is basically a PacketGenerator that takes no input side
// packets and produces a single output side packet.
repeated PacketFactoryConfig packet_factory = 6;
// Configs for PacketGenerators. Generators take zero or more
// input side packets and produce any number of output side
// packets. For example, MediaDecoderCalculator takes an input
// side packet with type DeletingFile. However, most users want
// to specify videos by ContentIdHex (i.e. video id). By using
// the VideoIdToLocalFileGenerator, a user can specify a video id
// (as a string) and obtain a DeletingFile to use with the decoder.
// PacketGenerators can take as a input side packet the output side
// packet of another PacketGenerator. The graph of PacketGenerators
// must be a directed acyclic graph.
repeated PacketGeneratorConfig packet_generator = 7;
// Number of threads for running calculators in multithreaded mode.
// If not specified, the scheduler will pick an appropriate number
// of threads depending on the number of available processors.
// To run on the calling thread, specify "ApplicationThreadExecutor"
// see: http://g3doc/mediapipe/g3doc/running.md.
int32 num_threads = 8;
// Configs for StatusHandlers that will be called after each call to
// Run() on the graph. StatusHandlers take zero or more input side
// packets and the ::util::Status returned by a graph run. For example,
// a StatusHandler could store information about graph failures and
// their causes for later monitoring. Note that graph failures during
// initialization may cause required input side packets (created by a
// PacketFactory or PacketGenerator) to be missing. In these cases,
// the handler with missing input side packets will be skipped.
repeated StatusHandlerConfig status_handler = 9;
// Specify input streams to the entire graph. Streams specified here may have
// packets added to them using CalculatorGraph::AddPacketToInputStream. This
// works much like a source calculator, except that the source is outside of
// the mediapipe graph.
repeated string input_stream = 10;
// Output streams for the graph when used as a subgraph.
repeated string output_stream = 15;
// Input side packets for the graph when used as a subgraph.
repeated string input_side_packet = 16;
// Output side packets for the graph when used as a subgraph.
repeated string output_side_packet = 17;
// Maximum queue size of any input stream in the graph. This can be used to
// control the memory usage of a MediaPipe graph by preventing fast sources
// from flooding the graph with packets. Any source that is connected to an
// input stream that has hit its maximum capacity will not be scheduled until
// the queue size falls under the specified limits, or if the scheduler queue
// is empty and no other nodes are running (to prevent possible deadlocks due
// to a incorrectly specified value). This global parameter is set to 100
// packets by default to enable pipelining. If any node indicates that it
// buffers packets before emitting them, then the max(node_buffer_size,
// max_queue_size) is used. Set this parameter to -1 to disable throttling
// (i.e. the graph will use as much memory as it requires). If not specified,
// the limit is 100 packets.
int32 max_queue_size = 11;
// If true, the graph run fails with an error when throttling prevents all
// calculators from running. If false, max_queue_size for an input stream
// is adjusted when throttling prevents all calculators from running.
bool report_deadlock = 21;
// Config for this graph's InputStreamHandler.
// If unspecified, the framework will automatically install the default
// handler, which works as follows.
// The calculator's Process() method is called for timestamp t when:
// - at least one stream has a packet available at t; and,
// - all other streams either have packets at t, or it is known that they will
// not have packets at t (i.e. their next timestamp bound is greater than t).
// The handler then provides all available packets with timestamp t, with no
// preprocessing.
InputStreamHandlerConfig input_stream_handler = 12;
// Config for this graph's OutputStreamHandler.
// If unspecified, the default output stream handler will be automatically
// installed by the framework which does not modify any outgoing packets.
OutputStreamHandlerConfig output_stream_handler = 13;
// Configs for Executors.
// The names of the executors must be distinct. The default executor, whose
// name is the empty string, is predefined. The num_threads field of the
// CalculatorGraphConfig specifies the number of threads in the default
// executor. If the config for the default executor is specified, the
// CalculatorGraphConfig must not have the num_threads field.
repeated ExecutorConfig executor = 14;
// The default profiler-config for all calculators. If set, this defines the
// profiling settings such as num_histogram_intervals for every calculator in
// the graph. Each of these settings can be overridden by the
// |profiler_config| specified for a node.
ProfilerConfig profiler_config = 18;
// The namespace used for class name lookup within this graph.
// An unqualified or partially qualified class name is looked up in
// this namespace first and then in enclosing namespaces.
string package = 19;
// The type name for the graph config, used for registering and referencing
// the graph config.
string type = 20;
// Can be used for annotating a graph.
MediaPipeOptions options = 1001;
}
+36
View File
@@ -0,0 +1,36 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Definitions for CalculatorBase.
#include "mediapipe/framework/calculator_base.h"
#include <algorithm>
namespace mediapipe {
CalculatorBase::CalculatorBase() {}
CalculatorBase::~CalculatorBase() {}
Timestamp CalculatorBase::SourceProcessOrder(
const CalculatorContext* cc) const {
Timestamp result = Timestamp::Max();
for (const OutputStreamShard& output : cc->Outputs()) {
result = std::min(result, output.NextTimestampBound());
}
return result;
}
} // namespace mediapipe
+224
View File
@@ -0,0 +1,224 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Defines CalculatorBase, the base class for feature computation.
#ifndef MEDIAPIPE_FRAMEWORK_CALCULATOR_BASE_H_
#define MEDIAPIPE_FRAMEWORK_CALCULATOR_BASE_H_
#include <type_traits>
#include "mediapipe/framework/calculator_context.h"
#include "mediapipe/framework/calculator_contract.h"
#include "mediapipe/framework/deps/registration.h"
#include "mediapipe/framework/port.h"
#include "mediapipe/framework/port/status.h"
#include "mediapipe/framework/timestamp.h"
namespace mediapipe {
// Experimental: CalculatorBase will eventually replace Calculator as the
// base class of leaf (non-subgraph) nodes in a CalculatorGraph.
//
// The base calculator class. A subclass must, at a minimum, provide the
// implementation of GetContract(), Process(), and register the calculator
// using REGISTER_CALCULATOR(MyClass).
//
// The framework calls four primary functions on a calculator.
// On initialization of the graph, a static function is called.
// GetContract()
// Then, for each run of the graph on a set of input side packets, the
// following sequence will occur.
// Open()
// Process() (repeatedly)
// Close()
//
// The entire calculator is constructed and destroyed for each graph run
// (set of input side packets, which could mean once per video, or once
// per image). Any expensive operations and large objects should be
// input side packets.
//
// The framework calls Open() to initialize the calculator.
// If appropriate, Open() should call cc->SetOffset() or
// cc->Outputs().Get(id)->SetNextTimestampBound() to allow the framework to
// better optimize packet queueing.
//
// The framework calls Process() for every packet received on the input
// streams. The framework guarantees that cc->InputTimestamp() will
// increase with every call to Process(). An empty packet will be on the
// input stream if there is no packet on a particular input stream (but
// some other input stream has a packet).
//
// The framework calls Close() after all calls to Process().
//
// Calculators with no inputs are referred to as "sources" and are handled
// slightly differently than non-sources (see the function comments for
// Process() for more details).
//
// Calculators must be thread-compatible.
// The framework does not call the non-const methods of a calculator from
// multiple threads at the same time. However, the thread that calls the
// methods of a calculator is not fixed. Therefore, calculators should not
// use ThreadLocal objects.
class CalculatorBase {
public:
CalculatorBase();
virtual ~CalculatorBase();
// The subclasses of CalculatorBase must implement GetContract.
// The calculator cannot be registered without it. Notice that although
// this function is static the registration macro provides access to
// each subclass' GetContract function.
//
// static ::mediapipe::Status GetContract(CalculatorContract* cc);
//
// GetContract fills in the calculator's contract with the framework, such
// as its expectations of what packets it will receive. When this function
// is called, the numbers of inputs, outputs, and input side packets will
// have already been determined by the calculator graph. You can use
// indexes, tags, or tag:index to access input streams, output streams,
// or input side packets.
//
// Example (uses tags for inputs and indexes for outputs and input side
// packets):
// cc->Inputs().Tag("VIDEO").Set<ImageFrame>("Input Image Frames.");
// cc->Inputs().Tag("AUDIO").Set<Matrix>("Input Audio Frames.");
// cc->Outputs().Index(0).Set<Matrix>("Output FooBar feature.");
// cc->InputSidePackets().Index(0).Set<MyModel>(
// "Model used for FooBar feature extraction.");
//
// Example (same number and type of outputs as inputs):
// for (int i = 0; i < cc->Inputs().NumEntries(); ++i) {
// // SetAny() is used to specify that whatever the type of the
// // stream is, it's acceptable. This does not mean that any
// // packet is acceptable. Packets in the stream still have a
// // particular type. SetAny() has the same effect as explicitly
// // setting the type to be the stream's type.
// cc->Inputs().Index(i).SetAny(StrCat("Generic Input Stream ", i));
// // Set each output to accept the same specific type as the
// // corresponding input.
// cc->Outputs().Index(i).SetSameAs(
// &cc->Inputs().Index(i), StrCat("Generic Output Stream ", i));
// }
// Open is called before any Process() calls, on a freshly constructed
// calculator. Subclasses may override this method to perform necessary
// setup, and possibly output Packets and/or set output streams' headers.
// Must return ::mediapipe::OkStatus() to indicate success. On failure any
// other status code can be returned. If failure is returned then the
// framework will call neither Process() nor Close() on the calculator (so any
// necessary cleanup should be done before returning failure or in the
// destructor).
virtual ::mediapipe::Status Open(CalculatorContext* cc) {
return ::mediapipe::OkStatus();
}
// Processes the incoming inputs. May call the methods on cc to access
// inputs and produce outputs.
//
// Process() called on a non-source node must return
// ::mediapipe::OkStatus() to indicate that all went well, or any other
// status code to signal an error.
// For example:
// ::mediapipe::UnknownError("Failure Message");
// Notice the convenience functions in util/task/canonical_errors.h .
// If a non-source Calculator returns tool::StatusStop(), then this
// signals the graph is being cancelled early. In this case, all
// source Calculators and graph input streams will be closed (and
// remaining Packets will propagate through the graph).
//
// A source node will continue to have Process() called on it as long
// as it returns ::mediapipe::OkStatus(). To indicate that there is
// no more data to be generated return tool::StatusStop(). Any other
// status indicates an error has occurred.
virtual ::mediapipe::Status Process(CalculatorContext* cc) = 0;
// Is called if Open() was called and succeeded. Is called either
// immediately after processing is complete or after a graph run has ended
// (if an error occurred in the graph). Must return ::mediapipe::OkStatus()
// to indicate success. On failure any other status code can be returned.
// Packets may be output during a call to Close(). However, output packets
// are silently discarded if Close() is called after a graph run has ended.
//
// NOTE: If Close() needs to perform an action only when processing is
// complete, Close() must check if cc->GraphStatus() is OK.
virtual ::mediapipe::Status Close(CalculatorContext* cc) {
return ::mediapipe::OkStatus();
}
// Returns a value according to which the framework selects
// the next source calculator to Process(); smaller value means
// Process() first. The default implementation returns the smallest
// NextTimestampBound value over all the output streams, but subclasses
// may override this. If a calculator is not a source, this method is
// not called.
// TODO: Does this method need to be virtual? No Calculator
// subclasses override the SourceProcessOrder method.
virtual Timestamp SourceProcessOrder(const CalculatorContext* cc) const;
};
using CalculatorBaseRegistry =
GlobalFactoryRegistry<std::unique_ptr<CalculatorBase>>;
namespace internal {
// Gives access to the static functions within subclasses of CalculatorBase.
// This adds functionality akin to virtual static functions.
class StaticAccessToCalculatorBase {
public:
virtual ~StaticAccessToCalculatorBase() {}
virtual ::mediapipe::Status GetContract(CalculatorContract* cc) = 0;
};
using StaticAccessToCalculatorBaseRegistry =
GlobalFactoryRegistry<std::unique_ptr<StaticAccessToCalculatorBase>>;
// Functions for checking that the calculator has the required GetContract.
template <class T>
constexpr bool CalculatorHasGetContract(decltype(&T::GetContract) /*unused*/) {
typedef ::mediapipe::Status (*GetContractType)(CalculatorContract * cc);
return std::is_same<decltype(&T::GetContract), GetContractType>::value;
}
template <class T>
constexpr bool CalculatorHasGetContract(...) {
return false;
}
// Provides access to the static functions within a specific subclass
// of CalculatorBase.
template <typename CalculatorBaseSubclass>
class StaticAccessToCalculatorBaseTyped : public StaticAccessToCalculatorBase {
public:
static_assert(std::is_base_of<::mediapipe::CalculatorBase,
CalculatorBaseSubclass>::value,
"Classes registered with REGISTER_CALCULATOR must be "
"subclasses of ::mediapipe::CalculatorBase.");
static_assert(CalculatorHasGetContract<CalculatorBaseSubclass>(nullptr),
"GetContract() must be defined with the correct signature in "
"every calculator.");
// Provides access to the static function GetContract within a specific
// subclass of CalculatorBase.
::mediapipe::Status GetContract(CalculatorContract* cc) final {
// CalculatorBaseSubclass must implement this function, since it is not
// implemented in the parent class.
return CalculatorBaseSubclass::GetContract(cc);
}
};
} // namespace internal
} // namespace mediapipe
#endif // MEDIAPIPE_FRAMEWORK_CALCULATOR_BASE_H_
+228
View File
@@ -0,0 +1,228 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/calculator_base.h"
// TODO: Move protos in another CL after the C++ code migration.
#include "mediapipe/framework/calculator.pb.h"
#include "mediapipe/framework/calculator_context.h"
#include "mediapipe/framework/calculator_context_manager.h"
#include "mediapipe/framework/calculator_registry.h"
#include "mediapipe/framework/calculator_state.h"
#include "mediapipe/framework/output_stream.h"
#include "mediapipe/framework/output_stream_manager.h"
#include "mediapipe/framework/output_stream_shard.h"
#include "mediapipe/framework/packet_set.h"
#include "mediapipe/framework/packet_type.h"
#include "mediapipe/framework/port/canonical_errors.h"
#include "mediapipe/framework/port/gmock.h"
#include "mediapipe/framework/port/gtest.h"
#include "mediapipe/framework/port/status_matchers.h"
#include "mediapipe/framework/tool/status_util.h"
#include "mediapipe/framework/tool/tag_map_helper.h"
namespace mediapipe {
namespace test_ns {
// A calculator which does nothing but accepts any number of input/output
// streams and input side packets.
class DeadEndCalculator : public CalculatorBase {
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
for (int i = 0; i < cc->Inputs().NumEntries(); ++i) {
cc->Inputs().Index(i).SetAny();
}
for (int i = 0; i < cc->Outputs().NumEntries(); ++i) {
cc->Outputs().Index(i).SetAny();
}
for (int i = 0; i < cc->InputSidePackets().NumEntries(); ++i) {
cc->InputSidePackets().Index(i).SetAny();
}
return ::mediapipe::OkStatus();
}
::mediapipe::Status Open(CalculatorContext* cc) override {
return ::mediapipe::OkStatus();
}
::mediapipe::Status Process(CalculatorContext* cc) override {
if (cc->Inputs().NumEntries() > 0) {
return ::mediapipe::OkStatus();
} else {
// This is a source calculator, but we don't produce any outputs.
return tool::StatusStop();
}
}
};
REGISTER_CALCULATOR(::mediapipe::test_ns::DeadEndCalculator);
namespace whitelisted_ns {
class DeadCalculator : public CalculatorBase {
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
return ::mediapipe::OkStatus();
}
::mediapipe::Status Open(CalculatorContext* cc) override {
return ::mediapipe::OkStatus();
}
::mediapipe::Status Process(CalculatorContext* cc) override {
return ::mediapipe::OkStatus();
}
};
} // namespace whitelisted_ns
} // namespace test_ns
class EndCalculator : public CalculatorBase {
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
return ::mediapipe::OkStatus();
}
::mediapipe::Status Open(CalculatorContext* cc) override {
return ::mediapipe::OkStatus();
}
::mediapipe::Status Process(CalculatorContext* cc) override {
return ::mediapipe::OkStatus();
}
};
REGISTER_CALCULATOR(::mediapipe::EndCalculator);
namespace {
TEST(CalculatorTest, SourceProcessOrder) {
internal::Collection<OutputStreamManager> output_stream_managers(
tool::CreateTagMap(2).ValueOrDie());
PacketType output0_type;
PacketType output1_type;
output0_type.SetAny();
output1_type.SetAny();
MEDIAPIPE_ASSERT_OK(
output_stream_managers.Index(0).Initialize("output0", &output0_type));
MEDIAPIPE_ASSERT_OK(
output_stream_managers.Index(1).Initialize("output1", &output1_type));
PacketSet input_side_packets(tool::CreateTagMap({}).ValueOrDie());
CalculatorState calculator_state("Node", /*node_id=*/0, "Calculator",
CalculatorGraphConfig::Node(), nullptr);
calculator_state.SetInputSidePackets(&input_side_packets);
CalculatorContextManager calculator_context_manager;
CalculatorContext calculator_context(&calculator_state,
tool::CreateTagMap({}).ValueOrDie(),
output_stream_managers.TagMap());
InputStreamShardSet& input_set = calculator_context.Inputs();
OutputStreamShardSet& output_set = calculator_context.Outputs();
output_set.Index(0).SetSpec(output_stream_managers.Index(0).Spec());
output_set.Index(0).SetNextTimestampBound(Timestamp(10));
output_set.Index(1).SetSpec(output_stream_managers.Index(1).Spec());
output_set.Index(1).SetNextTimestampBound(Timestamp(11));
CalculatorContextManager().PushInputTimestampToContext(
&calculator_context, Timestamp::Unstarted());
InputStreamSet input_streams(input_set.TagMap());
OutputStreamSet output_streams(output_set.TagMap());
for (CollectionItemId id = input_streams.BeginId();
id < input_streams.EndId(); ++id) {
input_streams.Get(id) = &input_set.Get(id);
}
for (CollectionItemId id = output_streams.BeginId();
id < output_streams.EndId(); ++id) {
output_streams.Get(id) = &output_set.Get(id);
}
calculator_state.SetInputStreamSet(&input_streams);
calculator_state.SetOutputStreamSet(&output_streams);
test_ns::DeadEndCalculator calculator;
EXPECT_EQ(Timestamp(10), calculator.SourceProcessOrder(&calculator_context));
output_set.Index(0).SetNextTimestampBound(Timestamp(100));
EXPECT_EQ(Timestamp(11), calculator.SourceProcessOrder(&calculator_context));
}
// Tests registration of a calculator within a namespace.
// DeadEndCalculator is registered in namespace "mediapipe::test_ns".
TEST(CalculatorTest, CreateByName) {
MEDIAPIPE_EXPECT_OK(CalculatorBaseRegistry::CreateByName( //
"mediapipe.test_ns.DeadEndCalculator"));
MEDIAPIPE_EXPECT_OK(CalculatorBaseRegistry::CreateByName( //
".mediapipe.test_ns.DeadEndCalculator"));
MEDIAPIPE_EXPECT_OK(CalculatorBaseRegistry::CreateByNameInNamespace( //
"alpha", ".mediapipe.test_ns.DeadEndCalculator"));
MEDIAPIPE_EXPECT_OK(CalculatorBaseRegistry::CreateByNameInNamespace( //
"alpha", "mediapipe.test_ns.DeadEndCalculator"));
MEDIAPIPE_EXPECT_OK(CalculatorBaseRegistry::CreateByNameInNamespace( //
"mediapipe", "mediapipe.test_ns.DeadEndCalculator"));
MEDIAPIPE_EXPECT_OK(CalculatorBaseRegistry::CreateByNameInNamespace( //
"mediapipe.test_ns.sub_ns", "DeadEndCalculator"));
EXPECT_EQ(CalculatorBaseRegistry::CreateByNameInNamespace( //
"mediapipe", "DeadEndCalculator")
.status()
.code(),
::mediapipe::StatusCode::kNotFound);
EXPECT_EQ(CalculatorBaseRegistry::CreateByName( //
"DeadEndCalculator")
.status()
.code(),
::mediapipe::StatusCode::kNotFound);
}
// Tests registration of a calculator within a whitelisted namespace.
TEST(CalculatorTest, CreateByNameWhitelisted) {
// Reset the registration namespace whitelist.
*const_cast<std::unordered_set<std::string>*>(
&NamespaceWhitelist::TopNamespaces()) = std::unordered_set<std::string>{
"mediapipe::test_ns::whitelisted_ns",
"mediapipe",
};
// Register a whitelisted calculator.
CalculatorBaseRegistry::Register(
"::mediapipe::test_ns::whitelisted_ns::DeadCalculator",
absl::make_unique< ::mediapipe::test_ns::whitelisted_ns::DeadCalculator>);
// A whitelisted calculator can be found in its own namespace.
MEDIAPIPE_EXPECT_OK(CalculatorBaseRegistry::CreateByNameInNamespace( //
"", "mediapipe.test_ns.whitelisted_ns.DeadCalculator"));
MEDIAPIPE_EXPECT_OK(CalculatorBaseRegistry::CreateByNameInNamespace( //
"mediapipe.sub_ns", "test_ns.whitelisted_ns.DeadCalculator"));
MEDIAPIPE_EXPECT_OK(CalculatorBaseRegistry::CreateByNameInNamespace( //
"mediapipe.sub_ns", "mediapipe.EndCalculator"));
// A whitelisted calculator can be found in the top-level namespace.
MEDIAPIPE_EXPECT_OK(CalculatorBaseRegistry::CreateByNameInNamespace( //
"", "DeadCalculator"));
MEDIAPIPE_EXPECT_OK(CalculatorBaseRegistry::CreateByNameInNamespace( //
"mediapipe", "DeadCalculator"));
MEDIAPIPE_EXPECT_OK(CalculatorBaseRegistry::CreateByNameInNamespace( //
"mediapipe.test_ns.sub_ns", "DeadCalculator"));
MEDIAPIPE_EXPECT_OK(CalculatorBaseRegistry::CreateByNameInNamespace( //
"", "EndCalculator"));
MEDIAPIPE_EXPECT_OK(CalculatorBaseRegistry::CreateByNameInNamespace( //
"mediapipe.test_ns.sub_ns", "EndCalculator"));
}
} // namespace
} // namespace mediapipe
+76
View File
@@ -0,0 +1,76 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/calculator_context.h"
namespace mediapipe {
const std::string& CalculatorContext::CalculatorType() const {
CHECK(calculator_state_);
return calculator_state_->CalculatorType();
}
const CalculatorOptions& CalculatorContext::Options() const {
CHECK(calculator_state_);
return calculator_state_->Options();
}
const std::string& CalculatorContext::NodeName() const {
CHECK(calculator_state_);
return calculator_state_->NodeName();
}
int CalculatorContext::NodeId() const {
CHECK(calculator_state_);
return calculator_state_->NodeId();
}
Counter* CalculatorContext::GetCounter(const std::string& name) {
CHECK(calculator_state_);
return calculator_state_->GetCounter(name);
}
const PacketSet& CalculatorContext::InputSidePackets() const {
return calculator_state_->InputSidePackets();
}
OutputSidePacketSet& CalculatorContext::OutputSidePackets() {
return calculator_state_->OutputSidePackets();
}
InputStreamShardSet& CalculatorContext::Inputs() { return inputs_; }
const InputStreamShardSet& CalculatorContext::Inputs() const { return inputs_; }
OutputStreamShardSet& CalculatorContext::Outputs() { return outputs_; }
const OutputStreamShardSet& CalculatorContext::Outputs() const {
return outputs_;
}
void CalculatorContext::SetOffset(TimestampDiff offset) {
for (auto& stream : outputs_) {
stream.SetOffset(offset);
}
}
const InputStreamSet& CalculatorContext::InputStreams() const {
return calculator_state_->InputStreams();
}
const OutputStreamSet& CalculatorContext::OutputStreams() const {
return calculator_state_->OutputStreams();
}
} // namespace mediapipe
+178
View File
@@ -0,0 +1,178 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_FRAMEWORK_CALCULATOR_CONTEXT_H_
#define MEDIAPIPE_FRAMEWORK_CALCULATOR_CONTEXT_H_
#include <memory>
#include <queue>
#include <string>
#include <utility>
#include "mediapipe/framework/calculator_state.h"
#include "mediapipe/framework/counter.h"
#include "mediapipe/framework/graph_service.h"
#include "mediapipe/framework/input_stream_shard.h"
#include "mediapipe/framework/output_stream_shard.h"
#include "mediapipe/framework/packet_set.h"
#include "mediapipe/framework/port.h"
#include "mediapipe/framework/port/any_proto.h"
#include "mediapipe/framework/port/status.h"
#include "mediapipe/framework/timestamp.h"
namespace mediapipe {
// A CalculatorContext provides information about the graph it is running
// inside of through a number of accessor functions: Inputs(), Outputs(),
// InputSidePackets(), Options(), etc.
//
// CalculatorBase APIs, such as CalculatorBase::Open(CalculatorContext* cc),
// CalculatorBase::Process(CalculatorContext* cc), and
// CalculatorBase::Close(CalculatorContext* cc), will only interact with
// its own CalculatorContext object for exchanging data with the framework.
class CalculatorContext {
public:
CalculatorContext(CalculatorState* calculator_state,
std::shared_ptr<tool::TagMap> input_tag_map,
std::shared_ptr<tool::TagMap> output_tag_map)
: calculator_state_(calculator_state),
inputs_(std::move(input_tag_map)),
outputs_(std::move(output_tag_map)) {}
CalculatorContext(const CalculatorContext&) = delete;
CalculatorContext& operator=(const CalculatorContext&) = delete;
const std::string& NodeName() const;
int NodeId() const;
const std::string& CalculatorType() const;
// Returns the options given to this calculator. The Calculator or
// CalculatorBase implementation may get its options by calling
// GetExtension() on the result.
const CalculatorOptions& Options() const;
// Returns the options given to this calculator. Template argument T must
// be the type of the protobuf extension message or the protobuf::Any
// message containing the options.
template <class T>
const T& Options() const {
return calculator_state_->Options<T>();
}
// Returns a counter using the graph's counter factory. The counter's name is
// the passed-in name, prefixed by the calculator node's name (if present) or
// the calculator's type (if not).
Counter* GetCounter(const std::string& name);
// Returns the current input timestamp, or Timestamp::Unset if there are
// no input packets.
Timestamp InputTimestamp() const {
return input_timestamps_.empty() ? Timestamp::Unset()
: input_timestamps_.front();
}
// Returns a reference to the input side packet set.
const PacketSet& InputSidePackets() const;
// Returns a reference to the output side packet collection.
OutputSidePacketSet& OutputSidePackets();
// Returns a reference to the input stream collection.
// You may consume or move the value packets from the Inputs.
InputStreamShardSet& Inputs();
// Returns a const reference to the input stream collection.
const InputStreamShardSet& Inputs() const;
// Returns a reference to the output stream collection.
OutputStreamShardSet& Outputs();
// Returns a const reference to the output stream collection.
const OutputStreamShardSet& Outputs() const;
// Sets this packet timestamp offset for Packets going to all outputs.
// If you only want to set the offset for a single output stream then
// use OutputStream::SetOffset() directly.
void SetOffset(TimestampDiff offset);
// Returns the status of the graph run.
//
// NOTE: This method should only be called during CalculatorBase::Close().
::mediapipe::Status GraphStatus() const { return graph_status_; }
ProfilingContext* GetProfilingContext() const {
return calculator_state_->GetSharedProfilingContext().get();
}
template <typename T>
class ServiceBinding {
public:
bool IsAvailable() {
return calculator_state_->IsServiceAvailable(service_);
}
T& GetObject() { return calculator_state_->GetServiceObject(service_); }
ServiceBinding(CalculatorState* calculator_state,
const GraphService<T>& service)
: calculator_state_(calculator_state), service_(service) {}
private:
CalculatorState* calculator_state_;
const GraphService<T>& service_;
};
template <typename T>
ServiceBinding<T> Service(const GraphService<T>& service) {
return ServiceBinding<T>(calculator_state_, service);
}
private:
int NumberOfTimestamps() const {
return static_cast<int>(input_timestamps_.size());
}
bool HasInputTimestamp() const { return !input_timestamps_.empty(); }
// Adds a new input timestamp by the friend class CalculatorContextManager.
void PushInputTimestamp(Timestamp input_timestamp) {
input_timestamps_.push(input_timestamp);
}
void PopInputTimestamp() {
CHECK(!input_timestamps_.empty());
input_timestamps_.pop();
}
void SetGraphStatus(const ::mediapipe::Status& status) {
graph_status_ = status;
}
// Interface for the friend class Calculator.
const InputStreamSet& InputStreams() const;
const OutputStreamSet& OutputStreams() const;
// Stores the shared data across all CalculatorContext objects, including
// input side packets, calculator options, node name, etc.
// TODO: Removes unnecessary fields from CalculatorState after
// migrating all clients to CalculatorContext.
CalculatorState* calculator_state_;
InputStreamShardSet inputs_;
OutputStreamShardSet outputs_;
// The queue of timestamp values to Process() in this calculator context.
std::queue<Timestamp> input_timestamps_;
// The status of the graph run. Only used when Close() is called.
::mediapipe::Status graph_status_;
// Accesses CalculatorContext for setting input timestamp.
friend class CalculatorContextManager;
};
} // namespace mediapipe
#endif // MEDIAPIPE_FRAMEWORK_CALCULATOR_CONTEXT_H_
@@ -0,0 +1,111 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/calculator_context_manager.h"
#include <utility>
#include "absl/memory/memory.h"
#include "absl/synchronization/mutex.h"
#include "mediapipe/framework/port/logging.h"
namespace mediapipe {
void CalculatorContextManager::Initialize(
CalculatorState* calculator_state,
std::shared_ptr<tool::TagMap> input_tag_map,
std::shared_ptr<tool::TagMap> output_tag_map,
bool calculator_run_in_parallel) {
CHECK(calculator_state);
calculator_state_ = calculator_state;
input_tag_map_ = std::move(input_tag_map);
output_tag_map_ = std::move(output_tag_map);
calculator_run_in_parallel_ = calculator_run_in_parallel;
}
::mediapipe::Status CalculatorContextManager::PrepareForRun(
std::function<::mediapipe::Status(CalculatorContext*)>
setup_shards_callback) {
setup_shards_callback_ = std::move(setup_shards_callback);
default_context_ = absl::make_unique<CalculatorContext>(
calculator_state_, input_tag_map_, output_tag_map_);
return setup_shards_callback_(default_context_.get());
}
void CalculatorContextManager::CleanupAfterRun() {
default_context_ = nullptr;
absl::MutexLock lock(&contexts_mutex_);
active_contexts_.clear();
idle_contexts_.clear();
}
CalculatorContext* CalculatorContextManager::GetDefaultCalculatorContext()
const {
CHECK(default_context_.get());
return default_context_.get();
}
CalculatorContext* CalculatorContextManager::GetFrontCalculatorContext(
Timestamp* context_input_timestamp) {
CHECK(calculator_run_in_parallel_);
absl::MutexLock lock(&contexts_mutex_);
CHECK(!active_contexts_.empty());
*context_input_timestamp = active_contexts_.begin()->first;
return active_contexts_.begin()->second.get();
}
CalculatorContext* CalculatorContextManager::PrepareCalculatorContext(
Timestamp input_timestamp) {
if (!calculator_run_in_parallel_) {
return GetDefaultCalculatorContext();
}
absl::MutexLock lock(&contexts_mutex_);
CHECK(!::mediapipe::ContainsKey(active_contexts_, input_timestamp))
<< "Multiple invocations with the same timestamps are not allowed with "
"parallel execution, input_timestamp = "
<< input_timestamp;
CalculatorContext* calculator_context = nullptr;
if (idle_contexts_.empty()) {
auto new_context = absl::make_unique<CalculatorContext>(
calculator_state_, input_tag_map_, output_tag_map_);
MEDIAPIPE_CHECK_OK(setup_shards_callback_(new_context.get()));
calculator_context = new_context.get();
active_contexts_.emplace(input_timestamp, std::move(new_context));
} else {
// Retrieves an inactive calculator context from idle_contexts_.
calculator_context = idle_contexts_.front().get();
active_contexts_.emplace(input_timestamp,
std::move(idle_contexts_.front()));
idle_contexts_.pop_front();
}
return calculator_context;
}
void CalculatorContextManager::RecycleCalculatorContext() {
absl::MutexLock lock(&contexts_mutex_);
// The first element in active_contexts_ will be recycled.
auto iter = active_contexts_.begin();
idle_contexts_.push_back(std::move(iter->second));
active_contexts_.erase(iter);
}
bool CalculatorContextManager::HasActiveContexts() {
if (!calculator_run_in_parallel_) {
return false;
}
absl::MutexLock lock(&contexts_mutex_);
return !active_contexts_.empty();
}
} // namespace mediapipe
@@ -0,0 +1,146 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_FRAMEWORK_CALCULATOR_CONTEXT_MANAGER_H_
#define MEDIAPIPE_FRAMEWORK_CALCULATOR_CONTEXT_MANAGER_H_
#include <deque>
#include <functional>
#include <map>
#include <memory>
#include "absl/base/thread_annotations.h"
#include "absl/synchronization/mutex.h"
#include "mediapipe/framework/calculator_context.h"
#include "mediapipe/framework/calculator_state.h"
#include "mediapipe/framework/port/logging.h"
#include "mediapipe/framework/port/status.h"
#include "mediapipe/framework/timestamp.h"
#include "mediapipe/framework/tool/tag_map.h"
namespace mediapipe {
// Calculator context manager owns and manages all calculator context objects of
// a calculator node.
class CalculatorContextManager {
public:
CalculatorContextManager() {}
void Initialize(CalculatorState* calculator_state,
std::shared_ptr<tool::TagMap> input_tag_map,
std::shared_ptr<tool::TagMap> output_tag_map,
bool calculator_run_in_parallel);
// Sets the callback that can setup the input and output stream shards in a
// newly constructed calculator context. Then, initializes the default
// calculator context.
::mediapipe::Status PrepareForRun(
std::function<::mediapipe::Status(CalculatorContext*)>
setup_shards_callback);
// Invoked by CalculatorNode::CleanupAfterRun().
void CleanupAfterRun() LOCKS_EXCLUDED(contexts_mutex_);
// Returns true if the default calculator context has been initialized.
bool HasDefaultCalculatorContext() const {
return default_context_ != nullptr;
}
// Returns a pointer to the default calculator context that is used for
// sequential execution. A source node should always reuse its default
// calculator context.
CalculatorContext* GetDefaultCalculatorContext() const;
// Returns the context with the smallest input timestamp in active_contexts_.
// The input timestamp of the calculator context is returned in
// *context_input_timestamp.
CalculatorContext* GetFrontCalculatorContext(
Timestamp* context_input_timestamp) LOCKS_EXCLUDED(contexts_mutex_);
// For sequential execution, returns a pointer to the default calculator
// context. For parallel execution, creates or reuses a calculator context,
// and inserts the calculator context with the given input timestamp into
// active_contexts_. Returns a pointer to the prepared calculator context.
// The ownership of the calculator context object isn't tranferred to the
// caller.
CalculatorContext* PrepareCalculatorContext(Timestamp input_timestamp)
LOCKS_EXCLUDED(contexts_mutex_);
// Removes the context with the smallest input timestamp from active_contexts_
// and moves the calculator context to idle_contexts_. The caller must
// guarantee that the output shards in the calculator context have been
// propagated before calling this function.
void RecycleCalculatorContext() LOCKS_EXCLUDED(contexts_mutex_);
// Returns true if active_contexts_ is non-empty.
bool HasActiveContexts() LOCKS_EXCLUDED(contexts_mutex_);
int NumberOfContextTimestamps(
const CalculatorContext& calculator_context) const {
return calculator_context.NumberOfTimestamps();
}
bool ContextHasInputTimestamp(
const CalculatorContext& calculator_context) const {
return calculator_context.HasInputTimestamp();
}
void PushInputTimestampToContext(CalculatorContext* calculator_context,
Timestamp input_timestamp) {
CHECK(calculator_context);
calculator_context->PushInputTimestamp(input_timestamp);
}
void PopInputTimestampFromContext(CalculatorContext* calculator_context) {
CHECK(calculator_context);
calculator_context->PopInputTimestamp();
}
void SetGraphStatusInContext(CalculatorContext* calculator_context,
const ::mediapipe::Status& status) {
CHECK(calculator_context);
calculator_context->SetGraphStatus(status);
}
private:
CalculatorState* calculator_state_;
std::shared_ptr<tool::TagMap> input_tag_map_;
std::shared_ptr<tool::TagMap> output_tag_map_;
bool calculator_run_in_parallel_;
// The callback to setup the input and output stream shards in a newly
// constructed calculator context.
// NOTE: This callback invokes input/output stream handler methods.
// The callback is used to break the circular dependency between
// calculator context manager and input/output stream handlers.
std::function<::mediapipe::Status(CalculatorContext*)> setup_shards_callback_;
// The default calculator context that is always reused for sequential
// execution. It is also used by Open() and Close() method of a parallel
// calculator.
std::unique_ptr<CalculatorContext> default_context_;
// The mutex for synchronizing the operations on active_contexts_ and
// idle_contexts_ during parallel execution.
absl::Mutex contexts_mutex_;
// A map from input timestamps to calculator contexts.
std::map<Timestamp, std::unique_ptr<CalculatorContext>> active_contexts_
GUARDED_BY(contexts_mutex_);
// Idle calculator contexts that are ready for reuse.
std::deque<std::unique_ptr<CalculatorContext>> idle_contexts_
GUARDED_BY(contexts_mutex_);
};
} // namespace mediapipe
#endif // MEDIAPIPE_FRAMEWORK_CALCULATOR_CONTEXT_MANAGER_H_
@@ -0,0 +1,146 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/calculator_context.h"
// TODO: Move protos in another CL after the C++ code migration.
#include "mediapipe/framework/calculator.pb.h"
#include "mediapipe/framework/calculator_context_manager.h"
#include "mediapipe/framework/calculator_state.h"
#include "mediapipe/framework/output_stream_manager.h"
#include "mediapipe/framework/output_stream_shard.h"
#include "mediapipe/framework/port/canonical_errors.h"
#include "mediapipe/framework/port/gmock.h"
#include "mediapipe/framework/port/gtest.h"
#include "mediapipe/framework/port/parse_text_proto.h"
#include "mediapipe/framework/port/status_matchers.h"
#include "mediapipe/framework/testdata/night_light_calculator.pb.h"
#include "mediapipe/framework/testdata/sky_light_calculator.pb.h"
#include "mediapipe/framework/tool/tag_map_helper.h"
namespace mediapipe {
namespace test_ns {
std::string Proto3GraphStr() {
static std::string kProto3GraphStr = R"(
node {
calculator: "NightLightCalculator"
input_side_packet: "input_value"
output_stream: "values"
options {
[mediapipe.NightLightCalculatorOptions.ext] {
base_timestamp: 123
output_header: PASS_HEADER
jitter: 0.123
}
}
}
node {
calculator: "NightLightCalculator"
input_side_packet: "input_value"
output_stream: "values_also"
node_options: {
[type.googleapis.com/mediapipe.NightLightCalculatorOptions] {
base_timestamp: 123
output_header: PASS_HEADER
jitter: 0.123
}
}
}
node {
calculator: "SkyLightCalculator"
node_options: {
[type.googleapis.com/mediapipe.SkyLightCalculatorOptions] {
sky_color: "sky_blue"
}
}
}
node {
calculator: "SkyLightCalculator"
input_side_packet: "label"
input_stream: "values"
output_stream: "labelled_timestamps"
node_options: {
[type.googleapis.com/mediapipe.SkyLightCalculatorOptions] {
sky_color: "light_blue"
sky_grid: 2
sky_grid: 4
sky_grid: 8
}
}
}
node {
calculator: "MakeVectorCalculator"
input_stream: "labelled_timestamps"
output_stream: "timestamp_vectors"
}
)";
return kProto3GraphStr;
}
std::unique_ptr<CalculatorState> MakeCalculatorState(
const CalculatorGraphConfig::Node& node_config, int node_id) {
auto result = absl::make_unique<CalculatorState>(
"Node", node_id, "Calculator", node_config, nullptr);
return result;
}
std::unique_ptr<CalculatorContext> MakeCalculatorContext(
CalculatorState* calculator_state) {
return absl::make_unique<CalculatorContext>(
calculator_state, tool::CreateTagMap({}).ValueOrDie(),
tool::CreateTagMap({}).ValueOrDie());
}
TEST(CalculatorTest, NodeId) {
mediapipe::CalculatorGraphConfig config =
ParseTextProtoOrDie<mediapipe::CalculatorGraphConfig>(Proto3GraphStr());
auto calculator_state_0 = MakeCalculatorState(config.node(0), 0);
auto cc_0 = MakeCalculatorContext(&*calculator_state_0);
auto calculator_state_1 = MakeCalculatorState(config.node(1), 1);
auto cc_1 = MakeCalculatorContext(&*calculator_state_1);
auto calculator_state_3 = MakeCalculatorState(config.node(3), 3);
auto cc_3 = MakeCalculatorContext(&*calculator_state_3);
EXPECT_EQ(cc_0->NodeId(), calculator_state_0->NodeId());
EXPECT_EQ(cc_1->NodeId(), calculator_state_1->NodeId());
EXPECT_EQ(cc_3->NodeId(), calculator_state_3->NodeId());
}
TEST(CalculatorTest, GetOptions) {
mediapipe::CalculatorGraphConfig config =
ParseTextProtoOrDie<mediapipe::CalculatorGraphConfig>(Proto3GraphStr());
auto calculator_state_0 = MakeCalculatorState(config.node(0), 0);
auto cc_0 = MakeCalculatorContext(&*calculator_state_0);
auto calculator_state_1 = MakeCalculatorState(config.node(1), 1);
auto cc_1 = MakeCalculatorContext(&*calculator_state_1);
auto calculator_state_3 = MakeCalculatorState(config.node(3), 3);
auto cc_3 = MakeCalculatorContext(&*calculator_state_3);
// Get a proto2 options extension from Node::options.
EXPECT_EQ(cc_0->Options<NightLightCalculatorOptions>().jitter(), 0.123);
// Get a proto2 options extension from Node::node_options.
EXPECT_EQ(cc_1->Options<NightLightCalculatorOptions>().jitter(), 0.123);
// Get a proto3 options protobuf::Any from Node::node_options.
EXPECT_EQ(cc_3->Options<SkyLightCalculatorOptions>().sky_color(),
"light_blue");
}
} // namespace test_ns
} // namespace mediapipe
+140
View File
@@ -0,0 +1,140 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/calculator_contract.h"
#include <utility>
#include <vector>
#include "absl/memory/memory.h"
#include "mediapipe/framework/port/status.h"
#include "mediapipe/framework/port/status_builder.h"
#include "mediapipe/framework/tool/tag_map.h"
namespace mediapipe {
::mediapipe::Status CalculatorContract::Initialize(
const CalculatorGraphConfig::Node& node) {
std::vector<::mediapipe::Status> statuses;
auto input_stream_statusor = tool::TagMap::Create(node.input_stream());
if (!input_stream_statusor.ok()) {
statuses.push_back(std::move(input_stream_statusor).status());
}
auto output_stream_statusor = tool::TagMap::Create(node.output_stream());
if (!output_stream_statusor.ok()) {
statuses.push_back(std::move(output_stream_statusor).status());
}
auto input_side_packet_statusor =
tool::TagMap::Create(node.input_side_packet());
if (!input_side_packet_statusor.ok()) {
statuses.push_back(std::move(input_side_packet_statusor).status());
}
auto output_side_packet_statusor =
tool::TagMap::Create(node.output_side_packet());
if (!output_side_packet_statusor.ok()) {
statuses.push_back(std::move(output_side_packet_statusor).status());
}
if (!statuses.empty()) {
auto builder = ::mediapipe::UnknownErrorBuilder(MEDIAPIPE_LOC)
<< "Unable to initialize TagMaps for node.";
for (const auto& status : statuses) {
builder << "\n" << status.message();
}
#if !(defined(MEDIAPIPE_LITE) || defined(MEDIAPIPE_MOBILE))
builder << "\nFor calculator:\n";
builder << node.DebugString();
#endif // !(MEDIAPIPE_LITE || MEDIAPIPE_MOBILE)
return std::move(builder);
}
node_config_ = &node;
options_.Initialize(*node_config_);
// Create the PacketTypeSets.
inputs_ = absl::make_unique<PacketTypeSet>(
std::move(input_stream_statusor).ValueOrDie());
outputs_ = absl::make_unique<PacketTypeSet>(
std::move(output_stream_statusor).ValueOrDie());
input_side_packets_ = absl::make_unique<PacketTypeSet>(
std::move(input_side_packet_statusor).ValueOrDie());
output_side_packets_ = absl::make_unique<PacketTypeSet>(
std::move(output_side_packet_statusor).ValueOrDie());
return ::mediapipe::OkStatus();
}
::mediapipe::Status CalculatorContract::Initialize(
const PacketGeneratorConfig& node) {
std::vector<::mediapipe::Status> statuses;
auto input_side_packet_statusor =
tool::TagMap::Create(node.input_side_packet());
if (!input_side_packet_statusor.ok()) {
statuses.push_back(std::move(input_side_packet_statusor).status());
}
auto output_side_packet_statusor =
tool::TagMap::Create(node.output_side_packet());
if (!output_side_packet_statusor.ok()) {
statuses.push_back(std::move(output_side_packet_statusor).status());
}
if (!statuses.empty()) {
auto builder = UnknownErrorBuilder(MEDIAPIPE_LOC)
<< "NodeTypeInfo Initialization failed.";
for (const auto& status : statuses) {
builder << "\n" << status.message();
}
#if !(defined(MEDIAPIPE_LITE) || defined(MEDIAPIPE_MOBILE))
builder << "\nFor packet_generator:\n";
builder << node.DebugString();
#endif // !(MEDIAPIPE_LITE || MEDIAPIPE_MOBILE)
return std::move(builder);
}
input_side_packets_ = absl::make_unique<PacketTypeSet>(
std::move(input_side_packet_statusor).ValueOrDie());
output_side_packets_ = absl::make_unique<PacketTypeSet>(
std::move(output_side_packet_statusor).ValueOrDie());
return ::mediapipe::OkStatus();
}
::mediapipe::Status CalculatorContract::Initialize(
const StatusHandlerConfig& node) {
std::vector<::mediapipe::Status> statuses;
auto input_side_packet_statusor =
tool::TagMap::Create(node.input_side_packet());
if (!input_side_packet_statusor.ok()) {
statuses.push_back(std::move(input_side_packet_statusor).status());
}
if (!statuses.empty()) {
auto builder = ::mediapipe::UnknownErrorBuilder(MEDIAPIPE_LOC)
<< "NodeTypeInfo Initialization failed.";
for (const auto& status : statuses) {
builder << "\n" << status.message();
}
#if !(defined(MEDIAPIPE_LITE) || defined(MEDIAPIPE_MOBILE))
builder << "\nFor status_handler:\n";
builder << node.DebugString();
#endif // !(MEDIAPIPE_LITE || MEDIAPIPE_MOBILE)
return std::move(builder);
}
input_side_packets_ = absl::make_unique<PacketTypeSet>(
std::move(input_side_packet_statusor).ValueOrDie());
return ::mediapipe::OkStatus();
}
} // namespace mediapipe
+149
View File
@@ -0,0 +1,149 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_FRAMEWORK_CALCULATOR_CONTRACT_H_
#define MEDIAPIPE_FRAMEWORK_CALCULATOR_CONTRACT_H_
#include <map>
#include <memory>
#include <string>
#include <typeindex>
// TODO: Move protos in another CL after the C++ code migration.
#include "mediapipe/framework/calculator.pb.h"
#include "mediapipe/framework/graph_service.h"
#include "mediapipe/framework/mediapipe_options.pb.h"
#include "mediapipe/framework/packet_generator.pb.h"
#include "mediapipe/framework/packet_type.h"
#include "mediapipe/framework/port.h"
#include "mediapipe/framework/port/any_proto.h"
#include "mediapipe/framework/status_handler.pb.h"
#include "mediapipe/framework/tool/options_util.h"
namespace mediapipe {
// CalculatorContract contains the expectations and properties of a Node
// object, such as the expected packet types of input and output streams and
// input and output side packets.
//
// Setters and getters are available for specifying an InputStreamHandler and
// it's options from inside a calculator's GetContract() method. Ex:
// cc->SetInputStreamHandler("FixedSizeInputStreamHandler");
// MediaPipeOptions options;
// options.MutableExtension(FixedSizeInputStreamHandlerOptions::ext)
// ->set_fixed_min_size(2);
// cc->SetInputStreamHandlerOptions(options);
//
class CalculatorContract {
public:
::mediapipe::Status Initialize(const CalculatorGraphConfig::Node& node);
::mediapipe::Status Initialize(const PacketGeneratorConfig& node);
::mediapipe::Status Initialize(const StatusHandlerConfig& node);
// Returns the options given to this node.
const CalculatorOptions& Options() const { return node_config_->options(); }
// Returns the options given to this calculator. Template argument T must
// be the type of the protobuf extension message or the protobuf::Any
// message containing the options.
template <class T>
const T& Options() const {
return options_.Get<T>();
}
// Returns the PacketTypeSet for the input streams.
PacketTypeSet& Inputs() { return *inputs_; }
const PacketTypeSet& Inputs() const { return *inputs_; }
// Returns the PacketTypeSet for the output streams.
PacketTypeSet& Outputs() { return *outputs_; }
const PacketTypeSet& Outputs() const { return *outputs_; }
// Returns the PacketTypeSet for the input side packets.
PacketTypeSet& InputSidePackets() { return *input_side_packets_; }
const PacketTypeSet& InputSidePackets() const { return *input_side_packets_; }
// Returns the PacketTypeSet for the output side packets.
PacketTypeSet& OutputSidePackets() { return *output_side_packets_; }
const PacketTypeSet& OutputSidePackets() const {
return *output_side_packets_;
}
// Set this Node's default InputStreamHandler.
// If there is an InputStreamHandler specified in the graph (.pbtxt) for this
// Node, then the graph's InputStreamHandler will take priority.
void SetInputStreamHandler(const std::string& name) {
input_stream_handler_ = name;
}
void SetInputStreamHandlerOptions(const MediaPipeOptions& options) {
input_stream_handler_options_ = options;
}
// Returns the name of this Nodes's InputStreamHandler, or empty std::string
// if none is set.
std::string GetInputStreamHandler() const { return input_stream_handler_; }
// Returns the MediaPipeOptions of this Node's InputStreamHandler, or empty
// options if none is set.
MediaPipeOptions GetInputStreamHandlerOptions() const {
return input_stream_handler_options_;
}
class GraphServiceRequest {
public:
// APIs that should be used by calculators.
GraphServiceRequest& Optional() {
optional_ = true;
return *this;
}
// Internal use.
GraphServiceRequest(const GraphServiceBase& service) : service_(service) {}
const GraphServiceBase& Service() const { return service_; }
bool IsOptional() const { return optional_; }
private:
GraphServiceBase service_;
bool optional_ = false;
};
GraphServiceRequest& UseService(const GraphServiceBase& service) {
auto it = service_requests_.emplace(service.key, service).first;
return it->second;
}
const std::map<std::string, GraphServiceRequest>& ServiceRequests() const {
return service_requests_;
}
private:
template <class T>
void GetNodeOptions(T* result) const;
const CalculatorGraphConfig::Node* node_config_ = nullptr;
tool::OptionsMap options_;
std::unique_ptr<PacketTypeSet> inputs_;
std::unique_ptr<PacketTypeSet> outputs_;
std::unique_ptr<PacketTypeSet> input_side_packets_;
std::unique_ptr<PacketTypeSet> output_side_packets_;
std::string input_stream_handler_;
MediaPipeOptions input_stream_handler_options_;
std::map<std::string, GraphServiceRequest> service_requests_;
};
} // namespace mediapipe
#endif // MEDIAPIPE_FRAMEWORK_CALCULATOR_CONTRACT_H_
@@ -0,0 +1,101 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/calculator_contract.h"
// TODO: Move protos in another CL after the C++ code migration.
#include "mediapipe/framework/calculator.pb.h"
#include "mediapipe/framework/calculator_contract_test.pb.h"
#include "mediapipe/framework/packet_generator.pb.h"
#include "mediapipe/framework/port/gmock.h"
#include "mediapipe/framework/port/gtest.h"
#include "mediapipe/framework/port/parse_text_proto.h"
#include "mediapipe/framework/port/status_matchers.h"
#include "mediapipe/framework/status_handler.pb.h"
namespace mediapipe {
namespace {
TEST(CalculatorContractTest, Calculator) {
const CalculatorGraphConfig::Node node =
::mediapipe::ParseTextProtoOrDie<CalculatorGraphConfig::Node>(R"(
calculator: "MixtureOfExpertsFusionCalculator"
input_stream: "FRAME:fdense_pca_moe_aggregated_detection"
input_stream: "FNET:fnet_logreg_aggregated_detection"
input_stream: "EGRAPH:egraph_segment_aggregated_detection"
input_stream: "VIDEO:fdense_averaged_pca_moe_v2_detection"
input_side_packet: "FUSION_MODEL:egraph_topical_packet_factory"
output_stream: "egraph_topical_detection"
)");
CalculatorContract contract;
MEDIAPIPE_EXPECT_OK(contract.Initialize(node));
EXPECT_EQ(contract.Inputs().NumEntries(), 4);
EXPECT_EQ(contract.Outputs().NumEntries(), 1);
EXPECT_EQ(contract.InputSidePackets().NumEntries(), 1);
EXPECT_EQ(contract.OutputSidePackets().NumEntries(), 0);
}
TEST(CalculatorContractTest, CalculatorOptions) {
const CalculatorGraphConfig::Node node =
::mediapipe::ParseTextProtoOrDie<CalculatorGraphConfig::Node>(R"(
calculator: "CalculatorTestCalculator"
input_stream: "DATA:ycbcr_frames"
input_stream: "VIDEO_HEADER:ycbcr_frames_prestream"
output_stream: "DATA:ycbcr_downsampled"
output_stream: "VIDEO_HEADER:ycbcr_downsampled_prestream"
options {
[mediapipe.CalculatorContractTestOptions.ext] { test_field: 1.0 }
})");
CalculatorContract contract;
MEDIAPIPE_EXPECT_OK(contract.Initialize(node));
const auto& test_options =
contract.Options().GetExtension(CalculatorContractTestOptions::ext);
EXPECT_EQ(test_options.test_field(), 1.0);
EXPECT_EQ(contract.Inputs().NumEntries(), 2);
EXPECT_EQ(contract.Outputs().NumEntries(), 2);
EXPECT_EQ(contract.InputSidePackets().NumEntries(), 0);
EXPECT_EQ(contract.OutputSidePackets().NumEntries(), 0);
}
TEST(CalculatorContractTest, PacketGenerator) {
const PacketGeneratorConfig node =
::mediapipe::ParseTextProtoOrDie<PacketGeneratorConfig>(R"(
packet_generator: "DaredevilLabeledTimeSeriesGenerator"
input_side_packet: "labeled_time_series"
output_side_packet: "time_series_header"
output_side_packet: "input_matrix"
output_side_packet: "label_set"
output_side_packet: "content_fingerprint"
)");
CalculatorContract contract;
MEDIAPIPE_EXPECT_OK(contract.Initialize(node));
EXPECT_EQ(contract.InputSidePackets().NumEntries(), 1);
EXPECT_EQ(contract.OutputSidePackets().NumEntries(), 4);
}
TEST(CalculatorContractTest, StatusHandler) {
const StatusHandlerConfig node =
::mediapipe::ParseTextProtoOrDie<StatusHandlerConfig>(R"(
status_handler: "TaskInjectorStatusHandler"
input_side_packet: "ROW:cid"
input_side_packet: "SPEC:task_specification"
)");
CalculatorContract contract;
MEDIAPIPE_EXPECT_OK(contract.Initialize(node));
EXPECT_EQ(contract.InputSidePackets().NumEntries(), 2);
}
} // namespace
} // namespace mediapipe
@@ -0,0 +1,30 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Forked from mediapipe/framework/mediapipe_options.proto.
// The forked proto must remain identical to the original proto and should be
// ONLY used by mediapipe open source project.
syntax = "proto2";
package mediapipe;
import "mediapipe/framework/calculator.proto";
message CalculatorContractTestOptions {
extend CalculatorOptions {
optional CalculatorContractTestOptions ext = 188754615;
}
optional double test_field = 1 [default = -1.0];
}
@@ -0,0 +1,76 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// This header is used to include the core portions of the calculator
// framework. The comments that follow describe the main classes within
// the framework and how they interact.
//
// Calculator: A class which clients subclass to do actual work.
// It receives input and produces output which may go to many other
// Calculators connected in a directed acyclic graph.
//
// CalculatorGraph: A class which sets up a CalculatorGraphConfig and
// runs it. This is the controller class which governs the top level
// behavior of the framework and how things are run.
//
// CalculatorNode: A class which keeps track of a single Calculator and
// framework level details that the client does not need to worry about
// (such as how to advertise that the Calculator is blocked or unblocked).
//
// InputStream: A class which holds the next value in an input stream
// for a Calculator to use and provides access to the stream header.
// It is the superclass of InputStreamImpl which holds implementation
// details for the framework.
//
// InputStreamImpl: All information for the input stream.
// A CalculatorNode and OutputStreamImpl has access to this information,
// but the Calculator does not.
//
// OutputStream: A class which gets the output packets from a Calculator
// and relays them to the next calculators or the framework.
//
// OutputStreamImpl: The framework level information for an OutputStream.
// A CalculatorNode has access to this information but the Calculator
// does not.
//
// CalculatorState: Data class to hold information the Calculator needs
// access to. This data persists across multiple runs of the graph,
// whereas the Calculators will be destroyed and recreated.
#ifndef MEDIAPIPE_FRAMEWORK_CALCULATOR_FRAMEWORK_H_
#define MEDIAPIPE_FRAMEWORK_CALCULATOR_FRAMEWORK_H_
#include "mediapipe/framework/calculator_base.h"
#include "mediapipe/framework/calculator_graph.h"
#include "mediapipe/framework/calculator_registry.h"
#include "mediapipe/framework/counter_factory.h"
#include "mediapipe/framework/input_stream.h"
#include "mediapipe/framework/output_side_packet.h"
#include "mediapipe/framework/output_stream.h"
#include "mediapipe/framework/packet.h"
#include "mediapipe/framework/packet_generator.h"
#include "mediapipe/framework/packet_generator_graph.h"
#include "mediapipe/framework/packet_set.h"
#include "mediapipe/framework/packet_type.h"
#include "mediapipe/framework/port.h"
#include "mediapipe/framework/status_handler.h"
#include "mediapipe/framework/subgraph.h"
#include "mediapipe/framework/timestamp.h"
#include "mediapipe/framework/tool/sink.h"
#include "mediapipe/framework/tool/status_util.h"
#include "mediapipe/framework/tool/validate.h"
#include "mediapipe/framework/tool/validate_name.h"
#include "mediapipe/framework/validated_graph_config.h"
#endif // MEDIAPIPE_FRAMEWORK_CALCULATOR_FRAMEWORK_H_
File diff suppressed because it is too large Load Diff
+647
View File
@@ -0,0 +1,647 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Declares CalculatorGraph, which links Calculators into a directed acyclic
// graph, and allows its evaluation.
#ifndef MEDIAPIPE_FRAMEWORK_CALCULATOR_GRAPH_H_
#define MEDIAPIPE_FRAMEWORK_CALCULATOR_GRAPH_H_
#include <atomic>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include "absl/base/macros.h"
#include "absl/container/fixed_array.h"
#include "absl/synchronization/mutex.h"
#include "mediapipe/framework/calculator.pb.h"
#include "mediapipe/framework/calculator_base.h"
#include "mediapipe/framework/calculator_node.h"
#include "mediapipe/framework/counter_factory.h"
#include "mediapipe/framework/executor.h"
#include "mediapipe/framework/graph_output_stream.h"
#include "mediapipe/framework/graph_service.h"
#include "mediapipe/framework/mediapipe_profiling.h"
#include "mediapipe/framework/output_side_packet_impl.h"
#include "mediapipe/framework/output_stream.h"
#include "mediapipe/framework/output_stream_manager.h"
#include "mediapipe/framework/output_stream_poller.h"
#include "mediapipe/framework/output_stream_shard.h"
#include "mediapipe/framework/packet.h"
#include "mediapipe/framework/packet_generator.pb.h"
#include "mediapipe/framework/packet_generator_graph.h"
#include "mediapipe/framework/port.h"
#include "mediapipe/framework/port/integral_types.h"
#include "mediapipe/framework/port/status.h"
#include "mediapipe/framework/scheduler.h"
#include "mediapipe/framework/thread_pool_executor.pb.h"
#ifndef MEDIAPIPE_DISABLE_GPU
namespace mediapipe {
class GpuResources;
class GpuSharedData;
} // namespace mediapipe
#endif // !defined(MEDIAPIPE_DISABLE_GPU)
namespace mediapipe {
typedef ::mediapipe::StatusOr<OutputStreamPoller> StatusOrPoller;
// The class representing a DAG of calculator nodes.
//
// CalculatorGraph is the primary API for the MediaPipe Framework.
// In general, CalculatorGraph should be used if the only thing you need
// to do is run the graph (without pushing data in or extracting it as
// the graph runs).
//
// Example:
// // Build dependency "//mediapipe/framework:calculator_framework".
//
// #include "mediapipe/framework/calculator_framework.h"
//
// mediapipe::CalculatorGraphConfig config;
// RETURN_IF_ERROR(mediapipe::tool::ParseGraphFromString(THE_CONFIG,
// &config)); mediapipe::CalculatorGraph graph;
// RETURN_IF_ERROR(graph.Initialize(config));
//
// std::map<std::string, mediapipe::Packet> extra_side_packets;
// extra_side_packets["video_id"] = mediapipe::MakePacket<std::string>(
// "3edb9503834e9b42");
// RETURN_IF_ERROR(graph.Run(extra_side_packets));
//
// // Run again (demonstrating the more concise initializer list syntax).
// RETURN_IF_ERROR(graph.Run(
// {{"video_id", mediapipe::MakePacket<std::string>("Ex-uGhDzue4")}}));
// // See mediapipe/framework/graph_runner.h for an interface
// // to insert and extract packets from a graph as it runs.
class CalculatorGraph {
public:
// Defines possible modes for adding a packet to a graph input stream.
// WAIT_TILL_NOT_FULL can be used to control the memory usage of a graph by
// avoiding adding a new packet until all dependent input streams fall below
// the maximum queue size specified in the graph configuration.
// ADD_IF_NOT_FULL could also be used to control the latency if used in a
// real-time graph (e.g. drop camera frames if the MediaPipe graph queues are
// full).
enum class GraphInputStreamAddMode {
// Blocks and waits until none of the affected streams
// are full. Note that if max_queue_size is set to -1, the packet will be
// added regardless of queue size.
WAIT_TILL_NOT_FULL,
// Returns and does not add packet if any affected input
// stream is full.
ADD_IF_NOT_FULL
};
// Creates an uninitialized graph.
CalculatorGraph();
CalculatorGraph(const CalculatorGraph&) = delete;
CalculatorGraph& operator=(const CalculatorGraph&) = delete;
// Initializes the graph from its proto description (using Initialize())
// and crashes if something goes wrong.
explicit CalculatorGraph(const CalculatorGraphConfig& config);
virtual ~CalculatorGraph();
// Initializes the graph from a its proto description.
// side_packets that are provided at this stage are common across all Run()
// invocations and could be used to execute PacketGenerators immediately.
::mediapipe::Status Initialize(
const CalculatorGraphConfig& config,
const std::map<std::string, Packet>& side_packets);
// Convenience version which does not take side packets.
::mediapipe::Status Initialize(const CalculatorGraphConfig& config);
// Initializes the CalculatorGraph from the specified graph and subgraph
// configs. Template graph and subgraph configs can be specified through
// |input_templates|. Every subgraph must have its graph type specified in
// CalclatorGraphConfig.type. A subgraph can be instantiated directly by
// specifying its type in |graph_type|. A template graph can be instantiated
// directly by specifying its template arguments in |arguments|.
::mediapipe::Status Initialize(
const std::vector<CalculatorGraphConfig>& configs,
const std::vector<CalculatorGraphTemplate>& templates,
const std::map<std::string, Packet>& side_packets = {},
const std::string& graph_type = "",
const Subgraph::SubgraphOptions* options = nullptr);
// Resturns the canonicalized CalculatorGraphConfig for this graph.
const CalculatorGraphConfig& Config() const {
return validated_graph_->Config();
}
// Observes the named output stream. packet_callback will be invoked on every
// packet emitted by the output stream. Can only be called before Run() or
// StartRun().
// TODO: Rename to AddOutputStreamCallback.
::mediapipe::Status ObserveOutputStream(
const std::string& stream_name,
std::function<::mediapipe::Status(const Packet&)> packet_callback);
// Adds an OutputStreamPoller for a stream. This provides a synchronous,
// polling API for accessing a stream's output. For asynchronous output, use
// ObserveOutputStream. See also the helpers in tool/sink.h.
StatusOrPoller AddOutputStreamPoller(const std::string& stream_name);
// Gets output side packet by name after the graph is done. However, base
// packets (generated by PacketGenerators) can be retrieved before
// graph is done. Returns error if the graph is still running (for non-base
// packets) or the output side packet is not found or empty.
::mediapipe::StatusOr<Packet> GetOutputSidePacket(
const std::string& packet_name);
// Runs the graph after adding the given extra input side packets. All
// arguments are forgotten after Run() returns.
// Run() is a blocking call and will return when all calculators are done.
virtual ::mediapipe::Status Run(
const std::map<std::string, Packet>& extra_side_packets);
// Run the graph without adding any input side packets.
::mediapipe::Status Run() { return Run({}); }
// Start a run of the graph. StartRun, WaitUntilDone, HasError,
// AddPacketToInputStream, and CloseInputStream allow more control over
// the execution of the graph run. You can insert packets directly into
// a stream while the graph is running. Once StartRun has been called,
// the graph will continue to run until WaitUntilDone() is called.
// If StartRun returns an error, then the graph is not started and a
// subsequent call to StartRun can be attempted.
//
// Example:
// RETURN_IF_ERROR(graph.StartRun(...));
// while (true) {
// if (graph.HasError() || want_to_stop) break;
// RETURN_IF_ERROR(graph.AddPacketToInputStream(...));
// }
// for (const std::string& stream : streams) {
// RETURN_IF_ERROR(graph.CloseInputStream(stream));
// }
// RETURN_IF_ERROR(graph.WaitUntilDone());
::mediapipe::Status StartRun(
const std::map<std::string, Packet>& extra_side_packets) {
return StartRun(extra_side_packets, {});
}
// In addition to the above StartRun, add additional parameter to set the
// stream header before running.
// Note: We highly discourage the use of stream headers, this is added for the
// compatibility of existing calculators that use headers during Open().
::mediapipe::Status StartRun(
const std::map<std::string, Packet>& extra_side_packets,
const std::map<std::string, Packet>& stream_headers);
// Wait for the current run to finish (block the current thread
// until all source calculators have returned StatusStop(), all
// graph_input_streams_ have been closed, and no more calculators can
// be run). This function can be called only after StartRun().
::mediapipe::Status WaitUntilDone();
// Wait until the running graph is in the idle mode, which is when nothing can
// be scheduled and nothing is running in the worker threads. This function
// can be called only after StartRun().
// NOTE: The graph must not have any source nodes because source nodes prevent
// the running graph from becoming idle until the source nodes are done.
::mediapipe::Status WaitUntilIdle();
// Wait until a packet is emitted on one of the observed output streams.
// Returns immediately if a packet has already been emitted since the last
// call to this function.
// Returns OutOfRangeError if the graph terminated while waiting.
::mediapipe::Status WaitForObservedOutput();
// Quick non-locking means of checking if the graph has encountered an error.
bool HasError() const { return has_error_; }
// Add a Packet to a graph input stream based on the graph input stream add
// mode. If the mode is ADD_IF_NOT_FULL, the packet will not be added if any
// queue exceeds max_queue_size specified by the graph config and will return
// StatusUnavailable. The WAIT_TILL_NOT_FULL mode (default) will block until
// the queues fall below the max_queue_size before adding the packet. If the
// mode is max_queue_size is -1, then the packet is added regardless of the
// sizes of the queues in the graph. The input stream must have been specified
// in the configuration as a graph level input_stream. On error, nothing is
// added.
::mediapipe::Status AddPacketToInputStream(const std::string& stream_name,
const Packet& packet);
// Same as the l-value version of this function by the same name, but moves
// the r-value referenced packet into the stream instead of copying it over.
// This allows the graph to take exclusive ownership of the packet, which may
// allow more memory optimizations. Note that, if an error is returned, the
// packet may remain valid. In particular, when using the ADD_IF_NOT_FULL
// mode with a full queue, this will return StatusUnavailable and the caller
// may try adding the packet again later.
::mediapipe::Status AddPacketToInputStream(const std::string& stream_name,
Packet&& packet);
// Sets the queue size of a graph input stream, overriding the graph default.
::mediapipe::Status SetInputStreamMaxQueueSize(const std::string& stream_name,
int max_queue_size);
// Check if an input stream exists in the graph
bool HasInputStream(const std::string& name);
// Close a graph input stream. If the graph has any graph input streams
// then Run() will not return until all the graph input streams have
// been closed (and all packets propagate through the graph).
// Note that multiple threads cannot call CloseInputStream() on the same
// stream_name at the same time.
::mediapipe::Status CloseInputStream(const std::string& stream_name);
// Closes all the graph input streams.
// TODO: deprecate this function in favor of CloseAllPacketSources.
::mediapipe::Status CloseAllInputStreams();
// Closes all the graph input streams and source calculator nodes.
::mediapipe::Status CloseAllPacketSources();
// Returns the pointer to the stream with the given name, or dies if none
// exists. The result remains owned by the CalculatorGraph.
ABSL_DEPRECATED(
"Prefer using a Calculator to get information of all sorts out of the "
"graph.")
const OutputStreamManager* FindOutputStreamManager(const std::string& name);
// Returns the ProfilingContext assocoaited with the CalculatorGraph.
ProfilingContext* profiler() { return profiler_.get(); }
// Collects the runtime profile for Open(), Process(), and Close() of each
// calculator in the graph. May be called at any time after the graph has been
// initialized.
ABSL_DEPRECATED("Use profiler()->GetCalculatorProfiles() instead")
::mediapipe::Status GetCalculatorProfiles(
std::vector<CalculatorProfile>*) const;
// Set the type of counter used in this graph.
void SetCounterFactory(CounterFactory* factory) {
counter_factory_.reset(factory);
}
CounterFactory* GetCounterFactory() { return counter_factory_.get(); }
// Callback when an error is encountered.
// Adds the error to the vector of errors.
void RecordError(const ::mediapipe::Status& error)
LOCKS_EXCLUDED(error_mutex_);
// Returns the maximum input stream queue size.
int GetMaxInputStreamQueueSize();
// Get the mode for adding packets to an input stream.
GraphInputStreamAddMode GetGraphInputStreamAddMode() const;
// Set the mode for adding packets to an input stream.
void SetGraphInputStreamAddMode(GraphInputStreamAddMode mode);
// Aborts the scheduler if the graph is not terminated; no-op otherwise.
void Cancel();
// Pauses the scheduler. Only used by calculator graph testing.
ABSL_DEPRECATED(
"CalculatorGraph will not allow external callers to explictly pause and "
"resume a graph.")
void Pause();
// Resumes the scheduler. Only used by calculator graph testing.
ABSL_DEPRECATED(
"CalculatorGraph will not allow external callers to explictly pause and "
"resume a graph.")
void Resume();
// Sets the executor that will run the nodes assigned to the executor
// named |name|. If |name| is empty, this sets the default executor. Must
// be called before the graph is initialized.
::mediapipe::Status SetExecutor(const std::string& name,
std::shared_ptr<Executor> executor);
// WARNING: the following public methods are exposed to Scheduler only.
// Return true if all the graph input streams have been closed.
bool GraphInputStreamsClosed() {
return num_closed_graph_input_streams_ == graph_input_streams_.size();
}
// Returns true if this node or graph input stream is connected to
// any input stream whose queue has hit maximum capacity.
bool IsNodeThrottled(int node_id) LOCKS_EXCLUDED(full_input_streams_mutex_);
// If any active source node or graph input stream is throttled and not yet
// closed, increases the max_queue_size for each full input stream in the
// graph.
// Returns true if at least one max_queue_size has been grown.
bool UnthrottleSources() LOCKS_EXCLUDED(full_input_streams_mutex_);
// Returns the scheduler's runtime measures for overhead measurement.
// Only meant for test purposes.
internal::SchedulerTimes GetSchedulerTimes() {
return scheduler_.GetSchedulerTimes();
}
#ifndef MEDIAPIPE_DISABLE_GPU
// Returns a pointer to the GpuResources in use, if any.
// Only meant for internal use.
std::shared_ptr<::mediapipe::GpuResources> GetGpuResources() const;
::mediapipe::Status SetGpuResources(
std::shared_ptr<::mediapipe::GpuResources> resources);
// Helper for PrepareForRun. If it returns a non-empty map, those packets
// must be added to the existing side packets, replacing existing values
// that have the same key.
::mediapipe::StatusOr<std::map<std::string, Packet>> PrepareGpu(
const std::map<std::string, Packet>& side_packets);
#endif // !defined(MEDIAPIPE_DISABLE_GPU)
template <typename T>
::mediapipe::Status SetServiceObject(const GraphService<T>& service,
std::shared_ptr<T> object) {
return SetServicePacket(service,
MakePacket<std::shared_ptr<T>>(std::move(object)));
}
template <typename T>
std::shared_ptr<T> GetServiceObject(const GraphService<T>& service) {
Packet p = GetServicePacket(service);
if (p.IsEmpty()) return nullptr;
return p.Get<std::shared_ptr<T>>();
}
// Only the Java API should call this directly.
::mediapipe::Status SetServicePacket(const GraphServiceBase& service,
Packet p);
private:
// GraphRunState is used as a parameter in the function CallStatusHandlers.
enum class GraphRunState {
// State of the graph before the run; see status_handler.h for details.
PRE_RUN,
// State of the graph after after the run; set by CleanUpAfterRun.
POST_RUN,
};
// The graph input streams (which have packets added to them from
// outside the graph). Since these will be connected directly to a
// node's input streams they are implemented as "output" streams.
// Based on the assumption that all the graph input packets must be added to a
// graph input stream sequentially, a GraphInputStream object only contains
// one reusable output stream shard.
class GraphInputStream {
public:
explicit GraphInputStream(OutputStreamManager* manager)
: manager_(manager) {
shard_.SetSpec(manager_->Spec());
}
void PrepareForRun(
std::function<void(::mediapipe::Status)> error_callback) {
manager_->PrepareForRun(std::move(error_callback));
}
void SetMaxQueueSize(int max_queue_size) {
manager_->SetMaxQueueSize(max_queue_size);
}
void SetHeader(const Packet& header);
void AddPacket(const Packet& packet) { shard_.AddPacket(packet); }
void AddPacket(Packet&& packet) { shard_.AddPacket(std::move(packet)); }
void PropagateUpdatesToMirrors();
void Close();
bool IsClosed() const { return manager_->IsClosed(); }
OutputStreamManager* GetManager() { return manager_; }
private:
OutputStreamManager* manager_ = nullptr;
OutputStreamShard shard_;
};
// Initializes the graph from a ValidatedGraphConfig object.
::mediapipe::Status Initialize(
std::unique_ptr<ValidatedGraphConfig> validated_graph,
const std::map<std::string, Packet>& side_packets);
// AddPacketToInputStreamInternal template is called by either
// AddPacketToInputStream(Packet&& packet) or
// AddPacketToInputStream(const Packet& packet).
template <typename T>
::mediapipe::Status AddPacketToInputStreamInternal(
const std::string& stream_name, T&& packet);
// Sets the executor that will run the nodes assigned to the executor
// named |name|. If |name| is empty, this sets the default executor.
// Does not check that the graph is uninitialized and |name| is not a
// reserved executor name.
::mediapipe::Status SetExecutorInternal(const std::string& name,
std::shared_ptr<Executor> executor);
// If the num_threads field in default_executor_options is not specified,
// assigns a reasonable value based on system configuration and the graph.
// Then, creates the default thread pool if appropriate.
//
// Only called by InitializeExecutors().
::mediapipe::Status InitializeDefaultExecutor(
const ThreadPoolExecutorOptions& default_executor_options,
bool use_application_thread);
// Creates a thread pool as the default executor. The num_threads argument
// overrides the num_threads field in default_executor_options.
::mediapipe::Status CreateDefaultThreadPool(
const ThreadPoolExecutorOptions& default_executor_options,
int num_threads);
// Returns true if |name| is a reserved executor name.
static bool IsReservedExecutorName(const std::string& name);
// Helper functions for Initialize().
::mediapipe::Status InitializeExecutors();
::mediapipe::Status InitializePacketGeneratorGraph(
const std::map<std::string, Packet>& side_packets);
::mediapipe::Status InitializeStreams();
::mediapipe::Status InitializeProfiler();
::mediapipe::Status InitializeCalculatorNodes();
// Iterates through all nodes and schedules any that can be opened.
void ScheduleAllOpenableNodes();
// Does the bulk of the work for StartRun but does not start the scheduler.
::mediapipe::Status PrepareForRun(
const std::map<std::string, Packet>& extra_side_packets,
const std::map<std::string, Packet>& stream_headers);
// Cleans up any remaining state after the run and returns any errors that may
// have occurred during the run. Called after the scheduler has terminated.
::mediapipe::Status FinishRun();
// Cleans up any remaining state after the run. All status handlers run here
// if their requested input side packets exist.
// The original |*status| is passed to all the status handlers. If any status
// handler fails, it appends its error to errors_, and CleanupAfterRun sets
// |*status| to the new combined errors on return.
void CleanupAfterRun(::mediapipe::Status* status)
LOCKS_EXCLUDED(error_mutex_);
// Combines errors into a status. Returns true if the vector of errors is
// non-empty.
bool GetCombinedErrors(const std::string& error_prefix,
::mediapipe::Status* error_status);
// Convenience overload which specifies a default error prefix.
bool GetCombinedErrors(::mediapipe::Status* error_status);
// Calls HandlePreRunStatus or HandleStatus on the StatusHandlers. Which one
// is called depends on the GraphRunState parameter (PRE_RUN or POST_RUN).
// current_run_side_packets_ must be set before this function is called.
// On error, has_error_ will be set.
void CallStatusHandlers(GraphRunState graph_run_state,
const ::mediapipe::Status& status);
// Callback function to throttle or unthrottle source nodes when a stream
// becomes full or non-full. A node is throttled (i.e. prevented being
// scheduled) if it has caused a downstream input queue to become full. Note
// that all sources (including graph input streams) that affect this stream
// will be throttled. A node is unthrottled (i.e. added to the scheduler
// queue) if all downstream input queues have become non-full.
//
// This method is invoked from an input stream when its queue becomes full or
// non-full. However, since streams are not allowed to hold any locks while
// invoking a callback, this method must re-lock the stream and query its
// status before taking any action.
void UpdateThrottledNodes(InputStreamManager* stream, bool* stream_was_full);
Packet GetServicePacket(const GraphServiceBase& service);
#ifndef MEDIAPIPE_DISABLE_GPU
// Owns the legacy GpuSharedData if we need to create one for backwards
// compatibility.
std::unique_ptr<::mediapipe::GpuSharedData> legacy_gpu_shared_;
#endif // !defined(MEDIAPIPE_DISABLE_GPU)
// True if the graph was initialized.
bool initialized_ = false;
// A packet type that has SetAny() called on it.
PacketType any_packet_type_;
// The ValidatedGraphConfig object defining this CalculatorGraph.
std::unique_ptr<ValidatedGraphConfig> validated_graph_;
// The PacketGeneratorGraph to use to generate all the input side packets.
PacketGeneratorGraph packet_generator_graph_;
// True if the graph has source nodes.
bool has_sources_ = false;
// A flat array of InputStreamManager/OutputStreamManager/
// OutputSidePacketImpl/CalculatorNode corresponding to the input/output
// stream indexes, output side packet indexes, and calculator indexes
// respectively in validated_graph_.
// Once allocated these structures must not be reallocated since
// internal structures may point to individual entries in the array.
std::unique_ptr<InputStreamManager[]> input_stream_managers_;
std::unique_ptr<OutputStreamManager[]> output_stream_managers_;
std::unique_ptr<OutputSidePacketImpl[]> output_side_packets_;
std::unique_ptr<absl::FixedArray<CalculatorNode>> nodes_;
// The graph output streams.
std::vector<std::shared_ptr<internal::GraphOutputStream>>
graph_output_streams_;
// Maximum queue size for an input stream. This is used by the scheduler to
// restrict memory usage.
int max_queue_size_ = -1;
// Mode for adding packets to a graph input stream. Set to block until all
// affected input streams are not full by default.
GraphInputStreamAddMode graph_input_stream_add_mode_
GUARDED_BY(full_input_streams_mutex_);
// For a source node or graph input stream (specified using id),
// this stores the set of dependent input streams that have hit their
// maximum capacity. Graph input streams are also treated as nodes.
// A node is scheduled only if this set is empty. Similarly, a packet
// is added to a graph input stream only if this set is empty.
// Note that this vector contains an unused entry for each non-source node.
std::vector<std::unordered_set<InputStreamManager*>> full_input_streams_
GUARDED_BY(full_input_streams_mutex_);
// Maps stream names to graph input stream objects.
std::unordered_map<std::string, std::unique_ptr<GraphInputStream>>
graph_input_streams_;
// Maps graph input streams to their virtual node ids.
std::unordered_map<std::string, int> graph_input_stream_node_ids_;
// Maps graph input streams to their max queue size.
std::unordered_map<std::string, int> graph_input_stream_max_queue_size_;
// The factory for making counters associated with this graph.
std::unique_ptr<CounterFactory> counter_factory_;
// Executors for the scheduler, keyed by the executor's name. The default
// executor's name is the empty std::string.
std::map<std::string, std::shared_ptr<Executor>> executors_;
// The processed input side packet map for this run.
std::map<std::string, Packet> current_run_side_packets_;
std::map<std::string, Packet> service_packets_;
// Vector of errors encountered while running graph. Always use RecordError()
// to add an error to this vector.
std::vector<::mediapipe::Status> errors_ GUARDED_BY(error_mutex_);
// True if the default executor uses the application thread.
bool use_application_thread_ = false;
// Condition variable that waits until all input streams that depend on a
// graph input stream are below the maximum queue size.
absl::CondVar wait_to_add_packet_cond_var_
GUARDED_BY(full_input_streams_mutex_);
// Mutex for the vector of errors.
absl::Mutex error_mutex_;
// Status variable to indicate if the graph has encountered an error.
std::atomic<bool> has_error_;
// Mutex for full_input_streams_.
mutable absl::Mutex full_input_streams_mutex_;
// Number of closed graph input streams. This is a separate variable because
// it is not safe to hold a lock on the scheduler while calling Close() on an
// input stream. Hence, we decouple the closing of the stream and checking its
// status.
// TODO: update this comment.
std::atomic<unsigned int> num_closed_graph_input_streams_;
// The graph tracing and profiling interface. It is owned by the
// CalculatorGraph using a shared_ptr in order to allow threadsafe access
// to the ProfilingContext from clients that may outlive the CalculatorGraph
// such as GlContext. It is declared here before the Scheduler so that it
// remains available during the Scheduler destructor.
std::shared_ptr<ProfilingContext> profiler_;
internal::Scheduler scheduler_;
};
} // namespace mediapipe
#endif // MEDIAPIPE_FRAMEWORK_CALCULATOR_GRAPH_H_
@@ -0,0 +1,546 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <map>
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/calculator_graph.h"
#include "mediapipe/framework/port/canonical_errors.h"
#include "mediapipe/framework/port/core_proto_inc.h"
#include "mediapipe/framework/port/gmock.h"
#include "mediapipe/framework/port/gtest.h"
#include "mediapipe/framework/port/integral_types.h"
#include "mediapipe/framework/port/logging.h"
#include "mediapipe/framework/port/status_matchers.h"
#include "mediapipe/framework/tool/sink.h"
#include "mediapipe/framework/tool/status_util.h"
namespace mediapipe {
namespace {
class CalculatorGraphEventLoopTest : public testing::Test {
public:
void AddThreadSafeVectorSink(const Packet& packet) {
absl::WriterMutexLock lock(&output_packets_mutex_);
output_packets_.push_back(packet);
}
protected:
std::vector<Packet> output_packets_ GUARDED_BY(output_packets_mutex_);
absl::Mutex output_packets_mutex_;
};
// Allows blocking of the Process() call by locking the blocking_mutex passed to
// the input side packet. Used to force input stream queues to build up for
// testing.
class BlockingPassThroughCalculator : public CalculatorBase {
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
cc->Inputs().Index(0).SetAny();
cc->Outputs().Index(0).SetSameAs(&cc->Inputs().Index(0));
cc->InputSidePackets().Index(0).Set<std::unique_ptr<absl::Mutex>>();
return ::mediapipe::OkStatus();
}
::mediapipe::Status Open(CalculatorContext* cc) final {
mutex_ = GetFromUniquePtr<absl::Mutex>(cc->InputSidePackets().Index(0));
return ::mediapipe::OkStatus();
}
::mediapipe::Status Process(CalculatorContext* cc) final {
mutex_->Lock();
cc->Outputs().Index(0).AddPacket(
cc->Inputs().Index(0).Value().At(cc->InputTimestamp()));
mutex_->Unlock();
return ::mediapipe::OkStatus();
}
private:
absl::Mutex* mutex_;
};
REGISTER_CALCULATOR(BlockingPassThroughCalculator);
struct SimpleHeader {
int width;
int height;
};
class UsingHeaderCalculator : public CalculatorBase {
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
cc->Inputs().Index(0).SetAny();
cc->Outputs().Index(0).SetSameAs(&cc->Inputs().Index(0));
return ::mediapipe::OkStatus();
}
::mediapipe::Status Open(CalculatorContext* cc) final {
if (cc->Inputs().Index(0).Header().IsEmpty()) {
return ::mediapipe::UnknownError("No stream header present.");
}
const SimpleHeader& header =
cc->Inputs().Index(0).Header().Get<SimpleHeader>();
std::unique_ptr<SimpleHeader> output_header(new SimpleHeader);
output_header->width = header.width;
output_header->height = header.height;
cc->Outputs().Index(0).SetHeader(Adopt(output_header.release()));
return ::mediapipe::OkStatus();
}
::mediapipe::Status Process(CalculatorContext* cc) final {
cc->Outputs().Index(0).AddPacket(
cc->Inputs().Index(0).Value().At(cc->InputTimestamp()));
return ::mediapipe::OkStatus();
}
};
REGISTER_CALCULATOR(UsingHeaderCalculator);
TEST_F(CalculatorGraphEventLoopTest, WellProvisionedEventLoop) {
CalculatorGraphConfig graph_config;
ASSERT_TRUE(proto_ns::TextFormat::ParseFromString(
R"(
node {
calculator: "PassThroughCalculator"
input_stream: "input_numbers"
output_stream: "output_numbers"
}
node {
calculator: "CallbackCalculator"
input_stream: "output_numbers"
input_side_packet: "CALLBACK:callback"
}
input_stream: "input_numbers"
)",
&graph_config));
// Start MediaPipe graph.
CalculatorGraph graph(graph_config);
MEDIAPIPE_ASSERT_OK(graph.StartRun(
{{"callback", MakePacket<std::function<void(const Packet&)>>(std::bind(
&CalculatorGraphEventLoopTest::AddThreadSafeVectorSink,
this, std::placeholders::_1))}}));
// Insert 100 packets at the rate the calculator can keep up with.
for (int i = 0; i < 100; ++i) {
MEDIAPIPE_ASSERT_OK(graph.AddPacketToInputStream(
"input_numbers", Adopt(new int(i)).At(Timestamp(i))));
// Wait for all packets to be received by the sink.
while (true) {
{
absl::ReaderMutexLock lock(&output_packets_mutex_);
if (output_packets_.size() > i) {
break;
}
}
absl::SleepFor(absl::Microseconds(1));
}
}
// Check partial results.
{
absl::ReaderMutexLock lock(&output_packets_mutex_);
ASSERT_EQ(100, output_packets_.size());
for (int i = 0; i < 100; ++i) {
EXPECT_EQ(i, output_packets_[i].Get<int>());
}
}
// Insert 100 more packets at rate the graph can't keep up.
for (int i = 100; i < 200; ++i) {
MEDIAPIPE_ASSERT_OK(graph.AddPacketToInputStream(
"input_numbers", Adopt(new int(i)).At(Timestamp(i))));
}
// Don't wait but just close the input stream.
MEDIAPIPE_ASSERT_OK(graph.CloseInputStream("input_numbers"));
// Wait properly via the API until the graph is done.
MEDIAPIPE_ASSERT_OK(graph.WaitUntilDone());
// Check final results.
{
absl::ReaderMutexLock lock(&output_packets_mutex_);
ASSERT_EQ(200, output_packets_.size());
for (int i = 0; i < 200; ++i) {
EXPECT_EQ(i, output_packets_[i].Get<int>());
}
}
}
// Pass-Through calculator that fails upon receiving the 10th packet.
class FailingPassThroughCalculator : public CalculatorBase {
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
cc->Inputs().Index(0).SetAny();
cc->Outputs().Index(0).SetSameAs(&cc->Inputs().Index(0));
return ::mediapipe::OkStatus();
}
::mediapipe::Status Process(CalculatorContext* cc) final {
Timestamp timestamp = cc->InputTimestamp();
if (timestamp.Value() == 9) {
return ::mediapipe::UnknownError(
"Meant to fail (magicstringincludedhere).");
}
cc->Outputs().Index(0).AddPacket(
cc->Inputs().Index(0).Value().At(timestamp));
return ::mediapipe::OkStatus();
}
};
REGISTER_CALCULATOR(FailingPassThroughCalculator);
TEST_F(CalculatorGraphEventLoopTest, FailingEventLoop) {
CalculatorGraphConfig graph_config;
ASSERT_TRUE(proto_ns::TextFormat::ParseFromString(
R"(
node {
calculator: "FailingPassThroughCalculator"
input_stream: "input_numbers"
output_stream: "output_numbers"
}
node {
calculator: "CallbackCalculator"
input_stream: "output_numbers"
input_side_packet: "CALLBACK:callback"
}
input_stream: "input_numbers")",
&graph_config));
// Start MediaPipe graph.
CalculatorGraph graph(graph_config);
MEDIAPIPE_ASSERT_OK(graph.StartRun(
{{"callback", MakePacket<std::function<void(const Packet&)>>(std::bind(
&CalculatorGraphEventLoopTest::AddThreadSafeVectorSink,
this, std::placeholders::_1))}}));
// Insert packets.
::mediapipe::Status status;
for (int i = 0; true; ++i) {
status = graph.AddPacketToInputStream("input_numbers",
Adopt(new int(i)).At(Timestamp(i)));
if (!status.ok()) {
ASSERT_TRUE(graph.HasError()); // Graph failed.
ASSERT_THAT(
status.message(),
testing::HasSubstr("Meant to fail (magicstringincludedhere)."));
break;
}
}
MEDIAPIPE_ASSERT_OK(graph.CloseInputStream("input_numbers"));
status = graph.WaitUntilDone();
ASSERT_THAT(status.message(),
testing::HasSubstr("Meant to fail (magicstringincludedhere)."));
}
// Test the step by step mode.
TEST_F(CalculatorGraphEventLoopTest, StepByStepSchedulerLoop) {
CalculatorGraphConfig graph_config;
ASSERT_TRUE(proto_ns::TextFormat::ParseFromString(
R"(
node {
calculator: "PassThroughCalculator"
input_stream: "input_numbers"
output_stream: "output_numbers"
}
node {
calculator: "CallbackCalculator"
input_stream: "output_numbers"
input_side_packet: "CALLBACK:callback"
}
input_stream: "input_numbers"
)",
&graph_config));
// Start MediaPipe graph.
CalculatorGraph graph(graph_config);
MEDIAPIPE_ASSERT_OK(graph.StartRun(
{{"callback", MakePacket<std::function<void(const Packet&)>>(std::bind(
&CalculatorGraphEventLoopTest::AddThreadSafeVectorSink,
this, std::placeholders::_1))}}));
// Add packet one at a time, we should be able to syncrhonize the output for
// each addition in the step by step mode.
for (int i = 0; i < 100; ++i) {
MEDIAPIPE_ASSERT_OK(graph.AddPacketToInputStream(
"input_numbers", Adopt(new int(i)).At(Timestamp(i))));
MEDIAPIPE_ASSERT_OK(graph.WaitUntilIdle());
absl::ReaderMutexLock lock(&output_packets_mutex_);
ASSERT_EQ(i + 1, output_packets_.size());
}
// Don't wait but just close the input stream.
MEDIAPIPE_ASSERT_OK(graph.CloseInputStream("input_numbers"));
// Wait properly via the API until the graph is done.
MEDIAPIPE_ASSERT_OK(graph.WaitUntilDone());
}
// Test setting the stream header.
TEST_F(CalculatorGraphEventLoopTest, SetStreamHeader) {
CalculatorGraphConfig graph_config;
ASSERT_TRUE(proto_ns::TextFormat::ParseFromString(
R"(
node {
calculator: "UsingHeaderCalculator"
input_stream: "input_numbers"
output_stream: "output_numbers"
}
node {
calculator: "CallbackCalculator"
input_stream: "output_numbers"
input_side_packet: "CALLBACK:callback"
}
input_stream: "input_numbers"
)",
&graph_config));
CalculatorGraph graph(graph_config);
MEDIAPIPE_ASSERT_OK(graph.StartRun(
{{"callback", MakePacket<std::function<void(const Packet&)>>(std::bind(
&CalculatorGraphEventLoopTest::AddThreadSafeVectorSink,
this, std::placeholders::_1))}}));
::mediapipe::Status status = graph.WaitUntilIdle();
// Expect to fail if header not set.
ASSERT_FALSE(status.ok());
EXPECT_EQ(status.code(), ::mediapipe::StatusCode::kUnknown);
EXPECT_THAT(status.message(),
testing::HasSubstr("No stream header present."));
CalculatorGraph graph2(graph_config);
std::unique_ptr<SimpleHeader> header(new SimpleHeader);
header->width = 320;
header->height = 240;
// With stream header set, the StartRun should succeed.
MEDIAPIPE_ASSERT_OK(graph2.StartRun(
{{"callback", MakePacket<std::function<void(const Packet&)>>(std::bind(
&CalculatorGraphEventLoopTest::AddThreadSafeVectorSink,
this, std::placeholders::_1))}},
{{"input_numbers", Adopt(header.release())}}));
// Don't wait but just close the input stream.
MEDIAPIPE_ASSERT_OK(graph2.CloseInputStream("input_numbers"));
// Wait properly via the API until the graph is done.
MEDIAPIPE_ASSERT_OK(graph2.WaitUntilDone());
}
// Test ADD_IF_NOT_FULL mode for graph input streams (by creating more packets
// than the queue will support). At least some of these attempts should fail.
TEST_F(CalculatorGraphEventLoopTest, TryToAddPacketToInputStream) {
CalculatorGraphConfig graph_config;
ASSERT_TRUE(proto_ns::TextFormat::ParseFromString(
R"(
node {
calculator: "BlockingPassThroughCalculator"
input_stream: "input_numbers"
output_stream: "output_numbers"
input_side_packet: "blocking_mutex"
}
node {
calculator: "CallbackCalculator"
input_stream: "output_numbers"
input_side_packet: "CALLBACK:callback"
}
input_stream: "input_numbers"
num_threads: 2
max_queue_size: 1
)",
&graph_config));
absl::Mutex* mutex = new absl::Mutex();
Packet mutex_side_packet = AdoptAsUniquePtr(mutex);
CalculatorGraph graph(graph_config);
graph.SetGraphInputStreamAddMode(
CalculatorGraph::GraphInputStreamAddMode::ADD_IF_NOT_FULL);
// Start MediaPipe graph.
MEDIAPIPE_ASSERT_OK(graph.StartRun(
{{"callback", MakePacket<std::function<void(const Packet&)>>(std::bind(
&CalculatorGraphEventLoopTest::AddThreadSafeVectorSink,
this, std::placeholders::_1))},
{"blocking_mutex", mutex_side_packet}}));
constexpr int kNumInputPackets = 2;
constexpr int kMaxQueueSize = 1;
// Lock the mutex so that the BlockingPassThroughCalculator cannot read any of
// these packets.
mutex->Lock();
int fail_count = 0;
// Expect at least kNumInputPackets - kMaxQueueSize - 1 attempts to add
// packets to fail since the queue builds up. The -1 is because our throttling
// mechanism could be off by 1 at most due to the order of acquisition of
// locks.
for (int i = 0; i < kNumInputPackets; ++i) {
::mediapipe::Status status = graph.AddPacketToInputStream(
"input_numbers", Adopt(new int(i)).At(Timestamp(i)));
if (!status.ok()) {
++fail_count;
}
}
mutex->Unlock();
EXPECT_GE(fail_count, kNumInputPackets - kMaxQueueSize - 1);
// Don't wait but just close the input stream.
MEDIAPIPE_ASSERT_OK(graph.CloseInputStream("input_numbers"));
// Wait properly via the API until the graph is done.
MEDIAPIPE_ASSERT_OK(graph.WaitUntilDone());
}
// Verify that "max_queue_size: -1" disables throttling of graph-input-streams.
TEST_F(CalculatorGraphEventLoopTest, ThrottlingDisabled) {
CalculatorGraphConfig graph_config;
ASSERT_TRUE(proto_ns::TextFormat::ParseFromString(
R"(
node {
calculator: "BlockingPassThroughCalculator"
input_stream: "input_numbers"
output_stream: "output_numbers"
input_side_packet: "blocking_mutex"
}
input_stream: "input_numbers"
max_queue_size: -1
)",
&graph_config));
absl::Mutex* mutex = new absl::Mutex();
Packet mutex_side_packet = AdoptAsUniquePtr(mutex);
CalculatorGraph graph(graph_config);
graph.SetGraphInputStreamAddMode(
CalculatorGraph::GraphInputStreamAddMode::ADD_IF_NOT_FULL);
// Start MediaPipe graph.
MEDIAPIPE_ASSERT_OK(graph.StartRun({{"blocking_mutex", mutex_side_packet}}));
// Lock the mutex so that the BlockingPassThroughCalculator cannot read any
// of these packets.
mutex->Lock();
for (int i = 0; i < 10; ++i) {
MEDIAPIPE_EXPECT_OK(graph.AddPacketToInputStream(
"input_numbers", Adopt(new int(i)).At(Timestamp(i))));
}
mutex->Unlock();
MEDIAPIPE_EXPECT_OK(graph.CloseInputStream("input_numbers"));
MEDIAPIPE_EXPECT_OK(graph.WaitUntilDone());
}
// Verify that the graph input stream throttling code still works if we run the
// graph twice.
TEST_F(CalculatorGraphEventLoopTest, ThrottleGraphInputStreamTwice) {
CalculatorGraphConfig graph_config;
ASSERT_TRUE(proto_ns::TextFormat::ParseFromString(
R"(
node {
calculator: "BlockingPassThroughCalculator"
input_stream: "input_numbers"
output_stream: "output_numbers"
input_side_packet: "blocking_mutex"
}
input_stream: "input_numbers"
max_queue_size: 1
)",
&graph_config));
absl::Mutex* mutex = new absl::Mutex();
Packet mutex_side_packet = AdoptAsUniquePtr(mutex);
CalculatorGraph graph(graph_config);
graph.SetGraphInputStreamAddMode(
CalculatorGraph::GraphInputStreamAddMode::ADD_IF_NOT_FULL);
// Run the graph twice.
for (int i = 0; i < 2; ++i) {
// Start MediaPipe graph.
MEDIAPIPE_ASSERT_OK(
graph.StartRun({{"blocking_mutex", mutex_side_packet}}));
// Lock the mutex so that the BlockingPassThroughCalculator cannot read any
// of these packets.
mutex->Lock();
::mediapipe::Status status = ::mediapipe::OkStatus();
for (int i = 0; i < 10; ++i) {
status = graph.AddPacketToInputStream("input_numbers",
Adopt(new int(i)).At(Timestamp(i)));
if (!status.ok()) {
break;
}
}
mutex->Unlock();
ASSERT_FALSE(status.ok());
EXPECT_EQ(status.code(), ::mediapipe::StatusCode::kUnavailable);
EXPECT_THAT(status.message(), testing::HasSubstr("Graph is throttled."));
MEDIAPIPE_ASSERT_OK(graph.CloseInputStream("input_numbers"));
MEDIAPIPE_ASSERT_OK(graph.WaitUntilDone());
}
}
// Test WAIT_TILL_NOT_FULL mode (default mode) for graph input streams (by
// creating more packets than the queue will support). All packets sent to the
// graph should be processed.
TEST_F(CalculatorGraphEventLoopTest, WaitToAddPacketToInputStream) {
CalculatorGraphConfig graph_config;
ASSERT_TRUE(proto_ns::TextFormat::ParseFromString(
R"(
node {
calculator: "PassThroughCalculator"
input_stream: "input_numbers"
output_stream: "output_numbers"
}
node {
calculator: "CallbackCalculator"
input_stream: "output_numbers"
input_side_packet: "CALLBACK:callback"
}
input_stream: "input_numbers"
num_threads: 2
max_queue_size: 10
)",
&graph_config));
// Start MediaPipe graph.
CalculatorGraph graph(graph_config);
MEDIAPIPE_ASSERT_OK(graph.StartRun(
{{"callback", MakePacket<std::function<void(const Packet&)>>(std::bind(
&CalculatorGraphEventLoopTest::AddThreadSafeVectorSink,
this, std::placeholders::_1))}}));
constexpr int kNumInputPackets = 20;
// All of these packets should be accepted by the graph.
int fail_count = 0;
for (int i = 0; i < kNumInputPackets; ++i) {
::mediapipe::Status status = graph.AddPacketToInputStream(
"input_numbers", Adopt(new int(i)).At(Timestamp(i)));
if (!status.ok()) {
++fail_count;
}
}
EXPECT_EQ(0, fail_count);
// Don't wait but just close the input stream.
MEDIAPIPE_ASSERT_OK(graph.CloseInputStream("input_numbers"));
// Wait properly via the API until the graph is done.
MEDIAPIPE_ASSERT_OK(graph.WaitUntilDone());
absl::ReaderMutexLock lock(&output_packets_mutex_);
ASSERT_EQ(kNumInputPackets, output_packets_.size());
}
} // namespace
} // namespace mediapipe
@@ -0,0 +1,383 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <map>
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/calculator_graph.h"
#include "mediapipe/framework/port/canonical_errors.h"
#include "mediapipe/framework/port/core_proto_inc.h"
#include "mediapipe/framework/port/gmock.h"
#include "mediapipe/framework/port/gtest.h"
#include "mediapipe/framework/port/integral_types.h"
#include "mediapipe/framework/port/logging.h"
#include "mediapipe/framework/port/status_matchers.h"
#include "mediapipe/framework/tool/sink.h"
#include "mediapipe/framework/tool/status_util.h"
namespace mediapipe {}
namespace testing_ns {
using ::mediapipe::CalculatorBase;
using ::mediapipe::CalculatorContext;
using ::mediapipe::CalculatorContract;
using ::mediapipe::CalculatorGraphConfig;
using ::mediapipe::GetFromUniquePtr;
using ::mediapipe::InputStreamShardSet;
using ::mediapipe::MakePacket;
using ::mediapipe::OutputStreamShardSet;
using ::mediapipe::Timestamp;
namespace proto_ns = ::mediapipe::proto_ns;
using ::mediapipe::CalculatorGraph;
using ::mediapipe::Packet;
class InfiniteSequenceCalculator : public mediapipe::CalculatorBase {
public:
static ::mediapipe::Status GetContract(mediapipe::CalculatorContract* cc) {
cc->Outputs().Tag("OUT").Set<int>();
cc->Outputs().Tag("EVENT").Set<int>();
return ::mediapipe::OkStatus();
}
::mediapipe::Status Open(CalculatorContext* cc) override {
cc->Outputs().Tag("EVENT").AddPacket(MakePacket<int>(1).At(Timestamp(1)));
return ::mediapipe::OkStatus();
}
::mediapipe::Status Process(CalculatorContext* cc) override {
cc->Outputs().Tag("OUT").AddPacket(
MakePacket<int>(count_).At(Timestamp(count_)));
count_++;
return ::mediapipe::OkStatus();
}
::mediapipe::Status Close(CalculatorContext* cc) override {
cc->Outputs().Tag("EVENT").AddPacket(MakePacket<int>(2).At(Timestamp(2)));
return ::mediapipe::OkStatus();
}
private:
int count_ = 0;
};
REGISTER_CALCULATOR(::testing_ns::InfiniteSequenceCalculator);
class StoppingPassThroughCalculator : public mediapipe::CalculatorBase {
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
for (int i = 0; i < cc->Inputs().NumEntries(""); ++i) {
cc->Inputs().Get("", i).SetAny();
cc->Outputs().Get("", i).SetSameAs(&cc->Inputs().Get("", i));
}
cc->Outputs().Tag("EVENT").Set<int>();
return ::mediapipe::OkStatus();
}
::mediapipe::Status Open(CalculatorContext* cc) override {
cc->Outputs().Tag("EVENT").AddPacket(MakePacket<int>(1).At(Timestamp(1)));
return ::mediapipe::OkStatus();
}
::mediapipe::Status Process(CalculatorContext* cc) override {
for (int i = 0; i < cc->Inputs().NumEntries(""); ++i) {
if (!cc->Inputs().Get("", i).IsEmpty()) {
cc->Outputs().Get("", i).AddPacket(cc->Inputs().Get("", i).Value());
}
}
return (++count_ <= max_count_) ? ::mediapipe::OkStatus()
: ::mediapipe::tool::StatusStop();
}
::mediapipe::Status Close(CalculatorContext* cc) override {
cc->Outputs().Tag("EVENT").AddPacket(MakePacket<int>(2).At(Timestamp(2)));
return ::mediapipe::OkStatus();
}
private:
int count_ = 0;
int max_count_ = 10;
};
REGISTER_CALCULATOR(::testing_ns::StoppingPassThroughCalculator);
// A simple Semaphore for synchronizing test threads.
class AtomicSemaphore {
public:
AtomicSemaphore(int64_t supply) : supply_(supply) {}
void Acquire(int64_t amount) {
while (supply_.fetch_sub(amount) - amount < 0) {
Release(amount);
}
}
void Release(int64_t amount) { supply_ += amount; }
private:
std::atomic<int64_t> supply_;
};
// A ProcessFunction that passes through all packets.
::mediapipe::Status DoProcess(const InputStreamShardSet& inputs,
OutputStreamShardSet* outputs) {
for (int i = 0; i < inputs.NumEntries(); ++i) {
if (!inputs.Index(i).Value().IsEmpty()) {
outputs->Index(i).AddPacket(inputs.Index(i).Value());
}
}
return ::mediapipe::OkStatus();
}
typedef std::function<::mediapipe::Status(const InputStreamShardSet&,
OutputStreamShardSet*)>
ProcessFunction;
// A Calculator that delegates its Process function to a callback function.
class ProcessCallbackCalculator : public CalculatorBase {
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
for (int i = 0; i < cc->Inputs().NumEntries(); ++i) {
cc->Inputs().Index(i).SetAny();
cc->Outputs().Index(i).SetSameAs(&cc->Inputs().Index(0));
}
cc->InputSidePackets().Index(0).Set<std::unique_ptr<ProcessFunction>>();
return ::mediapipe::OkStatus();
}
::mediapipe::Status Open(CalculatorContext* cc) final {
callback_ =
*GetFromUniquePtr<ProcessFunction>(cc->InputSidePackets().Index(0));
return ::mediapipe::OkStatus();
}
::mediapipe::Status Process(CalculatorContext* cc) final {
return callback_(cc->Inputs(), &(cc->Outputs()));
}
private:
ProcessFunction callback_;
};
REGISTER_CALCULATOR(::testing_ns::ProcessCallbackCalculator);
// Tests CloseAllPacketSources.
TEST(CalculatorGraphStoppingTest, CloseAllPacketSources) {
CalculatorGraphConfig graph_config;
ASSERT_TRUE(proto_ns::TextFormat::ParseFromString(R"(
max_queue_size: 5
input_stream: 'input'
node {
calculator: 'InfiniteSequenceCalculator'
output_stream: 'OUT:count'
output_stream: 'EVENT:event'
}
node {
calculator: 'StoppingPassThroughCalculator'
input_stream: 'count'
input_stream: 'input'
output_stream: 'count_out'
output_stream: 'input_out'
output_stream: 'EVENT:event_out'
}
package: 'testing_ns'
)",
&graph_config));
CalculatorGraph graph;
MEDIAPIPE_ASSERT_OK(graph.Initialize(graph_config, {}));
// Observe output packets, and call CloseAllPacketSources after kNumPackets.
std::vector<Packet> out_packets;
std::vector<Packet> count_packets;
std::vector<int> event_packets;
std::vector<int> event_out_packets;
int kNumPackets = 8;
MEDIAPIPE_ASSERT_OK(graph.ObserveOutputStream( //
"input_out", [&](const Packet& packet) {
out_packets.push_back(packet);
if (out_packets.size() >= kNumPackets) {
MEDIAPIPE_EXPECT_OK(graph.CloseAllPacketSources());
}
return ::mediapipe::OkStatus();
}));
MEDIAPIPE_ASSERT_OK(graph.ObserveOutputStream( //
"count_out", [&](const Packet& packet) {
count_packets.push_back(packet);
return ::mediapipe::OkStatus();
}));
MEDIAPIPE_ASSERT_OK(graph.ObserveOutputStream( //
"event", [&](const Packet& packet) {
event_packets.push_back(packet.Get<int>());
return ::mediapipe::OkStatus();
}));
MEDIAPIPE_ASSERT_OK(graph.ObserveOutputStream( //
"event_out", [&](const Packet& packet) {
event_out_packets.push_back(packet.Get<int>());
return ::mediapipe::OkStatus();
}));
MEDIAPIPE_ASSERT_OK(graph.StartRun({}));
for (int i = 0; i < kNumPackets; ++i) {
MEDIAPIPE_EXPECT_OK(graph.AddPacketToInputStream(
"input", MakePacket<int>(i).At(Timestamp(i))));
}
// The graph run should complete with no error status.
MEDIAPIPE_EXPECT_OK(graph.WaitUntilDone());
EXPECT_EQ(kNumPackets, out_packets.size());
EXPECT_LE(kNumPackets, count_packets.size());
std::vector<int> expected_events = {1, 2};
EXPECT_EQ(event_packets, expected_events);
EXPECT_EQ(event_out_packets, expected_events);
}
// Verify that deadlock due to throttling can be reported.
TEST(CalculatorGraphStoppingTest, DeadlockReporting) {
CalculatorGraphConfig config;
ASSERT_TRUE(proto_ns::TextFormat::ParseFromString(R"(
input_stream: 'in_1'
input_stream: 'in_2'
max_queue_size: 2
node {
calculator: 'ProcessCallbackCalculator'
input_stream: 'in_1'
input_stream: 'in_2'
output_stream: 'out_1'
output_stream: 'out_2'
input_side_packet: 'callback_1'
}
package: 'testing_ns'
report_deadlock: true
)",
&config));
CalculatorGraph graph;
MEDIAPIPE_ASSERT_OK(graph.Initialize(config));
graph.SetGraphInputStreamAddMode(
CalculatorGraph::GraphInputStreamAddMode::WAIT_TILL_NOT_FULL);
std::vector<Packet> out_packets;
MEDIAPIPE_ASSERT_OK(
graph.ObserveOutputStream("out_1", [&out_packets](const Packet& packet) {
out_packets.push_back(packet);
return ::mediapipe::OkStatus();
}));
// Lambda that waits for a local semaphore.
AtomicSemaphore semaphore(0);
ProcessFunction callback_1 = [&semaphore](const InputStreamShardSet& inputs,
OutputStreamShardSet* outputs) {
semaphore.Acquire(1);
return DoProcess(inputs, outputs);
};
// Lambda that adds a packet to the calculator graph.
auto add_packet = [&graph](std::string s, int i) {
return graph.AddPacketToInputStream(s, MakePacket<int>(i).At(Timestamp(i)));
};
// Start the graph.
MEDIAPIPE_ASSERT_OK(graph.StartRun({
{"callback_1", AdoptAsUniquePtr(new auto(callback_1))},
}));
// Add 3 packets to "in_1" with no packets on "in_2".
// This causes throttling and deadlock with max_queue_size 2.
semaphore.Release(3);
MEDIAPIPE_EXPECT_OK(add_packet("in_1", 1));
MEDIAPIPE_EXPECT_OK(add_packet("in_1", 2));
EXPECT_FALSE(add_packet("in_1", 3).ok());
::mediapipe::Status status = graph.WaitUntilIdle();
EXPECT_EQ(status.code(), ::mediapipe::StatusCode::kUnavailable);
EXPECT_THAT(
status.message(),
testing::HasSubstr("Detected a deadlock due to input throttling"));
MEDIAPIPE_ASSERT_OK(graph.CloseAllInputStreams());
EXPECT_FALSE(graph.WaitUntilDone().ok());
ASSERT_EQ(0, out_packets.size());
}
// Verify that input streams grow due to deadlock resolution.
TEST(CalculatorGraphStoppingTest, DeadlockResolution) {
CalculatorGraphConfig config;
ASSERT_TRUE(proto_ns::TextFormat::ParseFromString(R"(
input_stream: 'in_1'
input_stream: 'in_2'
max_queue_size: 2
node {
calculator: 'ProcessCallbackCalculator'
input_stream: 'in_1'
input_stream: 'in_2'
output_stream: 'out_1'
output_stream: 'out_2'
input_side_packet: 'callback_1'
}
package: 'testing_ns'
)",
&config));
CalculatorGraph graph;
MEDIAPIPE_ASSERT_OK(graph.Initialize(config));
graph.SetGraphInputStreamAddMode(
CalculatorGraph::GraphInputStreamAddMode::WAIT_TILL_NOT_FULL);
std::vector<Packet> out_packets;
MEDIAPIPE_ASSERT_OK(
graph.ObserveOutputStream("out_1", [&out_packets](const Packet& packet) {
out_packets.push_back(packet);
return ::mediapipe::OkStatus();
}));
// Lambda that waits for a local semaphore.
AtomicSemaphore semaphore(0);
ProcessFunction callback_1 = [&semaphore](const InputStreamShardSet& inputs,
OutputStreamShardSet* outputs) {
semaphore.Acquire(1);
return DoProcess(inputs, outputs);
};
// Lambda that adds a packet to the calculator graph.
auto add_packet = [&graph](std::string s, int i) {
return graph.AddPacketToInputStream(s, MakePacket<int>(i).At(Timestamp(i)));
};
// Start the graph.
MEDIAPIPE_ASSERT_OK(graph.StartRun({
{"callback_1", AdoptAsUniquePtr(new auto(callback_1))},
}));
// Add 9 packets to "in_1" with no packets on "in_2".
// This grows the input stream "in_1" to max-queue-size 10.
semaphore.Release(9);
for (int i = 1; i <= 9; ++i) {
MEDIAPIPE_EXPECT_OK(add_packet("in_1", i));
MEDIAPIPE_ASSERT_OK(graph.WaitUntilIdle());
}
// Advance the timestamp-bound and flush "in_1".
semaphore.Release(1);
MEDIAPIPE_EXPECT_OK(add_packet("in_2", 30));
MEDIAPIPE_ASSERT_OK(graph.WaitUntilIdle());
// Fill up input stream "in_1", with the semaphore blocked and deadlock
// resolution disabled.
for (int i = 11; i < 23; ++i) {
MEDIAPIPE_EXPECT_OK(add_packet("in_1", i));
}
// Adding any more packets fails with error "Graph is throttled".
graph.SetGraphInputStreamAddMode(
CalculatorGraph::GraphInputStreamAddMode::ADD_IF_NOT_FULL);
EXPECT_FALSE(add_packet("in_1", 23).ok());
// Allow the 12 blocked calls to "callback_1" to complete.
semaphore.Release(12);
MEDIAPIPE_ASSERT_OK(graph.WaitUntilIdle());
MEDIAPIPE_ASSERT_OK(graph.CloseAllInputStreams());
MEDIAPIPE_ASSERT_OK(graph.WaitUntilDone());
ASSERT_EQ(21, out_packets.size());
}
} // namespace testing_ns
File diff suppressed because it is too large Load Diff
+825
View File
@@ -0,0 +1,825 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/calculator_node.h"
#include <set>
#include <string>
#include <unordered_map>
#include <utility>
#include "absl/memory/memory.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
#include "absl/strings/substitute.h"
#include "absl/synchronization/mutex.h"
#include "mediapipe/framework/calculator.pb.h"
#include "mediapipe/framework/calculator_base.h"
#include "mediapipe/framework/calculator_registry_util.h"
#include "mediapipe/framework/counter_factory.h"
#include "mediapipe/framework/input_stream_manager.h"
#include "mediapipe/framework/mediapipe_profiling.h"
#include "mediapipe/framework/output_stream_manager.h"
#include "mediapipe/framework/packet.h"
#include "mediapipe/framework/packet_set.h"
#include "mediapipe/framework/packet_type.h"
#include "mediapipe/framework/port.h"
#include "mediapipe/framework/port/logging.h"
#include "mediapipe/framework/port/proto_ns.h"
#include "mediapipe/framework/port/ret_check.h"
#include "mediapipe/framework/port/source_location.h"
#include "mediapipe/framework/port/status_builder.h"
#include "mediapipe/framework/timestamp.h"
#include "mediapipe/framework/tool/status_util.h"
#include "mediapipe/framework/tool/tag_map.h"
#include "mediapipe/framework/tool/validate_name.h"
#include "mediapipe/gpu/graph_support.h"
namespace mediapipe {
namespace {
const PacketType* GetPacketType(const PacketTypeSet& packet_type_set,
const std::string& tag, const int index) {
CollectionItemId id;
if (tag.empty()) {
id = packet_type_set.GetId("", index);
} else {
id = packet_type_set.GetId(tag, 0);
}
CHECK(id.IsValid()) << "Internal mediapipe error.";
return &packet_type_set.Get(id);
}
} // namespace
CalculatorNode::CalculatorNode() {}
Timestamp CalculatorNode::SourceProcessOrder(
const CalculatorContext* cc) const {
return calculator_->SourceProcessOrder(cc);
}
::mediapipe::Status CalculatorNode::Initialize(
const ValidatedGraphConfig* validated_graph, int node_id,
InputStreamManager* input_stream_managers,
OutputStreamManager* output_stream_managers,
OutputSidePacketImpl* output_side_packets, int* buffer_size_hint,
std::shared_ptr<ProfilingContext> profiling_context) {
RET_CHECK(buffer_size_hint) << "buffer_size_hint is NULL";
node_id_ = node_id;
validated_graph_ = validated_graph;
profiling_context_ = profiling_context;
const CalculatorGraphConfig::Node& node_config =
validated_graph_->Config().node(node_id_);
name_ = CanonicalNodeName(validated_graph_->Config(), node_id_);
max_in_flight_ = node_config.max_in_flight();
max_in_flight_ = max_in_flight_ ? max_in_flight_ : 1;
if (!node_config.executor().empty()) {
executor_ = node_config.executor();
}
source_layer_ = node_config.source_layer();
const NodeTypeInfo& node_type_info =
validated_graph_->CalculatorInfos()[node_id_];
uses_gpu_ =
node_type_info.InputSidePacketTypes().HasTag(kGpuSharedTagName) ||
ContainsKey(node_type_info.Contract().ServiceRequests(), kGpuService.key);
// TODO Propagate types between calculators when SetAny is used.
RETURN_IF_ERROR(InitializeOutputSidePackets(
node_type_info.OutputSidePacketTypes(), output_side_packets));
RETURN_IF_ERROR(InitializeInputSidePackets(output_side_packets));
RETURN_IF_ERROR(InitializeOutputStreamHandler(
node_config.output_stream_handler(), node_type_info.OutputStreamTypes()));
RETURN_IF_ERROR(InitializeOutputStreams(output_stream_managers));
calculator_state_ = absl::make_unique<CalculatorState>(
name_, node_id_, node_config.calculator(), node_config,
profiling_context_);
// Inform the scheduler that this node has buffering behavior and that the
// maximum input queue size should be adjusted accordingly.
*buffer_size_hint = node_config.buffer_size_hint();
calculator_context_manager_.Initialize(
calculator_state_.get(), node_type_info.InputStreamTypes().TagMap(),
node_type_info.OutputStreamTypes().TagMap(),
/*calculator_run_in_parallel=*/max_in_flight_ > 1);
// The graph specified InputStreamHandler takes priority.
const bool graph_specified =
node_config.input_stream_handler().has_input_stream_handler();
const bool calc_specified = !(node_type_info.GetInputStreamHandler().empty());
// Only use calculator ISH if available, and if the graph ISH is not set.
InputStreamHandlerConfig handler_config;
const bool use_calc_specified = calc_specified && !graph_specified;
if (use_calc_specified) {
*(handler_config.mutable_input_stream_handler()) =
node_type_info.GetInputStreamHandler();
*(handler_config.mutable_options()) =
node_type_info.GetInputStreamHandlerOptions();
}
// Use calculator or graph specified InputStreamHandler, or the default ISH
// already set from graph.
RETURN_IF_ERROR(InitializeInputStreamHandler(
use_calc_specified ? handler_config : node_config.input_stream_handler(),
node_type_info.InputStreamTypes()));
return InitializeInputStreams(input_stream_managers, output_stream_managers);
}
::mediapipe::Status CalculatorNode::InitializeOutputSidePackets(
const PacketTypeSet& output_side_packet_types,
OutputSidePacketImpl* output_side_packets) {
output_side_packets_ =
absl::make_unique<OutputSidePacketSet>(output_side_packet_types.TagMap());
const NodeTypeInfo& node_type_info =
validated_graph_->CalculatorInfos()[node_id_];
int base_index = node_type_info.OutputSidePacketBaseIndex();
RET_CHECK_LE(0, base_index);
for (CollectionItemId id = output_side_packets_->BeginId();
id < output_side_packets_->EndId(); ++id) {
output_side_packets_->GetPtr(id) =
&output_side_packets[base_index + id.value()];
}
return ::mediapipe::OkStatus();
}
::mediapipe::Status CalculatorNode::InitializeInputSidePackets(
OutputSidePacketImpl* output_side_packets) {
const NodeTypeInfo& node_type_info =
validated_graph_->CalculatorInfos()[node_id_];
int base_index = node_type_info.InputSidePacketBaseIndex();
RET_CHECK_LE(0, base_index);
// Set all the mirrors.
for (CollectionItemId id = node_type_info.InputSidePacketTypes().BeginId();
id < node_type_info.InputSidePacketTypes().EndId(); ++id) {
int output_side_packet_index =
validated_graph_->InputSidePacketInfos()[base_index + id.value()]
.upstream;
if (output_side_packet_index < 0) {
// Not generated by a graph node. Comes from an extra side packet
// provided to the graph.
continue;
}
OutputSidePacketImpl* origin_output_side_packet =
&output_side_packets[output_side_packet_index];
VLOG(2) << "Adding mirror for input side packet with id " << id.value()
<< " and flat index " << base_index + id.value()
<< " which will be connected to output side packet with flat index "
<< output_side_packet_index;
origin_output_side_packet->AddMirror(&input_side_packet_handler_, id);
}
return ::mediapipe::OkStatus();
}
::mediapipe::Status CalculatorNode::InitializeOutputStreams(
OutputStreamManager* output_stream_managers) {
RET_CHECK(output_stream_managers) << "output_stream_managers is NULL";
const NodeTypeInfo& node_type_info =
validated_graph_->CalculatorInfos()[node_id_];
RET_CHECK_LE(0, node_type_info.OutputStreamBaseIndex());
OutputStreamManager* current_output_stream_managers =
&output_stream_managers[node_type_info.OutputStreamBaseIndex()];
return output_stream_handler_->InitializeOutputStreamManagers(
current_output_stream_managers);
}
::mediapipe::Status CalculatorNode::InitializeInputStreams(
InputStreamManager* input_stream_managers,
OutputStreamManager* output_stream_managers) {
RET_CHECK(input_stream_managers) << "input_stream_managers is NULL";
RET_CHECK(output_stream_managers) << "output_stream_managers is NULL";
const NodeTypeInfo& node_type_info =
validated_graph_->CalculatorInfos()[node_id_];
RET_CHECK_LE(0, node_type_info.InputStreamBaseIndex());
InputStreamManager* current_input_stream_managers =
&input_stream_managers[node_type_info.InputStreamBaseIndex()];
RETURN_IF_ERROR(input_stream_handler_->InitializeInputStreamManagers(
current_input_stream_managers));
// Set all the mirrors.
for (CollectionItemId id = node_type_info.InputStreamTypes().BeginId();
id < node_type_info.InputStreamTypes().EndId(); ++id) {
int output_stream_index =
validated_graph_
->InputStreamInfos()[node_type_info.InputStreamBaseIndex() +
id.value()]
.upstream;
RET_CHECK_LE(0, output_stream_index);
OutputStreamManager* origin_output_stream_manager =
&output_stream_managers[output_stream_index];
VLOG(2) << "Adding mirror for input stream with id " << id.value()
<< " and flat index "
<< node_type_info.InputStreamBaseIndex() + id.value()
<< " which will be connected to output stream with flat index "
<< output_stream_index;
origin_output_stream_manager->AddMirror(input_stream_handler_.get(), id);
}
return ::mediapipe::OkStatus();
}
::mediapipe::Status CalculatorNode::InitializeInputStreamHandler(
const InputStreamHandlerConfig& handler_config,
const PacketTypeSet& input_stream_types) {
const ProtoString& input_stream_handler_name =
handler_config.input_stream_handler();
RET_CHECK(!input_stream_handler_name.empty());
ASSIGN_OR_RETURN(input_stream_handler_,
InputStreamHandlerRegistry::CreateByNameInNamespace(
validated_graph_->Package(), input_stream_handler_name,
input_stream_types.TagMap(),
&calculator_context_manager_, handler_config.options(),
/*calculator_run_in_parallel=*/max_in_flight_ > 1),
_ << "\"" << input_stream_handler_name
<< "\" is not a registered input stream handler.");
return ::mediapipe::OkStatus();
}
::mediapipe::Status CalculatorNode::InitializeOutputStreamHandler(
const OutputStreamHandlerConfig& handler_config,
const PacketTypeSet& output_stream_types) {
const ProtoString& output_stream_handler_name =
handler_config.output_stream_handler();
RET_CHECK(!output_stream_handler_name.empty());
ASSIGN_OR_RETURN(output_stream_handler_,
OutputStreamHandlerRegistry::CreateByNameInNamespace(
validated_graph_->Package(), output_stream_handler_name,
output_stream_types.TagMap(),
&calculator_context_manager_, handler_config.options(),
/*calculator_run_in_parallel=*/max_in_flight_ > 1),
_ << "\"" << output_stream_handler_name
<< "\" is not a registered output stream handler.");
return ::mediapipe::OkStatus();
}
::mediapipe::Status CalculatorNode::ConnectShardsToStreams(
CalculatorContext* calculator_context) {
RET_CHECK(calculator_context);
RETURN_IF_ERROR(
input_stream_handler_->SetupInputShards(&calculator_context->Inputs()));
return output_stream_handler_->SetupOutputShards(
&calculator_context->Outputs());
}
void CalculatorNode::SetExecutor(const std::string& executor) {
absl::MutexLock status_lock(&status_mutex_);
CHECK_LT(status_, kStateOpened);
executor_ = executor;
}
bool CalculatorNode::Prepared() const {
absl::MutexLock status_lock(&status_mutex_);
return status_ >= kStatePrepared;
}
bool CalculatorNode::Opened() const {
absl::MutexLock status_lock(&status_mutex_);
return status_ >= kStateOpened;
}
bool CalculatorNode::Active() const {
absl::MutexLock status_lock(&status_mutex_);
return status_ >= kStateActive;
}
bool CalculatorNode::Closed() const {
absl::MutexLock status_lock(&status_mutex_);
return status_ >= kStateClosed;
}
void CalculatorNode::SetMaxInputStreamQueueSize(int max_queue_size) {
CHECK(input_stream_handler_);
input_stream_handler_->SetMaxQueueSize(max_queue_size);
}
::mediapipe::Status CalculatorNode::PrepareForRun(
const std::map<std::string, Packet>& all_side_packets,
const std::map<std::string, Packet>& service_packets,
std::function<void()> ready_for_open_callback,
std::function<void()> source_node_opened_callback,
std::function<void(CalculatorContext*)> schedule_callback,
std::function<void(::mediapipe::Status)> error_callback,
CounterFactory* counter_factory) {
RET_CHECK(ready_for_open_callback) << "ready_for_open_callback is NULL";
RET_CHECK(schedule_callback) << "schedule_callback is NULL";
RET_CHECK(error_callback) << "error_callback is NULL";
calculator_state_->ResetBetweenRuns();
ready_for_open_callback_ = std::move(ready_for_open_callback);
source_node_opened_callback_ = std::move(source_node_opened_callback);
input_stream_handler_->PrepareForRun(
[this]() { CalculatorNode::InputStreamHeadersReady(); },
[this]() { CalculatorNode::CheckIfBecameReady(); },
std::move(schedule_callback), error_callback);
output_stream_handler_->PrepareForRun(error_callback);
const PacketTypeSet* input_side_packet_types =
&validated_graph_->CalculatorInfos()[node_id_].InputSidePacketTypes();
RETURN_IF_ERROR(input_side_packet_handler_.PrepareForRun(
input_side_packet_types, all_side_packets,
[this]() { CalculatorNode::InputSidePacketsReady(); },
std::move(error_callback)));
calculator_state_->SetInputSidePackets(
&input_side_packet_handler_.InputSidePackets());
calculator_state_->SetOutputSidePackets(output_side_packets_.get());
calculator_state_->SetCounterFactory(counter_factory);
const auto& contract =
validated_graph_->CalculatorInfos()[node_id_].Contract();
for (const auto& svc_req : contract.ServiceRequests()) {
const auto& req = svc_req.second;
std::string key{req.Service().key};
auto it = service_packets.find(key);
if (it == service_packets.end()) {
RET_CHECK(req.IsOptional())
<< "required service '" << key << "' was not provided";
} else {
calculator_state_->SetServicePacket(key, it->second);
}
}
RETURN_IF_ERROR(calculator_context_manager_.PrepareForRun(std::bind(
&CalculatorNode::ConnectShardsToStreams, this, std::placeholders::_1)));
auto calculator_statusor = CreateCalculator(
input_stream_handler_->InputTagMap(),
output_stream_handler_->OutputTagMap(), validated_graph_->Package(),
calculator_state_.get(),
calculator_context_manager_.GetDefaultCalculatorContext());
if (!calculator_statusor.ok()) {
return calculator_statusor.status();
}
calculator_ = std::move(calculator_statusor).ValueOrDie();
needs_to_close_ = false;
{
absl::MutexLock status_lock(&status_mutex_);
status_ = kStatePrepared;
scheduling_state_ = kIdle;
current_in_flight_ = 0;
input_stream_headers_ready_called_ = false;
input_side_packets_ready_called_ = false;
input_stream_headers_ready_ =
(input_stream_handler_->UnsetHeaderCount() == 0);
input_side_packets_ready_ =
(input_side_packet_handler_.MissingInputSidePacketCount() == 0);
}
return ::mediapipe::OkStatus();
}
::mediapipe::Status CalculatorNode::OpenNode() {
VLOG(2) << "CalculatorNode::OpenNode() for " << DebugName();
CalculatorContext* default_context =
calculator_context_manager_.GetDefaultCalculatorContext();
InputStreamShardSet* inputs = &default_context->Inputs();
// The upstream calculators may set the headers in the output streams during
// Calculator::Open(), needs to update the header packets in input stream
// shards.
input_stream_handler_->UpdateInputShardHeaders(inputs);
OutputStreamShardSet* outputs = &default_context->Outputs();
output_stream_handler_->PrepareOutputs(Timestamp::Unstarted(), outputs);
calculator_context_manager_.PushInputTimestampToContext(
default_context, Timestamp::Unstarted());
::mediapipe::Status result;
{
MEDIAPIPE_PROFILING(OPEN, default_context);
LegacyCalculatorSupport::Scoped<CalculatorContext> s(default_context);
result = calculator_->Open(default_context);
}
calculator_context_manager_.PopInputTimestampFromContext(default_context);
if (IsSource()) {
// A source node has a dummy input timestamp of 0 for Process(). This input
// timestamp is not popped until Close() is called.
calculator_context_manager_.PushInputTimestampToContext(default_context,
Timestamp(0));
}
LOG_IF(FATAL, result == tool::StatusStop()) << absl::Substitute(
"Open() on node \"$0\" returned tool::StatusStop() which should only be "
"used to signal that a source node is done producing data.",
DebugName());
RETURN_IF_ERROR(result).SetPrepend() << absl::Substitute(
"Calculator::Open() for node \"$0\" failed: ", DebugName());
needs_to_close_ = true;
output_stream_handler_->Open(outputs);
{
absl::MutexLock status_lock(&status_mutex_);
status_ = kStateOpened;
}
return ::mediapipe::OkStatus();
}
void CalculatorNode::ActivateNode() {
absl::MutexLock status_lock(&status_mutex_);
CHECK_EQ(status_, kStateOpened) << DebugName();
status_ = kStateActive;
}
void CalculatorNode::CloseInputStreams() {
{
absl::MutexLock status_lock(&status_mutex_);
if (status_ == kStateClosed) {
return;
}
}
VLOG(2) << "Closing node " << DebugName() << " input streams.";
// Clear the input queues and prevent the upstream nodes from filling them
// back in. We may still get ProcessNode called on us after this.
input_stream_handler_->Close();
}
void CalculatorNode::CloseOutputStreams(OutputStreamShardSet* outputs) {
{
absl::MutexLock status_lock(&status_mutex_);
if (status_ == kStateClosed) {
return;
}
}
VLOG(2) << "Closing node " << DebugName() << " output streams.";
output_stream_handler_->Close(outputs);
}
::mediapipe::Status CalculatorNode::CloseNode(
const ::mediapipe::Status& graph_status, bool graph_run_ended) {
{
absl::MutexLock status_lock(&status_mutex_);
RET_CHECK_NE(status_, kStateClosed)
<< "CloseNode() must only be called once.";
}
CloseInputStreams();
CalculatorContext* default_context =
calculator_context_manager_.GetDefaultCalculatorContext();
OutputStreamShardSet* outputs = &default_context->Outputs();
output_stream_handler_->PrepareOutputs(Timestamp::Done(), outputs);
if (IsSource()) {
calculator_context_manager_.PopInputTimestampFromContext(default_context);
calculator_context_manager_.PushInputTimestampToContext(default_context,
Timestamp::Done());
}
calculator_context_manager_.SetGraphStatusInContext(default_context,
graph_status);
::mediapipe::Status result;
{
MEDIAPIPE_PROFILING(CLOSE, default_context);
LegacyCalculatorSupport::Scoped<CalculatorContext> s(default_context);
result = calculator_->Close(default_context);
}
needs_to_close_ = false;
LOG_IF(FATAL, result == tool::StatusStop()) << absl::Substitute(
"Close() on node \"$0\" returned tool::StatusStop() which should only be "
"used to signal that a source node is done producing data.",
DebugName());
// If the graph run has ended, we are cleaning up after the run and don't
// need to propagate updates to mirrors, so we can skip this
// CloseOutputStreams() call. CleanupAfterRun() will close the output
// streams.
if (!graph_run_ended) {
CloseOutputStreams(outputs);
}
{
absl::MutexLock status_lock(&status_mutex_);
status_ = kStateClosed;
}
RETURN_IF_ERROR(result).SetPrepend() << absl::Substitute(
"Calculator::Close() for node \"$0\" failed: ", DebugName());
VLOG(2) << "Closed node " << DebugName();
return ::mediapipe::OkStatus();
}
void CalculatorNode::CleanupAfterRun(const ::mediapipe::Status& graph_status) {
if (needs_to_close_) {
calculator_context_manager_.PushInputTimestampToContext(
calculator_context_manager_.GetDefaultCalculatorContext(),
Timestamp::Done());
CloseNode(graph_status, /*graph_run_ended=*/true).IgnoreError();
}
calculator_ = nullptr;
// All pending output packets are automatically dropped when calculator
// context manager destroys all calculator context objects.
calculator_context_manager_.CleanupAfterRun();
CloseInputStreams();
// All output stream shards have been destroyed by calculator context manager.
CloseOutputStreams(/*outputs=*/nullptr);
{
absl::MutexLock lock(&status_mutex_);
status_ = kStateUninitialized;
scheduling_state_ = kIdle;
current_in_flight_ = 0;
}
}
void CalculatorNode::SchedulingLoop() {
int max_allowance = 0;
{
absl::MutexLock lock(&status_mutex_);
if (status_ == kStateClosed) {
scheduling_state_ = kIdle;
return;
}
max_allowance = max_in_flight_ - current_in_flight_;
}
while (true) {
Timestamp input_bound;
// input_bound is set to a meaningful value iff the latest readiness of the
// node is kNotReady when ScheduleInvocations() returns.
input_stream_handler_->ScheduleInvocations(max_allowance, &input_bound);
if (input_bound != Timestamp::Unset()) {
// Updates the minimum timestamp for which a new packet could possibly
// arrive.
output_stream_handler_->UpdateTaskTimestampBound(input_bound);
}
{
absl::MutexLock lock(&status_mutex_);
if (scheduling_state_ == kSchedulingPending &&
current_in_flight_ < max_in_flight_) {
max_allowance = max_in_flight_ - current_in_flight_;
scheduling_state_ = kScheduling;
} else {
scheduling_state_ = kIdle;
break;
}
}
}
}
bool CalculatorNode::ReadyForOpen() const {
absl::MutexLock lock(&status_mutex_);
return input_stream_headers_ready_ && input_side_packets_ready_;
}
void CalculatorNode::InputStreamHeadersReady() {
bool ready_for_open = false;
{
absl::MutexLock lock(&status_mutex_);
CHECK_EQ(status_, kStatePrepared) << DebugName();
CHECK(!input_stream_headers_ready_called_);
input_stream_headers_ready_called_ = true;
input_stream_headers_ready_ = true;
ready_for_open = input_side_packets_ready_;
}
if (ready_for_open) {
ready_for_open_callback_();
}
}
void CalculatorNode::InputSidePacketsReady() {
bool ready_for_open = false;
{
absl::MutexLock lock(&status_mutex_);
CHECK_EQ(status_, kStatePrepared) << DebugName();
CHECK(!input_side_packets_ready_called_);
input_side_packets_ready_called_ = true;
input_side_packets_ready_ = true;
ready_for_open = input_stream_headers_ready_;
}
if (ready_for_open) {
ready_for_open_callback_();
}
}
void CalculatorNode::CheckIfBecameReady() {
{
absl::MutexLock lock(&status_mutex_);
// Doesn't check if status_ is kStateActive since the function can only be
// invoked by non-source nodes.
if (status_ != kStateOpened) {
return;
}
if (scheduling_state_ == kIdle && current_in_flight_ < max_in_flight_) {
scheduling_state_ = kScheduling;
} else {
if (scheduling_state_ == kScheduling) {
// Changes the state to scheduling pending if another thread is doing
// the scheduling.
scheduling_state_ = kSchedulingPending;
}
return;
}
}
SchedulingLoop();
}
void CalculatorNode::NodeOpened() {
if (IsSource()) {
source_node_opened_callback_();
} else if (input_stream_handler_->NumInputStreams() != 0) {
// A node with input streams may have received input packets generated by
// the upstreams nodes' Open() or Process() methods. Check if the node is
// ready to run.
CheckIfBecameReady();
}
}
void CalculatorNode::EndScheduling() {
{
absl::MutexLock lock(&status_mutex_);
if (status_ != kStateOpened && status_ != kStateActive) {
return;
}
--current_in_flight_;
CHECK_GE(current_in_flight_, 0);
if (scheduling_state_ == kScheduling) {
// Changes the state to scheduling pending if another thread is doing the
// scheduling.
scheduling_state_ = kSchedulingPending;
return;
} else if (scheduling_state_ == kSchedulingPending) {
// Quits when another thread is already doing the scheduling.
return;
}
scheduling_state_ = kScheduling;
}
SchedulingLoop();
}
bool CalculatorNode::TryToBeginScheduling() {
absl::MutexLock lock(&status_mutex_);
if (current_in_flight_ < max_in_flight_) {
++current_in_flight_;
return true;
}
return false;
}
std::string CalculatorNode::DebugInputStreamNames() const {
return input_stream_handler_->DebugStreamNames();
}
std::string CalculatorNode::DebugName() const {
DCHECK(calculator_state_);
const std::string first_output_stream_name =
output_stream_handler_->FirstStreamName();
if (!first_output_stream_name.empty()) {
// A calculator is unique by its output streams (one of them is
// sufficient) unless it is a sink. For readability, its type name is
// included.
return absl::Substitute(
"[$0, $1 with output stream: $2]", calculator_state_->NodeName(),
calculator_state_->CalculatorType(), first_output_stream_name);
}
// If it is a sink, its full node spec is returned.
return absl::Substitute(
"[$0, $1 with node ID: $2 and $3]", calculator_state_->NodeName(),
calculator_state_->CalculatorType(), node_id_, DebugInputStreamNames());
}
// TODO: Split this function.
::mediapipe::Status CalculatorNode::ProcessNode(
CalculatorContext* calculator_context) {
if (IsSource()) {
// This is a source Calculator.
if (Closed()) {
return ::mediapipe::OkStatus();
}
const Timestamp input_timestamp = calculator_context->InputTimestamp();
OutputStreamShardSet* outputs = &calculator_context->Outputs();
output_stream_handler_->PrepareOutputs(input_timestamp, outputs);
VLOG(2) << "Calling Calculator::Process() for node: " << DebugName();
::mediapipe::Status result;
{
MEDIAPIPE_PROFILING(PROCESS, calculator_context);
LegacyCalculatorSupport::Scoped<CalculatorContext> s(calculator_context);
result = calculator_->Process(calculator_context);
}
bool node_stopped = false;
if (!result.ok()) {
if (result == tool::StatusStop()) {
// Needs to call CloseNode().
node_stopped = true;
} else {
return ::mediapipe::StatusBuilder(result, MEDIAPIPE_LOC).SetPrepend()
<< absl::Substitute(
"Calculator::Process() for node \"$0\" failed: ",
DebugName());
}
}
output_stream_handler_->PostProcess(input_timestamp);
if (node_stopped) {
RETURN_IF_ERROR(
CloseNode(::mediapipe::OkStatus(), /*graph_run_ended=*/false));
}
return ::mediapipe::OkStatus();
} else {
// This is not a source Calculator.
InputStreamShardSet* const inputs = &calculator_context->Inputs();
OutputStreamShardSet* const outputs = &calculator_context->Outputs();
::mediapipe::Status result =
::mediapipe::InternalError("Calculator context has no input packets.");
int num_invocations = calculator_context_manager_.NumberOfContextTimestamps(
*calculator_context);
RET_CHECK(num_invocations <= 1 || max_in_flight_ <= 1)
<< "num_invocations:" << num_invocations
<< ", max_in_flight_:" << max_in_flight_;
for (int i = 0; i < num_invocations; ++i) {
const Timestamp input_timestamp = calculator_context->InputTimestamp();
// The node is ready for Process().
if (input_timestamp.IsAllowedInStream()) {
input_stream_handler_->FinalizeInputSet(input_timestamp, inputs);
output_stream_handler_->PrepareOutputs(input_timestamp, outputs);
VLOG(2) << "Calling Calculator::Process() for node: " << DebugName();
{
MEDIAPIPE_PROFILING(PROCESS, calculator_context);
LegacyCalculatorSupport::Scoped<CalculatorContext> s(
calculator_context);
result = calculator_->Process(calculator_context);
}
// Removes one packet from each shard and progresses to the next input
// timestamp.
input_stream_handler_->ClearCurrentInputs(calculator_context);
// Nodes are allowed to return StatusStop() to cause the termination
// of the graph. This is different from an error in that it will
// ensure that all sources will be closed and that packets in input
// streams will be processed before the graph is terminated.
if (!result.ok() && result != tool::StatusStop()) {
return ::mediapipe::StatusBuilder(result, MEDIAPIPE_LOC).SetPrepend()
<< absl::Substitute(
"Calculator::Process() for node \"$0\" failed: ",
DebugName());
}
output_stream_handler_->PostProcess(input_timestamp);
if (result == tool::StatusStop()) {
return result;
}
} else if (input_timestamp == Timestamp::Done()) {
// Some or all the input streams are closed and there are not enough
// open input streams for Process(). So this node needs to be closed
// too.
// If the streams are closed, there shouldn't be more input.
CHECK_EQ(calculator_context_manager_.NumberOfContextTimestamps(
*calculator_context),
1);
return CloseNode(::mediapipe::OkStatus(), /*graph_run_ended=*/false);
} else {
RET_CHECK_FAIL()
<< "Invalid input timestamp in ProcessNode(). timestamp: "
<< input_timestamp;
}
}
return result;
}
}
void CalculatorNode::SetQueueSizeCallbacks(
InputStreamManager::QueueSizeCallback becomes_full_callback,
InputStreamManager::QueueSizeCallback becomes_not_full_callback) {
CHECK(input_stream_handler_);
input_stream_handler_->SetQueueSizeCallbacks(
std::move(becomes_full_callback), std::move(becomes_not_full_callback));
}
} // namespace mediapipe
+373
View File
@@ -0,0 +1,373 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Declares CalculatorNode which is internally used by the Calculator framework
// (in particular, CalculatorGraph and Calculator) to perform the computations.
#ifndef MEDIAPIPE_FRAMEWORK_CALCULATOR_NODE_H_
#define MEDIAPIPE_FRAMEWORK_CALCULATOR_NODE_H_
#include <stddef.h>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include "absl/base/macros.h"
#include "absl/synchronization/mutex.h"
#include "mediapipe/framework/calculator.pb.h"
#include "mediapipe/framework/calculator_base.h"
#include "mediapipe/framework/calculator_context.h"
#include "mediapipe/framework/calculator_context_manager.h"
#include "mediapipe/framework/calculator_state.h"
#include "mediapipe/framework/input_side_packet_handler.h"
#include "mediapipe/framework/input_stream_handler.h"
#include "mediapipe/framework/legacy_calculator_support.h"
#include "mediapipe/framework/output_side_packet_impl.h"
#include "mediapipe/framework/output_stream_handler.h"
#include "mediapipe/framework/packet.h"
#include "mediapipe/framework/packet_set.h"
#include "mediapipe/framework/packet_type.h"
#include "mediapipe/framework/port.h"
#include "mediapipe/framework/port/integral_types.h"
#include "mediapipe/framework/port/status.h"
#include "mediapipe/framework/stream_handler.pb.h"
#include "mediapipe/framework/timestamp.h"
#include "mediapipe/framework/tool/validate_name.h"
#include "mediapipe/framework/validated_graph_config.h"
namespace mediapipe {
class CounterFactory;
class InputStreamManager;
class OutputStreamManager;
namespace internal {
class SchedulerQueue;
} // namespace internal
class CalculatorNode {
public:
// Handy typedef for a map from the name of an output stream to the set of ids
// of upstream sources that affect it.
typedef std::unordered_map<std::string, std::unordered_set<int>>
OutputStreamToSourcesMap;
CalculatorNode();
CalculatorNode(const CalculatorNode&) = delete;
CalculatorNode& operator=(const CalculatorNode&) = delete;
int Id() const { return node_id_; }
// Returns a value according to which the scheduler queue determines the
// relative priority between runnable source nodes; a smaller value means
// running first. If a node is not a source, this method is not called.
Timestamp SourceProcessOrder(const CalculatorContext* cc) const;
// Retrieves a std::string name for the node. If the node's name was set in
// the calculator graph config, it will be returned. Otherwise, a
// human-readable std::string that uniquely identifies the node is returned,
// e.g.
// "[FooBarCalculator with first output stream \"foo_bar_output\"]" for
// non-sink nodes and "[FooBarCalculator with node ID: 42 and input streams:
// \"foo_bar_input\"]" for sink nodes. This name should be used in error
// messages where more context info is helpful.
std::string DebugName() const;
// Name of the executor which the node will execute on. If empty, the node
// will execute on the default executor.
const std::string& Executor() const { return executor_; }
// Changes the executor a node is assigned to.
void SetExecutor(const std::string& executor);
// Calls Process() on the Calculator corresponding to this node.
::mediapipe::Status ProcessNode(CalculatorContext* calculator_context);
// Initializes the node. The buffer_size_hint argument is
// set to the value specified in the graph proto for this field.
// input_stream_managers/output_stream_managers is expected to point to
// a contiguous flat array with Input/OutputStreamManagers corresponding
// to the input/output stream indexes in validated_graph.
// output_side_packets is expected to point to a contiguous flat array with
// OutputSidePacketImpls corresponding to the output side packet indexes in
// validated_graph.
::mediapipe::Status Initialize(
const ValidatedGraphConfig* validated_graph, int node_id,
InputStreamManager* input_stream_managers,
OutputStreamManager* output_stream_managers,
OutputSidePacketImpl* output_side_packets, int* buffer_size_hint,
std::shared_ptr<ProfilingContext> profiling_context);
// Sets up the node at the beginning of CalculatorGraph::Run(). This
// method is executed before any OpenNode() calls to the nodes
// within a CalculatorGraph. Creates a Calculator, and clears the
// input queues. Sets the callback to run when the node wants to
// schedule itself for later processing (in the order determined by
// the priority queue). ready_for_open_callback is called when OpenNode()
// can be scheduled. source_node_opened_callback is called when a source
// node is opened. schedule_callback is passed to the InputStreamHandler
// and is called each time a new invocation can be scheduled.
::mediapipe::Status PrepareForRun(
const std::map<std::string, Packet>& all_side_packets,
const std::map<std::string, Packet>& service_packets,
std::function<void()> ready_for_open_callback,
std::function<void()> source_node_opened_callback,
std::function<void(CalculatorContext*)> schedule_callback,
std::function<void(::mediapipe::Status)> error_callback,
CounterFactory* counter_factory) LOCKS_EXCLUDED(status_mutex_);
// Opens the node.
::mediapipe::Status OpenNode() LOCKS_EXCLUDED(status_mutex_);
// Called when a source node's layer becomes active.
void ActivateNode() LOCKS_EXCLUDED(status_mutex_);
// Cleans up the node after the CalculatorGraph has been run. Deletes
// the Calculator managed by this node. graph_status is the status of
// the graph run.
void CleanupAfterRun(const ::mediapipe::Status& graph_status)
LOCKS_EXCLUDED(status_mutex_);
// Returns true iff PrepareForRun() has been called (and types verified).
bool Prepared() const LOCKS_EXCLUDED(status_mutex_);
// Returns true iff Open() has been called on the calculator.
bool Opened() const LOCKS_EXCLUDED(status_mutex_);
// Returns true iff a source calculator's layer is active.
bool Active() const LOCKS_EXCLUDED(status_mutex_);
// Returns true iff Close() has been called on the calculator.
bool Closed() const LOCKS_EXCLUDED(status_mutex_);
// Returns true iff this is a source node.
//
// A source node has no input streams but has at least one output stream. A
// node with no input streams and no output streams is essentially a packet
// generator and is not a source node.
bool IsSource() const {
return input_stream_handler_->NumInputStreams() == 0 &&
output_stream_handler_->NumOutputStreams() != 0;
}
int source_layer() const { return source_layer_; }
// Checks if the node can be scheduled; if so, increases current_in_flight_
// and returns true; otherwise, returns false.
// If true is returned, the scheduler must commit to executing the node, and
// then call EndScheduling when finished running it.
// If false is returned, the scheduler must not execute the node.
// This method is thread-safe.
bool TryToBeginScheduling() LOCKS_EXCLUDED(status_mutex_);
// Subtracts one from current_in_flight_ to allow a new invocation to be
// scheduled. Then, it checks scheduling_state_ and invokes SchedulingLoop()
// if necessary. This method is thread-safe.
// TODO: this could be done implicitly by the call to ProcessNode
// or CloseNode.
void EndScheduling() LOCKS_EXCLUDED(status_mutex_);
// Returns true if OpenNode() can be scheduled.
bool ReadyForOpen() const LOCKS_EXCLUDED(status_mutex_);
// Called by the InputStreamHandler when all the input stream headers
// become available.
void InputStreamHeadersReady() LOCKS_EXCLUDED(status_mutex_);
// Called by the InputSidePacketHandler when all the input side packets
// become available.
void InputSidePacketsReady() LOCKS_EXCLUDED(status_mutex_);
// Checks scheduling_state_, and then invokes SchedulingLoop() if necessary.
// This method is thread-safe.
void CheckIfBecameReady() LOCKS_EXCLUDED(status_mutex_);
// Called by SchedulerQueue when a node is opened.
void NodeOpened() LOCKS_EXCLUDED(status_mutex_);
// Returns whether this is a GPU calculator node.
bool UsesGpu() const { return uses_gpu_; }
// Returns the scheduler queue the node is assigned to.
internal::SchedulerQueue* GetSchedulerQueue() const {
return scheduler_queue_;
}
// Sets the scheduler queue the node is assigned to.
void SetSchedulerQueue(internal::SchedulerQueue* queue) {
scheduler_queue_ = queue;
}
// Sets callbacks in the scheduler that should be invoked when an input queue
// becomes full/non-full.
void SetQueueSizeCallbacks(
InputStreamManager::QueueSizeCallback becomes_full_callback,
InputStreamManager::QueueSizeCallback becomes_not_full_callback);
// Sets each of this node's input streams to use the specified
// max_queue_size to trigger callbacks.
void SetMaxInputStreamQueueSize(int max_queue_size);
// Closes the node's calculator and input and output streams.
// graph_status is the current status of the graph run. graph_run_ended
// indicates whether the graph run has ended.
::mediapipe::Status CloseNode(const ::mediapipe::Status& graph_status,
bool graph_run_ended)
LOCKS_EXCLUDED(status_mutex_);
// Returns a pointer to the default calculator context that is used for
// sequential execution. A source node should always reuse its default
// calculator context.
CalculatorContext* GetDefaultCalculatorContext() const {
return calculator_context_manager_.GetDefaultCalculatorContext();
}
const CalculatorState& GetCalculatorState() const {
return *calculator_state_;
}
private:
// Sets up the output side packets from the master flat array.
::mediapipe::Status InitializeOutputSidePackets(
const PacketTypeSet& output_side_packet_types,
OutputSidePacketImpl* output_side_packets);
// Connects the input side packets as mirrors on the output side packets.
// Output side packets are looked up in the master flat array which is
// provided.
::mediapipe::Status InitializeInputSidePackets(
OutputSidePacketImpl* output_side_packets);
// Sets up the output streams from the master flat array.
::mediapipe::Status InitializeOutputStreams(
OutputStreamManager* output_stream_managers);
// Sets up the input streams and connects them as mirrors on the
// output streams. Both input streams and output streams are looked
// up in the master flat arrays which are provided.
::mediapipe::Status InitializeInputStreams(
InputStreamManager* input_stream_managers,
OutputStreamManager* output_stream_managers);
::mediapipe::Status InitializeInputStreamHandler(
const InputStreamHandlerConfig& handler_config,
const PacketTypeSet& input_stream_types);
::mediapipe::Status InitializeOutputStreamHandler(
const OutputStreamHandlerConfig& handler_config,
const PacketTypeSet& output_stream_types);
// Connects the input/output stream shards in the given calculator context to
// the input/output streams of the node.
::mediapipe::Status ConnectShardsToStreams(
CalculatorContext* calculator_context);
// The general scheduling logic shared by EndScheduling() and
// CheckIfBecameReady().
// Inside the function, a while loop keeps preparing CalculatorContexts and
// scheduling the node until 1) the node becomes not ready or 2) the max
// number of in flight invocations is reached. It also attempts to propagate
// the latest input timestamp bound if no invocations can be scheduled.
void SchedulingLoop();
// Closes the input and output streams.
void CloseInputStreams() LOCKS_EXCLUDED(status_mutex_);
void CloseOutputStreams(OutputStreamShardSet* outputs)
LOCKS_EXCLUDED(status_mutex_);
// Get a std::string describing the input streams.
std::string DebugInputStreamNames() const;
// The calculator.
std::unique_ptr<CalculatorBase> calculator_;
// Keeps data which a Calculator subclass needs access to.
std::unique_ptr<CalculatorState> calculator_state_;
int node_id_ = -1;
std::string name_; // Optional user-defined name
// Name of the executor which the node will execute on. If empty, the node
// will execute on the default executor.
std::string executor_;
// The layer a source calculator operates on.
int source_layer_ = 0;
// The status of the current Calculator that this CalculatorNode
// is wrapping. kStateActive is currently used only for source nodes.
enum NodeStatus {
kStateUninitialized = 0,
kStatePrepared = 1,
kStateOpened = 2,
kStateActive = 3,
kStateClosed = 4
};
NodeStatus status_ GUARDED_BY(status_mutex_){kStateUninitialized};
// The max number of invocations that can be scheduled in parallel.
int max_in_flight_ = 1;
// The following two variables are used for the concurrency control of node
// scheduling.
//
// The number of invocations that are scheduled but not finished.
int current_in_flight_ GUARDED_BY(status_mutex_) = 0;
// SchedulingState incidates the current state of the node scheduling process.
// There are four possible transitions:
// (a) From kIdle to kScheduling.
// Any thread that makes this transition becomes the scheduling thread and
// will be responsible for preparing and scheduling all possible invocations.
// (b) From kScheduling to kSchedulingPending.
// Any thread, except the scheduling thread, can make this transition.
// kSchedulingPending indicates that some recent changes require the
// scheduling thread to recheck the node readiness after current scheduling
// iteration.
// (c) From kSchedulingPending to kScheduling.
// Made by the scheduling thread to indicate that it has already caught up
// with all the recent changes that can affect node readiness.
// (d) From kScheduling to kIdle. Made by the scheduling thread when there is
// no more scheduling work to be done.
enum SchedulingState {
kIdle = 0, //
kScheduling = 1, //
kSchedulingPending = 2
};
SchedulingState scheduling_state_ GUARDED_BY(status_mutex_) = kIdle;
std::function<void()> ready_for_open_callback_;
std::function<void()> source_node_opened_callback_;
bool input_stream_headers_ready_called_ GUARDED_BY(status_mutex_) = false;
bool input_side_packets_ready_called_ GUARDED_BY(status_mutex_) = false;
bool input_stream_headers_ready_ GUARDED_BY(status_mutex_) = false;
bool input_side_packets_ready_ GUARDED_BY(status_mutex_) = false;
// Owns and manages all CalculatorContext objects.
CalculatorContextManager calculator_context_manager_;
std::shared_ptr<ProfilingContext> profiling_context_;
// Mutex for node status.
mutable absl::Mutex status_mutex_;
// Manages the set of input side packets.
InputSidePacketHandler input_side_packet_handler_;
// Collection of all OutputSidePacket objects.
std::unique_ptr<OutputSidePacketSet> output_side_packets_;
std::unique_ptr<InputStreamHandler> input_stream_handler_;
std::unique_ptr<OutputStreamHandler> output_stream_handler_;
// Whether this is a GPU calculator.
bool uses_gpu_ = false;
// True if CleanupAfterRun() needs to call CloseNode().
bool needs_to_close_ = false;
internal::SchedulerQueue* scheduler_queue_ = nullptr;
const ValidatedGraphConfig* validated_graph_ = nullptr;
};
} // namespace mediapipe
#endif // MEDIAPIPE_FRAMEWORK_CALCULATOR_NODE_H_
+575
View File
@@ -0,0 +1,575 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/calculator_node.h"
#include <unistd.h>
#include <memory>
#include "absl/memory/memory.h"
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/port/gmock.h"
#include "mediapipe/framework/port/gtest.h"
#include "mediapipe/framework/port/logging.h"
#include "mediapipe/framework/port/parse_text_proto.h"
#include "mediapipe/framework/port/status.h"
#include "mediapipe/framework/port/status_macros.h"
#include "mediapipe/framework/port/status_matchers.h"
namespace mediapipe {
namespace {
class CountCalculator : public CalculatorBase {
public:
CountCalculator() { ++num_constructed_; }
~CountCalculator() override { ++num_destroyed_; }
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
++num_fill_expectations_;
cc->Inputs().Get(cc->Inputs().BeginId()).Set<int>();
cc->Outputs().Get(cc->Outputs().BeginId()).Set<int>();
cc->InputSidePackets().Get(cc->InputSidePackets().BeginId()).Set<int>();
return ::mediapipe::OkStatus();
}
::mediapipe::Status Open(CalculatorContext* cc) override {
++num_open_;
// Simulate doing nontrivial work to ensure that the time spent in the
// method will register on streamz each time it is called.
usleep(100);
return ::mediapipe::OkStatus();
}
::mediapipe::Status Process(CalculatorContext* cc) override {
++num_process_;
int input_stream_int = cc->Inputs().Get(cc->Inputs().BeginId()).Get<int>();
int side_packet_int =
cc->InputSidePackets().Get(cc->InputSidePackets().BeginId()).Get<int>();
cc->Outputs()
.Get(cc->Outputs().BeginId())
.AddPacket(MakePacket<int>(input_stream_int + side_packet_int)
.At(cc->InputTimestamp()));
// Simulate doing nontrivial work to ensure that the time spent in the
// method will register on streamz each time it is called.
usleep(100);
return ::mediapipe::OkStatus();
}
::mediapipe::Status Close(CalculatorContext* cc) override {
++num_close_;
// Simulate doing nontrivial work to ensure that the time spent in the
// method will register on streamz each time it is called.
usleep(100);
return ::mediapipe::OkStatus();
}
static int num_constructed_;
static int num_fill_expectations_;
static int num_open_;
static int num_process_;
static int num_close_;
static int num_destroyed_;
};
REGISTER_CALCULATOR(CountCalculator);
int CountCalculator::num_constructed_ = 0;
int CountCalculator::num_fill_expectations_ = 0;
int CountCalculator::num_open_ = 0;
int CountCalculator::num_process_ = 0;
int CountCalculator::num_close_ = 0;
int CountCalculator::num_destroyed_ = 0;
void SourceNodeOpenedNoOp() {}
void CheckFail(const ::mediapipe::Status& status) {
LOG(FATAL) << "The test triggered the error callback with status: " << status;
}
class CalculatorNodeTest : public ::testing::Test {
public:
void ReadyForOpen(int* count) { ++(*count); }
void Notification(CalculatorContext* cc, int* count) {
CHECK(cc);
cc_ = cc;
++(*count);
}
protected:
void InitializeEnvironment(bool use_tags) {
CountCalculator::num_constructed_ = 0;
CountCalculator::num_fill_expectations_ = 0;
CountCalculator::num_open_ = 0;
CountCalculator::num_process_ = 0;
CountCalculator::num_close_ = 0;
CountCalculator::num_destroyed_ = 0;
std::string first_two_nodes_string =
"node {\n" // Node index 0
" calculator: \"SidePacketsToStreamsCalculator\"\n"
" input_side_packet: \"input_b\"\n" // Input side packet index 0
" output_stream: \"unused_stream\"\n" // Output stream 0
"}\n"
"node {\n" // Node index 1
" calculator: \"PassThroughCalculator\"\n"
" input_stream: \"unused_stream\"\n" // Input stream index 0
" output_stream: \"stream_a\"\n" // Output stream index 1
" input_side_packet: \"input_a\"\n" // Input side packet index 1
" input_side_packet: \"input_b\"\n" // Input side packet index 2
"}\n";
CalculatorGraphConfig graph_config;
// Add the test for the node under test.
if (use_tags) {
graph_config = ::mediapipe::ParseTextProtoOrDie<CalculatorGraphConfig>(
first_two_nodes_string +
"node {\n" // Node index 2
" calculator: \"CountCalculator\"\n"
" input_stream: \"INPUT_TAG:stream_a\"\n" // Input stream index 1
" output_stream: \"OUTPUT_TAG:stream_b\"\n" // Output stream index 2
// Input side packet index 3
" input_side_packet: \"INPUT_SIDE_PACKET_TAG:input_a\"\n"
"}\n");
} else {
graph_config = ::mediapipe::ParseTextProtoOrDie<CalculatorGraphConfig>(
first_two_nodes_string +
"node {\n" // Node index 2
" calculator: \"CountCalculator\"\n"
" input_stream: \"stream_a\"\n" // Input stream index 1
" output_stream: \"stream_b\"\n" // Output stream index 2
" input_side_packet: \"input_a\"\n" // Input side packet index 3
"}\n");
}
MEDIAPIPE_CHECK_OK(validated_graph_.Initialize(graph_config));
MEDIAPIPE_CHECK_OK(InitializeStreams());
input_side_packets_.emplace("input_a", Adopt(new int(42)));
input_side_packets_.emplace("input_b", Adopt(new int(42)));
node_.reset(new CalculatorNode());
MEDIAPIPE_ASSERT_OK(node_->Initialize(
&validated_graph_, 2, input_stream_managers_.get(),
output_stream_managers_.get(), output_side_packets_.get(),
&buffer_size_hint_, graph_profiler_));
}
::mediapipe::Status PrepareNodeForRun() {
return node_->PrepareForRun( //
input_side_packets_, //
service_packets_, //
std::bind(&CalculatorNodeTest::ReadyForOpen, //
this, //
&ready_for_open_count_), //
SourceNodeOpenedNoOp, //
std::bind(&CalculatorNodeTest::Notification, //
this, std::placeholders::_1, //
&schedule_count_), //
CheckFail, //
nullptr);
}
::mediapipe::Status InitializeStreams() {
// START OF: code is copied from
// CalculatorGraph::InitializePacketGeneratorGraph.
// Create and initialize the output side packets.
output_side_packets_ = absl::make_unique<OutputSidePacketImpl[]>(
validated_graph_.OutputSidePacketInfos().size());
for (int index = 0; index < validated_graph_.OutputSidePacketInfos().size();
++index) {
const EdgeInfo& edge_info =
validated_graph_.OutputSidePacketInfos()[index];
RETURN_IF_ERROR(output_side_packets_[index].Initialize(
edge_info.name, edge_info.packet_type));
}
// END OF: code is copied from
// CalculatorGraph::InitializePacketGeneratorGraph.
// START OF: code is copied from CalculatorGraph::InitializeStreams.
// Create and initialize the input streams.
input_stream_managers_.reset(
new InputStreamManager[validated_graph_.InputStreamInfos().size()]);
for (int index = 0; index < validated_graph_.InputStreamInfos().size();
++index) {
const EdgeInfo& edge_info = validated_graph_.InputStreamInfos()[index];
RETURN_IF_ERROR(input_stream_managers_[index].Initialize(
edge_info.name, edge_info.packet_type, edge_info.back_edge));
}
// Create and initialize the output streams.
output_stream_managers_.reset(
new OutputStreamManager[validated_graph_.OutputStreamInfos().size()]);
for (int index = 0; index < validated_graph_.OutputStreamInfos().size();
++index) {
const EdgeInfo& edge_info = validated_graph_.OutputStreamInfos()[index];
RETURN_IF_ERROR(output_stream_managers_[index].Initialize(
edge_info.name, edge_info.packet_type));
}
// END OF: code is copied from CalculatorGraph::InitializeStreams.
stream_a_manager_ = &output_stream_managers_[1];
stream_b_manager_ = &output_stream_managers_[2];
return ::mediapipe::OkStatus();
}
virtual void SimulateParentOpenNode() { stream_a_manager_->LockIntroData(); }
virtual void TestCleanupAfterRunTwice();
std::map<std::string, Packet> input_side_packets_;
std::map<std::string, Packet> service_packets_;
std::unique_ptr<InputStreamManager[]> input_stream_managers_;
std::unique_ptr<OutputStreamManager[]> output_stream_managers_;
std::unique_ptr<OutputSidePacketImpl[]> output_side_packets_;
// A pointer to the output stream manager for stream_a.
// An alias for &output_stream_managers_[1].
OutputStreamManager* stream_a_manager_;
// A pointer to the output stream manager for stream_b.
// An alias for &output_stream_managers_[2].
OutputStreamManager* stream_b_manager_;
std::unique_ptr<CalculatorNode> node_;
ValidatedGraphConfig validated_graph_;
std::shared_ptr<ProfilingContext> graph_profiler_ =
std::make_shared<ProfilingContext>();
int ready_for_open_count_ = 0;
int schedule_count_ = 0;
int buffer_size_hint_ = -1;
// Stores the CalculatorContext passed to the ready_callback_ of node_, and we
// pass this to node_->ProcessNode().
CalculatorContext* cc_;
};
TEST_F(CalculatorNodeTest, Initialize) {
InitializeEnvironment(/*use_tags=*/false);
EXPECT_EQ(2, node_->Id());
EXPECT_THAT(node_->DebugName(),
::testing::AllOf(::testing::HasSubstr("CountCalculator"),
::testing::HasSubstr("stream_b")));
EXPECT_FALSE(node_->Prepared());
EXPECT_FALSE(node_->Opened());
EXPECT_FALSE(node_->Closed());
EXPECT_EQ(0, CountCalculator::num_constructed_);
EXPECT_EQ(1, CountCalculator::num_fill_expectations_);
EXPECT_EQ(0, CountCalculator::num_open_);
EXPECT_EQ(0, CountCalculator::num_process_);
EXPECT_EQ(0, CountCalculator::num_close_);
EXPECT_EQ(0, CountCalculator::num_destroyed_);
}
TEST_F(CalculatorNodeTest, PrepareForRun) {
InitializeEnvironment(/*use_tags=*/false);
MEDIAPIPE_ASSERT_OK(PrepareNodeForRun());
EXPECT_TRUE(node_->Prepared());
EXPECT_FALSE(node_->Opened());
EXPECT_FALSE(node_->Closed());
EXPECT_EQ(0, ready_for_open_count_);
EXPECT_EQ(0, schedule_count_);
EXPECT_EQ(1, CountCalculator::num_constructed_);
EXPECT_EQ(1, CountCalculator::num_fill_expectations_);
EXPECT_EQ(0, CountCalculator::num_open_);
EXPECT_EQ(0, CountCalculator::num_process_);
EXPECT_EQ(0, CountCalculator::num_close_);
EXPECT_EQ(0, CountCalculator::num_destroyed_);
}
TEST_F(CalculatorNodeTest, Open) {
InitializeEnvironment(/*use_tags=*/false);
MEDIAPIPE_ASSERT_OK(PrepareNodeForRun());
EXPECT_EQ(0, ready_for_open_count_);
SimulateParentOpenNode();
MEDIAPIPE_EXPECT_OK(node_->OpenNode());
EXPECT_TRUE(node_->Prepared());
EXPECT_TRUE(node_->Opened());
EXPECT_FALSE(node_->Closed());
// Nodes are not immediately scheduled upon opening.
EXPECT_EQ(0, schedule_count_);
EXPECT_EQ(1, CountCalculator::num_constructed_);
EXPECT_EQ(1, CountCalculator::num_fill_expectations_);
EXPECT_EQ(1, CountCalculator::num_open_);
EXPECT_EQ(0, CountCalculator::num_process_);
EXPECT_EQ(0, CountCalculator::num_close_);
EXPECT_EQ(0, CountCalculator::num_destroyed_);
}
TEST_F(CalculatorNodeTest, Process) {
InitializeEnvironment(/*use_tags=*/false);
MEDIAPIPE_ASSERT_OK(PrepareNodeForRun());
SimulateParentOpenNode();
MEDIAPIPE_EXPECT_OK(node_->OpenNode());
OutputStreamShard stream_a_shard;
stream_a_shard.SetSpec(stream_a_manager_->Spec());
stream_a_shard.Add(new int(1), Timestamp(1));
stream_a_manager_->PropagateUpdatesToMirrors(Timestamp(2), &stream_a_shard);
EXPECT_EQ(1, schedule_count_);
// Expects that a CalculatorContext has been prepared.
EXPECT_NE(nullptr, cc_);
EXPECT_TRUE(node_->TryToBeginScheduling());
MEDIAPIPE_EXPECT_OK(node_->ProcessNode(cc_));
cc_ = nullptr;
node_->EndScheduling();
EXPECT_EQ(1, schedule_count_);
// Expects that no CalculatorContext is prepared by EndScheduling().
EXPECT_EQ(nullptr, cc_);
EXPECT_TRUE(node_->Prepared());
EXPECT_TRUE(node_->Opened());
EXPECT_FALSE(node_->Closed());
EXPECT_EQ(1, schedule_count_);
EXPECT_EQ(1, CountCalculator::num_constructed_);
EXPECT_EQ(1, CountCalculator::num_fill_expectations_);
EXPECT_EQ(1, CountCalculator::num_open_);
EXPECT_EQ(1, CountCalculator::num_process_);
EXPECT_EQ(0, CountCalculator::num_close_);
EXPECT_EQ(0, CountCalculator::num_destroyed_);
}
TEST_F(CalculatorNodeTest, ProcessSeveral) {
InitializeEnvironment(/*use_tags=*/false);
MEDIAPIPE_ASSERT_OK(PrepareNodeForRun());
SimulateParentOpenNode();
MEDIAPIPE_EXPECT_OK(node_->OpenNode());
OutputStreamShard stream_a_shard;
stream_a_shard.SetSpec(stream_a_manager_->Spec());
stream_a_shard.Add(new int(1), Timestamp(1));
stream_a_manager_->PropagateUpdatesToMirrors(Timestamp(2), &stream_a_shard);
EXPECT_EQ(1, schedule_count_);
EXPECT_TRUE(node_->TryToBeginScheduling());
EXPECT_NE(nullptr, cc_);
MEDIAPIPE_EXPECT_OK(node_->ProcessNode(cc_));
node_->EndScheduling();
EXPECT_EQ(1, schedule_count_);
stream_a_manager_->ResetShard(&stream_a_shard);
stream_a_shard.Add(new int(2), Timestamp(4));
stream_a_shard.Add(new int(3), Timestamp(8));
stream_a_manager_->PropagateUpdatesToMirrors(Timestamp(9), &stream_a_shard);
// The packet at Timestamp 8 is left in the input queue.
EXPECT_EQ(2, schedule_count_);
EXPECT_TRUE(node_->TryToBeginScheduling());
// Expects that a CalculatorContext has been prepared.
EXPECT_NE(nullptr, cc_);
MEDIAPIPE_EXPECT_OK(node_->ProcessNode(cc_));
node_->EndScheduling();
EXPECT_EQ(3, schedule_count_);
EXPECT_TRUE(node_->TryToBeginScheduling());
stream_a_manager_->ResetShard(&stream_a_shard);
stream_a_shard.Add(new int(4), Timestamp(16));
stream_a_manager_->PropagateUpdatesToMirrors(Timestamp(17), &stream_a_shard);
// The packet at Timestamp 16 is left in the input queue.
EXPECT_EQ(3, schedule_count_);
// The max parallelism is already reached.
EXPECT_FALSE(node_->TryToBeginScheduling());
EXPECT_NE(nullptr, cc_);
MEDIAPIPE_EXPECT_OK(node_->ProcessNode(cc_));
node_->EndScheduling();
EXPECT_EQ(4, schedule_count_);
EXPECT_TRUE(node_->TryToBeginScheduling());
EXPECT_NE(nullptr, cc_);
MEDIAPIPE_EXPECT_OK(node_->ProcessNode(cc_));
cc_ = nullptr;
node_->EndScheduling();
// Expects that no CalculatorContext is prepared by EndScheduling().
EXPECT_EQ(nullptr, cc_);
EXPECT_EQ(4, schedule_count_);
EXPECT_TRUE(node_->Prepared());
EXPECT_TRUE(node_->Opened());
EXPECT_FALSE(node_->Closed());
EXPECT_EQ(1, CountCalculator::num_constructed_);
EXPECT_EQ(1, CountCalculator::num_fill_expectations_);
EXPECT_EQ(1, CountCalculator::num_open_);
EXPECT_EQ(4, CountCalculator::num_process_);
EXPECT_EQ(0, CountCalculator::num_close_);
EXPECT_EQ(0, CountCalculator::num_destroyed_);
}
TEST_F(CalculatorNodeTest, Close) {
InitializeEnvironment(/*use_tags=*/false);
MEDIAPIPE_ASSERT_OK(PrepareNodeForRun());
SimulateParentOpenNode();
MEDIAPIPE_EXPECT_OK(node_->OpenNode());
OutputStreamShard stream_a_shard;
stream_a_shard.SetSpec(stream_a_manager_->Spec());
stream_a_shard.Add(new int(1), Timestamp(1));
stream_a_manager_->PropagateUpdatesToMirrors(Timestamp(2), &stream_a_shard);
EXPECT_TRUE(node_->TryToBeginScheduling());
stream_a_manager_->Close();
// The max parallelism is already reached.
EXPECT_FALSE(node_->TryToBeginScheduling());
MEDIAPIPE_EXPECT_OK(node_->ProcessNode(cc_));
node_->EndScheduling();
EXPECT_TRUE(node_->TryToBeginScheduling());
MEDIAPIPE_EXPECT_OK(node_->ProcessNode(cc_));
EXPECT_TRUE(node_->Closed());
EXPECT_EQ(2, schedule_count_);
node_->EndScheduling();
EXPECT_TRUE(node_->Prepared());
EXPECT_TRUE(node_->Opened());
EXPECT_TRUE(node_->Closed());
EXPECT_EQ(2, schedule_count_);
EXPECT_EQ(1, CountCalculator::num_constructed_);
EXPECT_EQ(1, CountCalculator::num_fill_expectations_);
EXPECT_EQ(1, CountCalculator::num_open_);
EXPECT_EQ(1, CountCalculator::num_process_);
EXPECT_EQ(1, CountCalculator::num_close_);
EXPECT_EQ(0, CountCalculator::num_destroyed_);
}
TEST_F(CalculatorNodeTest, CleanupAfterRun) {
InitializeEnvironment(/*use_tags=*/false);
MEDIAPIPE_ASSERT_OK(PrepareNodeForRun());
SimulateParentOpenNode();
MEDIAPIPE_EXPECT_OK(node_->OpenNode());
OutputStreamShard stream_a_shard;
stream_a_shard.SetSpec(stream_a_manager_->Spec());
stream_a_shard.Add(new int(1), Timestamp(1));
stream_a_manager_->PropagateUpdatesToMirrors(Timestamp(2), &stream_a_shard);
EXPECT_TRUE(node_->TryToBeginScheduling());
stream_a_manager_->Close();
// The max parallelism is already reached.
EXPECT_FALSE(node_->TryToBeginScheduling());
MEDIAPIPE_EXPECT_OK(node_->ProcessNode(cc_));
node_->EndScheduling();
// Call ProcessNode again for the node to see the end of the stream.
EXPECT_TRUE(node_->TryToBeginScheduling());
MEDIAPIPE_EXPECT_OK(node_->ProcessNode(cc_));
node_->EndScheduling();
// The max parallelism is already reached.
EXPECT_FALSE(node_->TryToBeginScheduling());
node_->CleanupAfterRun(::mediapipe::OkStatus());
EXPECT_FALSE(node_->Prepared());
EXPECT_FALSE(node_->Opened());
EXPECT_FALSE(node_->Closed());
EXPECT_EQ(2, schedule_count_);
EXPECT_EQ(1, CountCalculator::num_constructed_);
EXPECT_EQ(1, CountCalculator::num_fill_expectations_);
EXPECT_EQ(1, CountCalculator::num_open_);
EXPECT_EQ(1, CountCalculator::num_process_);
EXPECT_EQ(1, CountCalculator::num_close_);
EXPECT_EQ(1, CountCalculator::num_destroyed_);
}
void CalculatorNodeTest::TestCleanupAfterRunTwice() {
MEDIAPIPE_ASSERT_OK(PrepareNodeForRun());
SimulateParentOpenNode();
MEDIAPIPE_EXPECT_OK(node_->OpenNode());
OutputStreamShard stream_a_shard;
stream_a_shard.SetSpec(stream_a_manager_->Spec());
stream_a_shard.Add(new int(1), Timestamp(1));
stream_a_manager_->PropagateUpdatesToMirrors(Timestamp(2), &stream_a_shard);
EXPECT_TRUE(node_->TryToBeginScheduling());
stream_a_manager_->Close();
// The max parallelism is already reached.
EXPECT_FALSE(node_->TryToBeginScheduling());
MEDIAPIPE_EXPECT_OK(node_->ProcessNode(cc_));
node_->EndScheduling();
// We should get Timestamp::Done here.
EXPECT_TRUE(node_->TryToBeginScheduling());
MEDIAPIPE_EXPECT_OK(node_->ProcessNode(cc_));
node_->EndScheduling();
node_->CleanupAfterRun(::mediapipe::OkStatus());
stream_a_manager_->PrepareForRun(nullptr);
MEDIAPIPE_ASSERT_OK(PrepareNodeForRun());
SimulateParentOpenNode();
MEDIAPIPE_EXPECT_OK(node_->OpenNode());
stream_a_manager_->ResetShard(&stream_a_shard);
stream_a_shard.Add(new int(2), Timestamp(4));
stream_a_shard.Add(new int(3), Timestamp(8));
stream_a_manager_->PropagateUpdatesToMirrors(Timestamp(9), &stream_a_shard);
EXPECT_TRUE(node_->TryToBeginScheduling());
stream_a_manager_->Close();
EXPECT_FALSE(node_->TryToBeginScheduling());
MEDIAPIPE_EXPECT_OK(node_->ProcessNode(cc_));
node_->EndScheduling();
EXPECT_TRUE(node_->TryToBeginScheduling());
MEDIAPIPE_EXPECT_OK(node_->ProcessNode(cc_));
node_->EndScheduling();
// We should get Timestamp::Done here.
EXPECT_TRUE(node_->TryToBeginScheduling());
MEDIAPIPE_EXPECT_OK(node_->ProcessNode(cc_));
node_->EndScheduling();
// The max parallelism is already reached.
EXPECT_FALSE(node_->TryToBeginScheduling());
node_->CleanupAfterRun(::mediapipe::OkStatus());
EXPECT_FALSE(node_->Prepared());
EXPECT_FALSE(node_->Opened());
EXPECT_FALSE(node_->Closed());
EXPECT_EQ(5, schedule_count_);
EXPECT_EQ(2, CountCalculator::num_constructed_);
EXPECT_EQ(1, CountCalculator::num_fill_expectations_);
EXPECT_EQ(2, CountCalculator::num_open_);
EXPECT_EQ(3, CountCalculator::num_process_);
EXPECT_EQ(2, CountCalculator::num_close_);
EXPECT_EQ(2, CountCalculator::num_destroyed_);
}
TEST_F(CalculatorNodeTest, CleanupAfterRunTwice) {
InitializeEnvironment(/*use_tags=*/false);
TestCleanupAfterRunTwice();
}
TEST_F(CalculatorNodeTest, CleanupAfterRunTwiceWithTags) {
InitializeEnvironment(/*use_tags=*/true);
TestCleanupAfterRunTwice();
}
} // namespace
} // namespace mediapipe
@@ -0,0 +1,44 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Forked from mediapipe/framework/calculator.proto.
// The forked proto must remain identical to the original proto and should be
// ONLY used by mediapipe open source project.
syntax = "proto2";
package mediapipe;
option java_package = "com.google.mediapipe.proto";
option java_outer_classname = "CalculatorOptionsProto";
// Options for Calculators. Each Calculator implementation should
// have its own options proto, which should look like this:
//
// message MyCalculatorOptions {
// extend CalculatorOptions {
// optional MyCalculatorOptions ext = <unique id, e.g. the CL#>;
// }
// optional string field_needed_by_my_calculator = 1;
// optional int32 another_field = 2;
// // etc
// }
message CalculatorOptions {
// If true, this proto specifies a subset of field values,
// which should override corresponding field values.
// Deprecated in cl/228195782.
optional bool merge_fields = 1 [deprecated = true];
extensions 20000 to max;
}
@@ -0,0 +1,157 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Verifies the correctness of parallel execution.
// $ bazel build -c opt \
// mediapipe/framework/calculator_parallel_execution_test \
// --runs_per_test=100
//
// TODO: Add more tests to verify the correctness of parallel execution.
#include <memory>
#include <random>
#include <string>
#include <vector>
#include "absl/memory/memory.h"
#include "absl/synchronization/mutex.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/port/gmock.h"
#include "mediapipe/framework/port/gtest.h"
#include "mediapipe/framework/port/integral_types.h"
#include "mediapipe/framework/port/parse_text_proto.h"
#include "mediapipe/framework/port/status.h"
#include "mediapipe/framework/port/status_matchers.h"
namespace mediapipe {
namespace {
using RandomEngine = std::mt19937_64;
inline void BusySleep(absl::Duration duration) {
absl::Time start_time = absl::Now();
while (absl::Now() - start_time < duration) {
}
}
class SlowPlusOneCalculator : public CalculatorBase {
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
cc->Inputs().Index(0).Set<int>();
cc->Outputs().Index(0).Set<int>();
return ::mediapipe::OkStatus();
}
::mediapipe::Status Open(CalculatorContext* cc) override {
cc->SetOffset(mediapipe::TimestampDiff(0));
return ::mediapipe::OkStatus();
}
::mediapipe::Status Process(CalculatorContext* cc) override {
if (cc->InputTimestamp().Value() % 4 == 0) {
return ::mediapipe::OkStatus();
}
RandomEngine random(testing::UnitTest::GetInstance()->random_seed());
std::uniform_int_distribution<> uniform_dist(0, 10);
BusySleep(absl::Milliseconds(90 + uniform_dist(random)));
cc->Outputs().Index(0).Add(new int(cc->Inputs().Index(0).Get<int>() + 1),
cc->InputTimestamp());
return ::mediapipe::OkStatus();
}
};
REGISTER_CALCULATOR(SlowPlusOneCalculator);
class ParallelExecutionTest : public testing::Test {
public:
void AddThreadSafeVectorSink(const Packet& packet) {
absl::WriterMutexLock lock(&output_packets_mutex_);
output_packets_.push_back(packet);
}
protected:
std::vector<Packet> output_packets_ GUARDED_BY(output_packets_mutex_);
absl::Mutex output_packets_mutex_;
};
TEST_F(ParallelExecutionTest, SlowPlusOneCalculatorsTest) {
CalculatorGraphConfig graph_config =
::mediapipe::ParseTextProtoOrDie<CalculatorGraphConfig>(R"(
input_stream: "input"
node {
calculator: "SlowPlusOneCalculator"
input_stream: "input"
output_stream: "first_calculator_output"
max_in_flight: 5
}
node {
calculator: "SlowPlusOneCalculator"
input_stream: "first_calculator_output"
output_stream: "output"
max_in_flight: 5
}
node {
calculator: "CallbackCalculator"
input_stream: "output"
input_side_packet: "CALLBACK:callback"
}
num_threads: 5
)");
// Starts MediaPipe graph.
CalculatorGraph graph(graph_config);
// Runs the graph twice.
for (int i = 0; i < 2; ++i) {
MEDIAPIPE_ASSERT_OK(graph.StartRun(
{{"callback", MakePacket<std::function<void(const Packet&)>>(std::bind(
&ParallelExecutionTest::AddThreadSafeVectorSink, this,
std::placeholders::_1))}}));
const int kTotalNums = 100;
int fail_count = 0;
for (int i = 0; i < kTotalNums; ++i) {
::mediapipe::Status status = graph.AddPacketToInputStream(
"input", Adopt(new int(i)).At(Timestamp(i)));
if (!status.ok()) {
++fail_count;
}
}
EXPECT_EQ(0, fail_count);
// Doesn't wait but just close the input stream.
MEDIAPIPE_ASSERT_OK(graph.CloseInputStream("input"));
// Waits properly via the API until the graph is done.
MEDIAPIPE_ASSERT_OK(graph.WaitUntilDone());
absl::ReaderMutexLock lock(&output_packets_mutex_);
ASSERT_EQ(kTotalNums - kTotalNums / 4, output_packets_.size());
int index = 1;
for (const Packet& packet : output_packets_) {
MEDIAPIPE_ASSERT_OK(packet.ValidateAsType<int>());
EXPECT_EQ(index + 2, packet.Get<int>());
EXPECT_EQ(Timestamp(index), packet.Timestamp());
if (++index % 4 == 0) {
++index;
}
}
output_packets_.clear();
}
}
} // namespace
} // namespace mediapipe
@@ -0,0 +1,191 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Forked from mediapipe/framework/calculator_profile.proto.
// The forked proto must remain identical to the original proto and should be
// ONLY used by mediapipe open source project.
syntax = "proto2";
package mediapipe;
import "mediapipe/framework/calculator.proto";
option java_package = "com.google.mediapipe.proto";
option java_outer_classname = "CalculatorProfileProto";
// Stores the profiling information.
//
// It is the responsibility of the user of this message to make sure the 'total'
// field and the interval information (num, size and count) are in a valid
// state and all get updated together.
//
// Each interval of the histogram is closed on the lower range and open on the
// higher end. An example histogram with interval_size=1000 and num_interval=3
// will have the following intervals:
// - First interval = [0, 1000)
// - Second interval = [1000, 2000)
// - Third interval = [2000, +inf)
//
// IMPORTANT: If You add any new field, update CalculatorProfiler::Reset()
// accordingly.
message TimeHistogram {
// Total time (in microseconds).
optional int64 total = 1 [default = 0];
// Size of the runtimes histogram intervals (in microseconds) to generate the
// histogram of the Process() time. The last interval extends to +inf.
optional int64 interval_size_usec = 2 [default = 1000000 /* 1 sec */];
// Number of intervals to generate the histogram of the Process() runtime.
optional int64 num_intervals = 3 [default = 1];
// Number of calls in each interval.
repeated int64 count = 4;
}
// Stores the profiling information of a stream.
message StreamProfile {
// Stream name.
optional string name = 1;
// If true, than this is a back edge input stream and won't be profiled.
optional bool back_edge = 2 [default = false];
// Total and histogram of the time that this stream took.
optional TimeHistogram latency = 3;
}
// Stores the profiling information for a calculator node.
// All the times are in microseconds.
message CalculatorProfile {
// The calculator name.
optional string name = 1;
// Total time the calculator spent on Open (in microseconds).
optional int64 open_runtime = 2 [default = 0];
// Total time the calculator spent on Close (in microseconds).
optional int64 close_runtime = 3 [default = 0];
// Total and histogram of the time that the calculator spent on the Process()
// (in microseconds).
optional TimeHistogram process_runtime = 4;
// Total and histogram of the time that the input latency, ie. difference
// between input timestamp and process call time.
// (in microseconds).
optional TimeHistogram process_input_latency = 5;
// Total and histogram of the time that the output latency, ie. difference
// between input timestamp and process finished time.
optional TimeHistogram process_output_latency = 6;
// Total and histogram of the time that input streams of this calculator took.
repeated StreamProfile input_stream_profiles = 7;
}
// Latency timing for recent mediapipe packets.
message GraphTrace {
// The timing for one packet across one packet stream.
message StreamTrace {
// The time at which the packet entered the stream.
optional int64 start_time = 1;
// The time at which the packet exited the stream.
optional int64 finish_time = 2;
// The identifying timetamp of the packet.
optional int64 packet_timestamp = 3;
// The index of the stream in the stream_name list.
optional int32 stream_id = 4;
// The address of the packet contents.
optional int64 packet_id = 5;
}
// The kind of event recorded.
enum EventType {
UNKNOWN = 0;
OPEN = 1;
PROCESS = 2;
CLOSE = 3;
NOT_READY = 4;
READY_FOR_PROCESS = 5;
READY_FOR_CLOSE = 6;
THROTTLED = 7;
UNTHROTTLED = 8;
CPU_TASK_USER = 9;
CPU_TASK_SYSTEM = 10;
GPU_TASK = 11;
DSP_TASK = 12;
TPU_TASK = 13;
GPU_CALIBRATION = 14;
}
// The timing for one packet set being processed at one caclulator node.
message CalculatorTrace {
// The index of the calculator node in the calculator_name list.
optional int32 node_id = 1;
// The input timestamp during Open, Process, or Close.
optional int64 input_timestamp = 2;
// The kind of event, 1=Open, 2=Process, 3=Close, etc.
optional EventType event_type = 3;
// The time at which the packets entered the caclulator node.
optional int64 start_time = 4;
// The time at which the packets exited the caclulator node.
optional int64 finish_time = 5;
// The timing data for each input packet.
repeated StreamTrace input_trace = 6;
// The identifying timetamp and stream_id for each output packet.
repeated StreamTrace output_trace = 7;
// An identifier for the current process thread.
optional int32 thread_id = 8;
}
// The time represented as 0 in the trace.
optional int64 base_time = 1;
// The timestamp represented as 0 in the trace.
optional int64 base_timestamp = 2;
// The list of calculator node names indexed by node id.
repeated string calculator_name = 3;
// The list of stream names indexed by stream id.
repeated string stream_name = 4;
// Recent packet timing informtion about each calculator node and stream.
repeated CalculatorTrace calculator_trace = 5;
}
// Latency events and summaries for recent mediapipe packets.
message GraphProfile {
// Recent packet timing informtion about each calculator node and stream.
repeated GraphTrace graph_trace = 1;
// Aggregated latency information about each calculator node.
repeated CalculatorProfile calculator_profiles = 2;
// The canonicalized calculator graph that is traced.
optional CalculatorGraphConfig config = 3;
}
+32
View File
@@ -0,0 +1,32 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Calculator registration.
#ifndef MEDIAPIPE_FRAMEWORK_CALCULATOR_REGISTRY_H_
#define MEDIAPIPE_FRAMEWORK_CALCULATOR_REGISTRY_H_
#include "mediapipe/framework/calculator_base.h"
#define REGISTER_CALCULATOR(name) \
REGISTER_FACTORY_FUNCTION_QUALIFIED(::mediapipe::CalculatorBaseRegistry, \
calculator_registration, name, \
absl::make_unique<name>); \
REGISTER_FACTORY_FUNCTION_QUALIFIED( \
::mediapipe::internal::StaticAccessToCalculatorBaseRegistry, \
access_registration, name, \
absl::make_unique< \
::mediapipe::internal::StaticAccessToCalculatorBaseTyped<name>>)
#endif // MEDIAPIPE_FRAMEWORK_CALCULATOR_REGISTRY_H_
@@ -0,0 +1,61 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/calculator_registry_util.h"
#include <algorithm>
#include <string>
#include "mediapipe/framework/collection.h"
#include "mediapipe/framework/collection_item_id.h"
#include "mediapipe/framework/packet_set.h"
#include "mediapipe/framework/port/status.h"
#include "mediapipe/framework/timestamp.h"
namespace mediapipe {
bool IsLegacyCalculator(const std::string& package_name,
const std::string& node_class) {
return false;
}
::mediapipe::Status VerifyCalculatorWithContract(
const std::string& package_name, const std::string& node_class,
CalculatorContract* contract) {
// A number of calculators use the non-CC methods on GlCalculatorHelper
// even though they are CalculatorBase-based.
ASSIGN_OR_RETURN(
auto static_access_to_calculator_base,
internal::StaticAccessToCalculatorBaseRegistry::CreateByNameInNamespace(
package_name, node_class),
_ << "Unable to find Calculator \"" << node_class << "\"");
RETURN_IF_ERROR(static_access_to_calculator_base->GetContract(contract))
.SetPrepend()
<< node_class << ": ";
return ::mediapipe::OkStatus();
}
::mediapipe::StatusOr<std::unique_ptr<CalculatorBase>> CreateCalculator(
const std::shared_ptr<tool::TagMap>& input_tag_map,
const std::shared_ptr<tool::TagMap>& output_tag_map,
const std::string& package_name, CalculatorState* calculator_state,
CalculatorContext* calculator_context) {
std::unique_ptr<CalculatorBase> calculator;
ASSIGN_OR_RETURN(calculator,
CalculatorBaseRegistry::CreateByNameInNamespace(
package_name, calculator_state->CalculatorType()));
return std::move(calculator);
}
} // namespace mediapipe
@@ -0,0 +1,46 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_FRAMEWORK_CALCULATOR_REGISTRY_UTIL_H_
#define MEDIAPIPE_FRAMEWORK_CALCULATOR_REGISTRY_UTIL_H_
#include <memory>
#include "mediapipe/framework/calculator_base.h"
#include "mediapipe/framework/calculator_context.h"
#include "mediapipe/framework/calculator_state.h"
#include "mediapipe/framework/port/status.h"
#include "mediapipe/framework/port/statusor.h"
#include "mediapipe/framework/tool/tag_map.h"
// Calculator registry util functions that supports both legacy Calculator API
// and CalculatorBase.
namespace mediapipe {
bool IsLegacyCalculator(const std::string& package_name,
const std::string& node_class);
::mediapipe::Status VerifyCalculatorWithContract(
const std::string& package_name, const std::string& node_class,
CalculatorContract* contract);
::mediapipe::StatusOr<std::unique_ptr<CalculatorBase>> CreateCalculator(
const std::shared_ptr<tool::TagMap>& input_tag_map,
const std::shared_ptr<tool::TagMap>& output_tag_map,
const std::string& package_name, CalculatorState* calculator_state,
CalculatorContext* calculator_context);
} // namespace mediapipe
#endif // MEDIAPIPE_FRAMEWORK_CALCULATOR_REGISTRY_UTIL_H_
+354
View File
@@ -0,0 +1,354 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Definitions for CalculatorRunner.
#include "mediapipe/framework/calculator_runner.h"
#include "absl/memory/memory.h"
#include "absl/strings/str_cat.h"
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/port/logging.h"
#include "mediapipe/framework/port/ret_check.h"
#include "mediapipe/framework/port/status.h"
namespace mediapipe {
const char CalculatorRunner::kSourcePrefix[] = "source_for_";
const char CalculatorRunner::kSinkPrefix[] = "sink_for_";
namespace {
// Calculator generating a stream with the given contents.
// Inputs: none
// Outputs: 1, with the contents provided via the input side packet.
// Input side packets: 1, pointing to CalculatorRunner::StreamContents.
class CalculatorRunnerSourceCalculator : public CalculatorBase {
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
cc->InputSidePackets()
.Index(0)
.Set<const CalculatorRunner::StreamContents*>();
cc->Outputs().Index(0).SetAny();
return ::mediapipe::OkStatus();
}
::mediapipe::Status Open(CalculatorContext* cc) override {
const auto* contents = cc->InputSidePackets()
.Index(0)
.Get<const CalculatorRunner::StreamContents*>();
// Set the header and packets of the output stream.
cc->Outputs().Index(0).SetHeader(contents->header);
for (const Packet& packet : contents->packets) {
cc->Outputs().Index(0).AddPacket(packet);
}
return ::mediapipe::OkStatus();
}
::mediapipe::Status Process(CalculatorContext* cc) override {
return tool::StatusStop();
}
};
REGISTER_CALCULATOR(CalculatorRunnerSourceCalculator);
// Calculator recording the contents of a stream.
// Inputs: 1, with the contents written to the input side packet.
// Outputs: none
// Input side packets: 1, pointing to CalculatorRunner::StreamContents.
class CalculatorRunnerSinkCalculator : public CalculatorBase {
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
cc->Inputs().Index(0).SetAny();
cc->InputSidePackets().Index(0).Set<CalculatorRunner::StreamContents*>();
return ::mediapipe::OkStatus();
}
::mediapipe::Status Open(CalculatorContext* cc) override {
contents_ = cc->InputSidePackets()
.Index(0)
.Get<CalculatorRunner::StreamContents*>();
contents_->header = cc->Inputs().Index(0).Header();
return ::mediapipe::OkStatus();
}
::mediapipe::Status Process(CalculatorContext* cc) override {
contents_->packets.push_back(cc->Inputs().Index(0).Value());
return ::mediapipe::OkStatus();
}
private:
CalculatorRunner::StreamContents* contents_ = nullptr;
};
REGISTER_CALCULATOR(CalculatorRunnerSinkCalculator);
} // namespace
CalculatorRunner::CalculatorRunner(
const CalculatorGraphConfig::Node& node_config) {
MEDIAPIPE_CHECK_OK(InitializeFromNodeConfig(node_config));
}
::mediapipe::Status CalculatorRunner::InitializeFromNodeConfig(
const CalculatorGraphConfig::Node& node_config) {
node_config_ = node_config;
if (node_config_.external_input_size() > 0) {
RET_CHECK_EQ(0, node_config_.input_side_packet_size())
<< "Only one of input_side_packet or (deprecated) external_input can "
"be set.";
node_config_.mutable_external_input()->Swap(
node_config_.mutable_input_side_packet());
}
ASSIGN_OR_RETURN(auto input_map,
tool::TagMap::Create(node_config_.input_stream()));
inputs_ = absl::make_unique<StreamContentsSet>(input_map);
ASSIGN_OR_RETURN(auto output_map,
tool::TagMap::Create(node_config_.output_stream()));
outputs_ = absl::make_unique<StreamContentsSet>(output_map);
ASSIGN_OR_RETURN(auto input_side_map,
tool::TagMap::Create(node_config_.input_side_packet()));
input_side_packets_ = absl::make_unique<PacketSet>(input_side_map);
ASSIGN_OR_RETURN(auto output_side_map,
tool::TagMap::Create(node_config_.output_side_packet()));
output_side_packets_ = absl::make_unique<PacketSet>(output_side_map);
return ::mediapipe::OkStatus();
}
CalculatorRunner::CalculatorRunner(const std::string& calculator_type,
const CalculatorOptions& options) {
node_config_.set_calculator(calculator_type);
*node_config_.mutable_options() = options;
log_calculator_proto_ = true;
}
#if !defined(MEDIAPIPE_PROTO_LITE)
CalculatorRunner::CalculatorRunner(const std::string& node_config_string) {
CalculatorGraphConfig::Node node_config;
CHECK(
proto_ns::TextFormat::ParseFromString(node_config_string, &node_config));
MEDIAPIPE_CHECK_OK(InitializeFromNodeConfig(node_config));
}
CalculatorRunner::CalculatorRunner(const std::string& calculator_type,
const std::string& options_string,
int num_inputs, int num_outputs,
int num_side_packets) {
node_config_.set_calculator(calculator_type);
CHECK(proto_ns::TextFormat::ParseFromString(options_string,
node_config_.mutable_options()));
SetNumInputs(num_inputs);
SetNumOutputs(num_outputs);
SetNumInputSidePackets(num_side_packets);
// Reset log_calculator_proto to false, since it was set to true by
// SetNum*() calls above. This constructor is not deprecated but is
// currently implemented in terms of deprecated functions.
log_calculator_proto_ = false;
}
#endif
CalculatorRunner::~CalculatorRunner() {}
void CalculatorRunner::SetNumInputs(int n) {
tool::TagAndNameInfo info;
for (int i = 0; i < n; ++i) {
info.names.push_back(absl::StrCat("input_", i));
}
InitializeInputs(info);
}
void CalculatorRunner::SetNumOutputs(int n) {
tool::TagAndNameInfo info;
for (int i = 0; i < n; ++i) {
info.names.push_back(absl::StrCat("output_", i));
}
InitializeOutputs(info);
}
void CalculatorRunner::SetNumInputSidePackets(int n) {
tool::TagAndNameInfo info;
for (int i = 0; i < n; ++i) {
info.names.push_back(absl::StrCat("side_packet_", i));
}
InitializeInputSidePackets(info);
}
void CalculatorRunner::InitializeInputs(const tool::TagAndNameInfo& info) {
CHECK(graph_ == nullptr);
MEDIAPIPE_CHECK_OK(
tool::SetFromTagAndNameInfo(info, node_config_.mutable_input_stream()));
inputs_.reset(new StreamContentsSet(info));
log_calculator_proto_ = true;
}
void CalculatorRunner::InitializeOutputs(const tool::TagAndNameInfo& info) {
CHECK(graph_ == nullptr);
MEDIAPIPE_CHECK_OK(
tool::SetFromTagAndNameInfo(info, node_config_.mutable_output_stream()));
outputs_.reset(new StreamContentsSet(info));
log_calculator_proto_ = true;
}
void CalculatorRunner::InitializeInputSidePackets(
const tool::TagAndNameInfo& info) {
CHECK(graph_ == nullptr);
MEDIAPIPE_CHECK_OK(tool::SetFromTagAndNameInfo(
info, node_config_.mutable_input_side_packet()));
input_side_packets_.reset(new PacketSet(info));
log_calculator_proto_ = true;
}
mediapipe::Counter* CalculatorRunner::GetCounter(const std::string& name) {
return graph_->GetCounterFactory()->GetCounter(name);
}
::mediapipe::Status CalculatorRunner::BuildGraph() {
if (graph_ != nullptr) {
// The graph was already built.
return ::mediapipe::OkStatus();
}
RET_CHECK(inputs_) << "The inputs were not initialized.";
RET_CHECK(outputs_) << "The outputs were not initialized.";
RET_CHECK(input_side_packets_)
<< "The input side packets were not initialized.";
CalculatorGraphConfig config;
// Add the calculator node.
*(config.add_node()) = node_config_;
for (int i = 0; i < node_config_.input_stream_size(); ++i) {
std::string name;
std::string tag;
int index;
RETURN_IF_ERROR(tool::ParseTagIndexName(node_config_.input_stream(i), &tag,
&index, &name));
// Add a source for each input stream.
auto* node = config.add_node();
node->set_calculator("CalculatorRunnerSourceCalculator");
node->add_output_stream(name);
node->add_input_side_packet(absl::StrCat(kSourcePrefix, name));
}
for (int i = 0; i < node_config_.output_stream_size(); ++i) {
std::string name;
std::string tag;
int index;
RETURN_IF_ERROR(tool::ParseTagIndexName(node_config_.output_stream(i), &tag,
&index, &name));
// Add a sink for each output stream.
auto* node = config.add_node();
node->set_calculator("CalculatorRunnerSinkCalculator");
node->add_input_stream(name);
node->add_input_side_packet(absl::StrCat(kSinkPrefix, name));
}
config.set_num_threads(1);
if (log_calculator_proto_) {
#if defined(MEDIAPIPE_PROTO_LITE)
LOG(INFO) << "Please initialize CalculatorRunner using the recommended "
"constructor:\n CalculatorRunner runner(node_config);";
#else
std::string config_string;
proto_ns::TextFormat::Printer printer;
printer.SetInitialIndentLevel(4);
printer.PrintToString(node_config_, &config_string);
LOG(INFO) << "Please initialize CalculatorRunner using the recommended "
"constructor:\n CalculatorRunner runner(R\"(\n"
<< config_string << "\n )\");";
#endif
}
graph_ = absl::make_unique<CalculatorGraph>();
RETURN_IF_ERROR(graph_->Initialize(config));
return ::mediapipe::OkStatus();
}
::mediapipe::Status CalculatorRunner::Run() {
RETURN_IF_ERROR(BuildGraph());
// Set the input side packets for the sources.
std::map<std::string, Packet> input_side_packets;
int positional_index = -1;
for (int i = 0; i < node_config_.input_stream_size(); ++i) {
std::string name;
std::string tag;
int index;
RETURN_IF_ERROR(tool::ParseTagIndexName(node_config_.input_stream(i), &tag,
&index, &name));
const CalculatorRunner::StreamContents* contents;
if (index == -1) {
// positional_index considers the case when the tag is empty, which is
// always the case when index == -1. If we ever support indices for
// non-empty tags ("ABC:input1" and "ABC:input2" with automatic indices),
// this should be changed to use a map insted.
contents = &inputs_->Get(tag, ++positional_index);
} else {
contents = &inputs_->Get(tag, index);
}
input_side_packets.emplace(absl::StrCat(kSourcePrefix, name),
Adopt(new auto(contents)));
}
// Set the input side packets for the calculator.
positional_index = -1;
for (int i = 0; i < node_config_.input_side_packet_size(); ++i) {
std::string name;
std::string tag;
int index;
RETURN_IF_ERROR(tool::ParseTagIndexName(node_config_.input_side_packet(i),
&tag, &index, &name));
const Packet* packet;
if (index == -1) {
packet = &input_side_packets_->Get(tag, ++positional_index);
} else {
packet = &input_side_packets_->Get(tag, index);
}
input_side_packets.emplace(name, *packet);
}
// Set the input side packets for the sinks.
positional_index = -1;
for (int i = 0; i < node_config_.output_stream_size(); ++i) {
std::string name;
std::string tag;
int index;
RETURN_IF_ERROR(tool::ParseTagIndexName(node_config_.output_stream(i), &tag,
&index, &name));
CalculatorRunner::StreamContents* contents;
if (index == -1) {
contents = &outputs_->Get(tag, ++positional_index);
} else {
contents = &outputs_->Get(tag, index);
}
// Clear |contents| because Run() may be called multiple times.
*contents = CalculatorRunner::StreamContents();
input_side_packets.emplace(absl::StrCat(kSinkPrefix, name),
Adopt(new auto(contents)));
}
RETURN_IF_ERROR(graph_->Run(input_side_packets));
positional_index = -1;
for (int i = 0; i < node_config_.output_side_packet_size(); ++i) {
std::string name;
std::string tag;
int index;
RETURN_IF_ERROR(tool::ParseTagIndexName(node_config_.output_side_packet(i),
&tag, &index, &name));
Packet& contents = output_side_packets_->Get(
tag, (index == -1) ? ++positional_index : index);
ASSIGN_OR_RETURN(contents, graph_->GetOutputSidePacket(name));
}
return ::mediapipe::OkStatus();
}
} // namespace mediapipe
+157
View File
@@ -0,0 +1,157 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Defines CalculatorRunner which can be used to run a Calculator in
// isolation. This is useful for testing.
#ifndef MEDIAPIPE_FRAMEWORK_CALCULATOR_RUNNER_H_
#define MEDIAPIPE_FRAMEWORK_CALCULATOR_RUNNER_H_
#include <memory>
#include <string>
#include <vector>
#include "absl/base/macros.h"
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/port/status.h"
namespace mediapipe {
class CalculatorGraph;
// The class for running the Calculator with given inputs and examining outputs.
class CalculatorRunner {
public:
// A representation of input or output stream contents.
struct StreamContents {
// The Packets in the stream.
std::vector<Packet> packets;
// Stream header.
Packet header;
};
// A collection of StreamContents by either index or tag.
typedef internal::Collection<StreamContents> StreamContentsSet;
// Preferred constructor.
// All the needed information comes from the node config.
// Example:
// CalculatorRunner runner(R"(
// calculator: "ScaleImageCalculator"
// input_stream: "ycbcr_frames"
// output_stream: "FRAMES:srgb_frames"
// output_stream: "VIDEO_HEADER:srgb_frames_header"
// options {
// [mediapipe.ScaleImageCalculatorOptions.ext] {
// target_height: 10
// preserve_aspect_ratio: true
// output_format: SRGB
// algorithm: AREA
// }
// }
// )");
explicit CalculatorRunner(const CalculatorGraphConfig::Node& node_config);
#if !defined(MEDIAPIPE_PROTO_LITE)
// Convenience constructor which takes a node_config std::string directly.
explicit CalculatorRunner(const std::string& node_config_string);
// Convenience constructor to initialize a calculator which uses indexes
// (not tags) for all its fields.
// NOTE: This constructor calls proto_ns::TextFormat::ParseFromString(), which
// is not available when using lite protos.
CalculatorRunner(const std::string& calculator_type,
const std::string& options_string, int num_inputs,
int num_outputs, int num_side_packets);
#endif
// Minimal constructor which requires additional calls to define inputs,
// outputs, and input side packets. Prefer using another constructor.
ABSL_DEPRECATED("Initialize CalculatorRunner with a proto instead.")
CalculatorRunner(const std::string& calculator_type,
const CalculatorOptions& options);
CalculatorRunner(const CalculatorRunner&) = delete;
CalculatorRunner& operator=(const CalculatorRunner&) = delete;
~CalculatorRunner();
// Sets the number of input streams, output streams, or input side packets,
// respectively. May not be called after Run() has been called.
ABSL_DEPRECATED("Initialize CalculatorRunner with a proto instead.")
void SetNumInputs(int n);
ABSL_DEPRECATED("Initialize CalculatorRunner with a proto instead.")
void SetNumOutputs(int n);
ABSL_DEPRECATED("Initialize CalculatorRunner with a proto instead.")
void SetNumInputSidePackets(int n);
// Initializes the inputs, outputs, or side packets using a
// TagAndNameInfo. This sets the corresponding section of node_config_.
// May not be called after Run() has been called.
ABSL_DEPRECATED("Initialize CalculatorRunner with a proto instead.")
void InitializeInputs(const tool::TagAndNameInfo& info);
ABSL_DEPRECATED("Initialize CalculatorRunner with a proto instead.")
void InitializeOutputs(const tool::TagAndNameInfo& info);
ABSL_DEPRECATED("Initialize CalculatorRunner with a proto instead.")
void InitializeInputSidePackets(const tool::TagAndNameInfo& info);
// Returns mutable access to the input stream contents.
StreamContentsSet* MutableInputs() { return inputs_.get(); }
// Returns mutable access to the input side packets.
PacketSet* MutableSidePackets() { return input_side_packets_.get(); }
// Runs the calculator, by calling Open(), Process() with the
// inputs provided via mutable_inputs(), and Close(). Returns the
// ::mediapipe::Status from CalculatorGraph::Run(). Internally, Run()
// constructs a CalculatorGraph in the first call, and calls
// CalculatorGraph::Run(). A single instance of CalculatorRunner
// uses the same instance of CalculatorGraph for all runs.
::mediapipe::Status Run();
// Returns the vector of contents of the output streams. The .header
// field contains the stream header and the .packets field contains
// the Packets from the stream, unless SetOutputPacketCallback()
// has been called with non-nullptr, in which case .packets will be empty.
const StreamContentsSet& Outputs() const { return *outputs_; }
// Returns the access to the output side packets.
const PacketSet& OutputSidePackets() { return *output_side_packets_.get(); }
// Returns a graph counter.
mediapipe::Counter* GetCounter(const std::string& name);
private:
static const char kSourcePrefix[];
static const char kSinkPrefix[];
// Initialize using a node config (does the constructor's work).
::mediapipe::Status InitializeFromNodeConfig(
const CalculatorGraphConfig::Node& node_config);
// Builds the graph if one does not already exist.
::mediapipe::Status BuildGraph();
CalculatorGraphConfig::Node node_config_;
// Log the calculator proto after it is created from the provided
// parameters. This aids users in migrating to the recommended
// constructor.
bool log_calculator_proto_ = false;
std::unique_ptr<StreamContentsSet> inputs_;
std::unique_ptr<StreamContentsSet> outputs_;
std::unique_ptr<PacketSet> input_side_packets_;
std::unique_ptr<PacketSet> output_side_packets_;
std::unique_ptr<CalculatorGraph> graph_;
};
} // namespace mediapipe
#endif // MEDIAPIPE_FRAMEWORK_CALCULATOR_RUNNER_H_
@@ -0,0 +1,239 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Tests CalculatorRunner.
#include "mediapipe/framework/calculator_runner.h"
#include "absl/strings/str_cat.h"
#include "mediapipe/framework/calculator_base.h"
#include "mediapipe/framework/calculator_registry.h"
#include "mediapipe/framework/input_stream.h"
#include "mediapipe/framework/output_stream.h"
#include "mediapipe/framework/packet_type.h"
#include "mediapipe/framework/port/gmock.h"
#include "mediapipe/framework/port/gtest.h"
#include "mediapipe/framework/port/logging.h"
#include "mediapipe/framework/port/status.h"
#include "mediapipe/framework/port/status_matchers.h"
#include "mediapipe/framework/timestamp.h"
namespace mediapipe {
namespace {
// Inputs: 2 streams with ints. Headers are strings.
// Input side packets: 1.
// Outputs: 3 streams with ints. #0 and #1 will contain the negated values from
// corresponding input streams, #2 will contain replicas of the input side
// packet
// at InputTimestamp. The headers are strings.
class CalculatorRunnerTestCalculator : public CalculatorBase {
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
cc->Inputs().Index(0).Set<int>();
cc->Inputs().Index(1).Set<int>();
cc->Outputs().Index(0).Set<int>();
cc->Outputs().Index(1).Set<int>();
cc->Outputs().Index(2).SetSameAs(&cc->InputSidePackets().Index(0));
cc->InputSidePackets().Index(0).SetAny();
cc->OutputSidePackets()
.Tag("SIDE_OUTPUT")
.SetSameAs(&cc->InputSidePackets().Index(0));
return ::mediapipe::OkStatus();
}
::mediapipe::Status Open(CalculatorContext* cc) override {
std::string input_header_string =
absl::StrCat(cc->Inputs().Index(0).Header().Get<std::string>(),
cc->Inputs().Index(1).Header().Get<std::string>());
for (int i = 0; i < cc->Outputs().NumEntries(); ++i) {
// Set the header to the concatenation of the input headers and
// the index of the output stream.
cc->Outputs().Index(i).SetHeader(
Adopt(new std::string(absl::StrCat(input_header_string, i))));
}
cc->OutputSidePackets()
.Tag("SIDE_OUTPUT")
.Set(cc->InputSidePackets().Index(0));
return ::mediapipe::OkStatus();
}
::mediapipe::Status Process(CalculatorContext* cc) override {
for (int index = 0; index < 2; ++index) {
cc->Outputs().Index(index).Add(
new int(-cc->Inputs().Index(index).Get<int>()), cc->InputTimestamp());
}
cc->Outputs().Index(2).AddPacket(
cc->InputSidePackets().Index(0).At(cc->InputTimestamp()));
return ::mediapipe::OkStatus();
}
};
REGISTER_CALCULATOR(CalculatorRunnerTestCalculator);
// Inputs: Any number of streams of integer, with any tags.
// Outputs: For each tag name (possibly including the empty tag), outputs a
// a single stream with the sum of the integers belonging to streams
// with the same tag name (and any index).
class CalculatorRunnerMultiTagTestCalculator : public CalculatorBase {
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
for (const std::string& tag : cc->Inputs().GetTags()) {
for (CollectionItemId item_id = cc->Inputs().BeginId(tag);
item_id < cc->Inputs().EndId(tag); ++item_id) {
cc->Inputs().Get(item_id).Set<int>();
}
cc->Outputs().Get(tag, 0).Set<int>();
}
return ::mediapipe::OkStatus();
}
::mediapipe::Status Process(CalculatorContext* cc) override {
for (const std::string& tag : cc->Inputs().GetTags()) {
auto sum = absl::make_unique<int>(0);
for (CollectionItemId item_id = cc->Inputs().BeginId(tag);
item_id < cc->Inputs().EndId(tag); ++item_id) {
if (!cc->Inputs().Get(item_id).IsEmpty()) {
*sum += cc->Inputs().Get(item_id).Get<int>();
}
}
cc->Outputs().Get(tag, 0).Add(sum.release(), cc->InputTimestamp());
}
return ::mediapipe::OkStatus();
}
};
REGISTER_CALCULATOR(CalculatorRunnerMultiTagTestCalculator);
TEST(CalculatorRunner, RunsCalculator) {
CalculatorRunner runner(R"(
calculator: "CalculatorRunnerTestCalculator"
input_stream: "input_0"
input_stream: "input_1"
output_stream: "output_0"
output_stream: "output_1"
output_stream: "output_2"
input_side_packet: "input_side_packet_0"
output_side_packet: "SIDE_OUTPUT:output_side_packet_0"
options {
}
)");
// Run CalculatorRunner::Run() several times, with different inputs. This
// tests that a CalculatorRunner instance can be reused.
for (int iter = 0; iter < 3; ++iter) {
LOG(INFO) << "iter: " << iter;
const int length = iter;
// Generate the inputs at timestamps 0 ... length-1, at timestamp t having
// values t and t*2 for the two streams, respectively.
const std::string kHeaderPrefix = "header";
for (int index = 0; index < 2; ++index) {
runner.MutableInputs()->Index(index).packets.clear();
for (int t = 0; t < length; ++t) {
runner.MutableInputs()->Index(index).packets.push_back(
Adopt(new int(t * (index + 1))).At(Timestamp(t)));
}
// Set the header to the concatenation of kHeaderPrefix and the index of
// the input stream.
runner.MutableInputs()->Index(index).header =
Adopt(new std::string(absl::StrCat(kHeaderPrefix, index)));
}
const int input_side_packet_content = 10 + iter;
runner.MutableSidePackets()->Index(0) =
Adopt(new int(input_side_packet_content));
MEDIAPIPE_ASSERT_OK(runner.Run());
EXPECT_EQ(input_side_packet_content,
runner.OutputSidePackets().Tag("SIDE_OUTPUT").Get<int>());
const auto& outputs = runner.Outputs();
ASSERT_EQ(3, outputs.NumEntries());
// Check the output headers and the number of Packets.
for (int index = 0; index < outputs.NumEntries(); ++index) {
// The header should be the concatenation of the input headers
// and the index of the output stream.
EXPECT_EQ(absl::StrCat(kHeaderPrefix, 0, kHeaderPrefix, 1, index),
outputs.Index(index).header.Get<std::string>());
// Check the packets.
const std::vector<Packet>& packets = outputs.Index(index).packets;
EXPECT_EQ(length, packets.size());
for (int t = 0; t < length; ++t) {
EXPECT_EQ(Timestamp(t), packets[t].Timestamp());
// The first two output streams are negations of the inputs, the last
// contains copies of the input side packet.
if (index < 2) {
EXPECT_EQ(-t * (index + 1), packets[t].Get<int>());
} else {
EXPECT_EQ(input_side_packet_content, packets[t].Get<int>());
}
}
}
}
}
TEST(CalculatorRunner, MultiTagTestCalculatorOk) {
CalculatorRunner runner(R"(
calculator: "CalculatorRunnerMultiTagTestCalculator"
input_stream: "A:0:full_0"
input_stream: "A:1:full_1"
input_stream: "A:2:full_2"
input_stream: "B:no_index_0"
input_stream: "no_tag_or_index_0"
input_stream: "no_tag_or_index_1"
output_stream: "A:output_a"
output_stream: "B:output_b"
output_stream: "output_c"
)");
for (int ts = 0; ts < 5; ++ts) {
for (int i = 0; i < 3; ++i) {
runner.MutableInputs()->Get("A", i).packets.push_back(
Adopt(new int(10 * ts + i)).At(Timestamp(ts)));
}
runner.MutableInputs()->Get("B", 0).packets.push_back(
Adopt(new int(100)).At(Timestamp(ts)));
runner.MutableInputs()
->Get("", ts % 2)
.packets.push_back(Adopt(new int(ts)).At(Timestamp(ts)));
}
MEDIAPIPE_ASSERT_OK(runner.Run());
const auto& outputs = runner.Outputs();
ASSERT_EQ(3, outputs.NumEntries());
for (int ts = 0; ts < 5; ++ts) {
const std::vector<Packet>& a_packets = outputs.Tag("A").packets;
const std::vector<Packet>& b_packets = outputs.Tag("B").packets;
const std::vector<Packet>& c_packets = outputs.Tag("").packets;
EXPECT_EQ(Timestamp(ts), a_packets[ts].Timestamp());
EXPECT_EQ(Timestamp(ts), b_packets[ts].Timestamp());
EXPECT_EQ(Timestamp(ts), c_packets[ts].Timestamp());
EXPECT_EQ(10 * 3 * ts + 3, a_packets[ts].Get<int>());
EXPECT_EQ(100, b_packets[ts].Get<int>());
EXPECT_EQ(ts, c_packets[ts].Get<int>());
}
}
TEST(CalculatorRunner, MultiTagTestInvalidStreamTagCrashes) {
const std::string graph_config = R"(
calculator: "CalculatorRunnerMultiTagTestCalculator"
input_stream: "A:0:a_0"
input_stream: "A:a_1"
input_stream: "A:2:a_2"
output_stream: "A:output_a"
)";
EXPECT_DEATH(CalculatorRunner runner(graph_config),
".*tag \"A\" index 0 already had a name "
"\"a_0\" but is being reassigned a name \"a_1\"");
}
} // namespace
} // namespace mediapipe
+82
View File
@@ -0,0 +1,82 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Definitions for CalculatorNode.
#include "mediapipe/framework/calculator_state.h"
#include <string>
#include "absl/strings/str_cat.h"
#include "mediapipe/framework/port/logging.h"
namespace mediapipe {
CalculatorState::CalculatorState(
const std::string& node_name, int node_id,
const std::string& calculator_type,
const CalculatorGraphConfig::Node& node_config,
std::shared_ptr<ProfilingContext> profiling_context)
: node_name_(node_name),
node_id_(node_id),
calculator_type_(calculator_type),
node_config_(node_config),
profiling_context_(profiling_context),
input_streams_(nullptr),
output_streams_(nullptr),
counter_factory_(nullptr) {
options_.Initialize(node_config);
ResetBetweenRuns();
}
CalculatorState::~CalculatorState() {}
void CalculatorState::SetInputStreamSet(InputStreamSet* input_stream_set) {
CHECK(input_stream_set);
input_streams_ = input_stream_set;
}
void CalculatorState::SetOutputStreamSet(OutputStreamSet* output_stream_set) {
CHECK(output_stream_set);
output_streams_ = output_stream_set;
}
void CalculatorState::ResetBetweenRuns() {
input_side_packets_ = nullptr;
input_streams_ = nullptr;
output_streams_ = nullptr;
counter_factory_ = nullptr;
}
void CalculatorState::SetInputSidePackets(const PacketSet* input_side_packets) {
CHECK(input_side_packets);
input_side_packets_ = input_side_packets;
}
void CalculatorState::SetOutputSidePackets(
OutputSidePacketSet* output_side_packets) {
CHECK(output_side_packets);
output_side_packets_ = output_side_packets;
}
Counter* CalculatorState::GetCounter(const std::string& name) {
CHECK(counter_factory_);
return counter_factory_->GetCounter(absl::StrCat(NodeName(), "-", name));
}
void CalculatorState::SetServicePacket(const std::string& key, Packet packet) {
service_packets_[key] = std::move(packet);
}
} // namespace mediapipe
+160
View File
@@ -0,0 +1,160 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Defines CalculatorState.
#ifndef MEDIAPIPE_FRAMEWORK_CALCULATOR_STATE_H_
#define MEDIAPIPE_FRAMEWORK_CALCULATOR_STATE_H_
#include <map>
#include <memory>
#include <string>
// TODO: Move protos in another CL after the C++ code migration.
#include "absl/base/macros.h"
#include "mediapipe/framework/calculator.pb.h"
#include "mediapipe/framework/counter.h"
#include "mediapipe/framework/counter_factory.h"
#include "mediapipe/framework/graph_service.h"
#include "mediapipe/framework/packet.h"
#include "mediapipe/framework/packet_set.h"
#include "mediapipe/framework/port.h"
#include "mediapipe/framework/port/any_proto.h"
#include "mediapipe/framework/tool/options_util.h"
namespace mediapipe {
class ProfilingContext;
// Holds data that the Calculator needs access to. This data is not
// stored in Calculator directly since Calculator will be destroyed after
// every CalculatorGraph::Run() . It is not stored in CalculatorNode
// because Calculator should not depend on CalculatorNode. All
// information conveyed in this class is flowing from the CalculatorNode
// to the Calculator.
class CalculatorState {
public:
CalculatorState(const std::string& node_name, int node_id,
const std::string& calculator_type,
const CalculatorGraphConfig::Node& node_config,
std::shared_ptr<ProfilingContext> profiling_context);
CalculatorState(const CalculatorState&) = delete;
CalculatorState& operator=(const CalculatorState&) = delete;
~CalculatorState();
// Sets the pointer to the InputStreamSet. The function is invoked by
// CalculatorNode::PrepareForRun.
void SetInputStreamSet(InputStreamSet* input_stream_set);
// Sets the pointer to the OutputStreamSet. The function is invoked by
// CalculatorNode::PrepareForRun.
void SetOutputStreamSet(OutputStreamSet* output_stream_set);
// Called before every call to Calculator::Open() (during the PrepareForRun
// phase).
void ResetBetweenRuns();
const std::string& CalculatorType() const { return calculator_type_; }
const CalculatorOptions& Options() const { return node_config_.options(); }
// Returns the options given to this calculator. Template argument T must
// be the type of the protobuf extension message or the protobuf::Any
// message containing the options.
template <class T>
const T& Options() const {
return options_.Get<T>();
}
const std::string& NodeName() const { return node_name_; }
const int& NodeId() const { return node_id_; }
////////////////////////////////////////
// Interface for Calculator.
////////////////////////////////////////
const InputStreamSet& InputStreams() const { return *input_streams_; }
const OutputStreamSet& OutputStreams() const { return *output_streams_; }
const PacketSet& InputSidePackets() const { return *input_side_packets_; }
OutputSidePacketSet& OutputSidePackets() { return *output_side_packets_; }
// Returns a counter using the graph's counter factory. The counter's
// name is the passed-in name, prefixed by the calculator NodeName.
Counter* GetCounter(const std::string& name);
std::shared_ptr<ProfilingContext> GetSharedProfilingContext() const {
return profiling_context_;
}
////////////////////////////////////////
// Interface for CalculatorNode.
////////////////////////////////////////
// Sets the input side packets.
void SetInputSidePackets(const PacketSet* input_side_packets);
// Sets the output side packets.
void SetOutputSidePackets(OutputSidePacketSet* output_side_packets);
// Sets the counter factory.
void SetCounterFactory(CounterFactory* counter_factory) {
counter_factory_ = counter_factory;
}
void SetServicePacket(const std::string& key, Packet packet);
bool IsServiceAvailable(const GraphServiceBase& service) {
return ContainsKey(service_packets_, service.key);
}
template <typename T>
T& GetServiceObject(const GraphService<T>& service) {
auto it = service_packets_.find(service.key);
CHECK(it != service_packets_.end());
return *it->second.template Get<std::shared_ptr<T>>();
}
private:
////////////////////////////////////////
// Persistent variables that are not cleared by ResetBetweenRuns().
////////////////////////////////////////
// The name associated with this calculator's node.
const std::string node_name_;
// The ID associated with this calculator's node.
const int node_id_;
// The registered type name of the Calculator.
const std::string calculator_type_;
// The Node protobuf containing the options for the calculator.
const CalculatorGraphConfig::Node node_config_;
// The unpacked protobuf options for the calculator.
tool::OptionsMap options_;
// The graph tracing and profiling interface.
std::shared_ptr<ProfilingContext> profiling_context_;
std::map<std::string, Packet> service_packets_;
////////////////////////////////////////
// Variables which ARE cleared by ResetBetweenRuns().
////////////////////////////////////////
// The InputStreamSet object is owned by the CalculatorNode.
// CalculatorState obtains its pointer in CalculatorNode::PrepareForRun.
InputStreamSet* input_streams_;
// The OutputStreamSet object is owned by the CalculatorNode.
// CalculatorState obtains its pointer in CalculatorNode::PrepareForRun.
OutputStreamSet* output_streams_;
// The set of input side packets set by CalculatorNode::PrepareForRun().
// ResetBetweenRuns() clears this PacketSet pointer.
const PacketSet* input_side_packets_;
// The OutputSidePacketSet object is owned by the CalculatorNode.
// CalculatorState obtains its pointer in CalculatorNode::PrepareForRun.
OutputSidePacketSet* output_side_packets_;
CounterFactory* counter_factory_;
};
} // namespace mediapipe
#endif // MEDIAPIPE_FRAMEWORK_CALCULATOR_STATE_H_
+53
View File
@@ -0,0 +1,53 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_FRAMEWORK_CAMERA_INTRINSICS_H_
#define MEDIAPIPE_FRAMEWORK_CAMERA_INTRINSICS_H_
class CameraIntrinsics {
public:
CameraIntrinsics(float fx, float fy, float cx, float cy, float width,
float height)
: fx_(fx), fy_(fy), cx_(cx), cy_(cy), width_(width), height_(height) {}
CameraIntrinsics(float fx, float fy, float cx, float cy)
: CameraIntrinsics(fx, fy, cx, cy, -1, -1) {}
float fx() const { return fx_; }
float fy() const { return fy_; }
float cx() const { return cx_; }
float cy() const { return cy_; }
float width() const { return width_; }
float height() const { return height_; }
private:
// Lens focal length along the x-axis, in pixels.
const float fx_;
// Lens focal length along the y-axis, in pixels.
const float fy_;
// Principal point, x-coordinate on the image, in pixels.
const float cx_;
// Principal point, y-coordinate on the image, in pixels.
const float cy_;
// Image width, in pixels.
const float width_;
// Image height, in pixels.
const float height_;
};
#endif // MEDIAPIPE_FRAMEWORK_CAMERA_INTRINSICS_H_
+563
View File
@@ -0,0 +1,563 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_FRAMEWORK_COLLECTION_H_
#define MEDIAPIPE_FRAMEWORK_COLLECTION_H_
#include <cstdlib>
#include <iterator>
#include <map>
#include <set>
#include <string>
#include <typeinfo>
#include <vector>
#include "absl/base/macros.h"
#include "absl/memory/memory.h"
#include "absl/strings/str_cat.h"
#include "mediapipe/framework/collection_item_id.h"
#include "mediapipe/framework/port/logging.h"
#include "mediapipe/framework/tool/tag_map.h"
#include "mediapipe/framework/tool/validate_name.h"
#include "mediapipe/framework/type_map.h"
namespace mediapipe {
namespace internal {
// A class to handle errors that occur in Collection. For most
// collections, these errors should be fatal. However, for a collection
// more like PacketTypeSet, the errors should be deferred and handled
// later.
//
// This class is thread compatible.
template <typename T>
struct CollectionErrorHandlerFatal {
// An error occurred during object lookup for the provided tag and
// index. The returned object reference will be provided instead.
//
// Since there isn't any state and we're not returning anything, we
// get away with only one version of this function (which is const
// but returns a non-const reference).
T& GetFallback(const std::string& tag, int index) const {
LOG(FATAL) << "Failed to get tag \"" << tag << "\" index " << index;
std::abort();
}
};
enum class CollectionStorage { kStoreValue = 0, kStorePointer };
// A collection of objects of type T.
//
// If storage == kStorePointer then T* will be stored instead of T, but
// the accessor functions will still return T types. The T objects must
// be owned elsewhere and remain alive as long as the collection is used.
// To set the pointers use the GetPtr() function.
//
// The ErrorHandler object allows errors to be deferred to a later time.
//
// This class is thread compatible as long as the ErrorHandler object is also
// thread compatible.
template <typename T,
CollectionStorage storage = CollectionStorage::kStoreValue,
typename ErrorHandler = CollectionErrorHandlerFatal<T>>
class Collection {
private:
template <typename ItType>
class DoubleDerefIterator;
public:
using value_type = T;
// The iterator is over value_type, requiring a double dereference if
// storage == kStorePointer.
using iterator =
typename std::conditional<storage == CollectionStorage::kStorePointer,
DoubleDerefIterator<value_type>,
value_type*>::type;
using const_iterator =
typename std::conditional<storage == CollectionStorage::kStorePointer,
DoubleDerefIterator<const value_type>,
const value_type*>::type;
using difference_type = ptrdiff_t;
using size_type = size_t;
using pointer = value_type*;
using reference = value_type&;
// The type that is stored by data_;
using stored_type =
typename std::conditional<storage == CollectionStorage::kStorePointer,
value_type*, value_type>::type;
// Collection must be initialized on construction.
Collection() = delete;
Collection(const Collection&) = delete;
Collection& operator=(const Collection&) = delete;
// Makes a Collection using the given TagMap (which should be shared
// between collections).
// Refer to mediapipe::tool::CreateTagMap for examples of how to construct a
// collection from a vector of "TAG:<index>:name" strings, or from an integer
// number of indexes, etc.
explicit Collection(std::shared_ptr<tool::TagMap> tag_map);
// Makes a Collection using the information in the TagAndNameInfo.
ABSL_DEPRECATED("Use Collection(tool::TagMap)")
explicit Collection(const tool::TagAndNameInfo& info);
// Convenience constructor which initializes a collection to use
// indexes and have num_entries inputs.
ABSL_DEPRECATED("Use Collection(tool::TagMap)")
explicit Collection(int num_entries);
// Convenience constructor which initializes a collection to use tags
// with the given names.
// Note: initializer_list constructor should not be marked explicit.
ABSL_DEPRECATED("Use Collection(tool::TagMap)")
Collection(const std::initializer_list<std::string>& tag_names);
// Access the data at a given CollectionItemId. This is the most efficient
// way to access data within the collection.
//
// Do not assume that Index(2) == Get(collection.TagMap()->BeginId() + 2).
value_type& Get(CollectionItemId id);
const value_type& Get(CollectionItemId id) const;
// Convenience functions.
value_type& Get(const std::string& tag, int index);
const value_type& Get(const std::string& tag, int index) const;
// Equivalent to Get("", index);
value_type& Index(int index);
const value_type& Index(int index) const;
// Equivalent to Get(tag, 0);
value_type& Tag(const std::string& tag);
const value_type& Tag(const std::string& tag) const;
// These functions only exist for collections with storage ==
// kStorePointer. GetPtr returns the stored ptr value rather than
// the value_type. The non-const version returns a reference so that
// the pointer can be set.
value_type*& GetPtr(CollectionItemId id);
// Const version returns a pointer to a const value (a const-ref to
// a pointer wouldn't be useful in this context).
const value_type* GetPtr(CollectionItemId id) const;
// Returns true if the collection has a tag other than "".
// TODO Deprecate and remove this function.
bool UsesTags() const;
// Returns a description of the collection.
std::string DebugString() const;
// Return the tag_map.
const std::shared_ptr<tool::TagMap>& TagMap() const;
// Iteration functions for use of the collection in a range based
// for loop. The items are provided in sorted tag order with indexes
// sequential within tags.
iterator begin();
iterator end();
const_iterator begin() const;
const_iterator end() const;
// Returns the error handler object.
const ErrorHandler& GetErrorHandler() const { return error_handler_; }
////////////////////////////////////////
// The remaining public functions directly call their equivalent
// in tool::TagMap. They are guaranteed to be equivalent for any
// Collection initialized using an equivalent tool::TagMap.
////////////////////////////////////////
// Returns true if the provided tag is available (not necessarily set yet).
bool HasTag(const std::string& tag) const { return tag_map_->HasTag(tag); }
// Returns the number of entries in this collection.
int NumEntries() const { return tag_map_->NumEntries(); }
// Returns the number of entries with the provided tag.
int NumEntries(const std::string& tag) const {
return tag_map_->NumEntries(tag);
}
// Get the id for the tag and index. This id is guaranteed valid for
// any Collection which was initialized with an equivalent tool::TagMap.
// If the tag or index are invalid then an invalid CollectionItemId
// is returned (with id.IsValid() == false).
//
// The id for indexes within the same tag are guaranteed to
// be sequential. Meaning, if tag "BLAH" has 3 indexes, then
// ++GetId("BLAH", 1) == GetId("BLAH", 2)
// However, be careful in using this fact, as it circumvents the
// validity checks in GetId() (i.e. ++GetId("BLAH", 2) looks like it
// is valid, while GetId("BLAH", 3) is not valid).
CollectionItemId GetId(const std::string& tag, int index) const {
return tag_map_->GetId(tag, index);
}
// Returns the names of the tags in this collection.
std::set<std::string> GetTags() const { return tag_map_->GetTags(); }
// Get a tag and index for the specified id. If the id is not valid,
// then {"", -1} will be returned.
std::pair<std::string, int> TagAndIndexFromId(CollectionItemId id) const {
return tag_map_->TagAndIndexFromId(id);
}
// The CollectionItemId corresponding to the first element in the collection.
// Looping over all elements can be done as follows.
// for (CollectionItemId id = collection.BeginId();
// id < collection.EndId(); ++id) {
// }
// However, if only one collection is involved, prefer using a range
// based for loop.
// for (Packet packet : Inputs()) {
// }
CollectionItemId BeginId() const { return tag_map_->BeginId(); }
// The CollectionItemId corresponding to an element immediately after
// the last element of the collection.
CollectionItemId EndId() const { return tag_map_->EndId(); }
// Same as BeginId()/EndId() but for only one tag. If the tag doesn't
// exist then an invalid CollectionItemId is returned. It is guaranteed
// that a loop constructed in this way will successfully not be entered
// for invalid tags.
// for (CollectionItemId id = collection.BeginId(tag);
// id < collection.EndId(tag); ++id) {
// }
CollectionItemId BeginId(const std::string& tag) const {
return tag_map_->BeginId(tag);
}
CollectionItemId EndId(const std::string& tag) const {
return tag_map_->EndId(tag);
}
private:
// An iterator which is identical to ItType** except that the
// dereference operator (operator*) does a double dereference and
// returns an ItType.
//
// This class is thread compatible.
template <typename ItType>
class DoubleDerefIterator {
public:
using iterator_category = std::random_access_iterator_tag;
using value_type = ItType;
using difference_type = std::ptrdiff_t;
using pointer = ItType*;
using reference = ItType&;
DoubleDerefIterator() : ptr_(nullptr) {}
reference operator*() { return **ptr_; }
pointer operator->() { return *ptr_; }
reference operator[](difference_type d) { return **(ptr_ + d); }
// Member operators.
DoubleDerefIterator& operator++() {
++ptr_;
return *this;
}
DoubleDerefIterator operator++(int) {
DoubleDerefIterator output(ptr_);
++ptr_;
return output;
}
DoubleDerefIterator& operator--() {
--ptr_;
return *this;
}
DoubleDerefIterator operator--(int) {
DoubleDerefIterator output(ptr_);
--ptr_;
return output;
}
DoubleDerefIterator& operator+=(difference_type d) {
ptr_ += d;
return *this;
}
DoubleDerefIterator& operator-=(difference_type d) {
ptr_ -= d;
return *this;
}
// Non-member binary operators.
friend bool operator==(DoubleDerefIterator lhs, DoubleDerefIterator rhs) {
return lhs.ptr_ == rhs.ptr_;
}
friend bool operator!=(DoubleDerefIterator lhs, DoubleDerefIterator rhs) {
return lhs.ptr_ != rhs.ptr_;
}
friend bool operator<(DoubleDerefIterator lhs, DoubleDerefIterator rhs) {
return lhs.ptr_ < rhs.ptr_;
}
friend bool operator<=(DoubleDerefIterator lhs, DoubleDerefIterator rhs) {
return lhs.ptr_ <= rhs.ptr_;
}
friend bool operator>(DoubleDerefIterator lhs, DoubleDerefIterator rhs) {
return lhs.ptr_ > rhs.ptr_;
}
friend bool operator>=(DoubleDerefIterator lhs, DoubleDerefIterator rhs) {
return lhs.ptr_ >= rhs.ptr_;
}
friend DoubleDerefIterator operator+(DoubleDerefIterator lhs,
difference_type d) {
return lhs.ptr_ + d;
}
friend DoubleDerefIterator operator+(difference_type d,
DoubleDerefIterator rhs) {
return rhs.ptr_ + d;
}
friend DoubleDerefIterator& operator-(DoubleDerefIterator lhs,
difference_type d) {
return lhs.ptr_ - d;
}
friend difference_type operator-(DoubleDerefIterator lhs,
DoubleDerefIterator rhs) {
return lhs.ptr_ - rhs.ptr_;
}
private:
explicit DoubleDerefIterator(ItType* const* data) : ptr_(data) {}
ItType* const* ptr_;
friend class Collection;
};
// TagMap for the collection.
std::shared_ptr<tool::TagMap> tag_map_;
// Indexed by Id. Use an array directly so that the type does not
// have to be copy constructable. The array has tag_map_->NumEntries()
// elements.
std::unique_ptr<stored_type[]> data_;
// A class which allows errors to be reported flexibly. The default
// instantiation performs a LOG(FATAL) and does not have any member
// variables (zero size).
ErrorHandler error_handler_;
};
// Definitions of templated functions for Collection.
template <typename T, CollectionStorage storage, typename ErrorHandler>
Collection<T, storage, ErrorHandler>::Collection(
std::shared_ptr<tool::TagMap> tag_map)
: tag_map_(std::move(tag_map)) {
if (tag_map_->NumEntries() != 0) {
data_ = absl::make_unique<stored_type[]>(tag_map_->NumEntries());
}
}
template <typename T, CollectionStorage storage, typename ErrorHandler>
Collection<T, storage, ErrorHandler>::Collection(
const tool::TagAndNameInfo& info) {
tag_map_ = std::move(tool::TagMap::Create(info).ValueOrDie());
if (tag_map_->NumEntries() != 0) {
data_ = absl::make_unique<stored_type[]>(tag_map_->NumEntries());
}
}
template <typename T, CollectionStorage storage, typename ErrorHandler>
Collection<T, storage, ErrorHandler>::Collection(const int num_entries) {
proto_ns::RepeatedPtrField<ProtoString> fields;
for (int i = 0; i < num_entries; ++i) {
*fields.Add() = absl::StrCat("name", i);
}
tag_map_ = std::move(tool::TagMap::Create(fields).ValueOrDie());
if (tag_map_->NumEntries() != 0) {
data_ = absl::make_unique<stored_type[]>(tag_map_->NumEntries());
}
}
template <typename T, CollectionStorage storage, typename ErrorHandler>
Collection<T, storage, ErrorHandler>::Collection(
const std::initializer_list<std::string>& tag_names) {
proto_ns::RepeatedPtrField<ProtoString> fields;
int i = 0;
for (const std::string& name : tag_names) {
*fields.Add() = absl::StrCat(name, ":name", i);
++i;
}
tag_map_ = std::move(tool::TagMap::Create(fields).ValueOrDie());
if (tag_map_->NumEntries() != 0) {
data_ = absl::make_unique<stored_type[]>(tag_map_->NumEntries());
}
}
template <typename T, CollectionStorage storage, typename ErrorHandler>
bool Collection<T, storage, ErrorHandler>::UsesTags() const {
auto& mapping = tag_map_->Mapping();
if (mapping.size() > 1) {
// At least one tag is not "".
return true;
}
if (mapping.empty()) {
// The mapping is empty, it doesn't use tags.
return false;
}
// If the one tag present is non-empty then we are using tags.
return mapping.begin()->first != "";
}
template <typename T, CollectionStorage storage, typename ErrorHandler>
typename Collection<T, storage, ErrorHandler>::value_type&
Collection<T, storage, ErrorHandler>::Get(CollectionItemId id) {
CHECK_LE(BeginId(), id);
CHECK_LT(id, EndId());
return begin()[id.value()];
}
template <typename T, CollectionStorage storage, typename ErrorHandler>
const typename Collection<T, storage, ErrorHandler>::value_type&
Collection<T, storage, ErrorHandler>::Get(CollectionItemId id) const {
CHECK_LE(BeginId(), id);
CHECK_LT(id, EndId());
return begin()[id.value()];
}
template <typename T, CollectionStorage storage, typename ErrorHandler>
typename Collection<T, storage, ErrorHandler>::value_type*&
Collection<T, storage, ErrorHandler>::GetPtr(CollectionItemId id) {
static_assert(storage == CollectionStorage::kStorePointer,
"::mediapipe::internal::Collection<T>::GetPtr() is only "
"available for collections that were defined with template "
"argument storage == CollectionStorage::kStorePointer.");
CHECK_LE(BeginId(), id);
CHECK_LT(id, EndId());
return data_[id.value()];
}
template <typename T, CollectionStorage storage, typename ErrorHandler>
const typename Collection<T, storage, ErrorHandler>::value_type*
Collection<T, storage, ErrorHandler>::GetPtr(CollectionItemId id) const {
static_assert(storage == CollectionStorage::kStorePointer,
"::mediapipe::internal::Collection<T>::GetPtr() is only "
"available for collections that were defined with template "
"argument storage == CollectionStorage::kStorePointer.");
CHECK_LE(BeginId(), id);
CHECK_LT(id, EndId());
return data_[id.value()];
}
template <typename T, CollectionStorage storage, typename ErrorHandler>
typename Collection<T, storage, ErrorHandler>::value_type&
Collection<T, storage, ErrorHandler>::Get(const std::string& tag, int index) {
CollectionItemId id = GetId(tag, index);
if (!id.IsValid()) {
return error_handler_.GetFallback(tag, index);
}
return begin()[id.value()];
}
template <typename T, CollectionStorage storage, typename ErrorHandler>
const typename Collection<T, storage, ErrorHandler>::value_type&
Collection<T, storage, ErrorHandler>::Get(const std::string& tag,
int index) const {
CollectionItemId id = GetId(tag, index);
if (!id.IsValid()) {
return error_handler_.GetFallback(tag, index);
}
return begin()[id.value()];
}
template <typename T, CollectionStorage storage, typename ErrorHandler>
typename Collection<T, storage, ErrorHandler>::value_type&
Collection<T, storage, ErrorHandler>::Index(int index) {
return Get("", index);
}
template <typename T, CollectionStorage storage, typename ErrorHandler>
const typename Collection<T, storage, ErrorHandler>::value_type&
Collection<T, storage, ErrorHandler>::Index(int index) const {
return Get("", index);
}
template <typename T, CollectionStorage storage, typename ErrorHandler>
typename Collection<T, storage, ErrorHandler>::value_type&
Collection<T, storage, ErrorHandler>::Tag(const std::string& tag) {
return Get(tag, 0);
}
template <typename T, CollectionStorage storage, typename ErrorHandler>
const typename Collection<T, storage, ErrorHandler>::value_type&
Collection<T, storage, ErrorHandler>::Tag(const std::string& tag) const {
return Get(tag, 0);
}
template <typename T, CollectionStorage storage, typename ErrorHandler>
std::string Collection<T, storage, ErrorHandler>::DebugString() const {
std::string output =
absl::StrCat("Collection of \"", MediaPipeTypeStringOrDemangled<T>(),
"\" with\n", tag_map_->DebugString());
return output;
}
template <typename T, CollectionStorage storage, typename ErrorHandler>
const std::shared_ptr<tool::TagMap>&
Collection<T, storage, ErrorHandler>::TagMap() const {
return tag_map_;
}
template <typename T, CollectionStorage storage, typename ErrorHandler>
typename Collection<T, storage, ErrorHandler>::iterator
Collection<T, storage, ErrorHandler>::begin() {
return iterator(data_.get());
}
template <typename T, CollectionStorage storage, typename ErrorHandler>
typename Collection<T, storage, ErrorHandler>::iterator
Collection<T, storage, ErrorHandler>::end() {
return iterator(data_.get() + tag_map_->NumEntries());
}
template <typename T, CollectionStorage storage, typename ErrorHandler>
typename Collection<T, storage, ErrorHandler>::const_iterator
Collection<T, storage, ErrorHandler>::begin() const {
return const_iterator(data_.get());
}
template <typename T, CollectionStorage storage, typename ErrorHandler>
typename Collection<T, storage, ErrorHandler>::const_iterator
Collection<T, storage, ErrorHandler>::end() const {
return const_iterator(data_.get() + tag_map_->NumEntries());
}
} // namespace internal
// Returns c.HasTag(tag) && !Tag(tag)->IsEmpty() (just for convenience).
// This version is used with Calculator.
template <class S>
bool HasTagValue(const internal::Collection<S*>& c, const std::string& tag) {
return c.HasTag(tag) && !c.Tag(tag)->IsEmpty();
}
// Returns c.HasTag(tag) && !Tag(tag).IsEmpty() (just for convenience).
// This version is used with CalculatorBase.
template <class S>
bool HasTagValue(const internal::Collection<S>& c, const std::string& tag) {
return c.HasTag(tag) && !c.Tag(tag).IsEmpty();
}
// Returns c.HasTag(tag) && !Tag(tag).IsEmpty() (just for convenience).
// This version is used with Calculator or CalculatorBase.
template <class C>
bool HasTagValue(const C& c, const std::string& tag) {
return HasTagValue(c->Inputs(), tag);
}
} // namespace mediapipe
#endif // MEDIAPIPE_FRAMEWORK_COLLECTION_H_
+27
View File
@@ -0,0 +1,27 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/collection_item_id.h"
namespace mediapipe {
std::ostream& operator<<(std::ostream& os, CollectionItemId arg) {
return os << arg.value();
}
CollectionItemId operator+(int lhs, CollectionItemId rhs) { return rhs + lhs; }
CollectionItemId operator-(int lhs, CollectionItemId rhs) { return -rhs + lhs; }
CollectionItemId operator*(int lhs, CollectionItemId rhs) { return rhs * lhs; }
} // namespace mediapipe
+177
View File
@@ -0,0 +1,177 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_FRAMEWORK_COLLECTION_ITEM_ID_H_
#define MEDIAPIPE_FRAMEWORK_COLLECTION_ITEM_ID_H_
#include "mediapipe/framework/deps/strong_int.h"
namespace mediapipe {
namespace tool {
class TagMap;
} // namespace tool
// TagMap allows access to a collection using a tag and index value.
// The underlying data in the collection is stored in a flat array.
// CollectionItemId is the index into that array. Although this type is
// conceptually an int we don't allow implicit type conversion so as to
// avoid confusion where a user accidentally forgets to query the TagMap
// to get an actual CollectionItemId.
// For example, accidentally using Inputs().Get(2) when Inputs().Index(2)
// was meant will cause a type error.
class CollectionItemId {
public:
// Static function to return an invalid id.
static const CollectionItemId GetInvalid() { return CollectionItemId(); }
// Construct an invalid CollectionItemId.
constexpr CollectionItemId() : value_(-1) {}
// Use the default copy constructor, assignment, and destructor.
CollectionItemId(const CollectionItemId&) = default;
~CollectionItemId() = default;
CollectionItemId& operator=(const CollectionItemId&) = default;
bool IsValid() const { return value_ >= 0; }
// Accesses the raw value.
constexpr int value() const { return value_; }
// Unary operators.
bool operator!() const { return value_ == 0; }
const CollectionItemId operator+() const { return CollectionItemId(value_); }
const CollectionItemId operator-() const { return CollectionItemId(-value_); }
// Increment and decrement operators.
CollectionItemId& operator++() { // ++x
++value_;
return *this;
}
const CollectionItemId operator++(int postfix_flag) { // x++
CollectionItemId temp(*this);
++value_;
return temp;
}
CollectionItemId& operator--() { // --x
--value_;
return *this;
}
const CollectionItemId operator--(int postfix_flag) { // x--
CollectionItemId temp(*this);
--value_;
return temp;
}
// Action-Assignment operators.
CollectionItemId& operator+=(CollectionItemId arg) {
value_ += arg.value_;
return *this;
}
CollectionItemId operator+(CollectionItemId arg) const {
return CollectionItemId(value_ + arg.value_);
}
template <typename ArgType>
CollectionItemId operator+(ArgType arg) const {
return CollectionItemId(value_ + arg);
}
CollectionItemId& operator-=(CollectionItemId arg) {
value_ -= arg.value_;
return *this;
}
CollectionItemId operator-(CollectionItemId arg) const {
return CollectionItemId(value_ - arg.value_);
}
template <typename ArgType>
CollectionItemId operator-(ArgType arg) const {
return CollectionItemId(value_ - arg);
}
template <typename ArgType>
CollectionItemId& operator*=(ArgType arg) {
value_ *= arg;
return *this;
}
CollectionItemId operator*(CollectionItemId arg) const {
return CollectionItemId(value_ * arg.value_);
}
template <typename ArgType>
CollectionItemId operator*(ArgType arg) const {
return CollectionItemId(value_ * arg);
}
template <typename ArgType>
CollectionItemId& operator/=(ArgType arg) {
value_ /= arg;
return *this;
}
CollectionItemId operator/(CollectionItemId arg) const {
return CollectionItemId(value_ / arg.value_);
}
template <typename ArgType>
CollectionItemId operator/(ArgType arg) const {
return CollectionItemId(value_ / arg);
}
template <typename ArgType>
CollectionItemId& operator%=(ArgType arg) {
value_ %= arg;
return *this;
}
CollectionItemId operator%(CollectionItemId arg) const {
return CollectionItemId(value_ % arg.value_);
}
template <typename ArgType>
CollectionItemId operator%(ArgType arg) const {
return CollectionItemId(value_ % arg);
}
inline bool operator>(CollectionItemId rhs) const {
return value_ > rhs.value_;
}
inline bool operator>=(CollectionItemId rhs) const {
return value_ >= rhs.value_;
}
inline bool operator<(CollectionItemId rhs) const {
return value_ < rhs.value_;
}
inline bool operator<=(CollectionItemId rhs) const {
return value_ <= rhs.value_;
}
inline bool operator==(CollectionItemId rhs) const {
return value_ == rhs.value_;
}
inline bool operator!=(CollectionItemId rhs) const {
return value_ != rhs.value_;
}
private:
friend class ::mediapipe::tool::TagMap;
// Initialization from a value.
explicit constexpr CollectionItemId(int init_value) : value_(init_value) {}
// The integer value of type int.
int value_;
};
std::ostream& operator<<(std::ostream& os, CollectionItemId arg);
CollectionItemId operator+(int lhs, CollectionItemId rhs);
CollectionItemId operator-(int lhs, CollectionItemId rhs);
CollectionItemId operator*(int lhs, CollectionItemId rhs);
} // namespace mediapipe
#endif // MEDIAPIPE_FRAMEWORK_COLLECTION_ITEM_ID_H_
+495
View File
@@ -0,0 +1,495 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/collection.h"
#include "absl/strings/str_cat.h"
#include "mediapipe/framework/packet_set.h"
#include "mediapipe/framework/port/gmock.h"
#include "mediapipe/framework/port/gtest.h"
#include "mediapipe/framework/port/status_matchers.h"
#include "mediapipe/framework/tool/tag_map_helper.h"
namespace mediapipe {
namespace {
TEST(CollectionTest, BasicByIndex) {
tool::TagAndNameInfo info;
info.names.push_back("name_1");
info.names.push_back("name_0");
info.names.push_back("name_2");
internal::Collection<int> collection(info);
collection.Index(1) = 101;
collection.Index(0) = 100;
collection.Index(2) = 102;
// Test the stored values.
EXPECT_EQ(100, collection.Index(0));
EXPECT_EQ(101, collection.Index(1));
EXPECT_EQ(102, collection.Index(2));
// Test access using a range based for.
int i = 0;
for (int num : collection) {
EXPECT_EQ(100 + i, num);
++i;
}
}
TEST(CollectionTest, BasicByTag) {
tool::TagAndNameInfo info;
info.names.push_back("name_1");
info.tags.push_back("TAG_1");
info.names.push_back("name_0");
info.tags.push_back("TAG_0");
info.names.push_back("name_2");
info.tags.push_back("TAG_2");
internal::Collection<int> collection(info);
collection.Tag("TAG_1") = 101;
collection.Tag("TAG_0") = 100;
collection.Tag("TAG_2") = 102;
// Test the stored values.
EXPECT_EQ(100, collection.Tag("TAG_0"));
EXPECT_EQ(101, collection.Tag("TAG_1"));
EXPECT_EQ(102, collection.Tag("TAG_2"));
// Test access using a range based for.
int i = 0;
for (int num : collection) {
// Numbers are in sorted order by tag.
EXPECT_EQ(100 + i, num);
++i;
}
}
TEST(CollectionTest, MixedTagAndIndexUsage) {
auto tags_statusor =
tool::CreateTagMap({"TAG_A:a", "TAG_B:1:b", "TAG_A:2:c", "TAG_B:d",
"TAG_C:0:e", "TAG_A:1:f"});
MEDIAPIPE_ASSERT_OK(tags_statusor);
internal::Collection<int> collection1(std::move(tags_statusor.ValueOrDie()));
collection1.Get("TAG_A", 0) = 100;
collection1.Get("TAG_A", 1) = 101;
collection1.Get("TAG_A", 2) = 102;
collection1.Get("TAG_B", 0) = 103;
collection1.Get("TAG_B", 1) = 104;
collection1.Get("TAG_C", 0) = 105;
// Test access using a range based for.
int i = 0;
for (int num : collection1) {
// Numbers are in sorted order by tag and then index.
EXPECT_EQ(100 + i, num);
++i;
}
EXPECT_EQ(6, i);
// Initialize the values of another collection while iterating through
// the entries of the first. This is testing that two collections
// can be looped through in lock step.
internal::Collection<char> collection2(collection1.TagMap());
i = 0;
for (CollectionItemId id = collection1.BeginId(); id < collection1.EndId();
++id) {
// Numbers are in sorted order by tag and then index.
EXPECT_EQ(100 + i, collection1.Get(id));
// Initialize the entries of the second collection.
collection2.Get(id) = 'a' + i;
++i;
}
EXPECT_EQ(6, i);
// Check the second collection.
EXPECT_EQ(6, collection2.NumEntries());
EXPECT_EQ('a', collection2.Get("TAG_A", 0));
EXPECT_EQ('b', collection2.Get("TAG_A", 1));
EXPECT_EQ('c', collection2.Get("TAG_A", 2));
EXPECT_EQ('d', collection2.Get("TAG_B", 0));
EXPECT_EQ('e', collection2.Get("TAG_B", 1));
EXPECT_EQ('f', collection2.Get("TAG_C", 0));
// And check it again with a loop.
i = 0;
for (int num : collection2) {
EXPECT_EQ('a' + i, num);
++i;
}
EXPECT_EQ(6, i);
// Initialize the values of another collection by iterating over
// each tag.
internal::Collection<std::string> collection3(collection1.TagMap());
i = 0;
for (const std::string& tag : collection1.GetTags()) {
int index_in_tag = 0;
for (CollectionItemId id = collection1.BeginId(tag);
id < collection1.EndId(tag); ++id) {
VLOG(1) << "tag: " << tag << " index_in_tag: " << index_in_tag
<< " collection index: " << i;
// Numbers are in sorted order by tag and then index.
EXPECT_EQ(100 + i, collection1.Get(id));
// Initialize the entries of the second collection.
collection3.Get(id) = absl::StrCat(i, " ", tag, " ", index_in_tag);
++i;
++index_in_tag;
}
}
EXPECT_EQ(6, i);
for (CollectionItemId id = collection1.BeginId("TAG_D");
id < collection1.EndId("TAG_D"); ++id) {
EXPECT_FALSE(true) << "iteration through non-existent tag found element.";
}
// Check the second collection.
EXPECT_EQ(6, collection3.NumEntries());
EXPECT_EQ("0 TAG_A 0", collection3.Get("TAG_A", 0));
EXPECT_EQ("1 TAG_A 1", collection3.Get("TAG_A", 1));
EXPECT_EQ("2 TAG_A 2", collection3.Get("TAG_A", 2));
EXPECT_EQ("3 TAG_B 0", collection3.Get("TAG_B", 0));
EXPECT_EQ("4 TAG_B 1", collection3.Get("TAG_B", 1));
EXPECT_EQ("5 TAG_C 0", collection3.Get("TAG_C", 0));
}
TEST(CollectionTest, StaticEmptyCollectionHeapCheck) {
// Ensure that static collections play nicely with the heap checker.
// "new T[0]" returns a non-null pointer which the heap checker has
// issues in tracking. Additionally, allocating of empty arrays is
// also inefficient as it invokes heap management routines.
static auto* collection1 = new PacketSet(tool::CreateTagMap({}).ValueOrDie());
// Heap check issues are most triggered when zero length and non-zero
// length allocations are interleaved. Additionally, this heap check
// wasn't triggered by "char", so a more complex type (Packet) is used.
static auto* collection2 =
new PacketSet(tool::CreateTagMap({"TAG:name"}).ValueOrDie());
static auto* collection3 = new PacketSet(tool::CreateTagMap({}).ValueOrDie());
static auto* collection4 =
new PacketSet(tool::CreateTagMap({"TAG:name"}).ValueOrDie());
static auto* collection5 = new PacketSet(tool::CreateTagMap({}).ValueOrDie());
EXPECT_EQ(0, collection1->NumEntries());
EXPECT_EQ(1, collection2->NumEntries());
EXPECT_EQ(0, collection3->NumEntries());
EXPECT_EQ(1, collection4->NumEntries());
EXPECT_EQ(0, collection5->NumEntries());
}
template <typename T>
::mediapipe::Status TestCollectionWithPointers(
const std::vector<T>& original_values, const T& inject1, const T& inject2) {
std::shared_ptr<tool::TagMap> tag_map =
tool::CreateTagMap({"TAG_A:a", "TAG_B:1:b", "TAG_A:2:c", "TAG_B:d",
"TAG_C:0:e", "TAG_A:1:f"})
.ValueOrDie();
{
// Test a regular collection.
std::vector<T> values = original_values;
internal::Collection<T> collection(tag_map);
collection.Get("TAG_A", 0) = values[0];
collection.Get("TAG_A", 1) = values[1];
collection.Get("TAG_A", 2) = values[2];
collection.Get("TAG_B", 0) = values[3];
collection.Get("TAG_B", 1) = values[4];
collection.Get("TAG_C", 0) = values[5];
const auto* collection_ptr = &collection;
EXPECT_EQ(values[0], collection.Get("TAG_A", 0));
EXPECT_EQ(values[1], collection.Get("TAG_A", 1));
EXPECT_EQ(values[2], collection.Get("TAG_A", 2));
EXPECT_EQ(values[3], collection.Get("TAG_B", 0));
EXPECT_EQ(values[4], collection.Get("TAG_B", 1));
EXPECT_EQ(values[5], collection.Get("TAG_C", 0));
EXPECT_EQ(values[0], collection_ptr->Get("TAG_A", 0));
EXPECT_EQ(values[1], collection_ptr->Get("TAG_A", 1));
EXPECT_EQ(values[2], collection_ptr->Get("TAG_A", 2));
EXPECT_EQ(values[3], collection_ptr->Get("TAG_B", 0));
EXPECT_EQ(values[4], collection_ptr->Get("TAG_B", 1));
EXPECT_EQ(values[5], collection_ptr->Get("TAG_C", 0));
// Test const-ness.
EXPECT_EQ(false, std::is_const<typename std::remove_reference<decltype(
collection.Get("TAG_A", 0))>::type>::value);
EXPECT_EQ(true, std::is_const<typename std::remove_reference<decltype(
collection_ptr->Get("TAG_A", 0))>::type>::value);
// Test access using a range based for.
int i = 0;
for (auto& value : *collection_ptr) {
EXPECT_EQ(values[i], value);
EXPECT_EQ(
true,
std::is_const<
typename std::remove_reference<decltype(value)>::type>::value);
++i;
}
i = 0;
for (auto& value : collection) {
EXPECT_EQ(values[i], value);
EXPECT_EQ(
false,
std::is_const<
typename std::remove_reference<decltype(value)>::type>::value);
++i;
}
// Test the random access operator in the iterator.
// the operator[] should not generally be used.
EXPECT_EQ(values[2], collection_ptr->begin()[2]);
collection.begin()[2] = inject2;
EXPECT_EQ(inject2, collection_ptr->Get("TAG_A", 2));
}
{
// Pointer Collection type with dereference_content set to true.
std::vector<T> values = original_values;
internal::Collection<T, internal::CollectionStorage::kStorePointer>
collection(tag_map);
collection.GetPtr(collection.GetId("TAG_A", 0)) = &values[0];
collection.GetPtr(collection.GetId("TAG_A", 1)) = &values[1];
collection.GetPtr(collection.GetId("TAG_A", 2)) = &values[2];
collection.GetPtr(collection.GetId("TAG_B", 0)) = &values[3];
collection.GetPtr(collection.GetId("TAG_B", 1)) = &values[4];
collection.GetPtr(collection.GetId("TAG_C", 0)) = &values[5];
const auto* collection_ptr = &collection;
EXPECT_EQ(values[0], collection.Get("TAG_A", 0));
EXPECT_EQ(values[1], collection.Get("TAG_A", 1));
EXPECT_EQ(values[2], collection.Get("TAG_A", 2));
EXPECT_EQ(values[3], collection.Get("TAG_B", 0));
EXPECT_EQ(values[4], collection.Get("TAG_B", 1));
EXPECT_EQ(values[5], collection.Get("TAG_C", 0));
EXPECT_EQ(values[0], collection_ptr->Get("TAG_A", 0));
EXPECT_EQ(values[1], collection_ptr->Get("TAG_A", 1));
EXPECT_EQ(values[2], collection_ptr->Get("TAG_A", 2));
EXPECT_EQ(values[3], collection_ptr->Get("TAG_B", 0));
EXPECT_EQ(values[4], collection_ptr->Get("TAG_B", 1));
EXPECT_EQ(values[5], collection_ptr->Get("TAG_C", 0));
// Test const-ness.
EXPECT_EQ(false, std::is_const<typename std::remove_reference<decltype(
collection.Get("TAG_A", 0))>::type>::value);
EXPECT_EQ(true, std::is_const<typename std::remove_reference<decltype(
collection_ptr->Get("TAG_A", 0))>::type>::value);
// Test access using a range based for.
int i = 0;
for (auto& value : *collection_ptr) {
EXPECT_EQ(values[i], value);
EXPECT_EQ(
true,
std::is_const<
typename std::remove_reference<decltype(value)>::type>::value);
++i;
}
i = 0;
for (auto& value : collection) {
EXPECT_EQ(values[i], value);
EXPECT_EQ(
false,
std::is_const<
typename std::remove_reference<decltype(value)>::type>::value);
++i;
}
i = 0;
for (CollectionItemId id = collection_ptr->BeginId();
id < collection_ptr->EndId(); ++id) {
// TODO Test that GetPtr() does not exist for
// storage == kStoreValue.
EXPECT_EQ(&values[i], collection_ptr->GetPtr(id));
EXPECT_EQ(values[i], *collection_ptr->GetPtr(id));
EXPECT_EQ(false, std::is_const<typename std::remove_reference<decltype(
*collection.GetPtr(id))>::type>::value);
EXPECT_EQ(true, std::is_const<typename std::remove_reference<decltype(
*collection_ptr->GetPtr(id))>::type>::value);
++i;
}
T injected = inject1;
collection.GetPtr(collection_ptr->GetId("TAG_A", 2)) = &injected;
EXPECT_EQ(&injected,
collection_ptr->GetPtr(collection_ptr->GetId("TAG_A", 2)));
EXPECT_EQ(injected,
*collection_ptr->GetPtr(collection_ptr->GetId("TAG_A", 2)));
EXPECT_EQ(injected, collection_ptr->Get("TAG_A", 2));
// Test the random access operator in the iterator.
// the operator[] should not generally be used.
EXPECT_EQ(
injected,
collection_ptr->begin()[collection_ptr->GetId("TAG_A", 2).value()]);
collection.begin()[collection_ptr->GetId("TAG_A", 2).value()] = inject2;
EXPECT_EQ(inject2, injected);
// Test access using a range based for.
i = 0;
for (const T& value : *collection_ptr) {
if (i != collection_ptr->GetId("TAG_A", 2).value()) {
EXPECT_EQ(values[i], value);
} else {
EXPECT_EQ(injected, value);
}
++i;
}
}
{
// Pointer Collection type with dereference_content set to false.
std::vector<T> values = original_values;
internal::Collection<T*, internal::CollectionStorage::kStoreValue>
collection(tag_map);
collection.Get("TAG_A", 0) = &values[0];
collection.Get("TAG_A", 1) = &values[1];
collection.Get("TAG_A", 2) = &values[2];
collection.Get("TAG_B", 0) = &values[3];
collection.Get("TAG_B", 1) = &values[4];
collection.Get("TAG_C", 0) = &values[5];
const auto* collection_ptr = &collection;
EXPECT_EQ(values[0], *collection.Get("TAG_A", 0));
EXPECT_EQ(values[1], *collection.Get("TAG_A", 1));
EXPECT_EQ(values[2], *collection.Get("TAG_A", 2));
EXPECT_EQ(values[3], *collection.Get("TAG_B", 0));
EXPECT_EQ(values[4], *collection.Get("TAG_B", 1));
EXPECT_EQ(values[5], *collection.Get("TAG_C", 0));
EXPECT_EQ(&values[0], collection.Get("TAG_A", 0));
EXPECT_EQ(&values[1], collection.Get("TAG_A", 1));
EXPECT_EQ(&values[2], collection.Get("TAG_A", 2));
EXPECT_EQ(&values[3], collection.Get("TAG_B", 0));
EXPECT_EQ(&values[4], collection.Get("TAG_B", 1));
EXPECT_EQ(&values[5], collection.Get("TAG_C", 0));
EXPECT_EQ(values[0], *collection_ptr->Get("TAG_A", 0));
EXPECT_EQ(values[1], *collection_ptr->Get("TAG_A", 1));
EXPECT_EQ(values[2], *collection_ptr->Get("TAG_A", 2));
EXPECT_EQ(values[3], *collection_ptr->Get("TAG_B", 0));
EXPECT_EQ(values[4], *collection_ptr->Get("TAG_B", 1));
EXPECT_EQ(values[5], *collection_ptr->Get("TAG_C", 0));
EXPECT_EQ(&values[0], collection_ptr->Get("TAG_A", 0));
EXPECT_EQ(&values[1], collection_ptr->Get("TAG_A", 1));
EXPECT_EQ(&values[2], collection_ptr->Get("TAG_A", 2));
EXPECT_EQ(&values[3], collection_ptr->Get("TAG_B", 0));
EXPECT_EQ(&values[4], collection_ptr->Get("TAG_B", 1));
EXPECT_EQ(&values[5], collection_ptr->Get("TAG_C", 0));
// Test const-ness.
EXPECT_EQ(false, std::is_const<typename std::remove_reference<decltype(
collection.Get("TAG_A", 0))>::type>::value);
EXPECT_EQ(true, std::is_const<typename std::remove_reference<decltype(
collection_ptr->Get("TAG_A", 0))>::type>::value);
// Test access using a range based for.
int i = 0;
for (auto& value : *collection_ptr) {
EXPECT_EQ(&values[i], value);
EXPECT_EQ(values[i], *value);
EXPECT_EQ(
true,
std::is_const<
typename std::remove_reference<decltype(value)>::type>::value);
// In const collections of pointers it's just the (stored) pointer
// which is const, not the underlying data.
EXPECT_EQ(
false,
std::is_const<
typename std::remove_reference<decltype(*value)>::type>::value);
++i;
}
i = 0;
for (auto& value : collection) {
EXPECT_EQ(&values[i], value);
EXPECT_EQ(values[i], *value);
EXPECT_EQ(
false,
std::is_const<
typename std::remove_reference<decltype(value)>::type>::value);
EXPECT_EQ(
false,
std::is_const<
typename std::remove_reference<decltype(*value)>::type>::value);
++i;
}
T injected = inject1;
collection.Get("TAG_A", 2) = &injected;
EXPECT_EQ(&injected, collection_ptr->Get("TAG_A", 2));
EXPECT_EQ(injected, *collection_ptr->Get("TAG_A", 2));
// Test the random access operator in the iterator.
// the operator[] should not generally be used.
EXPECT_EQ(
&injected,
collection_ptr->begin()[collection_ptr->GetId("TAG_A", 2).value()]);
*collection.begin()[collection_ptr->GetId("TAG_A", 2).value()] = inject2;
EXPECT_EQ(inject2, injected);
// Test access using a range based for.
i = 0;
for (const T* value : *collection_ptr) {
if (i != collection_ptr->GetId("TAG_A", 2).value()) {
EXPECT_EQ(&values[i], value);
EXPECT_EQ(values[i], *value);
} else {
EXPECT_EQ(&injected, value);
EXPECT_EQ(injected, *value);
}
++i;
}
}
return ::mediapipe::OkStatus();
}
TEST(CollectionTest, TestCollectionWithPointersIntAndString) {
MEDIAPIPE_ASSERT_OK(
TestCollectionWithPointers<int>({3, 7, -2, 0, 4, -3}, 17, 10));
MEDIAPIPE_ASSERT_OK(TestCollectionWithPointers<std::string>(
{"a0", "a1", "a2", "b0", "b1", "c0"}, "inject1", "inject2"));
}
TEST(CollectionTest, TestIteratorFunctions) {
std::shared_ptr<tool::TagMap> tag_map =
tool::CreateTagMap({"TAG_A:a", "TAG_B:1:b", "TAG_A:2:c", "TAG_B:d",
"TAG_C:0:e", "TAG_A:1:f"})
.ValueOrDie();
std::vector<std::string> values = {"a0", "a1", "a2", "b0", "b1", "c0"};
internal::Collection<std::string, internal::CollectionStorage::kStorePointer>
collection(tag_map);
collection.GetPtr(collection.GetId("TAG_A", 0)) = &values[0];
collection.GetPtr(collection.GetId("TAG_A", 1)) = &values[1];
collection.GetPtr(collection.GetId("TAG_A", 2)) = &values[2];
collection.GetPtr(collection.GetId("TAG_B", 0)) = &values[3];
collection.GetPtr(collection.GetId("TAG_B", 1)) = &values[4];
collection.GetPtr(collection.GetId("TAG_C", 0)) = &values[5];
EXPECT_EQ(false, std::is_const<typename std::remove_reference<decltype(
collection.begin())>::type>::value);
EXPECT_EQ(values[0], *collection.begin());
EXPECT_EQ(false, collection.begin()->empty());
EXPECT_EQ(false, (*collection.begin()).empty());
collection.begin()->assign("inject3");
EXPECT_EQ(values[0], "inject3");
const auto* collection_ptr = &collection;
EXPECT_EQ(true, std::is_const<typename std::remove_reference<decltype(
*collection_ptr->begin())>::type>::value);
EXPECT_EQ(values[0], *collection_ptr->begin());
EXPECT_EQ(false, collection_ptr->begin()->empty());
EXPECT_EQ(false, (*collection_ptr->begin()).empty());
}
} // namespace
} // namespace mediapipe
+36
View File
@@ -0,0 +1,36 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// The abstract class of counter.
#ifndef MEDIAPIPE_FRAMEWORK_COUNTER_H_
#define MEDIAPIPE_FRAMEWORK_COUNTER_H_
#include "mediapipe/framework/port/integral_types.h"
namespace mediapipe {
class Counter {
public:
Counter() {}
virtual ~Counter() {}
virtual void Increment() = 0;
virtual void IncrementBy(int amount) = 0;
virtual int64 Get() = 0;
};
} // namespace mediapipe
#endif // MEDIAPIPE_FRAMEWORK_COUNTER_H_
+80
View File
@@ -0,0 +1,80 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/counter_factory.h"
#include <vector>
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
namespace mediapipe {
namespace {
// Counter implementation when we're not using Flume.
// TODO: Consider using Dax atomic counters instead of this.
// This class is thread safe.
class BasicCounter : public Counter {
public:
explicit BasicCounter(const std::string& name) : value_(0) {}
void Increment() LOCKS_EXCLUDED(mu_) override {
absl::WriterMutexLock lock(&mu_);
++value_;
}
void IncrementBy(int amount) LOCKS_EXCLUDED(mu_) override {
absl::WriterMutexLock lock(&mu_);
value_ += amount;
}
int64 Get() LOCKS_EXCLUDED(mu_) override {
absl::ReaderMutexLock lock(&mu_);
return value_;
}
private:
absl::Mutex mu_;
int64 value_ GUARDED_BY(mu_);
};
} // namespace
CounterSet::CounterSet() {}
CounterSet::~CounterSet() LOCKS_EXCLUDED(mu_) { PublishCounters(); }
void CounterSet::PublishCounters() LOCKS_EXCLUDED(mu_) {}
void CounterSet::PrintCounters() LOCKS_EXCLUDED(mu_) {
absl::ReaderMutexLock lock(&mu_);
LOG_IF(INFO, !counters_.empty()) << "MediaPipe Counters:";
for (const auto& counter : counters_) {
LOG(INFO) << counter.first << ": " << counter.second->Get();
}
}
Counter* CounterSet::Get(const std::string& name) LOCKS_EXCLUDED(mu_) {
absl::ReaderMutexLock lock(&mu_);
if (!::mediapipe::ContainsKey(counters_, name)) {
return nullptr;
}
return counters_[name].get();
}
Counter* BasicCounterFactory::GetCounter(const std::string& name) {
return counter_set_.Emplace<BasicCounter>(name, name);
}
} // namespace mediapipe
+93
View File
@@ -0,0 +1,93 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_FRAMEWORK_COUNTER_FACTORY_H_
#define MEDIAPIPE_FRAMEWORK_COUNTER_FACTORY_H_
#include <algorithm>
#include <map>
#include <memory>
#include <string>
#include "absl/base/thread_annotations.h"
#include "absl/synchronization/mutex.h"
#include "absl/time/time.h"
#include "mediapipe/framework/counter.h"
#include "mediapipe/framework/port.h"
#include "mediapipe/framework/port/integral_types.h"
#include "mediapipe/framework/port/map_util.h"
namespace mediapipe {
// Holds a map of counter names to counter unique_ptrs.
// This class is thread safe.
class CounterSet {
public:
CounterSet();
// In builds with streamz export enabled, this will synchronously export
// the final counter values.
~CounterSet();
// Prints the values of all the counters.
// A call to PublishCounters will reset all counters.
void PrintCounters();
// Publishes the vales of all the counters for monitoring and resets
// all internal counters.
void PublishCounters();
// Adds a counter of the given type by constructing the counter in place.
// Returns a pointer to the new counter or if the counter already exists
// to the existing pointer.
template <typename CounterType, typename... Args>
Counter* Emplace(const std::string& name, Args&&... args)
LOCKS_EXCLUDED(mu_) {
absl::WriterMutexLock lock(&mu_);
std::unique_ptr<Counter>* existing_counter = FindOrNull(counters_, name);
if (existing_counter) {
return existing_counter->get();
}
Counter* counter = new CounterType(std::forward<Args>(args)...);
counters_[name].reset(counter);
return counter;
}
// Retrieves the counter with the given name; return nullptr if it doesn't
// exist.
Counter* Get(const std::string& name);
private:
absl::Mutex mu_;
std::map<std::string, std::unique_ptr<Counter>> counters_ GUARDED_BY(mu_);
};
// Generic counter factory
class CounterFactory {
public:
virtual ~CounterFactory() {}
virtual Counter* GetCounter(const std::string& name) = 0;
CounterSet* GetCounterSet() { return &counter_set_; }
protected:
CounterSet counter_set_;
};
// Counter factory that makes the counters be our own basic counters.
class BasicCounterFactory : public CounterFactory {
public:
~BasicCounterFactory() override {}
Counter* GetCounter(const std::string& name) override;
};
} // namespace mediapipe
#endif // MEDIAPIPE_FRAMEWORK_COUNTER_FACTORY_H_
@@ -0,0 +1,27 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/delegating_executor.h"
#include <utility>
namespace mediapipe {
namespace internal {
void DelegatingExecutor::Schedule(std::function<void()> task) {
callback_(std::move(task));
}
} // namespace internal
} // namespace mediapipe
+38
View File
@@ -0,0 +1,38 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_FRAMEWORK_DELEGATING_EXECUTOR_H_
#define MEDIAPIPE_FRAMEWORK_DELEGATING_EXECUTOR_H_
#include "mediapipe/framework/executor.h"
namespace mediapipe {
namespace internal {
// An executor that delegates the running of tasks using a callback.
class DelegatingExecutor : public Executor {
public:
explicit DelegatingExecutor(
std::function<void(std::function<void()>)> callback)
: callback_(std::move(callback)) {}
void Schedule(std::function<void()> task) override;
private:
std::function<void(std::function<void()>)> callback_;
};
} // namespace internal
} // namespace mediapipe
#endif // MEDIAPIPE_FRAMEWORK_DELEGATING_EXECUTOR_H_
+83
View File
@@ -0,0 +1,83 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_FRAMEWORK_DEMANGLE_H_
#define MEDIAPIPE_FRAMEWORK_DEMANGLE_H_
// We only support some compilers that support __cxa_demangle.
// TODO: Checks if Android NDK has fixed this issue or not.
#if defined(__ANDROID__) && (defined(__i386__) || defined(__x86_64__))
#define HAS_CXA_DEMANGLE 0
#elif (__GNUC__ >= 4 || (__GNUC__ >= 3 && __GNUC_MINOR__ >= 4)) && \
!defined(__mips__)
#define HAS_CXA_DEMANGLE 1
#elif defined(__clang__) && !defined(_MSC_VER)
#define HAS_CXA_DEMANGLE 1
#else
#define HAS_CXA_DEMANGLE 0
#endif
#include <stdlib.h>
#include <string>
#if HAS_CXA_DEMANGLE
#include <cxxabi.h>
#endif
namespace mediapipe {
// Demangle a mangled symbol name and return the demangled name.
// If 'mangled' isn't mangled in the first place, this function
// simply returns 'mangled' as is.
//
// This function is used for demangling mangled symbol names such as
// '_Z3bazifdPv'. It uses abi::__cxa_demangle() if your compiler has
// the API. Otherwise, this function simply returns 'mangled' as is.
//
// Currently, we support only GCC 3.4.x or later for the following
// reasons.
//
// - GCC 2.95.3 doesn't have cxxabi.h
// - GCC 3.3.5 and ICC 9.0 have a bug. Their abi::__cxa_demangle()
// returns junk values for non-mangled symbol names (ex. function
// names in C linkage). For example,
// abi::__cxa_demangle("main", 0, 0, &status)
// returns "unsigned long" and the status code is 0 (successful).
//
// Also,
//
// - MIPS is not supported because abi::__cxa_demangle() is not defined.
// - Android x86 is not supported because STLs don't define __cxa_demangle
//
// Prefer using MediaPipeTypeStringOrDemangled<T>() when possible (defined
// in type_map.h).
inline std::string Demangle(const char* mangled) {
int status = 0;
char* demangled = nullptr;
#if HAS_CXA_DEMANGLE
demangled = abi::__cxa_demangle(mangled, nullptr, nullptr, &status);
#endif
std::string out;
if (status == 0 && demangled != nullptr) { // Demangling succeeeded.
out.append(demangled);
free(demangled);
} else {
out.append(mangled);
}
return out;
}
} // namespace mediapipe
#endif // MEDIAPIPE_FRAMEWORK_DEMANGLE_H_
+450
View File
@@ -0,0 +1,450 @@
# Copyright 2019 The MediaPipe Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Description:
# The dependencies of mediapipe.
licenses(["notice"]) # Apache 2.0
load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_proto_library")
load("//mediapipe/framework/port:build_config.bzl", "mediapipe_py_proto_library")
package(default_visibility = ["//visibility:private"])
proto_library(
name = "proto_descriptor_proto",
srcs = ["proto_descriptor.proto"],
visibility = ["//mediapipe/framework:__subpackages__"],
)
mediapipe_cc_proto_library(
name = "proto_descriptor_cc_proto",
srcs = ["proto_descriptor.proto"],
visibility = ["//visibility:public"],
deps = [":proto_descriptor_proto"],
)
cc_library(
name = "aligned_malloc_and_free",
hdrs = ["aligned_malloc_and_free.h"],
visibility = ["//visibility:public"],
)
cc_library(
name = "cleanup",
hdrs = ["cleanup.h"],
visibility = ["//visibility:public"],
deps = ["@com_google_absl//absl/base:core_headers"],
)
cc_library(
name = "clock",
srcs = [
"clock.cc",
"monotonic_clock.cc",
],
hdrs = [
"clock.h",
"monotonic_clock.h",
],
visibility = ["//visibility:public"],
deps = [
"//mediapipe/framework/port:logging",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/synchronization",
"@com_google_absl//absl/time",
],
)
cc_library(
name = "message_matchers",
testonly = True,
hdrs = ["message_matchers.h"],
visibility = ["//visibility:public"],
deps = [
"//mediapipe/framework/port:core_proto",
"//mediapipe/framework/port:gtest_main",
],
)
cc_library(
name = "file_path",
srcs = ["file_path.cc"],
hdrs = ["file_path.h"],
visibility = ["//visibility:public"],
deps = [
"@com_google_absl//absl/strings",
],
)
cc_library(
name = "file_helpers",
srcs = ["file_helpers.cc"],
hdrs = ["file_helpers.h"],
visibility = ["//visibility:public"],
deps = [
":file_path",
":status",
"@com_google_absl//absl/strings",
],
)
cc_library(
name = "intops",
hdrs = [
"safe_int.h",
"strong_int.h",
],
visibility = ["//visibility:public"],
deps = [
"//mediapipe/framework/port",
"//mediapipe/framework/port:integral_types",
"//mediapipe/framework/port:logging",
"@com_google_absl//absl/base:core_headers",
],
)
cc_library(
name = "image_resizer",
hdrs = ["image_resizer.h"],
visibility = ["//visibility:public"],
deps = [
"//mediapipe/framework/port:integral_types",
"//mediapipe/framework/port:opencv_imgproc",
],
)
cc_library(
name = "map_util",
hdrs = ["map_util.h"],
# Use this library through "mediapipe/framework/port:map_util".
visibility = ["//mediapipe/framework/port:__pkg__"],
deps = ["//mediapipe/framework/port:logging"],
)
cc_library(
name = "mathutil",
hdrs = ["mathutil.h"],
visibility = ["//visibility:public"],
deps = [
"//mediapipe/framework/port:integral_types",
"//mediapipe/framework/port:logging",
],
)
cc_library(
name = "numbers",
hdrs = ["numbers.h"],
visibility = ["//mediapipe/framework/port:__pkg__"],
deps = [
"//mediapipe/framework/port:integral_types",
"@com_google_absl//absl/strings",
],
)
cc_library(
name = "no_destructor",
hdrs = ["no_destructor.h"],
visibility = ["//visibility:public"],
)
cc_library(
name = "point",
hdrs = ["point2.h"],
# Use this library through "mediapipe/framework/port:point".
visibility = ["//mediapipe/framework/port:__pkg__"],
deps = [
":mathutil",
":vector",
"//mediapipe/framework/port:integral_types",
"//mediapipe/framework/port:logging",
],
)
cc_library(
name = "random",
hdrs = ["random_base.h"],
visibility = ["//visibility:public"],
)
cc_library(
name = "rectangle",
hdrs = ["rectangle.h"],
# Use this library through "mediapipe/framework/port:rectangle".
visibility = ["//mediapipe/framework/port:__pkg__"],
deps = [
":point",
":vector",
"//mediapipe/framework/port:integral_types",
"//mediapipe/framework/port:logging",
],
)
cc_library(
name = "registration_token",
srcs = ["registration_token.cc"],
hdrs = ["registration_token.h"],
visibility = ["//visibility:public"],
)
cc_library(
name = "registration",
srcs = ["registration.cc"],
hdrs = ["registration.h"],
visibility = ["//visibility:public"],
deps = [
":registration_token",
"//mediapipe/framework/port:logging",
"//mediapipe/framework/port:status",
"//mediapipe/framework/port:statusor",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/meta:type_traits",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/synchronization",
],
)
cc_library(
name = "singleton",
hdrs = ["singleton.h"],
# Use this library through "mediapipe/framework/port:singleton".
visibility = ["//mediapipe/framework/port:__pkg__"],
deps = [
"@com_google_absl//absl/synchronization",
],
)
cc_library(
name = "source_location",
hdrs = ["source_location.h"],
# Use this library through "mediapipe/framework/port:source_location".
visibility = ["//mediapipe/framework/port:__pkg__"],
)
cc_library(
name = "status",
srcs = [
"status.cc",
"status_builder.cc",
],
hdrs = [
"canonical_errors.h",
"status.h",
"status_builder.h",
"status_macros.h",
],
# Use this library through "mediapipe/framework/port:status".
visibility = ["//mediapipe/framework/port:__pkg__"],
deps = [
":source_location",
"//mediapipe/framework/port:logging",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/strings",
],
)
cc_library(
name = "statusor",
srcs = ["statusor.cc"],
hdrs = [
"statusor.h",
"statusor_internals.h",
],
# Use this library through "mediapipe/framework/port:statusor".
visibility = ["//mediapipe/framework/port:__pkg__"],
deps = [
":status",
"//mediapipe/framework/port:logging",
"@com_google_absl//absl/base:core_headers",
],
)
cc_library(
name = "status_matchers",
testonly = 1,
hdrs = ["status_matchers.h"],
# Use this library through "mediapipe/framework/port:gtest_main".
visibility = ["//mediapipe/framework/port:__pkg__"],
deps = [
":status",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "ret_check",
srcs = ["ret_check.cc"],
hdrs = ["ret_check.h"],
# Use this library through "mediapipe/framework/port:ret_check".
visibility = ["//mediapipe/framework/port:__pkg__"],
deps = [
":status",
"@com_google_absl//absl/base:core_headers",
],
)
cc_library(
name = "thread_options",
hdrs = ["thread_options.h"],
visibility = ["//visibility:public"],
)
cc_library(
name = "threadpool",
srcs = ["threadpool.cc"],
hdrs = ["threadpool.h"],
# Use this library through "mediapipe/framework/port:threadpool".
visibility = ["//mediapipe/framework/port:__pkg__"],
deps = [
":thread_options",
"//mediapipe/framework/port:logging",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/synchronization",
],
)
cc_library(
name = "topologicalsorter",
srcs = ["topologicalsorter.cc"],
hdrs = ["topologicalsorter.h"],
# Use this library through "mediapipe/framework/port:topologicalsorter".
visibility = ["//mediapipe/framework/port:__pkg__"],
deps = [
"//mediapipe/framework/port:logging",
],
)
cc_library(
name = "vector",
hdrs = ["vector.h"],
# Use this library through "mediapipe/framework/port:vector".
visibility = ["//mediapipe/framework/port:__pkg__"],
deps = [
"//mediapipe/framework/port:integral_types",
"//mediapipe/framework/port:logging",
"@com_google_absl//absl/utility",
],
)
cc_test(
name = "mathutil_unittest",
srcs = ["mathutil_unittest.cc"],
visibility = ["//visibility:public"],
deps = [
":mathutil",
"//mediapipe/framework/port:benchmark",
"//mediapipe/framework/port:gtest_main",
],
)
cc_test(
name = "registration_token_test",
srcs = ["registration_token_test.cc"],
linkstatic = 1,
visibility = ["//visibility:public"],
deps = [
":registration_token",
"//mediapipe/framework/port:gtest_main",
],
)
cc_test(
name = "safe_int_test",
size = "small",
timeout = "long",
srcs = ["safe_int_test.cc"],
linkstatic = 1,
visibility = ["//visibility:public"],
deps = [
":intops",
"//mediapipe/framework/port:gtest_main",
"@com_google_absl//absl/base:core_headers",
],
)
cc_test(
name = "monotonic_clock_test",
srcs = ["monotonic_clock_test.cc"],
linkstatic = 1,
visibility = ["//visibility:public"],
deps = [
":clock",
"//mediapipe/framework/port:gtest_main",
"//mediapipe/framework/port:integral_types",
"//mediapipe/framework/port:logging",
"//mediapipe/framework/port:threadpool",
"//mediapipe/framework/tool:simulation_clock",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/synchronization",
"@com_google_absl//absl/time",
],
)
cc_test(
name = "status_builder_test",
size = "small",
srcs = ["status_builder_test.cc"],
linkstatic = 1,
deps = [
":status",
"//mediapipe/framework/port:gtest_main",
],
)
cc_test(
name = "status_test",
size = "small",
srcs = ["status_test.cc"],
linkstatic = 1,
deps = [
":status",
":status_matchers",
"//mediapipe/framework/port:gtest_main",
],
)
cc_test(
name = "statusor_test",
size = "small",
srcs = ["statusor_test.cc"],
linkstatic = 1,
deps = [
":status",
":statusor",
"//mediapipe/framework/port:gtest_main",
],
)
cc_test(
name = "topologicalsorter_test",
srcs = ["topologicalsorter_test.cc"],
linkstatic = 1,
deps = [
":topologicalsorter",
"//mediapipe/framework/port:gtest_main",
],
)
cc_test(
name = "threadpool_test",
srcs = ["threadpool_test.cc"],
linkstatic = 1,
deps = [
":threadpool",
"//mediapipe/framework/port:gtest_main",
"@com_google_absl//absl/synchronization",
],
)
@@ -0,0 +1,43 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_DEPS_ALIGNED_MALLOC_AND_FREE_H_
#define MEDIAPIPE_DEPS_ALIGNED_MALLOC_AND_FREE_H_
#include <stdlib.h> // for free(), aligned_alloc(),
#if defined(__ANDROID__)
#include <malloc.h> // for memalign()
#endif
inline void *aligned_malloc(size_t size, int minimum_alignment) {
#if defined(__ANDROID__) || defined(OS_ANDROID)
return memalign(minimum_alignment, size);
#else // !__ANDROID__ && !OS_ANDROID
void *ptr = nullptr;
// posix_memalign requires that the requested alignment be at least
// sizeof(void*). In this case, fall back on malloc which should return memory
// aligned to at least the size of a pointer.
const int required_alignment = sizeof(void *);
if (minimum_alignment < required_alignment) return malloc(size);
if (posix_memalign(&ptr, static_cast<size_t>(minimum_alignment), size) != 0)
return nullptr;
else
return ptr;
#endif
}
inline void aligned_free(void *aligned_memory) { free(aligned_memory); }
#endif // MEDIAPIPE_DEPS_ALIGNED_MALLOC_AND_FREE_H_
@@ -0,0 +1,86 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_DEPS_CANONICAL_ERRORS_H_
#define MEDIAPIPE_DEPS_CANONICAL_ERRORS_H_
#include "mediapipe/framework/deps/status.h"
namespace mediapipe {
// Each of the functions below creates a canonical error with the given
// message. The error code of the returned status object matches the name of
// the function.
inline ::mediapipe::Status AlreadyExistsError(absl::string_view message) {
return ::mediapipe::Status(::mediapipe::StatusCode::kAlreadyExists, message);
}
inline ::mediapipe::Status CancelledError() {
return ::mediapipe::Status(::mediapipe::StatusCode::kCancelled, "");
}
inline ::mediapipe::Status CancelledError(absl::string_view message) {
return ::mediapipe::Status(::mediapipe::StatusCode::kCancelled, message);
}
inline ::mediapipe::Status InternalError(absl::string_view message) {
return ::mediapipe::Status(::mediapipe::StatusCode::kInternal, message);
}
inline ::mediapipe::Status InvalidArgumentError(absl::string_view message) {
return ::mediapipe::Status(::mediapipe::StatusCode::kInvalidArgument,
message);
}
inline ::mediapipe::Status FailedPreconditionError(absl::string_view message) {
return ::mediapipe::Status(::mediapipe::StatusCode::kFailedPrecondition,
message);
}
inline ::mediapipe::Status NotFoundError(absl::string_view message) {
return ::mediapipe::Status(::mediapipe::StatusCode::kNotFound, message);
}
inline ::mediapipe::Status OutOfRangeError(absl::string_view message) {
return ::mediapipe::Status(::mediapipe::StatusCode::kOutOfRange, message);
}
inline ::mediapipe::Status PermissionDeniedError(absl::string_view message) {
return ::mediapipe::Status(::mediapipe::StatusCode::kPermissionDenied,
message);
}
inline ::mediapipe::Status UnimplementedError(absl::string_view message) {
return ::mediapipe::Status(::mediapipe::StatusCode::kUnimplemented, message);
}
inline ::mediapipe::Status UnknownError(absl::string_view message) {
return ::mediapipe::Status(::mediapipe::StatusCode::kUnknown, message);
}
inline ::mediapipe::Status UnavailableError(absl::string_view message) {
return ::mediapipe::Status(::mediapipe::StatusCode::kUnavailable, message);
}
inline bool IsCancelled(const ::mediapipe::Status& status) {
return status.code() == ::mediapipe::StatusCode::kCancelled;
}
inline bool IsNotFound(const ::mediapipe::Status& status) {
return status.code() == ::mediapipe::StatusCode::kNotFound;
}
} // namespace mediapipe
#endif // MEDIAPIPE_DEPS_CANONICAL_ERRORS_H_
+105
View File
@@ -0,0 +1,105 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// MakeCleanup(f) returns an RAII cleanup object that calls 'f' in its
// destructor. The easiest way to use MakeCleanup is with a lambda argument,
// capturing the return value in an 'auto' local variable. Most users will not
// need more sophisticated syntax than that.
//
// Example:
// void func() {}
// FILE* fp = fopen("data.txt", "r");
// if (fp == nullptr) return;
// auto fp_cleaner = ::mediapipe::MakeCleanup([fp] { fclose(fp); });
// // No matter what, fclose(fp) will happen.
// DataObject d;
// while (ReadDataObject(fp, &d)) {
// if (d.IsBad()) {
// LOG(ERROR) << "Bad Data";
// return;
// }
// PushGoodData(d);
// }
// }
#ifndef MEDIAPIPE_DEPS_CLEANUP_H_
#define MEDIAPIPE_DEPS_CLEANUP_H_
#include <type_traits>
#include <utility>
#include "absl/base/attributes.h"
namespace mediapipe {
template <typename F>
class Cleanup {
public:
Cleanup() : released_(true), f_() {}
template <typename G>
explicit Cleanup(G&& f) // NOLINT
: f_(std::forward<G>(f)) {} // NOLINT(build/c++11)
Cleanup(Cleanup&& src) // NOLINT
: released_(src.is_released()), f_(src.release()) {}
// Implicitly move-constructible from any compatible Cleanup<G>.
// The source will be released as if src.release() were called.
// A moved-from Cleanup can be safely destroyed or reassigned.
template <typename G>
Cleanup(Cleanup<G>&& src) // NOLINT
: released_(src.is_released()), f_(src.release()) {}
// Assignment to a Cleanup object behaves like destroying it
// and making a new one in its place, analogous to unique_ptr
// semantics.
Cleanup& operator=(Cleanup&& src) { // NOLINT
if (!released_) f_();
released_ = src.released_;
f_ = src.release();
return *this;
}
~Cleanup() {
if (!released_) f_();
}
// Releases the cleanup function instead of running it.
// Hint: use c.release()() to run early.
F release() {
released_ = true;
return std::move(f_);
}
bool is_released() const { return released_; }
private:
static_assert(!std::is_reference<F>::value, "F must not be a reference");
bool released_ = false;
F f_;
};
template <int&... ExplicitParameterBarrier, typename F,
typename DecayF = typename std::decay<F>::type>
ABSL_MUST_USE_RESULT Cleanup<DecayF> MakeCleanup(F&& f) {
static_assert(sizeof...(ExplicitParameterBarrier) == 0,
"No explicit template arguments.");
return Cleanup<DecayF>(std::forward<F>(f));
}
} // namespace mediapipe
#endif // MEDIAPIPE_DEPS_CLEANUP_H_
+55
View File
@@ -0,0 +1,55 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/deps/clock.h"
#include "absl/time/clock.h"
#include "mediapipe/framework/port/logging.h"
namespace mediapipe {
namespace {
// -----------------------------------------------------------------
// RealTimeClock
//
// This class is thread-safe.
class RealTimeClock : public Clock {
public:
virtual ~RealTimeClock() {
LOG(FATAL) << "RealTimeClock should never be destroyed";
}
absl::Time TimeNow() override { return absl::Now(); }
void Sleep(absl::Duration d) override { absl::SleepFor(d); }
void SleepUntil(absl::Time wakeup_time) override {
absl::Duration d = wakeup_time - TimeNow();
if (d > absl::ZeroDuration()) {
Sleep(d);
}
}
};
} // namespace
Clock::~Clock() {}
Clock* Clock::RealClock() {
static RealTimeClock* rtclock = new RealTimeClock;
return rtclock;
}
} // namespace mediapipe
+68
View File
@@ -0,0 +1,68 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_DEPS_CLOCK_H_
#define MEDIAPIPE_DEPS_CLOCK_H_
#include "absl/time/time.h"
namespace mediapipe {
// An abstract interface representing a Clock, which is an object that can
// tell you the current time, and sleep.
//
// This interface allows decoupling code that uses time from the code that
// creates a point in time. You can use this to your advantage by injecting
// Clocks into interfaces rather than having implementations call absl::Now()
// directly.
//
// The Clock::RealClock() function returns a pointer (that you do not own)
// to the global realtime clock.
//
// Example:
//
// bool IsWeekend(Clock* clock) {
// absl::Time now = clock->TimeNow();
// // ... code to check if 'now' is a weekend.
// }
//
// // Production code.
// IsWeekend(Clock::RealClock());
//
// // Test code:
// MyTestClock test_clock(SATURDAY);
// IsWeekend(&test_clock);
//
class Clock {
public:
// Returns a pointer to the global realtime clock. The caller does not
// own the returned pointer and should not delete it. The returned clock
// is thread-safe.
static Clock* RealClock();
virtual ~Clock();
// Returns the current time.
virtual absl::Time TimeNow() = 0;
// Sleeps for the specified duration.
virtual void Sleep(absl::Duration d) = 0;
// Sleeps until the specified time.
virtual void SleepUntil(absl::Time wakeup_time) = 0;
};
} // namespace mediapipe
#endif // MEDIAPIPE_DEPS_CLOCK_H_
+122
View File
@@ -0,0 +1,122 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/deps/file_helpers.h"
#include <dirent.h>
#include <stdio.h>
#include <sys/stat.h>
#include <cerrno>
#include "mediapipe/framework/deps/canonical_errors.h"
#include "mediapipe/framework/deps/file_path.h"
#include "mediapipe/framework/deps/status_builder.h"
namespace mediapipe {
namespace file {
::mediapipe::Status GetContents(absl::string_view file_name,
std::string* output) {
FILE* fp = fopen(file_name.data(), "r");
if (fp == NULL) {
return ::mediapipe::InvalidArgumentErrorBuilder(MEDIAPIPE_LOC)
<< "Can't find file: " << file_name;
}
output->clear();
while (!feof(fp)) {
char buf[4096];
size_t ret = fread(buf, 1, 4096, fp);
if (ret == 0 && ferror(fp)) {
return ::mediapipe::InternalErrorBuilder(MEDIAPIPE_LOC)
<< "Error while reading file: " << file_name;
}
output->append(std::string(buf, ret));
}
fclose(fp);
return ::mediapipe::OkStatus();
}
::mediapipe::Status SetContents(absl::string_view file_name,
absl::string_view content) {
FILE* fp = fopen(file_name.data(), "w");
if (fp == NULL) {
return ::mediapipe::InvalidArgumentErrorBuilder(MEDIAPIPE_LOC)
<< "Can't open file: " << file_name;
}
fwrite(content.data(), sizeof(char), content.size(), fp);
size_t ret = fclose(fp);
if (ret == 0 && ferror(fp)) {
return ::mediapipe::InternalErrorBuilder(MEDIAPIPE_LOC)
<< "Error while writing file: " << file_name;
}
return ::mediapipe::OkStatus();
}
::mediapipe::Status MatchInTopSubdirectories(
const std::string& parent_directory, const std::string& file_name,
std::vector<std::string>* results) {
DIR* dir = opendir(parent_directory.c_str());
CHECK(dir);
// Iterates through the parent direcotry.
while (true) {
struct dirent* dir_ent = readdir(dir);
if (dir_ent == nullptr) {
break;
}
if (std::string(dir_ent->d_name) == "." ||
std::string(dir_ent->d_name) == "..") {
continue;
}
std::string subpath =
JoinPath(parent_directory, std::string(dir_ent->d_name));
DIR* sub_dir = opendir(subpath.c_str());
// Iterates through the subdirecotry to find file matches.
while (true) {
struct dirent* dir_ent_2 = readdir(sub_dir);
if (dir_ent_2 == nullptr) {
break;
}
if (std::string(dir_ent_2->d_name) == "." ||
std::string(dir_ent_2->d_name) == "..") {
continue;
}
if (absl::EndsWith(std::string(dir_ent_2->d_name), file_name)) {
results->push_back(JoinPath(subpath, std::string(dir_ent_2->d_name)));
}
}
closedir(sub_dir);
}
closedir(dir);
return ::mediapipe::OkStatus();
}
::mediapipe::Status Exists(absl::string_view file_name) {
struct stat buffer;
int status;
status = stat(file_name.data(), &buffer);
if (status == 0) {
return ::mediapipe::OkStatus();
}
switch (errno) {
case EACCES:
return ::mediapipe::PermissionDeniedError("Insufficient permissions.");
default:
return ::mediapipe::NotFoundError("The path does not exist.");
}
}
} // namespace file
} // namespace mediapipe
+38
View File
@@ -0,0 +1,38 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_DEPS_FILE_HELPERS_H_
#define MEDIAPIPE_DEPS_FILE_HELPERS_H_
#include "absl/strings/match.h"
#include "mediapipe/framework/deps/status.h"
namespace mediapipe {
namespace file {
::mediapipe::Status GetContents(absl::string_view file_name,
std::string* output);
::mediapipe::Status SetContents(absl::string_view file_name,
absl::string_view content);
::mediapipe::Status MatchInTopSubdirectories(
const std::string& parent_directory, const std::string& file_name,
std::vector<std::string>* results);
::mediapipe::Status Exists(absl::string_view file_name);
} // namespace file
} // namespace mediapipe
#endif // MEDIAPIPE_DEPS_FILE_HELPERS_H_
+120
View File
@@ -0,0 +1,120 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/deps/file_path.h"
#include "absl/strings/str_cat.h"
namespace mediapipe {
namespace file {
// 40% of the time in JoinPath() is from calls with 2 arguments, so we
// specialize that case.
std::string JoinPath(absl::string_view path1, absl::string_view path2) {
if (path1.empty()) return std::string(path2);
if (path2.empty()) return std::string(path1);
if (path1.back() == '/') {
if (path2.front() == '/')
return absl::StrCat(path1, absl::ClippedSubstr(path2, 1));
} else {
if (path2.front() != '/') return absl::StrCat(path1, "/", path2);
}
return absl::StrCat(path1, path2);
}
namespace internal {
// Given a collection of file paths, append them all together,
// ensuring that the proper path separators are inserted between them.
std::string JoinPathImpl(bool honor_abs,
std::initializer_list<absl::string_view> paths) {
std::string result;
if (paths.size() != 0) {
// This size calculation is worst-case: it assumes one extra "/" for every
// path other than the first.
size_t total_size = paths.size() - 1;
for (const absl::string_view path : paths) total_size += path.size();
result.resize(total_size);
auto begin = result.begin();
auto out = begin;
bool trailing_slash = false;
for (absl::string_view path : paths) {
if (path.empty()) continue;
if (path.front() == '/') {
if (honor_abs) {
out = begin; // wipe out whatever we've built up so far.
} else if (trailing_slash) {
path.remove_prefix(1);
}
} else {
if (!trailing_slash && out != begin) *out++ = '/';
}
const size_t this_size = path.size();
memcpy(&*out, path.data(), this_size);
out += this_size;
trailing_slash = out[-1] == '/';
}
result.erase(out - begin);
}
return result;
}
// Return the parts of the basename of path, split on the final ".".
// If there is no "." in the basename or "." is the final character in the
// basename, the second value will be empty.
std::pair<absl::string_view, absl::string_view> SplitBasename(
absl::string_view path) {
path = Basename(path);
absl::string_view::size_type pos = path.find_last_of('.');
if (pos == absl::string_view::npos)
return std::make_pair(path, absl::ClippedSubstr(path, path.size(), 0));
return std::make_pair(path.substr(0, pos),
absl::ClippedSubstr(path, pos + 1));
}
} // namespace internal
absl::string_view Dirname(absl::string_view path) {
return SplitPath(path).first;
}
absl::string_view Basename(absl::string_view path) {
return SplitPath(path).second;
}
std::pair<absl::string_view, absl::string_view> SplitPath(
absl::string_view path) {
absl::string_view::size_type pos = path.find_last_of('/');
// Handle the case with no '/' in 'path'.
if (pos == absl::string_view::npos)
return std::make_pair(path.substr(0, 0), path);
// Handle the case with a single leading '/' in 'path'.
if (pos == 0)
return std::make_pair(path.substr(0, 1), absl::ClippedSubstr(path, 1));
return std::make_pair(path.substr(0, pos),
absl::ClippedSubstr(path, pos + 1));
}
absl::string_view Extension(absl::string_view path) {
return internal::SplitBasename(path).second;
}
} // namespace file
} // namespace mediapipe
+96
View File
@@ -0,0 +1,96 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_DEPS_FILE_PATH_H_
#define MEDIAPIPE_DEPS_FILE_PATH_H_
#include <initializer_list>
#include <string>
#include "absl/strings/string_view.h"
// A set of file pathname manipulation routines.
namespace mediapipe {
namespace file {
namespace internal {
// Not part of the public API.
std::string JoinPathImpl(bool honor_abs,
std::initializer_list<absl::string_view> paths);
} // namespace internal
// Join multiple paths together.
// JoinPath and JoinPathRespectAbsolute have slightly different semantics.
// JoinPath unconditionally joins all paths together, whereas
// JoinPathRespectAbsolute ignores any segments prior to the last absolute
// path. For example:
//
// Arguments | JoinPath | JoinPathRespectAbsolute
// ---------------------------+---------------------+-----------------------
// '/foo', 'bar' | /foo/bar | /foo/bar
// '/foo/', 'bar' | /foo/bar | /foo/bar
// '/foo', '/bar' | /foo/bar | /bar
// '/foo', '/bar', '/baz' | /foo/bar/baz | /baz
//
// All paths will be treated as relative paths, regardless of whether or not
// they start with a leading '/'. That is, all paths will be concatenated
// together, with the appropriate path separator inserted in between.
// Arguments must be convertible to absl::string_view.
//
// Usage:
// std::string path = file::JoinPath("/cns", dirname, filename);
// std::string path = file::JoinPath("./", filename);
//
// 0, 1, 2-path specializations exist to optimize common cases.
inline std::string JoinPath() { return std::string(); }
inline std::string JoinPath(absl::string_view path) {
return std::string(path.data(), path.size());
}
std::string JoinPath(absl::string_view path1, absl::string_view path2);
template <typename... T>
inline std::string JoinPath(absl::string_view path1, absl::string_view path2,
absl::string_view path3, const T&... args) {
return internal::JoinPathImpl(false, {path1, path2, path3, args...});
}
// Returns the part of the path before the final "/", EXCEPT:
// * If there is a single leading "/" in the path, the result will be the
// leading "/".
// * If there is no "/" in the path, the result is the empty prefix of the
// input std::string.
absl::string_view Dirname(absl::string_view path);
// Return the parts of the path, split on the final "/". If there is no
// "/" in the path, the first part of the output is empty and the second
// is the input. If the only "/" in the path is the first character, it is
// the first part of the output.
std::pair<absl::string_view, absl::string_view> SplitPath(
absl::string_view path);
// Returns the part of the path after the final "/". If there is no
// "/" in the path, the result is the same as the input.
// Note that this function's behavior differs from the Unix basename
// command if path ends with "/". For such paths, this function returns the
// empty std::string.
absl::string_view Basename(absl::string_view path);
// Returns the part of the basename of path after the final ".". If
// there is no "." in the basename, the result is empty.
absl::string_view Extension(absl::string_view path);
} // namespace file
} // namespace mediapipe
#endif // MEDIAPIPE_DEPS_FILE_PATH_H_
+36
View File
@@ -0,0 +1,36 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_DEPS_IMAGE_RESIZER_H_
#define MEDIAPIPE_DEPS_IMAGE_RESIZER_H_
#include "mediapipe/framework/port/integral_types.h"
#include "mediapipe/framework/port/opencv_imgproc_inc.h"
namespace mediapipe {
class ImageResizer {
public:
ImageResizer(double sharpen_coeff) {}
bool Resize(const cv::Mat& input_mat, cv::Mat* output_mat) {
cv::resize(input_mat, *output_mat, output_mat->size(), 0, 0,
cv::INTER_AREA);
return true;
}
};
} // namespace mediapipe
#endif // MEDIAPIPE_DEPS_IMAGE_RESIZER_H_
+152
View File
@@ -0,0 +1,152 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// This file provides utility functions for use with STL map-like data
// structures, such as std::map and hash_map. Some functions will also work with
// sets, such as ContainsKey().
#ifndef MEDIAPIPE_DEPS_MAP_UTIL_H_
#define MEDIAPIPE_DEPS_MAP_UTIL_H_
#include <stddef.h>
#include <iterator>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include "mediapipe/framework/port/logging.h"
namespace mediapipe {
// A note on terminology: `m` and `M` represent a map and its type.
//
// Returns a const reference to the value associated with the given key if it
// exists. Crashes otherwise.
//
// This is intended as a replacement for operator[] as an rvalue (for reading)
// when the key is guaranteed to exist.
//
// operator[] for lookup is discouraged for several reasons (note that these
// reasons may apply to only some map types):
// * It has a side-effect of inserting missing keys
// * It is not thread-safe (even when it is not inserting, it can still
// choose to resize the underlying storage)
// * It invalidates iterators (when it chooses to resize)
// * It default constructs a value object even if it doesn't need to
//
// This version assumes the key is printable, and includes it in the fatal log
// message.
template <typename M>
const typename M::value_type::second_type& FindOrDie(
const M& m, const typename M::value_type::first_type& key) {
auto it = m.find(key);
CHECK(it != m.end()) << "Map key not found: " << key;
return it->second;
}
// Same as above, but returns a non-const reference.
template <typename M>
typename M::value_type::second_type& FindOrDie(
M& m, // NOLINT
const typename M::value_type::first_type& key) {
auto it = m.find(key);
CHECK(it != m.end()) << "Map key not found: " << key;
return it->second;
}
// Returns a const reference to the value associated with the given key if it
// exists, otherwise returns a const reference to the provided default value.
//
// WARNING: If a temporary object is passed as the default "value,"
// this function will return a reference to that temporary object,
// which will be destroyed at the end of the statement. A common
// example: if you have a map with std::string values, and you pass a char*
// as the default "value," either use the returned value immediately
// or store it in a std::string (not std::string&).
template <typename M>
const typename M::value_type::second_type& FindWithDefault(
const M& m, const typename M::value_type::first_type& key,
const typename M::value_type::second_type& value) {
auto it = m.find(key);
if (it != m.end()) {
return it->second;
}
return value;
}
// Returns a pointer to the const value associated with the given key if it
// exists, or null otherwise.
template <typename M>
const typename M::value_type::second_type* FindOrNull(
const M& m, const typename M::value_type::first_type& key) {
auto it = m.find(key);
if (it == m.end()) {
return nullptr;
}
return &it->second;
}
// Returns a pointer to the non-const value associated with the given key if it
// exists, or null otherwise.
template <typename M>
typename M::value_type::second_type* FindOrNull(
M& m, // NOLINT
const typename M::value_type::first_type& key) {
auto it = m.find(key);
if (it == m.end()) {
return nullptr;
}
return &it->second;
}
// Returns true if and only if the given m contains the given key.
template <typename M, typename Key>
bool ContainsKey(const M& m, const Key& key) {
return m.find(key) != m.end();
}
// Inserts the given key and value into the given m if and only if the
// given key did NOT already exist in the m. If the key previously
// existed in the m, the value is not changed. Returns true if the
// key-value pair was inserted; returns false if the key was already present.
template <typename M>
bool InsertIfNotPresent(M* m, const typename M::value_type& vt) {
return m->insert(vt).second;
}
// Same as above except the key and value are passed separately.
template <typename M>
bool InsertIfNotPresent(M* m, const typename M::value_type::first_type& key,
const typename M::value_type::second_type& value) {
return InsertIfNotPresent(m, {key, value});
}
// Saves the reverse mapping into reverse. Returns true if values could all be
// inserted.
template <typename M, typename ReverseM>
bool ReverseMap(const M& m, ReverseM* reverse) {
CHECK(reverse != nullptr);
for (const auto& kv : m) {
if (!InsertIfNotPresent(reverse, kv.second, kv.first)) {
return false;
}
}
return true;
}
} // namespace mediapipe
#endif // MEDIAPIPE_DEPS_MAP_UTIL_H_
+406
View File
@@ -0,0 +1,406 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This class is intended to contain a collection of useful (static)
// mathematical functions, properly coded (by consulting numerical
// recipes or another authoritative source first).
#ifndef MEDIAPIPE_DEPS_MATHUTIL_H_
#define MEDIAPIPE_DEPS_MATHUTIL_H_
#include <cmath>
#include <limits>
#include <type_traits>
#include "mediapipe/framework/port/integral_types.h"
#include "mediapipe/framework/port/logging.h"
namespace mediapipe {
// ========================================================================= //
class MathUtil {
public:
// --------------------------------------------------------------------
// Round
// This function rounds a floating-point number to an integer. It
// works for positive or negative numbers.
//
// Values that are halfway between two integers may be rounded up or
// down, for example Round<int>(0.5) == 0 and Round<int>(1.5) == 2.
// This allows the function to be implemented efficiently on multiple
// hardware platforms (see the template specializations at the bottom
// of this file). You should not use this function if you care about which
// way such half-integers are rounded.
//
// Example usage:
// double y, z;
// int x = Round<int>(y + 3.7);
// int64 b = Round<int64>(0.3 * z);
//
// Note that the floating-point template parameter is typically inferred
// from the argument type, i.e. there is no need to specify it explicitly.
// --------------------------------------------------------------------
template <class IntOut, class FloatIn>
static IntOut Round(FloatIn x) {
static_assert(!std::numeric_limits<FloatIn>::is_integer,
"FloatIn is integer");
static_assert(std::numeric_limits<IntOut>::is_integer,
"IntOut is not integer");
// We don't use sgn(x) below because there is no need to distinguish the
// (x == 0) case. Also note that there are specialized faster versions
// of this function for Intel, ARM and PPC processors at the bottom
// of this file.
if (x > -0.5 && x < 0.5) {
// This case is special, because for largest floating point number
// below 0.5, the addition of 0.5 yields 1 and this would lead
// to incorrect result.
return static_cast<IntOut>(0);
}
return static_cast<IntOut>(x < 0 ? (x - 0.5) : (x + 0.5));
}
// Convert a floating-point number to an integer. For all inputs x where
// static_cast<IntOut>(x) is legal according to the C++ standard, the result
// is identical to that cast (i.e. the result is x with its fractional part
// truncated whenever that is representable as IntOut).
//
// static_cast would cause undefined behavior for the following cases, which
// have well-defined behavior for this function:
//
// 1. If x is NaN, the result is zero.
//
// 2. If the truncated form of x is above the representable range of IntOut,
// the result is std::numeric_limits<IntOut>::max().
//
// 3. If the truncated form of x is below the representable range of IntOut,
// the result is std::numeric_limits<IntOut>::min().
//
// Note that cases #2 and #3 cover infinities as well as finite numbers.
//
// The range of FloatIn must include the range of IntOut, otherwise
// the results are undefined.
template <class IntOut, class FloatIn>
static IntOut SafeCast(FloatIn x) {
static_assert(!std::numeric_limits<FloatIn>::is_integer,
"FloatIn is integer");
static_assert(std::numeric_limits<IntOut>::is_integer,
"IntOut is not integer");
static_assert(std::numeric_limits<IntOut>::radix == 2, "IntOut is base 2");
// Special case NaN, for which the logic below doesn't work.
if (std::isnan(x)) {
return 0;
}
// Negative values all clip to zero for unsigned results.
if (!std::numeric_limits<IntOut>::is_signed && x < 0) {
return 0;
}
// Handle infinities.
if (std::isinf(x)) {
return x < 0 ? std::numeric_limits<IntOut>::min()
: std::numeric_limits<IntOut>::max();
}
// Set exp such that x == f * 2^exp for some f with |f| in [0.5, 1.0),
// unless x is zero in which case exp == 0. Note that this implies that the
// magnitude of x is strictly less than 2^exp.
int exp = 0;
std::frexp(x, &exp);
// Let N be the number of non-sign bits in the representation of IntOut. If
// the magnitude of x is strictly less than 2^N, the truncated version of x
// is representable as IntOut. The only representable integer for which this
// is not the case is std::numeric_limits::min() for signed types (i.e.
// -2^N), but that is covered by the fall-through below.
if (exp <= std::numeric_limits<IntOut>::digits) {
return x;
}
// Handle numbers with magnitude >= 2^N.
return x < 0 ? std::numeric_limits<IntOut>::min()
: std::numeric_limits<IntOut>::max();
}
// --------------------------------------------------------------------
// SafeRound
// These functions round a floating-point number to an integer.
// Results are identical to Round, except in cases where
// the argument is NaN, or when the rounded value would overflow the
// return type. In those cases, Round has undefined
// behavior. SafeRound returns 0 when the argument is
// NaN, and returns the closest possible integer value otherwise (i.e.
// std::numeric_limits<IntOut>::max() for large positive values, and
// std::numeric_limits<IntOut>::min() for large negative values).
// The range of FloatIn must include the range of IntOut, otherwise
// the results are undefined.
// --------------------------------------------------------------------
template <class IntOut, class FloatIn>
static IntOut SafeRound(FloatIn x) {
static_assert(!std::numeric_limits<FloatIn>::is_integer,
"FloatIn is integer");
static_assert(std::numeric_limits<IntOut>::is_integer,
"IntOut is not integer");
if (std::isnan(x)) {
return 0;
} else {
return SafeCast<IntOut>((x < 0.) ? (x - 0.5) : (x + 0.5));
}
}
// --------------------------------------------------------------------
// FastIntRound, FastInt64Round
// Fast routines for converting floating-point numbers to integers.
//
// These routines are approximately 6 times faster than the default
// implementation of Round<int> on Intel processors (12 times faster on
// the Pentium 3). They are also more than 5 times faster than simply
// casting a "double" to an "int" using static_cast<int>. This is
// because casts are defined to truncate towards zero, which on Intel
// processors requires changing the rounding mode and flushing the
// floating-point pipeline (unless programs are compiled specifically
// for the Pentium 4, which has a new instruction to avoid this).
//
// Numbers that are halfway between two integers may be rounded up or
// down. This is because the conversion is done using the default
// rounding mode, which rounds towards the closest even number in case
// of ties. So for example, FastIntRound(0.5) == 0, but
// FastIntRound(1.5) == 2. These functions should only be used with
// applications that don't care about which way such half-integers are
// rounded.
//
// There are template specializations of Round() which call these
// functions (for "int" and "int64" only), but it's safer to call them
// directly.
// --------------------------------------------------------------------
static int32 FastIntRound(double x) {
#if defined __GNUC__ && (defined __i386__ || defined __SSE2__ || \
defined __aarch64__ || defined __powerpc64__)
#if defined __AVX__
// AVX.
int32 result;
__asm__ __volatile__(
"vcvtsd2si %1, %0"
: "=r"(result) // Output operand is a register
: "xm"(x)); // Input operand is an xmm register or memory
return result;
#elif defined __SSE2__
// SSE2.
int32 result;
__asm__ __volatile__(
"cvtsd2si %1, %0"
: "=r"(result) // Output operand is a register
: "xm"(x)); // Input operand is an xmm register or memory
return result;
#elif defined __i386__
// FPU stack. Adapted from /usr/include/bits/mathinline.h.
int32 result;
__asm__ __volatile__("fistpl %0"
: "=m"(result) // Output operand is a memory location
: "t"(x) // Input operand is top of FP stack
: "st"); // Clobbers (pops) top of FP stack
return result;
#elif defined __aarch64__
int64 result;
__asm__ __volatile__("fcvtns %d0, %d1"
: "=w"(result) // Vector floating point register
: "w"(x) // Vector floating point register
: /* No clobbers */);
return static_cast<int32>(result);
#elif defined __powerpc64__
int64 result;
__asm__ __volatile__("fctid %0, %1"
: "=d"(result)
: "d"(x)
: /* No clobbers */);
return result;
#endif // defined __powerpc64__
#else
return Round<int32>(x);
#endif // if defined __GNUC__ && ...
}
static int32 FastIntRound(float x) {
#if defined __GNUC__ && (defined __i386__ || defined __SSE2__ || \
defined __aarch64__ || defined __powerpc64__)
#if defined __AVX__
// AVX.
int32 result;
__asm__ __volatile__(
"vcvtss2si %1, %0"
: "=r"(result) // Output operand is a register
: "xm"(x)); // Input operand is an xmm register or memory
return result;
#elif defined __SSE2__
// SSE2.
int32 result;
__asm__ __volatile__(
"cvtss2si %1, %0"
: "=r"(result) // Output operand is a register
: "xm"(x)); // Input operand is an xmm register or memory
return result;
#elif defined __i386__
// FPU stack. Adapted from /usr/include/bits/mathinline.h.
int32 result;
__asm__ __volatile__("fistpl %0"
: "=m"(result) // Output operand is a memory location
: "t"(x) // Input operand is top of FP stack
: "st"); // Clobbers (pops) top of FP stack
return result;
#elif defined __aarch64__
int64 result;
__asm__ __volatile__("fcvtns %s0, %s1"
: "=w"(result) // Vector floating point register
: "w"(x) // Vector floating point register
: /* No clobbers */);
return static_cast<int32>(result);
#elif defined __powerpc64__
uint64 output;
__asm__ __volatile__("fctiw %0, %1"
: "=d"(output)
: "f"(x)
: /* No clobbers */);
return bit_cast<int32>(static_cast<uint32>(output >> 32));
#endif // defined __powerpc64__
#else
return Round<int32>(x);
#endif // if defined __GNUC__ && ...
}
static int64 FastInt64Round(double x) {
#if defined __GNUC__ && (defined __i386__ || defined __x86_64__ || \
defined __aarch64__ || defined __powerpc64__)
#if defined __AVX__
// AVX.
int64 result;
__asm__ __volatile__(
"vcvtsd2si %1, %0"
: "=r"(result) // Output operand is a register
: "xm"(x)); // Input operand is an xmm register or memory
return result;
#elif defined __x86_64__
// SSE2.
int64 result;
__asm__ __volatile__(
"cvtsd2si %1, %0"
: "=r"(result) // Output operand is a register
: "xm"(x)); // Input operand is an xmm register or memory
return result;
#elif defined __i386__
// There is no CVTSD2SI in i386 to produce a 64 bit int, even with SSE2.
// FPU stack. Adapted from /usr/include/bits/mathinline.h.
int64 result;
__asm__ __volatile__("fistpll %0"
: "=m"(result) // Output operand is a memory location
: "t"(x) // Input operand is top of FP stack
: "st"); // Clobbers (pops) top of FP stack
return result;
#elif defined __aarch64__
// Floating-point convert to signed integer,
// rounding to nearest with ties to even.
int64 result;
__asm__ __volatile__("fcvtns %d0, %d1"
: "=w"(result)
: "w"(x)
: /* No clobbers */);
return result;
#elif defined __powerpc64__
int64 result;
__asm__ __volatile__("fctid %0, %1"
: "=d"(result)
: "d"(x)
: /* No clobbers */);
return result;
#endif // if defined __powerpc64__
#else
return Round<int64>(x);
#endif // if defined __GNUC__ && ...
}
static int64 FastInt64Round(float x) {
return FastInt64Round(static_cast<double>(x));
}
static int32 FastIntRound(long double x) { return Round<int32>(x); }
static int64 FastInt64Round(long double x) { return Round<int64>(x); }
// Absolute value of the difference between two numbers.
// Works correctly for signed types and special floating point values.
template <typename T>
static typename std::make_unsigned<T>::type AbsDiff(const T x, const T y) {
// Carries out arithmetic as unsigned to avoid overflow.
typedef typename std::make_unsigned<T>::type R;
return x > y ? R(x) - R(y) : R(y) - R(x);
}
// Clamps value to the range [low, high]. Requires low <= high.
template <typename T> // T models LessThanComparable.
static const T& Clamp(const T& low, const T& high, const T& value) {
// Prevents errors in ordering the arguments.
DCHECK(!(high < low));
if (high < value) return high;
if (value < low) return low;
return value;
}
// If two (usually floating point) numbers are within a certain
// absolute margin of error.
template <typename T>
static bool WithinMargin(const T x, const T y, const T margin) {
DCHECK_GE(margin, 0);
return (std::abs(x) <= std::abs(y) + margin) &&
(std::abs(x) >= std::abs(y) - margin);
}
};
// ========================================================================= //
#if defined __GNUC__ && (defined __i386__ || defined __x86_64__ || \
defined __aarch64__ || defined __powerpc64__)
// We define template specializations of Round() to get the more efficient
// Intel versions when possible. Note that gcc does not currently support
// partial specialization of templatized functions.
template <>
inline int32 MathUtil::Round<int32, float>(float x) {
return FastIntRound(x);
}
template <>
inline int32 MathUtil::Round<int32, double>(double x) {
return FastIntRound(x);
}
template <>
inline int64 MathUtil::Round<int64, float>(float x) {
return FastInt64Round(x);
}
template <>
inline int64 MathUtil::Round<int64, double>(double x) {
return FastInt64Round(x);
}
#endif
} // namespace mediapipe
#endif // MEDIAPIPE_DEPS_MATHUTIL_H_
@@ -0,0 +1,879 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Test functions in MathUtil.
#include "mediapipe/framework/deps/mathutil.h"
#include <stdio.h>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <ostream>
#include "mediapipe/framework/port/benchmark.h"
#include "mediapipe/framework/port/gmock.h"
#include "mediapipe/framework/port/gtest.h"
namespace {
TEST(MathUtil, Round) {
// test float rounding
EXPECT_EQ(mediapipe::MathUtil::FastIntRound(0.7f), 1);
EXPECT_EQ(mediapipe::MathUtil::FastIntRound(5.7f), 6);
EXPECT_EQ(mediapipe::MathUtil::FastIntRound(6.3f), 6);
EXPECT_EQ(mediapipe::MathUtil::FastIntRound(1000000.7f), 1000001);
// test that largest representable number below 0.5 rounds to zero.
// this is important because naive implementation of round:
// static_cast<int>(r + 0.5f) is 1 due to implicit rounding in operator+
float rf = std::nextafter(0.5f, .0f);
EXPECT_LT(rf, 0.5f);
EXPECT_EQ(mediapipe::MathUtil::Round<int>(rf), 0);
// same test for double
double rd = std::nextafter(0.5, 0.0);
EXPECT_LT(rd, 0.5);
EXPECT_EQ(mediapipe::MathUtil::Round<int>(rd), 0);
// same test for long double
long double rl = std::nextafter(0.5l, 0.0l);
EXPECT_LT(rl, 0.5l);
EXPECT_EQ(mediapipe::MathUtil::Round<int>(rl), 0);
}
static void BM_IntCast(benchmark::State& state) {
double x = 0.1;
int sum = 0;
for (auto _ : state) {
sum += static_cast<int>(x);
x += 0.1;
sum += static_cast<int>(x);
x += 0.1;
sum += static_cast<int>(x);
x += 0.1;
sum += static_cast<int>(x);
x += 0.1;
sum += static_cast<int>(x);
x += 0.1;
}
EXPECT_NE(sum, 0); // Don't let 'sum' get optimized away.
}
BENCHMARK(BM_IntCast);
static void BM_Int64Cast(benchmark::State& state) {
double x = 0.1;
int64 sum = 0;
for (auto _ : state) {
sum += static_cast<int64>(x);
x += 0.1;
sum += static_cast<int64>(x);
x += 0.1;
sum += static_cast<int64>(x);
x += 0.1;
sum += static_cast<int64>(x);
x += 0.1;
sum += static_cast<int64>(x);
x += 0.1;
}
EXPECT_NE(sum, 0); // Don't let 'sum' get optimized away.
}
BENCHMARK(BM_Int64Cast);
static void BM_IntRound(benchmark::State& state) {
double x = 0.1;
int sum = 0;
for (auto _ : state) {
sum += mediapipe::MathUtil::Round<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<int>(x);
x += 0.1;
}
EXPECT_NE(sum, 0); // Don't let 'sum' get optimized away.
}
BENCHMARK(BM_IntRound);
static void BM_FastIntRound(benchmark::State& state) {
double x = 0.1;
int sum = 0;
for (auto _ : state) {
sum += mediapipe::MathUtil::FastIntRound(x);
x += 0.1;
sum += mediapipe::MathUtil::FastIntRound(x);
x += 0.1;
sum += mediapipe::MathUtil::FastIntRound(x);
x += 0.1;
sum += mediapipe::MathUtil::FastIntRound(x);
x += 0.1;
sum += mediapipe::MathUtil::FastIntRound(x);
x += 0.1;
}
EXPECT_NE(sum, 0); // Don't let 'sum' get optimized away.
}
BENCHMARK(BM_FastIntRound);
static void BM_Int64Round(benchmark::State& state) {
double x = 0.1;
int sum = 0;
for (auto _ : state) {
sum += mediapipe::MathUtil::Round<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<int64>(x);
x += 0.1;
}
EXPECT_NE(sum, 0); // Don't let 'sum' get optimized away.
}
BENCHMARK(BM_Int64Round);
static void BM_UintRound(benchmark::State& state) {
double x = 0.1;
int sum = 0;
for (auto _ : state) {
sum += mediapipe::MathUtil::Round<uint32>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<uint32>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<uint32>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<uint32>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<uint32>(x);
x += 0.1;
}
EXPECT_NE(sum, 0); // Don't let 'sum' get optimized away.
}
BENCHMARK(BM_UintRound);
static void BM_SafeIntCast(benchmark::State& state) {
double x = 0.1;
int sum = 0;
for (auto _ : state) {
sum += mediapipe::MathUtil::SafeCast<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeCast<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeCast<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeCast<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeCast<int>(x);
x += 0.1;
}
EXPECT_NE(sum, 0); // Don't let 'sum' get optimized away.
}
BENCHMARK(BM_SafeIntCast);
static void BM_SafeInt64Cast(benchmark::State& state) {
double x = 0.1;
int sum = 0;
for (auto _ : state) {
sum += mediapipe::MathUtil::SafeCast<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeCast<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeCast<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeCast<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeCast<int64>(x);
x += 0.1;
}
EXPECT_NE(sum, 0); // Don't let 'sum' get optimized away.
}
BENCHMARK(BM_SafeInt64Cast);
static void BM_SafeIntRound(benchmark::State& state) {
double x = 0.1;
int sum = 0;
for (auto _ : state) {
sum += mediapipe::MathUtil::SafeRound<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeRound<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeRound<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeRound<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeRound<int>(x);
x += 0.1;
}
EXPECT_NE(sum, 0); // Don't let 'sum' get optimized away.
}
BENCHMARK(BM_SafeIntRound);
static void BM_SafeInt64Round(benchmark::State& state) {
double x = 0.1;
int sum = 0;
for (auto _ : state) {
sum += mediapipe::MathUtil::SafeRound<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeRound<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeRound<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeRound<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeRound<int64>(x);
x += 0.1;
}
EXPECT_NE(sum, 0); // Don't let 'sum' get optimized away.
}
BENCHMARK(BM_SafeInt64Round);
TEST(MathUtil, IntRound) {
EXPECT_EQ(mediapipe::MathUtil::Round<int>(0.0), 0);
EXPECT_EQ(mediapipe::MathUtil::Round<int>(0.49), 0);
EXPECT_EQ(mediapipe::MathUtil::Round<int>(1.49), 1);
EXPECT_EQ(mediapipe::MathUtil::Round<int>(-0.49), 0);
EXPECT_EQ(mediapipe::MathUtil::Round<int>(-1.49), -1);
// Either adjacent integer is an acceptable result.
EXPECT_EQ(fabs(mediapipe::MathUtil::Round<int>(0.5) - 0.5), 0.5);
EXPECT_EQ(fabs(mediapipe::MathUtil::Round<int>(1.5) - 1.5), 0.5);
EXPECT_EQ(fabs(mediapipe::MathUtil::Round<int>(-0.5) + 0.5), 0.5);
EXPECT_EQ(fabs(mediapipe::MathUtil::Round<int>(-1.5) + 1.5), 0.5);
EXPECT_EQ(mediapipe::MathUtil::Round<int>(static_cast<double>(0x76543210)),
0x76543210);
// A double-precision number has a 53-bit mantissa (52 fraction bits),
// so the following value can be represented exactly.
int64 value64 = GG_ULONGLONG(0x1234567890abcd00);
EXPECT_EQ(mediapipe::MathUtil::Round<int64>(static_cast<double>(value64)),
value64);
}
template <class F>
F NextAfter(F x, F y);
template <>
float NextAfter(float x, float y) {
return nextafterf(x, y);
}
template <>
double NextAfter(double x, double y) {
return nextafter(x, y);
}
template <class FloatIn, class IntOut>
class SafeCastTester {
public:
static void Run() {
const IntOut imax = std::numeric_limits<IntOut>::max();
EXPECT_GT(imax, 0);
const IntOut imin = std::numeric_limits<IntOut>::min();
const bool s = std::numeric_limits<IntOut>::is_signed;
if (s) {
EXPECT_LT(imin, 0);
} else {
EXPECT_EQ(0, imin);
}
// Some basic tests.
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(0.0)),
0);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(-0.0)),
0);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(0.99)),
0);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(1.0)),
1);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(1.01)),
1);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(1.99)),
1);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(2.0)),
2);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(2.01)),
2);
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(-0.99)), 0);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(-1.0)),
s ? -1 : 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(-1.01)),
s ? -1 : 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(-1.99)),
s ? -1 : 0);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(-2.0)),
s ? -2 : 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(-2.01)),
s ? -2 : 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(117.9)),
117);
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(118.0)),
118);
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(118.1)),
118);
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(-117.9)),
s ? -117 : 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(-118.0)),
s ? -118 : 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(-118.1)),
s ? -118 : 0);
// Some edge cases.
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
std::numeric_limits<FloatIn>::max()),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
-std::numeric_limits<FloatIn>::max()),
imin);
const FloatIn inf_val = std::numeric_limits<FloatIn>::infinity();
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(inf_val), imax);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(-inf_val), imin);
const FloatIn nan_val = inf_val - inf_val;
EXPECT_TRUE(std::isnan(nan_val));
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(nan_val), 0);
// Some larger numbers.
if (sizeof(IntOut) >= 32) {
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(0x76543210)),
0x76543210);
}
if (sizeof(FloatIn) >= 64) {
// A double-precision number has a 53-bit mantissa (52 fraction bits),
// so the following value can be represented exactly by a double.
int64 value64 = GG_ULONGLONG(0x1234567890abcd00);
const IntOut expected =
(sizeof(IntOut) >= 64) ? static_cast<IntOut>(value64) : imax;
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(value64)),
expected);
}
// Check values near imin and imax
static const int kLoopCount = 10;
{
// Values greater than or equal to imax should convert to imax
FloatIn v = static_cast<FloatIn>(imax);
for (int i = 0; i < kLoopCount; i++) {
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(v), imax);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(v + 10000.)),
imax);
v = NextAfter(v, std::numeric_limits<FloatIn>::max());
}
}
{
// Values less than or equal to imin should convert to imin
FloatIn v = static_cast<FloatIn>(imin);
for (int i = 0; i < kLoopCount; i++) {
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(v), imin);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(v - 10000.)),
imin);
v = NextAfter(v, -std::numeric_limits<FloatIn>::max());
}
}
{
// Values slightly less than imax which can be exactly represented as a
// FloatIn should convert exactly to themselves.
IntOut v = imax;
for (int i = 0; i < kLoopCount; i++) {
v = std::min<IntOut>(v - 1,
NextAfter(static_cast<FloatIn>(v),
-std::numeric_limits<FloatIn>::max()));
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(v)), v);
}
}
{
// Values slightly greater than imin which can be exactly represented as a
// FloatIn should convert exactly to themselves.
IntOut v = imin;
for (int i = 0; i < kLoopCount; i++) {
v = std::max<IntOut>(v + 1,
NextAfter(static_cast<FloatIn>(v),
std::numeric_limits<FloatIn>::max()));
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(v)), v);
}
}
// When FloatIn is wider than IntOut, we can test that fractional conversion
// near imax works as expected.
if (sizeof(FloatIn) > sizeof(IntOut)) {
{
// Values slightly less than imax should convert to imax - 1
FloatIn v = static_cast<FloatIn>(imax);
for (int i = 0; i < kLoopCount; i++) {
v = NextAfter(static_cast<FloatIn>(v),
-std::numeric_limits<FloatIn>::max());
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(v)),
imax - 1);
}
}
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) + 0.1)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) + 0.99)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) + 1.0)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) + 1.99)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) + 2.0)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 0.1)),
imax - 1);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 0.99)),
imax - 1);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 1.0)),
imax - 1);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 1.01)),
imax - 2);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 1.99)),
imax - 2);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 2.0)),
imax - 2);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 2.01)),
imax - 3);
}
// When FloatIn is wider than IntOut, and IntOut is signed, we can test
// that fractional conversion near imin works as expected.
if (s && (sizeof(FloatIn) > sizeof(IntOut))) {
{
// Values just over imin should convert to imin + 1
FloatIn v = static_cast<FloatIn>(imin);
for (int i = 0; i < kLoopCount; i++) {
v = NextAfter(static_cast<FloatIn>(v),
std::numeric_limits<FloatIn>::max());
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(v)),
imin + 1);
}
}
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) - 0.1)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) - 0.99)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) - 1.0)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) - 0.99)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) - 2.0)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 0.1)),
imin + 1);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 0.99)),
imin + 1);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 1.0)),
imin + 1);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 1.01)),
imin + 2);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 1.99)),
imin + 2);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 2.0)),
imin + 2);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 2.01)),
imin + 3);
}
}
};
TEST(MathUtil, SafeCast) {
SafeCastTester<float, int8>::Run();
SafeCastTester<double, int8>::Run();
SafeCastTester<float, int16>::Run();
SafeCastTester<double, int16>::Run();
SafeCastTester<float, int32>::Run();
SafeCastTester<double, int32>::Run();
SafeCastTester<float, int64>::Run();
SafeCastTester<double, int64>::Run();
SafeCastTester<float, uint8>::Run();
SafeCastTester<double, uint8>::Run();
SafeCastTester<float, uint16>::Run();
SafeCastTester<double, uint16>::Run();
SafeCastTester<float, uint32>::Run();
SafeCastTester<double, uint32>::Run();
SafeCastTester<float, uint64>::Run();
SafeCastTester<double, uint64>::Run();
// Spot-check SafeCast<int>
EXPECT_EQ(mediapipe::MathUtil::SafeCast<int>(static_cast<float>(12345.678)),
12345);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<int>(static_cast<float>(12345.4321)),
12345);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<int>(static_cast<double>(-12345.678)),
-12345);
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<int>(static_cast<double>(-12345.4321)),
-12345);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<int>(1E47), 2147483647);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<int>(-1E47),
GG_LONGLONG(-2147483648));
}
template <class FloatIn, class IntOut>
class SafeRoundTester {
public:
static void Run() {
const IntOut imax = std::numeric_limits<IntOut>::max();
EXPECT_GT(imax, 0);
const IntOut imin = std::numeric_limits<IntOut>::min();
const bool s = std::numeric_limits<IntOut>::is_signed;
if (s) {
EXPECT_LT(imin, 0);
} else {
EXPECT_EQ(0, imin);
}
// Some basic tests.
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(0.0)),
0);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(-0.0)), 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(0.49)), 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(0.51)), 1);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(1.49)), 1);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(1.51)), 2);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(-0.49)), 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(-0.51)),
s ? -1 : 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(-1.49)),
s ? -1 : 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(-1.51)),
s ? -2 : 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(117.4)),
117);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(117.6)),
118);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(-117.4)),
s ? -117 : 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(-117.6)),
s ? -118 : 0);
// At the midpoint between ints, either adjacent int is an acceptable
// result.
EXPECT_EQ(
fabs(mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(0.5)) -
0.5),
0.5);
EXPECT_EQ(
fabs(mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(1.5)) -
1.5),
0.5);
EXPECT_EQ(fabs(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(117.5)) -
117.5),
0.5);
if (s) {
EXPECT_EQ(fabs(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(-0.5)) +
0.5),
0.5);
EXPECT_EQ(fabs(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(-1.5)) +
1.5),
0.5);
EXPECT_EQ(fabs(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(-117.5)) +
117.5),
0.5);
} else {
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(-0.5)),
0);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(-1.5)),
0);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(-117.5)),
0);
}
// Some edge cases.
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
std::numeric_limits<FloatIn>::max()),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
-std::numeric_limits<FloatIn>::max()),
imin);
const FloatIn inf_val = std::numeric_limits<FloatIn>::infinity();
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(inf_val), imax);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(-inf_val), imin);
const FloatIn nan_val = inf_val - inf_val;
EXPECT_TRUE(std::isnan(nan_val));
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(nan_val), 0);
// Some larger numbers.
if (sizeof(IntOut) >= 32) {
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(0x76543210)),
0x76543210);
}
if (sizeof(FloatIn) >= 64) {
// A double-precision number has a 53-bit mantissa (52 fraction bits),
// so the following value can be represented exactly by a double.
int64 value64 = GG_ULONGLONG(0x1234567890abcd00);
const IntOut expected =
(sizeof(IntOut) >= 64) ? static_cast<IntOut>(value64) : imax;
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(value64)),
expected);
}
// Check values near imin and imax
static const int kLoopCount = 10;
{
// Values greater than or equal to imax should round to imax
FloatIn v = static_cast<FloatIn>(imax);
for (int i = 0; i < kLoopCount; i++) {
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(v), imax);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(v + 10000.)),
imax);
v = NextAfter(v, std::numeric_limits<FloatIn>::max());
}
}
{
// Values less than or equal to imin should round to imin
FloatIn v = static_cast<FloatIn>(imin);
for (int i = 0; i < kLoopCount; i++) {
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(v), imin);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(v - 10000.)),
imin);
v = NextAfter(v, -std::numeric_limits<FloatIn>::max());
}
}
{
// Values slightly less than imax which can be exactly represented as a
// FloatIn should round exactly to themselves.
IntOut v = imax;
for (int i = 0; i < kLoopCount; i++) {
v = std::min<IntOut>(v - 1,
NextAfter(static_cast<FloatIn>(v),
-std::numeric_limits<FloatIn>::max()));
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(v)), v);
}
}
{
// Values slightly greater than imin which can be exactly represented as a
// FloatIn should round exactly to themselves.
IntOut v = imin;
for (int i = 0; i < kLoopCount; i++) {
v = std::max<IntOut>(v + 1,
NextAfter(static_cast<FloatIn>(v),
std::numeric_limits<FloatIn>::max()));
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(v)), v);
}
}
// When FloatIn is wider than IntOut, we can test that fractional rounding
// near imax works as expected.
if (sizeof(FloatIn) > sizeof(IntOut)) {
{
// Values slightly less than imax should round to imax
FloatIn v = static_cast<FloatIn>(imax);
for (int i = 0; i < kLoopCount; i++) {
v = NextAfter(static_cast<FloatIn>(v),
-std::numeric_limits<FloatIn>::max());
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(v)),
imax);
}
}
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) + 0.1)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) + 0.49)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) + 0.5)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) + 0.51)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) + 0.99)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 0.1)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 0.49)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 0.51)),
imax - 1);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 0.99)),
imax - 1);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 1.49)),
imax - 1);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 1.51)),
imax - 2);
}
// When FloatIn is wider than IntOut, or if IntOut is unsigned, we can test
// that fractional rounding near imin works as expected.
if (!s || (sizeof(FloatIn) > sizeof(IntOut))) {
{
// Values slightly greater than imin should round to imin
FloatIn v = static_cast<FloatIn>(imin);
for (int i = 0; i < kLoopCount; i++) {
v = NextAfter(static_cast<FloatIn>(v),
std::numeric_limits<FloatIn>::max());
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(v)),
imin);
}
}
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) - 0.1)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) - 0.49)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) - 0.5)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) - 0.51)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) - 0.99)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 0.1)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 0.49)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 0.51)),
imin + 1);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 0.99)),
imin + 1);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 1.49)),
imin + 1);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 1.51)),
imin + 2);
}
}
};
TEST(MathUtil, SafeRound) {
SafeRoundTester<float, int8>::Run();
SafeRoundTester<double, int8>::Run();
SafeRoundTester<float, int16>::Run();
SafeRoundTester<double, int16>::Run();
SafeRoundTester<float, int32>::Run();
SafeRoundTester<double, int32>::Run();
SafeRoundTester<float, int64>::Run();
SafeRoundTester<double, int64>::Run();
SafeRoundTester<float, uint8>::Run();
SafeRoundTester<double, uint8>::Run();
SafeRoundTester<float, uint16>::Run();
SafeRoundTester<double, uint16>::Run();
SafeRoundTester<float, uint32>::Run();
SafeRoundTester<double, uint32>::Run();
SafeRoundTester<float, uint64>::Run();
SafeRoundTester<double, uint64>::Run();
// Spot-check SafeRound<int>
EXPECT_EQ(mediapipe::MathUtil::SafeRound<int>(static_cast<float>(12345.678)),
12346);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<int>(static_cast<float>(12345.4321)),
12345);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<int>(static_cast<double>(-12345.678)),
-12346);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<int>(static_cast<double>(-12345.4321)),
-12345);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<int>(1E47), 2147483647);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<int>(-1E47),
GG_LONGLONG(-2147483648));
}
} // namespace
@@ -0,0 +1,64 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_DEPS_MESSAGE_MATCHERS_H_
#define MEDIAPIPE_DEPS_MESSAGE_MATCHERS_H_
#include "mediapipe/framework/port/core_proto_inc.h"
#include "mediapipe/framework/port/gmock.h"
namespace mediapipe {
namespace internal {
bool EqualsMessage(const proto_ns::MessageLite& m_1,
const proto_ns::MessageLite& m_2) {
std::string s_1, s_2;
m_1.SerializeToString(&s_1);
m_2.SerializeToString(&s_2);
return s_1 == s_2;
}
} // namespace internal
template <typename MessageType>
class ProtoMatcher : public testing::MatcherInterface<MessageType> {
using MatchResultListener = testing::MatchResultListener;
public:
explicit ProtoMatcher(const MessageType& message) : message_(message) {}
virtual bool MatchAndExplain(MessageType m, MatchResultListener*) const {
return internal::EqualsMessage(message_, m);
}
virtual void DescribeTo(::std::ostream* os) const {
#if defined(MEDIAPIPE_PROTO_LITE)
*os << "Protobuf messages have identical serializations.";
#else
*os << message_.DebugString();
#endif
}
private:
const MessageType message_;
};
template <typename MessageType>
inline testing::PolymorphicMatcher<ProtoMatcher<MessageType>> EqualsProto(
const MessageType& message) {
return testing::PolymorphicMatcher<ProtoMatcher<MessageType>>(
ProtoMatcher<MessageType>(message));
}
} // namespace mediapipe
#endif // MEDIAPIPE_DEPS_MESSAGE_MATCHERS_H_
+229
View File
@@ -0,0 +1,229 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/deps/monotonic_clock.h"
#include "absl/base/macros.h"
#include "absl/base/thread_annotations.h"
#include "absl/synchronization/mutex.h"
#include "absl/time/time.h"
#include "mediapipe/framework/port/logging.h"
namespace mediapipe {
// This state, which contains the "guts" of MonotonicClockImpl, is separate
// from the class instance so that it can be shared to implement a
// SynchronizedMonotonicClock. (The per-instance state of MonotonicClock is
// just for frills like the correction metrics and callback.) It lives in this
// private namespace so that test code can use it without exposing it to the
// world.
struct MonotonicClock::State {
// The clock whose time is being corrected.
Clock* raw_clock;
absl::Mutex lock;
// The largest time ever returned by Now().
absl::Time max_time GUARDED_BY(lock);
explicit State(Clock* clock)
: raw_clock(clock), max_time(absl::UnixEpoch()) {}
};
using State = MonotonicClock::State;
class MonotonicClockImpl : public MonotonicClock {
public:
// By default, MonotonicClockImpl owns the state_. ReleaseState(), below,
// can be used to prevent the MCI destructor from deleting a shared state_.
explicit MonotonicClockImpl(State* state)
: state_(state),
state_owned_(true),
last_raw_time_(absl::UnixEpoch()),
correction_count_(0),
max_correction_(absl::ZeroDuration()) {}
MonotonicClockImpl(const MonotonicClockImpl&) = delete;
MonotonicClockImpl& operator=(const MonotonicClockImpl&) = delete;
virtual ~MonotonicClockImpl() {
if (state_owned_) delete state_;
}
// Absolve this object of responsibility for state_.
void ReleaseState() {
CHECK(state_owned_);
state_owned_ = false;
}
//
// The Clock interface (see util/time/clock.h).
//
// The logic in TimeNow() is based on GFS_NowMS().
virtual absl::Time TimeNow() {
// These variables save some state from the critical section below.
absl::Time raw_time;
absl::Time local_max_time;
absl::Time local_last_raw_time;
// As there are several early exits from this function, use absl::MutexLock.
{
absl::MutexLock m(&state_->lock);
// Check consistency of internal data with state_.
CHECK_LE(last_raw_time_, state_->max_time)
<< "non-monotonic behavior: last_raw_time_=" << last_raw_time_
<< ", max_time=" << state_->max_time;
raw_time = state_->raw_clock->TimeNow();
// Normal case: time is advancing. Update state and return the raw time.
if (raw_time >= state_->max_time) {
last_raw_time_ = raw_time;
state_->max_time = raw_time;
return raw_time;
}
// Exceptional case: Raw time is within a window of a previous backward
// jump. We do not run any callbacks or update metrics here since we
// already did that when the backward jump was detected.
if (raw_time >= last_raw_time_) {
last_raw_time_ = raw_time;
return state_->max_time;
}
// Exceptional case: Raw time jumped backward. Remainder of function
// handles this case.
//
// First, update correction metrics.
++correction_count_;
absl::Duration delta = state_->max_time - raw_time;
CHECK_LT(absl::ZeroDuration(), delta);
if (delta > max_correction_) {
max_correction_ = delta;
}
// Copy state into local vars before updating last_raw_time_ and leaving
// the critical section.
local_max_time = state_->max_time;
local_last_raw_time = last_raw_time_;
last_raw_time_ = raw_time;
} // absl::MutexLock
// Return the saved maximum time.
return local_max_time;
}
// The strategy of Sleep and SleepUntil is K.I.S.S.: set an alarm on the
// raw_clock for the desired wakeup_time, and then snooze the alarm if we wake
// up too soon. This guarantees that the caller won't wake up too soon (which
// would require us to advance monotonic time simply by the act of waking up),
// however the caller may sleep for much longer (in monotonic time) if
// monotonic time jumps far into the future. Whether or not this happens
// depends on the behavior of the raw clock.
virtual void Sleep(absl::Duration d) {
absl::Time wakeup_time = TimeNow() + d;
SleepUntil(wakeup_time);
}
virtual void SleepUntil(absl::Time wakeup_time) {
while (TimeNow() < wakeup_time) {
state_->raw_clock->SleepUntil(wakeup_time);
}
}
//
// End of Clock interface.
//
private:
// Get metrics about time corrections.
virtual void GetCorrectionMetrics(int* correction_count,
double* max_correction) {
absl::MutexLock l(&state_->lock);
if (correction_count != nullptr) *correction_count = correction_count_;
if (max_correction != nullptr)
*max_correction = absl::FDivDuration(max_correction_, absl::Seconds(1));
}
// Reset values returned by GetCorrectionMetrics().
virtual void ResetCorrectionMetrics() {
absl::MutexLock l(&state_->lock);
correction_count_ = 0;
max_correction_ = absl::ZeroDuration();
}
// The guts of the monotonic clock. Caution: this may point to a static
// object.
State* state_;
// If true, this object owns state_ and is responsible for deallocating it.
bool state_owned_;
// last_raw_time_ remembers the last value obtained from raw_clock_.
// It prevents spurious calls to ReportCorrection when time moves
// forward by a smaller amount than a prior backward jump.
absl::Time last_raw_time_ GUARDED_BY(state_->lock);
// Variables that keep track of time corrections made by this instance of
// MonotonicClock. (All such metrics are instance-local for reasons
// described earlier.)
int correction_count_ GUARDED_BY(state_->lock);
absl::Duration max_correction_ GUARDED_BY(state_->lock);
};
// Factory methods.
MonotonicClock* MonotonicClock::CreateMonotonicClock(Clock* clock) {
State* state = new State(clock);
// MonotonicClockImpl takes ownership of state.
return new MonotonicClockImpl(state);
}
namespace {
State* GlobalSyncState() {
static State* sync_state = new State(Clock::RealClock());
return sync_state;
}
} // namespace
// The reason that SynchronizedMonotonicClock is not implemented as a singleton
// is so that different code bases can handle clock corrections their own way.
MonotonicClock* MonotonicClock::CreateSynchronizedMonotonicClock() {
MonotonicClockImpl* clock = new MonotonicClockImpl(GlobalSyncState());
// Release ownership of sync_state.
clock->ReleaseState();
return clock;
}
// Test access methods.
void MonotonicClockAccess::SynchronizedMonotonicClockReset() {
LOG(INFO) << "Resetting SynchronizedMonotonicClock";
State* sync_state = GlobalSyncState();
absl::MutexLock m(&sync_state->lock);
sync_state->max_time = absl::UnixEpoch();
}
State* MonotonicClockAccess::CreateMonotonicClockState(Clock* raw_clock) {
return new State(raw_clock);
}
void MonotonicClockAccess::DeleteMonotonicClockState(State* state) {
delete state;
}
MonotonicClock* MonotonicClockAccess::CreateMonotonicClock(State* state) {
MonotonicClockImpl* clock = new MonotonicClockImpl(state);
// Release ownership of sync_state.
clock->ReleaseState();
return clock;
}
} // namespace mediapipe
+100
View File
@@ -0,0 +1,100 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_DEPS_MONOTONIC_CLOCK_H_
#define MEDIAPIPE_DEPS_MONOTONIC_CLOCK_H_
#include "absl/time/time.h"
#include "mediapipe/framework/deps/clock.h"
namespace mediapipe {
// MonotonicClock is an interface for a Clock that never goes backward.
// Successive returned values from Now() are guaranteed to be monotonically
// non-decreasing, although they may not be monotonic with respect to values
// returned from other instances of MonotonicClock.
//
// You can wrap any Clock object in a MonotonicClock using the
// CreateMonotonicClock() factory method, including Clock::RealClock().
// However, if you want a monotonic version of real time, it is strongly
// recommended that you use the CreateSynchronizedMonotonicClock() factory
// method, which wraps Clock::RealClock() and guarantees that values returned
// from Now() are monotonic ACROSS instances of the class that are created by
// CreateSynchronizedMonotonicClock().
//
// All methods support concurrent access.
class MonotonicClock : public Clock {
public:
// The MonotonicClock state, which may be shared between MonotonicClocks.
struct State;
~MonotonicClock() override {}
// The Clock interface (see util/time/clock.h).
//
// Return a monotonically non-decreasing time.
absl::Time TimeNow() override = 0;
// Sleep and SleepUntil guarantee only that the caller will sleep for at
// least as long as specified in monotonic time. The caller may sleep for
// much longer (in monotonic time) if monotonic time jumps far into the
// future. Whether or not this happens depends on the behavior of the raw
// clock.
void Sleep(absl::Duration d) override = 0;
void SleepUntil(absl::Time wakeup_time) override = 0;
// Get metrics about time corrections.
virtual void GetCorrectionMetrics(int* correction_count,
double* max_correction) = 0;
// Reset values returned by GetCorrectionMetrics().
virtual void ResetCorrectionMetrics() = 0;
// Factory methods.
//
// Create a MonotonicClock based on the given raw_clock. This clock will
// return monotonically non-decreasing values from Now(), but may not behave
// monotonically with respect to other instances created by this function,
// even if they are based on the same raw_clock. Caller owns raw_clock.
static MonotonicClock* CreateMonotonicClock(Clock* raw_clock);
// Create an instance of MonotonicClock that is based on Clock::RealClock().
// All such instance are synced with each other such that return values from
// Now() are monotonic across instances. This allows independently developed
// code bases to have private instances of the synchronized MonotonicClock
// and know that they will never see time anomalies when calling from one
// code base to another. Each instance can have its own correction callback.
// Unlike Clock::RealClock(), caller owns this object and should delete it
// when no longer needed.
static MonotonicClock* CreateSynchronizedMonotonicClock();
};
class MonotonicClockTest;
// Provides access to MonotonicClock::State for unit-testing.
class MonotonicClockAccess {
private:
using State = MonotonicClock::State;
// Reset internal global state. Should only be called by test code.
static void SynchronizedMonotonicClockReset();
static State* CreateMonotonicClockState(Clock* raw_clock);
static void DeleteMonotonicClockState(State* state);
// Create a monotonic clock based on the given state. Caller owns state
// so that multiple such clocks can be created from the same state.
static MonotonicClock* CreateMonotonicClock(State* state);
friend class ::mediapipe::MonotonicClockTest;
};
} // namespace mediapipe
#endif // MEDIAPIPE_DEPS_MONOTONIC_CLOCK_H_
@@ -0,0 +1,539 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/deps/monotonic_clock.h"
#include <stddef.h>
#include <memory>
#include <random>
#include <vector>
#include "absl/base/thread_annotations.h"
#include "absl/memory/memory.h"
#include "absl/synchronization/mutex.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "mediapipe/framework/port/gtest.h"
#include "mediapipe/framework/port/integral_types.h"
#include "mediapipe/framework/port/logging.h"
#include "mediapipe/framework/port/threadpool.h"
#include "mediapipe/framework/tool/simulation_clock.h"
namespace mediapipe {
using RandomEngine = std::mt19937_64;
using State = MonotonicClock::State;
// absl::Now() recomputes clock drift approx. every 2 seconds, so run real
// clock tests for at least that long.
static const absl::Duration kDefaultRealTest = absl::Seconds(2.5);
class MonotonicClockTest : public testing::Test {
protected:
MonotonicClockTest() {}
virtual ~MonotonicClockTest() {}
void SetUp() override {
MonotonicClockAccess::SynchronizedMonotonicClockReset();
}
void VerifyCorrectionMetrics(MonotonicClock* clock,
int num_corrections_expect,
double max_correction_expect) {
int clock_num_corrections;
double clock_max_correction;
clock->GetCorrectionMetrics(&clock_num_corrections, &clock_max_correction);
ASSERT_EQ(num_corrections_expect, clock_num_corrections);
ASSERT_DOUBLE_EQ(max_correction_expect, clock_max_correction);
}
// This test produces no time corrections.
void TestSimulatedForwardTime(SimulationClock* sim_clock,
MonotonicClock* mono_clock) {
absl::Time base_time = sim_clock->TimeNow();
ASSERT_EQ(base_time, mono_clock->TimeNow());
sim_clock->Sleep(absl::Seconds(10));
ASSERT_EQ(base_time + absl::Seconds(10), sim_clock->TimeNow());
ASSERT_EQ(base_time + absl::Seconds(10), mono_clock->TimeNow());
sim_clock->Sleep(absl::Seconds(10));
ASSERT_EQ(base_time + absl::Seconds(20), sim_clock->TimeNow());
ASSERT_EQ(base_time + absl::Seconds(20), mono_clock->TimeNow());
sim_clock->Sleep(absl::Seconds(5));
ASSERT_EQ(base_time + absl::Seconds(25), sim_clock->TimeNow());
ASSERT_EQ(base_time + absl::Seconds(25), mono_clock->TimeNow());
VerifyCorrectionMetrics(mono_clock, 0, 0.0);
}
// This test produces three corrections: one with arguments
// (50, 100, 100), one with (80, 90, 100), and one with (60, 105, 105).
void TestSimulatedBackwardTime(SimulationClock* sim_clock,
MonotonicClock* mono_clock) {
absl::Time base_time = sim_clock->TimeNow();
sim_clock->Sleep(absl::Seconds(100));
ASSERT_EQ(base_time + absl::Seconds(100), sim_clock->TimeNow());
ASSERT_EQ(base_time + absl::Seconds(100), mono_clock->TimeNow());
VerifyCorrectionMetrics(mono_clock, 0, 0.0);
// Time moves backward -- expect a correction.
sim_clock->Sleep(absl::Seconds(-50));
ASSERT_EQ(base_time + absl::Seconds(50), sim_clock->TimeNow());
ASSERT_EQ(base_time + absl::Seconds(100), // correction
mono_clock->TimeNow());
VerifyCorrectionMetrics(mono_clock, 1, 50.0);
// Time moves forward, but not enough to exceed the last value returned by
// TimeNow(). No correction in this case.
sim_clock->Sleep(absl::Seconds(20));
ASSERT_EQ(base_time + absl::Seconds(70), sim_clock->TimeNow());
ASSERT_EQ(base_time + absl::Seconds(100), mono_clock->TimeNow());
VerifyCorrectionMetrics(mono_clock, 1, 50.0);
sim_clock->Sleep(absl::Seconds(20));
ASSERT_EQ(base_time + absl::Seconds(90), sim_clock->TimeNow());
ASSERT_EQ(base_time + absl::Seconds(100), mono_clock->TimeNow());
VerifyCorrectionMetrics(mono_clock, 1, 50.0);
// Time moves backwards again -- expect a correction.
sim_clock->Sleep(absl::Seconds(-10));
ASSERT_EQ(base_time + absl::Seconds(80), sim_clock->TimeNow());
ASSERT_EQ(base_time + absl::Seconds(100), // correction
mono_clock->TimeNow());
VerifyCorrectionMetrics(mono_clock, 2, 50.0);
// Time moves forward enough to advance monotonic time.
sim_clock->Sleep(absl::Seconds(25));
ASSERT_EQ(base_time + absl::Seconds(105), sim_clock->TimeNow());
ASSERT_EQ(base_time + absl::Seconds(105), mono_clock->TimeNow());
VerifyCorrectionMetrics(mono_clock, 2, 50.0);
// Time moves backward again.
sim_clock->Sleep(absl::Seconds(-45));
ASSERT_EQ(base_time + absl::Seconds(60), sim_clock->TimeNow());
ASSERT_EQ(base_time + absl::Seconds(105), // correction
mono_clock->TimeNow());
VerifyCorrectionMetrics(mono_clock, 3, 50.0);
// Reset metrics and re-verify.
mono_clock->ResetCorrectionMetrics();
VerifyCorrectionMetrics(mono_clock, 0, 0.0);
}
// Test that the Sleep/SleepUntil calls do not return until monotonic time
// passes the requested wakeup time.
void TestRandomSleep(MonotonicClock* mono_clock) {
RandomEngine random(testing::UnitTest::GetInstance()->random_seed());
const int kNumSamples = 5;
// Sleep.
for (int i = 0; i < kNumSamples; i++) {
absl::Duration sleep_time = absl::Seconds(
std::uniform_real_distribution<float>(0.0f, 0.2f)(random));
absl::Time before = mono_clock->TimeNow();
absl::Time wakeup_time = before + sleep_time;
mono_clock->Sleep(sleep_time);
absl::Time after = mono_clock->TimeNow();
ASSERT_LE(wakeup_time, after);
}
// SleepUntil.
for (int i = 0; i < kNumSamples; i++) {
absl::Duration sleep_time = absl::Seconds(
std::uniform_real_distribution<float>(0.0f, 0.2f)(random));
absl::Time before = mono_clock->TimeNow();
absl::Time wakeup_time = before + sleep_time;
mono_clock->SleepUntil(wakeup_time);
absl::Time after = mono_clock->TimeNow();
ASSERT_LE(wakeup_time, after);
}
}
static State* CreateMonotonicClockState(Clock* raw_clock) {
return MonotonicClockAccess::CreateMonotonicClockState(raw_clock);
}
static MonotonicClock* CreateMonotonicClock(State* state) {
return MonotonicClockAccess::CreateMonotonicClock(state);
}
static void DeleteMonotonicClockState(State* state) {
MonotonicClockAccess::DeleteMonotonicClockState(state);
}
};
// Time moves forward only -- there should be no time corrections.
TEST_F(MonotonicClockTest, SimulatedForwardTime) {
SimulationClock sim_clock;
sim_clock.ThreadStart();
MonotonicClock* mono_clock = MonotonicClock::CreateMonotonicClock(&sim_clock);
TestSimulatedForwardTime(&sim_clock, mono_clock);
sim_clock.ThreadFinish();
delete mono_clock;
}
// Time moves forward and backward.
TEST_F(MonotonicClockTest, SimulatedBackwardTime) {
SimulationClock sim_clock;
sim_clock.ThreadStart();
MonotonicClock* mono_clock = MonotonicClock::CreateMonotonicClock(&sim_clock);
TestSimulatedBackwardTime(&sim_clock, mono_clock);
sim_clock.ThreadFinish();
delete mono_clock;
}
// Time moves forward and backward.
TEST_F(MonotonicClockTest, SimulatedTime) {
SimulationClock sim_clock;
sim_clock.ThreadStart();
MonotonicClock* mono_clock = MonotonicClock::CreateMonotonicClock(&sim_clock);
TestSimulatedBackwardTime(&sim_clock, mono_clock);
absl::Time mono_time = mono_clock->TimeNow();
sim_clock.Sleep(absl::Seconds(-1));
ASSERT_EQ(mono_time, mono_clock->TimeNow());
sim_clock.ThreadFinish();
delete mono_clock;
}
// Take a random walk through time.
TEST_F(MonotonicClockTest, SimulatedRandomWalk) {
SimulationClock sim_clock;
sim_clock.ThreadStart();
MonotonicClock* mono_clock = MonotonicClock::CreateMonotonicClock(&sim_clock);
sim_clock.Sleep(absl::Now() - sim_clock.TimeNow());
ASSERT_EQ(sim_clock.TimeNow(), mono_clock->TimeNow());
// Generate kNumSamples random clock adjustments.
const int kNumSamples = 5;
RandomEngine random(testing::UnitTest::GetInstance()->random_seed());
// Keep track of maximum time on clock and corrections.
absl::Time max_time = sim_clock.TimeNow();
int num_corrections = 0;
absl::Duration max_correction = absl::ZeroDuration();
for (int i = 0; i < kNumSamples; i++) {
absl::Duration jump =
absl::Seconds(std::uniform_real_distribution<float>(-0.5, 0.5)(random));
sim_clock.Sleep(jump);
absl::Time sim_time = sim_clock.TimeNow();
if (jump < absl::ZeroDuration()) {
ASSERT_LT(sim_time, max_time);
absl::Duration correction = max_time - sim_time;
if (correction > max_correction) {
max_correction = correction;
}
++num_corrections;
}
if (sim_clock.TimeNow() > max_time) {
max_time = sim_clock.TimeNow();
}
ASSERT_EQ(max_time, mono_clock->TimeNow());
}
VerifyCorrectionMetrics(mono_clock, num_corrections,
absl::FDivDuration(max_correction, absl::Seconds(1)));
sim_clock.ThreadFinish();
delete mono_clock;
}
TEST_F(MonotonicClockTest, RealTime) {
MonotonicClock* mono_clock =
MonotonicClock::CreateMonotonicClock(Clock::RealClock());
// Call mono_clock->Now() continuously for FLAGS_real_test_secs seconds.
absl::Time start = absl::Now();
absl::Time time = start;
int64 num_calls = 0;
do {
absl::Time last_time = time;
time = mono_clock->TimeNow();
ASSERT_LE(last_time, time);
++num_calls;
} while (time - start < kDefaultRealTest);
// Just out of curiousity -- did real clock go backwards?
int clock_num_corrections;
mono_clock->GetCorrectionMetrics(&clock_num_corrections, NULL);
LOG(INFO) << clock_num_corrections << " corrections in " << num_calls
<< " calls to mono_clock->Now()";
delete mono_clock;
}
// Test the Sleep interface using a MonotonicClock.
TEST_F(MonotonicClockTest, RandomSleep) {
MonotonicClock* mono_clock =
MonotonicClock::CreateMonotonicClock(Clock::RealClock());
TestRandomSleep(mono_clock);
delete mono_clock;
}
// Test the Sleep interface using a SynchronizedMonotonicClock.
TEST_F(MonotonicClockTest, RandomSleepSynced) {
MonotonicClock* mono_clock =
MonotonicClock::CreateSynchronizedMonotonicClock();
TestRandomSleep(mono_clock);
delete mono_clock;
}
// Test that SleepUntil has no effect if monotonic time has passed the
// requested wakeup time.
TEST_F(MonotonicClockTest, SimulatedInsomnia) {
SimulationClock sim_clock;
sim_clock.ThreadStart();
MonotonicClock* mono_clock = MonotonicClock::CreateMonotonicClock(&sim_clock);
sim_clock.Sleep(absl::Now() - sim_clock.TimeNow());
ASSERT_EQ(sim_clock.TimeNow(), mono_clock->TimeNow());
sim_clock.Sleep(absl::Seconds(-3.14159));
// Even though sim_clock will never advance, this call will not sleep
// because monotonic_time has already advanced beyond the wakeup time.
mono_clock->SleepUntil(sim_clock.TimeNow() + absl::Seconds(1));
// Note that the same test can't be performed with Sleep because the argument
// to sleep is an offset from monotonic time, not raw time.
sim_clock.ThreadFinish();
delete mono_clock;
}
// Two monotonic clocks, clock1 and clock2, each synced to the same
// raw clock. Advance simulated time, read one clock, regress simulated
// time, and read the other clock. The values should be the same.
TEST_F(MonotonicClockTest, SyncedPair) {
SimulationClock sim_clock;
sim_clock.ThreadStart();
State* state = CreateMonotonicClockState(&sim_clock);
MonotonicClock* clock1 = CreateMonotonicClock(state);
MonotonicClock* clock2 = CreateMonotonicClock(state);
sim_clock.Sleep(absl::Seconds(1000));
ASSERT_EQ(sim_clock.TimeNow(), clock1->TimeNow());
ASSERT_EQ(sim_clock.TimeNow(), clock2->TimeNow());
absl::Time time1, time2;
sim_clock.Sleep(absl::Seconds(2));
time1 = clock1->TimeNow();
ASSERT_EQ(sim_clock.TimeNow(), time1);
sim_clock.Sleep(absl::Seconds(-5));
time2 = clock2->TimeNow();
ASSERT_EQ(time1, time2);
VerifyCorrectionMetrics(clock1, 0, 0.0);
VerifyCorrectionMetrics(clock2, 1, 5.0);
clock1->ResetCorrectionMetrics();
clock2->ResetCorrectionMetrics();
VerifyCorrectionMetrics(clock1, 0, 0.0);
VerifyCorrectionMetrics(clock2, 0, 0.0);
// In this example, time on clock1 goes forward by a greater amount than
// time goes backward on clock2. Although clock2 still reports the global
// monotonic time, it does not report a correction because it never
// observed a raw clock reading that went backward.
sim_clock.Sleep(absl::Seconds(10));
time1 = clock1->TimeNow();
ASSERT_EQ(sim_clock.TimeNow(), time1);
sim_clock.Sleep(absl::Seconds(-1));
time2 = clock2->TimeNow();
ASSERT_EQ(time1, time2);
VerifyCorrectionMetrics(clock1, 0, 0.0);
VerifyCorrectionMetrics(clock2, 0, 0.0);
sim_clock.ThreadFinish();
delete clock1;
delete clock2;
DeleteMonotonicClockState(state);
}
// Test that a globally-synchronized MonotonicClock is unaffected by clock
// behavior of a vanilla MonotonicClock.
TEST_F(MonotonicClockTest, UnsyncedPair) {
SimulationClock sim_clock;
sim_clock.ThreadStart();
MonotonicClock* sync_clock =
MonotonicClock::CreateSynchronizedMonotonicClock();
MonotonicClock* mono_clock = MonotonicClock::CreateMonotonicClock(&sim_clock);
absl::Time before = sync_clock->TimeNow();
sim_clock.Sleep(before - sim_clock.TimeNow());
ASSERT_EQ(before, mono_clock->TimeNow());
sim_clock.Sleep(absl::Seconds(61));
ASSERT_LT(sync_clock->TimeNow(), mono_clock->TimeNow());
sim_clock.ThreadFinish();
delete sync_clock;
delete mono_clock;
}
// The factory method CreateSynchronizedMonotonicClock should return a
// MonotonicClock based on real time. Since time waits for no unit test,
// we can't test equality of the time read from the factory-produced clock
// and the time read from a real clock. But we can verifying that, as long
// as the real clock moves forward, the time read from the factory-produced
// clock is bounded by consecutive readings of the real clock.
TEST_F(MonotonicClockTest, CreateSynchronizedMonotonicClock) {
Clock* real_clock = Clock::RealClock();
MonotonicClock* mono_clock =
MonotonicClock::CreateSynchronizedMonotonicClock();
const int kNumSamples = 100;
for (int i = 0; i < kNumSamples; ++i) {
absl::Time before = real_clock->TimeNow();
absl::Time now = mono_clock->TimeNow();
absl::Time after = real_clock->TimeNow();
if (after < before) {
// Real clock moved backward -- test is invalid.
continue;
}
ASSERT_LE(before, now);
ASSERT_LE(now, after);
}
delete mono_clock;
}
// Start up a number of threads to beat on the interface to verify that
// (a) nothing crashes and (b) nothing deadlocks.
class ClockFrenzy {
public:
ClockFrenzy()
: real_clock_(Clock::RealClock()),
random_(
new RandomEngine(testing::UnitTest::GetInstance()->random_seed())) {
}
void AddSimulationClock(SimulationClock* clock) {
sim_clocks_.push_back(clock);
}
void AddMonotonicClock(MonotonicClock* clock) {
mono_clocks_.push_back(clock);
}
void Feed() {
while (Running()) {
// 40% of the time, advance a simulated clock.
// 50% of the time, read a monotonic clock.
const int32 u = UniformRandom(100);
if (u < 40) {
// Pick a simulated clock and advance it.
const int nclocks = sim_clocks_.size();
if (nclocks == 0) continue;
SimulationClock* sim_clock = sim_clocks_[UniformRandom(nclocks)];
// Bias the clock towards forward movement.
sim_clock->Sleep(absl::Seconds(RndFloatRandom() - 0.2));
} else if (u < 90) {
// Pick a monotonic clock and read it.
const int nclocks = mono_clocks_.size();
if (nclocks == 0) continue;
MonotonicClock* mono_clock = mono_clocks_[UniformRandom(nclocks)];
mono_clock->TimeNow();
}
}
}
// Start Feed-ing threads.
void Start(int nthreads) {
absl::MutexLock l(&lock_);
running_ = true;
threads_ = absl::make_unique<::mediapipe::ThreadPool>("Frenzy", nthreads);
threads_->StartWorkers();
for (int i = 0; i < nthreads; ++i) {
threads_->Schedule([&]() { Feed(); });
}
}
void Stop() {
absl::MutexLock l(&lock_);
running_ = false;
}
bool Running() {
absl::MutexLock l(&lock_);
return running_;
}
// Wait for all threads to finish.
void Wait() { threads_.reset(); }
private:
Clock* real_clock_;
std::vector<SimulationClock*> sim_clocks_;
std::vector<MonotonicClock*> mono_clocks_;
std::unique_ptr<::mediapipe::ThreadPool> threads_;
// Provide a lock to avoid race conditions in non-threadsafe ACMRandom.
mutable absl::Mutex lock_;
std::unique_ptr<RandomEngine> random_ GUARDED_BY(lock_);
// The stopping notification.
bool running_;
// Thread-safe random number generation functions for use by other class
// member functions.
int32 UniformRandom(int32 n) {
absl::MutexLock l(&lock_);
return std::uniform_int_distribution<int32>(0, n - 1)(*random_);
}
float RndFloatRandom() {
absl::MutexLock l(&lock_);
return std::uniform_real_distribution<float>(0.0f, 1.0f)(*random_);
}
};
TEST_F(MonotonicClockTest, SimulatedFrenzy) {
ClockFrenzy f;
SimulationClock s1, s2;
s1.ThreadStart();
s2.ThreadStart();
f.AddSimulationClock(&s1);
f.AddSimulationClock(&s2);
MonotonicClock* m11 = MonotonicClock::CreateMonotonicClock(&s1);
State* state = CreateMonotonicClockState(&s1);
MonotonicClock* m12 = CreateMonotonicClock(state);
MonotonicClock* m13 = CreateMonotonicClock(state);
MonotonicClock* m21 = MonotonicClock::CreateMonotonicClock(&s2);
MonotonicClock* m22 = MonotonicClock::CreateMonotonicClock(&s2);
f.AddMonotonicClock(m11);
f.AddMonotonicClock(m12);
f.AddMonotonicClock(m13);
f.AddMonotonicClock(m21);
f.AddMonotonicClock(m22);
f.Start(10);
Clock::RealClock()->Sleep(absl::Seconds(1));
f.Stop();
f.Wait();
s2.ThreadFinish();
s1.ThreadFinish();
delete m11;
delete m12;
delete m13;
delete m21;
delete m22;
DeleteMonotonicClockState(state);
}
// Just for completeness, a frenzy with only real-time
// SynchronizedMonotonicClock instances.
TEST_F(MonotonicClockTest, RealFrenzy) {
ClockFrenzy f;
MonotonicClock* m1 = MonotonicClock::CreateSynchronizedMonotonicClock();
MonotonicClock* m2 = MonotonicClock::CreateSynchronizedMonotonicClock();
MonotonicClock* m3 = MonotonicClock::CreateSynchronizedMonotonicClock();
f.AddMonotonicClock(m1);
f.AddMonotonicClock(m2);
f.AddMonotonicClock(m3);
f.Start(10);
Clock::RealClock()->Sleep(kDefaultRealTest);
f.Stop();
f.Wait();
// Just out of curiousity -- did real clock go backwards?
int clock_num_corrections;
m1->GetCorrectionMetrics(&clock_num_corrections, NULL);
LOG_IF(INFO, clock_num_corrections > 0)
<< clock_num_corrections << " corrections";
m2->GetCorrectionMetrics(&clock_num_corrections, NULL);
LOG_IF(INFO, clock_num_corrections > 0)
<< clock_num_corrections << " corrections";
m3->GetCorrectionMetrics(&clock_num_corrections, NULL);
LOG_IF(INFO, clock_num_corrections > 0)
<< clock_num_corrections << " corrections";
delete m1;
delete m2;
delete m3;
}
} // namespace mediapipe
+115
View File
@@ -0,0 +1,115 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_DEPS_NO_DESTRUCTOR_H_
#define MEDIAPIPE_DEPS_NO_DESTRUCTOR_H_
#include <type_traits>
#include <utility>
namespace mediapipe {
// NoDestructor<T> is a wrapper around an object of type T that
// * stores the object of type T inline inside NoDestructor<T>
// * eagerly forwards constructor arguments to it (i.e. acts like T in terms
// of construction)
// * provides access to the object of type T like a pointer via ->, *, and get()
// (note that const NoDestructor<T> works like a pointer to const T)
// * never calls T's destructor for the object
// (hence NoDestructor<T> objects created on the stack or as member variables
// will lead to memory and/or resource leaks)
//
// One key use case of NoDestructor (which in itself is not lazy) is optimizing
// the following pattern of safe on-demand construction of an object with
// non-trivial constructor in static storage without destruction ever happening:
// const std::string& MyString() {
// static std::string* x = new std::string("foo"); // note the "static"
// return *x;
// }
// By using NoDestructor we do not need to involve heap allocation and
// corresponding pointer following (and hence extra CPU cache usage/needs)
// on each access:
// const std::string& MyString() {
// static NoDestructor<std::string> x("foo");
// return *x;
// }
// Since C++11 this static-in-a-function pattern results in exactly-once,
// thread-safe, on-demand construction of an object, and very fast access
// thereafter (the cost is a few extra cycles).
// NoDestructor makes accesses even faster by storing the object inline in
// static storage.
//
// Note that:
// * Since destructor is never called, the object lives on during program exit
// and can be safely accessed by any threads that have not been joined.
// * This static-in-a-function NoDestructor usage pattern should be preferred
// to uses of gtl::LazyStaticPtr in new code.
//
// Also note that
// static NoDestructor<NonPOD> ptr(whatever);
// can safely replace
// static NonPOD* ptr = new NonPOD(whatever);
// or
// static NonPOD obj(whatever);
// at file-level scope when the safe static-in-a-function pattern is infeasible
// to use for some good reason.
// All three of the NonPOD patterns above suffer from the same issue that
// initialization of that object happens non-thread-safely at
// a globally-undefined point during initialization of static-storage objects,
// but NoDestructor<> usage provides both the safety of having the object alive
// during program exit sequence and the performance of not doing extra memory
// dereference on access.
//
template <typename T>
class NoDestructor {
public:
typedef T element_type;
// Forwards arguments to the T's constructor: calls T(args...).
template <typename... Ts,
// Disable this overload when it might collide with copy/move.
typename std::enable_if<
!std::is_same<void(typename std::decay<Ts>::type...),
void(NoDestructor)>::value,
int>::type = 0>
explicit NoDestructor(Ts&&... args) {
new (&space_) T(std::forward<Ts>(args)...);
}
// Forwards copy and move construction for T. Enables usage like this:
// static NoDestructor<std::array<std::string, 3>> x{{{"1", "2", "3"}}};
// static NoDestructor<std::vector<int>> x{{1, 2, 3}};
explicit NoDestructor(const T& x) { new (&space_) T(x); }
explicit NoDestructor(T&& x) { new (&space_) T(std::move(x)); }
// No copying.
NoDestructor(const NoDestructor&) = delete;
NoDestructor& operator=(const NoDestructor&) = delete;
// Pretend to be a smart pointer to T with deep constness.
// Never returns a null pointer.
T& operator*() { return *get(); }
T* operator->() { return get(); }
T* get() { return reinterpret_cast<T*>(&space_); }
const T& operator*() const { return *get(); }
const T* operator->() const { return get(); }
const T* get() const { return reinterpret_cast<const T*>(&space_); }
private:
typename std::aligned_storage<sizeof(T), alignof(T)>::type space_;
};
} // namespace mediapipe
#endif // MEDIAPIPE_DEPS_NO_DESTRUCTOR_H_
+32
View File
@@ -0,0 +1,32 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_DEPS_NUMBERS_H_
#define MEDIAPIPE_DEPS_NUMBERS_H_
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "mediapipe/framework/port/integral_types.h"
namespace mediapipe {
ABSL_MUST_USE_RESULT inline std::string SimpleDtoa(double d) {
if (static_cast<double>(static_cast<int64>(d)) == d) {
return absl::StrCat(static_cast<int64>(d));
} else {
return absl::StrCat(d);
}
}
} // namespace mediapipe
#endif // MEDIAPIPE_DEPS_NUMBERS_H_
+137
View File
@@ -0,0 +1,137 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Class to handle two-dimensional points.
//
// The aim of this class is to be able to do sensible geometric operations
// with points and vectors, which are distinct mathematical concepts.
// Operators +, -, =, ==, <, etc. are overloaded with the proper semantics
// (e.g. Point = Point + constant * vector or Vector = Point - Point).
// For more about Point expressions, see Goldman, Ronald N., "Illicit
// Expressions in Vector Algebra," ACM Transactions on Graphics, 4(3),
// pp. 223-243, July 1985 (http://portal.acm.org/citation.cfm?id=282969).
//
// Please be careful about overflows when using points with integer types
// The calculations are carried with the same type as the vector's components
// type, e.g. if you are using uint8 as the base type, all values will be modulo
// 256. This feature is necessary to use the class in a more general framework
// where T != plain old data type.
#ifndef MEDIAPIPE_DEPS_POINT2_H_
#define MEDIAPIPE_DEPS_POINT2_H_
#include <cmath>
#include <cstdlib>
#include <iosfwd>
#include "mediapipe/framework/deps/mathutil.h"
#include "mediapipe/framework/deps/vector.h"
#include "mediapipe/framework/port/integral_types.h"
#include "mediapipe/framework/port/logging.h"
// Template class for 2D points
template <typename T>
class Point2 {
public:
typedef T ElementType;
typedef Vector2<T> Coords;
Point2() {}
Point2(const T& x, const T& y) : c_(x, y) {}
explicit Point2(const Coords& v) : c_(v) {}
Coords ToVector() const { return c_; }
void Set(const T& x, const T& y) { *this = Point2(x, y); }
T* Data() { return c_.Data(); }
const T* Data() const { return c_.Data(); }
void Clear() { *this = Point2(); }
Point2& operator+=(const Coords& v) {
c_ += v;
return *this;
}
Point2& operator-=(const Coords& v) {
c_ -= v;
return *this;
}
const T& operator[](std::size_t b) const { return Data()[b]; }
T& operator[](std::size_t b) { return Data()[b]; }
const T& x() const { return (*this)[0]; }
const T& y() const { return (*this)[1]; }
void set_x(const T& x) { (*this)[0] = x; }
void set_y(const T& y) { (*this)[1] = y; }
// Compares two points, returns true if all their components are within
// a difference of a tolerance.
bool aequal(const Point2& p, double tolerance) const {
using std::abs;
return (abs(c_[0] - p.c_[0]) <= tolerance) &&
(abs(c_[1] - p.c_[1]) <= tolerance);
}
private:
// Friend arithmetic operators.
friend Point2 operator+(const Point2& p, const Coords& v) {
return Point2(p.c_ + v);
}
friend Point2 operator+(const Coords& v, const Point2& p) {
return Point2(v + p.c_);
}
friend Point2 operator-(const Point2& p, const Coords& v) {
return Point2(p.c_ - v);
}
friend Coords operator-(const Point2& p1, const Point2& p2) {
return p1.c_ - p2.c_;
}
// Friend relational nonmember operators.
friend bool operator==(const Point2& a, const Point2& b) {
return a.c_ == b.c_;
}
friend bool operator!=(const Point2& a, const Point2& b) {
return a.c_ != b.c_;
}
friend bool operator<(const Point2& a, const Point2& b) {
return a.c_ < b.c_;
}
friend bool operator>(const Point2& a, const Point2& b) {
return a.c_ > b.c_;
}
friend bool operator<=(const Point2& a, const Point2& b) {
return a.c_ <= b.c_;
}
friend bool operator>=(const Point2& a, const Point2& b) {
return a.c_ >= b.c_;
}
// Streaming operator.
friend std::ostream& operator<<(std::ostream& out, const Point2& p) {
return out << "Point with coordinates: (" << p.c_[0] << ", " << p.c_[1]
<< ")";
}
Coords c_; // coordinates
};
typedef Point2<uint8> Point2_b;
typedef Point2<int> Point2_i;
typedef Point2<float> Point2_f;
typedef Point2<double> Point2_d;
#endif // MEDIAPIPE_DEPS_POINT2_H_
@@ -0,0 +1,40 @@
syntax = "proto2";
package mediapipe;
// Describes a field within a message.
message FieldDescriptorProto {
enum Type {
// 0 is reserved for errors.
TYPE_INVALID = 0;
// Order is weird for historical reasons.
TYPE_DOUBLE = 1;
TYPE_FLOAT = 2;
// Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if
// negative values are likely.
TYPE_INT64 = 3;
TYPE_UINT64 = 4;
// Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if
// negative values are likely.
TYPE_INT32 = 5;
TYPE_FIXED64 = 6;
TYPE_FIXED32 = 7;
TYPE_BOOL = 8;
TYPE_STRING = 9;
// Tag-delimited aggregate.
// Group type is deprecated and not supported in proto3. However, Proto3
// implementations should still be able to parse the group wire format and
// treat group fields as unknown fields.
TYPE_GROUP = 10;
TYPE_MESSAGE = 11; // Length-delimited aggregate.
// New in version 2.
TYPE_BYTES = 12;
TYPE_UINT32 = 13;
TYPE_ENUM = 14;
TYPE_SFIXED32 = 15;
TYPE_SFIXED64 = 16;
TYPE_SINT32 = 17; // Uses ZigZag encoding.
TYPE_SINT64 = 18; // Uses ZigZag encoding.
}
}
+27
View File
@@ -0,0 +1,27 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_DEPS_RANDOM_BASE_H_
#define MEDIAPIPE_DEPS_RANDOM_BASE_H_
class RandomBase {
public:
// constructors. Don't do too much.
RandomBase() {}
virtual ~RandomBase();
virtual float RandFloat() { return 0; }
};
#endif // MEDIAPIPE_DEPS_RANDOM_BASE_H_
+328
View File
@@ -0,0 +1,328 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Class for axis-aligned rectangles represented as two corner points
// (min_x, min_y) and (max_x, max_y). The methods such as Contain, Intersect
// and IsEmpty() assume that the points in region include the 4 boundary edges.
// The default box is initialized so that IsEmpty() is true. Note that the
// use of corner points supports both right-handed (Cartesian) and left-
// handed (image) coordinate systems.
#ifndef MEDIAPIPE_DEPS_RECTANGLE_H_
#define MEDIAPIPE_DEPS_RECTANGLE_H_
#include <algorithm>
#include <cmath>
#include <iosfwd>
#include <limits>
#include <ostream>
#include "mediapipe/framework/deps/point2.h"
#include "mediapipe/framework/port/integral_types.h"
template <typename T>
class Rectangle;
template <typename T>
std::ostream& operator<<(std::ostream&, const Rectangle<T>&);
template <typename T>
class Rectangle {
public:
typedef Rectangle<T> Self;
// Default constructed rectangle which is empty.
Rectangle() { SetEmpty(); }
// Creates a rectangle from the minimum point and the dimensions.
Rectangle(const T& x, const T& y, const T& width, const T& height);
// Creates a rectangle given two points. The resulting rectangle will
// have non-negative width and height.
Rectangle(const Point2<T>& p0, const Point2<T>& p1);
// Same as above but using vectors as input.
Rectangle(const Vector2<T>& p0, const Vector2<T>& p1);
// Sets min to be very large numbers and max to be very large negative numbers
// so that points can be used to correctly extend the rectangle.
void SetEmpty();
// A rectangle is empty if there are no points inside of it. A degenerate
// rectangle where the corners are coincident has zero area but is not empty.
bool IsEmpty() const { return min_.x() > max_.x() || min_.y() > max_.y(); }
bool operator==(const Rectangle&) const;
bool operator!=(const Rectangle&) const;
// Width and height are both max - min, which may be negative if SetEmpty()
// was called or the user explicity set the min and max points.
T Width() const { return max_.x() - min_.x(); }
T Height() const { return max_.y() - min_.y(); }
// Computes the area, which is negative if the width xor height is negative.
// The value is undefined if SetEmpty() is called.
// Watch out for large integer rectangles because the area may overflow.
T Area() const { return Width() * Height(); }
// Accessors are provided for both points and sides.
const T& xmin() const { return min_.x(); }
const T& xmax() const { return max_.x(); }
const T& ymin() const { return min_.y(); }
const T& ymax() const { return max_.y(); }
// Returns the min and max corner points.
const Point2<T>& min_xy() const { return min_; }
const Point2<T>& max_xy() const { return max_; }
// Sets the geometry of the rectangle given two points.
// The resulting rectangle will have non-negative width and height.
void Set(const Point2<T>& p0, const Point2<T>& p1);
// Same as above using vectors as input.
void Set(const Vector2<T>& p0, const Vector2<T>& p1);
// Sets the geometry of the rectangle given a minimum point and dimensions.
void Set(const T& x, const T& y, const T& width, const T& height);
// Sets the min and max values, and min greater than max is allowable,
// but the user has to be aware of the consequences such as negative width
// and height. Both point and side accessors are provided.
void set_xmin(const T& x) { min_.set_x(x); }
void set_xmax(const T& x) { max_.set_x(x); }
void set_ymin(const T& y) { min_.set_y(y); }
void set_ymax(const T& y) { max_.set_y(y); }
void set_min_xy(const Point2<T>& p) { min_.Set(p.x(), p.y()); }
void set_max_xy(const Point2<T>& p) { max_.Set(p.x(), p.y()); }
// Expands a rectangle to contain a point or vector.
void Expand(const T& x, const T& y);
void Expand(const Point2<T>& p);
void Expand(const Vector2<T>& p);
// Expands a rectangle to contain another rectangle.
void Expand(const Rectangle& other);
// Returns the union of this rectangle with another rectangle, which
// is the smallest rectangle that contains both rectangles.
Rectangle Union(const Rectangle& other) const;
// Returns the intersection of this rectangle with another rectangle.
// If the intersection is empty, returns a rectangle initialized by
// SetEmpty().
Rectangle Intersect(const Rectangle& other) const;
// Tests if this rectangle has a non-empty intersection with another rectangle
// including the boundary.
bool Intersects(const Rectangle& other) const;
// Tests if a point is inside or on any of the 4 edges of the rectangle.
bool Contains(const T& x, const T& y) const;
bool Contains(const Point2<T>& pt) const;
bool Contains(const Vector2<T>& pt) const;
// Tests if a rectangle is inside or on any of the 4 edges of the rectangle.
bool Contains(const Rectangle& other) const;
// Translates this rectangle by a vector.
void Translate(const Vector2<T>& v);
// Adds a border around the rectangle by subtracting the border size from the
// min point and adding it to the max point. The border size can be
// negative.
void AddBorder(const T& border_size);
// Debug printing.
friend std::ostream& operator<<<T>(std::ostream&, const Rectangle&);
private:
Point2<T> min_;
Point2<T> max_;
};
//
// Inline method definitions. These are not placed in the definition of the
// class to keep the class interface more readable.
//
template <typename T>
Rectangle<T>::Rectangle(const Point2<T>& p0, const Point2<T>& p1) {
Set(p0, p1);
}
template <typename T>
Rectangle<T>::Rectangle(const Vector2<T>& p0, const Vector2<T>& p1) {
Set(p0, p1);
}
template <typename T>
Rectangle<T>::Rectangle(const T& x, const T& y, const T& width,
const T& height) {
Set(x, y, width, height);
}
// The general version works only when T models Integer (there are more
// integer classes than float classes).
template <typename T>
void Rectangle<T>::SetEmpty() {
T min_value = std::numeric_limits<T>::min();
T max_value = std::numeric_limits<T>::max();
min_.Set(max_value, max_value);
max_.Set(min_value, min_value);
}
template <>
inline void Rectangle<float>::SetEmpty() {
float max_value = std::numeric_limits<float>::max();
min_.Set(max_value, max_value);
max_.Set(-max_value, -max_value);
}
template <>
inline void Rectangle<double>::SetEmpty() {
double max_value = std::numeric_limits<double>::max();
min_.Set(max_value, max_value);
max_.Set(-max_value, -max_value);
}
template <typename T>
bool Rectangle<T>::operator==(const Rectangle<T>& other) const {
return min_ == other.min_ && max_ == other.max_;
}
template <typename T>
bool Rectangle<T>::operator!=(const Rectangle<T>& other) const {
return !(*this == other);
}
template <typename T>
void Rectangle<T>::Set(const Vector2<T>& p0, const Vector2<T>& p1) {
if (p0[0] <= p1[0])
min_.set_x(p0[0]), max_.set_x(p1[0]);
else
max_.set_x(p0[0]), min_.set_x(p1[0]);
if (p0[1] <= p1[1])
min_.set_y(p0[1]), max_.set_y(p1[1]);
else
max_.set_y(p0[1]), min_.set_y(p1[1]);
}
template <typename T>
void Rectangle<T>::Set(const Point2<T>& p0, const Point2<T>& p1) {
Set(p0.ToVector(), p1.ToVector());
}
template <typename T>
void Rectangle<T>::Set(const T& x, const T& y, const T& width,
const T& height) {
min_.Set(x, y);
max_.Set(x + width, y + height);
}
template <typename T>
void Rectangle<T>::Expand(const T& x, const T& y) {
min_.Set(std::min(x, xmin()), std::min(y, ymin()));
max_.Set(std::max(x, xmax()), std::max(y, ymax()));
}
template <typename T>
void Rectangle<T>::Expand(const Point2<T>& p) {
Expand(p.x(), p.y());
}
template <typename T>
void Rectangle<T>::Expand(const Vector2<T>& v) {
Expand(v[0], v[1]);
}
template <typename T>
void Rectangle<T>::Expand(const Rectangle<T>& other) {
Expand(other.min_);
Expand(other.max_);
}
template <typename T>
void Rectangle<T>::Translate(const Vector2<T>& v) {
min_ += v;
max_ += v;
}
template <typename T>
bool Rectangle<T>::Contains(const T& x, const T& y) const {
return x >= xmin() && x <= xmax() && y >= ymin() && y <= ymax();
}
template <typename T>
bool Rectangle<T>::Contains(const Point2<T>& p) const {
return Contains(p.x(), p.y());
}
template <typename T>
bool Rectangle<T>::Contains(const Vector2<T>& v) const {
return Contains(v[0], v[1]);
}
template <typename T>
bool Rectangle<T>::Contains(const Rectangle<T>& r) const {
return Contains(r.min_) && Contains(r.max_);
}
template <typename T>
Rectangle<T> Rectangle<T>::Union(const Rectangle<T>& r) const {
return Rectangle<T>(
Point2<T>(std::min(xmin(), r.xmin()), std::min(ymin(), r.ymin())),
Point2<T>(std::max(xmax(), r.xmax()), std::max(ymax(), r.ymax())));
}
template <typename T>
Rectangle<T> Rectangle<T>::Intersect(const Rectangle<T>& r) const {
Point2<T> pmin(std::max(xmin(), r.xmin()), std::max(ymin(), r.ymin()));
Point2<T> pmax(std::min(xmax(), r.xmax()), std::min(ymax(), r.ymax()));
if (pmin.x() > pmax.x() || pmin.y() > pmax.y())
return Rectangle<T>();
else
return Rectangle<T>(pmin, pmax);
}
template <typename T>
bool Rectangle<T>::Intersects(const Rectangle<T>& r) const {
return !(IsEmpty() || r.IsEmpty() || r.xmax() < xmin() || xmax() < r.xmin() ||
r.ymax() < ymin() || ymax() < r.ymin());
}
template <typename T>
void Rectangle<T>::AddBorder(const T& border_size) {
min_.Set(xmin() - border_size, ymin() - border_size);
max_.Set(xmax() + border_size, ymax() + border_size);
}
template <typename T>
std::ostream& operator<<(std::ostream& out, const Rectangle<T>& r) {
out << "[(" << r.xmin() << ", " << r.ymin() << "), (" << r.xmax() << ", "
<< r.ymax() << ")]";
return out;
}
template <typename T>
class Rectangle;
typedef Rectangle<uint8> Rectangle_b;
typedef Rectangle<int> Rectangle_i;
typedef Rectangle<float> Rectangle_f;
typedef Rectangle<double> Rectangle_d;
#endif // MEDIAPIPE_DEPS_RECTANGLE_H_
+40
View File
@@ -0,0 +1,40 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/deps/registration.h"
namespace mediapipe {
namespace {
constexpr char const* kTopNamespaces[] = {
"mediapipe",
};
template <size_t SIZE, class T>
inline size_t array_size(T (&arr)[SIZE]) {
return SIZE;
}
} // namespace
/*static*/
const std::unordered_set<std::string>& NamespaceWhitelist::TopNamespaces() {
static std::unordered_set<std::string>* result =
new std::unordered_set<std::string>(
kTopNamespaces, kTopNamespaces + array_size(kTopNamespaces));
return *result;
}
} // namespace mediapipe
+387
View File
@@ -0,0 +1,387 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_DEPS_REGISTRATION_H_
#define MEDIAPIPE_DEPS_REGISTRATION_H_
#include <algorithm>
#include <functional>
#include <string>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include "absl/base/macros.h"
#include "absl/base/thread_annotations.h"
#include "absl/meta/type_traits.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "absl/synchronization/mutex.h"
#include "mediapipe/framework/deps/registration_token.h"
#include "mediapipe/framework/port/canonical_errors.h"
#include "mediapipe/framework/port/logging.h"
#include "mediapipe/framework/port/statusor.h"
namespace mediapipe {
// Usage:
//
// === Defining a registry ================================================
//
// class Widget {};
//
// using WidgetRegistry =
// GlobalFactoryRegistry<unique_ptr<Widget>, // return
// unique_ptr<Gadget>, const Thing*> // args
//
// === Registering an implementation =======================================
//
// class MyWidget : public Widget {
// static unique_ptr<Widget> Create(unique_ptr<Gadget> arg,
// const Thing* thing) {
// return MakeUnique<Widget>(std::move(arg), thing);
// }
// ...
// };
//
// REGISTER_FACTORY_FUNCTION_QUALIFIED(
// WidgetRegistry, widget_registration,
// ::my_ns::MyWidget, MyWidget::Create);
//
// === Using std::function =================================================
//
// class Client {};
//
// using ClientRegistry =
// GlobalFactoryRegistry<::mediapipe::StatusOr<unique_ptr<Client>>;
//
// class MyClient : public Client {
// public:
// MyClient(unique_ptr<Backend> backend)
// : backend_(std::move(backend)) {}
// private:
// const std::unique_ptr<Backend> backend_;
// };
//
// // Any std::function that returns a Client is valid to pass here. Below,
// // we use a lambda.
// REGISTER_FACTORY_FUNCTION_QUALIFIED(
// ClientRegistry, client_registration,
// ::my_ns::MyClient,
// []() {
// auto backend = absl::make_unique<Backend>("/path/to/backend");
// const ::mediapipe::Status status = backend->Init();
// if (!status.ok()) {
// return status;
// }
// std::unique_ptr<Client> client
// = absl::make_unique<MyClient>(std::move(backend));
// return client;
// });
//
// === Using the registry to create instances ==============================
//
// // Registry will return ::mediapipe::StatusOr<Object>
// ::mediapipe::StatusOr<unique_ptr<Widget>> s_or_widget =
// WidgetRegistry::CreateByName(
// "my_ns.MyWidget", std::move(gadget), thing);
// // Registry will return NOT_FOUND if the name is unknown.
// if (!s_or_widget.ok()) ... // handle error
// DoStuffWithWidget(std::move(s_or_widget).ValueOrDie());
//
// // It's also possible to find an instance by name within a source namespace.
// auto s_or_widget = WidgetRegistry::CreateByNameInNamespace(
// "my_ns.sub_namespace", "MyWidget");
//
// // It's also possible to just check if a name is registered without creating
// // an instance.
// bool registered = WidgetRegistry::IsRegistered("my_ns::MyWidget");
//
// // It's also possible to iterate through all registered function names.
// // This might be useful if clients outside of your codebase are registering
// // plugins.
// for (const auto& name : WidgetRegistry::GetRegisteredNames()) {
// ::mediapipe::StatusOr<unique_ptr<Widget>> s_or_widget =
// WidgetRegistry::CreateByName(name, std::move(gadget), thing);
// ...
// }
//
// === Injecting instances for testing =====================================
//
// Unregister unregisterer(WidgetRegistry::Register(
// "MockWidget",
// [](unique_ptr<Gadget> arg, const Thing* thing) {
// ...
// }));
namespace registration_internal {
constexpr char kCxxSep[] = "::";
constexpr char kNameSep[] = ".";
template <typename T>
struct WrapStatusOr {
using type = ::mediapipe::StatusOr<T>;
};
// Specialization to avoid double-wrapping types that are already StatusOrs.
template <typename T>
struct WrapStatusOr<::mediapipe::StatusOr<T>> {
using type = ::mediapipe::StatusOr<T>;
};
} // namespace registration_internal
class NamespaceWhitelist {
public:
static const std::unordered_set<std::string>& TopNamespaces();
};
template <typename R, typename... Args>
class FunctionRegistry {
public:
using Function = std::function<R(Args...)>;
using ReturnType = typename registration_internal::WrapStatusOr<R>::type;
FunctionRegistry() {}
FunctionRegistry(const FunctionRegistry&) = delete;
FunctionRegistry& operator=(const FunctionRegistry&) = delete;
RegistrationToken Register(const std::string& name, Function func)
LOCKS_EXCLUDED(lock_) {
std::string normalized_name = GetNormalizedName(name);
absl::WriterMutexLock lock(&lock_);
std::string adjusted_name = GetAdjustedName(normalized_name);
if (adjusted_name != normalized_name) {
functions_.insert(std::make_pair(adjusted_name, func));
}
if (functions_.insert(std::make_pair(normalized_name, std::move(func)))
.second) {
return RegistrationToken(
[this, normalized_name]() { Unregister(normalized_name); });
}
LOG(FATAL) << "Function with name " << name << " already registered.";
return RegistrationToken([]() {});
}
// Force 'args' to be deduced by templating the function, instead of just
// accepting Args. This is necessary to make 'args' a forwarding reference as
// opposed to a plain rvalue reference.
// https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers
//
// The absl::enable_if_t is used to disable this method if Args2 are not
// convertible to Args. This will allow the compiler to identify the offending
// line (i.e. the line where the method is called) in the first error message,
// rather than nesting it multiple levels down the error stack.
template <typename... Args2,
absl::enable_if_t<std::is_convertible<std::tuple<Args2...>,
std::tuple<Args...>>::value,
int> = 0>
ReturnType Invoke(const std::string& name, Args2&&... args)
LOCKS_EXCLUDED(lock_) {
Function function;
{
absl::ReaderMutexLock lock(&lock_);
auto it = functions_.find(name);
if (it == functions_.end()) {
return ::mediapipe::NotFoundError("No registered object with name: " +
name);
}
function = it->second;
}
return function(std::forward<Args2>(args)...);
}
// Invokes the specified factory function and returns the result.
// Namespaces in |name| and |ns| are separated by kNameSep.
template <typename... Args2>
ReturnType Invoke(const std::string& ns, const std::string& name,
Args2&&... args) LOCKS_EXCLUDED(lock_) {
return Invoke(GetQualifiedName(ns, name), args...);
}
// Note that it's possible for registered implementations to be subsequently
// unregistered, though this will never happen with registrations made via
// MEDIAPIPE_REGISTER_FACTORY_FUNCTION.
bool IsRegistered(const std::string& name) const LOCKS_EXCLUDED(lock_) {
absl::ReaderMutexLock lock(&lock_);
return functions_.count(name) != 0;
}
// Returns true if the specified factory function is available.
// Namespaces in |name| and |ns| are separated by kNameSep.
bool IsRegistered(const std::string& ns, const std::string& name) const
LOCKS_EXCLUDED(lock_) {
return IsRegistered(GetQualifiedName(ns, name));
}
// Returns a vector of all registered function names.
// Note that it's possible for registered implementations to be subsequently
// unregistered, though this will never happen with registrations made via
// MEDIAPIPE_REGISTER_FACTORY_FUNCTION.
std::unordered_set<std::string> GetRegisteredNames() const
LOCKS_EXCLUDED(lock_) {
absl::ReaderMutexLock lock(&lock_);
std::unordered_set<std::string> names;
std::for_each(functions_.cbegin(), functions_.cend(),
[&names](const std::pair<const std::string, Function>& pair) {
names.insert(pair.first);
});
return names;
}
// Normalizes a C++ qualified name. Validates the name qualification.
// The name must be either unqualified or fully qualified with a leading "::".
// The leading "::" in a fully qualified name is stripped.
std::string GetNormalizedName(const std::string& name) {
constexpr auto kCxxSep = registration_internal::kCxxSep;
std::vector<std::string> names = absl::StrSplit(name, kCxxSep);
if (names[0].empty()) {
names.erase(names.begin());
} else {
CHECK_EQ(1, names.size())
<< "A registered class name must be either fully qualified "
<< "with a leading :: or unqualified, got: " << name << ".";
}
return absl::StrJoin(names, kCxxSep);
}
// Returns the registry key for a name specified within a namespace.
// Namespaces are separated by kNameSep.
std::string GetQualifiedName(const std::string& ns,
const std::string& name) const {
constexpr auto kCxxSep = registration_internal::kCxxSep;
constexpr auto kNameSep = registration_internal::kNameSep;
std::vector<std::string> names = absl::StrSplit(name, kNameSep);
if (names[0].empty()) {
names.erase(names.begin());
return absl::StrJoin(names, kCxxSep);
}
std::string cxx_name = absl::StrJoin(names, kCxxSep);
if (ns.empty()) {
return cxx_name;
}
std::vector<std::string> spaces = absl::StrSplit(ns, kNameSep);
absl::ReaderMutexLock lock(&lock_);
while (!spaces.empty()) {
std::string cxx_ns = absl::StrJoin(spaces, kCxxSep);
std::string qualified_name = absl::StrCat(cxx_ns, kCxxSep, cxx_name);
if (functions_.count(qualified_name)) {
return qualified_name;
}
spaces.pop_back();
}
return cxx_name;
}
private:
mutable absl::Mutex lock_;
std::unordered_map<std::string, Function> functions_ GUARDED_BY(lock_);
// For names included in NamespaceWhitelist, strips the namespace.
std::string GetAdjustedName(const std::string& name) {
constexpr auto kCxxSep = registration_internal::kCxxSep;
std::vector<std::string> names = absl::StrSplit(name, kCxxSep);
std::string base_name = names.back();
names.pop_back();
std::string ns = absl::StrJoin(names, kCxxSep);
if (NamespaceWhitelist::TopNamespaces().count(ns)) {
return base_name;
}
return name;
}
void Unregister(const std::string& name) {
absl::WriterMutexLock lock(&lock_);
std::string adjusted_name = GetAdjustedName(name);
if (adjusted_name != name) {
functions_.erase(adjusted_name);
}
functions_.erase(name);
}
};
template <typename R, typename... Args>
class GlobalFactoryRegistry {
using Functions = FunctionRegistry<R, Args...>;
public:
static RegistrationToken Register(const std::string& name,
typename Functions::Function func) {
return functions()->Register(name, std::move(func));
}
// Same as CreateByNameInNamespace but without a namespace.
template <typename... Args2>
static typename Functions::ReturnType CreateByName(const std::string& name,
Args2&&... args) {
return CreateByNameInNamespace("", name, std::forward<Args2>(args)...);
}
// Same as IsRegistered(ns, name) but without a namespace.
static bool IsRegistered(const std::string& name) {
return functions()->IsRegistered("", name);
}
static std::unordered_set<std::string> GetRegisteredNames() {
return functions()->GetRegisteredNames();
}
// Invokes the specified factory function and returns the result.
// Namespaces in |name| and |ns| are separated by kNameSep.
// See comments re: use of Args2 and absl::enable_if_t on Invoke.
template <typename... Args2,
absl::enable_if_t<std::is_convertible<std::tuple<Args2...>,
std::tuple<Args...>>::value,
int> = 0>
static typename Functions::ReturnType CreateByNameInNamespace(
const std::string& ns, const std::string& name, Args2&&... args) {
return functions()->Invoke(ns, name, std::forward<Args2>(args)...);
}
// Returns true if the specified factory function is available.
// Namespaces in |name| and |ns| are separated by kNameSep.
static bool IsRegistered(const std::string& ns, const std::string& name) {
return functions()->IsRegistered(ns, name);
}
// Returns the factory function registry singleton.
static Functions* functions() {
static auto* functions = new Functions();
return functions;
}
private:
GlobalFactoryRegistry() = delete;
};
// Two levels of macros are required to convert __LINE__ into a std::string
// containing the line number.
#define REGISTRY_STATIC_VAR_INNER(var_name, line) var_name##_##line##__
#define REGISTRY_STATIC_VAR(var_name, line) \
REGISTRY_STATIC_VAR_INNER(var_name, line)
#define MEDIAPIPE_REGISTER_FACTORY_FUNCTION(RegistryType, name, ...) \
static auto* REGISTRY_STATIC_VAR(registration_##name, __LINE__) = \
new ::mediapipe::RegistrationToken( \
RegistryType::Register(#name, __VA_ARGS__))
#define REGISTER_FACTORY_FUNCTION_QUALIFIED(RegistryType, var_name, name, ...) \
static auto* REGISTRY_STATIC_VAR(var_name, __LINE__) = \
new ::mediapipe::RegistrationToken( \
RegistryType::Register(#name, __VA_ARGS__))
} // namespace mediapipe
#endif // MEDIAPIPE_DEPS_REGISTRATION_H_
@@ -0,0 +1,89 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/deps/registration_token.h"
#include <utility>
namespace mediapipe {
RegistrationToken::RegistrationToken(std::function<void()> unregisterer)
: unregister_function_(std::move(unregisterer)) {}
RegistrationToken::RegistrationToken(RegistrationToken&& rhs)
: unregister_function_(std::move(rhs.unregister_function_)) {
rhs.unregister_function_ = nullptr;
}
RegistrationToken& RegistrationToken::operator=(RegistrationToken&& rhs) {
if (&rhs != this) {
unregister_function_ = std::move(rhs.unregister_function_);
rhs.unregister_function_ = nullptr;
}
return *this;
}
void RegistrationToken::Unregister() {
if (unregister_function_ != nullptr) {
unregister_function_();
unregister_function_ = nullptr;
}
}
namespace {
struct CombinedToken {
void operator()() {
for (auto& f : functions) {
f();
}
}
std::vector<std::function<void()>> functions;
};
} // anonymous namespace
// static
RegistrationToken RegistrationToken::Combine(
std::vector<RegistrationToken> tokens) {
CombinedToken combined;
// When vector grows, it only moves elements if the move constructor is marked
// noexcept (or if the element isn't copyable). In related news, function's
// move constructor is not marked noexcept. By reserving the correct amount of
// space up front, we remove the need for the vector to grow, and thus
// eliminate copies.
combined.functions.reserve(tokens.size());
for (RegistrationToken& token : tokens) {
combined.functions.push_back(std::move(token.unregister_function_));
}
return RegistrationToken(std::move(combined));
}
Unregister::Unregister(RegistrationToken token) : token_(std::move(token)) {}
Unregister::~Unregister() { token_.Unregister(); }
Unregister::Unregister(Unregister&& rhs) : token_(std::move(rhs.token_)) {}
Unregister& Unregister::operator=(Unregister&& rhs) {
if (&rhs != this) {
token_.Unregister();
token_ = std::move(rhs.token_);
}
return *this;
}
void Unregister::Reset(RegistrationToken token) {
token_.Unregister();
token_ = std::move(token);
}
} // namespace mediapipe
@@ -0,0 +1,117 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_DEPS_REGISTRATION_TOKEN_H_
#define MEDIAPIPE_DEPS_REGISTRATION_TOKEN_H_
#include <functional>
#include <vector>
namespace mediapipe {
// RegistrationToken is a generic class that represents a registration that
// can be later undone, via a call to Unregister().
//
// It is generally a good idea for registration methods, such as
// RegisterListener(X) to return ways to undo the registration (for instance if
// X goes out of scope).
// RegistrationToken is a good candidate as a return value for those methods.
//
// Example usage:
//
// RegistrationToken token = MyCancellableRegisterListener(foo);
// ...
// do something
//
// token.Unregister();
//
//
// There is also a Unregister RAII helper below that automatically unregisters
// a token when it goes out of scope:
//
// {
// Unregister unregisterer(MyCancellableRegisterListener(foo));
// ...
// do something
//
// } // unregisterer goes out of scope, we are unregistered.
//
//
// Implementation: tokens are generic, they just accept a std::function<void()>
// that does the actual unregistration. It is up to each registration system to
// pass the function that corresponds to their own implementation for
// unregistering things.
//
// In that regard, tokens are basically a glorified unique_ptr<function>.
// The main advantage is that they guarantee the function can be called only
// once, and naming is also much clearer (Unregister versus operator()).
//
// Tokens are not copyable but they are movable, which reflects the fact that
// there should only ever be one token in charge of a particular registration
// at any time (else there could be confusion, who is in charge of
// unregistering).
//
// This class is thread compatible.
class RegistrationToken {
public:
explicit RegistrationToken(std::function<void()> unregisterer);
// It is useful to have an empty constructor for when we want to declare a
// token, and assign it later.
RegistrationToken() {}
RegistrationToken(const RegistrationToken&) = delete;
RegistrationToken& operator=(const RegistrationToken&) = delete;
RegistrationToken(RegistrationToken&& rhs);
RegistrationToken& operator=(RegistrationToken&& rhs);
// Unregisters the registration for which this token is in charge, and voids
// the token. It is safe to call this more than once, but further calls are
// guaranteed to be noop.
void Unregister();
// Returns a token whose Unregister() will Unregister() all <tokens>.
static RegistrationToken Combine(std::vector<RegistrationToken> tokens);
private:
std::function<void()> unregister_function_ = nullptr;
};
// RAII class for registration tokens: it calls Unregister() when it goes out
// of scope.
class Unregister {
public:
// Useful to have an empty constructor for when we want to assign it later.
// The default is an empty token that does nothing.
Unregister() : token_() {}
explicit Unregister(RegistrationToken token);
~Unregister();
Unregister(const Unregister&) = delete;
Unregister& operator=(const Unregister&) = delete;
Unregister(Unregister&& rhs);
Unregister& operator=(Unregister&& rhs);
// Similar to unique_ptr.reset() and the likes: this will unregister the
// current token if any, and then assume registration ownership of this new
// <token>.
void Reset(RegistrationToken token = RegistrationToken());
private:
RegistrationToken token_;
};
} // namespace mediapipe
#endif // MEDIAPIPE_DEPS_REGISTRATION_TOKEN_H_
@@ -0,0 +1,126 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/deps/registration_token.h"
#include <functional>
#include <utility>
#include "mediapipe/framework/port/gtest.h"
namespace mediapipe {
namespace {
class RegistrationTokenTest : public testing::Test {
public:
void CallFirst() { ++called_1_; }
void CallSecond() { ++called_2_; }
void CallThird() { ++called_3_; }
protected:
int called_1_{0};
int called_2_{0};
int called_3_{0};
};
// Trivial unregistration test.
TEST_F(RegistrationTokenTest, TestUnregister) {
std::function<void()> caller = [this]() {
RegistrationTokenTest::CallFirst();
};
RegistrationToken token(caller);
ASSERT_EQ(0, called_1_);
token.Unregister();
ASSERT_EQ(1, called_1_);
// Check that further calls have no effect.
token.Unregister();
token.Unregister();
ASSERT_EQ(1, called_1_);
// Test the RAII class.
ASSERT_EQ(0, called_2_);
RegistrationToken token2([this]() { RegistrationTokenTest::CallSecond(); });
{
Unregister t(std::move(token2));
ASSERT_EQ(0, called_2_);
}
// It was called since the Unregister() went out of scope.
ASSERT_EQ(1, called_2_);
}
// Tests that the result of a Combine() token does unregisters all combined
// tokens.
TEST_F(RegistrationTokenTest, TestCombine) {
std::function<void()> caller_1 = [this]() {
RegistrationTokenTest::CallFirst();
};
std::function<void()> caller_2 = [this]() {
RegistrationTokenTest::CallSecond();
};
std::function<void()> caller_3 = [this]() {
RegistrationTokenTest::CallThird();
};
RegistrationToken token_1(caller_1);
RegistrationToken token_2(caller_2);
RegistrationToken token_3(caller_3);
ASSERT_EQ(0, called_1_);
ASSERT_EQ(0, called_2_);
ASSERT_EQ(0, called_3_);
std::vector<RegistrationToken> tokens;
tokens.emplace_back(std::move(token_1));
tokens.emplace_back(std::move(token_2));
tokens.emplace_back(std::move(token_3));
RegistrationToken combined = RegistrationToken::Combine(std::move(tokens));
combined.Unregister();
ASSERT_EQ(1, called_1_);
ASSERT_EQ(1, called_2_);
ASSERT_EQ(1, called_3_);
// Check that the original tokens were invalidated by their move and do
// nothing.
token_1.Unregister();
token_2.Unregister();
token_3.Unregister();
ASSERT_EQ(1, called_1_);
ASSERT_EQ(1, called_2_);
ASSERT_EQ(1, called_3_);
}
TEST_F(RegistrationTokenTest, TestMove) {
RegistrationToken token([this] { CallFirst(); });
token = RegistrationToken([this] { CallFirst(); });
EXPECT_EQ(0, called_1_);
Unregister unreg;
unreg = Unregister(std::move(token));
EXPECT_EQ(0, called_1_);
unreg = Unregister(RegistrationToken([this] { CallFirst(); }));
EXPECT_EQ(1, called_1_);
unreg = Unregister(RegistrationToken([this] { CallFirst(); }));
EXPECT_EQ(2, called_1_);
unreg.Reset();
EXPECT_EQ(3, called_1_);
}
} // namespace
} // namespace mediapipe
+39
View File
@@ -0,0 +1,39 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/deps/ret_check.h"
namespace mediapipe {
::mediapipe::StatusBuilder RetCheckFailSlowPath(
::mediapipe::source_location location) {
// TODO Implement LogWithStackTrace().
return ::mediapipe::InternalErrorBuilder(location)
<< "RET_CHECK failure (" << location.file_name() << ":"
<< location.line() << ") ";
}
::mediapipe::StatusBuilder RetCheckFailSlowPath(
::mediapipe::source_location location, const char* condition) {
return ::mediapipe::RetCheckFailSlowPath(location) << condition;
}
::mediapipe::StatusBuilder RetCheckFailSlowPath(
::mediapipe::source_location location, const char* condition,
const ::mediapipe::Status& status) {
return ::mediapipe::RetCheckFailSlowPath(location)
<< condition << " returned " << status << " ";
}
} // namespace mediapipe
+65
View File
@@ -0,0 +1,65 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_DEPS_RET_CHECK_H_
#define MEDIAPIPE_DEPS_RET_CHECK_H_
#include "absl/base/optimization.h"
#include "mediapipe/framework/deps/status_builder.h"
#include "mediapipe/framework/deps/status_macros.h"
namespace mediapipe {
// Returns a StatusBuilder that corresponds to a `RET_CHECK` failure.
::mediapipe::StatusBuilder RetCheckFailSlowPath(
::mediapipe::source_location location);
// Returns a StatusBuilder that corresponds to a `RET_CHECK` failure.
::mediapipe::StatusBuilder RetCheckFailSlowPath(
::mediapipe::source_location location, const char* condition);
// Returns a StatusBuilder that corresponds to a `RET_CHECK` failure.
::mediapipe::StatusBuilder RetCheckFailSlowPath(
::mediapipe::source_location location, const char* condition,
const ::mediapipe::Status& status);
inline StatusBuilder RetCheckImpl(const ::mediapipe::Status& status,
const char* condition,
::mediapipe::source_location location) {
if (ABSL_PREDICT_TRUE(status.ok()))
return ::mediapipe::StatusBuilder(OkStatus(), location);
return RetCheckFailSlowPath(location, condition, status);
}
} // namespace mediapipe
#define RET_CHECK(cond) \
while (ABSL_PREDICT_FALSE(!(cond))) \
return ::mediapipe::RetCheckFailSlowPath(MEDIAPIPE_LOC, #cond)
#define RET_CHECK_OK(status) \
RETURN_IF_ERROR(::mediapipe::RetCheckImpl((status), #status, MEDIAPIPE_LOC))
#define RET_CHECK_FAIL() return ::mediapipe::RetCheckFailSlowPath(MEDIAPIPE_LOC)
#define MEDIAPIPE_INTERNAL_RET_CHECK_OP(name, op, lhs, rhs) \
RET_CHECK((lhs)op(rhs))
#define RET_CHECK_EQ(lhs, rhs) MEDIAPIPE_INTERNAL_RET_CHECK_OP(EQ, ==, lhs, rhs)
#define RET_CHECK_NE(lhs, rhs) MEDIAPIPE_INTERNAL_RET_CHECK_OP(NE, !=, lhs, rhs)
#define RET_CHECK_LE(lhs, rhs) MEDIAPIPE_INTERNAL_RET_CHECK_OP(LE, <=, lhs, rhs)
#define RET_CHECK_LT(lhs, rhs) MEDIAPIPE_INTERNAL_RET_CHECK_OP(LT, <, lhs, rhs)
#define RET_CHECK_GE(lhs, rhs) MEDIAPIPE_INTERNAL_RET_CHECK_OP(GE, >=, lhs, rhs)
#define RET_CHECK_GT(lhs, rhs) MEDIAPIPE_INTERNAL_RET_CHECK_OP(GT, >, lhs, rhs)
#endif // MEDIAPIPE_DEPS_RET_CHECK_H_
+310
View File
@@ -0,0 +1,310 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// A "safe int" is a StrongInt<T> which does additional validation of the
// various arithmetic and logical operations, and reacts to overflows and
// underflow and invalid operations. You can define the "safe int" types
// to react to errors in pre-defined ways or you can define your own policy
// classes.
//
// Usage:
// MEDIAPIPE_DEFINE_SAFE_INT_TYPE(Name, NativeType, PolicyType);
//
// Defines a new StrongInt type named 'Name' in the current namespace with
// underflow/overflow checking on all operations, with configurable error
// policy.
//
// Name: The desired name for the new StrongInt typedef. Must be unique
// within the current namespace.
// NativeType: The primitive integral type this StrongInt will hold, as
// defined by std::is_integral (see <type_traits>).
// PolicyType: The type of policy used by this StrongInt type. A few
// pre-built policy types are provided here, but the caller can
// define any custom policy they desire.
//
// PolicyTypes:
// LogFatalOnError: LOG(FATAL) when a error occurs.
#ifndef MEDIAPIPE_DEPS_SAFE_INT_H_
#define MEDIAPIPE_DEPS_SAFE_INT_H_
#include <limits.h>
#include <limits>
#include <type_traits>
#include "mediapipe/framework/deps/strong_int.h"
#include "mediapipe/framework/port/logging.h"
namespace mediapipe {
namespace intops {
// A StrongInt validator class for "safe" type enforcement. For signed types,
// this checks for overflows and underflows as well as undefined- or
// implementation-defined behaviors. For unsigned type, this further disallows
// operations that would take advantage of unsigned wrap-around behavior and
// operations which would discard data unexpectedly. This assumes two's
// complement representations, and that division truncates towards zero.
//
// For some more on overflow safety, see:
// https://www.securecoding.cert.org/confluence/display/seccode/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow?showComments=false
template <typename ErrorType>
class SafeIntStrongIntValidator {
private:
template <typename T>
static void SanityCheck() {
// Check that the underlying integral type provides a range that is
// compatible with two's complement.
if (std::numeric_limits<T>::is_signed) {
CHECK_EQ(-1,
std::numeric_limits<T>::min() + std::numeric_limits<T>::max())
<< "unexpected integral bounds";
}
// Check that division truncates towards 0 (implementation defined in
// C++'03, but standard in C++'11).
CHECK_EQ(12, 127 / 10) << "division does not truncate towards 0";
CHECK_EQ(-12, -127 / 10) << "division does not truncate towards 0";
CHECK_EQ(-12, 127 / -10) << "division does not truncate towards 0";
CHECK_EQ(12, -127 / -10) << "division does not truncate towards 0";
}
public:
template <typename T, typename U>
static void ValidateInit(U arg) {
// Do some sanity checks before proceeding.
SanityCheck<T>();
// If the argument is floating point, we can do a simple check to make
// sure the value is in range. It is undefined behavior to convert to int
// from a float that is out of range.
if (std::is_floating_point<U>::value) {
if (arg < std::numeric_limits<T>::min() ||
arg > std::numeric_limits<T>::max()) {
ErrorType::Error("SafeInt: init from out of bounds float", arg, "=");
}
} else {
// If the initial value (type U) is changed by being converted to and from
// the native type (type T), then it must be out of bounds for type T.
//
// If T is unsigned and the argument is negative, then it is clearly out
// of bounds for type T.
//
// If the initial value is greater than the max value for type T, then it
// is clearly out of bounds for type T. Before we check that, though, we
// must ensure that the initial value is positive, or else we could get
// unwanted promotion to unsigned, making the test wrong. If the initial
// value is negative, it can't be larger than the max value for type T.
if ((static_cast<U>(static_cast<T>(arg)) != arg) ||
(!std::numeric_limits<T>::is_signed && arg < 0) ||
(arg > 0 && arg > std::numeric_limits<T>::max())) {
ErrorType::Error("SafeInt: init from out of bounds value", arg, "=");
}
}
}
template <typename T>
static void ValidateNegate( // Signed types only.
typename std::enable_if<std::numeric_limits<T>::is_signed, T>::type
value) {
if (value == std::numeric_limits<T>::min()) {
ErrorType::Error("SafeInt: overflow", value, -1, "*");
}
}
template <typename T>
static void ValidateBitNot( // Unsigned types only.
typename std::enable_if<!std::numeric_limits<T>::is_signed, T>::type
value) {
// Do nothing.
}
template <typename T>
static void ValidateAdd(T lhs, T rhs) {
// The same logic applies to signed and unsigned types.
if ((rhs > 0) && (lhs > (std::numeric_limits<T>::max() - rhs))) {
ErrorType::Error("SafeInt: overflow", lhs, rhs, "+");
} else if ((rhs < 0) && (lhs < (std::numeric_limits<T>::min() - rhs))) {
ErrorType::Error("SafeInt: underflow", lhs, rhs, "+");
}
}
template <typename T>
static void ValidateSubtract(T lhs, T rhs) {
// The same logic applies to signed and unsigned types.
if ((rhs > 0) && (lhs < (std::numeric_limits<T>::min() + rhs))) {
ErrorType::Error("SafeInt: underflow", lhs, rhs, "-");
} else if ((rhs < 0) && (lhs > (std::numeric_limits<T>::max() + rhs))) {
ErrorType::Error("SafeInt: overflow", lhs, rhs, "-");
}
}
template <typename T, typename U>
static void ValidateMultiply(T lhs, U rhs) {
if (!std::numeric_limits<T>::is_signed) {
// Unsigned types only.
if (rhs < 0) {
ErrorType::Error("SafeInt: negation of unsigned type", lhs, rhs, "*");
}
}
// Multiplication by 0 can never overflow/underflow, but handling 0 makes
// the below code more complex.
if (lhs == 0 || rhs == 0) {
return;
}
// The remaining logic applies to signed and unsigned types. Note that
// while multiplication is commutative, the underlying StrongInt class
// always calls this with T as StrongInt<T>::ValueType.
if (lhs > 0) {
if (rhs > 0) {
if (lhs > (std::numeric_limits<T>::max() / rhs)) {
ErrorType::Error("SafeInt: overflow", lhs, rhs, "*");
}
} else {
if (rhs < (std::numeric_limits<T>::min() / lhs)) {
ErrorType::Error("SafeInt: underflow", lhs, rhs, "*");
}
}
} else {
if (rhs > 0) {
// Underflow could be tested by lhs < min / rhs, but that does not
// work if rhs is an unsigned type. Intead we test rhs > min / lhs.
// There is a special case for lhs = -1, which would overflow min / lhs.
if ((lhs == -1 && rhs - 1 > std::numeric_limits<T>::max()) ||
(lhs < -1 && rhs > std::numeric_limits<T>::min() / lhs)) {
ErrorType::Error("SafeInt: underflow", lhs, rhs, "*");
}
} else {
if ((lhs != 0) && (rhs < (std::numeric_limits<T>::max() / lhs))) {
ErrorType::Error("SafeInt: overflow", lhs, rhs, "*");
}
}
}
}
template <typename T, typename U>
static void ValidateDivide(T lhs, U rhs) {
// This applies to signed and unsigned types.
if (rhs == 0) {
ErrorType::Error("SafeInt: divide by zero", lhs, rhs, "/");
}
if (std::numeric_limits<T>::is_signed) {
// Signed types only.
if ((lhs == std::numeric_limits<T>::min()) && (rhs == -1)) {
ErrorType::Error("SafeInt: overflow", lhs, rhs, "/");
}
} else {
// Unsigned types only.
if (rhs < 0) {
ErrorType::Error("SafeInt: negation of unsigned type", lhs, rhs, "/");
}
}
}
template <typename T, typename U>
static void ValidateModulo(T lhs, U rhs) {
// This applies to signed and unsigned types.
if (rhs == 0) {
ErrorType::Error("SafeInt: divide by zero", lhs, rhs, "%");
}
if (std::numeric_limits<T>::is_signed) {
// Signed types only.
if ((lhs == std::numeric_limits<T>::min()) && (rhs == -1)) {
ErrorType::Error("SafeInt: overflow", lhs, rhs, "%");
}
} else {
// Unsigned types only.
if (rhs < 0) {
ErrorType::Error("SafeInt: negation of unsigned type", lhs, rhs, "%");
}
}
}
template <typename T>
static void ValidateLeftShift(T lhs, int64 rhs) {
if (std::numeric_limits<T>::is_signed) {
// Signed types only.
if (lhs < 0) {
ErrorType::Error("SafeInt: shift of negative value", lhs, rhs, "<<");
}
}
// The remaining logic applies to signed and unsigned types.
if (rhs < 0) {
ErrorType::Error("SafeInt: shift by negative arg", lhs, rhs, "<<");
}
if (rhs >= (sizeof(T) * CHAR_BIT)) {
ErrorType::Error("SafeInt: shift by large arg", lhs, rhs, "<<");
}
if (lhs > (std::numeric_limits<T>::max() >> rhs)) {
ErrorType::Error("SafeInt: overflow", lhs, rhs, "<<");
}
}
template <typename T>
static void ValidateRightShift(T lhs, int64 rhs) {
if (std::numeric_limits<T>::is_signed) {
// Signed types only.
if (lhs < 0) {
ErrorType::Error("SafeInt: shift of negative value", lhs, rhs, ">>");
}
}
// The remaining logic applies to signed and unsigned types.
if (rhs < 0) {
ErrorType::Error("SafeInt: shift by negative arg", lhs, rhs, ">>");
}
if (rhs >= (sizeof(T) * CHAR_BIT)) {
ErrorType::Error("SafeInt: shift by large arg", lhs, rhs, ">>");
}
}
template <typename T>
static void ValidateBitAnd( // Unsigned types only.
typename std::enable_if<!std::numeric_limits<T>::is_signed, T>::type lhs,
typename std::enable_if<!std::numeric_limits<T>::is_signed, T>::type
rhs) {
// Do nothing.
}
template <typename T>
static void ValidateBitOr( // Unsigned types only.
typename std::enable_if<!std::numeric_limits<T>::is_signed, T>::type lhs,
typename std::enable_if<!std::numeric_limits<T>::is_signed, T>::type
rhs) {
// Do nothing.
}
template <typename T>
static void ValidateBitXor( // Unsigned types only.
typename std::enable_if<!std::numeric_limits<T>::is_signed, T>::type lhs,
typename std::enable_if<!std::numeric_limits<T>::is_signed, T>::type
rhs) {
// Do nothing.
}
};
// A SafeIntStrongIntValidator policy class to LOG(FATAL) on errors.
struct LogFatalOnError {
template <typename Tlhs, typename Trhs>
static void Error(const char *error, Tlhs lhs, Trhs rhs, const char *op) {
LOG(FATAL) << error << ": (" << lhs << " " << op << " " << rhs << ")";
}
template <typename Tval>
static void Error(const char *error, Tval val, const char *op) {
LOG(FATAL) << error << ": (" << op << val << ")";
}
};
} // namespace intops
} // namespace mediapipe
// Defines the StrongInt using value_type and typedefs it to type_name, with
// strong checking of under/overflow conditions.
// The struct int_type_name ## _tag_ trickery is needed to ensure that a new
// type is created per type_name.
#define MEDIAPIPE_DEFINE_SAFE_INT_TYPE(type_name, value_type, policy_type) \
struct type_name##_safe_tag_ {}; \
typedef ::mediapipe::intops::StrongInt< \
type_name##_safe_tag_, value_type, \
::mediapipe::intops::SafeIntStrongIntValidator<policy_type>> \
type_name;
#endif // MEDIAPIPE_DEPS_SAFE_INT_H_
+771
View File
@@ -0,0 +1,771 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Unit test cases for SafeInt. Some of this overlaps with the testing for
// StrongInt, but it's important to test not only that SafeInt fails when
// expected, but that it passes when expected.
#include "mediapipe/framework/deps/safe_int.h"
#include "mediapipe/framework/port/gtest.h"
MEDIAPIPE_DEFINE_SAFE_INT_TYPE(SafeInt8, int8,
::mediapipe::intops::LogFatalOnError);
MEDIAPIPE_DEFINE_SAFE_INT_TYPE(SafeUInt8, uint8,
::mediapipe::intops::LogFatalOnError);
MEDIAPIPE_DEFINE_SAFE_INT_TYPE(SafeInt16, int16,
::mediapipe::intops::LogFatalOnError);
MEDIAPIPE_DEFINE_SAFE_INT_TYPE(SafeUInt16, uint16,
::mediapipe::intops::LogFatalOnError);
MEDIAPIPE_DEFINE_SAFE_INT_TYPE(SafeInt32, int32,
::mediapipe::intops::LogFatalOnError);
MEDIAPIPE_DEFINE_SAFE_INT_TYPE(SafeInt64, int64,
::mediapipe::intops::LogFatalOnError);
MEDIAPIPE_DEFINE_SAFE_INT_TYPE(SafeUInt32, uint32,
::mediapipe::intops::LogFatalOnError);
MEDIAPIPE_DEFINE_SAFE_INT_TYPE(SafeUInt64, uint64,
::mediapipe::intops::LogFatalOnError);
namespace mediapipe {
namespace intops {
//
// Test cases that apply to signed and unsigned types equally.
//
template <typename T>
class SignNeutralSafeIntTest : public ::testing::Test {
public:
typedef T SafeIntTypeUnderTest;
};
typedef ::testing::Types<SafeInt8, SafeUInt8, SafeInt16, SafeUInt16, SafeInt32,
SafeUInt32, SafeInt64, SafeUInt64>
AllSafeIntTypes;
TYPED_TEST_SUITE(SignNeutralSafeIntTest, AllSafeIntTypes);
TYPED_TEST(SignNeutralSafeIntTest, TestCtors) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test default construction.
T x;
EXPECT_EQ(V(), x.value());
}
{ // Test construction from a value.
T x(93);
EXPECT_EQ(V(93), x.value());
}
{ // Test copy construction.
T x(76);
T y(x);
EXPECT_EQ(V(76), y.value());
}
}
TYPED_TEST(SignNeutralSafeIntTest, TestUnaryOperators) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test unary plus of positive values.
T x(123);
EXPECT_EQ(V(123), (+x).value());
}
{ // Test logical not of positive values.
T x(123);
EXPECT_EQ(false, !x);
EXPECT_EQ(true, !!x);
}
{ // Test logical not of zero.
T x(0);
EXPECT_EQ(true, !x);
EXPECT_EQ(false, !!x);
}
}
TYPED_TEST(SignNeutralSafeIntTest, TestCtorFailures) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test out-of-bounds construction.
if (std::numeric_limits<V>::is_signed || sizeof(V) < sizeof(uint64)) {
EXPECT_DEATH((T(std::numeric_limits<uint64>::max())), "bounds");
}
}
{ // Test out-of-bounds construction from float.
EXPECT_DEATH((T(std::numeric_limits<float>::max())), "bounds");
EXPECT_DEATH((T(-std::numeric_limits<float>::max())), "bounds");
}
{ // Test out-of-bounds construction from double.
EXPECT_DEATH((T(std::numeric_limits<double>::max())), "bounds");
EXPECT_DEATH((T(-std::numeric_limits<double>::max())), "bounds");
}
{ // Test out-of-bounds construction from long double.
EXPECT_DEATH((T(std::numeric_limits<long double>::max())), "bounds");
EXPECT_DEATH((T(-std::numeric_limits<long double>::max())), "bounds");
}
}
TYPED_TEST(SignNeutralSafeIntTest, TestIncrementDecrement) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test simple increments and decrements.
T x(0);
EXPECT_EQ(V(0), x.value());
EXPECT_EQ(V(0), (x++).value());
EXPECT_EQ(V(1), x.value());
EXPECT_EQ(V(2), (++x).value());
EXPECT_EQ(V(2), x.value());
EXPECT_EQ(V(2), (x--).value());
EXPECT_EQ(V(1), x.value());
EXPECT_EQ(V(0), (--x).value());
EXPECT_EQ(V(0), x.value());
}
}
TYPED_TEST(SignNeutralSafeIntTest, TestIncrementDecrementFailures) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test overflowing increment.
T x(std::numeric_limits<V>::max() - 1);
EXPECT_EQ(std::numeric_limits<V>::max(), (++x).value());
EXPECT_DEATH(x++, "overflow");
EXPECT_DEATH(++x, "overflow");
}
{ // Test underflowing decrement.
T x(std::numeric_limits<V>::min() + 1);
EXPECT_EQ(std::numeric_limits<V>::min(), (--x).value());
EXPECT_DEATH(x--, "underflow");
EXPECT_DEATH(--x, "underflow");
}
}
#define TEST_T_OP_T(xval, op, yval) \
{ \
T x(xval); \
T y(yval); \
V expected = x.value() op y.value(); \
EXPECT_EQ(expected, (x op y).value()); \
EXPECT_EQ(expected, (x op## = y).value()); \
EXPECT_EQ(expected, x.value()); \
}
TYPED_TEST(SignNeutralSafeIntTest, TestAdd) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
// Test positive vs. positive addition.
TEST_T_OP_T(9, +, 3)
// Test addition by zero.
TEST_T_OP_T(93, +, 0);
}
TYPED_TEST(SignNeutralSafeIntTest, TestAddFailures) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test overflowing addition.
T x(std::numeric_limits<V>::max());
EXPECT_DEATH(x + T(1), "overflow");
EXPECT_DEATH(x += T(1), "overflow");
}
{ // Test overflowing addition.
T x(std::numeric_limits<V>::max());
EXPECT_DEATH(x + T(std::numeric_limits<V>::max()), "overflow");
EXPECT_DEATH(x += T(std::numeric_limits<V>::max()), "overflow");
}
}
TYPED_TEST(SignNeutralSafeIntTest, TestSubtract) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
// Test positive vs. positive subtraction.
TEST_T_OP_T(9, -, 3)
// Test subtraction of zero.
TEST_T_OP_T(93, -, 0);
}
TYPED_TEST(SignNeutralSafeIntTest, TestSubtractFailures) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test underflowing subtraction.
T x(std::numeric_limits<V>::min());
EXPECT_DEATH(x - T(1), "underflow");
EXPECT_DEATH(x -= T(1), "underflow");
}
{ // Test underflowing subtraction.
T x(std::numeric_limits<V>::min());
EXPECT_DEATH(x - T(std::numeric_limits<V>::max()), "underflow");
EXPECT_DEATH(x -= T(std::numeric_limits<V>::max()), "underflow");
}
}
#define TEST_T_OP_NUM(xval, op, numtype, yval) \
{ \
T x(xval); \
numtype y = yval; \
V expected = x.value() op y; \
EXPECT_EQ(expected, (x op y).value()); \
EXPECT_EQ(expected, (x op## = y).value()); \
EXPECT_EQ(expected, x.value()); \
}
TYPED_TEST(SignNeutralSafeIntTest, TestMultiply) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
// Test positive vs. positive multiplication across types.
TEST_T_OP_NUM(9, *, int32, 3);
TEST_T_OP_NUM(9, *, uint32, 3);
TEST_T_OP_NUM(9, *, float, 3);
TEST_T_OP_NUM(9, *, double, 3);
// Test positive vs. zero multiplication commutatively across types. This
// was a real bug.
TEST_T_OP_NUM(93, *, int32, 0);
TEST_T_OP_NUM(93, *, uint32, 0);
TEST_T_OP_NUM(93, *, float, 0);
TEST_T_OP_NUM(93, *, double, 0);
TEST_T_OP_NUM(0, *, int32, 76);
TEST_T_OP_NUM(0, *, uint32, 76);
TEST_T_OP_NUM(0, *, float, 76);
TEST_T_OP_NUM(0, *, double, 76);
// Test positive vs. epsilon multiplication.
TEST_T_OP_NUM(93, *, float, std::numeric_limits<float>::epsilon());
TEST_T_OP_NUM(93, *, double, std::numeric_limits<float>::epsilon());
{ // Test multiplication by float.
// Multiplication is the only operator that takes one numeric type and
// one StrongInt type *and* is commutative. This was a real bug.
T x(0);
EXPECT_EQ(0, (x * static_cast<float>(1.1)).value());
EXPECT_EQ(0, (static_cast<float>(1.1) * x).value());
}
}
TYPED_TEST(SignNeutralSafeIntTest, TestMultiplyFailures) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test overflowing multiplication.
T x(std::numeric_limits<V>::max());
EXPECT_DEATH(x * 2, "overflow");
EXPECT_DEATH(x *= 2, "overflow");
}
}
TYPED_TEST(SignNeutralSafeIntTest, TestDivide) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
// Test positive vs. positive division across types.
TEST_T_OP_NUM(9, /, int32, 3);
TEST_T_OP_NUM(9, /, uint32, 3);
TEST_T_OP_NUM(9, /, float, 3);
TEST_T_OP_NUM(9, /, double, 3);
// Test zero vs. positive division across types.
TEST_T_OP_NUM(0, /, int32, 76);
TEST_T_OP_NUM(0, /, uint32, 76);
TEST_T_OP_NUM(0, /, float, 76);
TEST_T_OP_NUM(0, /, double, 76);
}
TYPED_TEST(SignNeutralSafeIntTest, TestDivideFailures) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test divide by zero.
T x(93);
EXPECT_DEATH(x / 0, "divide by zero");
EXPECT_DEATH(x /= 0, "divide by zero");
}
}
TYPED_TEST(SignNeutralSafeIntTest, TestModulo) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
// Test positive vs. positive modulo across signedness.
TEST_T_OP_NUM(7, %, int32, 6);
TEST_T_OP_NUM(7, %, uint32, 6);
// Test zero vs. positive modulo across signedness.
TEST_T_OP_NUM(0, %, int32, 6);
TEST_T_OP_NUM(0, %, uint32, 6);
}
TYPED_TEST(SignNeutralSafeIntTest, TestModuloFailures) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test modulo by zero.
T x(93);
EXPECT_DEATH(x % 0, "divide by zero");
EXPECT_DEATH(x %= 0, "divide by zero");
}
}
TYPED_TEST(SignNeutralSafeIntTest, TestLeftShift) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
// Test basic shift.
TEST_T_OP_NUM(0x09, <<, int, 3);
// Test shift by zero.
TEST_T_OP_NUM(0x09, <<, int, 0);
}
TYPED_TEST(SignNeutralSafeIntTest, TestLeftShiftFailures) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test shift by a negative.
T x(9);
EXPECT_DEATH(x << -1, "shift by negative");
EXPECT_DEATH(x <<= -1, "shift by negative");
}
{ // Test shift by a too-large.
T x(9);
EXPECT_DEATH(x << sizeof(T) * CHAR_BIT, "shift by large");
EXPECT_DEATH(x <<= sizeof(T) * CHAR_BIT, "shift by large");
EXPECT_DEATH(x <<= 0x100000001ULL, "shift by large");
}
{ // Test overflowing shift.
T x(std::numeric_limits<V>::max());
EXPECT_DEATH(x << 1, "overflow");
EXPECT_DEATH(x <<= 1, "overflow");
}
}
TYPED_TEST(SignNeutralSafeIntTest, TestRightShift) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
// Test basic shift.
TEST_T_OP_NUM(0x09, >>, int, 3);
// Test shift by zero.
TEST_T_OP_NUM(0x09, >>, int, 0);
}
TYPED_TEST(SignNeutralSafeIntTest, TestRightShiftFailures) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test shift by a negative.
T x(9);
EXPECT_DEATH(x >> -1, "shift by negative");
EXPECT_DEATH(x >>= -1, "shift by negative");
}
{ // Test shift by a too-large.
T x(9);
EXPECT_DEATH(x >> sizeof(T) * CHAR_BIT, "shift by large");
EXPECT_DEATH(x >>= sizeof(T) * CHAR_BIT, "shift by large");
}
}
TYPED_TEST(SignNeutralSafeIntTest, TestFloatToIntTruncation) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
// Test construction from float.
{
float f = 93.123;
T x(f);
EXPECT_EQ(93, x.value());
}
{
float f = 93.76;
T x(f);
EXPECT_EQ(93, x.value());
}
// Test construction from double.
{
double f = 93.123;
T x(f);
EXPECT_EQ(93, x.value());
}
{
double f = 93.76;
T x(f);
EXPECT_EQ(93, x.value());
}
// Test construction from long double.
{
long double f = 93.123;
T x(f);
EXPECT_EQ(93, x.value());
}
{
long double f = 93.76;
T x(f);
EXPECT_EQ(93, x.value());
}
}
//
// Test cases that apply only to signed types.
//
template <typename T>
class SignedSafeIntTest : public ::testing::Test {
public:
typedef T SafeIntTypeUnderTest;
};
typedef ::testing::Types<SafeInt8, SafeInt16, SafeInt32, SafeInt64>
SignedSafeIntTypes;
TYPED_TEST_SUITE(SignedSafeIntTest, SignedSafeIntTypes);
TYPED_TEST(SignedSafeIntTest, TestCtors) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test construction from a negative value.
T x(-1);
EXPECT_EQ(V(-1), x.value());
}
}
TYPED_TEST(SignedSafeIntTest, TestUnaryOperators) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test unary plus and minus of positive values.
T x(123);
EXPECT_EQ(V(123), (+x).value());
EXPECT_EQ(V(-123), (-x).value());
}
{ // Test unary plus and minus of negative values.
T x(-123);
EXPECT_EQ(V(-123), (+x).value());
EXPECT_EQ(V(123), (-x).value());
}
{ // Test logical not of negative values.
T x(-123);
EXPECT_EQ(false, !x);
EXPECT_EQ(true, !!x);
}
}
TYPED_TEST(SignedSafeIntTest, TestUnaryOperatorsFailures) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test unary minus of negative values.
T y(std::numeric_limits<V>::min());
EXPECT_DEATH(-y, "overflow");
}
}
TYPED_TEST(SignedSafeIntTest, TestAdd) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
// Test negative vs. positive addition.
TEST_T_OP_T(-9, +, 3)
// Test positive vs. negative addition.
TEST_T_OP_T(9, +, -3)
// Test negative vs. negative addition.
TEST_T_OP_T(-9, +, -3)
}
TYPED_TEST(SignedSafeIntTest, TestAddFailures) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test underflow by addition of a negative.
T x(std::numeric_limits<V>::min());
EXPECT_DEATH(x + T(-1), "underflow");
EXPECT_DEATH(x += T(-1), "underflow");
}
}
TYPED_TEST(SignedSafeIntTest, TestSubtract) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
// Test negative vs. positive subtraction.
TEST_T_OP_T(-9, -, 3)
// Test positive vs. negative subtraction.
TEST_T_OP_T(9, -, -3)
// Test negative vs. negative subtraction.
TEST_T_OP_T(-9, -, -3)
// Test positive vs. positive subtraction resulting in negative.
TEST_T_OP_T(3, -, 9);
// Test subtraction from zero.
TEST_T_OP_T(0, -, 93);
}
TYPED_TEST(SignedSafeIntTest, TestSubtractFailures) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test overflow by subtraction of a negative.
T x(std::numeric_limits<V>::max());
EXPECT_DEATH(x - T(-1), "overflow");
EXPECT_DEATH(x -= T(-1), "overflow");
}
}
TYPED_TEST(SignedSafeIntTest, TestMultiply) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
// Test negative vs. positive multiplication across types.
TEST_T_OP_NUM(-9, *, int32, 3);
TEST_T_OP_NUM(-9, *, uint32, 3);
TEST_T_OP_NUM(-9, *, float, 3);
TEST_T_OP_NUM(-9, *, double, 3);
// Test positive vs. negative multiplication across types.
TEST_T_OP_NUM(9, *, int32, -3);
// Don't cover unsigneds that are initialized from negative values.
TEST_T_OP_NUM(9, *, float, -3);
TEST_T_OP_NUM(9, *, double, -3);
// Test negative vs. negative multiplication across types.
TEST_T_OP_NUM(-9, *, int32, -3);
// Don't cover unsigneds that are initialized from negative values.
TEST_T_OP_NUM(-9, *, float, -3);
TEST_T_OP_NUM(-9, *, double, -3);
// Test negative vs. zero multiplication commutatively across types.
TEST_T_OP_NUM(-93, *, int32, 0);
TEST_T_OP_NUM(-93, *, uint32, 0);
TEST_T_OP_NUM(-93, *, float, 0);
TEST_T_OP_NUM(-93, *, double, 0);
TEST_T_OP_NUM(0, *, int32, -76);
TEST_T_OP_NUM(0, *, uint32, -76);
TEST_T_OP_NUM(0, *, float, -76);
TEST_T_OP_NUM(0, *, double, -76);
// Test negative vs. epsilon multiplication.
TEST_T_OP_NUM(-93, *, float, std::numeric_limits<float>::epsilon());
TEST_T_OP_NUM(-93, *, double, std::numeric_limits<float>::epsilon());
}
TYPED_TEST(SignedSafeIntTest, TestMultiplyFailures) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test underflowing multiplication.
T x(std::numeric_limits<V>::min());
EXPECT_DEATH(x * 2, "underflow");
EXPECT_DEATH(x *= 2, "underflow");
}
{ // Test underflowing multiplication.
T x(std::numeric_limits<V>::max());
EXPECT_DEATH(x * -2, "underflow");
EXPECT_DEATH(x *= -2, "underflow");
}
{ // Test overflowing multiplication.
T x(std::numeric_limits<V>::min());
EXPECT_DEATH(x * -2, "overflow");
EXPECT_DEATH(x *= -2, "overflow");
}
{ // Test overflowing multiplication.
T x(std::numeric_limits<V>::min());
EXPECT_DEATH(x * -1, "overflow");
EXPECT_DEATH(x *= -1, "overflow");
}
{ // Test underflowing multiplication where rhs type is uint64.
T x(-2);
EXPECT_DEATH(x * kuint64max, "underflow");
EXPECT_DEATH(x *= kuint64max, "underflow");
}
}
TYPED_TEST(SignedSafeIntTest, TestDivide) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
// Test negative vs. positive division across types.
TEST_T_OP_NUM(-9, /, int32, 3);
TEST_T_OP_NUM(-9, /, uint32, 3);
TEST_T_OP_NUM(-9, /, float, 3);
TEST_T_OP_NUM(-9, /, double, 3);
// Test positive vs. negative division across types.
TEST_T_OP_NUM(9, /, int32, -3);
TEST_T_OP_NUM(9, /, uint32, -3);
TEST_T_OP_NUM(9, /, float, -3);
TEST_T_OP_NUM(9, /, double, -3);
// Test negative vs. negative division across types.
TEST_T_OP_NUM(-9, /, int32, -3);
TEST_T_OP_NUM(-9, /, uint32, -3);
TEST_T_OP_NUM(-9, /, float, -3);
TEST_T_OP_NUM(-9, /, double, -3);
// Test zero vs. negative division across types.
TEST_T_OP_NUM(0, /, int32, -76);
TEST_T_OP_NUM(0, /, uint32, -76);
TEST_T_OP_NUM(0, /, float, -76);
TEST_T_OP_NUM(0, /, double, -76);
}
TYPED_TEST(SignedSafeIntTest, TestDivideFailures) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test overflowing division.
T x(std::numeric_limits<V>::min());
EXPECT_DEATH(x / -1, "overflow");
EXPECT_DEATH(x /= -1, "overflow");
}
}
TYPED_TEST(SignedSafeIntTest, TestModulo) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
// Test negative vs. positive modulo across signedness.
TEST_T_OP_NUM(-7, %, int32, 6);
TEST_T_OP_NUM(-7, %, uint32, 6);
// Test positive vs. negative modulo across signedness.
TEST_T_OP_NUM(7, %, int32, -6);
TEST_T_OP_NUM(7, %, uint32, -6);
// Test negative vs. negative modulo across signedness.
TEST_T_OP_NUM(-7, %, int32, -6);
TEST_T_OP_NUM(-7, %, uint32, -6);
// Test zero vs. negative modulo across signedness.
TEST_T_OP_NUM(0, %, int32, -6);
TEST_T_OP_NUM(0, %, uint32, -6);
}
TYPED_TEST(SignedSafeIntTest, TestModuloFailures) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test overflowing modulo.
T x(std::numeric_limits<V>::min());
EXPECT_DEATH(x % -1, "overflow");
EXPECT_DEATH(x %= -1, "overflow");
}
}
TYPED_TEST(SignedSafeIntTest, TestLeftShiftFailures) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test shift of a negative.
T x(-9);
EXPECT_DEATH(x << 1, "shift of negative");
EXPECT_DEATH(x <<= 1, "shift of negative");
}
}
TYPED_TEST(SignedSafeIntTest, TestRightShiftFailures) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test shift of a negative.
T x(-9);
EXPECT_DEATH(x >> 1, "shift of negative");
EXPECT_DEATH(x >>= 1, "shift of negative");
}
}
//
// Test cases that apply only to unsigned types.
//
template <typename T>
class UnsignedSafeIntTest : public ::testing::Test {
public:
typedef T SafeIntTypeUnderTest;
};
typedef ::testing::Types<SafeUInt8, SafeUInt16, SafeUInt32, SafeUInt64>
UnsignedSafeIntTypes;
TYPED_TEST_SUITE(UnsignedSafeIntTest, UnsignedSafeIntTypes);
TYPED_TEST(UnsignedSafeIntTest, TestCtors) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test out-of-bounds construction.
EXPECT_DEATH(T(-1), "bounds");
}
{ // Test out-of-bounds construction from float.
EXPECT_DEATH((T(static_cast<float>(-1))), "bounds");
}
{ // Test out-of-bounds construction from double.
EXPECT_DEATH((T(static_cast<double>(-1))), "bounds");
}
{ // Test out-of-bounds construction from long double.
EXPECT_DEATH((T(static_cast<long double>(-1))), "bounds");
}
}
TYPED_TEST(UnsignedSafeIntTest, TestUnaryOperators) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test bitwise not of positive values.
T x(123);
EXPECT_EQ(V(~(x.value())), (~x).value());
EXPECT_EQ(x.value(), (~~x).value());
}
{ // Test bitwise not of zero.
T x(0x00);
EXPECT_EQ(V(~(x.value())), (~x).value());
EXPECT_EQ(x.value(), (~~x).value());
}
}
TYPED_TEST(UnsignedSafeIntTest, TestMultiply) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test multiplication by a negative.
T x(93);
EXPECT_DEATH(x * -1, "negation");
EXPECT_DEATH(x *= -1, "negation");
}
}
TYPED_TEST(UnsignedSafeIntTest, TestDivide) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test division by a negative.
T x(93);
EXPECT_DEATH(x / -1, "negation");
EXPECT_DEATH(x /= -1, "negation");
}
}
TYPED_TEST(UnsignedSafeIntTest, TestModulo) {
typedef typename TestFixture::SafeIntTypeUnderTest T;
typedef typename T::ValueType V;
{ // Test modulo by a negative.
T x(93);
EXPECT_DEATH(x % -5, "negation");
EXPECT_DEATH(x %= -5, "negation");
}
}
} // namespace intops
} // namespace mediapipe
+72
View File
@@ -0,0 +1,72 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_DEPS_SINGLETON_H_
#define MEDIAPIPE_DEPS_SINGLETON_H_
#include "absl/synchronization/mutex.h"
// The Singleton template class creates a single instance of template parameter
// |T| when needed in a thread-safe fashion. A pointer to this single instance
// may be retrieved through a call to get().
template <typename T>
class Singleton {
public:
// Returns the pointer to the singleton of type |T|.
// This method is thread-safe.
static T *get() LOCKS_EXCLUDED(mu_) {
absl::MutexLock lock(&mu_);
if (instance_) {
return instance_;
}
if (destroyed_) {
return nullptr;
}
if (instance_) {
return instance_;
}
instance_ = new T();
return instance_;
}
// Destroys the singleton . This method is only partially thread-safe.
// It ensures that instance_ gets destroyed only once, and once destroyed, it
// cannot be recreated. However, the callers of this method responsible for
// making sure that no other threads are accessing (or plan to access) the
// singleton any longer.
static void Destruct() LOCKS_EXCLUDED(mu_) {
absl::MutexLock lock(&mu_);
T *tmp_ptr = instance_;
instance_ = nullptr;
delete tmp_ptr;
destroyed_ = true;
}
private:
static T *instance_ GUARDED_BY(mu_);
static bool destroyed_ GUARDED_BY(mu_);
static absl::Mutex mu_;
};
template <typename T>
T *Singleton<T>::instance_ = nullptr;
template <typename T>
bool Singleton<T>::destroyed_ = false;
template <typename T>
absl::Mutex Singleton<T>::mu_;
#endif // MEDIAPIPE_DEPS_SINGLETON_H_
@@ -0,0 +1,64 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_DEPS_SOURCE_LOCATION_H_
#define MEDIAPIPE_DEPS_SOURCE_LOCATION_H_
#include <cstdint>
namespace mediapipe {
// Class representing a specific location in the source code of a program.
// source_location is copyable.
class source_location {
public:
// Avoid this constructor; it populates the object with dummy values.
constexpr source_location() : line_(0), file_name_(nullptr) {}
// Wrapper to invoke the private constructor below. This should only be
// used by the MEDIAPIPE_LOC macro, hence the name.
static constexpr source_location DoNotInvokeDirectly(std::uint_least32_t line,
const char* file_name) {
return source_location(line, file_name);
}
// The line number of the captured source location.
constexpr std::uint_least32_t line() const { return line_; }
// The file name of the captured source location.
constexpr const char* file_name() const { return file_name_; }
// column() and function_name() are omitted because we don't have a
// way to support them.
private:
// Do not invoke this constructor directly. Instead, use the
// MEDIAPIPE_LOC macro below.
//
// file_name must outlive all copies of the source_location
// object, so in practice it should be a std::string literal.
constexpr source_location(std::uint_least32_t line, const char* file_name)
: line_(line), file_name_(file_name) {}
std::uint_least32_t line_;
const char* file_name_;
};
} // namespace mediapipe
// If a function takes a source_location parameter, pass this as the argument.
#define MEDIAPIPE_LOC \
::mediapipe::source_location::DoNotInvokeDirectly(__LINE__, __FILE__)
#endif // MEDIAPIPE_DEPS_SOURCE_LOCATION_H_
+133
View File
@@ -0,0 +1,133 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/deps/status.h"
#include <stdio.h>
namespace mediapipe {
Status::Status(::mediapipe::StatusCode code, absl::string_view msg) {
state_ = std::unique_ptr<State>(new State);
state_->code = code;
state_->msg = std::string(msg);
}
void Status::Update(const Status& new_status) {
if (ok()) {
*this = new_status;
}
}
void Status::SlowCopyFrom(const State* src) {
if (src == nullptr) {
state_ = nullptr;
} else {
state_ = std::unique_ptr<State>(new State(*src));
}
}
const std::string& Status::empty_string() {
static std::string* empty = new std::string;
return *empty;
}
std::string Status::ToString() const {
if (state_ == nullptr) {
return "OK";
} else {
char tmp[30];
const char* type;
switch (code()) {
case ::mediapipe::StatusCode::kCancelled:
type = "Cancelled";
break;
case ::mediapipe::StatusCode::kUnknown:
type = "Unknown";
break;
case ::mediapipe::StatusCode::kInvalidArgument:
type = "Invalid argument";
break;
case ::mediapipe::StatusCode::kDeadlineExceeded:
type = "Deadline exceeded";
break;
case ::mediapipe::StatusCode::kNotFound:
type = "Not found";
break;
case ::mediapipe::StatusCode::kAlreadyExists:
type = "Already exists";
break;
case ::mediapipe::StatusCode::kPermissionDenied:
type = "Permission denied";
break;
case ::mediapipe::StatusCode::kUnauthenticated:
type = "Unauthenticated";
break;
case ::mediapipe::StatusCode::kResourceExhausted:
type = "Resource exhausted";
break;
case ::mediapipe::StatusCode::kFailedPrecondition:
type = "Failed precondition";
break;
case ::mediapipe::StatusCode::kAborted:
type = "Aborted";
break;
case ::mediapipe::StatusCode::kOutOfRange:
type = "Out of range";
break;
case ::mediapipe::StatusCode::kUnimplemented:
type = "Unimplemented";
break;
case ::mediapipe::StatusCode::kInternal:
type = "Internal";
break;
case ::mediapipe::StatusCode::kUnavailable:
type = "Unavailable";
break;
case ::mediapipe::StatusCode::kDataLoss:
type = "Data loss";
break;
default:
snprintf(tmp, sizeof(tmp), "Unknown code(%d)",
static_cast<int>(code()));
type = tmp;
break;
}
std::string result(type);
result += ": ";
result += state_->msg;
return result;
}
}
void Status::IgnoreError() const {
// no-op
}
std::ostream& operator<<(std::ostream& os, const Status& x) {
os << x.ToString();
return os;
}
std::string* MediaPipeCheckOpHelperOutOfLine(const ::mediapipe::Status& v,
const char* msg) {
std::string r("Non-OK-status: ");
r += msg;
r += " status: ";
r += v.ToString();
// Leaks std::string but this is only to be used in a fatal error message
return new std::string(r);
}
} // namespace mediapipe
+172
View File
@@ -0,0 +1,172 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_DEPS_STATUS_H_
#define MEDIAPIPE_DEPS_STATUS_H_
#include <functional>
#include <iosfwd>
#include <memory>
#include <string>
#include "absl/strings/string_view.h"
#include "mediapipe/framework/port/logging.h"
namespace mediapipe {
enum class StatusCode {
kOk = 0,
kCancelled = 1,
kUnknown = 2,
kInvalidArgument = 3,
kDeadlineExceeded = 4,
kNotFound = 5,
kAlreadyExists = 6,
kPermissionDenied = 7,
kResourceExhausted = 8,
kFailedPrecondition = 9,
kAborted = 10,
kOutOfRange = 11,
kUnimplemented = 12,
kInternal = 13,
kUnavailable = 14,
kDataLoss = 15,
kUnauthenticated = 16,
kDoNotUseReservedForFutureExpansionUseDefaultInSwitchInstead_ = 20
};
#if defined(__clang__)
// Only clang supports warn_unused_result as a type annotation.
class ABSL_MUST_USE_RESULT Status;
#endif
// Denotes success or failure of a call in MediaPipe.
class Status {
public:
// Creates a success status.
Status() {}
// Creates a status with the specified error code and msg as a
// human-readable std::string containing more detailed information.
Status(::mediapipe::StatusCode code, absl::string_view msg);
// Copies the specified status.
Status(const Status& s);
void operator=(const Status& s);
// Returns true iff the status indicates success.
bool ok() const {
return (state_ == NULL) || (state_->code == ::mediapipe::StatusCode::kOk);
}
::mediapipe::StatusCode code() const {
return ok() ? ::mediapipe::StatusCode::kOk : state_->code;
}
const std::string& error_message() const {
return ok() ? empty_string() : state_->msg;
}
absl::string_view message() const {
return absl::string_view(error_message());
}
bool operator==(const Status& x) const;
bool operator!=(const Status& x) const;
// If `ok()`, stores `new_status` into `*this`. If `!ok()`,
// preserves the current status, but may augment with additional
// information about `new_status`.
//
// Convenient way of keeping track of the first error encountered.
// Instead of:
// `if (overall_status.ok()) overall_status = new_status`
// Use:
// `overall_status.Update(new_status);`
void Update(const Status& new_status);
// Returns a std::string representation of this status suitable for
// printing. Returns the std::string `"OK"` for success.
std::string ToString() const;
// Ignores any errors. This method does nothing except potentially suppress
// complaints from any tools that are checking that errors are not dropped on
// the floor.
void IgnoreError() const;
private:
static const std::string& empty_string();
struct State {
::mediapipe::StatusCode code;
std::string msg;
};
// OK status has a `NULL` state_. Otherwise, `state_` points to
// a `State` structure containing the error code and message(s)
std::unique_ptr<State> state_;
void SlowCopyFrom(const State* src);
};
inline Status::Status(const Status& s)
: state_((s.state_ == NULL) ? NULL : new State(*s.state_)) {}
inline void Status::operator=(const Status& s) {
// The following condition catches both aliasing (when this == &s),
// and the common case where both s and *this are ok.
if (state_ != s.state_) {
SlowCopyFrom(s.state_.get());
}
}
inline bool Status::operator==(const Status& x) const {
return (this->state_ == x.state_) || (ToString() == x.ToString());
}
inline bool Status::operator!=(const Status& x) const { return !(*this == x); }
inline Status OkStatus() { return Status(); }
std::ostream& operator<<(std::ostream& os, const Status& x);
typedef std::function<void(const Status&)> StatusCallback;
extern std::string* MediaPipeCheckOpHelperOutOfLine(
const ::mediapipe::Status& v, const char* msg);
inline std::string* MediaPipeCheckOpHelper(::mediapipe::Status v,
const char* msg) {
if (v.ok()) return nullptr;
return MediaPipeCheckOpHelperOutOfLine(v, msg);
}
#define MEDIAPIPE_DO_CHECK_OK(val, level) \
while (auto _result = ::mediapipe::MediaPipeCheckOpHelper(val, #val)) \
LOG(level) << *(_result)
// To be consistent with MEDIAPIPE_EXPECT_OK, we add prefix MEDIAPIPE_ to
// CHECK_OK, QCHECK_OK, and DCHECK_OK. We prefer to use the marcos with
// MEDIAPIPE_ prefix in mediapipe's codebase.
#define MEDIAPIPE_CHECK_OK(val) MEDIAPIPE_DO_CHECK_OK(val, FATAL)
#define MEDIAPIPE_QCHECK_OK(val) MEDIAPIPE_DO_CHECK_OK(val, QFATAL)
#ifndef NDEBUG
#define MEDIAPIPE_DCHECK_OK(val) MEDIAPIPE_CHECK_OK(val)
#else
#define MEDIAPIPE_DCHECK_OK(val) \
while (false && (::mediapipe::OkStatus() == (val))) LOG(FATAL)
#endif
} // namespace mediapipe
#endif // MEDIAPIPE_DEPS_STATUS_H_
@@ -0,0 +1,85 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/deps/status_builder.h"
#include "absl/memory/memory.h"
namespace mediapipe {
StatusBuilder::StatusBuilder(const StatusBuilder& sb) {
status_ = sb.status_;
file_ = sb.file_;
line_ = sb.line_;
no_logging_ = sb.no_logging_;
stream_ = absl::make_unique<std::ostringstream>(sb.stream_->str());
join_style_ = sb.join_style_;
}
StatusBuilder& StatusBuilder::operator=(const StatusBuilder& sb) {
status_ = sb.status_;
file_ = sb.file_;
line_ = sb.line_;
no_logging_ = sb.no_logging_;
stream_ = absl::make_unique<std::ostringstream>(sb.stream_->str());
join_style_ = sb.join_style_;
return *this;
}
StatusBuilder& StatusBuilder::SetAppend() {
if (status_.ok()) return *this;
join_style_ = MessageJoinStyle::kAppend;
return *this;
}
StatusBuilder& StatusBuilder::SetPrepend() {
if (status_.ok()) return *this;
join_style_ = MessageJoinStyle::kPrepend;
return *this;
}
StatusBuilder& StatusBuilder::SetNoLogging() {
no_logging_ = true;
return *this;
}
StatusBuilder::operator Status() const& {
if (stream_->str().empty() || no_logging_) {
return status_;
}
return StatusBuilder(*this).JoinMessageToStatus();
}
StatusBuilder::operator Status() && {
if (stream_->str().empty() || no_logging_) {
return status_;
}
return JoinMessageToStatus();
}
::mediapipe::Status StatusBuilder::JoinMessageToStatus() {
std::string message;
if (join_style_ == MessageJoinStyle::kAnnotate) {
if (!status_.ok()) {
message = absl::StrCat(status_.error_message(), "; ", stream_->str());
}
} else {
message = join_style_ == MessageJoinStyle::kPrepend
? absl::StrCat(stream_->str(), status_.error_message())
: absl::StrCat(status_.error_message(), stream_->str());
}
return Status(status_.code(), message);
}
} // namespace mediapipe
+148
View File
@@ -0,0 +1,148 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_DEPS_STATUS_BUILDER_H_
#define MEDIAPIPE_DEPS_STATUS_BUILDER_H_
#include "absl/base/attributes.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "mediapipe/framework/deps/source_location.h"
#include "mediapipe/framework/deps/status.h"
namespace mediapipe {
class ABSL_MUST_USE_RESULT StatusBuilder {
public:
StatusBuilder(const StatusBuilder& sb);
StatusBuilder& operator=(const StatusBuilder& sb);
// Creates a `StatusBuilder` based on an original status. If logging is
// enabled, it will use `location` as the location from which the log message
// occurs. A typical user will call this with `MEDIAPIPE_LOC`.
StatusBuilder(const ::mediapipe::Status& original_status,
::mediapipe::source_location location)
: status_(original_status),
line_(location.line()),
file_(location.file_name()),
stream_(new std::ostringstream) {}
StatusBuilder(::mediapipe::Status&& original_status,
::mediapipe::source_location location)
: status_(std::move(original_status)),
line_(location.line()),
file_(location.file_name()),
stream_(new std::ostringstream) {}
// Creates a `StatusBuilder` from a mediapipe status code. If logging is
// enabled, it will use `location` as the location from which the log message
// occurs. A typical user will call this with `MEDIAPIPE_LOC`.
StatusBuilder(::mediapipe::StatusCode code,
::mediapipe::source_location location)
: status_(code, ""),
line_(location.line()),
file_(location.file_name()),
stream_(new std::ostringstream) {}
StatusBuilder(const ::mediapipe::Status& original_status, const char* file,
int line)
: status_(original_status),
line_(line),
file_(file),
stream_(new std::ostringstream) {}
bool ok() const { return status_.ok(); }
StatusBuilder& SetAppend();
StatusBuilder& SetPrepend();
StatusBuilder& SetNoLogging();
template <typename T>
StatusBuilder& operator<<(const T& msg) {
if (status_.ok()) return *this;
*stream_ << msg;
return *this;
}
operator Status() const&;
operator Status() &&;
::mediapipe::Status JoinMessageToStatus();
private:
// Specifies how to join the error message in the original status and any
// additional message that has been streamed into the builder.
enum class MessageJoinStyle {
kAnnotate,
kAppend,
kPrepend,
};
// The status that the result will be based on.
::mediapipe::Status status_;
// The line to record if this file is logged.
int line_;
// Not-owned: The file to record if this status is logged.
const char* file_;
bool no_logging_ = false;
// The additional messages added with `<<`.
std::unique_ptr<std::ostringstream> stream_;
// Specifies how to join the message in `status_` and `stream_`.
MessageJoinStyle join_style_ = MessageJoinStyle::kAnnotate;
};
inline StatusBuilder AlreadyExistsErrorBuilder(
::mediapipe::source_location location) {
return StatusBuilder(::mediapipe::StatusCode::kAlreadyExists, location);
}
inline StatusBuilder FailedPreconditionErrorBuilder(
::mediapipe::source_location location) {
return StatusBuilder(::mediapipe::StatusCode::kFailedPrecondition, location);
}
inline StatusBuilder InternalErrorBuilder(
::mediapipe::source_location location) {
return StatusBuilder(::mediapipe::StatusCode::kInternal, location);
}
inline StatusBuilder InvalidArgumentErrorBuilder(
::mediapipe::source_location location) {
return StatusBuilder(::mediapipe::StatusCode::kInvalidArgument, location);
}
inline StatusBuilder NotFoundErrorBuilder(
::mediapipe::source_location location) {
return StatusBuilder(::mediapipe::StatusCode::kNotFound, location);
}
inline StatusBuilder UnavailableErrorBuilder(
::mediapipe::source_location location) {
return StatusBuilder(::mediapipe::StatusCode::kUnavailable, location);
}
inline StatusBuilder UnimplementedErrorBuilder(
::mediapipe::source_location location) {
return StatusBuilder(::mediapipe::StatusCode::kUnimplemented, location);
}
inline StatusBuilder UnknownErrorBuilder(
::mediapipe::source_location location) {
return StatusBuilder(::mediapipe::StatusCode::kUnknown, location);
}
} // namespace mediapipe
#endif // MEDIAPIPE_DEPS_STATUS_BUILDER_H_
@@ -0,0 +1,75 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/deps/status_builder.h"
#include "mediapipe/framework/port/gtest.h"
namespace mediapipe {
TEST(StatusBuilder, AnnotateMode) {
::mediapipe::Status status =
StatusBuilder(::mediapipe::Status(::mediapipe::StatusCode::kNotFound,
"original message"),
MEDIAPIPE_LOC)
<< "annotated message1 "
<< "annotated message2";
ASSERT_FALSE(status.ok());
EXPECT_EQ(status.code(), ::mediapipe::StatusCode::kNotFound);
EXPECT_EQ(status.error_message(),
"original message; annotated message1 annotated message2");
}
TEST(StatusBuilder, PrependMode) {
::mediapipe::Status status =
StatusBuilder(
::mediapipe::Status(::mediapipe::StatusCode::kInvalidArgument,
"original message"),
MEDIAPIPE_LOC)
.SetPrepend()
<< "prepended message1 "
<< "prepended message2 ";
ASSERT_FALSE(status.ok());
EXPECT_EQ(status.code(), ::mediapipe::StatusCode::kInvalidArgument);
EXPECT_EQ(status.error_message(),
"prepended message1 prepended message2 original message");
}
TEST(StatusBuilder, AppendMode) {
::mediapipe::Status status =
StatusBuilder(::mediapipe::Status(::mediapipe::StatusCode::kInternal,
"original message"),
MEDIAPIPE_LOC)
.SetAppend()
<< " extra message1"
<< " extra message2";
ASSERT_FALSE(status.ok());
EXPECT_EQ(status.code(), ::mediapipe::StatusCode::kInternal);
EXPECT_EQ(status.error_message(),
"original message extra message1 extra message2");
}
TEST(StatusBuilder, NoLoggingMode) {
::mediapipe::Status status =
StatusBuilder(::mediapipe::Status(::mediapipe::StatusCode::kUnavailable,
"original message"),
MEDIAPIPE_LOC)
.SetNoLogging()
<< " extra message";
ASSERT_FALSE(status.ok());
EXPECT_EQ(status.code(), ::mediapipe::StatusCode::kUnavailable);
EXPECT_EQ(status.error_message(), "original message");
}
} // namespace mediapipe
+221
View File
@@ -0,0 +1,221 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Helper macros and methods to return and propagate errors with
// `::mediapipe::Status`.
//
// The owners of mediapipe do not endorse use of these macros as a good
// programming practice, and would prefer that you write the equivalent C++
// directly. The macros are provided and supported for those that disagree,
// with the goal of having a single, consistent, and robust implementation.
#ifndef MEDIAPIPE_DEPS_STATUS_MACROS_H_
#define MEDIAPIPE_DEPS_STATUS_MACROS_H_
#include "mediapipe/framework/deps/status.h"
#include "mediapipe/framework/deps/status_builder.h"
// Evaluates an expression that produces a `::mediapipe::Status`. If the status
// is not ok, returns it from the current function.
//
// For example:
// ::mediapipe::Status MultiStepFunction() {
// RETURN_IF_ERROR(Function(args...));
// RETURN_IF_ERROR(foo.Method(args...));
// return ::mediapipe::OkStatus();
// }
//
// The macro ends with a `::mediapipe::StatusBuilder` which allows the returned
// status to be extended with more details. Any chained expressions after the
// macro will not be evaluated unless there is an error.
//
// For example:
// ::mediapipe::Status MultiStepFunction() {
// RETURN_IF_ERROR(Function(args...)) << "in MultiStepFunction";
// RETURN_IF_ERROR(foo.Method(args...)).Log(base_logging::ERROR)
// << "while processing query: " << query.DebugString();
// return ::mediapipe::OkStatus();
// }
//
// `::mediapipe::StatusBuilder` supports adapting the builder chain using a
// `With` method and a functor. This allows for powerful extensions to the
// macro.
//
// For example, teams can define local policies to use across their code:
//
// StatusBuilder TeamPolicy(StatusBuilder builder) {
// return std::move(builder.Log(base_logging::WARNING).Attach(...));
// }
//
// RETURN_IF_ERROR(foo()).With(TeamPolicy);
// RETURN_IF_ERROR(bar()).With(TeamPolicy);
//
// Changing the return type allows the macro to be used with Task and Rpc
// interfaces. See `::mediapipe::TaskReturn` and `rpc::RpcSetStatus` for
// details.
//
// void Read(StringPiece name, ::mediapipe::Task* task) {
// int64 id;
// RETURN_IF_ERROR(GetIdForName(name, &id)).With(TaskReturn(task));
// RETURN_IF_ERROR(ReadForId(id)).With(TaskReturn(task));
// task->Return();
// }
//
// If using this macro inside a lambda, you need to annotate the return type
// to avoid confusion between a `::mediapipe::StatusBuilder` and a
// `::mediapipe::Status` type. E.g.
//
// []() -> ::mediapipe::Status {
// RETURN_IF_ERROR(Function(args...));
// RETURN_IF_ERROR(foo.Method(args...));
// return ::mediapipe::OkStatus();
// }
#define RETURN_IF_ERROR(expr) \
STATUS_MACROS_IMPL_ELSE_BLOCKER_ \
if (::mediapipe::status_macro_internal::StatusAdaptorForMacros \
status_macro_internal_adaptor = {(expr), __FILE__, __LINE__}) { \
} else /* NOLINT */ \
return status_macro_internal_adaptor.Consume()
// Executes an expression `rexpr` that returns a `::mediapipe::StatusOr<T>`. On
// OK, extracts its value into the variable defined by `lhs`, otherwise returns
// from the current function. By default the error status is returned
// unchanged, but it may be modified by an `error_expression`. If there is an
// error, `lhs` is not evaluated; thus any side effects that `lhs` may have
// only occur in the success case.
//
// Interface:
//
// ASSIGN_OR_RETURN(lhs, rexpr)
// ASSIGN_OR_RETURN(lhs, rexpr, error_expression);
//
// WARNING: expands into multiple statements; it cannot be used in a single
// statement (e.g. as the body of an if statement without {})!
//
// Example: Declaring and initializing a new variable (ValueType can be anything
// that can be initialized with assignment, including references):
// ASSIGN_OR_RETURN(ValueType value, MaybeGetValue(arg));
//
// Example: Assigning to an existing variable:
// ValueType value;
// ASSIGN_OR_RETURN(value, MaybeGetValue(arg));
//
// Example: Assigning to an expression with side effects:
// MyProto data;
// ASSIGN_OR_RETURN(*data.mutable_str(), MaybeGetValue(arg));
// // No field "str" is added on error.
//
// Example: Assigning to a std::unique_ptr.
// ASSIGN_OR_RETURN(std::unique_ptr<T> ptr, MaybeGetPtr(arg));
//
// If passed, the `error_expression` is evaluated to produce the return
// value. The expression may reference any variable visible in scope, as
// well as a `::mediapipe::StatusBuilder` object populated with the error and
// named by a single underscore `_`. The expression typically uses the
// builder to modify the status and is returned directly in manner similar
// to RETURN_IF_ERROR. The expression may, however, evaluate to any type
// returnable by the function, including (void). For example:
//
// Example: Adjusting the error message.
// ASSIGN_OR_RETURN(ValueType value, MaybeGetValue(query),
// _ << "while processing query " << query.DebugString());
//
// Example: Logging the error on failure.
// ASSIGN_OR_RETURN(ValueType value, MaybeGetValue(query), _.LogError());
//
#define ASSIGN_OR_RETURN(...) \
STATUS_MACROS_IMPL_GET_VARIADIC_(__VA_ARGS__, \
STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_3_, \
STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_2_) \
(__VA_ARGS__)
// =================================================================
// == Implementation details, do not rely on anything below here. ==
// =================================================================
#define STATUS_MACROS_IMPL_GET_VARIADIC_(_1, _2, _3, NAME, ...) NAME
#define STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_2_(lhs, rexpr) \
STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_3_(lhs, rexpr, std::move(_))
#define STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_3_(lhs, rexpr, error_expression) \
STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_( \
STATUS_MACROS_IMPL_CONCAT_(_status_or_value, __LINE__), lhs, rexpr, \
error_expression)
#define STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_(statusor, lhs, rexpr, \
error_expression) \
auto statusor = (rexpr); \
if (ABSL_PREDICT_FALSE(!statusor.ok())) { \
::mediapipe::StatusBuilder _(std::move(statusor).status(), __FILE__, \
__LINE__); \
(void)_; /* error_expression is allowed to not use this variable */ \
return (error_expression); \
} \
lhs = std::move(statusor).ValueOrDie()
// Internal helper for concatenating macro values.
#define STATUS_MACROS_IMPL_CONCAT_INNER_(x, y) x##y
#define STATUS_MACROS_IMPL_CONCAT_(x, y) STATUS_MACROS_IMPL_CONCAT_INNER_(x, y)
// The GNU compiler emits a warning for code like:
//
// if (foo)
// if (bar) { } else baz;
//
// because it thinks you might want the else to bind to the first if. This
// leads to problems with code like:
//
// if (do_expr) RETURN_IF_ERROR(expr) << "Some message";
//
// The "switch (0) case 0:" idiom is used to suppress this.
#define STATUS_MACROS_IMPL_ELSE_BLOCKER_ \
switch (0) \
case 0: \
default: // NOLINT
namespace mediapipe {
namespace status_macro_internal {
// Provides a conversion to bool so that it can be used inside an if statement
// that declares a variable.
class StatusAdaptorForMacros {
public:
StatusAdaptorForMacros(const Status& status, const char* file, int line)
: builder_(status, file, line) {}
StatusAdaptorForMacros(Status&& status, const char* file, int line)
: builder_(std::move(status), file, line) {}
StatusAdaptorForMacros(const StatusBuilder& builder, const char* /* file */,
int /* line */)
: builder_(builder) {}
StatusAdaptorForMacros(StatusBuilder&& builder, const char* /* file */,
int /* line */)
: builder_(std::move(builder)) {}
StatusAdaptorForMacros(const StatusAdaptorForMacros&) = delete;
StatusAdaptorForMacros& operator=(const StatusAdaptorForMacros&) = delete;
explicit operator bool() const { return ABSL_PREDICT_TRUE(builder_.ok()); }
StatusBuilder&& Consume() { return std::move(builder_); }
private:
StatusBuilder builder_;
};
} // namespace status_macro_internal
} // namespace mediapipe
#endif // MEDIAPIPE_DEPS_STATUS_MACROS_H_
@@ -0,0 +1,28 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_DEPS_STATUS_MATCHERS_H_
#define MEDIAPIPE_DEPS_STATUS_MATCHERS_H_
#include "gtest/gtest.h"
#include "mediapipe/framework/deps/status.h"
// EXPECT_OK marco is already defined in our external dependency library
// protobuf. To be consistent with MEDIAPIPE_EXPECT_OK, we also add prefix
// MEDIAPIPE_ to ASSERT_OK. We prefer to use the marcos with MEDIAPIPE_ prefix
// in mediapipe's codebase.
#define MEDIAPIPE_EXPECT_OK(statement) EXPECT_TRUE((statement).ok())
#define MEDIAPIPE_ASSERT_OK(statement) ASSERT_TRUE((statement).ok())
#endif // MEDIAPIPE_DEPS_STATUS_MATCHERS_H_
+98
View File
@@ -0,0 +1,98 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/deps/status.h"
#include "mediapipe/framework/deps/status_matchers.h"
#include "mediapipe/framework/port/gtest.h"
namespace mediapipe {
TEST(Status, OK) {
EXPECT_EQ(OkStatus().code(), ::mediapipe::StatusCode::kOk);
EXPECT_EQ(OkStatus().error_message(), "");
MEDIAPIPE_EXPECT_OK(OkStatus());
MEDIAPIPE_ASSERT_OK(OkStatus());
EXPECT_EQ(OkStatus(), Status());
Status s;
EXPECT_TRUE(s.ok());
}
TEST(DeathStatus, CheckOK) {
Status status(::mediapipe::StatusCode::kInvalidArgument, "Invalid");
ASSERT_DEATH(MEDIAPIPE_CHECK_OK(status), "Invalid");
}
TEST(Status, Set) {
Status status;
status = Status(::mediapipe::StatusCode::kCancelled, "Error message");
EXPECT_EQ(status.code(), ::mediapipe::StatusCode::kCancelled);
EXPECT_EQ(status.error_message(), "Error message");
}
TEST(Status, Copy) {
Status a(::mediapipe::StatusCode::kInvalidArgument, "Invalid");
Status b(a);
ASSERT_EQ(a.ToString(), b.ToString());
}
TEST(Status, Assign) {
Status a(::mediapipe::StatusCode::kInvalidArgument, "Invalid");
Status b;
b = a;
ASSERT_EQ(a.ToString(), b.ToString());
}
TEST(Status, Update) {
Status s;
s.Update(OkStatus());
ASSERT_TRUE(s.ok());
Status a(::mediapipe::StatusCode::kInvalidArgument, "Invalid");
s.Update(a);
ASSERT_EQ(s.ToString(), a.ToString());
Status b(::mediapipe::StatusCode::kInternal, "Invalid");
s.Update(b);
ASSERT_EQ(s.ToString(), a.ToString());
s.Update(OkStatus());
ASSERT_EQ(s.ToString(), a.ToString());
ASSERT_FALSE(s.ok());
}
TEST(Status, EqualsOK) { ASSERT_EQ(OkStatus(), Status()); }
TEST(Status, EqualsSame) {
Status a(::mediapipe::StatusCode::kInvalidArgument, "Invalid");
Status b(::mediapipe::StatusCode::kInvalidArgument, "Invalid");
ASSERT_EQ(a, b);
}
TEST(Status, EqualsCopy) {
const Status a(::mediapipe::StatusCode::kInvalidArgument, "Invalid");
const Status b = a;
ASSERT_EQ(a, b);
}
TEST(Status, EqualsDifferentCode) {
const Status a(::mediapipe::StatusCode::kInvalidArgument, "Invalid");
const Status b(::mediapipe::StatusCode::kInternal, "Internal");
ASSERT_NE(a, b);
}
TEST(Status, EqualsDifferentMessage) {
const Status a(::mediapipe::StatusCode::kInvalidArgument, "message");
const Status b(::mediapipe::StatusCode::kInvalidArgument, "another");
ASSERT_NE(a, b);
}
} // namespace mediapipe
+38
View File
@@ -0,0 +1,38 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/deps/statusor.h"
#include "absl/base/attributes.h"
#include "mediapipe/framework/deps/canonical_errors.h"
#include "mediapipe/framework/deps/status.h"
#include "mediapipe/framework/port/logging.h"
namespace mediapipe {
namespace internal_statusor {
void Helper::HandleInvalidStatusCtorArg(::mediapipe::Status* status) {
const char* kMessage =
"An OK status is not a valid constructor argument to StatusOr<T>";
LOG(ERROR) << kMessage;
*status = ::mediapipe::InternalError(kMessage);
}
void Helper::Crash(const ::mediapipe::Status& status) {
LOG(FATAL) << "Attempting to fetch value instead of handling error "
<< status;
}
} // namespace internal_statusor
} // namespace mediapipe
+331
View File
@@ -0,0 +1,331 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// StatusOr<T> is the union of a Status object and a T
// object. StatusOr models the concept of an object that is either a
// usable value, or an error Status explaining why such a value is
// not present. To this end, StatusOr<T> does not allow its Status
// value to be Status::OK. Furthermore, the value of a StatusOr<T*>
// must not be null. This is enforced by a debug check in most cases,
// but even when it is not, clients must not set the value to null.
//
// The primary use-case for StatusOr<T> is as the return value of a
// function which may fail.
//
// Example client usage for a StatusOr<T>, where T is not a pointer:
//
// ::mediapipe::StatusOr<float> result = DoBigCalculationThatCouldFail();
// if (result.ok()) {
// float answer = result.ValueOrDie();
// printf("Big calculation yielded: %f", answer);
// } else {
// LOG(ERROR) << result.status();
// }
//
// Example client usage for a StatusOr<T*>:
//
// ::mediapipe::StatusOr<Foo*> result = FooFactory::MakeNewFoo(arg);
// if (result.ok()) {
// std::unique_ptr<Foo> foo(result.ValueOrDie());
// foo->DoSomethingCool();
// } else {
// LOG(ERROR) << result.status();
// }
//
// Example client usage for a StatusOr<std::unique_ptr<T>>:
//
// ::mediapipe::StatusOr<std::unique_ptr<Foo>> result =
// FooFactory::MakeNewFoo(arg);
// if (result.ok()) {
// std::unique_ptr<Foo> foo = std::move(result.ValueOrDie());
// foo->DoSomethingCool();
// } else {
// LOG(ERROR) << result.status();
// }
//
// Example factory implementation returning StatusOr<T*>:
//
// ::mediapipe::StatusOr<Foo*> FooFactory::MakeNewFoo(int arg) {
// if (arg <= 0) {
// return ::mediapipe::InvalidArgumentError("Arg must be positive");
// } else {
// return new Foo(arg);
// }
// }
//
// Note that the assignment operators require that destroying the currently
// stored value cannot invalidate the argument; in other words, the argument
// cannot be an alias for the current value, or anything owned by the current
// value.
#ifndef MEDIAPIPE_DEPS_DEFAULT_STATUSOR_H_
#define MEDIAPIPE_DEPS_DEFAULT_STATUSOR_H_
#include "absl/base/attributes.h"
#include "mediapipe/framework/deps/status.h"
#include "mediapipe/framework/deps/status_builder.h"
#include "mediapipe/framework/deps/statusor_internals.h"
namespace mediapipe {
#if defined(__clang__)
// Only clang supports warn_unused_result as a type annotation.
template <typename T>
class ABSL_MUST_USE_RESULT StatusOr;
#endif
template <typename T>
class StatusOr : private internal_statusor::StatusOrData<T>,
private internal_statusor::TraitsBase<
std::is_copy_constructible<T>::value,
std::is_move_constructible<T>::value> {
template <typename U>
friend class StatusOr;
typedef internal_statusor::StatusOrData<T> Base;
public:
typedef T element_type;
// Constructs a new StatusOr with Status::UNKNOWN status. This is marked
// 'explicit' to try to catch cases like 'return {};', where people think
// StatusOr<std::vector<int>> will be initialized with an empty vector,
// instead of a Status::UNKNOWN status.
explicit StatusOr();
// StatusOr<T> will be copy constructible/assignable if T is copy
// constructible.
StatusOr(const StatusOr&) = default;
StatusOr& operator=(const StatusOr&) = default;
// StatusOr<T> will be move constructible/assignable if T is move
// constructible.
StatusOr(StatusOr&&) = default;
StatusOr& operator=(StatusOr&&) = default;
// Conversion copy/move constructor, T must be convertible from U.
// TODO: These should not participate in overload resolution if U
// is not convertible to T.
template <typename U>
StatusOr(const StatusOr<U>& other);
template <typename U>
StatusOr(StatusOr<U>&& other);
// Conversion copy/move assignment operator, T must be convertible from U.
template <typename U>
StatusOr& operator=(const StatusOr<U>& other);
template <typename U>
StatusOr& operator=(StatusOr<U>&& other);
// Constructs a new StatusOr with the given value. After calling this
// constructor, calls to ValueOrDie() will succeed, and calls to status() will
// return OK.
//
// NOTE: Not explicit - we want to use StatusOr<T> as a return type
// so it is convenient and sensible to be able to do 'return T()'
// when the return type is StatusOr<T>.
//
// REQUIRES: T is copy constructible.
StatusOr(const T& value);
// Constructs a new StatusOr with the given non-ok status. After calling
// this constructor, calls to ValueOrDie() will CHECK-fail.
//
// NOTE: Not explicit - we want to use StatusOr<T> as a return
// value, so it is convenient and sensible to be able to do 'return
// Status()' when the return type is StatusOr<T>.
//
// REQUIRES: !status.ok(). This requirement is DCHECKed.
// In optimized builds, passing Status::OK() here will have the effect
// of passing ::mediapipe::StatusCode::kInternal as a fallback.
StatusOr(const ::mediapipe::Status& status);
StatusOr& operator=(const ::mediapipe::Status& status);
StatusOr(const ::mediapipe::StatusBuilder& builder);
StatusOr& operator=(const ::mediapipe::StatusBuilder& builder);
// TODO: Add operator=(T) overloads.
// Similar to the `const T&` overload.
//
// REQUIRES: T is move constructible.
StatusOr(T&& value);
// RValue versions of the operations declared above.
StatusOr(::mediapipe::Status&& status);
StatusOr& operator=(::mediapipe::Status&& status);
StatusOr(::mediapipe::StatusBuilder&& builder);
StatusOr& operator=(::mediapipe::StatusBuilder&& builder);
// Returns this->status().ok()
bool ok() const { return this->status_.ok(); }
// Returns a reference to mediapipe status. If this contains a T, then
// returns Status::OK().
const ::mediapipe::Status& status() const&;
::mediapipe::Status status() &&;
// Returns a reference to our current value, or CHECK-fails if !this->ok().
//
// Note: for value types that are cheap to copy, prefer simple code:
//
// T value = statusor.ValueOrDie();
//
// Otherwise, if the value type is expensive to copy, but can be left
// in the StatusOr, simply assign to a reference:
//
// T& value = statusor.ValueOrDie(); // or `const T&`
//
// Otherwise, if the value type supports an efficient move, it can be
// used as follows:
//
// T value = std::move(statusor).ValueOrDie();
//
// The std::move on statusor instead of on the whole expression enables
// warnings about possible uses of the statusor object after the move.
// C++ style guide waiver for ref-qualified overloads granted in cl/143176389
// See go/ref-qualifiers for more details on such overloads.
const T& ValueOrDie() const&;
T& ValueOrDie() &;
const T&& ValueOrDie() const&&;
T&& ValueOrDie() &&;
T ConsumeValueOrDie() { return std::move(ValueOrDie()); }
// Ignores any errors. This method does nothing except potentially suppress
// complaints from any tools that are checking that errors are not dropped on
// the floor.
void IgnoreError() const;
};
////////////////////////////////////////////////////////////////////////////////
// Implementation details for StatusOr<T>
template <typename T>
StatusOr<T>::StatusOr()
: Base(::mediapipe::Status(::mediapipe::StatusCode::kUnknown, "")) {}
template <typename T>
StatusOr<T>::StatusOr(const T& value) : Base(value) {}
template <typename T>
StatusOr<T>::StatusOr(const ::mediapipe::Status& status) : Base(status) {}
template <typename T>
StatusOr<T>::StatusOr(const ::mediapipe::StatusBuilder& builder)
: Base(builder) {}
template <typename T>
StatusOr<T>& StatusOr<T>::operator=(const ::mediapipe::Status& status) {
this->Assign(status);
return *this;
}
template <typename T>
StatusOr<T>& StatusOr<T>::operator=(const ::mediapipe::StatusBuilder& builder) {
return *this = static_cast<::mediapipe::Status>(builder);
}
template <typename T>
StatusOr<T>::StatusOr(T&& value) : Base(std::move(value)) {}
template <typename T>
StatusOr<T>::StatusOr(::mediapipe::Status&& status) : Base(std::move(status)) {}
template <typename T>
StatusOr<T>::StatusOr(::mediapipe::StatusBuilder&& builder)
: Base(std::move(builder)) {}
template <typename T>
StatusOr<T>& StatusOr<T>::operator=(::mediapipe::Status&& status) {
this->Assign(std::move(status));
return *this;
}
template <typename T>
StatusOr<T>& StatusOr<T>::operator=(::mediapipe::StatusBuilder&& builder) {
return *this = static_cast<::mediapipe::Status>(std::move(builder));
}
template <typename T>
template <typename U>
inline StatusOr<T>::StatusOr(const StatusOr<U>& other)
: Base(static_cast<const typename StatusOr<U>::Base&>(other)) {}
template <typename T>
template <typename U>
inline StatusOr<T>& StatusOr<T>::operator=(const StatusOr<U>& other) {
if (other.ok())
this->Assign(other.ValueOrDie());
else
this->Assign(other.status());
return *this;
}
template <typename T>
template <typename U>
inline StatusOr<T>::StatusOr(StatusOr<U>&& other)
: Base(static_cast<typename StatusOr<U>::Base&&>(other)) {}
template <typename T>
template <typename U>
inline StatusOr<T>& StatusOr<T>::operator=(StatusOr<U>&& other) {
if (other.ok()) {
this->Assign(std::move(other).ValueOrDie());
} else {
this->Assign(std::move(other).status());
}
return *this;
}
template <typename T>
const ::mediapipe::Status& StatusOr<T>::status() const& {
return this->status_;
}
template <typename T>
::mediapipe::Status StatusOr<T>::status() && {
return ok() ? ::mediapipe::OkStatus() : std::move(this->status_);
}
template <typename T>
const T& StatusOr<T>::ValueOrDie() const& {
this->EnsureOk();
return this->data_;
}
template <typename T>
T& StatusOr<T>::ValueOrDie() & {
this->EnsureOk();
return this->data_;
}
template <typename T>
const T&& StatusOr<T>::ValueOrDie() const&& {
this->EnsureOk();
return std::move(this->data_);
}
template <typename T>
T&& StatusOr<T>::ValueOrDie() && {
this->EnsureOk();
return std::move(this->data_);
}
template <typename T>
void StatusOr<T>::IgnoreError() const {
// no-op
}
} // namespace mediapipe
#endif // MEDIAPIPE_DEPS_DEFAULT_STATUSOR_H_
@@ -0,0 +1,245 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_DEPS_STATUSOR_INTERNALS_H_
#define MEDIAPIPE_DEPS_STATUSOR_INTERNALS_H_
#include "absl/base/attributes.h"
#include "mediapipe/framework/deps/status.h"
namespace mediapipe {
namespace internal_statusor {
class Helper {
public:
// Move type-agnostic error handling to the .cc.
static void HandleInvalidStatusCtorArg(::mediapipe::Status*);
ABSL_ATTRIBUTE_NORETURN static void Crash(const ::mediapipe::Status& status);
};
// Construct an instance of T in `p` through placement new, passing Args... to
// the constructor.
// This abstraction is here mostly for the gcc performance fix.
template <typename T, typename... Args>
void PlacementNew(void* p, Args&&... args) {
#if defined(__GNUC__) && !defined(__clang__)
// Teach gcc that 'p' cannot be null, fixing code size issues.
if (p == nullptr) __builtin_unreachable();
#endif
new (p) T(std::forward<Args>(args)...);
}
// Helper base class to hold the data and all operations.
// We move all this to a base class to allow mixing with the appropriate
// TraitsBase specialization.
template <typename T>
class StatusOrData {
template <typename U>
friend class StatusOrData;
public:
StatusOrData() = delete;
StatusOrData(const StatusOrData& other) {
if (other.ok()) {
MakeValue(other.data_);
MakeStatus();
} else {
MakeStatus(other.status_);
}
}
StatusOrData(StatusOrData&& other) noexcept {
if (other.ok()) {
MakeValue(std::move(other.data_));
MakeStatus();
} else {
MakeStatus(std::move(other.status_));
}
}
template <typename U>
StatusOrData(const StatusOrData<U>& other) {
if (other.ok()) {
MakeValue(other.data_);
MakeStatus();
} else {
MakeStatus(other.status_);
}
}
template <typename U>
StatusOrData(StatusOrData<U>&& other) {
if (other.ok()) {
MakeValue(std::move(other.data_));
MakeStatus();
} else {
MakeStatus(std::move(other.status_));
}
}
explicit StatusOrData(const T& value) : data_(value) { MakeStatus(); }
explicit StatusOrData(T&& value) : data_(std::move(value)) { MakeStatus(); }
explicit StatusOrData(const ::mediapipe::Status& status) : status_(status) {
EnsureNotOk();
}
explicit StatusOrData(::mediapipe::Status&& status)
: status_(std::move(status)) {
EnsureNotOk();
}
StatusOrData& operator=(const StatusOrData& other) {
if (this == &other) return *this;
if (other.ok())
Assign(other.data_);
else
Assign(other.status_);
return *this;
}
StatusOrData& operator=(StatusOrData&& other) {
if (this == &other) return *this;
if (other.ok())
Assign(std::move(other.data_));
else
Assign(std::move(other.status_));
return *this;
}
~StatusOrData() {
if (ok()) {
status_.~Status();
data_.~T();
} else {
status_.~Status();
}
}
void Assign(const T& value) {
if (ok()) {
data_.~T();
MakeValue(value);
} else {
MakeValue(value);
status_ = ::mediapipe::OkStatus();
}
}
void Assign(T&& value) {
if (ok()) {
data_.~T();
MakeValue(std::move(value));
} else {
MakeValue(std::move(value));
status_ = ::mediapipe::OkStatus();
}
}
void Assign(const ::mediapipe::Status& status) {
Clear();
status_ = status;
EnsureNotOk();
}
void Assign(::mediapipe::Status&& status) {
Clear();
status_ = std::move(status);
EnsureNotOk();
}
bool ok() const { return status_.ok(); }
protected:
// status_ will always be active after the constructor.
// We make it a union to be able to initialize exactly how we need without
// waste.
// Eg. in the copy constructor we use the default constructor of Status in
// the ok() path to avoid an extra Ref call.
union {
::mediapipe::Status status_;
};
// data_ is active iff status_.ok()==true
struct Dummy {};
union {
// When T is const, we need some non-const object we can cast to void* for
// the placement new. dummy_ is that object.
Dummy dummy_;
T data_;
};
void Clear() {
if (ok()) data_.~T();
}
void EnsureOk() const {
if (!ok()) Helper::Crash(status_);
}
void EnsureNotOk() {
if (ok()) Helper::HandleInvalidStatusCtorArg(&status_);
}
// Construct the value (ie. data_) through placement new with the passed
// argument.
template <typename Arg>
void MakeValue(Arg&& arg) {
internal_statusor::PlacementNew<T>(&dummy_, std::forward<Arg>(arg));
}
// Construct the status (ie. status_) through placement new with the passed
// argument.
template <typename... Args>
void MakeStatus(Args&&... args) {
internal_statusor::PlacementNew<::mediapipe::Status>(
&status_, std::forward<Args>(args)...);
}
};
// Helper base class to allow implicitly deleted constructors and assignment
// operations in StatusOr.
// TraitsBase will explicitly delete what it can't support and StatusOr will
// inherit that behavior implicitly.
template <bool Copy, bool Move>
struct TraitsBase {
TraitsBase() = default;
TraitsBase(const TraitsBase&) = default;
TraitsBase(TraitsBase&&) = default;
TraitsBase& operator=(const TraitsBase&) = default;
TraitsBase& operator=(TraitsBase&&) = default;
};
template <>
struct TraitsBase<false, true> {
TraitsBase() = default;
TraitsBase(const TraitsBase&) = delete;
TraitsBase(TraitsBase&&) = default;
TraitsBase& operator=(const TraitsBase&) = delete;
TraitsBase& operator=(TraitsBase&&) = default;
};
template <>
struct TraitsBase<false, false> {
TraitsBase() = default;
TraitsBase(const TraitsBase&) = delete;
TraitsBase(TraitsBase&&) = delete;
TraitsBase& operator=(const TraitsBase&) = delete;
TraitsBase& operator=(TraitsBase&&) = delete;
};
} // namespace internal_statusor
} // namespace mediapipe
#endif // MEDIAPIPE_DEPS_STATUSOR_INTERNALS_H_
+438
View File
@@ -0,0 +1,438 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Unit tests for StatusOr
#include "mediapipe/framework/deps/statusor.h"
#include <memory>
#include <type_traits>
#include "mediapipe/framework/deps/canonical_errors.h"
#include "mediapipe/framework/deps/status.h"
#include "mediapipe/framework/port/gtest.h"
namespace mediapipe {
namespace {
class Base1 {
public:
virtual ~Base1() {}
int pad_;
};
class Base2 {
public:
virtual ~Base2() {}
int yetotherpad_;
};
class Derived : public Base1, public Base2 {
public:
~Derived() override {}
int evenmorepad_;
};
class CopyNoAssign {
public:
explicit CopyNoAssign(int value) : foo_(value) {}
CopyNoAssign(const CopyNoAssign& other) : foo_(other.foo_) {}
int foo_;
private:
const CopyNoAssign& operator=(const CopyNoAssign&);
};
class NoDefaultConstructor {
public:
explicit NoDefaultConstructor(int foo);
};
static_assert(!std::is_default_constructible<NoDefaultConstructor>(),
"Should not be default-constructible.");
StatusOr<std::unique_ptr<int>> ReturnUniquePtr() {
// Uses implicit constructor from T&&
return std::unique_ptr<int>(new int(0));
}
TEST(StatusOr, ElementType) {
static_assert(std::is_same<StatusOr<int>::element_type, int>(), "");
static_assert(std::is_same<StatusOr<char>::element_type, char>(), "");
}
TEST(StatusOr, TestNoDefaultConstructorInitialization) {
// Explicitly initialize it with an error code.
::mediapipe::StatusOr<NoDefaultConstructor> statusor(
::mediapipe::CancelledError(""));
EXPECT_FALSE(statusor.ok());
EXPECT_EQ(statusor.status().code(), ::mediapipe::StatusCode::kCancelled);
// Default construction of StatusOr initializes it with an UNKNOWN error code.
::mediapipe::StatusOr<NoDefaultConstructor> statusor2;
EXPECT_FALSE(statusor2.ok());
EXPECT_EQ(statusor2.status().code(), ::mediapipe::StatusCode::kUnknown);
}
TEST(StatusOr, TestMoveOnlyInitialization) {
::mediapipe::StatusOr<std::unique_ptr<int>> thing(ReturnUniquePtr());
ASSERT_TRUE(thing.ok());
EXPECT_EQ(0, *thing.ValueOrDie());
int* previous = thing.ValueOrDie().get();
thing = ReturnUniquePtr();
EXPECT_TRUE(thing.ok());
EXPECT_EQ(0, *thing.ValueOrDie());
EXPECT_NE(previous, thing.ValueOrDie().get());
}
TEST(StatusOr, TestMoveOnlyStatusCtr) {
::mediapipe::StatusOr<std::unique_ptr<int>> thing(
::mediapipe::CancelledError(""));
ASSERT_FALSE(thing.ok());
}
TEST(StatusOr, TestMoveOnlyValueExtraction) {
::mediapipe::StatusOr<std::unique_ptr<int>> thing(ReturnUniquePtr());
ASSERT_TRUE(thing.ok());
std::unique_ptr<int> ptr = thing.ConsumeValueOrDie();
EXPECT_EQ(0, *ptr);
thing = std::move(ptr);
ptr = std::move(thing.ValueOrDie());
EXPECT_EQ(0, *ptr);
}
TEST(StatusOr, TestMoveOnlyConversion) {
::mediapipe::StatusOr<std::unique_ptr<const int>> const_thing(
ReturnUniquePtr());
EXPECT_TRUE(const_thing.ok());
EXPECT_EQ(0, *const_thing.ValueOrDie());
// Test rvalue converting assignment
const int* const_previous = const_thing.ValueOrDie().get();
const_thing = ReturnUniquePtr();
EXPECT_TRUE(const_thing.ok());
EXPECT_EQ(0, *const_thing.ValueOrDie());
EXPECT_NE(const_previous, const_thing.ValueOrDie().get());
}
TEST(StatusOr, TestMoveOnlyVector) {
// Sanity check that ::mediapipe::StatusOr<MoveOnly> works in vector.
std::vector<::mediapipe::StatusOr<std::unique_ptr<int>>> vec;
vec.push_back(ReturnUniquePtr());
vec.resize(2);
auto another_vec = std::move(vec);
EXPECT_EQ(0, *another_vec[0].ValueOrDie());
EXPECT_EQ(::mediapipe::StatusCode::kUnknown, another_vec[1].status().code());
}
TEST(StatusOr, TestMoveWithValuesAndErrors) {
::mediapipe::StatusOr<std::string> status_or(std::string(1000, '0'));
::mediapipe::StatusOr<std::string> value1(std::string(1000, '1'));
::mediapipe::StatusOr<std::string> value2(std::string(1000, '2'));
::mediapipe::StatusOr<std::string> error1(
Status(::mediapipe::StatusCode::kUnknown, "error1"));
::mediapipe::StatusOr<std::string> error2(
Status(::mediapipe::StatusCode::kUnknown, "error2"));
ASSERT_TRUE(status_or.ok());
EXPECT_EQ(std::string(1000, '0'), status_or.ValueOrDie());
// Overwrite the value in status_or with another value.
status_or = std::move(value1);
ASSERT_TRUE(status_or.ok());
EXPECT_EQ(std::string(1000, '1'), status_or.ValueOrDie());
// Overwrite the value in status_or with an error.
status_or = std::move(error1);
ASSERT_FALSE(status_or.ok());
EXPECT_EQ("error1", status_or.status().error_message());
// Overwrite the error in status_or with another error.
status_or = std::move(error2);
ASSERT_FALSE(status_or.ok());
EXPECT_EQ("error2", status_or.status().error_message());
// Overwrite the error with a value.
status_or = std::move(value2);
ASSERT_TRUE(status_or.ok());
EXPECT_EQ(std::string(1000, '2'), status_or.ValueOrDie());
}
TEST(StatusOr, TestCopyWithValuesAndErrors) {
::mediapipe::StatusOr<std::string> status_or(std::string(1000, '0'));
::mediapipe::StatusOr<std::string> value1(std::string(1000, '1'));
::mediapipe::StatusOr<std::string> value2(std::string(1000, '2'));
::mediapipe::StatusOr<std::string> error1(
Status(::mediapipe::StatusCode::kUnknown, "error1"));
::mediapipe::StatusOr<std::string> error2(
Status(::mediapipe::StatusCode::kUnknown, "error2"));
ASSERT_TRUE(status_or.ok());
EXPECT_EQ(std::string(1000, '0'), status_or.ValueOrDie());
// Overwrite the value in status_or with another value.
status_or = value1;
ASSERT_TRUE(status_or.ok());
EXPECT_EQ(std::string(1000, '1'), status_or.ValueOrDie());
// Overwrite the value in status_or with an error.
status_or = error1;
ASSERT_FALSE(status_or.ok());
EXPECT_EQ("error1", status_or.status().error_message());
// Overwrite the error in status_or with another error.
status_or = error2;
ASSERT_FALSE(status_or.ok());
EXPECT_EQ("error2", status_or.status().error_message());
// Overwrite the error with a value.
status_or = value2;
ASSERT_TRUE(status_or.ok());
EXPECT_EQ(std::string(1000, '2'), status_or.ValueOrDie());
// Verify original values unchanged.
EXPECT_EQ(std::string(1000, '1'), value1.ValueOrDie());
EXPECT_EQ("error1", error1.status().error_message());
EXPECT_EQ("error2", error2.status().error_message());
EXPECT_EQ(std::string(1000, '2'), value2.ValueOrDie());
}
TEST(StatusOr, TestDefaultCtor) {
::mediapipe::StatusOr<int> thing;
EXPECT_FALSE(thing.ok());
EXPECT_EQ(thing.status().code(), ::mediapipe::StatusCode::kUnknown);
}
TEST(StatusOrDeathTest, TestDefaultCtorValue) {
::mediapipe::StatusOr<int> thing;
EXPECT_DEATH(thing.ValueOrDie(), "");
const ::mediapipe::StatusOr<int> thing2;
EXPECT_DEATH(thing.ValueOrDie(), "");
}
TEST(StatusOr, TestStatusCtor) {
::mediapipe::StatusOr<int> thing(
::mediapipe::Status(::mediapipe::StatusCode::kCancelled, ""));
EXPECT_FALSE(thing.ok());
EXPECT_EQ(thing.status().code(), ::mediapipe::StatusCode::kCancelled);
}
TEST(StatusOr, TestValueCtor) {
const int kI = 4;
const ::mediapipe::StatusOr<int> thing(kI);
EXPECT_TRUE(thing.ok());
EXPECT_EQ(kI, thing.ValueOrDie());
}
TEST(StatusOr, TestCopyCtorStatusOk) {
const int kI = 4;
const ::mediapipe::StatusOr<int> original(kI);
const ::mediapipe::StatusOr<int> copy(original);
EXPECT_EQ(copy.status(), original.status());
EXPECT_EQ(original.ValueOrDie(), copy.ValueOrDie());
}
TEST(StatusOr, TestCopyCtorStatusNotOk) {
::mediapipe::StatusOr<int> original(
Status(::mediapipe::StatusCode::kCancelled, ""));
::mediapipe::StatusOr<int> copy(original);
EXPECT_EQ(copy.status(), original.status());
}
TEST(StatusOr, TestCopyCtorNonAssignable) {
const int kI = 4;
CopyNoAssign value(kI);
::mediapipe::StatusOr<CopyNoAssign> original(value);
::mediapipe::StatusOr<CopyNoAssign> copy(original);
EXPECT_EQ(copy.status(), original.status());
EXPECT_EQ(original.ValueOrDie().foo_, copy.ValueOrDie().foo_);
}
TEST(StatusOr, TestCopyCtorStatusOKConverting) {
const int kI = 4;
::mediapipe::StatusOr<int> original(kI);
::mediapipe::StatusOr<double> copy(original);
EXPECT_EQ(copy.status(), original.status());
EXPECT_DOUBLE_EQ(original.ValueOrDie(), copy.ValueOrDie());
}
TEST(StatusOr, TestCopyCtorStatusNotOkConverting) {
::mediapipe::StatusOr<int> original(
Status(::mediapipe::StatusCode::kCancelled, ""));
::mediapipe::StatusOr<double> copy(original);
EXPECT_EQ(copy.status(), original.status());
}
TEST(StatusOr, TestAssignmentStatusOk) {
const int kI = 4;
::mediapipe::StatusOr<int> source(kI);
::mediapipe::StatusOr<int> target;
target = source;
EXPECT_EQ(target.status(), source.status());
EXPECT_EQ(source.ValueOrDie(), target.ValueOrDie());
}
TEST(StatusOr, TestAssignmentStatusNotOk) {
::mediapipe::StatusOr<int> source(
Status(::mediapipe::StatusCode::kCancelled, ""));
::mediapipe::StatusOr<int> target;
target = source;
EXPECT_EQ(target.status(), source.status());
}
TEST(StatusOr, TestStatus) {
::mediapipe::StatusOr<int> good(4);
EXPECT_TRUE(good.ok());
::mediapipe::StatusOr<int> bad(
Status(::mediapipe::StatusCode::kCancelled, ""));
EXPECT_FALSE(bad.ok());
EXPECT_EQ(bad.status(), Status(::mediapipe::StatusCode::kCancelled, ""));
}
TEST(StatusOr, TestValue) {
const int kI = 4;
::mediapipe::StatusOr<int> thing(kI);
EXPECT_EQ(kI, thing.ValueOrDie());
}
TEST(StatusOr, TestValueConst) {
const int kI = 4;
const ::mediapipe::StatusOr<int> thing(kI);
EXPECT_EQ(kI, thing.ValueOrDie());
}
TEST(StatusOrDeathTest, TestValueNotOk) {
::mediapipe::StatusOr<int> thing(
::mediapipe::Status(::mediapipe::StatusCode::kCancelled, "cancelled"));
EXPECT_DEATH(thing.ValueOrDie(), "cancelled");
}
TEST(StatusOrDeathTest, TestValueNotOkConst) {
const ::mediapipe::StatusOr<int> thing(
::mediapipe::Status(::mediapipe::StatusCode::kUnknown, ""));
EXPECT_DEATH(thing.ValueOrDie(), "");
}
TEST(StatusOr, TestPointerDefaultCtor) {
::mediapipe::StatusOr<int*> thing;
EXPECT_FALSE(thing.ok());
EXPECT_EQ(thing.status().code(), ::mediapipe::StatusCode::kUnknown);
}
TEST(StatusOrDeathTest, TestPointerDefaultCtorValue) {
::mediapipe::StatusOr<int*> thing;
EXPECT_DEATH(thing.ValueOrDie(), "");
}
TEST(StatusOr, TestPointerStatusCtor) {
::mediapipe::StatusOr<int*> thing(
Status(::mediapipe::StatusCode::kCancelled, ""));
EXPECT_FALSE(thing.ok());
EXPECT_EQ(thing.status(), Status(::mediapipe::StatusCode::kCancelled, ""));
}
TEST(StatusOr, TestPointerValueCtor) {
const int kI = 4;
::mediapipe::StatusOr<const int*> thing(&kI);
EXPECT_TRUE(thing.ok());
EXPECT_EQ(&kI, thing.ValueOrDie());
}
TEST(StatusOr, TestPointerCopyCtorStatusOk) {
const int kI = 0;
::mediapipe::StatusOr<const int*> original(&kI);
::mediapipe::StatusOr<const int*> copy(original);
EXPECT_EQ(copy.status(), original.status());
EXPECT_EQ(original.ValueOrDie(), copy.ValueOrDie());
}
TEST(StatusOr, TestPointerCopyCtorStatusNotOk) {
::mediapipe::StatusOr<int*> original(
Status(::mediapipe::StatusCode::kCancelled, ""));
::mediapipe::StatusOr<int*> copy(original);
EXPECT_EQ(copy.status(), original.status());
}
TEST(StatusOr, TestPointerCopyCtorStatusOKConverting) {
Derived derived;
::mediapipe::StatusOr<Derived*> original(&derived);
::mediapipe::StatusOr<Base2*> copy(original);
EXPECT_EQ(copy.status(), original.status());
EXPECT_EQ(static_cast<const Base2*>(original.ValueOrDie()),
copy.ValueOrDie());
}
TEST(StatusOr, TestPointerCopyCtorStatusNotOkConverting) {
::mediapipe::StatusOr<Derived*> original(
::mediapipe::Status(::mediapipe::StatusCode::kCancelled, ""));
::mediapipe::StatusOr<Base2*> copy(original);
EXPECT_EQ(copy.status(), original.status());
}
TEST(StatusOr, TestPointerAssignmentStatusOk) {
const int kI = 0;
::mediapipe::StatusOr<const int*> source(&kI);
::mediapipe::StatusOr<const int*> target;
target = source;
EXPECT_EQ(target.status(), source.status());
EXPECT_EQ(source.ValueOrDie(), target.ValueOrDie());
}
TEST(StatusOr, TestPointerAssignmentStatusNotOk) {
::mediapipe::StatusOr<int*> source(
::mediapipe::Status(::mediapipe::StatusCode::kCancelled, ""));
::mediapipe::StatusOr<int*> target;
target = source;
EXPECT_EQ(target.status(), source.status());
}
TEST(StatusOr, TestPointerStatus) {
const int kI = 0;
::mediapipe::StatusOr<const int*> good(&kI);
EXPECT_TRUE(good.ok());
::mediapipe::StatusOr<const int*> bad(
::mediapipe::Status(::mediapipe::StatusCode::kCancelled, ""));
EXPECT_EQ(bad.status(),
::mediapipe::Status(::mediapipe::StatusCode::kCancelled, ""));
}
TEST(StatusOr, TestPointerValue) {
const int kI = 0;
::mediapipe::StatusOr<const int*> thing(&kI);
EXPECT_EQ(&kI, thing.ValueOrDie());
}
TEST(StatusOr, TestPointerValueConst) {
const int kI = 0;
const ::mediapipe::StatusOr<const int*> thing(&kI);
EXPECT_EQ(&kI, thing.ValueOrDie());
}
TEST(StatusOrDeathTest, TestPointerValueNotOk) {
::mediapipe::StatusOr<int*> thing(
::mediapipe::Status(::mediapipe::StatusCode::kCancelled, "cancelled"));
EXPECT_DEATH(thing.ValueOrDie(), "cancelled");
}
TEST(StatusOrDeathTest, TestPointerValueNotOkConst) {
const ::mediapipe::StatusOr<int*> thing(
::mediapipe::Status(::mediapipe::StatusCode::kCancelled, "cancelled"));
EXPECT_DEATH(thing.ValueOrDie(), "cancelled");
}
} // namespace
} // namespace mediapipe
+461
View File
@@ -0,0 +1,461 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// StrongInt<T> is a simple template class mechanism for defining "logical"
// integer-like class types that support almost all of the same functionality
// as native integer types, but which prevents assignment, construction, and
// other operations from other integer-like types. In other words, you cannot
// assign from raw integer types or other StrongInt<> types, nor can you do
// most arithmetic or logical operations. This provides a simple form of
// dimensionality in that you can add two instances of StrongInt<T>, producing
// a StrongInt<T>, but you can not add a StrongInt<T> and a raw T nor can you
// add a StrongInt<T> and a StrongInt<U>. Details on supported operations are
// below.
//
// In addition to type strength, StrongInt provides a way to inject (optional)
// validation of the various operations. This allows you to define StrongInt
// types that check for overflow conditions and react in standard or custom
// ways.
//
// A StrongInt<T> with a NullStrongIntValidator should compile away to a raw T
// in optimized mode. What this means is that the generated assembly for:
//
// int64 foo = 123;
// int64 bar = 456;
// int64 baz = foo + bar;
// constexpr int64 fubar = 789;
//
// ...should be identical to the generated assembly for:
//
// DEFINE_STRONG_INT_TYPE(MyStrongInt, int64);
// MyStrongInt foo(123);
// MyStrongInt bar(456);
// MyStrongInt baz = foo + bar;
// constexpr MyStrongInt fubar(789);
//
// Since the methods are all inline and non-virtual and the class has just
// one data member, the compiler can erase the StrongInt class entirely in its
// code-generation phase. This also means that you can pass StrongInt<T>
// around by value just as you would a raw T.
//
// It is important to note that StrongInt does NOT generate compile time
// warnings or errors for overflows on implicit constant conversions.
//
// Usage:
// StrongInt<TagType, NativeType, ValidatorType = NullStrongIntValidator>
//
// Creates a new StrongInt instance directly.
//
// TagType: The unique type which discriminates this StrongInt<T> from
// other StrongInt<U> types.
// NativeType: The primitive integral type this StrongInt will hold, as
// defined by std::is_integral (see <type_traits>).
// ValidatorType: The type of validation used by this StrongInt type. A
// few pre-built validator types are provided here, but the caller can
// define any custom validator they desire.
//
// Supported operations:
// StrongInt<T> = StrongInt<T>
// !StrongInt<T> => bool
// ~StrongInt<T> => StrongInt<T>
// -StrongInt<T> => StrongInt<T>
// +StrongInt<T> => StrongInt<T>
// ++StrongInt<T> => StrongInt<T>
// StrongInt<T>++ => StrongInt<T>
// --StrongInt<T> => StrongInt<T>
// StrongInt<T>-- => StrongInt<T>
// StrongInt<T> + StrongInt<T> => StrongInt<T>
// StrongInt<T> - StrongInt<T> => StrongInt<T>
// StrongInt<T> * (numeric type) => StrongInt<T>
// StrongInt<T> / (numeric type) => StrongInt<T>
// StrongInt<T> % (numeric type) => StrongInt<T>
// StrongInt<T> << (numeric type) => StrongInt<T>
// StrongInt<T> >> (numeric type) => StrongInt<T>
// StrongInt<T> & StrongInt<T> => StrongInt<T>
// StrongInt<T> | StrongInt<T> => StrongInt<T>
// StrongInt<T> ^ StrongInt<T> => StrongInt<T>
//
// For binary operations, the equivalent op-equal (eg += vs. +) operations are
// also supported. Other operator combinations should cause compile-time
// errors.
//
// Validators:
// NullStrongIntValidator: Do no validation. This should be entirely
// optimized away by the compiler.
#ifndef MEDIAPIPE_DEPS_STRONG_INT_H_
#define MEDIAPIPE_DEPS_STRONG_INT_H_
#include <iosfwd>
#include <limits>
#include <ostream>
#include <type_traits>
#include "absl/base/macros.h"
#include "mediapipe/framework/port/integral_types.h"
#include "mediapipe/framework/port/port.h"
namespace mediapipe {
namespace intops {
// Define the validators which can be plugged-in to make StrongInt resilient to
// things like overflows. This is a do-nothing implementation of the
// compile-time interface.
//
// NOTE: For all validation functions that operate on an existing StrongInt<T>,
// the type argument 'T' *must* be StrongInt<T>::ValueType (the int type being
// strengthened).
struct NullStrongIntValidator {
// Verify initialization of StrongInt<T> from arg, type U.
//
// Note that this templated default implementation has an arbitrary bool
// return value for the sole purpose of conforming to c++11 constexpr.
//
// Custom validator implementations can choose to return void or use a similar
// return value constexpr construct if constexpr initialization is desirable.
//
// The StrongInt class does not care about or use the returned value. Any
// returned value is solely there to allow the constexpr declaration; custom
// validators can only fail / abort when detecting an invalid value.
//
// For example, other than the constexpr behavior, the below 2 custom
// validator implementations are logically equivalent:
//
// template<typename T, typename U>
// static void ValidateInit(U arg) {
// if (arg < 0) LOG(FATAL) << "arg < 0";
// }
//
// template<typename T, typename U>
// static constexpr bool ValidateInit(U arg) {
// return (arg < 0) ? (LOG(FATAL) << "arg < 0", false) : false;
// }
//
// A constexpr ValidateInit implementation has the added advantage that the
// validation can take place (fail) at compile time.
template <typename T, typename U>
static constexpr bool ValidateInit(U arg) {
return true;
}
// Verify -value.
template <typename T>
static void ValidateNegate(T value) { /* do nothing */
}
// Verify ~value;
template <typename T>
static void ValidateBitNot(T value) { /* do nothing */
}
// Verify lhs + rhs.
template <typename T>
static void ValidateAdd(T lhs, T rhs) { /* do nothing */
}
// Verify lhs - rhs.
template <typename T>
static void ValidateSubtract(T lhs, T rhs) { /* do nothing */
}
// Verify lhs * rhs.
template <typename T, typename U>
static void ValidateMultiply(T lhs, U rhs) { /* do nothing */
}
// Verify lhs / rhs.
template <typename T, typename U>
static void ValidateDivide(T lhs, U rhs) { /* do nothing */
}
// Verify lhs % rhs.
template <typename T, typename U>
static void ValidateModulo(T lhs, U rhs) { /* do nothing */
}
// Verify lhs << rhs.
template <typename T>
static void ValidateLeftShift(T lhs, int64 rhs) { /* do nothing */
}
// Verify lhs >> rhs.
template <typename T>
static void ValidateRightShift(T lhs, int64 rhs) { /* do nothing */
}
// Verify lhs & rhs.
template <typename T>
static void ValidateBitAnd(T lhs, T rhs) { /* do nothing */
}
// Verify lhs | rhs.
template <typename T>
static void ValidateBitOr(T lhs, T rhs) { /* do nothing */
}
// Verify lhs ^ rhs.
template <typename T>
static void ValidateBitXor(T lhs, T rhs) { /* do nothing */
}
};
// Holds an integer value (of type NativeType) and behaves as a NativeType by
// exposing assignment, unary, comparison, and arithmetic operators.
//
// This class is NOT thread-safe.
template <typename TagType, typename NativeType,
typename ValidatorType = NullStrongIntValidator>
class StrongInt {
public:
typedef NativeType ValueType;
// Default value initialization.
constexpr StrongInt()
: value_((ValidatorType::template ValidateInit<ValueType>(NativeType()),
NativeType())) {}
// Explicit initialization from another StrongInt type that has an
// implementation of:
//
// ToType StrongIntConvert(FromType source, ToType*);
//
// This uses Argument Dependent Lookup (ADL) to find which function to
// call.
//
// Example: Assume you have two StrongInt types.
//
// DEFINE_STRONG_INT_TYPE(Bytes, int64);
// DEFINE_STRONG_INT_TYPE(Megabytes, int64);
//
// If you want to be able to (explicitly) construct an instance of Bytes from
// an instance of Megabytes, simply define a converter function in the same
// namespace as either Bytes or Megabytes (or both):
//
// Megabytes StrongIntConvert(Bytes arg, Megabytes* /* unused */) {
// return Megabytes((arg >> 20).value());
// };
//
// The second argument is needed to differentiate conversions, and it always
// passed as NULL.
template <typename ArgTagType, typename ArgNativeType,
typename ArgValidatorType>
explicit StrongInt(
StrongInt<ArgTagType, ArgNativeType, ArgValidatorType> arg) {
// We have to pass both the "from" type and the "to" type as args for the
// conversions to be differentiated. The converter can not be a template
// because explicit template call syntax defeats ADL.
StrongInt *dummy = NULL;
StrongInt converted = StrongIntConvert(arg, dummy);
value_ = converted.value();
}
// Explicit initialization from a numeric primitive.
template <typename T, typename = typename std::enable_if<
std::is_convertible<T, ValueType>::value>::type>
explicit constexpr StrongInt(T init_value)
: value_((ValidatorType::template ValidateInit<ValueType>(init_value),
static_cast<ValueType>(init_value))) {}
// Use the default copy constructor, assignment, and destructor.
// Accesses the raw value.
constexpr ValueType value() const { return value_; }
// Accesses the raw value, with cast.
// Primarily for compatibility with int-type.h
template <typename ValType>
constexpr ValType value() const {
return static_cast<ValType>(value_);
}
// Metadata functions.
static ValueType Max() { return std::numeric_limits<ValueType>::max(); }
static ValueType Min() { return std::numeric_limits<ValueType>::min(); }
// Unary operators.
bool operator!() const { return value_ == 0; }
const StrongInt operator+() const { return StrongInt(value_); }
const StrongInt operator-() const {
ValidatorType::template ValidateNegate<ValueType>(value_);
return StrongInt(-value_);
}
const StrongInt operator~() const {
ValidatorType::template ValidateBitNot<ValueType>(value_);
return StrongInt(ValueType(~value_));
}
// Increment and decrement operators.
StrongInt &operator++() { // ++x
ValidatorType::template ValidateAdd<ValueType>(value_, ValueType(1));
++value_;
return *this;
}
const StrongInt operator++(int postfix_flag) { // x++
ValidatorType::template ValidateAdd<ValueType>(value_, ValueType(1));
StrongInt temp(*this);
++value_;
return temp;
}
StrongInt &operator--() { // --x
ValidatorType::template ValidateSubtract<ValueType>(value_, ValueType(1));
--value_;
return *this;
}
const StrongInt operator--(int postfix_flag) { // x--
ValidatorType::template ValidateSubtract<ValueType>(value_, ValueType(1));
StrongInt temp(*this);
--value_;
return temp;
}
// Action-Assignment operators.
StrongInt &operator+=(StrongInt arg) {
ValidatorType::template ValidateAdd<ValueType>(value_, arg.value());
value_ += arg.value();
return *this;
}
StrongInt &operator-=(StrongInt arg) {
ValidatorType::template ValidateSubtract<ValueType>(value_, arg.value());
value_ -= arg.value();
return *this;
}
template <typename ArgType>
StrongInt &operator*=(ArgType arg) {
ValidatorType::template ValidateMultiply<ValueType, ArgType>(value_, arg);
value_ *= arg;
return *this;
}
template <typename ArgType>
StrongInt &operator/=(ArgType arg) {
ValidatorType::template ValidateDivide<ValueType, ArgType>(value_, arg);
value_ /= arg;
return *this;
}
template <typename ArgType>
StrongInt &operator%=(ArgType arg) {
ValidatorType::template ValidateModulo<ValueType, ArgType>(value_, arg);
value_ %= arg;
return *this;
}
StrongInt &operator<<=(int64 arg) { // NOLINT(whitespace/operators)
ValidatorType::template ValidateLeftShift<ValueType>(value_, arg);
value_ <<= arg;
return *this;
}
StrongInt &operator>>=(int64 arg) { // NOLINT(whitespace/operators)
ValidatorType::template ValidateRightShift<ValueType>(value_, arg);
value_ >>= arg;
return *this;
}
StrongInt &operator&=(StrongInt arg) {
ValidatorType::template ValidateBitAnd<ValueType>(value_, arg.value());
value_ &= arg.value();
return *this;
}
StrongInt &operator|=(StrongInt arg) {
ValidatorType::template ValidateBitOr<ValueType>(value_, arg.value());
value_ |= arg.value();
return *this;
}
StrongInt &operator^=(StrongInt arg) {
ValidatorType::template ValidateBitXor<ValueType>(value_, arg.value());
value_ ^= arg.value();
return *this;
}
private:
// The integer value of type ValueType.
ValueType value_;
static_assert(std::is_integral<ValueType>::value,
"invalid integer type for strong int");
};
// Provide the << operator, primarily for logging purposes.
template <typename TagType, typename ValueType, typename ValidatorType>
std::ostream &operator<<(std::ostream &os,
StrongInt<TagType, ValueType, ValidatorType> arg) {
return os << arg.value();
}
// Provide the << operator, primarily for logging purposes. Specialized for int8
// so that an integer and not a character is printed.
template <typename TagType, typename ValidatorType>
std::ostream &operator<<(std::ostream &os,
StrongInt<TagType, int8, ValidatorType> arg) {
return os << static_cast<int>(arg.value());
}
// Provide the << operator, primarily for logging purposes. Specialized for
// uint8 so that an integer and not a character is printed.
template <typename TagType, typename ValidatorType>
std::ostream &operator<<(std::ostream &os,
StrongInt<TagType, uint8, ValidatorType> arg) {
return os << static_cast<unsigned int>(arg.value());
}
// Define operators that take two StrongInt arguments. These operators are
// defined in terms of their op-equal member function cousins.
#define STRONG_INT_VS_STRONG_INT_BINARY_OP(op) \
template <typename TagType, typename ValueType, typename ValidatorType> \
inline StrongInt<TagType, ValueType, ValidatorType> operator op( \
StrongInt<TagType, ValueType, ValidatorType> lhs, \
StrongInt<TagType, ValueType, ValidatorType> rhs) { \
lhs op## = rhs; \
return lhs; \
}
STRONG_INT_VS_STRONG_INT_BINARY_OP(+);
STRONG_INT_VS_STRONG_INT_BINARY_OP(-);
STRONG_INT_VS_STRONG_INT_BINARY_OP(&);
STRONG_INT_VS_STRONG_INT_BINARY_OP(|);
STRONG_INT_VS_STRONG_INT_BINARY_OP(^);
#undef STRONG_INT_VS_STRONG_INT_BINARY_OP
// Define operators that take one StrongInt and one native integer argument.
// These operators are defined in terms of their op-equal member function
// cousins, mostly.
#define STRONG_INT_VS_NUMERIC_BINARY_OP(op) \
template <typename TagType, typename ValueType, typename ValidatorType, \
typename NumType> \
inline StrongInt<TagType, ValueType, ValidatorType> operator op( \
StrongInt<TagType, ValueType, ValidatorType> lhs, NumType rhs) { \
lhs op## = rhs; \
return lhs; \
}
// This is used for commutative operators between one StrongInt and one native
// integer argument. That is a long way of saying "multiplication".
#define NUMERIC_VS_STRONG_INT_BINARY_OP(op) \
template <typename TagType, typename ValueType, typename ValidatorType, \
typename NumType> \
inline StrongInt<TagType, ValueType, ValidatorType> operator op( \
NumType lhs, StrongInt<TagType, ValueType, ValidatorType> rhs) { \
rhs op## = lhs; \
return rhs; \
}
STRONG_INT_VS_NUMERIC_BINARY_OP(*);
NUMERIC_VS_STRONG_INT_BINARY_OP(*);
STRONG_INT_VS_NUMERIC_BINARY_OP(/);
STRONG_INT_VS_NUMERIC_BINARY_OP(%);
STRONG_INT_VS_NUMERIC_BINARY_OP(<<); // NOLINT(whitespace/operators)
STRONG_INT_VS_NUMERIC_BINARY_OP(>>); // NOLINT(whitespace/operators)
#undef STRONG_INT_VS_NUMERIC_BINARY_OP
#undef NUMERIC_VS_STRONG_INT_BINARY_OP
// Define comparison operators. We allow all comparison operators.
#define STRONG_INT_COMPARISON_OP(op) \
template <typename TagType, typename ValueType, typename ValidatorType> \
inline bool operator op(StrongInt<TagType, ValueType, ValidatorType> lhs, \
StrongInt<TagType, ValueType, ValidatorType> rhs) { \
return lhs.value() op rhs.value(); \
}
STRONG_INT_COMPARISON_OP(==); // NOLINT(whitespace/operators)
STRONG_INT_COMPARISON_OP(!=); // NOLINT(whitespace/operators)
STRONG_INT_COMPARISON_OP(<); // NOLINT(whitespace/operators)
STRONG_INT_COMPARISON_OP(<=); // NOLINT(whitespace/operators)
STRONG_INT_COMPARISON_OP(>); // NOLINT(whitespace/operators)
STRONG_INT_COMPARISON_OP(>=); // NOLINT(whitespace/operators)
#undef STRONG_INT_COMPARISON_OP
} // namespace intops
} // namespace mediapipe
#endif // MEDIAPIPE_DEPS_STRONG_INT_H_
+70
View File
@@ -0,0 +1,70 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_DEPS_THREAD_OPTIONS_H_
#define MEDIAPIPE_DEPS_THREAD_OPTIONS_H_
#include <stddef.h>
#include <set>
#include <string>
namespace mediapipe {
// Options to configure a thread. Default values are listed in
// the field descriptions.
class ThreadOptions {
public:
ThreadOptions() : stack_size_(0), nice_priority_level_(0) {}
// Set the thread stack size (in bytes). Passing stack_size==0 resets
// the stack size to the default value for the system. The system default
// is also the default for this class.
ThreadOptions& set_stack_size(size_t stack_size) {
stack_size_ = stack_size;
return *this;
}
ThreadOptions& set_nice_priority_level(int nice_priority_level) {
nice_priority_level_ = nice_priority_level;
return *this;
}
ThreadOptions& set_cpu_set(const std::set<int>& cpu_set) {
cpu_set_ = cpu_set;
return *this;
}
ThreadOptions& set_name_prefix(const std::string& name_prefix) {
name_prefix_ = name_prefix;
return *this;
}
size_t stack_size() const { return stack_size_; }
int nice_priority_level() const { return nice_priority_level_; }
const std::set<int>& cpu_set() const { return cpu_set_; }
std::string name_prefix() const { return name_prefix_; }
private:
size_t stack_size_; // Size of thread stack
int nice_priority_level_; // Nice priority level of the workers
std::set<int> cpu_set_; // CPU set for affinity setting
std::string name_prefix_; // Name of the thread
};
} // namespace mediapipe
#endif // MEDIAPIPE_DEPS_THREAD_OPTIONS_H_
+193
View File
@@ -0,0 +1,193 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/deps/threadpool.h"
#include <errno.h>
#include <pthread.h>
#include <string.h>
#include <sys/syscall.h>
#include <unistd.h>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "mediapipe/framework/port/logging.h"
namespace mediapipe {
class ThreadPool::WorkerThread {
public:
// Creates and starts a thread that runs pool->RunWorker().
WorkerThread(ThreadPool* pool, const std::string& name_prefix);
// REQUIRES: Join() must have been called.
~WorkerThread();
// Joins with the running thread.
void Join();
private:
static void* ThreadBody(void* arg);
ThreadPool* pool_;
std::string name_prefix_;
pthread_t thread_;
};
ThreadPool::WorkerThread::WorkerThread(ThreadPool* pool,
const std::string& name_prefix)
: pool_(pool), name_prefix_(name_prefix) {
pthread_create(&thread_, nullptr, ThreadBody, this);
}
ThreadPool::WorkerThread::~WorkerThread() {}
void ThreadPool::WorkerThread::Join() { pthread_join(thread_, nullptr); }
void* ThreadPool::WorkerThread::ThreadBody(void* arg) {
auto thread = reinterpret_cast<WorkerThread*>(arg);
int nice_priority_level =
thread->pool_->thread_options().nice_priority_level();
const std::set<int> selected_cpus = thread->pool_->thread_options().cpu_set();
const std::string name =
internal::CreateThreadName(thread->name_prefix_, syscall(SYS_gettid));
#if defined(__linux__)
if (nice_priority_level != 0) {
if (nice(nice_priority_level) != -1 || errno == 0) {
VLOG(1) << "Changed the nice priority level by " << nice_priority_level;
} else {
LOG(ERROR) << "Error : " << strerror(errno) << std::endl
<< "Could not change the nice priority level by "
<< nice_priority_level;
}
}
if (!selected_cpus.empty()) {
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
for (const int cpu : selected_cpus) {
CPU_SET(cpu, &cpu_set);
}
if (sched_setaffinity(syscall(SYS_gettid), sizeof(cpu_set_t), &cpu_set) !=
-1 ||
errno == 0) {
VLOG(1) << "Pinned the thread pool executor to processor "
<< absl::StrJoin(selected_cpus, ", processor ") << ".";
} else {
LOG(ERROR) << "Error : " << strerror(errno) << std::endl
<< "Failed to set processor affinity. Ignore processor "
"affinity setting for now.";
}
}
int error = pthread_setname_np(pthread_self(), name.c_str());
if (error != 0) {
LOG(ERROR) << "Error : " << strerror(error) << std::endl
<< "Failed to set name for thread: " << name;
}
#else
if (nice_priority_level != 0 || !selected_cpus.empty()) {
LOG(ERROR) << "Thread priority and processor affinity feature aren't "
"supported on the current platform.";
}
int error = pthread_setname_np(name.c_str());
if (error != 0) {
LOG(ERROR) << "Error : " << strerror(error) << std::endl
<< "Failed to set name for thread: " << name;
}
#endif
thread->pool_->RunWorker();
return nullptr;
}
ThreadPool::ThreadPool(int num_threads) {
num_threads_ = (num_threads == 0) ? 1 : num_threads;
}
ThreadPool::ThreadPool(const std::string& name_prefix, int num_threads)
: name_prefix_(name_prefix) {
num_threads_ = (num_threads == 0) ? 1 : num_threads;
}
ThreadPool::ThreadPool(const ThreadOptions& thread_options,
const std::string& name_prefix, int num_threads)
: name_prefix_(name_prefix), thread_options_(thread_options) {
num_threads_ = (num_threads == 0) ? 1 : num_threads;
}
ThreadPool::~ThreadPool() {
mutex_.Lock();
stopped_ = true;
condition_.SignalAll();
mutex_.Unlock();
for (int i = 0; i < threads_.size(); ++i) {
threads_[i]->Join();
delete threads_[i];
}
threads_.clear();
}
void ThreadPool::StartWorkers() {
for (int i = 0; i < num_threads_; ++i) {
threads_.push_back(new WorkerThread(this, name_prefix_));
}
}
void ThreadPool::Schedule(std::function<void()> callback) {
mutex_.Lock();
tasks_.push_back(std::move(callback));
condition_.Signal();
mutex_.Unlock();
}
int ThreadPool::num_threads() const { return num_threads_; }
void ThreadPool::RunWorker() {
mutex_.Lock();
while (true) {
if (!tasks_.empty()) {
std::function<void()> task = std::move(tasks_.front());
tasks_.pop_front();
mutex_.Unlock();
task();
mutex_.Lock();
} else {
if (stopped_) {
break;
} else {
condition_.Wait(&mutex_);
}
}
}
mutex_.Unlock();
}
const ThreadOptions& ThreadPool::thread_options() const {
return thread_options_;
}
namespace internal {
std::string CreateThreadName(const std::string& prefix, int thread_id) {
std::string name = absl::StrCat(prefix, "/", thread_id);
// 16 is the limit allowed by `pthread_setname_np`, including
// the terminating null byte ('\0')
constexpr size_t kMaxThreadNameLength = 15;
name.resize(std::min(name.length(), kMaxThreadNameLength));
return name;
}
} // namespace internal
} // namespace mediapipe
+117
View File
@@ -0,0 +1,117 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_DEPS_THREADPOOL_H_
#define MEDIAPIPE_DEPS_THREADPOOL_H_
#include <deque>
#include <functional>
#include <string>
#include <vector>
#include "absl/synchronization/mutex.h"
#include "mediapipe/framework/deps/thread_options.h"
namespace mediapipe {
// A thread pool consists of a set of threads that sit around waiting
// for callbacks to appear on a queue. When that happens, one of the
// threads pulls a callback off the queue and runs it.
//
// The thread pool is shut down when the pool is destroyed.
//
// Sample usage:
//
// {
// ThreadPool pool("testpool", num_workers);
// pool.StartWorkers();
// for (int i = 0; i < N; ++i) {
// pool.Schedule([i]() { DoWork(i); });
// }
// }
//
class ThreadPool {
public:
// Create a thread pool that provides a concurrency of "num_threads"
// threads. I.e., if "num_threads" items are added, they are all
// guaranteed to run concurrently without excessive delay.
// It has an effectively infinite maximum queue length.
// If num_threads is 1, the callbacks are run in FIFO order.
explicit ThreadPool(int num_threads);
ThreadPool(const ThreadPool&) = delete;
ThreadPool& operator=(const ThreadPool&) = delete;
// Like the ThreadPool(int num_threads) constructor, except that
// it also associates "name_prefix" with each of the threads
// in the thread pool.
ThreadPool(const std::string& name_prefix, int num_threads);
// Create a thread pool that creates and can use up to "num_threads"
// threads. Any standard thread options, such as stack size, should
// be passed via "thread_options". "name_prefix" specifies the
// thread name prefix.
ThreadPool(const ThreadOptions& thread_options,
const std::string& name_prefix, int num_threads);
// Waits for closures (if any) to complete. May be called without
// having called StartWorkers().
~ThreadPool();
// REQUIRES: StartWorkers has not been called
// Actually start the worker threads.
void StartWorkers();
// REQUIRES: StartWorkers has been called
// Add specified callback to queue of pending callbacks. Eventually a
// thread will pull this callback off the queue and execute it.
void Schedule(std::function<void()> callback);
// Provided for debugging and testing only.
int num_threads() const;
// Standard thread options. Use this accessor to get them.
const ThreadOptions& thread_options() const;
private:
class WorkerThread;
void RunWorker();
std::string name_prefix_;
std::vector<WorkerThread*> threads_;
int num_threads_;
absl::Mutex mutex_;
absl::CondVar condition_;
bool stopped_ GUARDED_BY(mutex_) = false;
std::deque<std::function<void()>> tasks_ GUARDED_BY(mutex_);
ThreadOptions thread_options_;
};
namespace internal {
// Creates name for thread in a thread pool based on provided prefix and
// thread id. Length of the resulting name is guaranteed to be less or equal
// to 15. Name or thread id can be truncated to achieve that, see truncation
// samples below:
// name_prefix, 1234 -> name_prefix/123
// name_prefix, 1234567 -> name_prefix/123
// name_prefix_long, 1234 -> name_prefix_lon
std::string CreateThreadName(const std::string& prefix, int thread_id);
} // namespace internal
} // namespace mediapipe
#endif // MEDIAPIPE_DEPS_THREADPOOL_H_
+118
View File
@@ -0,0 +1,118 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/deps/threadpool.h"
#include <set>
#include "absl/synchronization/mutex.h"
#include "mediapipe/framework/port/gtest.h"
namespace mediapipe {
TEST(ThreadPoolTest, DestroyWithoutStart) {
ThreadPool thread_pool("testpool", 10);
}
TEST(ThreadPoolTest, EmptyThread) {
ThreadPool thread_pool("testpool", 0);
ASSERT_EQ(1, thread_pool.num_threads());
thread_pool.StartWorkers();
}
TEST(ThreadPoolTest, SingleThread) {
absl::Mutex mu;
int n = 100;
{
ThreadPool thread_pool("testpool", 1);
ASSERT_EQ(1, thread_pool.num_threads());
thread_pool.StartWorkers();
for (int i = 0; i < 100; ++i) {
thread_pool.Schedule([&n, &mu]() mutable {
absl::MutexLock l(&mu);
--n;
});
}
}
EXPECT_EQ(0, n);
}
TEST(ThreadPoolTest, MultiThreads) {
absl::Mutex mu;
int n = 100;
{
ThreadPool thread_pool("testpool", 10);
ASSERT_EQ(10, thread_pool.num_threads());
thread_pool.StartWorkers();
for (int i = 0; i < 100; ++i) {
thread_pool.Schedule([&n, &mu]() mutable {
absl::MutexLock l(&mu);
--n;
});
}
}
EXPECT_EQ(0, n);
}
TEST(ThreadPoolTest, CreateWithThreadOptions) {
ThreadPool thread_pool(ThreadOptions(), "testpool", 10);
ASSERT_EQ(10, thread_pool.num_threads());
thread_pool.StartWorkers();
}
TEST(ThreadPoolTest, CreateWithThreadPriority) {
ThreadOptions thread_options = ThreadOptions().set_nice_priority_level(-10);
ThreadPool thread_pool(thread_options, "testpool", 10);
ASSERT_EQ(10, thread_pool.num_threads());
ASSERT_EQ(-10, thread_pool.thread_options().nice_priority_level());
thread_pool.StartWorkers();
}
TEST(ThreadPoolTest, CreateWithCPUAffinity) {
ThreadOptions thread_options = ThreadOptions().set_cpu_set({0});
ThreadPool thread_pool(thread_options, "testpool", 10);
ASSERT_EQ(10, thread_pool.num_threads());
ASSERT_EQ(1, thread_pool.thread_options().cpu_set().size());
thread_pool.StartWorkers();
}
TEST(ThreadPoolTest, CreateThreadName) {
ASSERT_EQ("name_prefix/123", internal::CreateThreadName("name_prefix", 1234));
ASSERT_EQ("name_prefix/123",
internal::CreateThreadName("name_prefix", 12345));
ASSERT_EQ("name_prefix/123",
internal::CreateThreadName("name_prefix", 123456));
ASSERT_EQ("name_prefix/123",
internal::CreateThreadName("name_prefix", 1234567));
ASSERT_EQ("name_prefix/123",
internal::CreateThreadName("name_prefix", 1234567891));
ASSERT_EQ("name_prefix_/12",
internal::CreateThreadName("name_prefix_", 1234));
ASSERT_EQ("name_pre/123456",
internal::CreateThreadName("name_pre", 1234567891));
ASSERT_EQ("n/1", internal::CreateThreadName("n", 1));
ASSERT_EQ("name_p/12345678",
internal::CreateThreadName("name_p", 1234567891));
ASSERT_EQ("/1", internal::CreateThreadName("", 1));
ASSERT_EQ("name_prefix_lon",
internal::CreateThreadName("name_prefix_long", 1234));
ASSERT_EQ("name_prefix_lon",
internal::CreateThreadName("name_prefix_lon", 1234));
}
} // namespace mediapipe
@@ -0,0 +1,153 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/deps/topologicalsorter.h"
#include <algorithm>
#include "mediapipe/framework/port/logging.h"
namespace mediapipe {
TopologicalSorter::TopologicalSorter(int num_nodes) : num_nodes_(num_nodes) {
CHECK_GE(num_nodes_, 0);
adjacency_lists_.resize(num_nodes_);
}
void TopologicalSorter::AddEdge(int from, int to) {
CHECK(!traversal_started_ && from < num_nodes_ && to < num_nodes_ &&
from >= 0 && to >= 0);
adjacency_lists_[from].push_back(to);
}
bool TopologicalSorter::GetNext(int* node_index, bool* cyclic,
std::vector<int>* output_cycle_nodes) {
if (!traversal_started_) {
// Iterates over all adjacency lists, and fills the indegree_ vector.
indegree_.assign(num_nodes_, 0);
for (int from = 0; from < num_nodes_; ++from) {
std::vector<int>& adjacency_list = adjacency_lists_[from];
// Eliminates duplicate edges.
std::sort(adjacency_list.begin(), adjacency_list.end());
adjacency_list.erase(
std::unique(adjacency_list.begin(), adjacency_list.end()),
adjacency_list.end());
for (int to : adjacency_list) {
++indegree_[to];
}
}
// Fills the nodes_with_zero_indegree_ vector.
for (int i = 0; i < num_nodes_; ++i) {
if (indegree_[i] == 0) {
nodes_with_zero_indegree_.push(i);
}
}
num_nodes_left_ = num_nodes_;
traversal_started_ = true;
}
*cyclic = false;
if (num_nodes_left_ == 0) {
// Done the traversal.
return false;
}
if (nodes_with_zero_indegree_.empty()) {
*cyclic = true;
FindCycle(output_cycle_nodes);
return false;
}
// Gets the least node.
--num_nodes_left_;
*node_index = nodes_with_zero_indegree_.top();
nodes_with_zero_indegree_.pop();
// Swap out the adjacency list, since we won't need it afterwards,
// to decrease memory usage.
std::vector<int> adjacency_list;
adjacency_list.swap(adjacency_lists_[*node_index]);
// Updates the indegree_ vector and nodes_with_zero_indegree_ queue.
for (int i = 0; i < adjacency_list.size(); ++i) {
if (--indegree_[adjacency_list[i]] == 0) {
nodes_with_zero_indegree_.push(adjacency_list[i]);
}
}
return true;
}
void TopologicalSorter::FindCycle(std::vector<int>* cycle_nodes) {
cycle_nodes->clear();
// To find a cycle, we start a DFS from each yet-unvisited node and
// try to find a cycle, if we don't find it then we know for sure that
// no cycle is reachable from any of the explored nodes (so, we don't
// explore them in later DFSs).
std::vector<bool> no_cycle_reachable_from(num_nodes_, false);
// The DFS stack will contain a chain of nodes, from the root of the
// DFS to the current leaf.
struct DfsState {
int node;
// Points at the first child node that we did *not* yet look at.
int adjacency_list_index;
explicit DfsState(int _node) : node(_node), adjacency_list_index(0) {}
};
std::vector<DfsState> dfs_stack;
std::vector<bool> in_cur_stack(num_nodes_, false);
for (int start_node = 0; start_node < num_nodes_; ++start_node) {
if (no_cycle_reachable_from[start_node]) {
continue;
}
// Starts the DFS.
dfs_stack.push_back(DfsState(start_node));
in_cur_stack[start_node] = true;
while (!dfs_stack.empty()) {
DfsState* cur_state = &dfs_stack.back();
if (cur_state->adjacency_list_index >=
adjacency_lists_[cur_state->node].size()) {
no_cycle_reachable_from[cur_state->node] = true;
in_cur_stack[cur_state->node] = false;
dfs_stack.pop_back();
continue;
}
// Looks at the current child, and increases the current state's
// adjacency_list_index.
const int child =
adjacency_lists_[cur_state->node][cur_state->adjacency_list_index];
++(cur_state->adjacency_list_index);
if (no_cycle_reachable_from[child]) {
continue;
}
if (in_cur_stack[child]) {
// We detected a cycle! Fills it and return.
for (;;) {
cycle_nodes->push_back(dfs_stack.back().node);
if (dfs_stack.back().node == child) {
std::reverse(cycle_nodes->begin(), cycle_nodes->end());
return;
}
dfs_stack.pop_back();
}
}
// Pushs the child onto the stack.
dfs_stack.push_back(DfsState(child));
in_cur_stack[child] = true;
}
}
// If we're here, then all the DFS stopped, and they never encountered
// a cycle (otherwise, we would have returned). Just exit; the output
// vector has been cleared already.
}
} // namespace mediapipe
@@ -0,0 +1,83 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_DEPS_TOPOLOGICALSORTER_H_
#define MEDIAPIPE_DEPS_TOPOLOGICALSORTER_H_
#include <functional>
#include <queue>
#include <vector>
namespace mediapipe {
// TopologicalSorter provides topologically sorted traversal of the nodes of a
// directed acyclic graph (DAG) with up to INT_MAX nodes. The sorter requires
// that all nodes and edges be added before traversing the nodes, otherwise it
// will die with a fatal error. If a cycle is detected during the traversal,
// the sorter will stop the traversal, and set the cycle_nodes vector.
//
// Sample usage:
// TopologicalSorter sorter(num_nodes);
// sorter.AddEdge(ObjToIndex(obj_a), ObjToIndex(obj_b));
// sorter.AddEdge(ObjToIndex(obj_a), ObjToIndex(obj_c));
// ...
// sorter.AddEdge(ObjToIndex(obj_b), ObjToIndex(obj_c));
// int idx;
// bool cyclic = false;
// std::vector<int> cycle_nodes;
// while (sorter.GetNext(&idx, &cyclic, &cycle_nodes)) {
// if (cyclic) {
// PrintCycleNodes(cycle_nodes);
// } else {
// LOG(INFO) << idx;
// }
// }
class TopologicalSorter {
public:
explicit TopologicalSorter(int num_nodes);
TopologicalSorter(const TopologicalSorter&) = delete;
TopologicalSorter& operator=(const TopologicalSorter&) = delete;
// Adds a directed edge with the given endpoints to the graph.
void AddEdge(int from, int to);
// Visits the least node in topological order over the current set of
// nodes and edges, and marks that node as visited.
// The repeated calls to GetNext() will visit all nodes in order. Writes the
// newly visited node into *node_index and returns true with *cyclic set to
// false (assuming the graph has not yet been discovered to be cyclic).
// Returns false if all nodes have been visited, or if the graph is
// discovered to be cyclic, in which case *cyclic is also set to true.
bool GetNext(int* node_index, bool* cyclic,
std::vector<int>* output_cycle_nodes);
private:
// Finds the cycle.
void FindCycle(std::vector<int>* cycle_nodes);
const int num_nodes_;
// Outoging adjacency lists.
std::vector<std::vector<int>> adjacency_lists_;
// If true, no more AddEdge() can be called.
bool traversal_started_ = false;
int num_nodes_left_;
std::priority_queue<int, std::vector<int>, std::greater<int>>
nodes_with_zero_indegree_;
std::vector<int> indegree_;
};
} // namespace mediapipe
#endif // MEDIAPIPE_DEPS_TOPOLOGICALSORTER_H_
@@ -0,0 +1,110 @@
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/deps/topologicalsorter.h"
#include "mediapipe/framework/port/gtest.h"
namespace mediapipe {
TEST(TopologicalSorterTest, NoConnection) {
TopologicalSorter sorter(3);
std::vector<int> expected_result({0, 1, 2});
int visited = 0;
int node_index;
bool cyclic;
std::vector<int> cycle_nodes;
while (sorter.GetNext(&node_index, &cyclic, &cycle_nodes)) {
EXPECT_EQ(expected_result[visited], node_index);
++visited;
}
ASSERT_FALSE(cyclic);
EXPECT_EQ(3, visited);
}
TEST(TopologicalSorterTest, SimpleDAG) {
TopologicalSorter sorter(5);
sorter.AddEdge(4, 0);
sorter.AddEdge(4, 1);
sorter.AddEdge(4, 2);
sorter.AddEdge(0, 3);
sorter.AddEdge(1, 3);
sorter.AddEdge(3, 2);
std::vector<int> expected_result({4, 0, 1, 3, 2});
int visited = 0;
int node_index;
bool cyclic;
std::vector<int> cycle_nodes;
while (sorter.GetNext(&node_index, &cyclic, &cycle_nodes)) {
EXPECT_EQ(expected_result[visited], node_index);
++visited;
}
ASSERT_FALSE(cyclic);
EXPECT_EQ(5, visited);
}
TEST(TopologicalSorterTest, DuplicatedEdges) {
TopologicalSorter sorter(5);
sorter.AddEdge(3, 2);
sorter.AddEdge(4, 0);
sorter.AddEdge(4, 2);
sorter.AddEdge(4, 1);
sorter.AddEdge(3, 2);
sorter.AddEdge(4, 2);
sorter.AddEdge(1, 3);
sorter.AddEdge(0, 3);
sorter.AddEdge(1, 3);
sorter.AddEdge(3, 2);
std::vector<int> expected_result({4, 0, 1, 3, 2});
int visited = 0;
int node_index;
bool cyclic;
std::vector<int> cycle_nodes;
while (sorter.GetNext(&node_index, &cyclic, &cycle_nodes)) {
EXPECT_EQ(expected_result[visited], node_index);
++visited;
}
ASSERT_FALSE(cyclic);
EXPECT_EQ(5, visited);
}
TEST(TopologicalSorterTest, Cycle) {
// Cycle: 1->3->2->1
TopologicalSorter sorter(5);
sorter.AddEdge(4, 0);
sorter.AddEdge(4, 1);
sorter.AddEdge(4, 2);
sorter.AddEdge(0, 3);
sorter.AddEdge(1, 3);
sorter.AddEdge(3, 2);
sorter.AddEdge(2, 1);
int node_index;
bool cyclic;
std::vector<int> cycle_nodes;
while (sorter.GetNext(&node_index, &cyclic, &cycle_nodes)) {
}
EXPECT_TRUE(cyclic);
std::vector<int> expected_cycle({1, 3, 2});
ASSERT_EQ(3, cycle_nodes.size());
for (int i = 0; i < 3; ++i) {
EXPECT_EQ(expected_cycle[i], cycle_nodes[i]);
}
}
} // namespace mediapipe

Some files were not shown because too many files have changed in this diff Show More