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 parametrleştirme (ör. InferenceCalculator üzerinde yetki verilmiş bir kullanıcı ayarlama, grafiğin bölümlerini etkinleştirin/devre dışı bırakın)
  • Grafikleri tekilleştirme (ör. pbtxt dosyasında CPU ve GPU'ya özel grafikler yerine gerekli grafikleri oluşturan ve mümkün olduğunca çok veriyi paylaşan tek bir kodunuz ekleyin)
  • İsteğe bağlı grafik giriş/çı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 derleme işlevi aşağıdaki gibi 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:

  • Stream/SidePacket olarak grafik girişleri 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 bağlamak için Stream/SidePacket::ConnectTo kullanın: düğüm girişleri (Node::In/SideIn) ve grafik çıkışları (Graph::Out/SideOut)
    • Bir "kısayol" vardır. >> operatörünü kullanabilirsiniz. ConnectTo işlevi (ör. x >> node.In("IN")).
  • Stream/SidePacket::Cast, AnyType akışının veya yan paketini yayınlamak için kullanılır (ör. Stream<AnyType> in = graph.In(0);) belirli bir türe
    • AnyType yerine gerçek türleri kullanırsanız aşağıdakiler için daha iyi bir yol belirleyebilirsiniz: Grafik oluşturucu özelliklerinden yararlanın ve grafiklerinizi iyileştirin okunabilirlik.

Gelişmiş Kullanım

Yardımcı İşlevler

Çıkarım oluşturma kodunu özel bir yardımcı program işlevine çıkaralım. okunabilirlik ve kodun yeniden kullanımı için yardı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, arama sonuçlarının ne olduğunu gösteren ve türlerini ele aldık.

Kolayca yeniden kullanılabilir, ör. Ekstra bir açıklama daha eklemek istiyorsanız yalnızca birkaç satır model çıkarımı:

  // 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 çoğaltmanız gerekmez (InferenceCalculator, TENSORS, MODEL) veya bazı yerlerde özel sabit değerler kullanın. ayrıntılar RunInference işlevine yerelleştirilmiştir.

Yardımcı Program Sınıfları

Elbette sadece işlevlerle ilgili değil, bazı durumlarda, Grafik oluşturma kodunuzu oluşturmanıza yardımcı olabilecek yardımcı dersler sunun ve hataya daha az açıktır.

MediaPipe, PassThroughCalculator hesap makinesi sunar. Bu hesap için kullanır:

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 kullanılan 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 temsilinin hataya açık olması mümkün olsa da (aktarılacak çok sayıda girdimiz olduğunda) C++ kodu ise daha da kötü görünür: yinelenen boş etiketler ve Cast çağrısı. Hadi PassThroughNodeBuilder kullanıma sunarak nasıl daha iyi performans gösterebileceğimizi öğrenin:

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_;
};

Grafik oluşturma kodu artık aşağıdaki gibi 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 inşaattan geçerken yanlış sıra veya dizinden yararlanamazsınız PassThrough içinden Cast türünü tahmin ederek biraz yazma işleminden kaçının giriş.

Yapılması ve Yapılmaması Gerekenler

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

Aşağıdaki kodda:

  • Grafikte kaç tane girişiniz olduğunu tahmin etmek zor olabilir.
  • Genel olarak hataya açık ve gelecekte sürdürmesi zor olabilir (ör. dizininiz doğru mu? adı nedir? Bazı girişler kaldırılırsa veya isteğe bağlı hale getirilirse ne olur? vb.) bakın.
  • Diğer grafikler farklı olabileceği için RunSomething yeniden kullanımı sınırlıdır girişler

YAPILMAYACAKLAR - Hatalı 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 bir kod örneğidir.

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();
}

std::optional her zaman en başta tanımlayın:

YAPILACAKLAR: iyi bir kod örneğidir.

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

Grafik çıkış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 ve gelecekte sürdürmesi zor olabilir (ör. dizininiz doğru mu? adı nedir? bazı dışlayıcılar kaldırılırsa veya isteğe bağlı hale getirilirse ne olur? vb.) bakın.
  • Diğer grafikler farklı çıkışlara sahip olabileceğinden RunSomething yeniden kullanımı sınırlıdır

YAPILMAYACAKLAR - Hatalı 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şturucunun en sonunda tanımlayın:

YAPILACAKLAR: iyi bir kod örneğidir.

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'ta paket akışları ve yan paketler, işleme düğüm. Düğüm giriş gereksinimleri ve çıkış ürünleri, kullandığı akışlar ve yan paketler açısından bağımsız üretiyor.

YAPILMAYACAKLAR - Hatalı 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.Ör. node4, girişlerinin nerede olduğunu bilir (node1, node2, node3) üzerinden geliyor ve yeniden düzenleme işlemini karmaşık hale getiriyor. bakım ve kodun yeniden kullanımı
    • Bu tür bir kullanım kalıbı, düğümlerin bulunduğu protondaki gösterimden varsayılan olarak ayrılır.
  • node#.Out("OUTPUT") çağrı yineleniyor ve siz okudukça okunabilirlik zorlaşıyor daha net adlar kullanabilir ve gerçek bir tür de sağlayabilir.

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

YAPILACAKLAR: iyi bir kod örneğidir.

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, node4 için güncelleme yapılması gerekiyor (proto temsilinde olduğu gibi) Bu arada) birbirlerinden ayırt edilirler.

Genel olarak yukarıdaki kod, proto grafiğini daha yakın bir şekilde kopyalar:

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"

Bunların da ötesinde, artık diğer grafiklerde tekrar kullanmak üzere yardımcı program işlevlerini ayıklayabilirsiniz:

YAPILACAKLAR: iyi bir kod örneğidir.

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üğümler

YAPILMAYACAKLAR - Hatalı 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 düğümün nerede başladığını sona eriyor. Bunu iyileştirmek ve kod okuyucularınıza yardımcı olmak için her düğümden önceki ve sonraki satırlar:

YAPILACAKLAR: iyi bir kod örneğidir.

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 protokolüyle eşleşiyor daha iyi temsil edilmesine yardımcı olur.

Düğümleri yardımcı program işlevlerine çıkarırsanız bunlar işlevlerin kapsamındadır ve bunların nerede başlayıp bittiği açıktır. Dolayısıyla, sahip olanlar:

YAPILACAKLAR: iyi bir kod örneğidir.

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();
}