C++'ta Yapı Grafikleri

C++ grafik oluşturucu aşağıdakiler için güçlü bir araçtır:

  • Karmaşık grafikler oluşturma
  • Grafikleri parametreleştirme (ör. InferenceCalculator üzerinde bir temsilci belirleme, grafiğin bölümlerini etkinleştirme/devre dışı bırakma)
  • Grafikleri tekilleştirme (ör. pbtxt'de CPU ve GPU'ya ayrılmış grafikler yerine, gerekli grafikleri oluşturan ve mümkün olduğunca çok paylaşan tek bir kod kullanabilirsiniz)
  • İsteğe bağlı grafik girişlerini/çıkışlarını destekleme
  • Grafikleri platforma göre özelleştirme

Temel Kullanım

C++ grafik oluşturucunun basit bir grafik için nasıl kullanılabileceğini görelim:

# Graph inputs.
input_stream: "input_tensors"
input_side_packet: "model"

# Graph outputs.
output_stream: "output_tensors"

node {
  calculator: "InferenceCalculator"
  input_stream: "TENSORS:input_tensors"
  input_side_packet: "MODEL:model"
  output_stream: "TENSORS:output_tensors"
  options: {
    [drishti.InferenceCalculatorOptions.ext] {
      # Requesting GPU delegate.
      delegate { gpu {} }
    }
  }
}

Yukarıdaki CalculatorGraphConfig öğesini derleme işlevi şöyle görünebilir:

CalculatorGraphConfig BuildGraph() {
  Graph graph;

  // Graph inputs.
  Stream<std::vector<Tensor>> input_tensors =
      graph.In(0).SetName("input_tensors").Cast<std::vector<Tensor>>();
  SidePacket<TfLiteModelPtr> model =
      graph.SideIn(0).SetName("model").Cast<TfLiteModelPtr>();

  auto& inference_node = graph.AddNode("InferenceCalculator");
  auto& inference_opts =
      inference_node.GetOptions<InferenceCalculatorOptions>();
  // Requesting GPU delegate.
  inference_opts.mutable_delegate()->mutable_gpu();
  input_tensors.ConnectTo(inference_node.In("TENSORS"));
  model.ConnectTo(inference_node.SideIn("MODEL"));
  Stream<std::vector<Tensor>> output_tensors =
      inference_node.Out("TENSORS").Cast<std::vector<Tensor>>();

  // Graph outputs.
  output_tensors.SetName("output_tensors").ConnectTo(graph.Out(0));

  // Get `CalculatorGraphConfig` to pass it into `CalculatorGraph`
  return graph.GetConfig();
}

Kısa özet:

  • Grafik girişlerini Stream/SidePacket olarak almak için Graph::In/SideIn kullanın
  • Düğüm çıkışlarını Stream/SidePacket olarak almak için Node::Out/SideOut kullanın
  • Akışları ve yan paketleri düğüm girişlerine (Node::In/SideIn) ve grafik çıkışlarına (Graph::Out/SideOut) bağlamak için Stream/SidePacket::ConnectTo hizmetini kullanın
    • ConnectTo işlevinin yerine kullanabileceğiniz bir "kısayol" operatörü >> vardır (ör. x >> node.In("IN")).
  • Stream/SidePacket::Cast, AnyType öğesinin (ör. Stream<AnyType> in = graph.In(0);) akışını veya yan paketini belirli bir türe yayınlamak için kullanılır
    • AnyType yerine gerçek türleri kullandığınızda, grafik oluşturucu özelliklerinden yararlanmak ve grafiklerinizin okunabilirliğini iyileştirmek için daha iyi bir yönteme sahip olursunuz.

Gelişmiş Kullanım

Yardımcı İşlevler

Okunabilirlik ve kodun yeniden kullanımı için çıkarım oluşturma kodunu özel bir yardımcı program işlevine çıkaralım:

