diff --git a/include/user/deviceAttachmentSpec.h b/include/user/deviceAttachmentSpec.h index d6786b3..af1b6eb 100644 --- a/include/user/deviceAttachmentSpec.h +++ b/include/user/deviceAttachmentSpec.h @@ -121,24 +121,31 @@ public: * @param synonymNames The collection of synonymous parameter names to try * @param defaultValue The default value to return if no parameter is found * @return The parsed integer value, or defaultValue if none found - * @note Synonyms are tried in reverse order; lattermost synonym wins if multiple are present + * @note The lattermost supplied matching param wins if multiple are present */ + template static int parseOptionalParamAsIntWithSynonyms( const std::vector>& params, - const std::vector& synonymNames, + const SynonymCollectionT& synonymNames, int defaultValue ) { - // Loop through synonyms in reverse order; lattermost synonym wins. - for (auto synIt = synonymNames.rbegin(); - synIt != synonymNames.rend(); ++synIt) + // Loop through params in reverse order; lattermost supplied param wins. + for (auto paramIt = params.rbegin(); paramIt != params.rend(); ++paramIt) { - const auto& paramName = *synIt; + const auto& [paramName, paramValue] = *paramIt; + auto synonymIt = std::find( + synonymNames.begin(), synonymNames.end(), paramName); + + if (synonymIt == synonymNames.end()) + { continue; } + try { - return parseRequiredParamAsInt(params, paramName); - } catch (const std::exception&) { - // Parameter not found or parse error, continue to next synonym - continue; + return std::stoi(paramValue); + } catch (const std::exception& e) { + throw std::runtime_error( + "Failed to parse '" + paramName + "' param value '" + + paramValue + "' as integer: " + e.what()); } } diff --git a/include/user/intrinThresholdParams.h b/include/user/intrinThresholdParams.h new file mode 100644 index 0000000..deed2fa --- /dev/null +++ b/include/user/intrinThresholdParams.h @@ -0,0 +1,251 @@ +#ifndef SMO_INTRIN_THRESHOLD_PARAMS_H +#define SMO_INTRIN_THRESHOLD_PARAMS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace smo::intrin { + +enum class ThresholdUnit +{ + Percentage, + Absolute, +}; + +struct ParsedThresholdParam +{ + int value; + ThresholdUnit unit; + std::string_view matchedName; + bool wasSpecified; +}; + +inline constexpr std::array kPosIntPcParamNames = { + "postrin-interest-percentage", + "postrin-interest-pc", +}; + +inline constexpr std::array kPosIntThrParamNames = { + "postrin-interest-threshold", + "postrin-interest-thresh", + "postrin-interest-thr", +}; + +inline constexpr std::array kNegIntPcParamNames = { + "negtrin-interest-percentage", + "negtrin-interest-pc", +}; + +inline constexpr std::array kNegIntThrParamNames = { + "negtrin-interest-threshold", + "negtrin-interest-thresh", + "negtrin-interest-thr", +}; + +inline constexpr std::array kPosDistPcParamNames = { + "postrin-distraction-percentage", + "postrin-distraction-pc", +}; + +inline constexpr std::array kPosDistThrParamNames = { + "postrin-distraction-threshold", + "postrin-distraction-thresh", + "postrin-distraction-thr", +}; + +inline constexpr std::array kNegDistPcParamNames = { + "negtrin-distraction-percentage", + "negtrin-distraction-pc", +}; + +inline constexpr std::array kNegDistThrParamNames = { + "negtrin-distraction-threshold", + "negtrin-distraction-thresh", + "negtrin-distraction-thr", +}; + +inline constexpr std::array kStupefactionPcParamNames = { + "postrin-stupefaction-percentage", + "postrin-stupefaction-pc", + "postrin-stupefying-percentage", + "postrin-stupefying-pc", + "stupefaction-percentage", + "stupefaction-pc", + "stupefying-percentage", + "stupefying-pc", +}; + +inline constexpr std::array kStupefactionThrParamNames = { + "postrin-stupefaction-threshold", + "postrin-stupefaction-thresh", + "postrin-stupefaction-thr", + "postrin-stupefying-threshold", + "postrin-stupefying-thresh", + "postrin-stupefying-thr", + "stupefaction-threshold", + "stupefaction-thresh", + "stupefaction-thr", + "stupefying-threshold", + "stupefying-thresh", + "stupefying-thr", +}; + +inline constexpr std::array kIntolerablePcParamNames = { + "negtrin-intolerable-percentage", + "negtrin-intolerable-pc", + "intolerable-percentage", + "intolerable-pc", +}; + +inline constexpr std::array kIntolerableThrParamNames = { + "negtrin-intolerable-threshold", + "negtrin-intolerable-thresh", + "negtrin-intolerable-thr", + "intolerable-threshold", + "intolerable-thresh", + "intolerable-thr", +}; + +inline constexpr std::array kForbiddenUnitlessIntrinParamNames = { + "postrin-interest", + "negtrin-interest", + "postrin-distraction", + "negtrin-distraction", + "postrin-stupefaction", + "postrin-stupefying", + "stupefaction", + "stupefying", + "negtrin-intolerable", + "intolerable", +}; + +template +bool arrayContains( + const std::array& names, + std::string_view candidate) +{ + for (const auto& name : names) { + if (name == candidate) { return true; } + } + + return false; +} + +template +bool namesContain( + const NameCollectionT& names, + std::string_view candidate) +{ + for (const auto& name : names) { + if (name == candidate) { return true; } + } + + return false; +} + +inline void validateNoForbiddenUnitlessIntrinParams( + const std::vector>& params) +{ + for (const auto& [name, value] : params) + { + (void)value; + + if (arrayContains(kForbiddenUnitlessIntrinParamNames, name)) + { + throw std::runtime_error( + "Intrinsic threshold param '" + name + + "' is invalid without a unit suffix. Use a " + "'-percentage'/'-pc' or '-threshold'/'-thresh'/'-thr' variant."); + } + } +} + +inline std::optional> findLastMatchingIntParam( + const std::vector>& params, + const auto& synonymNames) +{ + for (auto paramIt = params.rbegin(); paramIt != params.rend(); ++paramIt) + { + const auto& [name, value] = *paramIt; + if (!namesContain(synonymNames, name)) + { continue; } + + try { + return std::pair(name, std::stoi(value)); + } + catch (const std::exception& e) + { + throw std::runtime_error( + "Failed to parse '" + name + "' param value '" + value + + "' as integer: " + e.what()); + } + } + + return std::nullopt; +} + +inline ParsedThresholdParam parseOptionalThresholdParam( + const std::vector>& params, + const auto& percentageNames, + const auto& absoluteNames, + int defaultValue, + ThresholdUnit defaultUnit) +{ + for (auto paramIt = params.rbegin(); paramIt != params.rend(); ++paramIt) + { + const auto& [name, value] = *paramIt; + ThresholdUnit unit; + + if (namesContain(percentageNames, name)) + { unit = ThresholdUnit::Percentage; } + else if (namesContain(absoluteNames, name)) + { unit = ThresholdUnit::Absolute; } + else + { continue; } + + try + { + return ParsedThresholdParam{ + .value = std::stoi(value), + .unit = unit, + .matchedName = name, + .wasSpecified = true, + }; + } + catch (const std::exception& e) + { + throw std::runtime_error( + "Failed to parse '" + name + "' param value '" + value + + "' as integer: " + e.what()); + } + } + + return ParsedThresholdParam{ + .value = defaultValue, + .unit = defaultUnit, + .matchedName = {}, + .wasSpecified = false, + }; +} + +inline uint32_t resolveThresholdValue( + const ParsedThresholdParam& param, + size_t percentageBase) +{ + if (param.unit == ThresholdUnit::Percentage) { + return static_cast((percentageBase * param.value) / 100); + } + + return static_cast(param.value); +} + +} // namespace smo::intrin + +#endif // SMO_INTRIN_THRESHOLD_PARAMS_H diff --git a/stimBuffApis/livoxGen1/pcloudAmbienceStimulusBuffer.h b/stimBuffApis/livoxGen1/pcloudAmbienceStimulusBuffer.h index d333961..27fdd1d 100644 --- a/stimBuffApis/livoxGen1/pcloudAmbienceStimulusBuffer.h +++ b/stimBuffApis/livoxGen1/pcloudAmbienceStimulusBuffer.h @@ -10,11 +10,23 @@ #include #include #include +#include #include "lg1PcloudAmbienceStencil.h" namespace smo { namespace stim_buff { +inline intrin::ParsedThresholdParam parsePostrinInterestParam( + const std::shared_ptr& deviceAttachmentSpec) +{ + return intrin::parseOptionalThresholdParam( + deviceAttachmentSpec->qualeIfaceApiParams, + intrin::kPosIntPcParamNames, + intrin::kPosIntThrParamNames, + 90, + intrin::ThresholdUnit::Percentage); +} + // Forward declaration class StimulusProducer; @@ -40,22 +52,18 @@ public: callbacks, flags), nStencils(nStencils_) { - // Parse postrinInterestPercentage (interpreted as percentage 0-100) - const std::vector postrinInterestThresholdParamNames = { - "postrin-interest-percentage", - "postrin-interest-pc", - "postrin-interest" - }; + intrin::validateNoForbiddenUnitlessIntrinParams( + deviceAttachmentSpec->qualeIfaceApiParams); - postrinInterestPercentage = static_cast( - device::DeviceAttachmentSpec::parseOptionalParamAsIntWithSynonyms( - deviceAttachmentSpec->qualeIfaceApiParams, - postrinInterestThresholdParamNames, - 90)); + const auto postrinInterestParam = + parsePostrinInterestParam(deviceAttachmentSpec); + postrinInterestPercentage = + postrinInterestParam.unit == intrin::ThresholdUnit::Percentage + ? static_cast(postrinInterestParam.value) + : 0U; + postrinInterestThreshold = intrin::resolveThresholdValue( + postrinInterestParam, nDgramsPerFrame_); - // Convert percentage to absolute frame count - postrinInterestThreshold = static_cast( - (nDgramsPerFrame_ * postrinInterestPercentage) / 100); // Parse ambienceIntensityLowVal from qualeIfaceApiParams const std::vector ambienceIntensityLowValParamNames = { "ambience-intensity-low-val"