Patch Detail
Show a patch.
GET /api/1.1/patches/21029/?format=api
{ "id": 21029, "url": "https://patchwork.libcamera.org/api/1.1/patches/21029/?format=api", "web_url": "https://patchwork.libcamera.org/patch/21029/", "project": { "id": 1, "url": "https://patchwork.libcamera.org/api/1.1/projects/1/?format=api", "name": "libcamera", "link_name": "libcamera", "list_id": "libcamera_core", "list_email": "libcamera-devel@lists.libcamera.org", "web_url": "", "scm_url": "", "webscm_url": "" }, "msgid": "<20240826193159.1503757-2-chenghaoyang@google.com>", "date": "2024-08-26T19:26:55", "name": "[v1,1/1] libcamera: Introduce scheduler and task for complex pipeline data flow", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "4ef4d65bc3495b545ce192e22d90c6a497045c10", "submitter": { "id": 117, "url": "https://patchwork.libcamera.org/api/1.1/people/117/?format=api", "name": "Cheng-Hao Yang", "email": "chenghaoyang@chromium.org" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/21029/mbox/", "series": [ { "id": 4542, "url": "https://patchwork.libcamera.org/api/1.1/series/4542/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=4542", "date": "2024-08-26T19:26:55", "name": "Add Task and Scheduler", "version": 1, "mbox": "https://patchwork.libcamera.org/series/4542/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/21029/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/21029/checks/", "tags": {}, "headers": { "Return-Path": "<libcamera-devel-bounces@lists.libcamera.org>", "X-Original-To": "parsemail@patchwork.libcamera.org", "Delivered-To": "parsemail@patchwork.libcamera.org", "Received": [ "from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 81E1BC324C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 26 Aug 2024 19:32:07 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 56EF663423;\n\tMon, 26 Aug 2024 21:32:06 +0200 (CEST)", "from mail-wm1-x333.google.com (mail-wm1-x333.google.com\n\t[IPv6:2a00:1450:4864:20::333])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 4F01A63417\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 26 Aug 2024 21:32:03 +0200 (CEST)", "by mail-wm1-x333.google.com with SMTP id\n\t5b1f17b1804b1-428101fa30aso41047055e9.3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 26 Aug 2024 12:32:03 -0700 (PDT)", "from chenghaoyang-germany.c.googlers.com.com\n\t(161.126.77.34.bc.googleusercontent.com. [34.77.126.161])\n\tby smtp.gmail.com with ESMTPSA id\n\tffacd0b85a97d-3730813c465sm11393711f8f.32.2024.08.26.12.32.01\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tMon, 26 Aug 2024 12:32:01 -0700 (PDT)" ], "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=chromium.org header.i=@chromium.org\n\theader.b=\"Slo3xX2R\"; dkim-atps=neutral", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=chromium.org; s=google; t=1724700722; x=1725305522;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=5ddcLsBxvva5yT04eJ8qegakPvV0LdJ3HB59Xaog07M=;\n\tb=Slo3xX2RbVrZnVxcGFBS4o1l6CNDxXKG96lXNjAPa5KogE8C+XslAs98v9QCrrDHxG\n\t0rIWOOrNuFY+M6s0F1uwSMA75YjjwQ6PzcVmes8r6LOhsReoO9R9LLjhtyO2nqLssi0Y\n\tm5hJvNBK8a81mL6f+qdeMWw2reOkmcN5zTWW0=", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1724700722; x=1725305522;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc\n\t:subject:date:message-id:reply-to;\n\tbh=5ddcLsBxvva5yT04eJ8qegakPvV0LdJ3HB59Xaog07M=;\n\tb=umKoOA6nTLOgiI6aGgKGsviW3YXDy13akdth5JY4hufXR1/odS8ZQUfM736TofLOgv\n\tXgyPWaU3yDZthxe5ux632/kM3xfzHH4X28m3ctEYi8/4RH24N7JqSwQO2H+xYscYU9sN\n\texH4/Q2Rt4j30QfvkDFv1oY5WochCUL5lPJhFslVDx2fMAkViSOSlnrnn0VrsERMsFNb\n\tvDcQiG4BqX/xGjamCsdhVUzQQc0+Xbv8ttMkuFo5EFabGKIPG/8y15MDP7HgJnMU0mhj\n\t2ugGAIY0RWbzZF4mFOv73ugk5szs0djB4ZQiq3ckQvAGDbK4MLKbVzwY841i3lqkTf1+\n\tSFXw==", "X-Gm-Message-State": "AOJu0YyxFrhmxAhAdErhvBg2685QL5/iWVm1145mMnXqhpWFfWCPh6m5\n\tRT4RZmbnFOKmqCskmRxmOJo+t3mCOxONYeESjO2Zjhb63hI8vZhEVezdUaS9PDHzps9x2KFeWq2\n\tU0Dou", "X-Google-Smtp-Source": "AGHT+IF6WAQa+45y1cfgKqrAiEybS11mHJV5nVut1v44eIDyK5QsrrJJpITWYi1jPEsc13k9+/r9aA==", "X-Received": "by 2002:adf:f044:0:b0:371:6fb9:95a with SMTP id\n\tffacd0b85a97d-3748c7d5398mr462925f8f.34.1724700722182; \n\tMon, 26 Aug 2024 12:32:02 -0700 (PDT)", "From": "Harvey Yang <chenghaoyang@chromium.org>", "X-Google-Original-From": "Harvey Yang <chenghaoyang@google.com>", "To": "libcamera-devel@lists.libcamera.org", "Cc": "Harvey Yang <chenghaoyang@google.com>,\n\tHan-Lin Chen <hanlinchen@chromium.org>,\n\tHarvey Yang <chenghaoyang@chromium.org>", "Subject": "[PATCH v1 1/1] libcamera: Introduce scheduler and task for complex\n\tpipeline data flow", "Date": "Mon, 26 Aug 2024 19:26:55 +0000", "Message-ID": "<20240826193159.1503757-2-chenghaoyang@google.com>", "X-Mailer": "git-send-email 2.46.0.295.g3b9ea8a38a-goog", "In-Reply-To": "<20240826193159.1503757-1-chenghaoyang@google.com>", "References": "<20240826193159.1503757-1-chenghaoyang@google.com>", "MIME-Version": "1.0", "Content-Transfer-Encoding": "8bit", "X-BeenThere": "libcamera-devel@lists.libcamera.org", "X-Mailman-Version": "2.1.29", "Precedence": "list", "List-Id": "<libcamera-devel.lists.libcamera.org>", "List-Unsubscribe": "<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>", "List-Archive": "<https://lists.libcamera.org/pipermail/libcamera-devel/>", "List-Post": "<mailto:libcamera-devel@lists.libcamera.org>", "List-Help": "<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>", "List-Subscribe": "<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>", "Errors-To": "libcamera-devel-bounces@lists.libcamera.org", "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>" }, "content": "The patch introduces scheduler and task to define the data flow and task\nordering for complex pipeline more easily. The scheduler schedules tasks\nin a topological order from partial relation defined for each task when a\nrequest comes in.\n\nSigned-off-by: Han-Lin Chen <hanlinchen@chromium.org>\nCo-developed-by: Harvey Yang <chenghaoyang@chromium.org>\n---\n include/libcamera/internal/meson.build | 1 +\n include/libcamera/internal/task_scheduler.h | 116 ++++++++\n src/libcamera/meson.build | 1 +\n src/libcamera/task_scheduler.cpp | 301 ++++++++++++++++++++\n 4 files changed, 419 insertions(+)\n create mode 100644 include/libcamera/internal/task_scheduler.h\n create mode 100644 src/libcamera/task_scheduler.cpp", "diff": "diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build\nindex 1c5eef9ca..62e49ba60 100644\n--- a/include/libcamera/internal/meson.build\n+++ b/include/libcamera/internal/meson.build\n@@ -37,6 +37,7 @@ libcamera_internal_headers = files([\n 'shared_mem_object.h',\n 'source_paths.h',\n 'sysfs.h',\n+ 'task_scheduler.h',\n 'v4l2_device.h',\n 'v4l2_pixelformat.h',\n 'v4l2_subdevice.h',\ndiff --git a/include/libcamera/internal/task_scheduler.h b/include/libcamera/internal/task_scheduler.h\nnew file mode 100644\nindex 000000000..5a325b87b\n--- /dev/null\n+++ b/include/libcamera/internal/task_scheduler.h\n@@ -0,0 +1,116 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2024, Google Inc.\n+ *\n+ * task_scheduler.h - A task scheduler\n+ */\n+\n+#pragma once\n+\n+#include <chrono>\n+#include <list>\n+#include <map>\n+#include <string>\n+#include <unordered_map>\n+#include <unordered_set>\n+\n+#include <libcamera/base/object.h>\n+#include <libcamera/base/timer.h>\n+\n+namespace libcamera {\n+\n+class Scheduler;\n+\n+class Task\n+{\n+public:\n+\tTask(Scheduler *scheduler, const std::string &id = \"\");\n+\tvirtual ~Task() = default;\n+\n+\tvirtual void notifyDone();\n+\n+\tvirtual void run() = 0;\n+\tstd::string &id() { return id_; }\n+\n+\tbool isRunning() { return running_; }\n+\n+protected:\n+\tScheduler *scheduler_;\n+\tstd::string id_;\n+\n+private:\n+\tfriend Scheduler;\n+\n+\tbool running_ = false;\n+\n+\tvoid depend(Task *task);\n+\tsize_t removeDependency(Task *task);\n+\tvoid launch();\n+\n+\tstd::list<Task *> precedents_;\n+\tstd::list<Task *> succedents_;\n+\n+\tstd::chrono::steady_clock::time_point launchTime_;\n+};\n+\n+class Scheduler : public Object\n+{\n+public:\n+\tstatic void precede(Task *precedent, Task *task);\n+\n+\tScheduler();\n+\n+\tvoid schedule();\n+\tvoid log();\n+\n+protected:\n+\tvoid queueTask(Task *task, int32_t group);\n+\tvoid succeedPrevTaskByStep(int32_t group, size_t step, Task *task);\n+\tstd::list<Task *> &groupTasks(int32_t group);\n+\n+\tstd::map<int32_t, std::string> groupNames_;\n+\n+private:\n+\tfriend Task;\n+\n+\tvoid removeFromGroupTasks(Task *task);\n+\n+\tvoid taskDone(Task *task);\n+\tSignal<Task *> taskDone_;\n+\n+\tstd::unordered_map<Task *, std::unique_ptr<Task>> tasksHolder_;\n+\n+\tstd::map<int32_t, std::list<Task *>> groupTasks_;\n+\tstd::unordered_set<Task *> pendingTasks_;\n+\tstd::unordered_set<Task *> runningTasks_;\n+};\n+\n+template<typename Category, std::enable_if_t<std::is_enum_v<Category>> * = nullptr>\n+class CategorizedScheduler : public Scheduler\n+{\n+\tstatic_assert(std::is_enum<Category>::value, \"Category should be an enum\");\n+\n+public:\n+\tCategorizedScheduler(const std::map<Category, std::string> &categoryNames)\n+\t{\n+\t\tfor (auto &[group, name] : categoryNames)\n+\t\t\tScheduler::groupNames_[(int32_t)group] = name;\n+\t}\n+\n+\tvoid queueTask(Task *task, Category group)\n+\t{\n+\t\tScheduler::queueTask(task, static_cast<int32_t>(group));\n+\t}\n+\n+\tstd::list<Task *> &groupTasks(Category group)\n+\t{\n+\t\treturn Scheduler::groupTasks(static_cast<int32_t>(group));\n+\t}\n+\n+\tvoid succeedPrevTaskByStep(Category group, size_t step, Task *task)\n+\t{\n+\t\tScheduler::succeedPrevTaskByStep(static_cast<uint32_t>(group), step, task);\n+\t}\n+};\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\nindex aa9ab0291..e790813d1 100644\n--- a/src/libcamera/meson.build\n+++ b/src/libcamera/meson.build\n@@ -47,6 +47,7 @@ libcamera_internal_sources = files([\n 'shared_mem_object.cpp',\n 'source_paths.cpp',\n 'sysfs.cpp',\n+ 'task_scheduler.cpp',\n 'v4l2_device.cpp',\n 'v4l2_pixelformat.cpp',\n 'v4l2_subdevice.cpp',\ndiff --git a/src/libcamera/task_scheduler.cpp b/src/libcamera/task_scheduler.cpp\nnew file mode 100644\nindex 000000000..7cc471d89\n--- /dev/null\n+++ b/src/libcamera/task_scheduler.cpp\n@@ -0,0 +1,301 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2024, Google Inc.\n+ *\n+ * task_scheduler.cpp - A task scheduler\n+ */\n+\n+#include \"libcamera/internal/task_scheduler.h\"\n+\n+#include <libcamera/base/log.h>\n+\n+/**\n+ * \\internal\n+ * \\file task_scheduler.h\n+ * \\brief Task and (Categorized)Scheduler that run tasks based on dependencies\n+ */\n+\n+namespace libcamera {\n+\n+LOG_DEFINE_CATEGORY(Task)\n+\n+/**\n+ * \\class Task\n+ * \\brief A set of operations to execute as an individual Task\n+ *\n+ * The Task class contains a set of operations to be executed. It may have\n+ * dependencies on other tasks, and should only be executed by the\n+ * Scheduler it belongs to when all of its dependency tasks are executed.\n+ *\n+ * Upon its end of execution, it should notify its Scheduler by invoking\n+ * Scheduler::taskDone.\n+ */\n+\n+/**\n+ * \\brief Construct a Task instance\n+ * \\param[in] scheduler The Scheduler this task belongs to\n+ * \\param[in] id The ID of this task, only being used in log\n+ */\n+Task::Task(Scheduler *scheduler, const std::string &id)\n+\t: scheduler_(scheduler), id_(id)\n+{\n+}\n+\n+/**\n+ * \\brief Notify Scheduler the task has completed\n+ *\n+ * It's only called when all operations in this task is done, and it notifies\n+ * the Scheduler so that the dependencies of its succedents can be resolved.\n+ *\n+ * Some derived classes might override this function to do some cleanup at the\n+ * end of the Task. However, notifying the Scheduler should still be done.\n+ */\n+void Task::notifyDone()\n+{\n+\tscheduler_->invokeMethod(&Scheduler::taskDone, ConnectionTypeQueued, this);\n+}\n+\n+/**\n+ * \\fn Task::run()\n+ * \\brief Run the operations defined by the derived class\n+ *\n+ * This function runs the operations that should be done, or wait for some\n+ * callbacks / events, like ISP processing frames, or IPA returning metadata.\n+ */\n+\n+/**\n+ * \\fn Task::id()\n+ * \\brief The ID of the Task\n+ */\n+\n+/**\n+ * \\fn Task::isRunning()\n+ * \\brief The state if the task has already been running\n+ *\n+ * It's mostly used after the user calls Scheduler::groupTasks, and needs to\n+ * decide if it should use or depend on a Task based on its state.\n+ */\n+\n+/**\n+ * \\var Task::scheduler_\n+ * \\brief The Scheduler this Task belongs to\n+ */\n+\n+/**\n+ * \\var Task::id_\n+ * \\brief The id of this Task\n+ */\n+\n+size_t Task::removeDependency(Task *task)\n+{\n+\tprecedents_.remove(task);\n+\treturn precedents_.size();\n+}\n+\n+void Task::depend(Task *task)\n+{\n+\tprecedents_.emplace_back(task);\n+\ttask->succedents_.emplace_back(this);\n+}\n+\n+void Task::launch()\n+{\n+\tASSERT(precedents_.empty());\n+\n+\trunning_ = true;\n+\tlaunchTime_ = std::chrono::steady_clock::now();\n+\n+\tauto *method = new BoundMethodMember{\n+\t\tthis, scheduler_, &Task::run, ConnectionTypeQueued\n+\t};\n+\n+\tmethod->activate();\n+}\n+\n+/**\n+ * \\class Scheduler\n+ * \\brief Scheduler to run Tasks based on their dependencies\n+ *\n+ * This is the very basic implementation of Scheduler that runs Tasks when\n+ * they don't have any dependency left. It doesn't have priorities among Tasks.\n+ */\n+\n+/**\n+ * \\brief Adds a dependency from \\a precedent to \\a task\n+ * \\param[in] precedent The precedent Task that is a dependency\n+ * \\param[in] task The Task that needs to set a dependency\n+ */\n+void Scheduler::precede(Task *precedent, Task *task)\n+{\n+\tASSERT(task && precedent);\n+\ttask->depend(precedent);\n+}\n+\n+Scheduler::Scheduler() = default;\n+\n+/**\n+ * \\brief Launch all Tasks that don't have any dependency\n+ *\n+ * This is mostly called after some Tasks are queued into the Scheduler.\n+ */\n+void Scheduler::schedule()\n+{\n+\tfor (auto it = pendingTasks_.begin(); it != pendingTasks_.end();) {\n+\t\tauto *task = *it;\n+\t\tif (!task->precedents_.empty()) {\n+\t\t\tit++;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\trunningTasks_.emplace(task);\n+\t\tit = pendingTasks_.erase(it);\n+\n+\t\ttask->launch();\n+\t}\n+}\n+\n+/**\n+ * \\brief Log all non-finished Tasks, grouped by the group ids\n+ */\n+void Scheduler::log()\n+{\n+\tstd::stringstream ss;\n+\tfor (auto &[group, name] : groupNames_)\n+\t\tss << name << \"[\" << groupTasks_[group].size() << \"] \";\n+\n+\tLOG(Task, Info) << ss.str();\n+}\n+\n+/**\n+ * \\brief Queues \\a task into the Scheduler, which takes the ownership of the\n+ * Task\n+ * \\param[in] task The task being queued and will be executed\n+ * \\param[in] group The group ID that the Task belongs to\n+ *\n+ * Note that \\a task is passed as a raw pointer, to make the developer create\n+ * and pass Task pointers more easily without holding both raw pointers and\n+ * unique pointers. Scheduler will then construct a std::unique_ptr to take the\n+ * ownership.\n+ *\n+ * The task will then be held as a pending task. The user of Scheduler should\n+ * call Scheduler::schedule() to make new tasks without dependencies start to\n+ * be executed. A Task with dependencies will be executed when its last\n+ * precedent Task is done.\n+ */\n+void Scheduler::queueTask(Task *task, int32_t group)\n+{\n+\t/* \\todo: Detect cyclic dependency */\n+\ttasksHolder_.emplace(task, std::unique_ptr<Task>(task));\n+\n+\tpendingTasks_.emplace(task);\n+\tgroupTasks_[group].emplace_back(task);\n+}\n+\n+/**\n+ * \\brief Sets a dependency from the \\a step th latest Task in group \\a group\n+ * to \\a task\n+ * \\param[in] group The group ID\n+ * \\param[in] step The number of Tasks to trace back, 0 being the latest one\n+ * \\param[in] task The Task that needs to add a dependency\n+ *\n+ * If the number of Tasks in group \\a group is less than or equals to \\a step,\n+ * such a dependent Task doesn't exist, and no dependency will be added.\n+ */\n+void Scheduler::succeedPrevTaskByStep(int32_t group, size_t step, Task *task)\n+{\n+\tASSERT(task);\n+\n+\tauto &tasks = groupTasks_[group];\n+\tif (tasks.size() <= step)\n+\t\treturn;\n+\n+\tauto iter = tasks.rbegin();\n+\tfor (size_t i = 0; i < step; i++)\n+\t\titer++;\n+\n+\tprecede(*iter, task);\n+}\n+\n+/**\n+ * \\brief Return a list of unfinished Tasks that belong to the group ID\n+ * \\param[in] group The group ID\n+ */\n+std::list<Task *> &Scheduler::groupTasks(int32_t group)\n+{\n+\treturn groupTasks_[group];\n+}\n+\n+/**\n+ * \\var Scheduler::groupNames_\n+ * \\brief The map from group ID to group name\n+ *\n+ * Should be set by the Scheduler's derived class\n+ */\n+\n+void Scheduler::removeFromGroupTasks(Task *task)\n+{\n+\tfor (auto &[group, tasks] : groupTasks_)\n+\t\ttasks.remove(task);\n+}\n+\n+void Scheduler::taskDone(Task *task)\n+{\n+\t/* Sample execution time of the task, from launch to notifyDone */\n+\tstd::chrono::milliseconds milliseconds =\n+\t\tstd::chrono::duration_cast<std::chrono::milliseconds>(\n+\t\t\tstd::chrono::steady_clock::now() - task->launchTime_);\n+\n+\tLOG(Task, Debug) << \"Task \" << task->id() << \" executed in \"\n+\t\t\t << milliseconds.count() << \"ms\";\n+\n+\ttaskDone_.emit(task);\n+\n+\trunningTasks_.erase(task);\n+\tremoveFromGroupTasks(task);\n+\n+\tfor (auto *succedent : task->succedents_) {\n+\t\tif (0 == succedent->removeDependency(task)) {\n+\t\t\trunningTasks_.emplace(succedent);\n+\t\t\tpendingTasks_.erase(succedent);\n+\n+\t\t\tsuccedent->launch();\n+\t\t}\n+\t}\n+\n+\ttasksHolder_.erase(task);\n+}\n+\n+/**\n+ * \\class CategorizedScheduler\n+ * \\brief A Scheduler that uses an enum Category as the Task's group ID\n+ */\n+\n+/**\n+ * \\fn CategorizedScheduler::CategorizedScheduler(\n+ * const std::map<Category, std::string> &categoryNames)\n+ * \\brief Construct a CategorizedScheduler with category names\n+ * \\param[in] categoryNames The map from enum group IDs to group names\n+ */\n+\n+/**\n+ * \\fn void CategorizedScheduler::queueTask(Task *task, Category group)\n+ * \\brief Call Scheduler::queueTask with Category enum as the group ID\n+ * \\param[in] task The task being queued and will be executed\n+ * \\param[in] group The group ID in enum Category that the Task belongs to\n+ */\n+\n+/**\n+ * \\fn std::list<Task *> &CategorizedScheduler::groupTasks(Category group)\n+ * \\brief Call Scheduler::groupTasks with Category enum as the group ID\n+ * \\param[in] group The group ID in enum Category\n+ */\n+\n+/**\n+ * \\fn void CategorizedScheduler::succeedPrevTaskByStep(Category group, size_t step, Task *task)\n+ * \\brief Call Scheduler::succeedPrevTaskByStep with Category enum as the group ID\n+ * \\param[in] group The group ID in enum Category\n+ * \\param[in] step The number of Tasks to trace back, 0 being the latest one\n+ * \\param[in] task The Task that needs to add a dependency\n+ */\n+\n+} /* namespace libcamera */\n", "prefixes": [ "v1", "1/1" ] }