// Updates graph to run inference.
Stream<std::vector<Tensor>> RunInference(
    Stream<std::vector<Tensor>> tensors, SidePacket<TfLiteModelPtr> model,
    const InferenceCalculatorOptions::Delegate& delegate, Graph& graph) {
  auto& inference_node = graph.AddNode("InferenceCalculator");
  auto& inference_opts =
      inference_node.GetOptions<InferenceCalculatorOptions>();
  *inference_opts.mutable_delegate() = delegate;
  tensors.ConnectTo(inference_node.In("TENSORS"));
  model.ConnectTo(inference_node.SideIn("MODEL"));
  return inference_node.Out("TENSORS").Cast<std::vector<Tensor>>();
}

CalculatorGraphConfig BuildGraph() {
  Graph graph;

  // Graph inputs.
  Stream<std::vector<Tensor>> input_tensors =
      graph.In(0).SetName("input_tensors").Cast<std::vector<Tensor>>();
  SidePacket<TfLiteModelPtr> model =
      graph.SideIn(0).SetName("model").Cast<TfLiteModelPtr>();

  InferenceCalculatorOptions::Delegate delegate;
  delegate.mutable_gpu();
  Stream<std::vector<Tensor>> output_tensors =
      RunInference(input_tensors, model, delegate, graph);

  // Graph outputs.
  output_tensors.SetName("output_tensors").ConnectTo(graph.Out(0));

  return graph.GetConfig();
}

Sonuç olarak RunInference, giriş/çıkışları ve türlerini belirten net bir arayüz sağlar.

Kolayca yeniden kullanılabilir. Örneğin, fazladan bir model çıkarımı çalıştırmak isterseniz yalnızca birkaç satırdan ibarettir:

  // Run first inference.
  Stream<std::vector<Tensor>> output_tensors =
      RunInference(input_tensors, model, delegate, graph);
  // Run second inference on the output of the first one.
  Stream<std::vector<Tensor>> extra_output_tensors =
      RunInference(output_tensors, extra_model, delegate, graph);

Ayrıca adları ve etiketleri (InferenceCalculator, TENSORS, MODEL) kopyalamanız veya özel sabit değerler eklemeniz gerekmez. Bu ayrıntılar RunInference işlevi için yerelleştirilir.

Yardımcı Sınıflar

Ayrıca, kesinlikle fonksiyonlarla ilgili değildir. Bazı durumlarda, grafik oluşturma kodunuzu daha okunabilir ve hataya daha az açık hale getirmenize yardımcı olabilecek yardımcı program sınıflarını kullanmak da yararlıdır.

MediaPipe, girdilerini geçiren PassThroughCalculator hesaplayıcı sunar:

input_stream: "float_value"
input_stream: "int_value"
input_stream: "bool_value"

output_stream: "passed_float_value"
output_stream: "passed_int_value"
output_stream: "passed_bool_value"

node {
  calculator: "PassThroughCalculator"
  input_stream: "float_value"
  input_stream: "int_value"
  input_stream: "bool_value"
  # The order must be the same as for inputs (or you can use explicit indexes)
  output_stream: "passed_float_value"
  output_stream: "passed_int_value"
  output_stream: "passed_bool_value"
}

Yukarıdaki grafiği oluşturmak için basit C++ yapı kodunu inceleyelim:

