diff --git a/src/CalcManager/CCalcManager.cpp b/src/CalcManager/CCalcManager.cpp index 5080bbc2..0c4601cc 100644 --- a/src/CalcManager/CCalcManager.cpp +++ b/src/CalcManager/CCalcManager.cpp @@ -1,8 +1,14 @@ +#define __STDC_WANT_LIB_EXT1__ 1 #include "pch.h" #include "CCalcManager.h" #include "CalculatorManager.h" #include "CalculatorResource.h" #include +#include +#include +#include +#include +#include using namespace CalculationManager; @@ -71,7 +77,12 @@ public: { auto str = convert.to_bytes(memorizedNumbers[i]); auto pData = new char[str.size() + 1]; - strncpy_s(pData, str.size(), str.data(), str.size()); + +#ifdef __STDC_LIB_EXT1__ + strcpy_s(pData, str.size(), str.data()); +#else + strcpy(pData, str.data()); +#endif numbers[i] = pData; } @@ -114,10 +125,15 @@ public: void* CalculatorManager_Create(CalculatorManager_CreateParams* pParams) { + printf("-> NativeCalcManager:CalculatorManager_Create(%p)\n", pParams); + auto calcDisplay = new CalcDisplay(*pParams); auto resProvider = new ResourceProvider(*pParams); + printf("NativeCalcManager:CalculatorManager_Create: Got providers\n"); + auto cm = new CalculatorManager(calcDisplay, resProvider); + printf("<- NativeCalcManager:CalculatorManager_Create(%p)\n", pParams); return cm; } diff --git a/src/CalcManager/CCalcManager.h b/src/CalcManager/CCalcManager.h index 02db9d34..37e10406 100644 --- a/src/CalcManager/CCalcManager.h +++ b/src/CalcManager/CCalcManager.h @@ -51,8 +51,14 @@ struct CalculatorManager_CreateParams { GetCEngineStringFunc GetCEngineString; }; +#if defined(__EMSCRIPTEN__) +#define DLL_EXPORT +#else +#define DLL_EXPORT __declspec(dllexport) +#endif + extern "C" { - __declspec(dllexport) void* CalculatorManager_Create(CalculatorManager_CreateParams* params); - __declspec(dllexport) void CalculatorManager_SendCommand(void* manager, int command); + DLL_EXPORT void* CalculatorManager_Create(CalculatorManager_CreateParams* params); + DLL_EXPORT void CalculatorManager_SendCommand(void* manager, int command); } diff --git a/src/CalcManager/CalcManager.vcxproj b/src/CalcManager/CalcManager.vcxproj index c22904ad..d5bf99b1 100644 --- a/src/CalcManager/CalcManager.vcxproj +++ b/src/CalcManager/CalcManager.vcxproj @@ -314,7 +314,6 @@ - @@ -355,7 +354,6 @@ Create Create - diff --git a/src/CalcManager/CalcManager.vcxproj.filters b/src/CalcManager/CalcManager.vcxproj.filters index 40d45f51..d32a02a8 100644 --- a/src/CalcManager/CalcManager.vcxproj.filters +++ b/src/CalcManager/CalcManager.vcxproj.filters @@ -76,7 +76,6 @@ - CEngine @@ -106,7 +105,6 @@ RatPack - diff --git a/src/CalcManager/CalcManager.wasm b/src/CalcManager/CalcManager.wasm new file mode 100644 index 00000000..ec28806f Binary files /dev/null and b/src/CalcManager/CalcManager.wasm differ diff --git a/src/CalcManager/CalculatorManager.cpp b/src/CalcManager/CalculatorManager.cpp index 2b224020..b60e5ecf 100644 --- a/src/CalcManager/CalculatorManager.cpp +++ b/src/CalcManager/CalculatorManager.cpp @@ -14,7 +14,11 @@ static constexpr size_t SERIALIZED_NUMBER_MINSIZE = 3; // Converts Memory Command enum value to unsigned char, // while ignoring Warning C4309: 'conversion' : truncation of constant value +#if defined(__EMSCRIPTEN__) +#define MEMORY_COMMAND_TO_UNSIGNED_CHAR(c) static_cast(c) +#else #define MEMORY_COMMAND_TO_UNSIGNED_CHAR(c) __pragma(warning(push)) __pragma(warning(disable : 4309)) static_cast(c) __pragma(warning(pop)) +#endif namespace CalculationManager { diff --git a/src/CalcManager/CalculatorVector.h b/src/CalcManager/CalculatorVector.h index 3795ecf0..8595b0b5 100644 --- a/src/CalcManager/CalculatorVector.h +++ b/src/CalcManager/CalculatorVector.h @@ -10,7 +10,7 @@ #include "Ratpack/CalcErr.h" #include // for std::out_of_range -#if !defined(__WEBASSEMBLY__) +#if !defined(__EMSCRIPTEN__) #include #include // for SAL #endif diff --git a/src/CalcManager/UnitConverter.cpp b/src/CalcManager/UnitConverter.cpp deleted file mode 100644 index 3b5490a0..00000000 --- a/src/CalcManager/UnitConverter.cpp +++ /dev/null @@ -1,1105 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -#include -#include -#include // for std::sort -#include "Command.h" -#include "UnitConverter.h" - -using namespace concurrency; -using namespace std; -using namespace UnitConversionManager; - -static constexpr uint32_t EXPECTEDSERIALIZEDTOKENCOUNT = 7; -static constexpr uint32_t EXPECTEDSERIALIZEDCONVERSIONDATATOKENCOUNT = 3; -static constexpr uint32_t EXPECTEDSERIALIZEDCATEGORYTOKENCOUNT = 3; -static constexpr uint32_t EXPECTEDSERIALIZEDUNITTOKENCOUNT = 6; -static constexpr uint32_t EXPECTEDSTATEDATATOKENCOUNT = 5; -static constexpr uint32_t EXPECTEDMAPCOMPONENTTOKENCOUNT = 2; - -static constexpr int32_t MAXIMUMDIGITSALLOWED = 15; -static constexpr int32_t OPTIMALDIGITSALLOWED = 7; - -static constexpr wchar_t LEFTESCAPECHAR = L'{'; -static constexpr wchar_t RIGHTESCAPECHAR = L'}'; - -static const double OPTIMALDECIMALALLOWED = pow(10, -1 * (OPTIMALDIGITSALLOWED - 1)); -static const double MINIMUMDECIMALALLOWED = pow(10, -1 * (MAXIMUMDIGITSALLOWED - 1)); - -unordered_map quoteConversions; -unordered_map unquoteConversions; - -/// -/// Constructor, sets up all the variables and requires a configLoader -/// -/// An instance of the IConverterDataLoader interface which we use to read in category/unit names and conversion data -UnitConverter::UnitConverter(_In_ const shared_ptr& dataLoader) - : UnitConverter::UnitConverter(dataLoader, nullptr) -{ -} - -/// -/// Constructor, sets up all the variables and requires two configLoaders -/// -/// An instance of the IConverterDataLoader interface which we use to read in category/unit names and conversion data -/// An instance of the IConverterDataLoader interface, specialized for loading currency data from an internet service -UnitConverter::UnitConverter(_In_ const shared_ptr& dataLoader, _In_ const shared_ptr& currencyDataLoader) -{ - m_dataLoader = dataLoader; - m_currencyDataLoader = currencyDataLoader; - // declaring the delimiter character conversion map - quoteConversions[L'|'] = L"{p}"; - quoteConversions[L'['] = L"{lc}"; - quoteConversions[L']'] = L"{rc}"; - quoteConversions[L':'] = L"{co}"; - quoteConversions[L','] = L"{cm}"; - quoteConversions[L';'] = L"{sc}"; - quoteConversions[LEFTESCAPECHAR] = L"{lb}"; - quoteConversions[RIGHTESCAPECHAR] = L"{rb}"; - unquoteConversions[L"{p}"] = L'|'; - unquoteConversions[L"{lc}"] = L'['; - unquoteConversions[L"{rc}"] = L']'; - unquoteConversions[L"{co}"] = L':'; - unquoteConversions[L"{cm}"] = L','; - unquoteConversions[L"{sc}"] = L';'; - unquoteConversions[L"{lb}"] = LEFTESCAPECHAR; - unquoteConversions[L"{rb}"] = RIGHTESCAPECHAR; - ClearValues(); - ResetCategoriesAndRatios(); -} - -void UnitConverter::Initialize() -{ - m_dataLoader->LoadData(); -} - -bool UnitConverter::CheckLoad() -{ - if (m_categories.empty()) - { - ResetCategoriesAndRatios(); - } - return !m_categories.empty(); -} - -/// -/// Returns a list of the categories in use by this converter -/// -vector UnitConverter::GetCategories() -{ - CheckLoad(); - return m_categories; -} - -/// -/// Sets the current category in use by this converter, -/// and returns a list of unit types that exist under the given category. -/// -/// Category struct which we are setting -CategorySelectionInitializer UnitConverter::SetCurrentCategory(const Category& input) -{ - if (m_currencyDataLoader != nullptr && m_currencyDataLoader->SupportsCategory(input)) - { - m_currencyDataLoader->LoadData(); - } - - vector newUnitList{}; - if (CheckLoad()) - { - if (m_currentCategory.id != input.id) - { - vector& unitVector = m_categoryToUnits[m_currentCategory]; - for (unsigned int i = 0; i < unitVector.size(); i++) - { - unitVector[i].isConversionSource = (unitVector[i].id == m_fromType.id); - unitVector[i].isConversionTarget = (unitVector[i].id == m_toType.id); - } - m_currentCategory = input; - if (!m_currentCategory.supportsNegative && m_currentDisplay.front() == L'-') - { - m_currentDisplay.erase(0, 1); - } - } - - newUnitList = m_categoryToUnits[input]; - } - - InitializeSelectedUnits(); - return make_tuple(newUnitList, m_fromType, m_toType); -} - -/// -/// Gets the category currently being used -/// -Category UnitConverter::GetCurrentCategory() -{ - return m_currentCategory; -} - -/// -/// Sets the current unit types to be used, indicates a likely change in the -/// display values, so we re-calculate and callback the updated values -/// -/// Unit struct which the user is modifying -/// Unit struct we are converting to -void UnitConverter::SetCurrentUnitTypes(const Unit& fromType, const Unit& toType) -{ - if (!CheckLoad()) - { - return; - } - - m_fromType = fromType; - m_toType = toType; - Calculate(); - - UpdateCurrencySymbols(); -} - -/// -/// Switches the active field, indicating that we are now entering data into -/// what was originally the return field, and storing results into what was -/// originally the current field. We swap appropriate values, -/// but do not callback, as values have not changed. -/// -/// -/// wstring representing the value user had in the field they've just activated. -/// We use this to handle cases where the front-end may choose to trim more digits -/// than we have been storing internally, in which case appending will not function -/// as expected without the use of this parameter. -/// -void UnitConverter::SwitchActive(const wstring& newValue) -{ - if (!CheckLoad()) - { - return; - } - - swap(m_fromType, m_toType); - swap(m_currentHasDecimal, m_returnHasDecimal); - m_returnDisplay = m_currentDisplay; - m_currentDisplay = newValue; - m_currentHasDecimal = (m_currentDisplay.find(L'.') != m_currentDisplay.npos); - m_switchedActive = true; - - if (m_currencyDataLoader != nullptr && m_vmCurrencyCallback != nullptr) - { - shared_ptr currencyDataLoader = GetCurrencyConverterDataLoader(); - const pair currencyRatios = currencyDataLoader->GetCurrencyRatioEquality(m_fromType, m_toType); - - m_vmCurrencyCallback->CurrencyRatiosCallback(currencyRatios.first, currencyRatios.second); - } -} - -wstring UnitConverter::CategoryToString(const Category& c, const wchar_t* delimiter) -{ - wstringstream out(wstringstream::out); - out << Quote(std::to_wstring(c.id)) << delimiter << Quote(std::to_wstring(c.supportsNegative)) << delimiter << Quote(c.name) << delimiter; - return out.str(); -} - -vector UnitConverter::StringToVector(const wstring& w, const wchar_t* delimiter, bool addRemainder) -{ - size_t delimiterIndex = w.find(delimiter); - size_t startIndex = 0; - vector serializedTokens = vector(); - while (delimiterIndex != w.npos) - { - serializedTokens.push_back(w.substr(startIndex, delimiterIndex - startIndex)); - startIndex = delimiterIndex + (int)wcslen(delimiter); - delimiterIndex = w.find(delimiter, startIndex); - } - if (addRemainder) - { - delimiterIndex = w.size(); - serializedTokens.push_back(w.substr(startIndex, delimiterIndex - startIndex)); - } - return serializedTokens; -} - -Category UnitConverter::StringToCategory(const wstring& w) -{ - vector tokenList = StringToVector(w, L";"); - assert(tokenList.size() == EXPECTEDSERIALIZEDCATEGORYTOKENCOUNT); - Category serializedCategory; - serializedCategory.id = _wtoi(Unquote(tokenList[0]).c_str()); - serializedCategory.supportsNegative = (tokenList[1].compare(L"1") == 0); - serializedCategory.name = Unquote(tokenList[2]); - return serializedCategory; -} - -wstring UnitConverter::UnitToString(const Unit& u, const wchar_t* delimiter) -{ - wstringstream out(wstringstream::out); - out << Quote(std::to_wstring(u.id)) << delimiter << Quote(u.name) << delimiter << Quote(u.abbreviation) << delimiter - << std::to_wstring(u.isConversionSource) << delimiter << std::to_wstring(u.isConversionTarget) << delimiter << std::to_wstring(u.isWhimsical) - << delimiter; - return out.str(); -} - -Unit UnitConverter::StringToUnit(const wstring& w) -{ - vector tokenList = StringToVector(w, L";"); - assert(tokenList.size() == EXPECTEDSERIALIZEDUNITTOKENCOUNT); - Unit serializedUnit; - serializedUnit.id = _wtoi(Unquote(tokenList[0]).c_str()); - serializedUnit.name = Unquote(tokenList[1]); - serializedUnit.accessibleName = serializedUnit.name; - serializedUnit.abbreviation = Unquote(tokenList[2]); - serializedUnit.isConversionSource = (tokenList[3].compare(L"1") == 0); - serializedUnit.isConversionTarget = (tokenList[4].compare(L"1") == 0); - serializedUnit.isWhimsical = (tokenList[5].compare(L"1") == 0); - return serializedUnit; -} - -ConversionData UnitConverter::StringToConversionData(const wstring& w) -{ - vector tokenList = StringToVector(w, L";"); - assert(tokenList.size() == EXPECTEDSERIALIZEDCONVERSIONDATATOKENCOUNT); - ConversionData serializedConversionData; - serializedConversionData.ratio = stod(Unquote(tokenList[0]).c_str()); - serializedConversionData.offset = stod(Unquote(tokenList[1]).c_str()); - serializedConversionData.offsetFirst = (tokenList[2].compare(L"1") == 0); - return serializedConversionData; -} - -wstring UnitConverter::ConversionDataToString(ConversionData d, const wchar_t* delimiter) -{ - wstringstream out(wstringstream::out); - out.precision(32); - out << fixed << d.ratio; - wstring ratio = out.str(); - out.str(L""); - out << fixed << d.offset; - wstring offset = out.str(); - out.str(L""); - TrimString(ratio); - TrimString(offset); - out << Quote(ratio) << delimiter << Quote(offset) << delimiter << std::to_wstring(d.offsetFirst) << delimiter; - return out.str(); -} - -/// -/// Serializes the data in the converter and returns it as a string -/// -wstring UnitConverter::Serialize() -{ - if (!CheckLoad()) - { - return wstring(); - } - - wstringstream out(wstringstream::out); - const wchar_t* delimiter = L";"; - - out << UnitToString(m_fromType, delimiter) << "|"; - out << UnitToString(m_toType, delimiter) << "|"; - out << CategoryToString(m_currentCategory, delimiter) << "|"; - out << std::to_wstring(m_currentHasDecimal) << delimiter << std::to_wstring(m_returnHasDecimal) << delimiter << std::to_wstring(m_switchedActive) - << delimiter; - out << m_currentDisplay << delimiter << m_returnDisplay << delimiter << "|"; - wstringstream categoryString(wstringstream::out); - wstringstream categoryToUnitString(wstringstream::out); - wstringstream unitToUnitToDoubleString(wstringstream::out); - for (const Category& c : m_categories) - { - categoryString << CategoryToString(c, delimiter) << ","; - } - - for (const auto& cur : m_categoryToUnits) - { - categoryToUnitString << CategoryToString(cur.first, delimiter) << "["; - for (const Unit& u : cur.second) - { - categoryToUnitString << UnitToString(u, delimiter) << ","; - } - categoryToUnitString << "[" - << "]"; - } - - for (const auto& cur : m_ratioMap) - { - unitToUnitToDoubleString << UnitToString(cur.first, delimiter) << "["; - for (const auto& curConversion : cur.second) - { - unitToUnitToDoubleString << UnitToString(curConversion.first, delimiter) << ":"; - unitToUnitToDoubleString << ConversionDataToString(curConversion.second, delimiter) << ":,"; - } - unitToUnitToDoubleString << "[" - << "]"; - } - - out << categoryString.str() << "|"; - out << categoryToUnitString.str() << "|"; - out << unitToUnitToDoubleString.str() << "|"; - wstring test = out.str(); - return test; -} - -/// -/// De-Serializes the data in the converter from a string -/// -/// wstring holding the serialized data. If it does not have expected number of parameters, we will ignore it -void UnitConverter::DeSerialize(const wstring& serializedData) -{ - ClearValues(); - ResetCategoriesAndRatios(); - - if (serializedData.empty()) - { - return; - } - - vector outerTokens = StringToVector(serializedData, L"|"); - assert(outerTokens.size() == EXPECTEDSERIALIZEDTOKENCOUNT); - m_fromType = StringToUnit(outerTokens[0]); - m_toType = StringToUnit(outerTokens[1]); - m_currentCategory = StringToCategory(outerTokens[2]); - vector stateDataTokens = StringToVector(outerTokens[3], L";"); - assert(stateDataTokens.size() == EXPECTEDSTATEDATATOKENCOUNT); - m_currentHasDecimal = (stateDataTokens[0].compare(L"1") == 0); - m_returnHasDecimal = (stateDataTokens[1].compare(L"1") == 0); - m_switchedActive = (stateDataTokens[2].compare(L"1") == 0); - m_currentDisplay = stateDataTokens[3]; - m_returnDisplay = stateDataTokens[4]; - vector categoryListTokens = StringToVector(outerTokens[4], L","); - for (wstring token : categoryListTokens) - { - m_categories.push_back(StringToCategory(token)); - } - vector unitVectorTokens = StringToVector(outerTokens[5], L"]"); - for (wstring unitVector : unitVectorTokens) - { - vector mapcomponents = StringToVector(unitVector, L"["); - assert(mapcomponents.size() == EXPECTEDMAPCOMPONENTTOKENCOUNT); - Category key = StringToCategory(mapcomponents[0]); - vector units = StringToVector(mapcomponents[1], L","); - for (wstring unit : units) - { - m_categoryToUnits[key].push_back(StringToUnit(unit)); - } - } - vector ratioMapTokens = StringToVector(outerTokens[6], L"]"); - for (wstring token : ratioMapTokens) - { - vector ratioMapComponentTokens = StringToVector(token, L"["); - assert(ratioMapComponentTokens.size() == EXPECTEDMAPCOMPONENTTOKENCOUNT); - Unit key = StringToUnit(ratioMapComponentTokens[0]); - vector ratioMapList = StringToVector(ratioMapComponentTokens[1], L","); - for (wstring subtoken : ratioMapList) - { - vector ratioMapSubComponentTokens = StringToVector(subtoken, L":"); - assert(ratioMapSubComponentTokens.size() == EXPECTEDMAPCOMPONENTTOKENCOUNT); - Unit subkey = StringToUnit(ratioMapSubComponentTokens[0]); - ConversionData conversion = StringToConversionData(ratioMapSubComponentTokens[1]); - m_ratioMap[key][subkey] = conversion; - } - } - UpdateViewModel(); -} - -/// -/// De-Serializes the data in the converter from a string -/// -/// wstring holding the serialized data. If it does not have expected number of parameters, we will ignore it -void UnitConverter::RestoreUserPreferences(const wstring& userPreferences) -{ - if (userPreferences.empty()) - { - return; - } - - vector outerTokens = StringToVector(userPreferences, L"|"); - if (outerTokens.size() != 3) - { - return; - } - - auto fromType = StringToUnit(outerTokens[0]); - auto toType = StringToUnit(outerTokens[1]); - m_currentCategory = StringToCategory(outerTokens[2]); - - // Only restore from the saved units if they are valid in the current available units. - auto itr = m_categoryToUnits.find(m_currentCategory); - if (itr != m_categoryToUnits.end()) - { - const auto& curUnits = itr->second; - if (find(curUnits.begin(), curUnits.end(), fromType) != curUnits.end()) - { - m_fromType = fromType; - } - if (find(curUnits.begin(), curUnits.end(), toType) != curUnits.end()) - { - m_toType = toType; - } - } -} - -/// -/// Serializes the Category and Associated Units in the converter and returns it as a string -/// -wstring UnitConverter::SaveUserPreferences() -{ - wstringstream out(wstringstream::out); - const wchar_t* delimiter = L";"; - - out << UnitToString(m_fromType, delimiter) << "|"; - out << UnitToString(m_toType, delimiter) << "|"; - out << CategoryToString(m_currentCategory, delimiter) << "|"; - wstring test = out.str(); - return test; -} - -/// -/// Sanitizes the input string, escape quoting any symbols we rely on for our delimiters, and returns the sanitized string. -/// -/// wstring to be sanitized -wstring UnitConverter::Quote(const wstring& s) -{ - wstringstream quotedString(wstringstream::out); - - // Iterate over the delimiter characters we need to quote - wstring::const_iterator cursor = s.begin(); - while (cursor != s.end()) - { - if (quoteConversions.find(*cursor) != quoteConversions.end()) - { - quotedString << quoteConversions[*cursor]; - } - else - { - quotedString << *cursor; - } - ++cursor; - } - return quotedString.str(); -} - -/// -/// Unsanitizes the sanitized input string, returning it to its original contents before we had quoted it. -/// -/// wstring to be unsanitized -wstring UnitConverter::Unquote(const wstring& s) -{ - wstringstream quotedSubString(wstringstream::out); - wstringstream unquotedString(wstringstream::out); - wstring::const_iterator cursor = s.begin(); - while (cursor != s.end()) - { - if (*cursor == LEFTESCAPECHAR) - { - quotedSubString.str(L""); - while (cursor != s.end() && *cursor != RIGHTESCAPECHAR) - { - quotedSubString << *cursor; - ++cursor; - } - if (cursor == s.end()) - { - // Badly formatted - break; - } - else - { - quotedSubString << *cursor; - unquotedString << unquoteConversions[quotedSubString.str()]; - } - } - else - { - unquotedString << *cursor; - } - ++cursor; - } - return unquotedString.str(); -} - -/// -/// Handles inputs to the converter from the view-model, corresponding to a given button or keyboard press -/// -/// Command enum representing the command that was entered -void UnitConverter::SendCommand(Command command) -{ - if (!CheckLoad()) - { - return; - } - - // TODO: Localization of characters - bool clearFront = false; - if (m_currentDisplay == L"0") - { - clearFront = true; - } - bool clearBack = false; - if ((m_currentHasDecimal && m_currentDisplay.size() - 1 >= MAXIMUMDIGITSALLOWED) - || (!m_currentHasDecimal && m_currentDisplay.size() >= MAXIMUMDIGITSALLOWED)) - { - clearBack = true; - } - if (command != Command::Negate && m_switchedActive) - { - ClearValues(); - m_switchedActive = false; - clearFront = true; - clearBack = false; - } - switch (command) - { - case Command::Zero: - m_currentDisplay += L"0"; - break; - - case Command::One: - m_currentDisplay += L"1"; - break; - - case Command::Two: - m_currentDisplay += L"2"; - break; - - case Command::Three: - m_currentDisplay += L"3"; - break; - - case Command::Four: - m_currentDisplay += L"4"; - break; - - case Command::Five: - m_currentDisplay += L"5"; - break; - - case Command::Six: - m_currentDisplay += L"6"; - break; - - case Command::Seven: - m_currentDisplay += L"7"; - break; - - case Command::Eight: - m_currentDisplay += L"8"; - break; - - case Command::Nine: - m_currentDisplay += L"9"; - break; - - case Command::Decimal: - clearFront = false; - clearBack = false; - if (!m_currentHasDecimal) - { - m_currentDisplay += L"."; - m_currentHasDecimal = true; - } - break; - - case Command::Backspace: - clearFront = false; - clearBack = false; - if ((m_currentDisplay.front() != '-' && m_currentDisplay.size() > 1) || m_currentDisplay.size() > 2) - { - if (m_currentDisplay.back() == '.') - { - m_currentHasDecimal = false; - } - m_currentDisplay.pop_back(); - } - else - { - m_currentDisplay = L"0"; - m_currentHasDecimal = false; - } - break; - - case Command::Negate: - clearFront = false; - clearBack = false; - if (m_currentCategory.supportsNegative) - { - if (m_currentDisplay.front() == '-') - { - m_currentDisplay.erase(0, 1); - } - else - { - m_currentDisplay.insert(0, 1, '-'); - } - } - break; - - case Command::Clear: - clearFront = false; - clearBack = false; - ClearValues(); - break; - - case Command::Reset: - clearFront = false; - clearBack = false; - ClearValues(); - ResetCategoriesAndRatios(); - break; - - default: - break; - } - - if (clearFront) - { - m_currentDisplay.erase(0, 1); - } - if (clearBack) - { - m_currentDisplay.erase(m_currentDisplay.size() - 1, 1); - m_vmCallback->MaxDigitsReached(); - } - - Calculate(); -} - -/// -/// Sets the callback interface to send display update calls to -/// -/// instance of IDisplayCallback interface that receives our update calls -void UnitConverter::SetViewModelCallback(_In_ const shared_ptr& newCallback) -{ - m_vmCallback = newCallback; - if (CheckLoad()) - { - UpdateViewModel(); - } -} - -void UnitConverter::SetViewModelCurrencyCallback(_In_ const shared_ptr& newCallback) -{ - m_vmCurrencyCallback = newCallback; - - shared_ptr currencyDataLoader = GetCurrencyConverterDataLoader(); - if (currencyDataLoader != nullptr) - { - currencyDataLoader->SetViewModelCallback(newCallback); - } -} - -task> UnitConverter::RefreshCurrencyRatios() -{ - shared_ptr currencyDataLoader = GetCurrencyConverterDataLoader(); - return create_task([this, currencyDataLoader]() { - if (currencyDataLoader != nullptr) - { - return currencyDataLoader->TryLoadDataFromWebOverrideAsync(); - } - else - { - return task_from_result(false); - } - }) - .then( - [this, currencyDataLoader](bool didLoad) { - wstring timestamp = L""; - if (currencyDataLoader != nullptr) - { - timestamp = currencyDataLoader->GetCurrencyTimestamp(); - } - - return make_pair(didLoad, timestamp); - }, - task_continuation_context::use_default()); -} - -shared_ptr UnitConverter::GetCurrencyConverterDataLoader() -{ - return dynamic_pointer_cast(m_currencyDataLoader); -} - -/// -/// Converts a double value into another unit type, currently by multiplying by the given double ratio -/// -/// double input value to convert -/// double conversion ratio to use -double UnitConverter::Convert(double value, ConversionData conversionData) -{ - if (conversionData.offsetFirst) - { - return (value + conversionData.offset) * conversionData.ratio; - } - else - { - return (value * conversionData.ratio) + conversionData.offset; - } -} - -/// -/// Calculates the suggested values for the current display value and returns them as a vector -/// -vector> UnitConverter::CalculateSuggested() -{ - if (m_currencyDataLoader != nullptr && m_currencyDataLoader->SupportsCategory(m_currentCategory)) - { - return vector>(); - } - - vector> returnVector; - vector intermediateVector; - vector intermediateWhimsicalVector; - unordered_map ratios = m_ratioMap[m_fromType]; - // Calculate converted values for every other unit type in this category, along with their magnitude - for (const auto& cur : ratios) - { - if (cur.first != m_fromType && cur.first != m_toType) - { - double convertedValue = Convert(stod(m_currentDisplay), cur.second); - SuggestedValueIntermediate newEntry; - newEntry.magnitude = log10(convertedValue); - newEntry.value = convertedValue; - newEntry.type = cur.first; - if (newEntry.type.isWhimsical == false) - intermediateVector.push_back(newEntry); - else - intermediateWhimsicalVector.push_back(newEntry); - } - } - - // Sort the resulting list by absolute magnitude, breaking ties by choosing the positive value - sort(intermediateVector.begin(), intermediateVector.end(), [](SuggestedValueIntermediate first, SuggestedValueIntermediate second) { - if (abs(first.magnitude) == abs(second.magnitude)) - { - return first.magnitude > second.magnitude; - } - else - { - return abs(first.magnitude) < abs(second.magnitude); - } - }); - - // Now that the list is sorted, iterate over it and populate the return vector with properly rounded and formatted return strings - for (const auto& entry : intermediateVector) - { - wstring roundedString; - if (abs(entry.value) < 100) - { - roundedString = RoundSignificant(entry.value, 2); - } - else if (abs(entry.value) < 1000) - { - roundedString = RoundSignificant(entry.value, 1); - } - else - { - roundedString = RoundSignificant(entry.value, 0); - } - if (stod(roundedString) != 0.0 || m_currentCategory.supportsNegative) - { - TrimString(roundedString); - returnVector.push_back(make_tuple(roundedString, entry.type)); - } - } - - // The Whimsicals are determined differently - // Sort the resulting list by absolute magnitude, breaking ties by choosing the positive value - sort(intermediateWhimsicalVector.begin(), intermediateWhimsicalVector.end(), [](SuggestedValueIntermediate first, SuggestedValueIntermediate second) { - if (abs(first.magnitude) == abs(second.magnitude)) - { - return first.magnitude > second.magnitude; - } - else - { - return abs(first.magnitude) < abs(second.magnitude); - } - }); - - // Now that the list is sorted, iterate over it and populate the return vector with properly rounded and formatted return strings - vector> whimsicalReturnVector; - - for (const auto& entry : intermediateWhimsicalVector) - { - wstring roundedString; - if (abs(entry.value) < 100) - { - roundedString = RoundSignificant(entry.value, 2); - } - else if (abs(entry.value) < 1000) - { - roundedString = RoundSignificant(entry.value, 1); - } - else - { - roundedString = RoundSignificant(entry.value, 0); - } - - // How to work out which is the best whimsical value to add to the vector? - if (stod(roundedString) != 0.0) - { - TrimString(roundedString); - whimsicalReturnVector.push_back(make_tuple(roundedString, entry.type)); - } - } - // Pickup the 'best' whimsical value - currently the first one - if (whimsicalReturnVector.size() != 0) - { - returnVector.push_back(whimsicalReturnVector.at(0)); - } - - return returnVector; -} - -/// -/// Resets categories and ratios -/// -void UnitConverter::ResetCategoriesAndRatios() -{ - m_categories = m_dataLoader->LoadOrderedCategories(); - - m_switchedActive = false; - - if (m_categories.empty()) - { - return; - } - - m_currentCategory = m_categories[0]; - - m_categoryToUnits.clear(); - m_ratioMap.clear(); - bool readyCategoryFound = false; - for (const Category& category : m_categories) - { - shared_ptr activeDataLoader = GetDataLoaderForCategory(category); - if (activeDataLoader == nullptr) - { - // The data loader is different depending on the category, e.g. currency data loader - // is different from the static data loader. - // If there is no data loader for this category, continue. - continue; - } - - vector units = activeDataLoader->LoadOrderedUnits(category); - m_categoryToUnits[category] = units; - - // Just because the units are empty, doesn't mean the user can't select this category, - // we just want to make sure we don't let an unready category be the default. - if (!units.empty()) - { - for (Unit u : units) - { - m_ratioMap[u] = activeDataLoader->LoadOrderedRatios(u); - } - - if (!readyCategoryFound) - { - m_currentCategory = category; - readyCategoryFound = true; - } - } - } - - InitializeSelectedUnits(); -} - -/// -/// Sets the active data loader based on the input category. -/// -shared_ptr UnitConverter::GetDataLoaderForCategory(const Category& category) -{ - if (m_currencyDataLoader != nullptr && m_currencyDataLoader->SupportsCategory(category)) - { - return m_currencyDataLoader; - } - else - { - return m_dataLoader; - } -} - -/// -/// Sets the initial values for m_fromType and m_toType. -/// This is an internal helper method as opposed to SetCurrentUnits -/// which is for external use by clients. -/// If we fail to set units, we will fallback to the EMPTY_UNIT. -/// -void UnitConverter::InitializeSelectedUnits() -{ - if (m_categoryToUnits.empty()) - { - return; - } - - auto itr = m_categoryToUnits.find(m_currentCategory); - if (itr == m_categoryToUnits.end()) - { - return; - } - - vector curUnits = itr->second; - if (!curUnits.empty()) - { - bool conversionSourceSet = false; - bool conversionTargetSet = false; - for (const Unit& cur : curUnits) - { - if (!conversionSourceSet && cur.isConversionSource) - { - m_fromType = cur; - conversionSourceSet = true; - } - - if (!conversionTargetSet && cur.isConversionTarget) - { - m_toType = cur; - conversionTargetSet = true; - } - - if (conversionSourceSet && conversionTargetSet) - { - return; - } - } - } - - m_fromType = EMPTY_UNIT; - m_toType = EMPTY_UNIT; -} - -/// -/// Resets the value fields to 0 -/// -void UnitConverter::ClearValues() -{ - m_currentHasDecimal = false; - m_returnHasDecimal = false; - m_currentDisplay = L"0"; -} - -/// -/// Checks if either unit is EMPTY_UNIT. -/// -bool UnitConverter::AnyUnitIsEmpty() -{ - return m_fromType == EMPTY_UNIT || m_toType == EMPTY_UNIT; -} - -/// -/// Calculates a new return value based on the current display value -/// -void UnitConverter::Calculate() -{ - if (AnyUnitIsEmpty()) - { - m_returnDisplay = m_currentDisplay; - m_returnHasDecimal = m_currentHasDecimal; - TrimString(m_returnDisplay); - UpdateViewModel(); - return; - } - - unordered_map conversionTable = m_ratioMap[m_fromType]; - double returnValue = stod(m_currentDisplay); - if (conversionTable[m_toType].ratio == 1.0 && conversionTable[m_toType].offset == 0.0) - { - m_returnDisplay = m_currentDisplay; - m_returnHasDecimal = m_currentHasDecimal; - TrimString(m_returnDisplay); - } - else - { - returnValue = Convert(returnValue, conversionTable[m_toType]); - m_returnDisplay = RoundSignificant(returnValue, MAXIMUMDIGITSALLOWED); - TrimString(m_returnDisplay); - int numPreDecimal = (int)m_returnDisplay.size(); - if (m_returnDisplay.find(L'.') != m_returnDisplay.npos) - { - numPreDecimal = (int)m_returnDisplay.find(L'.'); - } - if (returnValue < 0) - { - numPreDecimal--; - } - - if (numPreDecimal > MAXIMUMDIGITSALLOWED || (returnValue != 0 && abs(returnValue) < MINIMUMDECIMALALLOWED)) - { - wstringstream out(wstringstream::out); - out << scientific << returnValue; - m_returnDisplay = out.str(); - } - else - { - returnValue = stod(m_returnDisplay); - wstring returnString; - if (m_currentDisplay.size() <= OPTIMALDIGITSALLOWED && abs(returnValue) >= OPTIMALDECIMALALLOWED) - { - returnString = RoundSignificant(returnValue, OPTIMALDIGITSALLOWED - min(numPreDecimal, OPTIMALDIGITSALLOWED)); - } - else - { - returnString = RoundSignificant(returnValue, MAXIMUMDIGITSALLOWED - min(numPreDecimal, MAXIMUMDIGITSALLOWED)); - } - m_returnDisplay = returnString; - TrimString(m_returnDisplay); - } - m_returnHasDecimal = (m_returnDisplay.find(L'.') != m_returnDisplay.npos); - } - UpdateViewModel(); -} - -/// -/// Trims out any trailing zeros or decimals in the given input string -/// -/// wstring to trim -void UnitConverter::TrimString(wstring& returnString) -{ - if (returnString.find(L'.') == m_returnDisplay.npos) - { - return; - } - - wstring::iterator iter; - for (iter = returnString.end() - 1;; iter--) - { - if (*iter != L'0') - { - returnString.erase(iter + 1, returnString.end()); - break; - } - } - if (*(returnString.end() - 1) == L'.') - { - returnString.erase(returnString.end() - 1, returnString.end()); - } -} - -/// -/// Rounds the given double to the given number of significant digits -/// -/// input double -/// int number of significant digits to round to -wstring UnitConverter::RoundSignificant(double num, int numSignificant) -{ - wstringstream out(wstringstream::out); - out << fixed; - out.precision(numSignificant); - out << num; - return out.str(); -} - -void UnitConverter::UpdateCurrencySymbols() -{ - if (m_currencyDataLoader != nullptr && m_vmCurrencyCallback != nullptr) - { - shared_ptr currencyDataLoader = GetCurrencyConverterDataLoader(); - const pair currencySymbols = currencyDataLoader->GetCurrencySymbols(m_fromType, m_toType); - const pair currencyRatios = currencyDataLoader->GetCurrencyRatioEquality(m_fromType, m_toType); - - m_vmCurrencyCallback->CurrencySymbolsCallback(currencySymbols.first, currencySymbols.second); - m_vmCurrencyCallback->CurrencyRatiosCallback(currencyRatios.first, currencyRatios.second); - } -} - -void UnitConverter::UpdateViewModel() -{ - m_vmCallback->DisplayCallback(m_currentDisplay, m_returnDisplay); - m_vmCallback->SuggestedValueCallback(CalculateSuggested()); -} diff --git a/src/CalcManager/UnitConverter.h b/src/CalcManager/UnitConverter.h deleted file mode 100644 index b111ee79..00000000 --- a/src/CalcManager/UnitConverter.h +++ /dev/null @@ -1,318 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -#pragma once - -#include -#include -#include -#include // for SAL -#include // for std::shared_ptr - -namespace UnitConversionManager -{ - enum class Command; - - struct Unit - { - Unit() - { - } - Unit(int id, std::wstring name, std::wstring abbreviation, bool isConversionSource, bool isConversionTarget, bool isWhimsical) - : id(id) - , name(name) - , accessibleName(name) - , abbreviation(abbreviation) - , isConversionSource(isConversionSource) - , isConversionTarget(isConversionTarget) - , isWhimsical(isWhimsical) - { - } - - Unit( - int id, - std::wstring currencyName, - std::wstring countryName, - std::wstring abbreviation, - bool isRtlLanguage, - bool isConversionSource, - bool isConversionTarget) - : id(id) - , abbreviation(abbreviation) - , isConversionSource(isConversionSource) - , isConversionTarget(isConversionTarget) - , isWhimsical(false) - { - std::wstring nameValue1 = isRtlLanguage ? currencyName : countryName; - std::wstring nameValue2 = isRtlLanguage ? countryName : currencyName; - - name = nameValue1 + L" - " + nameValue2; - accessibleName = nameValue1 + L" " + nameValue2; - } - - virtual ~Unit() - { - } - - int id; - std::wstring name; - std::wstring accessibleName; - std::wstring abbreviation; - bool isConversionSource; - bool isConversionTarget; - bool isWhimsical; - - bool operator!=(const Unit& that) const - { - return that.id != id; - } - - bool operator==(const Unit& that) const - { - return that.id == id; - } - }; - - // The EMPTY_UNIT acts as a 'null-struct' so that - // Unit pointers can safely be dereferenced without - // null checks. - // - // unitId, name, abbreviation, isConversionSource, isConversionTarget, isWhimsical - inline const Unit EMPTY_UNIT = Unit{ -1, L"", L"", true, true, false }; - - struct Category - { - Category() - { - } - - Category(int id, std::wstring name, bool supportsNegative) - : id(id) - , name(name) - , supportsNegative(supportsNegative) - { - } - - int id; - std::wstring name; - bool supportsNegative; - - bool operator!=(const Category& that) const - { - return that.id != id; - } - - bool operator==(const Category& that) const - { - return that.id == id; - } - }; - - class UnitHash - { - public: - size_t operator()(const Unit& x) const - { - return x.id; - } - }; - - class CategoryHash - { - public: - size_t operator()(const Category& x) const - { - return x.id; - } - }; - - struct SuggestedValueIntermediate - { - double magnitude; - double value; - Unit type; - }; - - struct ConversionData - { - ConversionData() - { - } - ConversionData(double ratio, double offset, bool offsetFirst) - : ratio(ratio) - , offset(offset) - , offsetFirst(offsetFirst) - { - } - - virtual ~ConversionData() - { - } - - double ratio; - double offset; - bool offsetFirst; - }; - - struct CurrencyStaticData - { - std::wstring countryCode; - std::wstring countryName; - std::wstring currencyCode; - std::wstring currencyName; - std::wstring currencySymbol; - }; - - struct CurrencyRatio - { - double ratio; - std::wstring sourceCurrencyCode; - std::wstring targetCurrencyCode; - }; - - typedef std::tuple, UnitConversionManager::Unit, UnitConversionManager::Unit> CategorySelectionInitializer; - typedef std::unordered_map< - UnitConversionManager::Unit, - std::unordered_map, - UnitConversionManager::UnitHash> - UnitToUnitToConversionDataMap; - typedef std::unordered_map, UnitConversionManager::CategoryHash> - CategoryToUnitVectorMap; - - class IViewModelCurrencyCallback - { - public: - virtual ~IViewModelCurrencyCallback(){}; - virtual void CurrencyDataLoadFinished(bool didLoad) = 0; - virtual void CurrencySymbolsCallback(_In_ const std::wstring& fromSymbol, _In_ const std::wstring& toSymbol) = 0; - virtual void CurrencyRatiosCallback(_In_ const std::wstring& ratioEquality, _In_ const std::wstring& accRatioEquality) = 0; - virtual void CurrencyTimestampCallback(_In_ const std::wstring& timestamp, bool isWeekOldData) = 0; - virtual void NetworkBehaviorChanged(_In_ int newBehavior) = 0; - }; - - class IConverterDataLoader - { - public: - virtual ~IConverterDataLoader(){}; - virtual void LoadData() = 0; // prepare data if necessary before calling other functions - virtual std::vector LoadOrderedCategories() = 0; - virtual std::vector LoadOrderedUnits(const Category& c) = 0; - virtual std::unordered_map LoadOrderedRatios(const Unit& u) = 0; - virtual bool SupportsCategory(const Category& target) = 0; - }; - - class ICurrencyConverterDataLoader - { - public: - virtual void SetViewModelCallback(const std::shared_ptr& callback) = 0; - virtual std::pair - GetCurrencySymbols(_In_ const UnitConversionManager::Unit& unit1, _In_ const UnitConversionManager::Unit& unit2) = 0; - virtual std::pair - GetCurrencyRatioEquality(_In_ const UnitConversionManager::Unit& unit1, _In_ const UnitConversionManager::Unit& unit2) = 0; - virtual std::wstring GetCurrencyTimestamp() = 0; - - virtual concurrency::task TryLoadDataFromCacheAsync() = 0; - virtual concurrency::task TryLoadDataFromWebAsync() = 0; - virtual concurrency::task TryLoadDataFromWebOverrideAsync() = 0; - }; - - class IUnitConverterVMCallback - { - public: - virtual ~IUnitConverterVMCallback(){}; - virtual void DisplayCallback(const std::wstring& from, const std::wstring& to) = 0; - virtual void SuggestedValueCallback(const std::vector>& suggestedValues) = 0; - virtual void MaxDigitsReached() = 0; - }; - - class IUnitConverter - { - public: - virtual ~IUnitConverter() - { - } - virtual void Initialize() = 0; // Use to initialize first time, use deserialize instead to rehydrate - virtual std::vector GetCategories() = 0; - virtual CategorySelectionInitializer SetCurrentCategory(const Category& input) = 0; - virtual Category GetCurrentCategory() = 0; - virtual void SetCurrentUnitTypes(const Unit& fromType, const Unit& toType) = 0; - virtual void SwitchActive(const std::wstring& newValue) = 0; - virtual std::wstring Serialize() = 0; - virtual void DeSerialize(const std::wstring& serializedData) = 0; - virtual std::wstring SaveUserPreferences() = 0; - virtual void RestoreUserPreferences(_In_ const std::wstring& userPreferences) = 0; - virtual void SendCommand(Command command) = 0; - virtual void SetViewModelCallback(_In_ const std::shared_ptr& newCallback) = 0; - virtual void SetViewModelCurrencyCallback(_In_ const std::shared_ptr& newCallback) = 0; - virtual concurrency::task> RefreshCurrencyRatios() = 0; - virtual void Calculate() = 0; - virtual void ResetCategoriesAndRatios() = 0; - }; - - class UnitConverter : public IUnitConverter, public std::enable_shared_from_this - { - public: - UnitConverter(_In_ const std::shared_ptr& dataLoader); - UnitConverter(_In_ const std::shared_ptr& dataLoader, _In_ const std::shared_ptr& currencyDataLoader); - - // IUnitConverter - void Initialize() override; - std::vector GetCategories() override; - CategorySelectionInitializer SetCurrentCategory(const Category& input) override; - Category GetCurrentCategory() override; - void SetCurrentUnitTypes(const Unit& fromType, const Unit& toType) override; - void SwitchActive(const std::wstring& newValue) override; - std::wstring Serialize() override; - void DeSerialize(const std::wstring& serializedData) override; - std::wstring SaveUserPreferences() override; - void RestoreUserPreferences(const std::wstring& userPreference) override; - void SendCommand(Command command) override; - void SetViewModelCallback(_In_ const std::shared_ptr& newCallback) override; - void SetViewModelCurrencyCallback(_In_ const std::shared_ptr& newCallback) override; - concurrency::task> RefreshCurrencyRatios() override; - void Calculate() override; - void ResetCategoriesAndRatios() override; - // IUnitConverter - - static std::vector StringToVector(const std::wstring& w, const wchar_t* delimiter, bool addRemainder = false); - static std::wstring Quote(const std::wstring& s); - static std::wstring Unquote(const std::wstring& s); - - private: - bool CheckLoad(); - double Convert(double value, ConversionData conversionData); - std::vector> CalculateSuggested(); - void ClearValues(); - void TrimString(std::wstring& input); - void InitializeSelectedUnits(); - std::wstring RoundSignificant(double num, int numSignificant); - Category StringToCategory(const std::wstring& w); - std::wstring CategoryToString(const Category& c, const wchar_t* delimiter); - std::wstring UnitToString(const Unit& u, const wchar_t* delimiter); - Unit StringToUnit(const std::wstring& w); - ConversionData StringToConversionData(const std::wstring& w); - std::wstring ConversionDataToString(ConversionData d, const wchar_t* delimiter); - void UpdateCurrencySymbols(); - void UpdateViewModel(); - bool AnyUnitIsEmpty(); - std::shared_ptr GetDataLoaderForCategory(const Category& category); - std::shared_ptr GetCurrencyConverterDataLoader(); - - private: - std::shared_ptr m_dataLoader; - std::shared_ptr m_currencyDataLoader; - std::shared_ptr m_vmCallback; - std::shared_ptr m_vmCurrencyCallback; - std::vector m_categories; - CategoryToUnitVectorMap m_categoryToUnits; - UnitToUnitToConversionDataMap m_ratioMap; - Category m_currentCategory; - Unit m_fromType; - Unit m_toType; - std::wstring m_currentDisplay; - std::wstring m_returnDisplay; - bool m_currentHasDecimal; - bool m_returnHasDecimal; - bool m_switchedActive; - }; -} diff --git a/src/CalcManager/build.sh b/src/CalcManager/build.sh new file mode 100644 index 00000000..8d76ba7a --- /dev/null +++ b/src/CalcManager/build.sh @@ -0,0 +1,11 @@ +emcc \ + -std=c++17 \ + -s WASM=1 \ + -s LEGALIZE_JS_FFI=0 \ + -s RESERVED_FUNCTION_POINTERS=64 \ + -s ALLOW_MEMORY_GROWTH=1 \ + -s BINARYEN=1 \ + -s SIDE_MODULE=1 \ + -o CalcManager.wasm \ + -s EXPORT_ALL=1 \ + CEngine/*.cpp RatPack/*.cpp *.cpp -I. diff --git a/src/CalcManager/compat.h b/src/CalcManager/compat.h index b7c9ba5f..becc507c 100644 --- a/src/CalcManager/compat.h +++ b/src/CalcManager/compat.h @@ -1,6 +1,6 @@ #pragma once -#if defined(__WEBASSEMBLY__) +#if defined(__EMSCRIPTEN__) #define HRESULT long #define _In_opt_ @@ -27,4 +27,4 @@ typedef unsigned long DWORD; #define HRESULT_CODE(hr) ((hr)&0xFFFF) #define SCODE_CODE(sc) ((sc)&0xFFFF) -#endif \ No newline at end of file +#endif diff --git a/src/CalcManager/output.txt b/src/CalcManager/output.txt new file mode 100644 index 00000000..e69de29b diff --git a/src/CalcManager/pch.h b/src/CalcManager/pch.h index e66e1a38..f255b1f1 100644 --- a/src/CalcManager/pch.h +++ b/src/CalcManager/pch.h @@ -11,12 +11,15 @@ #include #include #include -#include #include -#include #include #include #include #include #include + +#if !defined(__EMSCRIPTEN__) +#include #include +#include +#endif