Skip to content
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

#72 #69 #37 #14 #35 Redfine all Model classes to be readonly for public access. Use FinishUp method to calc useful properties and support for extended Multiplexing. #73

Conversation

Uight
Copy link
Contributor

@Uight Uight commented Aug 20, 2024

All classes belonging to the model namespace are now readonly for public access. The immutable stuff was therefor removed. Internally all values can be written as before. Every class that profits from it now has a FinishUp method to calculate some usefull properties. Also the signals and the messages are now a dictionary which should resolve #14.

Some changes are breaking changes!!! (e.g dbc is now part of model namespace)

In the finish up methods i also update the MessageID for all signals in a message after applying the extendedId which should fix #72.
Also the IsMultiplexed value is also calculates once in this FinishUp method to resolve #37

The targetFramework was set to .net462, netstandard2.0 and .net8.0 to resolve #69

Also there are some other properties in the signal class like HasScaling which would help if extending the packling/unpacking functionalities for a next step after #67

Edit: Now should also resolve #35

…eadonly for public access. Use FinishUp method to calc useful properties.
@Uight
Copy link
Contributor Author

Uight commented Aug 21, 2024

im now working on a follow up to this. i enabled nullability for the dbcParserLib which makes it easier to see which properties could actually be null. I needed this for ExtendedMultiplexing as i adjusted the MultiplexingInfo to contain either the simple info or the complex info or none if the signal is not multiplexed.
im currently trying to figure out a way to make access to custom properties better. (all values are nullable and one is set atm). When i figure that out and this PR should be merged allready ill create a new PR or ill update this one.

@Uight
Copy link
Contributor Author

Uight commented Aug 23, 2024

With the last commit i added support for Extended Multiplexing in attempt to resolve #35
because im using nullables there i changes the project to define that but then i realized that tons of stuff in the custom properties is nullable. so i changed the properties to use interfaces instead of 5 seperate fields which i think is nicer but now i think i should probably use a interface for the multiplexing too.

@EFeru you might wanna check the new extended multiplexing support. i suppose all information is there now but im not so sure about the structure. Also please check out the design for the model classes. I think the way it is now is atleast better as before but could probably still be improved im open to suggestens.

Im also not to sure about these three functions:
FillNodesNotSetCustomPropertyWithDefault
FillMessagesNotSetCustomPropertyWithDefault
FillSignalsNotSetCustomPropertyWithDefault

I decided to set the defautl only if a default exists which i think is not required and only if the value has not been set yet. However this could be changes to accomodate #70 i think at least.

@Uight Uight changed the title #72 #69 #37 #14 Redfine all Model classes to be readonly for public access. Use FinishUp method to calc useful properties. #72 #69 #37 #14 #35 Redfine all Model classes to be readonly for public access. Use FinishUp method to calc useful properties. Aug 23, 2024
@Uight Uight changed the title #72 #69 #37 #14 #35 Redfine all Model classes to be readonly for public access. Use FinishUp method to calc useful properties. #72 #69 #37 #14 #35 Redfine all Model classes to be readonly for public access. Use FinishUp method to calc useful properties and support for extended Multiplexing. Aug 24, 2024
@Uight
Copy link
Contributor Author

Uight commented Aug 24, 2024

Im now considering this more or less done. If merged i would suggest doing this as a 2.0 Version as many things changed.

As for the extended multiplexing support i cleaned that up but as for now the order is pretty much reversed. You can find the multiplexor for any multiplexed signal. But if you want the signals a multiplexor applies to you would need to search yourself. This is because the way extended multiplexing is defined. However you could add the multiplexed signals to a multiplexor after all the multiplexing was finalized.

@Adhara3
Copy link
Collaborator

Adhara3 commented Aug 26, 2024

Hi,

thanks for the effort. I would nevertheless try to split this one into multiple PRs.
Changing the API is very different than adding full multiplexed support. At least the former requires some alignement on design decisions. It's a bit tricky in these issues comments, but if we have no other way to communicate, let's open a new issue talking about design targets and API.

Cheers
A

@Uight
Copy link
Contributor Author

Uight commented Aug 26, 2024

@Adhara3 im not sure if splitting this up is possible in a good way. Sure i could remove the framework stuff then probably the check would also go thorugh as UnitTest is running to.
The problem with the multiplexed stuff is that i based that on teh methods for finishing up stuff added with the rest of the api change.
If you want to open a seperate issue for the api stuff i would be happy to discuss.

Copy link
Collaborator

@Adhara3 Adhara3 left a comment

Choose a reason for hiding this comment

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

Overall this PR is too wide and big. IMHO it should be splitted into smaller parts. Some are easy to do, like the target framework, other maybe more complex, but reviewing these massive and heterogeneous code changes is very hard and error prone.

Moreover, API changes and design changes should be handled with care and maybe discussed into a dedicated issue to reach a common agreement on the way to go (so that design, intentions and scenarios are shared upfront).
In particular please pay attention to the immutable/FinishUp relationship and to the string.Empty vs null meaning.

Otherwise the fixes sound reasonable, I have some minor style concerns (e.g. type checking in switch statement) but put aside some possible performance and readability, I am not too strongly opinionated

Thanks for your effort

@@ -22,13 +22,14 @@ public SignalLineParser(IParseFailureObserver observer)