CalculatorGraphConfig BuildGraph() {
  Graph graph;

  // Graph inputs.
  Stream<float> float_value = graph.In(0).SetName("float_value").Cast<float>();
  Stream<int> int_value = graph.In(1).SetName("int_value").Cast<int>();
  Stream<bool> bool_value = graph.In(2).SetName("bool_value").Cast<bool>();

  auto& pass_node = graph.AddNode("PassThroughCalculator");
  float_value.ConnectTo(pass_node.In("")[0]);
  int_value.ConnectTo(pass_node.In("")[1]);
  bool_value.ConnectTo(pass_node.In("")[2]);
  Stream<float> passed_float_value = pass_node.Out("")[0].Cast<float>();
  Stream<int> passed_int_value = pass_node.Out("")[1].Cast<int>();
  Stream<bool> passed_bool_value = pass_node.Out("")[2].Cast<bool>();

  // Graph outputs.
  passed_float_value.SetName("passed_float_value").ConnectTo(graph.Out(0));
  passed_int_value.SetName("passed_int_value").ConnectTo(graph.Out(1));
  passed_bool_value.SetName("passed_bool_value").ConnectTo(graph.Out(2));

  // Get `CalculatorGraphConfig` to pass it into `CalculatorGraph`
  return graph.GetConfig();
}

pbtxt gösterimi hataya açık olabilir (geçecek çok sayıda giriş olduğunda), C++ kodu daha da kötü görünür: tekrar eden boş etiketler ve Cast çağrıları. Şimdi, PassThroughNodeBuilder özelliğini kullanıma sunarak nasıl daha iyi hale getirebileceğimize göz atalım:

class PassThroughNodeBuilder {
 public:
  explicit PassThroughNodeBuilder(Graph& graph)
      : node_(graph.AddNode("PassThroughCalculator")) {}

  template <typename T>
  Stream<T> PassThrough(Stream<T> stream) {
    stream.ConnectTo(node_.In(index_));
    return node_.Out(index_++).Cast<T>();
  }

 private:
  int index_ = 0;
  GenericNode& node_;
};

Şimdi de grafik oluşturma kodu şöyle görünebilir:

CalculatorGraphConfig BuildGraph() {
  Graph graph;

  // Graph inputs.
  Stream<float> float_value = graph.In(0).SetName("float_value").Cast<float>();
  Stream<int> int_value = graph.In(1).SetName("int_value").Cast<int>();
  Stream<bool> bool_value = graph.In(2).SetName("bool_value").Cast<bool>();

  PassThroughNodeBuilder pass_node_builder(graph);
  Stream<float> passed_float_value = pass_node_builder.PassThrough(float_value);
  Stream<int> passed_int_value = pass_node_builder.PassThrough(int_value);
  Stream<bool> passed_bool_value = pass_node_builder.PassThrough(bool_value);

  // Graph outputs.
  passed_float_value.SetName("passed_float_value").ConnectTo(graph.Out(0));
  passed_int_value.SetName("passed_int_value").ConnectTo(graph.Out(1));
  passed_bool_value.SetName("passed_bool_value").ConnectTo(graph.Out(2));

  // Get `CalculatorGraphConfig` to pass it into `CalculatorGraph`
  return graph.GetConfig();
}

Artık yapı kodunda yanlış sıralama veya dizine ekleme yapamaz ve PassThrough girişinden Cast türünü tahmin ederek bazı yazmalardan kurtulamazsınız.

Yapılması ve Yapılmaması Gerekenler

Mümkünse grafik girişlerini en başta tanımlayın

Aşağıdaki kodda:

  • Grafikte kaç girdiniz olduğunu tahmin etmek zor olabilir.
  • Genel olarak hataya açık olabilir ve ileride bakımı zor olabilir (ör. doğru bir dizin mi? adı mı? Bazı girişler kaldırılırsa veya isteğe bağlı yapılırsa ne olur?)
  • Diğer grafikler farklı girdilere sahip olabileceğinden RunSomething yeniden kullanımı sınırlıdır

YAPILMAYACAKLAR. Bozuk kod örneği.

Stream<D> RunSomething(Stream<A> a, Stream<B> b, Graph& graph) {
  Stream<C> c = graph.In(2).SetName("c").Cast<C>();  // Bad.
  // ...
}

CalculatorGraphConfig BuildGraph() {
  Graph graph;

  Stream<A> a = graph.In(0).SetName("a").Cast<A>();
  // 10/100/N lines of code.
  Stream<B> b = graph.In(1).SetName("b").Cast<B>()  // Bad.
  Stream<D> d = RunSomething(a, b, graph);
  // ...

  return graph.GetConfig();
}

Bunun yerine, grafik girişlerinizi grafik oluşturucunuzun en başında tanımlayın:

YAPILACAKLAR — iyi kod örneği.

Stream<D> RunSomething(Stream<A> a, Stream<B> b, Stream<C> c, Graph& graph) {
  // ...
}

CalculatorGraphConfig BuildGraph() {
  Graph graph;

  // Inputs.
  Stream<A> a = graph.In(0).SetName("a").Cast<A>();
  Stream<B> b = graph.In(1).SetName("b").Cast<B>();
  Stream<C> c = graph.In(2).SetName("c").Cast<C>();

  // 10/100/N lines of code.
  Stream<D> d = RunSomething(a, b, c, graph);
  // ...

  return graph.GetConfig();
}

Her zaman tanımlı olmayan bir giriş akışınız veya yan paketiniz varsa std::optional öğesini kullanın ve en başa yerleştirin:

YAPILACAKLAR — iyi kod örneği.

std::optional<Stream<A>> a;
if (needs_a) {
  a = graph.In(0).SetName(a).Cast<A>();
}

Grafik çıktılarını en sonda tanımlayın

Aşağıdaki kodda:

  • Grafikte kaç çıkışınız olduğunu tahmin etmek zor olabilir.
  • Genel olarak hataya açık olabilir ve ileride bakımı zor olabilir (ör. doğru bir dizin mi? adı mı? Bazı çıkışlar kaldırılırsa veya isteğe bağlı hale getirilirse ne olur?)
  • Diğer grafiklerin farklı çıktıları olabileceğinden RunSomething yeniden kullanımı sınırlıdır

YAPILMAYACAKLAR. Bozuk kod örneği.

void RunSomething(Stream<Input> input, Graph& graph) {
  // ...
  node.Out("OUTPUT_F")
      .SetName("output_f").ConnectTo(graph.Out(2));  // Bad.
}

CalculatorGraphConfig BuildGraph() {
  Graph graph;

  // 10/100/N lines of code.
  node.Out("OUTPUT_D")
      .SetName("output_d").ConnectTo(graph.Out(0));  // Bad.
  // 10/100/N lines of code.
  node.Out("OUTPUT_E")
      .SetName("output_e").ConnectTo(graph.Out(1));  // Bad.
  // 10/100/N lines of code.
  RunSomething(input, graph);
  // ...

  return graph.GetConfig();
}

Bunun yerine, grafik çıkışlarınızı, grafik oluşturucunuzun en sonunda tanımlayın:

YAPILACAKLAR — iyi kod örneği.

Stream<F> RunSomething(Stream<Input> input, Graph& graph) {
  // ...
  return node.Out("OUTPUT_F").Cast<F>();
}

CalculatorGraphConfig BuildGraph() {
  Graph graph;

  // 10/100/N lines of code.
  Stream<D> d = node.Out("OUTPUT_D").Cast<D>();
  // 10/100/N lines of code.
  Stream<E> e = node.Out("OUTPUT_E").Cast<E>();
  // 10/100/N lines of code.
  Stream<F> f = RunSomething(input, graph);
  // ...

  // Outputs.
  d.SetName("output_d").ConnectTo(graph.Out(0));
  e.SetName("output_e").ConnectTo(graph.Out(1));
  f.SetName("output_f").ConnectTo(graph.Out(2));

  return graph.GetConfig();
}

Düğümleri birbirinden ayrılmış halde tutun

MediaPipe'te paket akışları ve yan paketler, işleme düğümleri kadar anlamlıdır. Ayrıca tüm düğüm giriş gereksinimleri ve çıkış ürünleri, tükettiği ve ürettiği akışlar ve yan paketler açısından açık ve bağımsız bir şekilde ifade edilir.

YAPILMAYACAKLAR. Bozuk kod örneği.

CalculatorGraphConfig BuildGraph() {
  Graph graph;

  // Inputs.
  Stream<A> a = graph.In(0).Cast<A>();

  auto& node1 = graph.AddNode("Calculator1");
  a.ConnectTo(node1.In("INPUT"));

  auto& node2 = graph.AddNode("Calculator2");
  node1.Out("OUTPUT").ConnectTo(node2.In("INPUT"));  // Bad.

  auto& node3 = graph.AddNode("Calculator3");
  node1.Out("OUTPUT").ConnectTo(node3.In("INPUT_B"));  // Bad.
  node2.Out("OUTPUT").ConnectTo(node3.In("INPUT_C"));  // Bad.

  auto& node4 = graph.AddNode("Calculator4");
  node1.Out("OUTPUT").ConnectTo(node4.In("INPUT_B"));  // Bad.
  node2.Out("OUTPUT").ConnectTo(node4.In("INPUT_C"));  // Bad.
  node3.Out("OUTPUT").ConnectTo(node4.In("INPUT_D"));  // Bad.

  // Outputs.
  node1.Out("OUTPUT").SetName("b").ConnectTo(graph.Out(0));  // Bad.
  node2.Out("OUTPUT").SetName("c").ConnectTo(graph.Out(1));  // Bad.
  node3.Out("OUTPUT").SetName("d").ConnectTo(graph.Out(2));  // Bad.
  node4.Out("OUTPUT").SetName("e").ConnectTo(graph.Out(3));  // Bad.

  return graph.GetConfig();
}

Yukarıdaki kodda:

  • Düğümler birbirine bağlıdır.Örneğin node4, girişlerinin nereden geldiğini (node1, node2, node3) bilir ve yeniden düzenleme, bakım ve kodun yeniden kullanılmasını karmaşık hale getirir.
    • Bu tür kullanım kalıbı, varsayılan olarak düğümlerin ayrıldığı proto gösterimden düşürülür.
  • node#.Out("OUTPUT") çağrıları yineleniyor ve bunun yerine daha açık adlar kullanabileceğiniz ve gerçek bir tür sunabileceğiniz için okunabilirliği olumsuz etkiler.

Dolayısıyla, yukarıdaki sorunları gidermek için aşağıdaki grafik oluşturma kodunu yazabilirsiniz:

YAPILACAKLAR — iyi kod örneği.

CalculatorGraphConfig BuildGraph() {
  Graph graph;

  // Inputs.
  Stream<A> a = graph.In(0).Cast<A>();

  // `node1` usage is limited to 3 lines below.
  auto& node1 = graph.AddNode("Calculator1");
  a.ConnectTo(node1.In("INPUT"));
  Stream<B> b = node1.Out("OUTPUT").Cast<B>();

  // `node2` usage is limited to 3 lines below.
  auto& node2 = graph.AddNode("Calculator2");
  b.ConnectTo(node2.In("INPUT"));
  Stream<C> c = node2.Out("OUTPUT").Cast<C>();

  // `node3` usage is limited to 4 lines below.
  auto& node3 = graph.AddNode("Calculator3");
  b.ConnectTo(node3.In("INPUT_B"));
  c.ConnectTo(node3.In("INPUT_C"));
  Stream<D> d = node3.Out("OUTPUT").Cast<D>();

  // `node4` usage is limited to 5 lines below.
  auto& node4 = graph.AddNode("Calculator4");
  b.ConnectTo(node4.In("INPUT_B"));
  c.ConnectTo(node4.In("INPUT_C"));
  d.ConnectTo(node4.In("INPUT_D"));
  Stream<E> e = node4.Out("OUTPUT").Cast<E>();

  // Outputs.
  b.SetName("b").ConnectTo(graph.Out(0));
  c.SetName("c").ConnectTo(graph.Out(1));
  d.SetName("d").ConnectTo(graph.Out(2));
  e.SetName("e").ConnectTo(graph.Out(3));

  return graph.GetConfig();
}

Artık gerekirse node1 öğesini kolayca kaldırıp b öğesini bir grafik girişi haline getirebilirsiniz. node2, node3 ve node4 öğeleri birbirinden ayrıldığından bunlar için herhangi bir güncelleme gerekmez (proto gösterimdeki gibi).

Genel olarak yukarıdaki kod, proto grafiğini daha yakından çoğaltır:

input_stream: "a"

node {
  calculator: "Calculator1"
  input_stream: "INPUT:a"
  output_stream: "OUTPUT:b"
}

node {
  calculator: "Calculator2"
  input_stream: "INPUT:b"
  output_stream: "OUTPUT:C"
}

node {
  calculator: "Calculator3"
  input_stream: "INPUT_B:b"
  input_stream: "INPUT_C:c"
  output_stream: "OUTPUT:d"
}

node {
  calculator: "Calculator4"
  input_stream: "INPUT_B:b"
  input_stream: "INPUT_C:c"
  input_stream: "INPUT_D:d"
  output_stream: "OUTPUT:e"
}

output_stream: "b"
output_stream: "c"
output_stream: "d"
output_stream: "e"

Üstelik, artık diğer grafiklerde tekrar kullanmak üzere yardımcı program fonksiyonlarını çıkartabilirsiniz:

YAPILACAKLAR — iyi kod örneği.

Stream<B> RunCalculator1(Stream<A> a, Graph& graph) {
  auto& node = graph.AddNode("Calculator1");
  a.ConnectTo(node.In("INPUT"));
  return node.Out("OUTPUT").Cast<B>();
}

Stream<C> RunCalculator2(Stream<B> b, Graph& graph) {
  auto& node = graph.AddNode("Calculator2");
  b.ConnectTo(node.In("INPUT"));
  return node.Out("OUTPUT").Cast<C>();
}

Stream<D> RunCalculator3(Stream<B> b, Stream<C> c, Graph& graph) {
  auto& node = graph.AddNode("Calculator3");
  b.ConnectTo(node.In("INPUT_B"));
  c.ConnectTo(node.In("INPUT_C"));
  return node.Out("OUTPUT").Cast<D>();
}

Stream<E> RunCalculator4(Stream<B> b, Stream<C> c, Stream<D> d, Graph& graph) {
  auto& node = graph.AddNode("Calculator4");
  b.ConnectTo(node.In("INPUT_B"));
  c.ConnectTo(node.In("INPUT_C"));
  d.ConnectTo(node.In("INPUT_D"));
  return node.Out("OUTPUT").Cast<E>();
}

CalculatorGraphConfig BuildGraph() {
  Graph graph;

  // Inputs.
  Stream<A> a = graph.In(0).Cast<A>();

  Stream<B> b = RunCalculator1(a, graph);
  Stream<C> c = RunCalculator2(b, graph);
  Stream<D> d = RunCalculator3(b, c, graph);
  Stream<E> e = RunCalculator4(b, c, d, graph);

  // Outputs.
  b.SetName("b").ConnectTo(graph.Out(0));
  c.SetName("c").ConnectTo(graph.Out(1));
  d.SetName("d").ConnectTo(graph.Out(2));
  e.SetName("e").ConnectTo(graph.Out(3));

  return graph.GetConfig();
}

Daha iyi okunabilirlik için ayrı düğümleri

YAPILMAYACAKLAR. Bozuk kod örneği.

