Skip to content

Conversation

stv0g
Copy link
Contributor

@stv0g stv0g commented Jun 12, 2025

This PR is adding a new node-type for improving the interface between VILLASnode and OPAL-RT software platforms using OPAL-RT Orchestra Co-simulation framework.

We are using this new node-type both in the ENSURE and SEGuRo projects.

See:

@stv0g stv0g requested review from fwege and al3xa23 June 12, 2025 11:09
@stv0g stv0g self-assigned this Jun 12, 2025
@stv0g stv0g added the enhancement New feature or request label Jun 12, 2025
@stv0g stv0g requested a review from n-eiling as a code owner June 12, 2025 11:09
return *reinterpret_cast<const double *>(orchestraData);

default:
throw RuntimeError("Orchestra signal type {} is not supported",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid exception

@stv0g stv0g force-pushed the node-opal-orchestra branch from 14c5307 to 8f323f8 Compare June 13, 2025 05:32
Copy link
Contributor

@al3xa23 al3xa23 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, nice work. I added some minor comments since I am not yet that familiar with code reviews.
General question: I assume the tests are following in the next PR?

@stv0g stv0g force-pushed the node-opal-orchestra branch from 3989418 to d7d2e08 Compare July 17, 2025 17:18
@stv0g
Copy link
Contributor Author

stv0g commented Jul 17, 2025

Hi @al3xa23,

General question: I assume the tests are following in the next PR?

Good point. I dont think we can test this node-type via an integration test unfortunately, as it depends on an Orchestra framework which is provided by an OPAL-RT real-time simulation model. As this is closed-source software, we can not simply include it into our CI tests.

Do you think this qualifies for an exception?

@al3xa23
Copy link
Contributor

al3xa23 commented Jul 18, 2025

Ah okay, I understand. It is fine from my side :)

@n-eiling
Copy link
Member

Would be interesting to know what the license states specifically. Can we even publish this as open source? If yes, then at least the header needs to be open source as well, right? Or is there some clause specifying terms for use as a library?

@stv0g
Copy link
Contributor Author

stv0g commented Aug 16, 2025

Would be interesting to know what the license states specifically. Can we even publish this as open source? If yes, then at least the header needs to be open source as well, right? Or is there some clause specifying terms for use as a library?

The libOpalOrchestra library and its header which are not licensed under an open-source license. This contribution includes neither the library itself or its header.

It merely uses it by dynamically linking against it.

I do not see an issue here as we are licensing the node-type itself (this contribution) under a permissive OSS license (Apache-2.0).

@stv0g stv0g force-pushed the node-opal-orchestra branch from 5ad58b6 to e6b015a Compare August 18, 2025 13:12
@stv0g
Copy link
Contributor Author

stv0g commented Aug 18, 2025

@n-eiling I have addressed most your comments. I only kept using exceptions for now. Which is something I would like to reconsider and also discuss among other contributors (@pjungkamp, @windrad6).

Are you fine with the changes so far (ignoring the exceptions for now)?

@stv0g stv0g requested a review from n-eiling August 18, 2025 13:14
@stv0g
Copy link
Contributor Author

stv0g commented Aug 18, 2025

@al3xa23 Would you mind doing another round of review, and/or approving the PR if you are fine with it?

@pjungkamp
Copy link
Contributor

Just a small nitpick from my side. You're using std::map which requires all entries to be sorted by the key when iterating. This leads most implementations to implement this as e.g. a red-black tree. See https://en.cppreference.com/w/cpp/container/map.html.

I'd prefer to see at least a std::unordered_map here, which is a hash table, since you don't seem to rely on the key ordering in std::map. See https://en.cppreference.com/w/cpp/container/unordered_map.html.

The tree structure of std::map leads to memory access patterns that are hostile to CPU caches for almost everything from lookup to traversal and should almost never be used.

@pjungkamp
Copy link
Contributor

And another nitpick.

Please don't use const std::string & in new code. This much better served by either a plain std::string if you intend to store it somewhere (e.g. in constructors) or by a std::string_view if you're just inspecting/viewing it but don't want to own it.


example

class SomeItem {
private:
  std::string some_string;

public:
  explicit SomeItem(std::string some_string) : some_string(std::move(some_string)) {}
}

unsigned short length;
double defaultValue;

DataItem(const std::string &name) : name(name) {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
DataItem(const std::string &name) : name(name) {}
explicit DataItem(std::string name) : name(std::move(name)) {}

std::map<std::string, std::shared_ptr<Item>> items;
std::string name;

BusItem(const std::string &name) : items(), name(name) {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
BusItem(const std::string &name) : items(), name(name) {}
explicit BusItem(std::string name) : items(), name(std::move(name)) {}

std::map<std::string, std::shared_ptr<Item>> items;
std::string name;

DataSet(const std::string &name) : items(), name(name) {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
DataSet(const std::string &name) : items(), name(name) {}
explicit DataSet(std::string name) : items(), name(std::move(name)) {}

Comment on lines +116 to +161
std::shared_ptr<DataItem> DataSet::upsertItem(const std::string &path,
bool &inserted) {
return upsertItem(split(path), inserted);
}

std::shared_ptr<DataItem>
DataSet::upsertItem(std::vector<std::string> pathComponents, bool &inserted) {
auto &name = pathComponents.front();

auto it = items.find(name);
if (it == items.end()) { // No item with this name exists. Create a new one.
if (pathComponents.size() == 1) { // No bus, just a signal.
auto item = std::make_shared<DataItem>(name);
items.emplace(name, item);
inserted = true;
return item;
} else {
auto bus = std::make_shared<BusItem>(name);
items.emplace(name, bus);

pathComponents.erase(pathComponents.begin());
return bus->upsertItem(pathComponents, inserted);
}
} else {
if (pathComponents.size() == 1) {
auto item = std::dynamic_pointer_cast<DataItem>(it->second);
if (!item) {
throw RuntimeError("Item with name '{}' is not a data item",
pathComponents.front());
}

inserted = false;
return item;
} else {
// Item with this name exists. Check if it is a bus.
auto bus = std::dynamic_pointer_cast<BusItem>(it->second);
if (!bus) {
throw RuntimeError("Item with name '{}' is not a bus",
pathComponents.front());
}

pathComponents.erase(pathComponents.begin());
return bus->upsertItem(pathComponents, inserted);
}
}
}
Copy link
Contributor

@pjungkamp pjungkamp Aug 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::shared_ptr<DataItem> DataSet::upsertItem(const std::string &path,
bool &inserted) {
return upsertItem(split(path), inserted);
}
std::shared_ptr<DataItem>
DataSet::upsertItem(std::vector<std::string> pathComponents, bool &inserted) {
auto &name = pathComponents.front();
auto it = items.find(name);
if (it == items.end()) { // No item with this name exists. Create a new one.
if (pathComponents.size() == 1) { // No bus, just a signal.
auto item = std::make_shared<DataItem>(name);
items.emplace(name, item);
inserted = true;
return item;
} else {
auto bus = std::make_shared<BusItem>(name);
items.emplace(name, bus);
pathComponents.erase(pathComponents.begin());
return bus->upsertItem(pathComponents, inserted);
}
} else {
if (pathComponents.size() == 1) {
auto item = std::dynamic_pointer_cast<DataItem>(it->second);
if (!item) {
throw RuntimeError("Item with name '{}' is not a data item",
pathComponents.front());
}
inserted = false;
return item;
} else {
// Item with this name exists. Check if it is a bus.
auto bus = std::dynamic_pointer_cast<BusItem>(it->second);
if (!bus) {
throw RuntimeError("Item with name '{}' is not a bus",
pathComponents.front());
}
pathComponents.erase(pathComponents.begin());
return bus->upsertItem(pathComponents, inserted);
}
}
}
std::shared_ptr<DataItem>
DataSet::upsertItem(std::string_view path, bool &inserted) {
auto separator = path.find('/');
auto name = std::string(path.substr(0, separator));
auto it = items.find(name);
if (it == items.end()) { // No item with this name exists. Create a new one.
if (separator == std::string_view::npos) { // No bus, just a signal.
auto item = std::make_shared<DataItem>(name);
items.emplace(std::move(name), item);
inserted = true;
return item;
} else {
auto bus = std::make_shared<BusItem>(name);
items.emplace(std::move(name), bus);
return bus->upsertItem(path.substr(separator + 1), inserted);
}
} else {
if (separator == std::string_view::npos) {
auto item = std::dynamic_pointer_cast<DataItem>(it->second);
if (!item) {
throw RuntimeError("Item with name '{}' is not a data item", name);
}
inserted = false;
return item;
} else {
// Item with this name exists. Check if it is a bus.
auto bus = std::dynamic_pointer_cast<BusItem>(it->second);
if (!bus) {
throw RuntimeError("Item with name '{}' is not a bus", name);
}
return bus->upsertItem(path.substr(separator + 1), inserted);
}
}
}

Why did you create that std::vector<std::string> overload for the path components? I don't see it being used anywhere.

This version does not allocate any memory unless explicitly requested by a std::string constructor without being more complicated. You can also remove the split function at the top of the file.

Of course it's just a suggestion. It's also not in a hot path, so the current version would also be fine. I just wanted to do some advertisement for std::string_view.

@stv0g stv0g force-pushed the node-opal-orchestra branch from 748e62e to 78e2259 Compare August 26, 2025 05:46
stv0g and others added 3 commits August 26, 2025 09:04
Signed-off-by: Steffen Vogel <post@steffenvogel.de>
Signed-off-by: Steffen Vogel <post@steffenvogel.de>
Signed-off-by: Steffen Vogel <steffen.vogel@opal-rt.com>
stv0g added 14 commits August 26, 2025 09:04
… for absent indices

Signed-off-by: Steffen Vogel <steffen.vogel@opal-rt.com>
Signed-off-by: Steffen Vogel <steffen.vogel@opal-rt.com>
Signed-off-by: Steffen Vogel <steffen.vogel@opal-rt.com>
Signed-off-by: Steffen Vogel <steffen.vogel@opal-rt.com>
…nly setting

Signed-off-by: Steffen Vogel <steffen.vogel@opal-rt.com>
Signed-off-by: Steffen Vogel <steffen.vogel@opal-rt.com>
…ommands

Signed-off-by: Steffen Vogel <steffen.vogel@opal-rt.com>
Signed-off-by: Steffen Vogel <steffen.vogel@opal-rt.com>
Signed-off-by: Steffen Vogel <steffen.vogel@opal-rt.com>
…tions

Signed-off-by: Steffen Vogel <steffen.vogel@opal-rt.com>
Signed-off-by: Steffen Vogel <steffen.vogel@opal-rt.com>
Signed-off-by: Steffen Vogel <steffen.vogel@opal-rt.com>
Signed-off-by: Steffen Vogel <steffen.vogel@opal-rt.com>
Signed-off-by: Steffen Vogel <steffen.vogel@opal-rt.com>
@stv0g stv0g force-pushed the node-opal-orchestra branch from 78e2259 to 73d36c9 Compare August 26, 2025 07:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request node::opal
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants