C++ এ গ্রাফ তৈরি করা

C++ গ্রাফ নির্মাতা এর জন্য একটি শক্তিশালী টুল:

  • জটিল গ্রাফ তৈরি করা
  • প্যারামেট্রিজিং গ্রাফ (যেমন InferenceCalculator একজন প্রতিনিধি সেট করা, গ্রাফের অংশগুলি সক্রিয়/অক্ষম করা)
  • গ্রাফ ডিডুপ্লিকেট করা (যেমন pbtxt-এ CPU এবং GPU ডেডিকেটেড গ্রাফের পরিবর্তে আপনার কাছে একটি একক কোড থাকতে পারে যা প্রয়োজনীয় গ্রাফ তৈরি করে, যতটা সম্ভব ভাগ করে নেওয়া)
  • ঐচ্ছিক গ্রাফ ইনপুট/আউটপুট সমর্থন করে
  • প্ল্যাটফর্ম প্রতি গ্রাফ কাস্টমাইজ করা

মৌলিক ব্যবহার

চলুন দেখি কিভাবে C++ গ্রাফ বিল্ডার একটি সাধারণ গ্রাফের জন্য ব্যবহার করা যেতে পারে:

# 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 {} }
    }
  }
}

উপরের CalculatorGraphConfig তৈরি করার ফাংশনটি দেখতে এরকম হতে পারে:

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

সংক্ষিপ্ত সারসংক্ষেপ:

  • Stream/SidePacket হিসাবে গ্রাফ ইনপুট পেতে Graph::In/SideIn ব্যবহার করুন
  • Stream/SidePacket হিসাবে নোড আউটপুট পেতে Node::Out/SideOut ব্যবহার করুন
  • নোড ইনপুট ( Node::In/SideIn ) এবং গ্রাফ আউটপুট ( Graph::Out/SideOut ) স্ট্রিম এবং সাইড প্যাকেটগুলিকে সংযুক্ত করতে Stream/SidePacket::ConnectTo ব্যবহার করুন
    • একটি "শর্টকাট" অপারেটর >> আছে যা আপনি ConnectTo ফাংশনের পরিবর্তে ব্যবহার করতে পারেন (যেমন x >> node.In("IN") )।
  • Stream/SidePacket::Cast ব্যবহার করা হয় AnyType স্ট্রিম বা সাইড প্যাকেট (যেমন Stream<AnyType> in = graph.In(0); ) একটি নির্দিষ্ট টাইপে কাস্ট করতে।
    • AnyType পরিবর্তে প্রকৃত প্রকারগুলি ব্যবহার করা আপনাকে গ্রাফ বিল্ডার সক্ষমতা প্রকাশ করতে এবং আপনার গ্রাফ পঠনযোগ্যতা উন্নত করার জন্য একটি ভাল পথে সেট করে।

উন্নত ব্যবহার

ইউটিলিটি ফাংশন

পঠনযোগ্যতা এবং কোড পুনঃব্যবহারের জন্য সাহায্য করার জন্য একটি ডেডিকেটেড ইউটিলিটি ফাংশনে অনুমান নির্মাণ কোড বের করা যাক:

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

ফলস্বরূপ, RunInference একটি স্পষ্ট ইন্টারফেস প্রদান করে যে ইনপুট/আউটপুট এবং তাদের প্রকারগুলি কী।

এটি সহজেই পুনরায় ব্যবহার করা যেতে পারে, যেমন আপনি যদি একটি অতিরিক্ত মডেল অনুমান চালাতে চান তবে এটি শুধুমাত্র কয়েকটি লাইন:

  // 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);

এবং আপনাকে নাম এবং ট্যাগ ( InferenceCalculator , TENSORS , MODEL ) নকল করতে হবে না বা এখানে এবং সেখানে উত্সর্গীকৃত ধ্রুবকগুলি প্রবর্তন করতে হবে না - এই বিবরণগুলি RunInference ফাংশনে স্থানীয়করণ করা হয়েছে৷

ইউটিলিটি ক্লাস

এবং অবশ্যই, এটি শুধুমাত্র ফাংশন সম্পর্কে নয়, কিছু ক্ষেত্রে ইউটিলিটি ক্লাস চালু করা উপকারী যা আপনার গ্রাফ নির্মাণ কোডকে আরও পাঠযোগ্য এবং কম ত্রুটি প্রবণ করতে সাহায্য করতে পারে।

MediaPipe PassThroughCalculator ক্যালকুলেটর অফার করে, যা কেবল তার ইনপুটগুলির মধ্য দিয়ে যাচ্ছে:

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"
}

আসুন উপরের গ্রাফটি তৈরি করার জন্য সোজা C++ নির্মাণ কোডটি দেখি:

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 উপস্থাপনা ত্রুটি প্রবণ হতে পারে (যখন আমাদের পাস করার জন্য অনেক ইনপুট থাকে), C++ কোড আরও খারাপ দেখায়: বারবার খালি ট্যাগ এবং Cast কল। আসুন দেখি কিভাবে আমরা একটি PassThroughNodeBuilder প্রবর্তন করে আরও ভাল করতে পারি:

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

এবং এখন গ্রাফ নির্মাণ কোড এর মত দেখতে পারে:

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

এখন আপনি নির্মাণ কোডের মাধ্যমে আপনার পাসে ভুল অর্ডার বা সূচী রাখতে পারবেন না এবং PassThrough ইনপুট থেকে Cast ধরণ অনুমান করে কিছু টাইপিং সংরক্ষণ করতে পারবেন।

করণীয় এবং করণীয়

যদি সম্ভব হয় তবে একেবারে শুরুতে গ্রাফ ইনপুটগুলি সংজ্ঞায়িত করুন

নীচের কোডে:

  • গ্রাফে আপনার কতগুলি ইনপুট আছে তা অনুমান করা কঠিন।
  • সামগ্রিকভাবে ত্রুটি প্রবণ এবং ভবিষ্যতে বজায় রাখা কঠিন হতে পারে (যেমন এটি একটি সঠিক সূচক? নাম? যদি কিছু ইনপুট সরানো হয় বা ঐচ্ছিক করা হয়? ইত্যাদি)।
  • RunSomething পুনঃব্যবহার সীমিত কারণ অন্যান্য গ্রাফে বিভিন্ন ইনপুট থাকতে পারে

করবেন না - খারাপ কোডের উদাহরণ।

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

পরিবর্তে, আপনার গ্রাফ নির্মাতার একেবারে শুরুতে আপনার গ্রাফ ইনপুটগুলি সংজ্ঞায়িত করুন:

DO - ভাল কোডের উদাহরণ।

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 ব্যবহার করুন যদি আপনার কাছে একটি ইনপুট স্ট্রীম বা সাইড প্যাকেট থাকে যা সর্বদা সংজ্ঞায়িত করা হয় না এবং এটিকে একেবারে শুরুতে রাখুন:

DO - ভাল কোডের উদাহরণ।

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

একেবারে শেষে গ্রাফ আউটপুট সংজ্ঞায়িত করুন

নীচের কোডে:

  • গ্রাফে আপনার কতগুলি আউটপুট আছে তা অনুমান করা কঠিন।
  • সামগ্রিকভাবে ত্রুটি প্রবণ এবং ভবিষ্যতে বজায় রাখা কঠিন হতে পারে (যেমন এটি একটি সঠিক সূচক? নাম? যদি কিছু আউটপাস সরানো হয় বা ঐচ্ছিক করা হয়? ইত্যাদি)।
  • RunSomething পুনঃব্যবহার সীমিত কারণ অন্যান্য গ্রাফের বিভিন্ন আউটপুট থাকতে পারে

করবেন না - খারাপ কোডের উদাহরণ।

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

পরিবর্তে, আপনার গ্রাফ নির্মাতার একেবারে শেষে আপনার গ্রাফ আউটপুটগুলি সংজ্ঞায়িত করুন:

DO - ভাল কোডের উদাহরণ।

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

নোডগুলি একে অপরের থেকে বিচ্ছিন্ন রাখুন

মিডিয়াপাইপে, প্যাকেট স্ট্রিম এবং সাইড প্যাকেটগুলি প্রক্রিয়াকরণ নোডের মতো অর্থবহ। এবং যেকোন নোড ইনপুট প্রয়োজনীয়তা এবং আউটপুট পণ্যগুলি স্ট্রীম এবং সাইড প্যাকেটগুলির পরিপ্রেক্ষিতে স্পষ্টভাবে এবং স্বাধীনভাবে প্রকাশ করা হয় যা এটি ব্যবহার করে এবং উত্পাদন করে।

করবেন না - খারাপ কোডের উদাহরণ।

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

উপরের কোডে:

  • নোডগুলি একে অপরের সাথে মিলিত হয়, যেমন node4 জানে যে এর ইনপুটগুলি কোথা থেকে আসছে ( node1 , node2 , node3 ) এবং এটি রিফ্যাক্টরিং, রক্ষণাবেক্ষণ এবং কোড পুনঃব্যবহারকে জটিল করে তোলে
    • এই ধরনের ব্যবহার প্যাটার্ন হল প্রোটো প্রতিনিধিত্ব থেকে একটি ডাউনগ্রেড, যেখানে নোডগুলি ডিফল্টরূপে ডিকপল করা হয়।
  • node#.Out("OUTPUT") কলগুলি সদৃশ হয় এবং পাঠযোগ্যতা ক্ষতিগ্রস্থ হয় কারণ আপনি পরিবর্তে ক্লিনার নাম ব্যবহার করতে পারেন এবং একটি প্রকৃত প্রকার প্রদান করতে পারেন।

সুতরাং, উপরের সমস্যাগুলি সমাধান করতে আপনি নিম্নলিখিত গ্রাফ নির্মাণ কোড লিখতে পারেন:

DO - ভাল কোডের উদাহরণ।

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

এখন, যদি প্রয়োজন হয়, আপনি সহজেই node1 মুছে ফেলতে পারেন এবং b একটি গ্রাফ ইনপুট তৈরি করতে পারেন এবং node2 , node3 , node4 (প্রথা অনুসারে প্রোটো উপস্থাপনার মতোই) তে কোনো আপডেটের প্রয়োজন নেই, কারণ এগুলি একে অপরের থেকে আলাদা করা হয়েছে।

সামগ্রিকভাবে, উপরের কোডটি প্রোটো গ্রাফটিকে আরও ঘনিষ্ঠভাবে প্রতিলিপি করে:

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"

তার উপরে, এখন আপনি অন্যান্য গ্রাফগুলিতে আরও পুনঃব্যবহারের জন্য ইউটিলিটি ফাংশনগুলি বের করতে পারেন:

DO - ভাল কোডের উদাহরণ।

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

ভাল পঠনযোগ্যতার জন্য পৃথক নোড

করবেন না - খারাপ কোডের উদাহরণ।

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

উপরের কোডে, প্রতিটি নোড কোথায় শুরু হয় এবং শেষ হয় সেই ধারণাটি উপলব্ধি করা কঠিন হতে পারে। এটি উন্নত করতে এবং আপনার কোড পাঠকদের সাহায্য করতে, আপনি প্রতিটি নোডের আগে এবং পরে খালি লাইন রাখতে পারেন:

DO - ভাল কোডের উদাহরণ।

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

এছাড়াও, উপরের উপস্থাপনাটি CalculatorGraphConfig প্রোটো উপস্থাপনাকে আরও ভালভাবে মেলে।

আপনি যদি ইউটিলিটি ফাংশনগুলিতে নোডগুলি বের করেন, তবে সেগুলি ইতিমধ্যেই ফাংশনের মধ্যে স্কোপ করা হয়েছে এবং সেগুলি কোথায় শুরু এবং শেষ হবে তা পরিষ্কার, তাই এটি থাকা সম্পূর্ণ সূক্ষ্ম:

DO - ভাল কোডের উদাহরণ।

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