From c84671272a9d277546d11919728600a83eee746c Mon Sep 17 00:00:00 2001 From: Simone Rondelli Date: Fri, 25 Feb 2022 16:09:39 +0100 Subject: [PATCH 1/2] Create PoC of SLAM functionality using Gaia --- gaia_slam/.clang-format | 112 ++++++++++++++++ gaia_slam/.clang-tidy | 77 +++++++++++ gaia_slam/.gitignore | 4 + gaia_slam/CMakeLists.txt | 75 +++++++++++ gaia_slam/gaia/gaia_slam.ddl | 66 +++++++++ gaia_slam/gaia/gaia_slam.ruleset | 191 +++++++++++++++++++++++++++ gaia_slam/include/graph.hpp | 24 ++++ gaia_slam/include/vertex_types.hpp | 20 +++ gaia_slam/src/graph.cpp | 64 +++++++++ gaia_slam/src/main_direct_access.cpp | 102 ++++++++++++++ gaia_slam/src/main_rules.cpp | 118 +++++++++++++++++ 11 files changed, 853 insertions(+) create mode 100644 gaia_slam/.clang-format create mode 100644 gaia_slam/.clang-tidy create mode 100644 gaia_slam/.gitignore create mode 100644 gaia_slam/CMakeLists.txt create mode 100644 gaia_slam/gaia/gaia_slam.ddl create mode 100644 gaia_slam/gaia/gaia_slam.ruleset create mode 100644 gaia_slam/include/graph.hpp create mode 100644 gaia_slam/include/vertex_types.hpp create mode 100644 gaia_slam/src/graph.cpp create mode 100644 gaia_slam/src/main_direct_access.cpp create mode 100644 gaia_slam/src/main_rules.cpp diff --git a/gaia_slam/.clang-format b/gaia_slam/.clang-format new file mode 100644 index 0000000..8bb94e5 --- /dev/null +++ b/gaia_slam/.clang-format @@ -0,0 +1,112 @@ +BasedOnStyle: LLVM +UseCRLF: false +UseTab: Never +IndentWidth: 4 +ColumnLimit: 0 +# The following configuration encodes the BreakBeforeBraces Allman style +# but for BeforeLambdaBody to allow one-liner lambdas. +# See discussion here about customizing BreakBeforeBraces: https://reviews.llvm.org/D94906#2505095. +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: Always + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true + +# Force pointers to the type for C++. +DerivePointerAlignment: false +PointerAlignment: Left + +# Align public/final/protected on the left. +AccessModifierOffset: -4 + +# From clang-format-11 there are more fine grained options for this setting. +AlignOperands: DontAlign +BreakBeforeBinaryOperators: None +BreakBeforeTernaryOperators: true + +AllowAllConstructorInitializersOnNextLine: true +ConstructorInitializerAllOnOneLineOrOnePerLine: true +BreakConstructorInitializers: BeforeComma +ConstructorInitializerIndentWidth: 4 + +AllowShortFunctionsOnASingleLine: None +# Merge lambda into a single line if argument of a function. +AllowShortLambdasOnASingleLine: Inline +# (clang-format-13) Align lambda body relative to the indentation level of the outer scope the lambda signature resides in. +# LambdaBodyIndentation: OuterScope + +# If false, all arguments will either be all on the same line or will have one line each. +BinPackArguments: true +BinPackParameters: true +AlignAfterOpenBracket: DontAlign +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true + +# Merge multiple #include blocks together and sort as one. Then split into groups based on category priority: +# 1. Associated file header (XYZ.hpp) +# 2. C system headers. (Priority: 1-2) +# 3. C++ standard library headers (Priority: 5) +# 4. third party library headers (Priority: 10) +# 5. your other project headers (Priority: 20-22 + 30) +# Note this could break the code, read: https://stackoverflow.com/questions/37927553/can-clang-format-break-my-code +IncludeBlocks: Regroup +IncludeCategories: + # Public Gaia headers. + - Regex: 'gaia\/' + Priority: 20 + # Internal Gaia headers. + - Regex: 'gaia_internal\/' + Priority: 21 + # Internal Gaia headers. + - Regex: 'gaia_spdlog\/' + Priority: 22 + # Internal Gaia headers. + - Regex: 'gaia_spdlog_setup\/' + Priority: 22 + # Third-party headers. + - Regex: '[flatbuffers|gtest|libexplain|llvm|pybind11|rocksdb|spdlog|tabulate]\/' + Priority: 10 + SortPriority: 10 + - Regex: 'backward' + Priority: 10 + SortPriority: 11 + - Regex: 'cpptoml' + Priority: 10 + SortPriority: 12 + - Regex: 'liburing' + Priority: 10 + SortPriority: 13 + - Regex: 'json\.hpp' + Priority: 10 + SortPriority: 14 + # C system headers. + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: '^ + +#include + +// Rule examples. These examples are in two categories. The first shows +// the basics about rules, including how they're called and how data +// can be accessed from within a rule. The second shows rules being +// called that create new records, in turn inducing additional rules to +// fire. + + +// This ruleset provides example rules showing how rules are defined and +// how data can be accessed within a rule. +ruleset gaia_slam_rule_examples +{ + // Explicit rules + // These rules fire when a record is inserted and/or modified. + // Each rule is called once each change/insertion, with a + // reference to that record being passed in as a parameter. + // + // These rules are: + // on_insert() fired when a new record is created + // on_update() fired when a record or field is updated + // on_change() fired when a record is created or updated + + + // Record insertion + on_insert(vertex) + { + gaia_log::app().info("Inserted vertex record with ID {}", vertex.id); + } + + // NOTE that having 2 on_insert rules for the same table results + // in both rules firing. + + + // Record updates + // An 'on_update' rule fires whenever there is an update to the contents + // of any field in a record. + on_update(vertex) + { + // When referencing fields in a record, if the field name is + // unique then it can be used directly, w/o specifying + // the table (e.g., 'pose_x') while if a record + // occurs in different tables (e.g., 'id') then the table + // must be specified (e.g. 'vertex.id'). + gaia_log::app().info("Vertex {} has updated pose_x to {}", vertex.id, pose_x); + } + + // Update rules can be for records or for specific fields within a + // record. For example, here's a rule strictly for the vertex + // 'pose_y' field. + on_update(vertex.pose_y) + { + gaia_log::app().info("Vertex {} has updated pose_y to {}", vertex.id, vertex.pose_y); + } + + // Record change (i.e., insertion or update) + on_change(edge) + { + // Fired for any insert or update to the edge table. + gaia_log::app().info("Edge {} has been inserted/updated!", edge.id); + } + + + // Using tags + // Tags are a form of shorthand letting you use a shorter label + // to describe a longer table name. In the contexts of 'for' + // loops and 'on_xxxxxx()' functions, a tag is defined by placing + // it before a colon, e.g., :. E.g., + + // Use the tag 'e' for the table name 'edge'. + on_change(e: edge) + { + gaia_log::app().info("Edge (e) {} has been inserted/updated!", e.id); + } + + + // Following references between tables. + // The symbol "->" + // The field in a record is accessed using .. When + // following the reference to another table, the "->" operator + // is used. For example (table)->(referenced table). + + on_change(v: vertex) + { + gaia_log::app().info("Vertex {} is part of graph {}", v.id, v->graph.uuid); + } + + + // Iterating through a 1:n relationship + // + // This example combines tags and following references between + // tables to list the entries in B that is liked to by table A + // as a result in changes to table A. + + on_update(graph) // Graph record has changed. + { + // Iterate through graph record's vertices. This is done with + // a 'for' loop over the graph's 'vertices' reference. + std::stringstream ss; + ss << "Graph " << graph.uuid << " updated. Listing vertices:\n"; + for (graph.vertices->vertex) + { + ss << " " << vertex.id << "\n"; + } + gaia_log::app().info(ss.str().c_str()); + } + + // Here's an example of the preceding function using tags + on_update(g: graph) // Graph record 'g' has changed. + { + std::stringstream ss; + ss << "Graph " << g.uuid << " vertices:\n"; + for (g.vertices->v:vertex) + { + ss << " " << v.id << "\n"; + } + gaia_log::app().info(ss.str().c_str()); + } +} + + +// Rule examples where the creates other records. +// The serial_group() keyword makes the rules in the ruleset to run serially: +// only one rule is executed in a given point in time. +ruleset event_rule_example : serial_group() +{ + // When a new graph record is inserted, create a vertex. + // Note that the newly created vertex will induce the + // "on_insert(vertex)" rule(s) to fire. + on_insert(graph) + { + int64_t vertex_id = random(); + gaia_log::app().info("Graph {} creating vertex with ID {}", graph.uuid, vertex_id); + + // Create a new vertex record + vertex.insert( + id: vertex_id, + graph_uuid: graph.uuid + ); + } + + // Here's an example with slightly more processing. The paradigm here + // is that incoming data has arrived, and that arrival in turn + // resulted in an entry being created in the incoming_data_event + // table, storing that data. + // For this example, the data stored in this event is used to + // create a new vertex. In the process, the vertex table is scanned + // to find then next lowest ID, showing how to traverse an entire + // table. + on_insert(incoming_data_event) + { + gaia_log::app().info("Processing incoming_data_event with ID {}", id); + + // The loop below finds the maximum id value for existing edge ids. + // In practice, we wouldn't search the table each time to look for + // a new ID. This is only done to provide an example of searching + // a table. + // Note that this uses a new syntax, modified from the 'tag' + // syntax described above. In this case the leading slash '/' means + // to search through all records in the specified table. + uint64_t max_vertex_id = 0; + for (/V:vertex) + { + if (V.id > max_vertex_id) + { + max_vertex_id = V.id; + } + } + + // Create a vertex record + std::string graph_uuid_string("aaaaaaaa-0000-0000-0000-000000000000"); + auto new_vertex = vertex.insert( + id: max_vertex_id + 1, + type: incoming_data_event.type, + data: incoming_data_event.data, + pose_x: incoming_data_event.pose_x, + pose_y: incoming_data_event.pose_y, + graph_uuid: graph_uuid_string.c_str() + ); + } +} diff --git a/gaia_slam/include/graph.hpp b/gaia_slam/include/graph.hpp new file mode 100644 index 0000000..e71c640 --- /dev/null +++ b/gaia_slam/include/graph.hpp @@ -0,0 +1,24 @@ +//////////////////////////////////////////////////// +// Copyright (c) Gaia Platform LLC +// +// Use of this source code is governed by the MIT +// license that can be found in the LICENSE.txt file +// or at https://opensource.org/licenses/MIT. +//////////////////////////////////////////////////// + +#pragma once + +#include + +namespace gaia::gaia_slam::graph +{ + +graph_t create_graph(const std::string& uuid); + +vertex_t create_vertex(const graph_t& graph, int64_t id, int64_t vertex_type); + +edge_t create_edge(int64_t id, const vertex_t& src, const vertex_t& dest); + +void clear_data(); + +} // namespace gaia::gaia_slam::graph diff --git a/gaia_slam/include/vertex_types.hpp b/gaia_slam/include/vertex_types.hpp new file mode 100644 index 0000000..fb34671 --- /dev/null +++ b/gaia_slam/include/vertex_types.hpp @@ -0,0 +1,20 @@ +//////////////////////////////////////////////////// +// Copyright (c) Gaia Platform LLC +// +// Use of this source code is governed by the MIT +// license that can be found in the LICENSE.txt file +// or at https://opensource.org/licenses/MIT. +//////////////////////////////////////////////////// + +#pragma once + +#include + +namespace gaia::gaia_slam::vertex_type +{ + +static constexpr uint8_t c_lidar_scan = 0; +static constexpr uint8_t c_image_keyframe = 1; +static constexpr uint8_t c_visual_landmark = 2; + +} // namespace gaia::gaia_slam::vertex_type diff --git a/gaia_slam/src/graph.cpp b/gaia_slam/src/graph.cpp new file mode 100644 index 0000000..0836bb3 --- /dev/null +++ b/gaia_slam/src/graph.cpp @@ -0,0 +1,64 @@ +//////////////////////////////////////////////////// +// Copyright (c) Gaia Platform LLC +// +// Use of this source code is governed by the MIT +// license that can be found in the LICENSE.txt file +// or at https://opensource.org/licenses/MIT. +//////////////////////////////////////////////////// + +#include "graph.hpp" + +namespace gaia::gaia_slam::graph +{ + +graph_t create_graph(const std::string& uuid) +{ + return graph_t::get(graph_t::insert_row(uuid.c_str())); +} + +vertex_t create_vertex(const graph_t& graph, int64_t id, int64_t vertex_type) +{ + vertex_writer vertex_w; + vertex_w.id = id; + vertex_w.type = vertex_type; + vertex_w.graph_uuid = graph.uuid(); + + return vertex_t::get(vertex_w.insert_row()); +} + +edge_t create_edge(int64_t id, const vertex_t& src, const vertex_t& dest) +{ + if (src.graph_uuid() == dest.graph_uuid()) + { + throw std::runtime_error("You cannot create an edge from vertexes in different graphs!"); + } + + edge_writer edge_w; + edge_w.id = id; + edge_w.src_id = src.id(); + edge_w.dest_id = dest.id(); + edge_w.graph_uuid = src.graph_uuid(); + + return edge_t::get(edge_w.insert_row()); +} + +template +void clear_table() +{ + for (auto obj_it = T_type::list().begin(); + obj_it != T_type::list().end();) + { + auto next_obj_it = obj_it++; + next_obj_it->delete_row(); + } +} + +void clear_data() +{ + clear_table(); + clear_table(); + clear_table(); + clear_table(); +} + +} // namespace gaia::gaia_slam::graph diff --git a/gaia_slam/src/main_direct_access.cpp b/gaia_slam/src/main_direct_access.cpp new file mode 100644 index 0000000..4cf115c --- /dev/null +++ b/gaia_slam/src/main_direct_access.cpp @@ -0,0 +1,102 @@ +//////////////////////////////////////////////////// +// Copyright (c) Gaia Platform LLC +// +// Use of this source code is governed by the MIT +// license that can be found in the LICENSE.txt file +// or at https://opensource.org/licenses/MIT. +//////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +#include "gaia_gaia_slam.h" +#include "graph.hpp" +#include "vertex_types.hpp" + +using namespace gaia::direct_access; +using namespace gaia::gaia_slam; +using namespace gaia::gaia_slam::graph; + +int main() +{ + gaia::system::initialize(); + + // This examples wants to focus on direct_access hence the rules are disabled. + gaia::rules::unsubscribe_rules(); + + // The no_auto_restart argument prevents beginning a new transaction + // when the current one is committed. + auto_transaction_t txn{auto_transaction_t::no_auto_restart}; + + clear_data(); + + graph_t graph = create_graph("graph_1"); + + vertex_t v1 = create_vertex(graph, 1, vertex_type::c_lidar_scan); + vertex_t v2 = create_vertex(graph, 2, vertex_type::c_lidar_scan); + vertex_t v3 = create_vertex(graph, 3, vertex_type::c_visual_landmark); + + edge_t e1 = create_edge(1, v1, v2); + edge_t e2 = create_edge(2, v2, v3); + + // Get all graph vertexes and edges. + + gaia_log::app().info("=== Printing all vertexes from Graph({}) ===", graph.uuid()); + + for (const vertex_t& v : graph.vertices()) + { + gaia_log::app().info(" Vertex({})", v.id()); + } + + gaia_log::app().info("=== Printing all edges from Graph({}) ===", graph.uuid()); + + for (const edge_t& e : graph.edges()) + { + gaia_log::app().info(" Edge({}): ({}) --> ({})", e.id(), e.src().id(), e.dest().id()); + } + + gaia_log::app().info("=== Get all vertexes with type lidar_scan ===", graph.uuid()); + + for (const vertex_t& v : vertex_t::list() + .where(vertex_expr::type == vertex_type::c_lidar_scan)) + { + gaia_log::app().info(" Vertex({})", v.id()); + } + + gaia_log::app().info("=== Get the vertex with id 2 and type lidar_scan ===", graph.uuid()); + + auto vertex_it = vertex_t::list() + .where( + vertex_expr::id == 2 && + vertex_expr::type == vertex_type::c_lidar_scan); + + if (vertex_it.begin() == vertex_it.end()) + { + throw std::runtime_error("Impossible to find vertex with id 1 and type lidar_scan"); + } + + gaia_log::app().info(" Vertex({})", vertex_it.begin()->id()); + + gaia_log::app().info("=== Get all edges with src_id or dest_id equals 1 ===", graph.uuid()); + + auto edge_it = edge_t::list() + .where( + edge_expr::dest == v1 || + edge_expr::src == v1); + + for (const edge_t& e : edge_it) + { + gaia_log::app().info(" Edge({}): ({}) --> ({})", e.id(), e.src().id(), e.dest().id()); + } + + txn.commit(); + + /// + /// Now you can write your additional logic to use the direct_access API. + /// + + gaia::system::shutdown(); +} diff --git a/gaia_slam/src/main_rules.cpp b/gaia_slam/src/main_rules.cpp new file mode 100644 index 0000000..c835c5f --- /dev/null +++ b/gaia_slam/src/main_rules.cpp @@ -0,0 +1,118 @@ +//////////////////////////////////////////////////// +// Copyright (c) Gaia Platform LLC +// +// Use of this source code is governed by the MIT +// license that can be found in the LICENSE.txt file +// or at https://opensource.org/licenses/MIT. +//////////////////////////////////////////////////// + +#include + +#include + +#include +#include +#include + +#include "gaia_gaia_slam.h" +#include "graph.hpp" +#include "vertex_types.hpp" + +using namespace gaia::direct_access; +using namespace gaia::gaia_slam; +using namespace gaia::gaia_slam::graph; + +using std::this_thread::sleep_for; + +constexpr size_t c_num_data_events = 5; +constexpr uint32_t c_rule_wait_millis = 100; + +/** + * Wait an arbitrary amount of time for rule execution to terminate. + * Rules are triggered after commit and can take some time to fully execute. + */ +void wait_for_rules() +{ + sleep_for(std::chrono::milliseconds(c_rule_wait_millis)); +} + +int main() +{ + gaia::system::initialize(); + + // We explicitly handle the transactions with begin_transaction() and commit_transaction() + // to trigger the rules. + gaia::db::begin_transaction(); + clear_data(); + gaia::db::commit_transaction(); + + gaia_log::app().info("=== Creates a new Graph and observe the corresponding rules triggered ==="); + + // This code triggers the following rules: + // 1. on_insert(graph): triggered when a graph is created and creates a random vertex (which triggers the next rule). + // 2. on_insert(vertex): triggered when a vertex is created. + // 3. on_change(v: vertex): Triggered when a vertex is created or updated. + gaia::db::begin_transaction(); + graph_t graph = create_graph("graph_1"); + gaia::db::commit_transaction(); + + wait_for_rules(); + + gaia_log::app().info("=== Updates a vertex and observe the corresponding rules triggered ==="); + + // This code triggers the following rules: + // 1. on_update(vertex): triggered when any vertex field is updated. + // 2. on_update(vertex.confidence): triggered when the confidence field is updated. + // 3. on_change(v: vertex): Triggered when a vertex is created or updated. + gaia::db::begin_transaction(); + vertex_t vertex1 = *vertex_t::list().begin(); + vertex_writer vertex_w = vertex1.writer(); + vertex_w.pose_y = 0.4; + vertex_w.pose_x = 0.1; + vertex_w.update_row(); + gaia::db::commit_transaction(); + + wait_for_rules(); + + gaia_log::app().info("=== Inserting {} incoming_data_event of type {} ===", c_num_data_events, vertex_type::c_lidar_scan); + + // This code triggers the following rules: + // 1. on_insert(incoming_data_event): triggered when an incoming_data_event is inserted. This insert a new vertex. + // 2. All the rules triggered on vertex insertion. + gaia::db::begin_transaction(); + for (int i = 0; i < c_num_data_events; i++) + { + incoming_data_event_writer data_w; + data_w.id = i; + data_w.data = {1, 2, 3, 4, 5}; + data_w.pose_x = i * 2; + data_w.pose_y = i * 3; + data_w.type = vertex_type::c_image_keyframe; + data_w.insert_row(); + } + gaia::db::commit_transaction(); + + wait_for_rules(); + + gaia_log::app().info("=== Retrieving vertexes/edges with type {} ===", vertex_type::c_image_keyframe); + + // This code verify that the vertex with type vertex_type::c_image_keyframe has been created. + gaia::db::begin_transaction(); + for (const vertex_t& v : vertex_t::list() + .where(vertex_expr::type == vertex_type::c_image_keyframe)) + { + gaia_log::app().info("Vertex(id:{} type:{} pose_x:{} pose_y:{})", v.id(), v.type(), v.pose_x(), v.pose_y()); + } + gaia::db::commit_transaction(); + + wait_for_rules(); + + /// + /// Now you can write your additional logic to trigger the other rules, + /// or write some new rules! + /// + /// For instance, you may want to create edges between vertexes.. + /// + + gaia::system::shutdown(); +} From bafc2d3046b5dcd8a5cd853ae460a6869e9c5dec Mon Sep 17 00:00:00 2001 From: Simone Rondelli Date: Mon, 28 Feb 2022 23:59:38 +0100 Subject: [PATCH 2/2] Address PR feedback --- gaia_slam/.clang-format | 112 -------------- gaia_slam/.clang-tidy | 77 ---------- gaia_slam/.gitignore | 2 + gaia_slam/CMakeLists.txt | 1 - gaia_slam/README.md | 11 ++ gaia_slam/gaia/gaia_slam.ddl | 19 ++- gaia_slam/gaia/gaia_slam.ruleset | 214 +++++++-------------------- gaia_slam/src/graph.cpp | 12 +- gaia_slam/src/main_direct_access.cpp | 24 +-- gaia_slam/src/main_rules.cpp | 41 ++--- 10 files changed, 109 insertions(+), 404 deletions(-) delete mode 100644 gaia_slam/.clang-format delete mode 100644 gaia_slam/.clang-tidy create mode 100644 gaia_slam/README.md diff --git a/gaia_slam/.clang-format b/gaia_slam/.clang-format deleted file mode 100644 index 8bb94e5..0000000 --- a/gaia_slam/.clang-format +++ /dev/null @@ -1,112 +0,0 @@ -BasedOnStyle: LLVM -UseCRLF: false -UseTab: Never -IndentWidth: 4 -ColumnLimit: 0 -# The following configuration encodes the BreakBeforeBraces Allman style -# but for BeforeLambdaBody to allow one-liner lambdas. -# See discussion here about customizing BreakBeforeBraces: https://reviews.llvm.org/D94906#2505095. -BreakBeforeBraces: Custom -BraceWrapping: - AfterCaseLabel: true - AfterClass: true - AfterControlStatement: Always - AfterEnum: true - AfterFunction: true - AfterNamespace: true - AfterObjCDeclaration: true - AfterStruct: true - AfterUnion: true - AfterExternBlock: true - BeforeCatch: true - BeforeElse: true - BeforeLambdaBody: false - BeforeWhile: false - IndentBraces: false - SplitEmptyFunction: true - SplitEmptyRecord: true - SplitEmptyNamespace: true - -# Force pointers to the type for C++. -DerivePointerAlignment: false -PointerAlignment: Left - -# Align public/final/protected on the left. -AccessModifierOffset: -4 - -# From clang-format-11 there are more fine grained options for this setting. -AlignOperands: DontAlign -BreakBeforeBinaryOperators: None -BreakBeforeTernaryOperators: true - -AllowAllConstructorInitializersOnNextLine: true -ConstructorInitializerAllOnOneLineOrOnePerLine: true -BreakConstructorInitializers: BeforeComma -ConstructorInitializerIndentWidth: 4 - -AllowShortFunctionsOnASingleLine: None -# Merge lambda into a single line if argument of a function. -AllowShortLambdasOnASingleLine: Inline -# (clang-format-13) Align lambda body relative to the indentation level of the outer scope the lambda signature resides in. -# LambdaBodyIndentation: OuterScope - -# If false, all arguments will either be all on the same line or will have one line each. -BinPackArguments: true -BinPackParameters: true -AlignAfterOpenBracket: DontAlign -AllowAllArgumentsOnNextLine: true -AllowAllParametersOfDeclarationOnNextLine: true - -# Merge multiple #include blocks together and sort as one. Then split into groups based on category priority: -# 1. Associated file header (XYZ.hpp) -# 2. C system headers. (Priority: 1-2) -# 3. C++ standard library headers (Priority: 5) -# 4. third party library headers (Priority: 10) -# 5. your other project headers (Priority: 20-22 + 30) -# Note this could break the code, read: https://stackoverflow.com/questions/37927553/can-clang-format-break-my-code -IncludeBlocks: Regroup -IncludeCategories: - # Public Gaia headers. - - Regex: 'gaia\/' - Priority: 20 - # Internal Gaia headers. - - Regex: 'gaia_internal\/' - Priority: 21 - # Internal Gaia headers. - - Regex: 'gaia_spdlog\/' - Priority: 22 - # Internal Gaia headers. - - Regex: 'gaia_spdlog_setup\/' - Priority: 22 - # Third-party headers. - - Regex: '[flatbuffers|gtest|libexplain|llvm|pybind11|rocksdb|spdlog|tabulate]\/' - Priority: 10 - SortPriority: 10 - - Regex: 'backward' - Priority: 10 - SortPriority: 11 - - Regex: 'cpptoml' - Priority: 10 - SortPriority: 12 - - Regex: 'liburing' - Priority: 10 - SortPriority: 13 - - Regex: 'json\.hpp' - Priority: 10 - SortPriority: 14 - # C system headers. - - Regex: '^<.*\.h>' - Priority: 1 - - Regex: '^ +#include #include -// Rule examples. These examples are in two categories. The first shows -// the basics about rules, including how they're called and how data -// can be accessed from within a rule. The second shows rules being -// called that create new records, in turn inducing additional rules to -// fire. +constexpr uint64_t c_first_vertex_id = 0; +std::atomic c_last_edge_id = 0; - -// This ruleset provides example rules showing how rules are defined and -// how data can be accessed within a rule. -ruleset gaia_slam_rule_examples +// The serial_group() keyword makes the rules in the ruleset run serially: +// only one rule is executed at a time. This prevents transaction +// conflicts when modifying the same data. +ruleset data_processing : serial_group() { - // Explicit rules - // These rules fire when a record is inserted and/or modified. - // Each rule is called once each change/insertion, with a - // reference to that record being passed in as a parameter. - // - // These rules are: - // on_insert() fired when a new record is created - // on_update() fired when a record or field is updated - // on_change() fired when a record is created or updated - - - // Record insertion - on_insert(vertex) - { - gaia_log::app().info("Inserted vertex record with ID {}", vertex.id); - } - - // NOTE that having 2 on_insert rules for the same table results - // in both rules firing. - - - // Record updates - // An 'on_update' rule fires whenever there is an update to the contents - // of any field in a record. - on_update(vertex) - { - // When referencing fields in a record, if the field name is - // unique then it can be used directly, w/o specifying - // the table (e.g., 'pose_x') while if a record - // occurs in different tables (e.g., 'id') then the table - // must be specified (e.g. 'vertex.id'). - gaia_log::app().info("Vertex {} has updated pose_x to {}", vertex.id, pose_x); - } - - // Update rules can be for records or for specific fields within a - // record. For example, here's a rule strictly for the vertex - // 'pose_y' field. - on_update(vertex.pose_y) - { - gaia_log::app().info("Vertex {} has updated pose_y to {}", vertex.id, vertex.pose_y); - } - - // Record change (i.e., insertion or update) - on_change(edge) - { - // Fired for any insert or update to the edge table. - gaia_log::app().info("Edge {} has been inserted/updated!", edge.id); - } - - - // Using tags - // Tags are a form of shorthand letting you use a shorter label - // to describe a longer table name. In the contexts of 'for' - // loops and 'on_xxxxxx()' functions, a tag is defined by placing - // it before a colon, e.g., :. E.g., - - // Use the tag 'e' for the table name 'edge'. - on_change(e: edge) - { - gaia_log::app().info("Edge (e) {} has been inserted/updated!", e.id); - } - - - // Following references between tables. - // The symbol "->" - // The field in a record is accessed using .. When - // following the reference to another table, the "->" operator - // is used. For example (table)->(referenced table). - on_change(v: vertex) - { - gaia_log::app().info("Vertex {} is part of graph {}", v.id, v->graph.uuid); - } - - - // Iterating through a 1:n relationship - // - // This example combines tags and following references between - // tables to list the entries in B that is liked to by table A - // as a result in changes to table A. - - on_update(graph) // Graph record has changed. - { - // Iterate through graph record's vertices. This is done with - // a 'for' loop over the graph's 'vertices' reference. - std::stringstream ss; - ss << "Graph " << graph.uuid << " updated. Listing vertices:\n"; - for (graph.vertices->vertex) - { - ss << " " << vertex.id << "\n"; - } - gaia_log::app().info(ss.str().c_str()); - } - - // Here's an example of the preceding function using tags - on_update(g: graph) // Graph record 'g' has changed. - { - std::stringstream ss; - ss << "Graph " << g.uuid << " vertices:\n"; - for (g.vertices->v:vertex) - { - ss << " " << v.id << "\n"; - } - gaia_log::app().info(ss.str().c_str()); - } -} - - -// Rule examples where the creates other records. -// The serial_group() keyword makes the rules in the ruleset to run serially: -// only one rule is executed in a given point in time. -ruleset event_rule_example : serial_group() -{ - // When a new graph record is inserted, create a vertex. - // Note that the newly created vertex will induce the - // "on_insert(vertex)" rule(s) to fire. - on_insert(graph) - { - int64_t vertex_id = random(); - gaia_log::app().info("Graph {} creating vertex with ID {}", graph.uuid, vertex_id); - - // Create a new vertex record - vertex.insert( - id: vertex_id, - graph_uuid: graph.uuid - ); - } - - // Here's an example with slightly more processing. The paradigm here - // is that incoming data has arrived, and that arrival in turn - // resulted in an entry being created in the incoming_data_event - // table, storing that data. - // For this example, the data stored in this event is used to - // create a new vertex. In the process, the vertex table is scanned - // to find then next lowest ID, showing how to traverse an entire - // table. + // Process an incoming data event by creating a new vertex in the graph. on_insert(incoming_data_event) { - gaia_log::app().info("Processing incoming_data_event with ID {}", id); + gaia_log::app().info("Processing incoming_data_event(id: {})", id); // The loop below finds the maximum id value for existing edge ids. // In practice, we wouldn't search the table each time to look for // a new ID. This is only done to provide an example of searching // a table. - // Note that this uses a new syntax, modified from the 'tag' - // syntax described above. In this case the leading slash '/' means - // to search through all records in the specified table. - uint64_t max_vertex_id = 0; - for (/V:vertex) + // The leading slash '/' means to search through all records in the + // specified table. + uint64_t max_vertex_id = c_first_vertex_id; + for (/v:vertex) { - if (V.id > max_vertex_id) + if (v.id > max_vertex_id) { - max_vertex_id = V.id; + max_vertex_id = v.id; } } + uint64_t new_vertex_id = max_vertex_id + 1; + gaia_log::app().info("Creating new vertex(id: {})", new_vertex_id); + // Create a vertex record - std::string graph_uuid_string("aaaaaaaa-0000-0000-0000-000000000000"); - auto new_vertex = vertex.insert( - id: max_vertex_id + 1, + std::string graph_id_string("graph_1"); + vertex.insert( + id: new_vertex_id, type: incoming_data_event.type, data: incoming_data_event.data, pose_x: incoming_data_event.pose_x, pose_y: incoming_data_event.pose_y, - graph_uuid: graph_uuid_string.c_str() + graph_id: graph_id_string.c_str() ); } + + // Process each new vertex by creating an edge with the previous existing vertex. + on_insert(vertex) + { + gaia_log::app().info("Processing vertex(id: {})", id); + + if (vertex.id == c_first_vertex_id) + { + return; + } + + uint64_t prev_vertex_id = vertex.id - 1; + uint64_t new_edge_id = c_last_edge_id.fetch_add(1); + gaia_log::app().info( + "Creating edge(id: {}) with between vertex(id: {}) and vertex(id: {})", + new_edge_id, prev_vertex_id, vertex.id); + + edge.insert( + id: new_edge_id, + graph_id: vertex.graph_id, + src_id: prev_vertex_id, + dest_id: vertex.id); + } +} + +ruleset graph_processing +{ + // Reacts to the creation of an edge by just printing it. + // This rule could contain part of the SALM algorithm. + on_insert(edge) + { + gaia_log::app().info( + "Created edge(id: {}) vertex(id: {}) -> vertex(id: {})", + edge.id, edge.src_id, edge.dest_id); + } } diff --git a/gaia_slam/src/graph.cpp b/gaia_slam/src/graph.cpp index 0836bb3..f116b92 100644 --- a/gaia_slam/src/graph.cpp +++ b/gaia_slam/src/graph.cpp @@ -11,9 +11,9 @@ namespace gaia::gaia_slam::graph { -graph_t create_graph(const std::string& uuid) +graph_t create_graph(const std::string& id) { - return graph_t::get(graph_t::insert_row(uuid.c_str())); + return graph_t::get(graph_t::insert_row(id.c_str())); } vertex_t create_vertex(const graph_t& graph, int64_t id, int64_t vertex_type) @@ -21,23 +21,23 @@ vertex_t create_vertex(const graph_t& graph, int64_t id, int64_t vertex_type) vertex_writer vertex_w; vertex_w.id = id; vertex_w.type = vertex_type; - vertex_w.graph_uuid = graph.uuid(); + vertex_w.graph_id = graph.id(); return vertex_t::get(vertex_w.insert_row()); } edge_t create_edge(int64_t id, const vertex_t& src, const vertex_t& dest) { - if (src.graph_uuid() == dest.graph_uuid()) + if (src.graph_id() == dest.graph_id()) { - throw std::runtime_error("You cannot create an edge from vertexes in different graphs!"); + throw std::runtime_error("You cannot create an edge from vertices in different graphs!"); } edge_writer edge_w; edge_w.id = id; edge_w.src_id = src.id(); edge_w.dest_id = dest.id(); - edge_w.graph_uuid = src.graph_uuid(); + edge_w.graph_id = src.graph_id(); return edge_t::get(edge_w.insert_row()); } diff --git a/gaia_slam/src/main_direct_access.cpp b/gaia_slam/src/main_direct_access.cpp index 4cf115c..a5ced8e 100644 --- a/gaia_slam/src/main_direct_access.cpp +++ b/gaia_slam/src/main_direct_access.cpp @@ -20,6 +20,10 @@ using namespace gaia::direct_access; using namespace gaia::gaia_slam; using namespace gaia::gaia_slam::graph; +/** + * Showcase the usage of the direct_access API to create/read data from the database. + * This example does not make usage of Rules. + */ int main() { gaia::system::initialize(); @@ -27,9 +31,7 @@ int main() // This examples wants to focus on direct_access hence the rules are disabled. gaia::rules::unsubscribe_rules(); - // The no_auto_restart argument prevents beginning a new transaction - // when the current one is committed. - auto_transaction_t txn{auto_transaction_t::no_auto_restart}; + gaia::db::begin_transaction(); clear_data(); @@ -42,23 +44,23 @@ int main() edge_t e1 = create_edge(1, v1, v2); edge_t e2 = create_edge(2, v2, v3); - // Get all graph vertexes and edges. + // Get all graph vertices and edges. - gaia_log::app().info("=== Printing all vertexes from Graph({}) ===", graph.uuid()); + gaia_log::app().info("=== Printing all vertices from Graph({}) ===", graph.id()); for (const vertex_t& v : graph.vertices()) { gaia_log::app().info(" Vertex({})", v.id()); } - gaia_log::app().info("=== Printing all edges from Graph({}) ===", graph.uuid()); + gaia_log::app().info("=== Printing all edges from Graph({}) ===", graph.id()); for (const edge_t& e : graph.edges()) { gaia_log::app().info(" Edge({}): ({}) --> ({})", e.id(), e.src().id(), e.dest().id()); } - gaia_log::app().info("=== Get all vertexes with type lidar_scan ===", graph.uuid()); + gaia_log::app().info("=== Get all vertices with type lidar_scan ===", graph.id()); for (const vertex_t& v : vertex_t::list() .where(vertex_expr::type == vertex_type::c_lidar_scan)) @@ -66,7 +68,7 @@ int main() gaia_log::app().info(" Vertex({})", v.id()); } - gaia_log::app().info("=== Get the vertex with id 2 and type lidar_scan ===", graph.uuid()); + gaia_log::app().info("=== Get the vertex with id 2 and type lidar_scan ===", graph.id()); auto vertex_it = vertex_t::list() .where( @@ -75,12 +77,12 @@ int main() if (vertex_it.begin() == vertex_it.end()) { - throw std::runtime_error("Impossible to find vertex with id 1 and type lidar_scan"); + throw std::runtime_error("Cannot find vertex with id 1 and type lidar_scan"); } gaia_log::app().info(" Vertex({})", vertex_it.begin()->id()); - gaia_log::app().info("=== Get all edges with src_id or dest_id equals 1 ===", graph.uuid()); + gaia_log::app().info("=== Get all edges with src_id or dest_id equals 1 ===", graph.id()); auto edge_it = edge_t::list() .where( @@ -92,7 +94,7 @@ int main() gaia_log::app().info(" Edge({}): ({}) --> ({})", e.id(), e.src().id(), e.dest().id()); } - txn.commit(); + gaia::db::commit_transaction(); /// /// Now you can write your additional logic to use the direct_access API. diff --git a/gaia_slam/src/main_rules.cpp b/gaia_slam/src/main_rules.cpp index c835c5f..a29eb4b 100644 --- a/gaia_slam/src/main_rules.cpp +++ b/gaia_slam/src/main_rules.cpp @@ -36,6 +36,9 @@ void wait_for_rules() sleep_for(std::chrono::milliseconds(c_rule_wait_millis)); } +/** + * Shows how mutating the database triggers the rules in `gaia/gaia_slam.ruleset`. + */ int main() { gaia::system::initialize(); @@ -46,39 +49,17 @@ int main() clear_data(); gaia::db::commit_transaction(); - gaia_log::app().info("=== Creates a new Graph and observe the corresponding rules triggered ==="); + gaia_log::app().info("=== Creates a new Graph ==="); - // This code triggers the following rules: - // 1. on_insert(graph): triggered when a graph is created and creates a random vertex (which triggers the next rule). - // 2. on_insert(vertex): triggered when a vertex is created. - // 3. on_change(v: vertex): Triggered when a vertex is created or updated. gaia::db::begin_transaction(); graph_t graph = create_graph("graph_1"); gaia::db::commit_transaction(); - wait_for_rules(); - - gaia_log::app().info("=== Updates a vertex and observe the corresponding rules triggered ==="); - - // This code triggers the following rules: - // 1. on_update(vertex): triggered when any vertex field is updated. - // 2. on_update(vertex.confidence): triggered when the confidence field is updated. - // 3. on_change(v: vertex): Triggered when a vertex is created or updated. - gaia::db::begin_transaction(); - vertex_t vertex1 = *vertex_t::list().begin(); - vertex_writer vertex_w = vertex1.writer(); - vertex_w.pose_y = 0.4; - vertex_w.pose_x = 0.1; - vertex_w.update_row(); - gaia::db::commit_transaction(); - - wait_for_rules(); - gaia_log::app().info("=== Inserting {} incoming_data_event of type {} ===", c_num_data_events, vertex_type::c_lidar_scan); - // This code triggers the following rules: - // 1. on_insert(incoming_data_event): triggered when an incoming_data_event is inserted. This insert a new vertex. - // 2. All the rules triggered on vertex insertion. + // Insert some incoming_data_event rows to simulate data arriving from sensors. + // This should trigger the following rule: + // - on_insert(incoming_data_event): triggered when an incoming_data_event is inserted. This insert a new vertex. gaia::db::begin_transaction(); for (int i = 0; i < c_num_data_events; i++) { @@ -94,9 +75,9 @@ int main() wait_for_rules(); - gaia_log::app().info("=== Retrieving vertexes/edges with type {} ===", vertex_type::c_image_keyframe); + gaia_log::app().info("=== Retrieving vertices/edges with type {} ===", vertex_type::c_image_keyframe); - // This code verify that the vertex with type vertex_type::c_image_keyframe has been created. + // This code verifies that the vertex with type vertex_type::c_image_keyframe has been created. gaia::db::begin_transaction(); for (const vertex_t& v : vertex_t::list() .where(vertex_expr::type == vertex_type::c_image_keyframe)) @@ -105,14 +86,10 @@ int main() } gaia::db::commit_transaction(); - wait_for_rules(); - /// /// Now you can write your additional logic to trigger the other rules, /// or write some new rules! /// - /// For instance, you may want to create edges between vertexes.. - /// gaia::system::shutdown(); }