From 091e294b79e70701f845b89bc8c0ace8b5f3a2be Mon Sep 17 00:00:00 2001 From: "Ralph J. Steinhagen" Date: Sat, 15 Jun 2024 09:19:06 +0200 Subject: [PATCH] bumped and synchronised code-style definitions with GR4 Signed-off-by: Ralph J. Steinhagen --- .clang-format | 237 +---------- .remarkrc.yaml | 5 + .restyled.yaml | 8 +- CONTRIBUTING.md | 54 ++- CORE_DEVELOPMENT_GUIDELINE.md | 501 ++++++++++++++++++++++++ CORE_NAMING_GUIDELINE.md | 189 +++++++++ ClangBuildAnalyzer.ini | 35 ++ DCO.txt | 37 ++ cmake/Dependencies.cmake | 2 +- formatFiles.sh | 58 +++ formatLastCommit.sh | 62 +++ src/service/gnuradio/GnuRadioWorker.hpp | 208 +++++----- src/ui/Flowgraph.cpp | 365 +++++++++-------- src/ui/cmake/Dependencies.cmake | 2 +- 14 files changed, 1238 insertions(+), 525 deletions(-) create mode 100644 .remarkrc.yaml create mode 100644 CORE_DEVELOPMENT_GUIDELINE.md create mode 100644 CORE_NAMING_GUIDELINE.md create mode 100644 ClangBuildAnalyzer.ini create mode 100644 DCO.txt create mode 100755 formatFiles.sh create mode 100755 formatLastCommit.sh diff --git a/.clang-format b/.clang-format index 255cb89c..34c48e2a 100644 --- a/.clang-format +++ b/.clang-format @@ -1,225 +1,20 @@ -# .clang-format for Qt Creator -# -# This is for clang-format >= 5.0. -# -# The configuration below follows the Qt Creator Coding Rules [1] as closely as -# possible. For documentation of the options, see [2]. -# -# Use ../../tests/manual/clang-format-for-qtc/test.cpp for documenting problems -# or testing changes. -# -# In case you update this configuration please also update the qtcStyle() in src\plugins\clangformat\clangformatutils.cpp -# -# [1] https://doc-snapshots.qt.io/qtcreator-extending/coding-style.html -# [2] https://clang.llvm.org/docs/ClangFormatStyleOptions.html -# ---- -Language: Cpp -AccessModifierOffset: -4 -AlignAfterOpenBracket: DontAlign -AlignConsecutiveAssignments: AcrossEmptyLines -AlignConsecutiveDeclarations: AcrossEmptyLines -AlignEscapedNewlines: DontAlign -AlignOperands: AlignAfterOperator -AlignTrailingComments: true -AllowAllParametersOfDeclarationOnNextLine: true -AllowAllArgumentsOnNextLine: true -#PackConstructorInitializers: PCIS_CurrentLine # starting with clang 14 -AllowAllConstructorInitializersOnNextLine: true -AllowShortLambdasOnASingleLine: All +BasedOnStyle: LLVM AllowShortCaseLabelsOnASingleLine: true -AllowShortFunctionsOnASingleLine: All -AllowShortIfStatementsOnASingleLine: Always -AllowShortLoopsOnASingleLine: true -AlwaysBreakAfterReturnType: None -AlwaysBreakBeforeMultilineStrings: false -AlwaysBreakTemplateDeclarations: MultiLine -BinPackArguments: true -BinPackParameters: true -BraceWrapping: - AfterCaseLabel: false - AfterClass: false - AfterControlStatement: Never - AfterEnum: false - AfterFunction: false - AfterNamespace: false - AfterObjCDeclaration: false - AfterStruct: false - AfterUnion: false - BeforeCatch: false - BeforeElse: false - IndentBraces: false - SplitEmptyFunction: false - SplitEmptyRecord: false - SplitEmptyNamespace: false -BreakBeforeBinaryOperators: All -BreakBeforeBraces: Custom -BreakBeforeInheritanceComma: false -BreakBeforeTernaryOperators: true -BreakConstructorInitializersBeforeComma: false -BreakConstructorInitializers: BeforeComma -BreakAfterJavaFieldAnnotations: true -BreakStringLiterals: true -ColumnLimit: 0 -CommentPragmas: '^ IWYU pragma:' -CompactNamespaces: true -ConstructorInitializerAllOnOneLineOrOnePerLine: true -ConstructorInitializerIndentWidth: 4 -ContinuationIndentWidth: 8 -Cpp11BracedListStyle: false -DerivePointerAlignment: false -DisableFormat: false -ExperimentalAutoDetectBinPacking: false -FixNamespaceComments: true -ForEachMacros: - - forever # avoids { wrapped to next line - - foreach - - Q_FOREACH - - BOOST_FOREACH -IncludeCategories: - - Regex: '^ Core"] +direction TB +subgraph SubFrame3["
"] +DR("Required: +* Code of Conduct +* Core Naming Guidelines +* Contribution Guidelines +* C++ Core Guidelines +* check existing BlockLib +* check w/ Maintainer +  by opening an issue +* apply `clang-format` +* unit-tests & code-coverage +* CI must pass (↔ Draft PR) +* 'lean code' principle +* 'clean code' principle +* specific complementary rules +"):::Recommended +end +end + +subgraph G0["Application Class 0
User Private"] +subgraph SubFrame0[" "] +AR["Recommended (if seeking help): +* check tutorials and examples +* concise, don't mix topics, MVP +* Code of Conduct +* Core Naming Guidelines + +Optional: +* learn basic C++ and watch: +  CppCon's 'Back to Basic' Track +  'Back to Basic' Slides +* learn basic Python and watch: +..."]:::Recommended +end +end +AR-.->SKIP("you may skip further +reading this guide"):::Must + +subgraph G1["Application Class 1
Out-of-Tree BlockLib "] +subgraph SubFrame1[" "] +BR["Recommended: +* Code of Conduct +* Core Naming Guidelines +* C++ Core Guidelines +* unit-tests & code-coverage + +optional: +* start familiarising with: +  'lean' and 'clean' coding"]:::Recommended +end +end + BR-.->SKIP + +subgraph G2["Application Class 2
Core Blocks"] +subgraph SubFrame2[" "] +CR["Required: +* Code of Conduct +* Core Naming Guidelines +* Contribution Guidelines +* C++ Core Guidelines +* check existing BlockLib +* check w/ Maintainer +  by opening an issue +* apply `clang-format` +* unit-tests & code-coverage +* CI must pass (↔ Draft PR) + +Recommended: +* 'lean code' principle +* 'clean code' principle +* specific complementary rules +"]:::Recommended +end +end +CR-.->|imitate existing code-base|SKIP +CR-.->|write better code|CM("this document is for you") + +DR-.->|write better code|CM +``` + +- This document is intended for maintainers, users that want to contribute to the core BlockLib, new Schedulers, or new features to the Core. +- These guidelines are a bit more formalised to promote sustainable coding practice and long-term maintainability of the project and require a bit of experience with modern C++. + +### Introduction + +Central to these guidelines are these fundamental questions: : **What constitutes 'lean' and 'clean' code, and how can we quantify and optimise it?** + +While 'clean code' and 'lean management' have sometimes devolved into buzzwords, their origins are rooted in: + +- **quantifiable metrics**: software must commit to quantifiable and actionable optimisation standards, beyond 'best practice' incantations. +- **lean code**: originating from the [Toyota Production System](https://global.toyota/en/company/vision-and-philosophy/production-system/) + ([wikipedia](https://en.wikipedia.org/wiki/Toyota_Production_System)), this philosophy focuses on streamlining processes, eliminating waste ([muda]()), and enhancing value creation for both the organization and its users. +- **clean code**: the principles laid out by Robert C. Martin, encapsulating best practices in programming for clarity and maintainability [[3, 4]](#3), + +**At its core: you cannot have one without the other.** establishing measurable 'lean' and 'clean' coding practices is essential +for effective communication and organization within diverse teams — comprising engineers, scientists, operators, and management — +without compromising cost, efficiency, and our broader objectives. + +Our goal is to establish principles and standards benefiting individual developers, teams, and management, ultimately improving the outcomes for all involved. + +## Purposeful and Performance Metric Prioritising Coding + +Striking a balance between ideal code and practical functionality is key. 'W.A.R.M.E.D' is a memorisation aid, inspired by +Casey Muratori's insights, that simplifies this into actionable steps. It focuses on producing robust, functional, and efficient +code without succumbing to over-engineering or speculative features. Each principle is associated with a +metric to measure and optimise success: + +1. **Write** code that directly addresses tangible, real-world problems. Embrace simplicity and avoid adding complexity that + isn’t necessary. In a professional context, use Story Points (SP) as an aid to estimate task complexity beforehand and track actual effort post-completion. + Ideally, keep tasks within a manageable scope of less than 5 SP, which aligns with a typical workweek, to promote efficiency and maintain focus. + This also facilitates task handover, ensuring others can seamlessly continue the work -- if needed. + Do not exceed 10 SP to prevent overcommitment, potentially block progress of others, and to keep the workload balanced and collaborative. + metric: Story Points (SP) + +2. **Agree** to well-established, field-tested designs with empirical backing, discarding unsupported unquantifiable ideas. + Focus discussions on tackling substantive challenges rather than unnecessary divergence into trivial details, known as [bike-shedding](https://en.wikipedia.org/wiki/Law_of_triviality). + Instead of prolonged design speculation, quick, benchmarked proofs of concept are preferred, acknowledging that only one, or perhaps none, may succeed. + metric: time/SPs spent on design discussions + +3. **Readable** code should be well-written prose, with each line and function mirroring the clarity and conciseness + of a sentence in a technical publication. Aim to reduce the source lines of code [[5-7]](#5) (e.g. using the + [COCOMO model](https://en.wikipedia.org/wiki/COCOMO)), which streamlines the codebase and simplifies long-term maintenance. + metric: source lines of code (SLOC)s + +4. **Modify** and add new features or expand APIs only when there is a clear and present need. Refrain from incorporating + enhancements for hypothetical scenarios; "add it later" should a component become essential. This not only avoids unnecessary bloat + but the simplicity of "adding it later" is also a litmus test to assess whether the software is composable, clean and lean. + metric: Story Points (SP) + +5. **Execute with Efficiency**: prioritise performance and reliability from the outset. Optimisation must be grounded in actual micro- + and macro benchmarks and validated through complementary functional (unit-)testing. + metric (application specific): e.g. throughput, latency, jitter, CPU usage, power consumption + +6. **Debug Strategically & use appropriate Tools**: tackle uncommon corner-cases and outliers at the end. Static code analysis tools (linters), + AI-based generative tools, and compilers are typically more proficient than humans at optimising problems on a small to medium scale. + Thus shift your attention from micro- to addressing broader, macro-level optimisations, complex algorithms, and architectural designs + that are beyond the capability of those tools. metric (beyond SPs): e.g. test coverage percentages + +Each step comes with inherent costs: time for development, time for arguments, time for SLOCs to read and modify, time for execution, +time to debug extraneous API and rare corner cases. Avoid the temptation of feature creep and premature optimisations. +Instead, weigh the 80/20 cost-benefit, focus on areas with the most value, and engage in continuous improvement. +This approach ensures resources are invested in aspects with the highest return on investment providing the 'biggest bang for the buck'. + +## Specific Development Guidelines + +These specific guidelines complement the [C++ Core Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines), providing direction in cases where there are multiple +coding approaches, that are not (yet) standardised, or slightly deviate from standard practices. + +0. **Apply Common Sense, pay-it-forward, and the Golden Rule** + ... for those with doubts, please read and adhere to the [Code of Conduct](CODE_OF_CONDUCT.md) and [Contribution Guidelines](CONTRIBUTING.md). + For most this is common sense and a minimum bit of required legalese to protect you, the community, + to keep the project clean, and above board w.r.t. national and international laws. + +1. **Best Clean- & Lean-Code Practices and the Boy-Scout Rule** + + - Adhere to the [C++ Core Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines) as your primary reference. + - Embrace the **Boy Scout Rule**: _Leave the code cleaner and leaner than you found it._ + - Engage in continuous learning and practice of clean and lean coding. It's like a sport: _use it or lose it_. + +2. **Tool Mastery**: _"If your only tool is a hammer, all problems appear as nails."_ + - Avoid committing code that triggers compiler warnings. `-Werror` may be activated turning warnings into an instant CI quality assurance fail. + - Leverage linters and static code analysis tools, like SonarQube, that uphold minimum as well as best-practise coding standards. + - Explore AI-based tools for code quality enhancement where appropriate. + - Prioritise clarity over complexity: strive for code that's easy to read, efficient, and maintainable. + You do not have to demonstrate the sophistication of your (meta-)programming capabilities, we just assume you are a good programmer. + - Automate code formatting with tools like `clang-format`. It is advisable to set up your IDE to run `clang-format` on save, + or to define a git hook that checks the formatting on commit. + +_Important Note: Non-compliance will lead to CI rejection of the merge request +and often reduces the likeliness of someone reviewing your high-level code concepts. It's like applying a spell and grammar +checker to your e-mails ... they are free and easy to use ... just use them._ + +3. **Coding Best Practices**: + + - Minimise variable scope and delay definitions until usage. + - Use `using` and `using namespace` sparingly and within limited scope. + - Avoid variable shadowing; opt for unique expressive names. + - Avoid `bool` function arguments in APIs -- they are often ambiguous and confusing. + - Create functions that are concise and focused, balancing the need to minimise complexity and length (SLOCs) + while avoiding an excessive number of minor, hard-to-manage functions. + +4. **The one C++ Standard**: + + - Use the latest C++ standard adopted by the project, considering the common denominator supported by CI's stable versions of GCC, Clang, and Emscripten. + - Capitalise on the Standard Template Library (STL, (std)libc++). Opt for ``, ``, ``, etc., over custom solutions. + - Favour `concepts` over `static_assert` for broader compile-time checks. + +5. **Minimise 3rd-Party Library Dependencies**: except in the following cases: + + - they are crucial for core functionality (e.g. ZeroMQ or OpenSSL for network communication) or are on the C++ future features roadmap (such as refl-cpp and fmt). + - Small libraries that can be internalised within the project are exceptions. + - They are pre-agreed upon, only used in a sub-project, and can be conditionally disabled + +6. **Effective Testing and Documentation:** + + - Ensure functional unit testing and meaningful code coverage. + - Use `static_assert`s for compile-time verifications. + - Provide usage examples to illustrate the library's simplified common core use alongside unit-tests that need to handle more complex and less common corner cases. + - Accept both test-driven and example-driven development for API design. + +7. **Naming Convention**: Please read and follow the [naming guidelines](CORE_NAMING_GUIDELINE.md). + +8. **Structs vs Classes**: strongly prioritise functional programming where possible to mitigate performance bottlenecks + and race conditions, especially in multi-threaded environments. + + - Favour public `structs` (aka. _aggregates_) over `classes` unless encapsulation of a non-public invariant or shared state (e.g. a HW resource) is required. + - Prefix member variables that have a predominantly 'private' use-case with an underscore (\_). + - structure members should be defined in the following order if possible: + 1. Meta-information about the structure/class (`static_assert`s that check compile-time properties of the type such as checking template parameters are valid in class templates); + 2. Member variables that define the private and public state. + 3. Public member functions; + 4. Protected member functions; + 5. Private member functions. + +9. **Header-only Code**: prefer writing header-only code + + - Prefer header-only code for better optimisation, despite slower compilation. + - Use `const` and `constexpr` liberally, except when they impede performance (e.g. 'const' disabling move semantics). + +10. **Contemporary C++ Practices:** + - Don't overuse and reserve the use of `auto` where it enhances readability or is required. + - Raw pointers + - should be limited to specific use cases, must be non-owning, and marked as unsafe when outside those parameters. + - prefer wrapping them into a RAII-enabled types. + - are fine if they are used without pointer arithmetic + - Pass by value or reference-to-const based on the data type size and context. + - Exceptions should remain exceptional. Opt for alternatives like `std::conditional<>` or `std::expected<>` for simpler stacks. + - **[Inheritance Is The Base Class of Evil](https://www.youtube.com/watch?v=bIhUE5uUFOA)**: + strongly favour composition over inheritance. Avoid the latter unless RTTI is absolutely necessary. + _N.B. This has a strong impact on the long-term evolution and maintainability of the project. + Strongly Object-Oriented-Designs with prolific inheritance hierarchy make it difficult to near impossible to change designs without breaking existing code._ + - Use contemporary C++ features like enum classes, `override`, and `final` where appropriate. + +## Code formatting + +We are using `clang-format` to ensure that code adheres to a consistent style, making it easier to read, review, and +maintain. Automation of the formatting process minimizes discrepancies in coding style and prevents formatting-related +conflicts during code reviews. It also helps to reduce cognitive load on developers, allowing focus on the algorithmic +and functional aspects of their contributions. + +### Rationale Behind Formatting Choices + +Among all the configurations, we would like to explain our decision to allow unlimited column length and +column alignment. + +1. Unlimited Column Length + + - **Developer Intent and Readability:** Enforcing a strict line length often leads to suboptimal formatting, + disrupting the logical structure of the code. Allowing unlimited column length provides developers the flexibility + to format code in a way that maintains readability and logical coherence. The objective is not to write + excessively long lines of code, but to adopt a formatting style that is context-sensitive, permitting longer + lines when they appropriately fit the context. + + - **Practicality in Modern C++:** Modern C++ code often involves templates and constructs that become difficult to + read with aggressive line wrapping. Allowing developers to determine line length ensures that complex code remains + comprehensible. + + - **Avoiding Unsafe Practices:** Strict line lengths may lead developers to use unsafe practices like + abbreviations, excessive inlining, or inappropriate use of `auto` types to fit within limits, introducing + unnecessary complexity and potential errors. Also very long lines of code often indicate underlying issues, such + as complex functions or functions with too many parameters, which should be reviewed and potentially refactored. + + - **Line Breaking Method:** A practical method for breaking lines is to insert a comment + marker `//` at the end of a line to indicate where it should be broken. On the other hand, setting a rigid column + limit restricts our ability to prevent automatic line breaks by `clang-format`, unless one explicitly disables and + re-enables formatting with `clang-format off/on` commands. + + - Whenever possible keep lines short. This makes the code easier to maintain and read. + +2. Column Alignment + + - **Improved Readability:** Column alignment visually separates types, field names, and values, making the code's + structure easier to understand at a glance. This is especially useful in sections with numerous declarations and + initializations, where the type can often be inferred from the value. + + - **Semantic Clarity:** Aligning columns semantically groups related items, facilitating quick identification of + patterns and discrepancies. This practice enhances code review and maintenance processes. + +## [Resources for Further Learning] + +- [1] Melvin E. Conway: _"How Do Committees Invent?"_. In: Datamation (1968) ([pdf](https://git.gsi.de/SDE/cxx-user-group/uploads/ba3fa2eddd700e7b50e89564c6ec0f70/Conway__Melvin_E.__How_Do_Committees_Invent_._In__Datamation__1968_.pdf)) +- [2] Casey Muratori, _"Lecture: The Only Unbreakable Law"_, ([55" video](https://youtu.be/5IUj1EZwpJY) + [summary](https://git.gsi.de/SDE/cxx-user-group/-/issues/40)) +- [3] Robert C. Martin, _"Clean Code: A Handbook of Agile Software Craftsmanship"_, Prentice Hall; 1st edition (1 Aug. 2008), 464 pp. ([link](https://www.amazon.de/-/en/Robert-Martin/dp/0132350882), [12" summary video](https://youtu.be/RAr4-MD10pQ), [TL;DR Summary](#clean_code)) +- [4] Robert C. Martin, Lecture Series: _"Coding Better World Together"_, UnityCoin, 2019 ([Lecture №1, 1'50"](https://youtu.be/7EmboKQH8lM), [№2, 1'06"](https://youtu.be/2a_ytyt9sf8), [№3, 60"](https://youtu.be/Qjywrq2gM8o), [№4, 1'30"](https://youtu.be/58jGpV2Cg50), [№5, 2'](https://youtu.be/sn0aFEMVTpA), [№6, 1'40"](https://youtu.be/l-gF0vDhJVI)) +- [5] Donald J. Reifer, Barry W. Boehm, and Sunita Chulani. _“The Rosetta Stone Making COCOMO 81 Estimates Work with COCOMO II.”_ (1999). ([paper](https://api.semanticscholar.org/CorpusID:14499705))) +- [6] Jairus Hihn Lum, Mori Khorrami, _"Handbook for Software Cost Estimation"_, Jet Propulsion Laboratory, Pasadena, California, 2000, ([paper](https://www.academia.edu/19060864/Handbook_for_Software_Cost_Estimation)) +- [7] David A. Wheeler, _"SLOCCount"_, 2001-2004, https://dwheeler.com/sloccount/ +- [8] Sean Parent, _"Inheritance Is The Base Class of Evil"_, GoingNative 2013 Event, 23 Sept 2013, ([talk 25"](https://www.youtube.com/watch?v=bIhUE5uUFOA)) + +## Appendix + +### Target Application Classes + +- **Application Class 0**: _For those tinkering with GR and writing private user code:_ + - Choose your style, conventions, rules, and guidelines that fit you best. + - As a _recommendation_ when seeking for help to get better support and high-quality feedback: + - Check tutorials and examples for existing solutions. + - Be concise in problem descriptions, avoid unrelated topics, and provide a minimum viable example ([MVP](https://en.wikipedia.org/wiki/Minimum_viable_product)) + - Adhere to the [Code of Conduct](CODE_OF_CONDUCT.md) and be courteous – many helpers are volunteers. + - Use the [Core Naming Guidelines](CORE_NAMING_GUIDELINE.md) for easier code readability and problem recognition. + - Optionally: take a bit of your time to improve basic C++ or learn modern C++ coding practices. It's worth the investment, it's efficient, simpler than before, and fun. Recommended resources: + - Lecture series (choose based on style and preference **!!selection & curation needed!!**): + - [The Cherno: C++ Course (free)](https://www.youtube.com/playlist?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb) + - [Codedamn: From Novice to Expert: Mastering C++ Programming (free)](https://codedamn.com/learn/cpp-language) + - [Udemy: The C++20 Masterclass: From Fundamentals to Advanced (paid)](https://www.udemy.com/course/the-modern-cpp-20-masterclass/) + - [Kate Gregory: C++20 Fundamentals (paid)](https://www.pluralsight.com/courses/cplusplus-20-fundamentals) + - [Udemy: Beginning C++ Programming - From Beginner to Beyond (paid)](https://www.udemy.com/course/beginning-c-plus-plus-programming/) + - [Codecademy: Learn C++ (paid)](https://www.codecademy.com/learn/learn-c-plus-plus) + - [The Great Courses: Introduction to C++: Programming Concepts and Applications (paid)](https://www.thegreatcourses.com/courses/introduction-to-c-plus-plus-programming-concepts-and-applications) + - Building upon that: + - [CppCon](https://cppcon.org/)'s ['Back to Basic' Track](https://www.youtube.com/watch?v=Bt3zcJZIalk&list=PLHTh1InhhwT4TJaHBVWzvBOYhp27UO7mI&pp=iAQB) and [Slides](https://github.com/CppCon/CppCon2023). + - YouTube Channels: [C++ Weekly](https://www.youtube.com/@cppweekly), [The Cherno](https://www.youtube.com/@TheCherno), [Casey Muratori's channel](https://www.youtube.com/@MollyRocket) + [//]: # ( \* if you do not plan to make your code public you could stop here, otherwise you may continue.) +- **Application Class 1**: _Developing Out-Of-Tree (OOT) Modules, Blocks, or Schedulers for others:_ + - As above, plus: + - Familiarise with the [C++ Core Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines) for safe syntax in modern C++. + - Optionally, learn 'lean' and 'clean' coding principles outlined below. +- **Application Class 2**: _Developing Core Blocks with long-term maintenance support interest (more than 2-3 years):_ + - Adhere to the [Code of Conduct](CODE_OF_CONDUCT.md), [Core Naming Guidelines](CORE_NAMING_GUIDELINE.md), [Contribution Guidelines](CONTRIBUTING.md) and [C++ Core Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines) + - Before starting developing a new block, check with the existing BlockLib consult core maintainers via the GH [issue tracker](./issues), and + 1. that there isn't already an existing implementation of the feature that you need, or + 2. that could be achieved through combining existing blocks (GR 4.0 compile-time merging capabilities offer these for free), and + 3. pick a template or existing block that you like and/or is closest to what you want to achieve, and + 4.where to place the new functionality/implementation in the BlockLib. + - Ensure that your code **passes all CI tests** (e.g. through opening a 'draft pull-request'), that it does not generate extra compiler warning, and uses the provided '**clang-format**' definition. +- **Application Class 3**: _Developing new features in the Core:_ + - This document is intended for maintainers, users that want to contribute to the core BlockLib, new Schedulers, or new features to the Core. + - These guidelines are a bit more formalised to promote sustainable coding practice and long-term maintainability of the project and require a bit of experience with modern C++. + +### 'Lean Management' TL;DR Summary: + +Lean management advocates the optimisation of processes and value by conserving resources and reducing unnecessary effort, time, and expense, whether in product development, service delivery, or R&D processes. +It is based on a clear understanding of what brings value to the organisation and its users. The methodology encourages the minimisation of waste ([muda](), anything that doesn't contribute to value), inconsistency ([mura](), wasting of time or resources), and overburden ([muri](), _"unreasonableness; impossible; beyond one's power; too difficult; by force; perforce; forcibly; compulsorily; excessiveness; immoderation"_). + +#### General Principles of Lean Management: + +1. **Identify Value**: start with understanding the stakeholders' needs. Value is what attracts and retains users and fulfils the organisation's objectives. It should be at the heart of every decision made. + +2. **Map the Value Stream**: recognise all critical steps involved in the process from inception to delivery (i.e. the full vertical stack). Critically identify areas that do not contribute to the end goals and devise ways to minimise or eliminate such inefficiencies. + +3. **Create Flow**: stream-line all processes and operations, reducing bottlenecks and delays. This encourages seamless transition of tasks and a smooth workflow, which is crucial for maintaining timelines and quality in product development, services, and R&D processes. + +4. **Establish Pull**: align output with short-term, medium-term to long-term demand in decreasing priority, creating a system that responds to actual needs rather than projected ones alone. + +5. **Pursue Perfection**: commit to continuous improvement. This iterative approach means always looking for ways to refine processes, increase efficiency, and enhance value creation, regardless of the field or sector. + +6. **Respect for People**: build a collaborative environment where team members, partners, and all stakeholders are valued and engaged. A respectful workplace leads to a more dedicated team, fostering innovation and proactive problem-solving. Software mirrors the organisational culture and how people interact with each other. + +[//]: + +### 'Clean Code' TL;DR summary of Robert C. Martin's book ([source](https://gist.github.com/wojteklu/73c6914cc446146b8b533c0988cf8d29)): + + +Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer +other than its original author. With understandability comes readability, changeability, extensibility and maintainability. + +
+ Click for more ... + +### General rules + +1. Follow standard conventions. +2. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible. +3. Boy scout rule. Leave the campground cleaner than you found it. +4. Always find root cause. Always look for the root cause of a problem. + +### Design rules + +1. Keep configurable data at high levels. +2. ~~Prefer polymorphism to if/else or switch/case.~~ -> _N.B. [Inheritance Is The Base Class of Evil](https://www.youtube.com/watch?v=bIhUE5uUFOA) _ +3. ~~Separate multi-threading code.~~ -> _N.B. we follow a functional programming style that minimise hidden state for thread-safety and performance reason_ +4. Prevent over-configurability. +5. Use dependency injection. +6. Follow Law of Demeter. A class should know only its direct dependencies. + +### Understandability tips + +1. Be consistent. If you do something a certain way, do all similar things in the same way. +2. Use explanatory variables. +3. Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place. +4. Prefer dedicated value objects to primitive type. +5. Avoid logical dependency. Don't write methods which works correctly depending on something else in the same class. +6. Avoid negative conditionals. + +### Names rules + +1. Choose descriptive and unambiguous names. +2. Make meaningful distinction. +3. Use pronounceable names. +4. Use searchable names. +5. Replace magic numbers with named constants. +6. Avoid encodings. Don't append prefixes or type information. + +### Functions rules + +1. Small. +2. Do one thing. +3. Use descriptive names. +4. Prefer fewer arguments. +5. Have no side effects. +6. Don't use flag arguments. Split method into several independent methods that can be called from the client without the flag. + +### Comments rules + +1. Always try to explain yourself in code. +2. Don't be redundant. +3. Don't add obvious noise. +4. Don't use closing brace comments. +5. Don't comment out code. Just remove. +6. Use as explanation of intent. +7. Use as clarification of code. +8. Use as warning of consequences. + +### Source code structure + +1. Separate concepts vertically. +2. Related code should appear vertically dense. +3. Declare variables close to their usage. +4. Dependent functions should be close. +5. Similar functions should be close. +6. Place functions in the downward direction. +7. Keep lines short. +8. Don't use horizontal alignment. +9. Use white space to associate related things and disassociate weakly related. +10. Don't break indentation. + +### Objects and data structures + +1. ~~Hide internal structure.~~ -> _N.B. prefix internal/private variables with `_` and only 'hide' if RAII dictates or absolute necessary._ +2. Prefer data structures. +3. Avoid hybrids structures (half object and half data). +4. Should be small. +5. Do one thing. +6. Small number of instance variables. +7. ~~Base class should know nothing about their derivatives.~~ -> _N.B. we frequently use the [CRTP pattern](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) to avoid inheritance and vtables._ +8. ~~Better to have many functions than to pass some code into a function to select a behaviour.~~ -> _N.B. small functions are good but also need to limit the level of indication because too many functions increase the cognitive load for readers and also limit compiler optimisation._ +9. Prefer non-static methods to static methods. + +### Tests + +1. One assert per test. +2. Readable. +3. Fast. +4. Independent. +5. Repeatable. + +### Code smells + +1. Rigidity. The software is difficult to change. A small change causes a cascade of subsequent changes. +2. Fragility. The software breaks in many places due to a single change. +3. Immobility. You cannot reuse parts of the code in other projects because of involved risks and high effort. +4. Needless Complexity. +5. Needless Repetition. +6. Opacity. The code is hard to understand. + +[//]: /details diff --git a/CORE_NAMING_GUIDELINE.md b/CORE_NAMING_GUIDELINE.md new file mode 100644 index 00000000..312978b7 --- /dev/null +++ b/CORE_NAMING_GUIDELINE.md @@ -0,0 +1,189 @@ +## Naming Rules and Guidelines + +In software development, the right naming conventions bridge the gap between human linguistic patterns and logical code structures, turning abstract concepts into meaningful expressions and algorithmic intent. By aligning our codebase with familiar linguistic constructs, we aim to foster clarity, maintainability, and a more intuitive understanding of our project. Embracing lean and clean code principles, this guideline aims at a concise yet expressive naming. While we strive for consistency, we emphasize the spirit of clarity and meaning over rigid adherence when faced with exceptions. + +This guideline is embedded into the larger [CORE_DEVELOPMENT_GUIDELINE.md](../blob/main/CORE_DEVELOPMENT_GUIDELINE.md) context of this project [README.md](../blob/main/README.md). + +
click here for the TL;DR summary + +## Naming Conventions: Quick Summary (TL;DR) + +- **General Philosophy**: Strive for clarity and intuition. When in doubt, prioritize clarity over strict adherence to these rules. +- **Classes/Structs/Enums**: Use `UpperCase` like `Graph` or `SubGraph`. +- **Methods/Functions/Lambdas**: Use `lowerCase` such as `start()` or `create()`. +- **Fields**: Use `snake_case` for public fields like `is_valid`. Non-public fields start with `_`, e.g., `_initialised`. +- **Function Variables & Parameters**: Use `lowerCase` or discretion. +- **Template Parameters**: Types use `T` or `TSpecificName`. Non-types are `lowerCase` or context-specific. +- **Constants**: Use `kUppercase`, e.g., `kConstant`. +- **C++ Concepts**: Favor `UpperCase` like `PortLike` or `HasMethod`. +- **Namespaces**: Always `lowercase`. +- **Type Aliases**: Use `UpperCase`. +- **Files & Directories**: Reflect primary class/struct/template definition. Short, `lowercase` for directories. +- **Unit Tests**: Prefix with `qa_`. +- **Comments**: Be concise, focus on the 'why'. Use `/** ... */` for documentation, `//` for inline. +- **Macros**: Always `UPPERCASE`. +
+ +You can place this right at the beginning of your detailed guidelines so that developers can quickly familiarize themselves with the essentials before diving into the comprehensive guide. + +### Preface Language + +The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://datatracker.ietf.org/doc/html/rfc2119) + +There are two distinct use cases to consider: + +### 1. STL-style Library Usage + +This pertains to ubiquitous, generic, domain-in-specific meta-programming which could, theoretically, be eventually included in the C++ standard, for instance, `type_list<>`: + +- You SHOULD adhere strictly to C++ ISO guidelines and naming conventions. +- These definitions SHOULD be kept in separate headers and possibly within a distinct sub-project (though within the same repository) to facilitate potential future refactoring and outsourcing. + +### 2. Domain-specific Library and User-code + +For the domain-specific library usage and user-defined code, adhere to the following rules: + +1. (templated-) **classes/structs** & **enums**: SHOULD use UpperCase in line with _proper nouns_. Examples: `Port`, `Block`, `Graph`, `SubGraph`, `Selector`. + - enumerator: use UpperCase or lowerCase based on whether the enumerator is a _proper noun_ (e.g. `Planet::Earth`, `Currency::Dollar`) or a _common noun_ concept (e.g. `Color::red`, `Direction::north`) or use your judgment. +2. (templated-) **methods/functions/lambdas**: SHOULD use lowerCase in akin to _verbs_. Examples: `scheduler.start()`, `scheduler.stop()`, `filter::create()`, `computeMagnitude(...)`. +3. **fields** of classes/structs: MUST use lower_case. Examples: `temperature`, `filter`, `is_valid`, `sample_rate'. + - place these at the top of the class definition for visibility (hidden state) + - non-public/private fields MUST be prefixed by `_`. Examples: `_initialised`, `_cachedVector` + - public API fields, especially if compile-time reflected, should strictly follow snake_case to simplify compatibility with [SigMF](https://github.com/sigmf/SigMF). +4. **function variables and parameters**: SHOULD use lowerCase or your own judgement. Examples: `sum`, `filter`, `isValid` + - for functions/methods: should be defined as close to their initial use as possible + - parameters may use `_` suffix in constructors to avoid name clashes with class field names (e.g. `User(std::string name_) : name(std::move(name_) {/*...*/}`) +5. **template parameters**: + - **type parameters**: SHOULD use standard notations like `T`, `U`, `V` or prefix with `T` for more specific types. Examples: `template`, `template` + - **non-type template parameters (NTTPs)**: SHOULD prefer lowerCase or use discretion based on context (e.g. `template`, `template`) +6. **constants**: SHOULD use kUppercase. Example: `kConstant`. +7. C++ **concepts**: SHOULD use UpperCase. Several alternatives are being considered. Among them are -- TO CHECK/VERIFY/ITERATE: (<-> must avoid collisions with template definitions) + 1. `IsBlock`, `HasMethod`, ... + 2. `PortLike` (pro: `func(PortLike auto port)`) vs `IsBlock` (pro: `requires (isBlock && ...)`) + - slightly alt: `PortLike` && (`is_port_v` || `isPort`) + 3. `PortLike` && `HasMethod` + 4. `BlockConcept` + 5. `XxxType` -> seems not to be favoured + 6. other... +8. **namespaces**: MUST use lowercase and descriptive scope names. +9. **using directives**: + - **using type aliases**: SHOULD follow the class/struct rule (i.e., CamelCase), employ very sparingly and only when it substantially boosts clarity and readability and is recurrently used, like in concise concepts. + - **using namespace**: MUST prefer the narrowest applicable scope **and** if clarity dictates. Avoid broad usage to prevent namespace pollution. +10. **file names**: SHOULD reflect the primary class/struct/template definition contained within and follow their rules +11. **directory names**: SHOULD use short lowercase names roughly linked to namespace (sub-)scope names (keep hierarchy level low). +12. **unit tests**: MUST use `qa_` as a prefix and append the header file name of the class/struct/function-scope to be tested. +13. **comments**: + - **class/struct/method comments**: if they cannot be trivially discerned from their name, brief implementation, etc. MUST be documented using the `/** ... */` or in-code `Doc<"..">` format. + - **inline comments**: SHOULD use `//` for brief single-line comments directly above or alongside the code they explain. Please do not restate the obvious but explain the 'why' or complex 'how'. + - **block comments**: MAY use `/* ... */` for multi-line comments. Please do not commit uncommented blocks of code but rather delete them. + - **TODO comments**: MAY use `// TODO: ...` to highlight incomplete or temporary sections, detailing what's left or the reason for the temporary state. + - _Note: if in doubt -- focus on lean- and clean code -- let the code speak for itself. Domain-specific aspects aside, the need for documentation can indicate poor, complex, or unintuitive designs._ +14. macros: MUST use UPPERCASE. Example: `ENABLE_REFLECTION`. + +- convention adheres to established standards and serves as a cautionary highlight. Its 'shouting' nature signals developers that using such global/singleton patterns should be the exception rather than the norm. + +Some example to provide a flavour of the naming scheme: + +```cpp +// FILE: Block.hpp +namespace gr::merged { +template +struct Block {}; // used-defined struct is proper noun (uppercase) +} // namespace gr + +// FILE: Graph.hpp + +namespace gr::merged { + +template +concept IsGraph = requires(T t) { + { t.is_valid } -> std::same_as; +}; + +/** @brief Represents a simple graph structure for signals. (alt documentation)*/ +template +class Graph : public Block> { + bool _initialised = true; // private member using `_` prefix + +public: + using Description = Doc; + static const int kDefaultSize = 10; + std::vector nodes; + bool is_valid; // field is public API using snake_case + + // constructor with parameter using `_` suffix to avoid name clashes + Graph(std::vector nodes_) : nodes(std::move(nodes_)) { + // TODO: initialization of other components + } + + template + void applyFilter(TFilter filter) { // templated method in camelCase + for (auto& node : nodes) { + // break up long method names semantically using namespace + node = filter::create(node); + } + // ... + } +}; + +} // namespace gr::merged +``` + +**Rationale for these Naming Conventions** + +Discussion on naming conventions often slides into [bikeshedding](https://en.wikipedia.org/wiki/Law_of_triviality), resembling debates like British vs. American spelling, where trivial disagreements overshadow larger, more critical issues. Below is a rationale behind our choices: + +1. **Aligning Code with Linguistics and Semantics**: domain-specific objects resemble _proper nouns_ (typically capitalised in natural languages), actions mirror _verbs_ (commonly lowercase), and variables mimic _common nouns_ (generic and lowercase). This analogy helps facilitate an intuitive understanding of code semantics. + +2. **English Vocabulary & Composition**: English predominantly uses single words, making up 80-90% of its vocabulary. Long or compound words often signal specific, non-generic meanings, placing them in the _proper noun_ category. These should not solely be represented through class names but preferably described through functional attributes of the class or methods. In coding, such nuances should not be represented by class names but described through functional attributes, such as template parameters, non-type template parameters (NTTP), or namespace scope. This ensures the class’s attributes are queryable through meta-programming techniques rather than a specific type name check, enhancing composability. + +3. **Research-Backed**: Research has shown that naming conventions significantly affect code readability and comprehension. These indicate that: + + - While familiar naming conventions can hasten reading, the priority in code review is accuracy, not speed. Over-reliance on familiarity may lead to missed nuances and errors. + - CamelCase improves reading accuracy for both novice and experienced coders. + - CamelCase does not significantly impact reading speed for experienced developers. + - CamelCase engages more deliberate cognitive processing, aligning with Kahneman's 'System 2' responses. + + References: [Binkley et al., 2009](http://www.cs.loyola.edu/~binkley/papers/icpc09-clouds.pdf), [Kahneman & Tversky, 1979](https://www.uzh.ch/cmsssl/suz/dam/jcr:00000000-64a0-5b1c-0000-00003b7ec704/10.05-kahneman-tversky-79.pdf), [Kahneman, 2003](https://www2.econ.iastate.edu/tesfatsi/JudgementAndChoice.MappingBoundedRationality.DKahneman2003.pdf). +
'System 1' vs. 'System 2' TL;DR Summary + + Psychologist Daniel Kahneman introduced the concepts of 'System 1' and 'System 2' as two distinct modes of thinking in his groundbreaking work. Here's a quick rundown of these systems: + + ### 'System 1' + + - **Type**: Automatic, instinctive. + - **Characteristics**: + - Fast + - Intuitive + - Emotion-driven + - Often operates subconsciously + - **When it's active**: + - Jumping to conclusions + - Making gut reactions + - Recognizing familiar patterns + + ### 'System 2' + + - **Type**: Deliberative, analytical. + - **Characteristics**: + - Slow + - Logical + - Requires effort + - Conscious thinking involved + - **When it's active**: + - Solving complex math problems + - Making deliberate choices + - Evaluating evidence + + The distinction between 'System 1' and 'System 2' thinking is not just theoretical; it has practical applications in numerous fields, including software development. For a deeper dive into these concepts and their implications, consider reading Kahneman's book "[Thinking, Fast and Slow](https://www.amazon.com/Daniel-Kahneman/dp/0374533555)". +
+ +4. **Leveraging Namespaces in C++**: for class or method meanings that are domain, context, or scope-specific, C++'s 'namespaces' are invaluable. They articulate this scope concisely, reducing the need for extended compound names. For example, `gr::algorithm::window::Type::Hann` or `gr::algorithm::window::create(Type windowFunction, std::size_t n, ...)`. + +While multiple naming conventions have their merits, our aim is clarity, simplicity and consistency. We're confident that our chosen conventions best achieve this, sidestepping protracted, non-scientific, and unproductive debates over preferences. diff --git a/ClangBuildAnalyzer.ini b/ClangBuildAnalyzer.ini new file mode 100644 index 00000000..f9ccd9a2 --- /dev/null +++ b/ClangBuildAnalyzer.ini @@ -0,0 +1,35 @@ +# ClangBuildAnalyzer reads ClangBuildAnalyzer.ini file from the working directory +# when invoked, and various aspects of reporting can be configured this way. +# This file example is setup to be exactly like what the defaults are. + +# How many of most expensive things are reported? +[counts] + +# files that took most time to parse +fileParse = 10 +# files that took most time to generate code for +fileCodegen = 10 +# functions that took most time to generate code for +function = 30 +# header files that were most expensive to include +header = 10 +# for each expensive header, this many include paths to it are shown +headerChain = 5 +# templates that took longest to instantiate +template = 30 + + +# Minimum times (in ms) for things to be recorded into trace +[minTimes] + +# parse/codegen for a file +file = 20 + +[misc] + +# Maximum length of symbol names printed; longer names will get truncated +maxNameLength = 1000 + +# Only print "root" headers in expensive header report, i.e. +# only headers that are directly included by at least one source file +onlyRootHeaders = true diff --git a/DCO.txt b/DCO.txt new file mode 100644 index 00000000..8201f992 --- /dev/null +++ b/DCO.txt @@ -0,0 +1,37 @@ +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +1 Letterman Drive +Suite D4700 +San Francisco, CA, 94129 + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index 03c50551..4f947056 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -3,7 +3,7 @@ include(FetchContent) FetchContent_Declare( gnuradio4 GIT_REPOSITORY https://github.com/fair-acc/gnuradio4.git - GIT_TAG f00cba2c81422f143c9d48552921ec63bbaef652 # main as of 2024-05-29 + GIT_TAG c51a5d5253bdd2ce7bbc785970b7ec4bbe2878dc # main as of 2024-06-14 ) FetchContent_Declare( diff --git a/formatFiles.sh b/formatFiles.sh new file mode 100755 index 00000000..15fbe663 --- /dev/null +++ b/formatFiles.sh @@ -0,0 +1,58 @@ +#/bin/bash + +#enforces .clang-format style guide prior to committing to the git repository + +CLANG_MIN_VERSION="9.0.0" + +set -e + +CLANG_FORMAT="$(command -v clang-format)" +CLANG_VERSION="$(${CLANG_FORMAT} --version | sed '/^clang-format version /!d;s///;s/-.*//;s///g')" + +compare_version () { + echo " " + if [[ $1 == $2 ]] + then + CLANG_MIN_VERSION_MATCH="=" + return + fi + local IFS=. + local i ver1=($1) ver2=($2) + # fill empty fields in ver1 with zeros + for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)) + do + ver1[i]=0 + done + for ((i=0; i<${#ver1[@]}; i++)) + do + if [[ -z ${ver2[i]} ]] + then + # fill empty fields in ver2 with zeros + ver2[i]=0 + fi + if ((10#${ver1[i]} > 10#${ver2[i]})) + then + CLANG_MIN_VERSION_MATCH="<" + return + fi + if ((10#${ver1[i]} < 10#${ver2[i]})) + then + CLANG_MIN_VERSION_MATCH=">" + return + fi + done + CLANG_MIN_VERSION_MATCH="=" + return +} + +compare_version ${CLANG_MIN_VERSION} "${CLANG_VERSION}" + +files=$((git status -uall --porcelain | awk 'match($1, "M??"){print $2}' | grep -Ei "\.(c|cc|cpp|cxx|c\+\+|h|hh|hpp|hxx|h\+\+|java)$") || true) +if [ -n "${files}" ]; then + + if [ -n "${CLANG_FORMAT}" ] && [ "$CLANG_MIN_VERSION_MATCH" != "<" ]; then + spaced_files=$(echo "$files" | paste -s -d " " -) + # echo "reformatting ${spaced_files}" + "${CLANG_FORMAT}" -style=file -i "$spaced_files" >/dev/null + fi +fi diff --git a/formatLastCommit.sh b/formatLastCommit.sh new file mode 100755 index 00000000..37ee0073 --- /dev/null +++ b/formatLastCommit.sh @@ -0,0 +1,62 @@ +#/bin/bash + +#enforces .clang-format style guide prior to committing to the git repository + +CLANG_MIN_VERSION="9.0.0" + +set -e + +CLANG_FORMAT="$(command -v clang-format)" +CLANG_VERSION="$(${CLANG_FORMAT} --version | sed '/^clang-format version /!d;s///;s/-.*//;s///g')" + +compare_version () { + echo " " + if [[ $1 == $2 ]] + then + CLANG_MIN_VERSION_MATCH="=" + return + fi + local IFS=. + local i ver1=($1) ver2=($2) + # fill empty fields in ver1 with zeros + for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)) + do + ver1[i]=0 + done + for ((i=0; i<${#ver1[@]}; i++)) + do + if [[ -z ${ver2[i]} ]] + then + # fill empty fields in ver2 with zeros + ver2[i]=0 + fi + if ((10#${ver1[i]} > 10#${ver2[i]})) + then + CLANG_MIN_VERSION_MATCH="<" + return + fi + if ((10#${ver1[i]} < 10#${ver2[i]})) + then + CLANG_MIN_VERSION_MATCH=">" + return + fi + done + CLANG_MIN_VERSION_MATCH="=" + return +} + +compare_version ${CLANG_MIN_VERSION} "${CLANG_VERSION}" +git reset HEAD~1 --soft + +files=$((git diff --name-only --cached | grep -Ei "\.(c|cc|cpp|cxx|c\+\+|h|hh|hpp|hxx|h\+\+|java)$") || true) +if [ -n "${files}" ]; then + + if [ -n "${CLANG_FORMAT}" ] && [ "$CLANG_MIN_VERSION_MATCH" != "<" ]; then + spaced_files=$(echo "$files" | paste -s -d " " -) + echo "reformatting ${spaced_files}" + "${CLANG_FORMAT}" -style=file -i "$spaced_files" >/dev/null + git --no-pager diff + git add "${spaced_files}" + fi +fi +git commit -C ORIG_HEAD diff --git a/src/service/gnuradio/GnuRadioWorker.hpp b/src/service/gnuradio/GnuRadioWorker.hpp index 4d63848e..2b2eb975 100644 --- a/src/service/gnuradio/GnuRadioWorker.hpp +++ b/src/service/gnuradio/GnuRadioWorker.hpp @@ -6,10 +6,10 @@ #include -#include #include #include #include +#include #include #include @@ -21,8 +21,7 @@ namespace opendigitizer::acq { namespace detail { template -inline std::optional -get(const gr::property_map &m, const std::string_view &key) { +inline std::optional get(const gr::property_map& m, const std::string_view& key) { const auto it = m.find(std::string(key)); if (it == m.end()) { return {}; @@ -30,33 +29,33 @@ get(const gr::property_map &m, const std::string_view &key) { try { return std::get(it->second); - } catch (const std::exception &e) { + } catch (const std::exception& e) { fmt::println(std::cerr, "Unexpected type for tag '{}'", key); return {}; } } -inline float doubleToFloat(double v) { - return static_cast(v); -} +inline float doubleToFloat(double v) { return static_cast(v); } inline std::string findTriggerName(std::span tags) { - for (const auto &tag : tags) { + for (const auto& tag : tags) { const auto v = tag.get(std::string(gr::tag::TRIGGER_NAME.key())); - if (!v) continue; + if (!v) { + continue; + } return std::get(v->get()); } return {}; } template -inline std::optional getSetting(const gr::BlockModel &block, const std::string &key) { +inline std::optional getSetting(const gr::BlockModel& block, const std::string& key) { try { const auto setting = block.settings().get(key); if (!setting) { return {}; } return std::get(*setting); - } catch (const std::exception &e) { + } catch (const std::exception& e) { fmt::println(std::cerr, "Unexpected type for '{}' property", key); return {}; } @@ -70,32 +69,35 @@ using enum gr::message::Command; using namespace opencmw::majordomo; using namespace std::chrono_literals; -enum class AcquisitionMode { - Continuous, - Triggered, - Multiplexed, - Snapshot -}; +enum class AcquisitionMode { Continuous, Triggered, Multiplexed, Snapshot }; constexpr inline AcquisitionMode parseAcquisitionMode(std::string_view v) { using enum AcquisitionMode; - if (v == "continuous") return Continuous; - if (v == "triggered") return Triggered; - if (v == "multiplexed") return Multiplexed; - if (v == "snapshot") return Snapshot; + if (v == "continuous") { + return Continuous; + } + if (v == "triggered") { + return Triggered; + } + if (v == "multiplexed") { + return Multiplexed; + } + if (v == "snapshot") { + return Snapshot; + } throw std::invalid_argument(fmt::format("Invalid acquisition mode '{}'", v)); } struct PollerKey { AcquisitionMode mode; std::string signal_name; - std::size_t pre_samples = 0; // Trigger - std::size_t post_samples = 0; // Trigger - std::size_t maximum_window_size = 0; // Multiplexed - std::chrono::nanoseconds snapshot_delay = std::chrono::nanoseconds(0); // Snapshot - std::string trigger_name = {}; // Trigger, Multiplexed, Snapshot + std::size_t pre_samples = 0; // Trigger + std::size_t post_samples = 0; // Trigger + std::size_t maximum_window_size = 0; // Multiplexed + std::chrono::nanoseconds snapshot_delay = std::chrono::nanoseconds(0); // Snapshot + std::string trigger_name = {}; // Trigger, Multiplexed, Snapshot - auto operator<=>(const PollerKey &) const noexcept = default; + auto operator<=>(const PollerKey&) const noexcept = default; }; struct StreamingPollerEntry { @@ -107,11 +109,10 @@ struct StreamingPollerEntry { std::optional signal_min; std::optional signal_max; - explicit StreamingPollerEntry(std::shared_ptr::Poller> p) - : poller{ p } {} + explicit StreamingPollerEntry(std::shared_ptr::Poller> p) : poller{p} {} - void populateFromTags(std::span &tags) { - for (const auto &tag : tags) { + void populateFromTags(std::span& tags) { + for (const auto& tag : tags) { if (const auto name = detail::get(tag.map, tag::SIGNAL_NAME.shortKey())) { signal_name = name; } @@ -133,7 +134,7 @@ struct SignalEntry { std::string unit; float sample_rate; - auto operator<=>(const SignalEntry &) const noexcept = default; + auto operator<=>(const SignalEntry&) const noexcept = default; }; struct DataSetPollerEntry { @@ -144,7 +145,7 @@ struct DataSetPollerEntry { template class GnuRadioAcquisitionWorker : public Worker { - gr::PluginLoader *_plugin_loader; + gr::PluginLoader* _plugin_loader; std::jthread _notifyThread; std::unique_ptr _pending_flow_graph; std::mutex _flow_graph_mutex; @@ -153,15 +154,13 @@ class GnuRadioAcquisitionWorker : public Worker; - explicit GnuRadioAcquisitionWorker(opencmw::URI brokerAddress, const opencmw::zmq::Context &context, gr::PluginLoader *pluginLoader, std::chrono::milliseconds rate, Settings settings = {}) - : super_t(std::move(brokerAddress), {}, context, std::move(settings)), _plugin_loader(pluginLoader) { + explicit GnuRadioAcquisitionWorker(opencmw::URI brokerAddress, const opencmw::zmq::Context& context, gr::PluginLoader* pluginLoader, std::chrono::milliseconds rate, Settings settings = {}) : super_t(std::move(brokerAddress), {}, context, std::move(settings)), _plugin_loader(pluginLoader) { // TODO would be useful if one can check if the external broker knows TimeDomainContext and throw an error if not init(rate); } template - explicit GnuRadioAcquisitionWorker(BrokerType &broker, gr::PluginLoader *pluginLoader, std::chrono::milliseconds rate) - : super_t(broker, {}), _plugin_loader(pluginLoader) { + explicit GnuRadioAcquisitionWorker(BrokerType& broker, gr::PluginLoader* pluginLoader, std::chrono::milliseconds rate) : super_t(broker, {}), _plugin_loader(pluginLoader) { // this makes sure the subscriptions are filtered correctly opencmw::query::registerTypes(TimeDomainContext(), broker); init(rate); @@ -173,19 +172,17 @@ class GnuRadioAcquisitionWorker : public Worker fg) { - std::lock_guard lg{ _flow_graph_mutex }; + std::lock_guard lg{_flow_graph_mutex}; _pending_flow_graph = std::move(fg); } - void setUpdateSignalEntriesCallback(std::function)> callback) { - _updateSignalEntriesCallback = std::move(callback); - } + void setUpdateSignalEntriesCallback(std::function)> callback) { _updateSignalEntriesCallback = std::move(callback); } private: void init(std::chrono::milliseconds rate) { // TODO instead of a notify thread with polling, we could also use callbacks. This would require // the ability to unregister callbacks though (RAII callback "handles" using shared_ptr/weak_ptr like it works for pollers??) - _notifyThread = std::jthread([this, rate](const std::stop_token &stoken) { + _notifyThread = std::jthread([this, rate](const std::stop_token& stoken) { auto update = std::chrono::system_clock::now(); // TODO: current load_grc creates Foo types no matter what the original type was // when supporting more types, we need some type erasure here @@ -197,23 +194,26 @@ class GnuRadioAcquisitionWorker : public Worker toScheduler; std::unique_ptr fromScheduler; - bool finished = false; + bool finished = false; while (!finished) { - const auto aboutToFinish = stoken.stop_requested(); - auto pendingFlowGraph = [this]() { std::lock_guard lg{ _flow_graph_mutex }; return std::exchange(_pending_flow_graph, {}); }(); + const auto aboutToFinish = stoken.stop_requested(); + auto pendingFlowGraph = [this]() { + std::lock_guard lg{_flow_graph_mutex}; + return std::exchange(_pending_flow_graph, {}); + }(); const auto hasScheduler = schedulerThread.joinable(); const bool stopScheduler = hasScheduler && (aboutToFinish || pendingFlowGraph); bool schedulerFinished = false; if (stopScheduler) { - sendMessage(*toScheduler, schedulerUniqueName, block::property::kLifeCycleState, { { "state", std::string(magic_enum::enum_name(lifecycle::State::REQUESTED_STOP)) } }, ""); + sendMessage(*toScheduler, schedulerUniqueName, block::property::kLifeCycleState, {{"state", std::string(magic_enum::enum_name(lifecycle::State::REQUESTED_STOP))}}, ""); } if (hasScheduler) { bool signalInfoChanged = false; auto messages = fromScheduler->streamReader().get(fromScheduler->streamReader().available()); - for (const auto &message : messages) { + for (const auto& message : messages) { if (message.endpoint == block::property::kLifeCycleState) { if (!message.data) { continue; @@ -228,11 +228,11 @@ class GnuRadioAcquisitionWorker : public Workersecond; + auto& entry = sinkIt->second; const auto signal_name = detail::get(*settings, "signal_name"); const auto signal_unit = detail::get(*settings, "signal_unit"); @@ -257,7 +257,7 @@ class GnuRadioAcquisitionWorker : public Worker entries; entries.reserve(signalEntryBySink.size()); - for (const auto &[_, entry] : signalEntryBySink) { + for (const auto& [_, entry] : signalEntryBySink) { entries.push_back(entry); } _updateSignalEntriesCallback(std::move(entries)); @@ -266,16 +266,16 @@ class GnuRadioAcquisitionWorker : public WorkerforEachBlock([&signalEntryBySink](const auto &block) { + pendingFlowGraph->forEachBlock([&signalEntryBySink](const auto& block) { if (block.typeName().starts_with("gr::basic::DataSink")) { - auto &entry = signalEntryBySink[std::string(block.uniqueName())]; + auto& entry = signalEntryBySink[std::string(block.uniqueName())]; entry.name = detail::getSetting(block, "signal_name").value_or(""); entry.unit = detail::getSetting(block, "signal_unit").value_or(""); entry.sample_rate = detail::getSetting(block, "sample_rate").value_or(1.f); @@ -309,7 +309,7 @@ class GnuRadioAcquisitionWorker : public Worker entries; entries.reserve(signalEntryBySink.size()); - for (const auto &[_, entry] : signalEntryBySink) { + for (const auto& [_, entry] : signalEntryBySink) { entries.push_back(entry); } _updateSignalEntriesCallback(std::move(entries)); @@ -335,33 +335,32 @@ class GnuRadioAcquisitionWorker : public Worker &streamingPollers, std::map &dataSetPollers) { + bool handleSubscriptions(std::map& streamingPollers, std::map& dataSetPollers) { bool pollersFinished = true; - for (const auto &subscription : super_t::activeSubscriptions()) { + for (const auto& subscription : super_t::activeSubscriptions()) { const auto filterIn = opencmw::query::deserialise(subscription.params()); try { const auto acquisitionMode = parseAcquisitionMode(filterIn.acquisitionModeFilter); - for (std::string_view signalName : filterIn.channelNameFilter | std::ranges::views::split(',') | std::ranges::views::transform([](const auto &&r) { return std::string_view{ &*r.begin(), std::ranges::distance(r) }; })) { + for (std::string_view signalName : filterIn.channelNameFilter | std::ranges::views::split(',') | std::ranges::views::transform([](const auto&& r) { return std::string_view{&*r.begin(), std::ranges::distance(r)}; })) { if (acquisitionMode == AcquisitionMode::Continuous) { - if (!handleStreamingSubscription(streamingPollers, filterIn, signalName)) + if (!handleStreamingSubscription(streamingPollers, filterIn, signalName)) { pollersFinished = false; + } } else { - if (!handleDataSetSubscription(dataSetPollers, filterIn, acquisitionMode, signalName)) + if (!handleDataSetSubscription(dataSetPollers, filterIn, acquisitionMode, signalName)) { pollersFinished = false; + } } } - } catch (const std::exception &e) { + } catch (const std::exception& e) { fmt::println(std::cerr, "Could not handle subscription {}: {}", subscription.toZmqTopic(), e.what()); } } return pollersFinished; } - auto getStreamingPoller(std::map &pollers, std::string_view signalName) { - const auto key = PollerKey{ - .mode = AcquisitionMode::Continuous, - .signal_name = std::string(signalName) - }; + auto getStreamingPoller(std::map& pollers, std::string_view signalName) { + const auto key = PollerKey{.mode = AcquisitionMode::Continuous, .signal_name = std::string(signalName)}; auto pollerIt = pollers.find(key); if (pollerIt == pollers.end()) { @@ -371,20 +370,21 @@ class GnuRadioAcquisitionWorker : public Worker &pollers, const TimeDomainContext &context, std::string_view signalName) { + bool handleStreamingSubscription(std::map& pollers, const TimeDomainContext& context, std::string_view signalName) { auto pollerIt = getStreamingPoller(pollers, signalName); - if (pollerIt == pollers.end()) // flushing, do not create new pollers + if (pollerIt == pollers.end()) { // flushing, do not create new pollers return true; + } - const auto &key = pollerIt->first; - auto &pollerEntry = pollerIt->second; + const auto& key = pollerIt->first; + auto& pollerEntry = pollerIt->second; if (!pollerEntry.poller) { return true; } Acquisition reply; - auto processData = [&reply, signalName, &pollerEntry](std::span data, std::span tags) { + auto processData = [&reply, signalName, &pollerEntry](std::span data, std::span tags) { pollerEntry.populateFromTags(tags); reply.acqTriggerName = "STREAMING"; reply.channelName = pollerEntry.signal_name.value_or(std::string(signalName)); @@ -403,7 +403,7 @@ class GnuRadioAcquisitionWorker : public Workerfinished.load(); if (pollerEntry.poller->process(processData)) { @@ -412,27 +412,21 @@ class GnuRadioAcquisitionWorker : public Worker &pollers, const TimeDomainContext &context, AcquisitionMode mode, std::string_view signalName) { - const auto key = PollerKey{ - .mode = mode, - .signal_name = std::string(signalName), - .pre_samples = static_cast(context.preSamples), - .post_samples = static_cast(context.postSamples), - .maximum_window_size = static_cast(context.maximumWindowSize), - .snapshot_delay = std::chrono::nanoseconds(context.snapshotDelay), - .trigger_name = context.triggerNameFilter - }; + auto getDataSetPoller(std::map& pollers, const TimeDomainContext& context, AcquisitionMode mode, std::string_view signalName) { + const auto key = PollerKey{.mode = mode, .signal_name = std::string(signalName), .pre_samples = static_cast(context.preSamples), .post_samples = static_cast(context.postSamples), .maximum_window_size = static_cast(context.maximumWindowSize), .snapshot_delay = std::chrono::nanoseconds(context.snapshotDelay), .trigger_name = context.triggerNameFilter}; auto pollerIt = pollers.find(key); if (pollerIt == pollers.end()) { - auto matcher = [trigger_name = context.triggerNameFilter](const gr::Tag &tag) { + auto matcher = [trigger_name = context.triggerNameFilter](const gr::Tag& tag) { using enum gr::basic::TriggerMatchResult; const auto v = tag.get(gr::tag::TRIGGER_NAME); if (trigger_name.empty()) { return v ? Matching : Ignore; } try { - if (!v) return Ignore; + if (!v) { + return Ignore; + } return std::get(v->get()) == trigger_name ? Matching : NotMatching; } catch (...) { return NotMatching; @@ -452,20 +446,21 @@ class GnuRadioAcquisitionWorker : public Worker &pollers, const TimeDomainContext &context, AcquisitionMode mode, std::string_view signalName) { + bool handleDataSetSubscription(std::map& pollers, const TimeDomainContext& context, AcquisitionMode mode, std::string_view signalName) { auto pollerIt = getDataSetPoller(pollers, context, mode, signalName); - if (pollerIt == pollers.end()) // flushing, do not create new pollers + if (pollerIt == pollers.end()) { // flushing, do not create new pollers return true; + } - const auto &key = pollerIt->first; - auto &pollerEntry = pollerIt->second; + const auto& key = pollerIt->first; + auto& pollerEntry = pollerIt->second; if (!pollerEntry.poller) { return true; } Acquisition reply; auto processData = [&reply, &key, signalName, &pollerEntry](std::span> dataSets) { - const auto &dataSet = dataSets[0]; + const auto& dataSet = dataSets[0]; if (!dataSet.timing_events.empty()) { reply.acqTriggerName = detail::findTriggerName(dataSet.timing_events[0]); } @@ -485,7 +480,7 @@ class GnuRadioAcquisitionWorker : public Workerfinished.load(); while (pollerEntry.poller->process(processData, 1)) { @@ -498,28 +493,24 @@ class GnuRadioAcquisitionWorker : public Worker class GnuRadioFlowGraphWorker : public Worker { - gr::PluginLoader *_plugin_loader; - TAcquisitionWorker &_acquisition_worker; + gr::PluginLoader* _plugin_loader; + TAcquisitionWorker& _acquisition_worker; std::mutex _flow_graph_lock; flowgraph::Flowgraph _flow_graph; public: using super_t = Worker; - explicit GnuRadioFlowGraphWorker(opencmw::URI brokerAddress, const opencmw::zmq::Context &context, gr::PluginLoader *pluginLoader, flowgraph::Flowgraph initialFlowGraph, TAcquisitionWorker &acquisitionWorker, Settings settings = {}) - : super_t(std::move(brokerAddress), {}, context, std::move(settings)), _plugin_loader(pluginLoader), _acquisition_worker(acquisitionWorker) { - init(std::move(initialFlowGraph)); - } + explicit GnuRadioFlowGraphWorker(opencmw::URI brokerAddress, const opencmw::zmq::Context& context, gr::PluginLoader* pluginLoader, flowgraph::Flowgraph initialFlowGraph, TAcquisitionWorker& acquisitionWorker, Settings settings = {}) : super_t(std::move(brokerAddress), {}, context, std::move(settings)), _plugin_loader(pluginLoader), _acquisition_worker(acquisitionWorker) { init(std::move(initialFlowGraph)); } template - explicit GnuRadioFlowGraphWorker(const BrokerType &broker, gr::PluginLoader *pluginLoader, flowgraph::Flowgraph initialFlowGraph, TAcquisitionWorker &acquisitionWorker) - : super_t(broker, {}), _plugin_loader(pluginLoader), _acquisition_worker(acquisitionWorker) { + explicit GnuRadioFlowGraphWorker(const BrokerType& broker, gr::PluginLoader* pluginLoader, flowgraph::Flowgraph initialFlowGraph, TAcquisitionWorker& acquisitionWorker) : super_t(broker, {}), _plugin_loader(pluginLoader), _acquisition_worker(acquisitionWorker) { init(std::move(initialFlowGraph)); } private: void init(flowgraph::Flowgraph initialFlowGraph) { - super_t::setCallback([this](const RequestContext &rawCtx, const flowgraph::FilterContext &filterIn, const flowgraph::Flowgraph &in, flowgraph::FilterContext &filterOut, flowgraph::Flowgraph &out) { + super_t::setCallback([this](const RequestContext& rawCtx, const flowgraph::FilterContext& filterIn, const flowgraph::Flowgraph& in, flowgraph::FilterContext& filterOut, flowgraph::Flowgraph& out) { if (rawCtx.request.command == opencmw::mdp::Command::Get) { handleGetRequest(filterIn, filterOut, out); } else if (rawCtx.request.command == opencmw::mdp::Command::Set) { @@ -527,33 +518,34 @@ class GnuRadioFlowGraphWorker : public Worker(gr::load_grc(*_plugin_loader, initialFlowGraph.flowgraph)); + auto grGraph = std::make_unique(gr::loadGrc(*_plugin_loader, initialFlowGraph.flowgraph)); _flow_graph = std::move(initialFlowGraph); _acquisition_worker.setGraph(std::move(grGraph)); - } catch (const std::string &e) { + } catch (const std::string& e) { throw std::invalid_argument(fmt::format("Could not parse flow graph: {}", e)); } } - void handleGetRequest(const flowgraph::FilterContext & /*filterIn*/, flowgraph::FilterContext & /*filterOut*/, flowgraph::Flowgraph &out) { + void handleGetRequest(const flowgraph::FilterContext& /*filterIn*/, flowgraph::FilterContext& /*filterOut*/, flowgraph::Flowgraph& out) { std::lock_guard lockGuard(_flow_graph_lock); out = _flow_graph; } - void handleSetRequest(const flowgraph::FilterContext & /*filterIn*/, flowgraph::FilterContext & /*filterOut*/, const flowgraph::Flowgraph &in, flowgraph::Flowgraph &out) { + void handleSetRequest(const flowgraph::FilterContext& /*filterIn*/, flowgraph::FilterContext& /*filterOut*/, const flowgraph::Flowgraph& in, flowgraph::Flowgraph& out) { { std::lock_guard lockGuard(_flow_graph_lock); try { - auto grGraph = std::make_unique(gr::load_grc(*_plugin_loader, in.flowgraph)); + auto grGraph = std::make_unique(gr::loadGrc(*_plugin_loader, in.flowgraph)); _flow_graph = in; out = in; _acquisition_worker.setGraph(std::move(grGraph)); - } catch (const std::string &e) { + } catch (const std::string& e) { throw std::invalid_argument(fmt::format("Could not parse flow graph: {}", e)); } } diff --git a/src/ui/Flowgraph.cpp b/src/ui/Flowgraph.cpp index f5903d60..f247175f 100644 --- a/src/ui/Flowgraph.cpp +++ b/src/ui/Flowgraph.cpp @@ -29,22 +29,14 @@ auto typeToName() { return value.type().name(); } -std::string valueTypeName(auto &port) { - static const std::map mangledToName{ - { typeToName(), "float"s }, - { typeToName(), "double"s }, +std::string valueTypeName(auto& port) { + static const std::map mangledToName{{typeToName(), "float"s}, {typeToName(), "double"s}, - { typeToName>(), "std::complex"s }, - { typeToName>(), "std::complex"s }, + {typeToName>(), "std::complex"s}, {typeToName>(), "std::complex"s}, - { typeToName>(), "gr::DataSet"s }, - { typeToName>(), "gr::DataSet"s }, + {typeToName>(), "gr::DataSet"s}, {typeToName>(), "gr::DataSet"s}, - { typeToName(), "std::int8_t"s }, - { typeToName(), "std::int16_t"s }, - { typeToName(), "std::int32_t"s }, - { typeToName(), "std::int64_t"s } - }; + {typeToName(), "std::int8_t"s}, {typeToName(), "std::int16_t"s}, {typeToName(), "std::int32_t"s}, {typeToName(), "std::int64_t"s}}; if (auto it = mangledToName.find(port.defaultValue().type().name()); it != mangledToName.end()) { return it->second; @@ -53,32 +45,26 @@ std::string valueTypeName(auto &port) { } } -const std::string &DataType::toString() const { - const static std::string names[] = { - "int32", "float32", "complex float 32" - }; +const std::string& DataType::toString() const { + const static std::string names[] = {"int32", "float32", "complex float 32"}; return names[m_id]; } std::string Block::Parameter::toString() const { - if (auto *e = std::get_if(this)) { + if (auto* e = std::get_if(this)) { return e->toString(); - } else if (auto *r = std::get_if(this)) { + } else if (auto* r = std::get_if(this)) { return r->value; - } else if (auto *i = std::get_if>(this)) { + } else if (auto* i = std::get_if>(this)) { return std::to_string(i->value); - } else if (auto *i = std::get_if>(this)) { + } else if (auto* i = std::get_if>(this)) { return std::to_string(i->value); } assert(0); return {}; } -BlockType::BlockType(std::string_view name_, std::string_view l, std::string_view cat) - : name(name_) - , label(l.empty() ? name_ : l) - , category(cat) { -} +BlockType::BlockType(std::string_view name_, std::string_view l, std::string_view cat) : name(name_), label(l.empty() ? name_ : l), category(cat) {} std::unique_ptr BlockType::createBlock(std::string_view name) const { auto params = defaultParameters; @@ -86,25 +72,25 @@ std::unique_ptr BlockType::createBlock(std::string_view name) const { return std::make_unique(name, this, std::move(params)); } -BlockType::Registry &BlockType::registry() { +BlockType::Registry& BlockType::registry() { static Registry r; return r; } -const BlockType *BlockType::Registry::get(std::string_view id) const { +const BlockType* BlockType::Registry::get(std::string_view id) const { auto it = m_types.find(id); return it == m_types.end() ? nullptr : it->second.get(); } -void BlockType::Registry::addBlockTypesFromPluginLoader(gr::PluginLoader &pluginLoader) { - for (const auto &typeName : pluginLoader.knownBlocks()) { +void BlockType::Registry::addBlockTypesFromPluginLoader(gr::PluginLoader& pluginLoader) { + for (const auto& typeName : pluginLoader.knownBlocks()) { // TODO make this also work if the block doesn't allow T=float, or has multiple // non-defaulted template parameters. // (needs information about possible instantiations from the plugin loader) auto availableParametrizations = pluginLoader.knownBlockParameterizations(typeName); auto prototypeParams = availableParametrizations.empty() ? std::string{} : availableParametrizations[0]; - auto prototype = pluginLoader.instantiate(typeName, prototypeParams); + auto prototype = pluginLoader.instantiate(typeName, prototypeParams); if (!prototype) { fmt::println(std::cerr, "Could not instantiate block of type '{}<{}>'", typeName, prototypeParams); continue; @@ -116,23 +102,23 @@ void BlockType::Registry::addBlockTypesFromPluginLoader(gr::PluginLoader &plugin std::ranges::transform(pluginLoader.knownBlockParameterizations(typeName), type->availableBaseTypes.begin(), [](auto s) { return DataType::fromString(s); }); std::ignore = prototype->settings().applyStagedParameters(); type->defaultParameters = prototype->settings().get(); - for (const auto &[id, v] : type->defaultParameters) { + for (const auto& [id, v] : type->defaultParameters) { if (auto param = std::get_if(&v)) { - type->parameters.emplace_back(id, id, BlockType::NumberParameter{ *param }); + type->parameters.emplace_back(id, id, BlockType::NumberParameter{*param}); } else if (auto param = std::get_if(&v)) { - type->parameters.emplace_back(id, id, BlockType::NumberParameter{ *param }); + type->parameters.emplace_back(id, id, BlockType::NumberParameter{*param}); } else if (auto param = std::get_if(&v)) { - type->parameters.emplace_back(id, id, BlockType::StringParameter{ *param }); + type->parameters.emplace_back(id, id, BlockType::StringParameter{*param}); } } // TODO Create input and output ports (needs port information in BlockModel) for (auto index = 0UZ; index < prototype->dynamicInputPortsSize(); index++) { - const auto &port = prototype->dynamicInputPort(index); + const auto& port = prototype->dynamicInputPort(index); type->inputs.emplace_back(port.type() == gr::PortType::MESSAGE ? "message"s : valueTypeName(port), port.name, false); } for (auto index = 0UZ; index < prototype->dynamicOutputPortsSize(); index++) { - const auto &port = prototype->dynamicOutputPort(index); + const auto& port = prototype->dynamicOutputPort(index); type->outputs.emplace_back(port.type() == gr::PortType::MESSAGE ? "message"s : valueTypeName(port), port.name, false); } @@ -140,83 +126,136 @@ void BlockType::Registry::addBlockTypesFromPluginLoader(gr::PluginLoader &plugin } } -void BlockType::Registry::addBlockType(std::unique_ptr &&t) { - m_types.insert({ t->name, std::move(t) }); -} +void BlockType::Registry::addBlockType(std::unique_ptr&& t) { m_types.insert({t->name, std::move(t)}); } -Block::Block(std::string_view name, const BlockType *t, gr::property_map params) - : name(name), m_type(t), m_parameters(std::move(params)) { +Block::Block(std::string_view name, const BlockType* t, gr::property_map params) : name(name), m_type(t), m_parameters(std::move(params)) { m_outputs.reserve(m_type->outputs.size()); m_inputs.reserve(m_type->inputs.size()); - for (auto &o : m_type->outputs) { - m_outputs.push_back({ this, o.type, o.dataset, Port::Kind::Output }); + for (auto& o : m_type->outputs) { + m_outputs.push_back({this, o.type, o.dataset, Port::Kind::Output}); } - for (auto &o : m_type->inputs) { - m_inputs.push_back({ this, o.type, o.dataset, Port::Kind::Input }); + for (auto& o : m_type->inputs) { + m_inputs.push_back({this, o.type, o.dataset, Port::Kind::Input}); } } -void Block::setParameter(const std::string &name, const pmtv::pmt &p) { +void Block::setParameter(const std::string& name, const pmtv::pmt& p) { m_parameters[name] = p; gr::Message msg; msg.serviceName = m_uniqueName; msg.endpoint = gr::block::property::kStagedSetting; - msg.data = gr::property_map{ { name, p } }; + msg.data = gr::property_map{{name, p}}; App::instance().sendMessage(msg); } -void Block::updateSettings(const gr::property_map &settings) { - for (const auto &[k, v] : settings) { +void Block::updateSettings(const gr::property_map& settings) { + for (const auto& [k, v] : settings) { m_parameters[k] = v; } } void Block::update() { - auto parseType = [](const std::string &t, bool dataset) -> DataType { + auto parseType = [](const std::string& t, bool dataset) -> DataType { // old names - if (t == "fc64") return DataType::ComplexFloat64; - if (t == "fc32" || t == "complex") return DataType::ComplexFloat32; - if (t == "sc64") return DataType::ComplexInt64; - if (t == "sc32") return DataType::ComplexInt32; - if (t == "sc16") return DataType::ComplexInt16; - if (t == "sc8") return DataType::ComplexInt8; - if (t == "f64") return dataset ? DataType::DataSetFloat64 : DataType::Float64; - if (t == "f32") return dataset ? DataType::DataSetFloat32 : DataType::Float32; - if (t == "s64") return DataType::Int64; - if (t == "s32") return DataType::Int32; - if (t == "s16") return DataType::Int16; - if (t == "s8") return DataType::Int8; - if (t == "byte") return DataType::Int8; - if (t == "bit" || t == "bits") return DataType::Bits; + if (t == "fc64") { + return DataType::ComplexFloat64; + } + if (t == "fc32" || t == "complex") { + return DataType::ComplexFloat32; + } + if (t == "sc64") { + return DataType::ComplexInt64; + } + if (t == "sc32") { + return DataType::ComplexInt32; + } + if (t == "sc16") { + return DataType::ComplexInt16; + } + if (t == "sc8") { + return DataType::ComplexInt8; + } + if (t == "f64") { + return dataset ? DataType::DataSetFloat64 : DataType::Float64; + } + if (t == "f32") { + return dataset ? DataType::DataSetFloat32 : DataType::Float32; + } + if (t == "s64") { + return DataType::Int64; + } + if (t == "s32") { + return DataType::Int32; + } + if (t == "s16") { + return DataType::Int16; + } + if (t == "s8") { + return DataType::Int8; + } + if (t == "byte") { + return DataType::Int8; + } + if (t == "bit" || t == "bits") { + return DataType::Bits; + } // GR4 names - if (t == "std::complex") return DataType::ComplexFloat64; - if (t == "std::complex") return DataType::ComplexFloat32; + if (t == "std::complex") { + return DataType::ComplexFloat64; + } + if (t == "std::complex") { + return DataType::ComplexFloat32; + } // if (t == "std::complex") return DataType::ComplexInt64; // if (t == "std::complex") return DataType::ComplexInt32; // if (t == "std::complex") return DataType::ComplexInt16; // if (t == "std::complex") return DataType::ComplexInt8; - if (t == "double") return dataset ? DataType::DataSetFloat64 : DataType::Float64; - if (t == "float") return dataset ? DataType::DataSetFloat32 : DataType::Float32; - if (t == "std::int64_t") return DataType::Int64; - if (t == "std::int32_t" || t == "int") return DataType::Int32; - if (t == "std::int16_t" || t == "short") return DataType::Int16; - if (t == "std::int8_t") return DataType::Int8; + if (t == "double") { + return dataset ? DataType::DataSetFloat64 : DataType::Float64; + } + if (t == "float") { + return dataset ? DataType::DataSetFloat32 : DataType::Float32; + } + if (t == "std::int64_t") { + return DataType::Int64; + } + if (t == "std::int32_t" || t == "int") { + return DataType::Int32; + } + if (t == "std::int16_t" || t == "short") { + return DataType::Int16; + } + if (t == "std::int8_t") { + return DataType::Int8; + } - if (t == "gr::DataSet") return DataType::DataSetFloat32; - if (t == "gr::DataSet") return DataType::DataSetFloat64; + if (t == "gr::DataSet") { + return DataType::DataSetFloat32; + } + if (t == "gr::DataSet") { + return DataType::DataSetFloat64; + } - if (t == "message") return DataType::AsyncMessage; - if (t == "bus") return DataType::BusConnection; - if (t == "") return DataType::Wildcard; - if (t == "untyped") return DataType::Untyped; + if (t == "message") { + return DataType::AsyncMessage; + } + if (t == "bus") { + return DataType::BusConnection; + } + if (t == "") { + return DataType::Wildcard; + } + if (t == "untyped") { + return DataType::Untyped; + } fmt::print("unhandled type {}\n", t); assert(0); return DataType::Untyped; }; - auto getType = [&](const std::string &t, bool dataset) { + auto getType = [&](const std::string& t, bool dataset) { // if (t.starts_with("${") && t.ends_with("}")) { // auto val = getParameterValue(t.substr(2, t.size() - 3)); // if (auto type = std::get_if(&val)) { @@ -225,30 +264,22 @@ void Block::update() { // } return parseType(t, dataset); }; - for (auto &in : m_inputs) { + for (auto& in : m_inputs) { in.type = getType(in.m_rawType, in.dataset); } - for (auto &out : m_outputs) { + for (auto& out : m_outputs) { out.type = getType(out.m_rawType, out.dataset); } } -void Block::setDatatype(DataType type) { - m_datatype = type; -} -DataType Block::datatype() const { - return m_datatype; -} +void Block::setDatatype(DataType type) { m_datatype = type; } +DataType Block::datatype() const { return m_datatype; } -std::string Block::EnumParameter::toString() const { - return definition.optionsLabels[optionIndex]; -} +std::string Block::EnumParameter::toString() const { return definition.optionsLabels[optionIndex]; } -static std::string_view strview(auto &&s) { - return { s.data(), s.size() }; -} +static std::string_view strview(auto&& s) { return {s.data(), s.size()}; } -static void readFile(const std::filesystem::path &file, std::string &str) { +static void readFile(const std::filesystem::path& file, std::string& str) { std::ifstream stream(file); if (!stream.is_open()) { throw std::runtime_error(fmt::format("Cannot open file '{}'", file.native())); @@ -262,7 +293,7 @@ static void readFile(const std::filesystem::path &file, std::string &str) { stream.read(str.data(), size); } -void BlockType::Registry::loadBlockDefinitions(const std::filesystem::path &dir) { +void BlockType::Registry::loadBlockDefinitions(const std::filesystem::path& dir) { if (!std::filesystem::exists(dir)) { std::cerr << "Cannot open directory '" << dir << "'\n"; return; @@ -271,21 +302,21 @@ void BlockType::Registry::loadBlockDefinitions(const std::filesystem::path &dir) std::filesystem::directory_iterator iterator(dir); std::string str; - for (const auto &entry : iterator) { - const auto &path = entry.path(); + for (const auto& entry : iterator) { + const auto& path = entry.path(); if (!path.native().ends_with(".block.yml")) { continue; } readFile(path, str); - YAML::Node config = YAML::Load(str); + YAML::Node config = YAML::Load(str); - auto id = config["id"].as(); - auto def = m_types.insert({ id, std::make_unique(id) }).first->second.get(); + auto id = config["id"].as(); + auto def = m_types.insert({id, std::make_unique(id)}).first->second.get(); - auto parameters = config["parameters"]; - for (const auto &p : parameters) { - const auto &idNode = p["id"]; + auto parameters = config["parameters"]; + for (const auto& p : parameters) { + const auto& idNode = p["id"]; if (!idNode || !idNode.IsScalar()) { continue; } @@ -302,25 +333,25 @@ void BlockType::Registry::loadBlockDefinitions(const std::filesystem::path &dir) auto label = labelNode && labelNode.IsScalar() ? labelNode.as(id) : id; if (dtype == "enum") { - const auto &opts = p["options"]; + const auto& opts = p["options"]; std::vector options; if (opts && opts.IsSequence()) { - for (const auto &n : opts) { + for (const auto& n : opts) { options.push_back(n.as()); } } - BlockType::EnumParameter par{ int(options.size()) }; - par.options = std::move(options); + BlockType::EnumParameter par{int(options.size())}; + par.options = std::move(options); par.optionsLabels = par.options; - const auto &attrs = p["option_attributes"]; + const auto& attrs = p["option_attributes"]; if (attrs && attrs.IsMap()) { for (auto it = attrs.begin(); it != attrs.end(); ++it) { auto key = it->first.as(); std::vector vals; - for (const auto &n : it->second) { + for (const auto& n : it->second) { vals.push_back(n.as()); } @@ -329,25 +360,25 @@ void BlockType::Registry::loadBlockDefinitions(const std::filesystem::path &dir) continue; } - par.optionsAttributes.insert({ key, std::move(vals) }); + par.optionsAttributes.insert({key, std::move(vals)}); } } - def->parameters.push_back(BlockType::Parameter{ id, label, std::move(par) }); + def->parameters.push_back(BlockType::Parameter{id, label, std::move(par)}); } else if (dtype == "int") { auto defaultValue = defaultNode ? defaultNode.as(0) : 0; - def->parameters.push_back(BlockType::Parameter{ id, label, BlockType::NumberParameter{ defaultValue } }); + def->parameters.push_back(BlockType::Parameter{id, label, BlockType::NumberParameter{defaultValue}}); } else if (dtype == "float") { auto defaultValue = defaultNode ? defaultNode.as(0) : 0; - def->parameters.push_back(BlockType::Parameter{ id, label, BlockType::NumberParameter{ defaultValue } }); + def->parameters.push_back(BlockType::Parameter{id, label, BlockType::NumberParameter{defaultValue}}); } else if (dtype == "raw") { auto defaultValue = defaultNode ? defaultNode.as(std::string{}) : ""; - def->parameters.push_back(BlockType::Parameter{ id, label, BlockType::StringParameter{ defaultValue } }); + def->parameters.push_back(BlockType::Parameter{id, label, BlockType::StringParameter{defaultValue}}); } } auto outputs = config["outputs"]; - for (const auto &o : outputs) { + for (const auto& o : outputs) { auto n = o["dtype"]; std::string type = ""; if (n) { @@ -357,11 +388,11 @@ void BlockType::Registry::loadBlockDefinitions(const std::filesystem::path &dir) if (auto id = o["id"]) { name = id.as(name); } - def->outputs.push_back({ type, name }); + def->outputs.push_back({type, name}); } auto inputs = config["inputs"]; - for (const auto &o : inputs) { + for (const auto& o : inputs) { auto n = o["dtype"]; std::string type = ""; if (n) { @@ -371,24 +402,24 @@ void BlockType::Registry::loadBlockDefinitions(const std::filesystem::path &dir) if (auto id = o["id"]) { name = id.as(name); } - def->inputs.push_back({ type, name }); + def->inputs.push_back({type, name}); } } } FlowGraph::FlowGraph() {} -void FlowGraph::parse(const std::filesystem::path &file) { +void FlowGraph::parse(const std::filesystem::path& file) { std::string str; readFile(file, str); parse(str); } -void FlowGraph::parse(const std::string &str) { +void FlowGraph::parse(const std::string& str) { clear(); - auto graph = gr::load_grc(*_pluginLoader, str); + auto graph = gr::loadGrc(*_pluginLoader, str); - graph.forEachBlock([&](const auto &grBlock) { + graph.forEachBlock([&](const auto& grBlock) { auto typeName = grBlock.typeName(); typeName = std::string_view(typeName.begin(), typeName.find('<')); auto type = BlockType::registry().get(typeName); @@ -403,12 +434,12 @@ void FlowGraph::parse(const std::string &str) { addBlock(std::move(block)); }); - auto findBlock = [&](std::string_view uniqueName) -> Block * { - const auto it = std::ranges::find_if(m_blocks, [uniqueName](const auto &b) { return b->m_uniqueName == uniqueName; }); + auto findBlock = [&](std::string_view uniqueName) -> Block* { + const auto it = std::ranges::find_if(m_blocks, [uniqueName](const auto& b) { return b->m_uniqueName == uniqueName; }); return it == m_blocks.end() ? nullptr : it->get(); }; - graph.forEachEdge([&](const auto &edge) { + graph.forEachEdge([&](const auto& edge) { auto srcBlock = findBlock(edge._sourceBlock->uniqueName()); assert(srcBlock); const auto sourcePort = edge._sourcePortDefinition.topLevel; @@ -430,9 +461,9 @@ void FlowGraph::parse(const std::string &str) { } void FlowGraph::clear() { - auto del = [&](auto &vec) { + auto del = [&](auto& vec) { if (blockDeletedCallback) { - for (auto &b : vec) { + for (auto& b : vec) { blockDeletedCallback(b.get()); } } @@ -442,30 +473,30 @@ void FlowGraph::clear() { m_connections.clear(); } -int FlowGraph::save(std::ostream &stream) { +int FlowGraph::save(std::ostream& stream) { YAML::Emitter out; { YamlMap root(out); root.write("blocks", [&]() { YamlSeq blocks(out); - auto emitBlock = [&](auto &&b) { + auto emitBlock = [&](auto&& b) { YamlMap map(out); map.write("name", b->name); map.write("id", b->typeName()); - const auto ¶meters = b->parameters(); + const auto& parameters = b->parameters(); if (!parameters.empty()) { map.write("parameters", [&]() { YamlMap pars(out); - for (const auto &[settingsKey, settingsValue] : parameters) { - std::visit([&](const T &value) { pars.write(settingsKey, value); }, settingsValue); + for (const auto& [settingsKey, settingsValue] : parameters) { + std::visit([&](const T& value) { pars.write(settingsKey, value); }, settingsValue); } }); } }; - for (auto &b : m_blocks) { + for (auto& b : m_blocks) { emitBlock(b); } }); @@ -473,7 +504,7 @@ int FlowGraph::save(std::ostream &stream) { if (!m_connections.empty()) { root.write("connections", [&]() { YamlSeq connections(out); - for (const auto &c : m_connections) { + for (const auto& c : m_connections) { out << YAML::Flow; YamlSeq seq(out); out << c.src.block->name << c.src.index; @@ -487,12 +518,12 @@ int FlowGraph::save(std::ostream &stream) { return int(out.size()); } -Block *FlowGraph::findBlock(std::string_view name) const { - const auto it = std::find_if(m_blocks.begin(), m_blocks.end(), [&](const auto &b) { return b->name == name; }); +Block* FlowGraph::findBlock(std::string_view name) const { + const auto it = std::find_if(m_blocks.begin(), m_blocks.end(), [&](const auto& b) { return b->name == name; }); return it == m_blocks.end() ? nullptr : it->get(); } -void FlowGraph::addBlock(std::unique_ptr &&block) { +void FlowGraph::addBlock(std::unique_ptr&& block) { block->m_flowGraph = this; block->update(); if (block->type().isPlotSink() && plotSinkBlockAddedCallback) { @@ -502,14 +533,14 @@ void FlowGraph::addBlock(std::unique_ptr &&block) { m_graphChanged = true; } -void FlowGraph::deleteBlock(Block *block) { - for (auto &p : block->inputs()) { - for (auto *c : p.connections) { +void FlowGraph::deleteBlock(Block* block) { + for (auto& p : block->inputs()) { + for (auto* c : p.connections) { disconnect(c); } } - for (auto &p : block->outputs()) { - for (auto *c : p.connections) { + for (auto& p : block->outputs()) { + for (auto* c : p.connections) { disconnect(c); } } @@ -518,16 +549,14 @@ void FlowGraph::deleteBlock(Block *block) { blockDeletedCallback(block); } - auto select = [&](const auto &b) { - return block == b.get(); - }; + auto select = [&](const auto& b) { return block == b.get(); }; std::erase_if(m_blocks, select); m_graphChanged = true; } -Connection *FlowGraph::connect(Block::Port *a, Block::Port *b) { +Connection* FlowGraph::connect(Block::Port* a, Block::Port* b) { assert(a->kind != b->kind); // make sure a is the output and b the input if (a->kind == Block::Port::Kind::Input) { @@ -546,12 +575,12 @@ Connection *FlowGraph::connect(Block::Port *a, Block::Port *b) { return &(*it); } -void FlowGraph::disconnect(Connection *c) { +void FlowGraph::disconnect(Connection* c) { auto it = m_connections.get_iterator(c); assert(it != m_connections.end()); - auto ports = { &c->src.block->outputs()[c->src.index], &c->dst.block->inputs()[c->dst.index] }; - for (auto *p : ports) { + auto ports = {&c->src.block->outputs()[c->src.index], &c->dst.block->inputs()[c->dst.index]}; + for (auto* p : ports) { p->connections.erase(std::remove(p->connections.begin(), p->connections.end(), c)); } @@ -561,23 +590,23 @@ void FlowGraph::disconnect(Connection *c) { void FlowGraph::addRemoteSource(std::string_view uri) { auto block = BlockType::registry().get("opendigitizer::RemoteSource")->createBlock("Remote Source"); - block->updateSettings({ { "remote_uri", std::string(uri) } }); + block->updateSettings({{"remote_uri", std::string(uri)}}); addBlock(std::move(block)); } namespace { -static bool isDrawable(const gr::property_map &meta, std::string_view category) { +static bool isDrawable(const gr::property_map& meta, std::string_view category) { auto it = meta.find("Drawable"); if (it == meta.end() || !std::holds_alternative(it->second)) { return false; } - const auto &drawableMap = std::get(it->second); + const auto& drawableMap = std::get(it->second); const auto catIt = drawableMap.find("Category"); return catIt != drawableMap.end() && std::holds_alternative(catIt->second) && std::get(catIt->second) == category; } -static std::unique_ptr createGRBlock(gr::PluginLoader &loader, const Block &block) { +static std::unique_ptr createGRBlock(gr::PluginLoader& loader, const Block& block) { DataType t = block.datatype(); auto params = block.parameters(); params["name"] = block.name; @@ -597,7 +626,7 @@ static std::unique_ptr createGRBlock(gr::PluginLoader &loader, c ExecutionContext FlowGraph::createExecutionContext() { ExecutionContext context; - for (const auto &block : m_blocks) { + for (const auto& block : m_blocks) { auto grBlock = createGRBlock(*_pluginLoader, *block); if (!grBlock) { continue; @@ -609,17 +638,17 @@ ExecutionContext FlowGraph::createExecutionContext() { context.toolbarBlocks.push_back(grBlock.get()); } if (isDrawable(block->metaInformation(), "ChartPane")) { - context.plotSinkGrBlocks.insert({ block->name, grBlock.get() }); + context.plotSinkGrBlocks.insert({block->name, grBlock.get()}); } context.graph.addBlock(std::move(grBlock)); } - auto findBlock = [&](std::string_view name) -> gr::BlockModel * { - const auto it = std::ranges::find_if(context.graph.blocks(), [&](auto &b) { return b->uniqueName() == name; }); + auto findBlock = [&](std::string_view name) -> gr::BlockModel* { + const auto it = std::ranges::find_if(context.graph.blocks(), [&](auto& b) { return b->uniqueName() == name; }); return it == context.graph.blocks().end() ? nullptr : it->get(); }; - for (auto &c : m_connections) { + for (auto& c : m_connections) { const auto src = findBlock(c.src.block->m_uniqueName); const auto dst = findBlock(c.dst.block->m_uniqueName); if (!src || !dst) { @@ -638,9 +667,9 @@ ExecutionContext FlowGraph::createExecutionContext() { return context; } -void FlowGraph::handleMessage(const gr::Message &msg) { +void FlowGraph::handleMessage(const gr::Message& msg) { if (msg.serviceName != App::instance().schedulerUniqueName() && msg.endpoint == gr::block::property::kSetting) { - const auto it = std::ranges::find_if(m_blocks, [&](const auto &b) { return b->m_uniqueName == msg.serviceName; }); + const auto it = std::ranges::find_if(m_blocks, [&](const auto& b) { return b->m_uniqueName == msg.serviceName; }); if (it == m_blocks.end()) { fmt::println(std::cerr, "Received settings for unknown block '{}'", msg.serviceName); return; @@ -658,17 +687,13 @@ void FlowGraph::handleMessage(const gr::Message &msg) { } } -void FlowGraph::setPluginLoader(std::shared_ptr loader) { - _pluginLoader = std::move(loader); -} +void FlowGraph::setPluginLoader(std::shared_ptr loader) { _pluginLoader = std::move(loader); } -void FlowGraph::changeBlockType(Block *block, DataType type) { - auto select = [&](const auto &b) { - return block == b.get(); - }; - auto it = std::ranges::find_if(m_blocks, select); +void FlowGraph::changeBlockType(Block* block, DataType type) { + auto select = [&](const auto& b) { return block == b.get(); }; + auto it = std::ranges::find_if(m_blocks, select); assert(it != m_blocks.end()); - std::unique_ptr b{ std::move((*it)) }; + std::unique_ptr b{std::move((*it))}; m_blocks.erase(it); deleteBlock(block); diff --git a/src/ui/cmake/Dependencies.cmake b/src/ui/cmake/Dependencies.cmake index 4570680c..41308982 100644 --- a/src/ui/cmake/Dependencies.cmake +++ b/src/ui/cmake/Dependencies.cmake @@ -48,7 +48,7 @@ FetchContent_Declare( FetchContent_Declare( gnuradio4 GIT_REPOSITORY https://github.com/fair-acc/gnuradio4.git - GIT_TAG f00cba2c81422f143c9d48552921ec63bbaef652 # main as of 2024-05-29 + GIT_TAG c51a5d5253bdd2ce7bbc785970b7ec4bbe2878dc # main as of 2024-06-14 ) FetchContent_MakeAvailable(imgui implot imgui-node-editor stb opencmw-cpp plf_colony gnuradio4)