CalculatorGraphConfig BuildGraph() {
  Graph graph;

  // Inputs.
  Stream<A> a = graph.In(0).Cast<A>();
  auto& node1 = graph.AddNode("Calculator1");
  a.ConnectTo(node1.In("INPUT"));
  Stream<B> b = node1.Out("OUTPUT").Cast<B>();
  auto& node2 = graph.AddNode("Calculator2");
  b.ConnectTo(node2.In("INPUT"));
  Stream<C> c = node2.Out("OUTPUT").Cast<C>();
  auto& node3 = graph.AddNode("Calculator3");
  b.ConnectTo(node3.In("INPUT_B"));
  c.ConnectTo(node3.In("INPUT_C"));
  Stream<D> d = node3.Out("OUTPUT").Cast<D>();
  auto& node4 = graph.AddNode("Calculator4");
  b.ConnectTo(node4.In("INPUT_B"));
  c.ConnectTo(node4.In("INPUT_C"));
  d.ConnectTo(node4.In("INPUT_D"));
  Stream<E> e = node4.Out("OUTPUT").Cast<E>();
  // Outputs.
  b.SetName("b").ConnectTo(graph.Out(0));
  c.SetName("c").ConnectTo(graph.Out(1));
  d.SetName("d").ConnectTo(graph.Out(2));
  e.SetName("e").ConnectTo(graph.Out(3));

  return graph.GetConfig();
}

Yukarıdaki kodda, her bir düğümün nerede başlayıp nerede bittiği fikrini anlamak zor olabilir. Bunu iyileştirmek ve kod okuyucularınıza yardımcı olmak için her düğümün öncesinde ve sonrasında boş satırlar kullanabilirsiniz:

YAPILACAKLAR — iyi kod örneği.

CalculatorGraphConfig BuildGraph() {
  Graph graph;

  // Inputs.
  Stream<A> a = graph.In(0).Cast<A>();

  auto& node1 = graph.AddNode("Calculator1");
  a.ConnectTo(node1.In("INPUT"));
  Stream<B> b = node1.Out("OUTPUT").Cast<B>();

  auto& node2 = graph.AddNode("Calculator2");
  b.ConnectTo(node2.In("INPUT"));
  Stream<C> c = node2.Out("OUTPUT").Cast<C>();

  auto& node3 = graph.AddNode("Calculator3");
  b.ConnectTo(node3.In("INPUT_B"));
  c.ConnectTo(node3.In("INPUT_C"));
  Stream<D> d = node3.Out("OUTPUT").Cast<D>();

  auto& node4 = graph.AddNode("Calculator4");
  b.ConnectTo(node4.In("INPUT_B"));
  c.ConnectTo(node4.In("INPUT_C"));
  d.ConnectTo(node4.In("INPUT_D"));
  Stream<E> e = node4.Out("OUTPUT").Cast<E>();

  // Outputs.
  b.SetName("b").ConnectTo(graph.Out(0));
  c.SetName("c").ConnectTo(graph.Out(1));
  d.SetName("d").ConnectTo(graph.Out(2));
  e.SetName("e").ConnectTo(graph.Out(3));

  return graph.GetConfig();
}

Ayrıca, yukarıdaki temsil CalculatorGraphConfig proto gösterimiyle daha iyi eşleşir.

Düğümleri yardımcı program fonksiyonlarına çıkarırsanız bunlar halihazırda işlevlerin kapsamına girer ve nerede başlayıp nerede bittiği bellidir. Bu nedenle tercihte sakınca yoktur:

YAPILACAKLAR — iyi kod örneği.

CalculatorGraphConfig BuildGraph() {
  Graph graph;

  // Inputs.
  Stream<A> a = graph.In(0).Cast<A>();

  Stream<B> b = RunCalculator1(a, graph);
  Stream<C> c = RunCalculator2(b, graph);
  Stream<D> d = RunCalculator3(b, c, graph);
  Stream<E> e = RunCalculator4(b, c, d, graph);

  // Outputs.
  b.SetName("b").ConnectTo(graph.Out(0));
  c.SetName("c").ConnectTo(graph.Out(1));
  d.SetName("d").ConnectTo(graph.Out(2));
  e.SetName("e").ConnectTo(graph.Out(3));

  return graph.GetConfig();
}