public bool TryParse(string line, IDbcBuilder builder, INextLineProvider nextLineProvider)
{
if (line.TrimStart().StartsWith(SignalLineStarter) == false)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why change this? The reason why we set condition like this is readability. ! at the beginning may go unnoticed

Copy link
Contributor Author

@Uight Uight Aug 27, 2024

Choose a reason for hiding this comment

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

A lot of the changes were rider auto applying my companies code guidlines. Youll see that in most files i touched. For some stuff like the m_ naming i changed project specific rider rules so it wouldnt change that too ;)

@@ -7,10 +7,10 @@ public class Message
{
public uint ID { get; internal set; }
public bool IsExtID { get; internal set; }
public string Name { get; internal set; }
public string Name { get; internal set; } = string.Empty;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't get why you are setting all those to default string.Empty. The idea is that a field that is null has not been set while a field that is "empty" has been set to empty during parsing. null and string.Empty (or null and new string[0]) do have different meaning and I believe default should be not set (i.e. null)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this was because i initially set the project to use nullablility which then markes a string as an issue if not defined nullable explicitly or initilaized.
overall i just dont like Nulls in a public API; expecially in this case here a name is also the key. name would definitly be something to set in the constructor.
i generally way wondering why mostly no constructors are used. When i would use property initializers instead like that i would still set them to init for set: public string Name {get;init;} which works with the property initializers aswell

public class Dbc
{
public IEnumerable<Node> Nodes { get; }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Given all the comments already provided about the read only approach I would stress that IEnumerable<T> is by design a readonly interface because it allows only reading (or iterating through iterator if you will), not editing (the collection).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Another change due to code guidlines wich i normally follow. We only use IEnumarbale if we have functions that yield return. IEnumerable for me markes that the list can be build up while processing which is not the case here

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ok, understood, sounds reasonable, event though how the collection is built, if lazy or eager, is a specific implementation detail of the implementing class.

{
Nodes = nodes;
Messages = messages;
EnvironmentVariables = environmentVariables;

FinishUp();
Copy link
Collaborator

Choose a reason for hiding this comment

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

I do get the intent here and I agree that precalculating some unmutable values (e.g. IsMultiplexed) may help with performance. Nevertheless I think this is the wrong place (I would not do this stuff in the constructor) and that from a design point of view there should be a better solution also to solve the read only part.

dbc being line by line requires mutability during parsing, this is why most objects were mutable. Properties are spread all over the dbc file and as such I need to keep an object open to add/change/override properties while I go. If the final object should be immutable and optimized, then probably it's a different object. We then need a internal class ParsingMessage (Message here is an example, but this should probably propagate to Signals too, if necessary) which is mutable (note the internal keyword, not public API) and when the parsing is over then ParsingMessages are converted into public class Message which is optimized and unmutable.
The conversion is not Message responsibility itself, but parsing engine's one (e.g. the parser knows whern it's done). Also, think about it, there is a sort of race condition. Message list is not ready/usable (IsMultiplexed is not telling the truth) until the list itself is passed into a dbc constructor...

Sure, that this would bring to some sort of code duplication but would allow better responsibility separation keeping mutability where required by language (i.e. dbc) design, forcing unmutable and optimized properties where required.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i would still think that using the immutable stuff would be good for public api. In fact the first implementation i did was using that.
I just had some problems with that: It was unclear if the packer should work on immutable stuff or not. i didnt like the classes exposed in the public api all being named immutable. Thats why i would name the immutable stuff just signal,message aso. and the mutable stuff that is internal could just be named mutable or something like that.
I also think the public api shouldnt allow reference replacement as then you could just add unvalidated stuff. thats why i did all the setter to internal.
And just wrapping the internal stuff with a readonly for pulic access meant that you dont have to rebuild the dbc necessaryly when only adding small stuff. With the immutable stuff you would need to regenaret the immutable objects.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I do agree that a read only dbc should be readonly.


public CustomProperty(CustomPropertyDefinition customPropertyDefinition)
public CustomProperty(string name, CustomPropertyDataType dataType, ICustomPropertyDefinition propertyDefinition, IParseFailureObserver observer)
Copy link
Collaborator

Choose a reason for hiding this comment

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

The pattern we were using is found in other projects like google's protobuf (see: oneof). I personally do not like runtime type checking. Moreover, CustomPropertyDataType should be part of CustomPropertyDefinition so if you really want to keep the interface and type checking, maybe CustomPropertyDataType should be part of ICustomPropertyDefinition

Copy link
Contributor Author

@Uight Uight Aug 27, 2024

Choose a reason for hiding this comment

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

the main problem for me with this approach is that you would always need a switch case when accessing the property which tends to be "long" code. In my code you coudl use the same thing for double,int and hex but for string and enum it still sucked ;)
the main thing that is annoying is that the property and the property defintion are seperated and you need this double checkign everywhere. I would consider it way nicer if you just have a custom proerty containing one of 5 objects which holds all the information in it.

Thanks for teh code review btw

Copy link
Collaborator

Choose a reason for hiding this comment

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

Property and definition are separated beacause in the dbc they are separated, they are different tags.
This is a scenario where the parsing classes may require some trick but then for the API we can merge them together once the parsing is done

Cheers
A

@Adhara3 Adhara3 closed this Aug 27, 2024
@Uight Uight deleted the Update-Messages-property-of-the-DBC-class-to-Dictionary branch August 28, 2024 20:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants