Project import generated by Copybara.
PiperOrigin-RevId: 253489161
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
Reference in New Issue
Block a user