Skip to content

Commit

Permalink
Use higher precision mass units for vitamins
Browse files Browse the repository at this point in the history
Vitamins are often present in very small quantities, so to specify
vitamins by weight, more precision that 1mg is required for mass.

Introduce and use a special higher-precision units type for vitamin
mass, and plug it into the code to all the vitamin mass parsing code.

'μg', 'ug', 'mcg' are supported when representing mass strings in JSON.
  • Loading branch information
ehughsbaird committed Oct 30, 2023
1 parent bc6987b commit 23d21b7
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 34 deletions.
2 changes: 1 addition & 1 deletion doc/JSON_INFO.md
Original file line number Diff line number Diff line change
Expand Up @@ -3762,7 +3762,7 @@ CBMs can be defined like this:
"cooks_like": "meat_cooked", // (Optional) If the item is used in a recipe, replaces it with its cooks_like
"parasites": 10, // (Optional) Probability of becoming parasitized when eating
"contamination": [ { "disease": "bad_food", "probability": 5 } ], // (Optional) List of diseases carried by this comestible and their associated probability. Values must be in the [0, 100] range.
"vitamins": [ [ "calcium", 5 ], [ "iron", 12 ] ], // Vitamins provided by consuming a charge (portion) of this. An integer percentage of ideal daily value average. Vitamins array keys include the following: calcium, iron, vitA, vitB, vitC, mutant_toxin, bad_food, blood, and redcells. Note that vitB is B12.
"vitamins": [ [ "calcium", "60 mg" ], [ "iron", 12 ] ], // Vitamins provided by consuming a charge (portion) of this. Some vitamins ("calcium", "iron", "vitC") can be specified with the weight of the vitamins in that food. Vitamins specified by weight can be in grams ("g"), milligrams ("mg") or micrograms ("μg", "ug", "mcg"). If a vitamin is not specified by weight, it is specified in "units", with meaning according to the vitamin definition. Nutrition vitamins ("calcium", "iron", "vitC") are an integer percentage of ideal daily value average. Vitamins array keys include the following: calcium, iron, vitC, mutant_toxin, bad_food, blood, and redcells.
"material": [ // All materials (IDs) this food is made of
{ "type": "flesh", "portion": 3 }, // See Generic Item attributes for type and portion details
{ "type": "wheat", "portion": 5 }
Expand Down
4 changes: 2 additions & 2 deletions src/item_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3328,7 +3328,7 @@ void Item_factory::load( islot_comestible &slot, const JsonObject &jo, const std
if( pair.has_int( 1 ) ) {
slot.default_nutrition.set_vitamin( vit, pair.get_int( 1 ) );
} else {
units::mass val = read_from_json_string<units::mass>( pair[1], units::mass_units );
vitamin_units::mass val = read_from_json_string( pair[1], vitamin_units::mass_units );
slot.default_nutrition.set_vitamin( vit, val );
}
}
Expand All @@ -3339,7 +3339,7 @@ void Item_factory::load( islot_comestible &slot, const JsonObject &jo, const std
if( pair.has_int( 1 ) ) {
slot.default_nutrition.add_vitamin( vit, pair.get_int( 1 ) );
} else {
units::mass val = read_from_json_string<units::mass>( pair[1], units::mass_units );
vitamin_units::mass val = read_from_json_string( pair[1], vitamin_units::mass_units );
slot.default_nutrition.add_vitamin( vit, val );
}
}
Expand Down
49 changes: 26 additions & 23 deletions src/stomach.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ void nutrients::max_in_place( const nutrients &r )
const vitamin_id &vit = vit_pair.first;
int other = r.get_vitamin( vit );
if( other != 0 ) {
std::variant<int, units::mass> &val = vitamins_[vit];
std::variant<int, vitamin_units::mass> &val = vitamins_[vit];
// We must be finalized because we're calling vitamin::all()
val = std::max( std::get<int>( val ), other );
}
Expand All @@ -51,13 +51,13 @@ std::map<vitamin_id, int> nutrients::vitamins() const
}

std::map<vitamin_id, int> ret;
for( const std::pair<const vitamin_id, std::variant<int, units::mass>> &vit : vitamins_ ) {
for( const std::pair<const vitamin_id, std::variant<int, vitamin_units::mass>> &vit : vitamins_ ) {
ret.emplace( vit.first, std::get<int>( vit.second ) );
}
return ret;
}

void nutrients::set_vitamin( const vitamin_id &vit, units::mass mass )
void nutrients::set_vitamin( const vitamin_id &vit, vitamin_units::mass mass )
{
if( finalized ) {
set_vitamin( vit, vit->units_from_mass( mass ) );
Expand All @@ -66,18 +66,18 @@ void nutrients::set_vitamin( const vitamin_id &vit, units::mass mass )
vitamins_[vit] = mass;
}

void nutrients::add_vitamin( const vitamin_id &vit, units::mass mass )
void nutrients::add_vitamin( const vitamin_id &vit, vitamin_units::mass mass )
{
if( finalized ) {
add_vitamin( vit, vit->units_from_mass( mass ) );
return;
}
auto iter = vitamins_.emplace( vit, 0_kilogram ).first;
if( !std::holds_alternative<units::mass>( iter->second ) ) {
auto iter = vitamins_.emplace( vit, vitamin_units::mass( 0, {} ) ).first;
if( !std::holds_alternative<vitamin_units::mass>( iter->second ) ) {
debugmsg( "Tried to add mass vitamin to units vitamin before vitamins were finalized!" );
return;
}
iter->second = std::get<units::mass>( iter->second ) + mass;
iter->second = std::get<vitamin_units::mass>( iter->second ) + mass;
}

void nutrients::set_vitamin( const vitamin_id &vit, int units )
Expand All @@ -89,7 +89,7 @@ void nutrients::set_vitamin( const vitamin_id &vit, int units )
void nutrients::add_vitamin( const vitamin_id &vit, int units )
{
auto iter = vitamins_.emplace( vit, 0 ).first;
if( std::holds_alternative<units::mass>( iter->second ) ) {
if( std::holds_alternative<vitamin_units::mass>( iter->second ) ) {
debugmsg( "Tried to add mass vitamin to units vitamin before vitamins were finalized!" );
return;
}
Expand All @@ -102,7 +102,7 @@ int nutrients::get_vitamin( const vitamin_id &vit ) const
if( it == vitamins_.end() ) {
return 0;
}
if( !finalized && std::holds_alternative<units::mass>( it->second ) ) {
if( !finalized && std::holds_alternative<vitamin_units::mass>( it->second ) ) {
debugmsg( "Called get_vitamin on a mass vitamin before vitamins were finalized!" );
return 0;
}
Expand All @@ -116,11 +116,11 @@ int nutrients::kcal() const

void nutrients::finalize_vitamins()
{
for( std::pair<const vitamin_id, std::variant<int, units::mass> > &vit : vitamins_ ) {
if( std::holds_alternative<units::mass>( vit.second ) ) {
vit.second = vit.first->units_from_mass( std::get<units::mass>( vit.second ) );
for( std::pair<const vitamin_id, std::variant<int, vitamin_units::mass> > &vit : vitamins_ ) {
if( std::holds_alternative<vitamin_units::mass>( vit.second ) ) {
vit.second = vit.first->units_from_mass( std::get<vitamin_units::mass>( vit.second ) );
}
if( std::holds_alternative<units::mass>( vit.second ) ) {
if( std::holds_alternative<vitamin_units::mass>( vit.second ) ) {
debugmsg( "Error occured during vitamin finalization!" );
}
}
Expand Down Expand Up @@ -150,8 +150,9 @@ nutrients &nutrients::operator+=( const nutrients &r )
debugmsg( "Nutrients not finalized when += called!" );
}
calories += r.calories;
for( const std::pair<const vitamin_id, std::variant<int, units::mass>> &vit : r.vitamins_ ) {
std::variant<int, units::mass> &here = vitamins_[vit.first];
for( const std::pair<const vitamin_id, std::variant<int, vitamin_units::mass>> &vit :
r.vitamins_ ) {
std::variant<int, vitamin_units::mass> &here = vitamins_[vit.first];
here = std::get<int>( here ) + std::get<int>( vit.second );
}
return *this;
Expand All @@ -163,8 +164,9 @@ nutrients &nutrients::operator-=( const nutrients &r )
debugmsg( "Nutrients not finalized when -= called!" );
}
calories -= r.calories;
for( const std::pair<const vitamin_id, std::variant<int, units::mass>> &vit : r.vitamins_ ) {
std::variant<int, units::mass> &here = vitamins_[vit.first];
for( const std::pair<const vitamin_id, std::variant<int, vitamin_units::mass>> &vit :
r.vitamins_ ) {
std::variant<int, vitamin_units::mass> &here = vitamins_[vit.first];
here = std::get<int>( here ) - std::get<int>( vit.second );
}
return *this;
Expand All @@ -176,8 +178,8 @@ nutrients &nutrients::operator*=( int r )
debugmsg( "Nutrients not finalized when *= called!" );
}
calories *= r;
for( const std::pair<const vitamin_id, std::variant<int, units::mass>> &vit : vitamins_ ) {
std::variant<int, units::mass> &here = vitamins_[vit.first];
for( const std::pair<const vitamin_id, std::variant<int, vitamin_units::mass>> &vit : vitamins_ ) {
std::variant<int, vitamin_units::mass> &here = vitamins_[vit.first];
here = std::get<int>( here ) * r;
}
return *this;
Expand All @@ -189,8 +191,8 @@ nutrients &nutrients::operator/=( int r )
debugmsg( "Nutrients not finalized when -= called!" );
}
calories = divide_round_up( calories, r );
for( const std::pair<const vitamin_id, std::variant<int, units::mass>> &vit : vitamins_ ) {
std::variant<int, units::mass> &here = vitamins_[vit.first];
for( const std::pair<const vitamin_id, std::variant<int, vitamin_units::mass>> &vit : vitamins_ ) {
std::variant<int, vitamin_units::mass> &here = vitamins_[vit.first];
here = divide_round_up( std::get<int>( here ), r );
}
return *this;
Expand Down Expand Up @@ -245,15 +247,16 @@ void stomach_contents::deserialize( const JsonObject &jo )
jo.read( "last_ate", last_ate );

// the next chunk deletes obsoleted vitamins
const auto predicate = []( const std::pair<vitamin_id, std::variant<int, units::mass> > &pair ) {
const auto predicate = []( const std::pair<vitamin_id, std::variant<int, vitamin_units::mass> >
&pair ) {
if( !pair.first.is_valid() ) {
DebugLog( D_WARNING, DC_ALL )
<< "deleted '" << pair.first.str() << "' from stomach_contents::nutrients::vitamins";
return true;
}
return false;
};
std::map<vitamin_id, std::variant<int, units::mass> >::iterator it = nutr.vitamins_.begin();
std::map<vitamin_id, std::variant<int, vitamin_units::mass> >::iterator it = nutr.vitamins_.begin();
while( ( it = std::find_if( it, nutr.vitamins_.end(), predicate ) ) != nutr.vitamins_.end() ) {
nutr.vitamins_.erase( it++ );
}
Expand Down
23 changes: 20 additions & 3 deletions src/stomach.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,23 @@ class JsonObject;
class JsonOut;
struct needs_rates;

namespace vitamin_units
{
using mass = units::quantity<int, units::mass_in_microgram_tag>;

constexpr mass microgram = units::quantity<int, units::mass_in_microgram_tag>( 1, {} );
constexpr mass milligram = units::quantity<int, units::mass_in_microgram_tag>( 1000, {} );
constexpr mass gram = units::quantity<int, units::mass_in_microgram_tag>( 1'000'000, {} );
const std::vector<std::pair<std::string, mass>> mass_units = { {
{ "ug", microgram },
{ "μg", microgram },
{ "mcg", microgram },
{ "mg", milligram },
{ "g", gram }
}
};
}; // namespace vitamin_units

// Separate struct for nutrients so that we can easily perform arithmetic on
// them
struct nutrients {
Expand All @@ -31,8 +48,8 @@ struct nutrients {
// For vitamins that support units::mass quantities
// If finalized == true, these will instantly convert to units,
// so make sure finalized = false if you call these before vitamins are loaded
void set_vitamin( const vitamin_id &, units::mass mass );
void add_vitamin( const vitamin_id &, units::mass mass );
void set_vitamin( const vitamin_id &, vitamin_units::mass mass );
void add_vitamin( const vitamin_id &, vitamin_units::mass mass );

void set_vitamin( const vitamin_id &, int units );
void add_vitamin( const vitamin_id &, int units );
Expand Down Expand Up @@ -68,7 +85,7 @@ struct nutrients {
bool finalized = true;

/** vitamins potentially provided by this comestible (if any) */
std::map<vitamin_id, std::variant<int, units::mass>> vitamins_;
std::map<vitamin_id, std::variant<int, vitamin_units::mass>> vitamins_;
};

// Contains all information that can pass out of (or into) a stomach
Expand Down
4 changes: 4 additions & 0 deletions src/units_fwd.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ class volume_in_milliliter_tag

using volume = quantity<int, volume_in_milliliter_tag>;

class mass_in_microgram_tag
{
};

class mass_in_milligram_tag
{
};
Expand Down
10 changes: 7 additions & 3 deletions src/vitamin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@ void vitamin::load_vitamin( const JsonObject &jo )
vit.min_ = jo.get_int( "min" );
vit.max_ = jo.get_int( "max", 0 );
vit.rate_ = read_from_json_string<time_duration>( jo.get_member( "rate" ), time_duration::units );
assign( jo, "weight_per_unit", vit.weight_per_unit );

if( jo.has_string( "weight_per_unit" ) ) {
vit.weight_per_unit = read_from_json_string( jo.get_member( "weight_per_unit" ),
vitamin_units::mass_units );
}

if( !jo.has_string( "vit_type" ) ) {
jo.throw_error_at( "vit_type", "vitamin must have a vitamin type" );
Expand Down Expand Up @@ -124,14 +128,14 @@ float vitamin::RDA_to_default( int percent ) const
return ( 24_hours / rate_ ) * ( static_cast<float>( percent ) / 100.0f );
}

int vitamin::units_from_mass( units::mass mass ) const
int vitamin::units_from_mass( vitamin_units::mass val ) const
{
if( !weight_per_unit.has_value() ) {
debugmsg( "Tried to convert vitamin in mass to units, but %s doesn't support mass for vitamins",
id_.str() );
return 1;
}
return mass / *weight_per_unit;
return val / *weight_per_unit;
}

namespace io
Expand Down
5 changes: 3 additions & 2 deletions src/vitamin.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <vector>

#include "calendar.h"
#include "stomach.h"
#include "translations.h"
#include "type_id.h"
#include "units.h"
Expand Down Expand Up @@ -109,13 +110,13 @@ class vitamin
*/
float RDA_to_default( int percent ) const;

int units_from_mass( units::mass mass ) const;
int units_from_mass( vitamin_units::mass val ) const;

private:
vitamin_id id_;
vitamin_type type_ = vitamin_type::num_vitamin_types;
translation name_;
std::optional<units::mass> weight_per_unit;
std::optional<vitamin_units::mass> weight_per_unit;
efftype_id deficiency_;
efftype_id excess_;
int min_ = 0;
Expand Down

0 comments on commit 23d21b7

Please sign in to comment.