-
Notifications
You must be signed in to change notification settings - Fork 5.4k
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
Proposal to modernize the architecture of Unit Conversions #379
Comments
This is your friendly Microsoft Issue Bot. I've seen this issue come in and have gone to tell a human about it. |
@danbelcher-MSFT Can you take a look at Rudy's proposal? |
Hey Rudy, I generally agree with your proposal and, in particular, I'm most excited for getting rid of the ratio matrix that is generated. I do have one question.. looking at your proposal for Multi-Part Unit, the data members you proposed were two parallel arrays ( class MultiPartUnit : public Unit {
protected:
std::vector<UnitMetadata> m_compositeUnitMetadata;
...
};
class FeetPlusInches : public MultiPartUnit {
public:
FeetPlusInches() :
m_compositeUnitMetadata{{ FeetMetadata(), InchesMetadata() }}
...
{}
std::vector<std::wstring> GetPartNames() {
std::vector<std::wstring> names;
for (auto& metadata : m_compositeUnitMetadata) {
names.push_back(metadata.Name());
}
return names;
}
... |
It was a very simplified version to only demo the idea, but I agree with you, we should have a class UnitMetadata/SubUnit and
and XAML-side, it can look like :
|
I'm all in favor of simplifying the UnitConverter logic. I'm approving the issue so it's unblocked for PR. I'm sure we'll figure out changes to the architecture as it develops. |
Hey @grochocki, not sure what next steps are for this refactoring work. |
I can start by creating an issue with step 1 only, then once (and if) step 1 approved and merged, create step2a/b, etc... |
I meant in terms of labeling as Approved, going through the feature process, etc. You can see in the history I added 'triage approved' but the issue bot removed the label because this isn't a bug. So my question is about what the right labels are and how to move the deliverable forward. |
We need to make some adjustments to how @MicrosoftIssueBot handles the |
I'm unsure if 'multi part unit's' are a great idea. It seems like a hack for the limitations of the current interface design. I'm wondering if a more conversional interface would be a better fit for such scenarios. So the user inputs
or even faster
and done. No wasting time with clicking buttons, searching through dropdown menus, and filling out forms. |
Hi, I'm writting a prototype in C#, which currently supports pivot value conversion, multi-unit conversion and custom conversion by implementing the interface. There is a little difference between my implementation and this proposal about multi-unit conversion. I didn't compose the multiple units as one unit and override the conversion methods. I just do sum/divide/mod calculations on pivot value: public double ConvertMultiUnitsToPivotValue(params UnitValue[] from)
{
double pivot = 0;
foreach (var unitValue in from)
{
if (!_converters.TryGetValue(unitValue.Unit, out var converter))
{
throw new UnitNotFoundException(unitValue.Unit.ToString());
}
pivot += converter.ConvertToPivotValue(unitValue.Value, unitValue.Unit);
}
return pivot;
}
public double[] ConvertPivotValueToMultiUnits(double pivotValue, params Unit[] toUnits)
{
var res = new double[toUnits.Length];
for (var i = 0; i < toUnits.Length; ++i)
{
var unit = toUnits[i];
if (!_converters.TryGetValue(unit, out var converter))
{
throw new UnitNotFoundException(unit.ToString());
}
var value = converter.ConvertFromPivotValue(pivotValue, unit);
if (i < toUnits.Length - 1)
{
value = Math.Round(value);
}
pivotValue -= converter.ConvertToPivotValue(value, unit);
res[i] = value;
}
return res;
} Link to the above code Repo of the prototype is here: https://github.com/xuhongxu96/UnitConverter |
Problem Statement
There are two flaws with the way the calculator currently manages unit conversions:
Here is a proposal to improve how the calculator manages unit conversions, and for clearing the path for new features going forward.
This proposal is a work-in-progress, some of the changes will lead to a big refactoring of a part of the application. Before even starting to modify how unit conversion works, let's list all the future needs we will have, to be sure to suit all of them and let's debate about the different actions in comments. Don't hesitate to share your ideas and feedback, this document will be regularly updated to take into account your suggestions.
Goal
This proposal is a work-in-progress, some of the changes will lead to a big refactoring of a part of the application. Before even starting to modify how unit conversion works, let's debate and list all the future needs we will have to be sure to suit all of them and let's debate about the different actions in comments. Don't hesitate to share your ideas and feedback, this document will be regularly updated to take into account your suggestions.
Proposal
Step 1: Simplify Data Loader + merge Unit and UnitData.
When the user opens one of the converters,
UnitConverterViewModel
creates aUnitConverter
object, hosting 2 Data loaders:UnitConverterDataLoader
andCurrencyConverterDataLoader
. To simplify our analysis, we can ignore the second data loader, but there are still many issues from the first data loader.The
UnitConverterDataLoader
hosts 2 main objects:map<CategoryId, vector<Unit>>
:Unit
objects containing:OrderedUnit
)UnitData
:UnitData
objects containing:Even if these 2 collections are static and hosted by the same class, they have data redundancy, with the association Category↔Unit being defined by the
map<CategoryId, Unit>
but also by theUnitData
.The only valuable information hosted by
UnitData
isFactor
and we have no reason not to mergeUnit
andUnitData
and create a single object containing all the necessary information to fully describe a unit:Step 2a: Get rid of the ratio matrix
When the users open one of the unit converters,
UnitConverterDataLoader
will, for each category, create a 2D table of all unit combinations from the same category (including pairs of the same unit but also ratio per pairs A->B, B->A) and will calculate the ratio between the 2 units.For unit conversations, 2996 ratios will be calculated (including 1369 for the category Data) and stored.
When the currency converter is opened or when the user refreshes the currency rates, the application calculates and stores 16,384 ratios, while the user will probably use only 1 or 2 of them.
A ratio is stored by a struct
ConversionData
, containing 2 doubles and boolean, so a total of 17 bytes for 1 ratio (without counting the overhead of the vector and the map), or 23KB for units and 278KB for currencies!Now that the calculator is open-sourced, new units will be added, increasing the number of ratios in a category by (Number of units+1)*2 -1).
(very simplified (and not UML compliant) diagram of the current architecture)
Contrary to
UnitConverterDataLoader
, values ofCurrencyConverterDataLoader
can be refreshed by the users, again creating a full matrices of 16K objects each time the user clicks on the update rate button.Based on the high number of calculations (and memory) necessary to build this matrix, this “optimization” doesn't seem to be one, especially when the conversion from one unit to another is a simple multiplication followed by a division (+ 2 additions if Offset is used), so all these efforts don’t seem to necessary.
Getting rid of these caches will have a direct impact on performance and memory usage.
Step 2b: UnitConverter shouldn’t clone data from the 2 data loaders.
Even if
UnitConverter
has access to data fromUnitConverterDataLoader
andCurrencyConverterDataLoader
,UnitConverter
will create and host a full copy of the ratio matrix, categories, and list of units in a category from the 2 data loaders, multiplying the space used by 2.Instead,
UnitConverter
should directly use the data from the data loaders and act as a Facade between the ViewModel and the DataLoaders, saving a lot of space and not forcing a useless sync of caches when users refresh currency rates.Step 3: Units conversions should manage non-linear unit conversions
The application currently only supports linear conversions (ax + b), making it difficult to support some new units.
Some examples of non-linear units:
We already face this issue with Temperature (Celsius, Fahrenheit, …) forcing us to make some exceptions in the application with custom codes.
The following ticket #347 asks for the support of interval of temperatures, instead of adding more exceptions, we should redesign how the unit conversation between units work.
Instead of using 2 doubles: factor and offset, we should use a pivot unit for each category (already the case, but we must explicitly name the unit used) and add 2 functions to
Unit
to convert values from and to this pivot unit.The conversion from one unit to another will be possible via the following code:
ToUnit→FromPivotUnit(FromUnit→ToPivotUnit(fromValue))
Some examples:
Length (pivot unit: millimeter) - linear conversion
Centimeter
Meter
Temperature (pivot unit: Celsius) - non-linear conversion
Celsius
Fahrenheit
To simplify the code (and because 99% of units will simply use a factor), we can provide a class
UnitUsingFactor
, inheriting from Unit and implementingFromPivotUnit
andToPivotUnit
.This solution will allow us to support all types of units without excessive effort and restrictions.
Step 4: Add support of multi-part units
The application currently only supports units with a single number. The 2 tickets #243 and #57 require the application to support units with sub-parts. While the pivot unit can still be a double, values should be represented with a double[]:
Example for Feet+Inches in the category Length (pivot unit: millimeters)
The UI will also need to be modified in order to support the display (and the editing) of units with subparts.
Conclusion
Once these changes are complete, the application will be more efficient and will allow opportunities for new scenarios that are currently not possible, but first, let's work all together on the new architecture.
Don't hesitate to give your feedback and debate, this post will be regularly updated to take feedback into account.
The text was updated successfully, but these errors were encountered: