-
Notifications
You must be signed in to change notification settings - Fork 21
Control access to state variables in transform method #421
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
ZedThree
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is broadly the right direction, although I haven't got a feel for exactly how ergonomic it is yet or how all the pieces connect exactly
cd81c8c to
5de4c7e
Compare
|
Does this now include #399? Is that on purpose? |
314cd8c to
ea2cf95
Compare
ea2cf95 to
9048b54
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #421 +/- ##
==========================================
+ Coverage 26.41% 29.52% +3.11%
==========================================
Files 90 94 +4
Lines 8105 8768 +663
Branches 1133 1228 +95
==========================================
+ Hits 2141 2589 +448
- Misses 5745 5914 +169
- Partials 219 265 +46 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
9048b54 to
e5d5af7
Compare
ead4a05 to
7d17184
Compare
ZedThree
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made a bunch of mostly minor comments. I realised why I've been struggling to get my head around some of the implementation details, and that's because the design feels a little backwards. We have an array where each index is a permission level and the element is a region. But if we look at say unix permissions, we have a structure where the position is the user/group and the value is the permission level (and this is a bit-field, being able to set read | write permissions).
What if we had something like this:
enum class AccessRights { none, read_if_set, read, write, final_ };
enum class PermissionRegions { interior, boundaries };
struct AccessPermission {
AccessRights interior = AccessRights::none;
AccessRights boundaries = AccessRights::none;
auto operator[](PermissionRegions region) {
switch(region) {
case PermissionRegions::interior:
return interior;
case PermissionRegions::boundaries:
return boundaries;
}
}
};Is this way round easier to reason about? It feels a lot simpler. To take the examples from the AccessRights docstring, we'd now have:
auto only_read_if_set = AccessPermission {
.interior = AccessRights::read_if_set,
.boundaries = AccessRights::read_if_set
};
auto read_only = AccessPermission {
.interior = AccessRights::read,
.boundaries = AccessRights::read
};
auto write_boundaries = AccessPermission {
.interior = AccessRights::none,
.boundaries = AccessRights::write
};
auto read_write_everywhere = AccessPermission {
.interior = AccessRights::write,
.boundaries = AccessRights::write
};
auto final_write_boundaries_read_interior = AccessPermission {
.interior = AccessRights::read,
.boundaries = AccessRights::write
};Checking if a AccessPermission is valid for a given region and access level is just:
read_only[region] >= AccessRights::readThis way round would of course also work with an array, but I keep suggesting structs because names are really important, and the lack of them in the current design makes e.g. applyLowerPermissions very difficult to parse. I'm not even sure it's needed this proposed design.
Applying the higher of two permissions is easier:
auto result = AccessPermission {
.interior = std::max(lhs.interior, rhs.interior),
.boundaries = std::max(lhs.boundaries, rhs.boundaries),
};5a25b72 to
2acb30f
Compare
|
I do agree that it is more intuitive to store the level of access for each region then the regions with each level of access. In fact, that was my original plan. The reason I changed it was that @bendudson was expressing some concerns about performance overhead. I'm pretty sure what I've done is more performant. Checking whether we have permission to read or write a particular variable will involved:
This remains constant regardless of how many regions we define (e.g., if we start using all the same types of regions defined in BOUT++). Assuming you need to check the permissions over n regions, your approach would involved
When we only have two regions there is little to no difference here. If we ever break down the boundaries into more fine-grained regions (e.g., RGN_NOBNDRY, RGN_XGUARDS, RGN_YGUARDS, RGN_ZGUARDS, RGN_CORNERS) then the savings start to become more significant. Moreover, the ability to use a bit-set to express the the desired region makes it straightforward to handle cases like RGN_ALL, RGN_NOX, RGN_GUARDS, etc. So, I guess the fundamental question is whether we should take a YAGNI approach to keep the implementation more intuitive based on the current features. I think what I've done is more future-proof but that future might never come. The performance probably isn't a big deal, especially since these checks will likely be switched off when running big simulations. |
|
The other problem with refactoring the way permissions are stored along the lines @ZedThree suggests is that I'm only on this project for another week, so it's not clear I'd have time to do that, get it reviewed, etc. |
3b76f8f to
a79db98
Compare
a79db98 to
5546501
Compare
|
@ZedThree I've created issues for all of your comments I have not implemented. Could you mark this as approved (or at least remove the request for changes)? |
bendudson
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @cmacmackin ! This is a quite monumental effort. Very impressive. I'm slightly sorry to see the Wild West tamed, but this is a much more organised and robust solution. Thanks for the manual writeup, and of course all the examples in components. I think this will be good to work with, and it removes some of the footguns that kept going off in our saloon.
I don't promise that I've read every line or tried this code out. I also don't promise that I haven't. I think this is the right direction to go in, and we can refine it as we use it more. Thanks Chris!
docs/sphinx/developer.rst
Outdated
| Options is a dictionary-like class originally developed for parsing BOUT++ options. | ||
| In Hermes-3, it is used as a general purpose dictionary. | ||
|
|
||
| What are Permissions? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this documentation will be hard to understand by people new to C++, and could use a couple of practical examples, for example based on evolve_density or similar. One thing that was a bit confusing in the beginning was that you set the permissions using names which are then substituted later. I was intuitively looking for a line saying something like setPermissions and couldn't find it, which was confusing.
Also added an additional constructor for SpeciesInfo
Co-authored-by: Peter Hill <[email protected]>
5546501 to
982df7a
Compare
- use forwarding reference `T&&` to avoid duplicating overloads on _l-values_/_r-values_ - use `std::enable_if` to hide templated functions from all `T` except `GuardedOptions` - use `decltype(auto)` and `return std::forward` to ensure return type is the same value category as input argument
Use some template black magic to avoid duplication
|
@ZedThree I believe I have addressed or opened issues for all of your comments. Could you please approve the changes? @mikekryjak I've updated the documentation as you requested. Could you confirm whether this is ready to be merged? |
mikekryjak
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the docs improvements @cmacmackin, it reads great now.
- Unused arguments in some of the functions for GuardedOptions - Unit tests expecting an exception to be thrown, but this only happens when checking is done.
This is a major refactor of how components work. They are now required to create a
Permissionsobject in their constructor, which contains information on which state variables the transform method is allowed to read and write. Trying to read/write a variable without permission will result in a runtime error. This should help ensure people are more thoughtful in their design of components and prevent inadvertent side-effects. It also provides necessary information for implementing the automatic ordering of the execution of components (#384).Closes #383