3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "irrlichttypes_bloated.h"
22 #include "exceptions.h"
23 #include "threading/mutex_auto_lock.h"
24 #include "util/strfnd.h"
30 #include "util/serialize.h"
36 static Settings main_settings
;
37 Settings
*g_settings
= &main_settings
;
38 std::string g_settings_path
;
46 Settings
& Settings::operator += (const Settings
&other
)
51 MutexAutoLock
lock(m_mutex
);
52 MutexAutoLock
lock2(other
.m_mutex
);
60 Settings
& Settings::operator = (const Settings
&other
)
65 MutexAutoLock
lock(m_mutex
);
66 MutexAutoLock
lock2(other
.m_mutex
);
75 bool Settings::checkNameValid(const std::string
&name
)
77 bool valid
= name
.find_first_of("=\"{}#") == std::string::npos
;
79 valid
= std::find_if(name
.begin(), name
.end(), ::isspace
) == name
.end();
82 errorstream
<< "Invalid setting name \"" << name
<< "\""
90 bool Settings::checkValueValid(const std::string
&value
)
92 if (value
.substr(0, 3) == "\"\"\"" ||
93 value
.find("\n\"\"\"") != std::string::npos
) {
94 errorstream
<< "Invalid character sequence '\"\"\"' found in"
95 " setting value!" << std::endl
;
101 std::string
Settings::getMultiline(std::istream
&is
, size_t *num_lines
)
109 std::getline(is
, line
);
110 if (line
== "\"\"\"")
113 value
.push_back('\n');
116 size_t len
= value
.size();
118 value
.erase(len
- 1);
127 bool Settings::readConfigFile(const char *filename
)
129 std::ifstream
is(filename
);
133 return parseConfigLines(is
, "");
137 bool Settings::parseConfigLines(std::istream
&is
, const std::string
&end
)
139 MutexAutoLock
lock(m_mutex
);
141 std::string line
, name
, value
;
144 std::getline(is
, line
);
145 SettingsParseEvent event
= parseConfigObject(line
, end
, name
, value
);
153 m_settings
[name
] = SettingsEntry(value
);
158 Settings
*group
= new Settings
;
159 if (!group
->parseConfigLines(is
, "}")) {
163 m_settings
[name
] = SettingsEntry(group
);
167 m_settings
[name
] = SettingsEntry(getMultiline(is
));
176 void Settings::writeLines(std::ostream
&os
, u32 tab_depth
) const
178 MutexAutoLock
lock(m_mutex
);
180 for (const auto &setting_it
: m_settings
)
181 printEntry(os
, setting_it
.first
, setting_it
.second
, tab_depth
);
185 void Settings::printEntry(std::ostream
&os
, const std::string
&name
,
186 const SettingsEntry
&entry
, u32 tab_depth
)
188 for (u32 i
= 0; i
!= tab_depth
; i
++)
191 if (entry
.is_group
) {
192 os
<< name
<< " = {\n";
194 entry
.group
->writeLines(os
, tab_depth
+ 1);
196 for (u32 i
= 0; i
!= tab_depth
; i
++)
202 if (entry
.value
.find('\n') != std::string::npos
)
203 os
<< "\"\"\"\n" << entry
.value
<< "\n\"\"\"\n";
205 os
<< entry
.value
<< "\n";
210 bool Settings::updateConfigObject(std::istream
&is
, std::ostream
&os
,
211 const std::string
&end
, u32 tab_depth
)
213 SettingEntries::const_iterator it
;
214 std::set
<std::string
> present_entries
;
215 std::string line
, name
, value
;
216 bool was_modified
= false;
217 bool end_found
= false;
219 // Add any settings that exist in the config file with the current value
220 // in the object if existing
221 while (is
.good() && !end_found
) {
222 std::getline(is
, line
);
223 SettingsParseEvent event
= parseConfigObject(line
, end
, name
, value
);
227 os
<< line
<< (is
.eof() ? "" : "\n");
231 value
= getMultiline(is
);
234 it
= m_settings
.find(name
);
235 if (it
!= m_settings
.end() &&
236 (it
->second
.is_group
|| it
->second
.value
!= value
)) {
237 printEntry(os
, name
, it
->second
, tab_depth
);
239 } else if (it
== m_settings
.end()) {
240 // Remove by skipping
245 if (event
== SPE_MULTILINE
)
246 os
<< value
<< "\n\"\"\"\n";
248 present_entries
.insert(name
);
251 it
= m_settings
.find(name
);
252 if (it
!= m_settings
.end() && it
->second
.is_group
) {
254 sanity_check(it
->second
.group
!= NULL
);
255 was_modified
|= it
->second
.group
->updateConfigObject(is
, os
,
257 } else if (it
== m_settings
.end()) {
258 // Remove by skipping
260 Settings removed_group
; // Move 'is' to group end
261 std::stringstream ss
;
262 removed_group
.updateConfigObject(is
, ss
, "}", tab_depth
+ 1);
265 printEntry(os
, name
, it
->second
, tab_depth
);
268 present_entries
.insert(name
);
271 os
<< line
<< (is
.eof() ? "" : "\n");
276 // Add any settings in the object that don't exist in the config file yet
277 for (it
= m_settings
.begin(); it
!= m_settings
.end(); ++it
) {
278 if (present_entries
.find(it
->first
) != present_entries
.end())
281 printEntry(os
, it
->first
, it
->second
, tab_depth
);
289 bool Settings::updateConfigFile(const char *filename
)
291 MutexAutoLock
lock(m_mutex
);
293 std::ifstream
is(filename
);
294 std::ostringstream
os(std::ios_base::binary
);
296 bool was_modified
= updateConfigObject(is
, os
, "");
302 if (!fs::safeWriteToFile(filename
, os
.str())) {
303 errorstream
<< "Error writing configuration file: \""
304 << filename
<< "\"" << std::endl
;
312 bool Settings::parseCommandLine(int argc
, char *argv
[],
313 std::map
<std::string
, ValueSpec
> &allowed_options
)
315 int nonopt_index
= 0;
316 for (int i
= 1; i
< argc
; i
++) {
317 std::string arg_name
= argv
[i
];
318 if (arg_name
.substr(0, 2) != "--") {
319 // If option doesn't start with -, read it in as nonoptX
320 if (arg_name
[0] != '-'){
321 std::string name
= "nonopt";
322 name
+= itos(nonopt_index
);
327 errorstream
<< "Invalid command-line parameter \""
328 << arg_name
<< "\": --<option> expected." << std::endl
;
332 std::string name
= arg_name
.substr(2);
334 std::map
<std::string
, ValueSpec
>::iterator n
;
335 n
= allowed_options
.find(name
);
336 if (n
== allowed_options
.end()) {
337 errorstream
<< "Unknown command-line parameter \""
338 << arg_name
<< "\"" << std::endl
;
342 ValueType type
= n
->second
.type
;
346 if (type
== VALUETYPE_FLAG
) {
349 if ((i
+ 1) >= argc
) {
350 errorstream
<< "Invalid command-line parameter \""
351 << name
<< "\": missing value" << std::endl
;
370 const SettingsEntry
&Settings::getEntry(const std::string
&name
) const
372 MutexAutoLock
lock(m_mutex
);
374 SettingEntries::const_iterator n
;
375 if ((n
= m_settings
.find(name
)) == m_settings
.end()) {
376 if ((n
= m_defaults
.find(name
)) == m_defaults
.end())
377 throw SettingNotFoundException("Setting [" + name
+ "] not found.");
383 const SettingsEntry
&Settings::getEntryDefault(const std::string
&name
) const
385 MutexAutoLock
lock(m_mutex
);
387 SettingEntries::const_iterator n
;
388 if ((n
= m_defaults
.find(name
)) == m_defaults
.end()) {
389 throw SettingNotFoundException("Setting [" + name
+ "] not found.");
395 Settings
*Settings::getGroup(const std::string
&name
) const
397 const SettingsEntry
&entry
= getEntry(name
);
399 throw SettingNotFoundException("Setting [" + name
+ "] is not a group.");
404 const std::string
&Settings::get(const std::string
&name
) const
406 const SettingsEntry
&entry
= getEntry(name
);
408 throw SettingNotFoundException("Setting [" + name
+ "] is a group.");
413 const std::string
&Settings::getDefault(const std::string
&name
) const
415 const SettingsEntry
&entry
= getEntryDefault(name
);
417 throw SettingNotFoundException("Setting [" + name
+ "] is a group.");
422 bool Settings::getBool(const std::string
&name
) const
424 return is_yes(get(name
));
428 u16
Settings::getU16(const std::string
&name
) const
430 return stoi(get(name
), 0, 65535);
434 s16
Settings::getS16(const std::string
&name
) const
436 return stoi(get(name
), -32768, 32767);
440 u32
Settings::getU32(const std::string
&name
) const
442 return (u32
) stoi(get(name
));
445 s32
Settings::getS32(const std::string
&name
) const
447 return stoi(get(name
));
451 float Settings::getFloat(const std::string
&name
) const
453 return stof(get(name
));
457 u64
Settings::getU64(const std::string
&name
) const
460 std::string s
= get(name
);
461 std::istringstream
ss(s
);
467 v2f
Settings::getV2F(const std::string
&name
) const
472 value
.X
= stof(f
.next(","));
473 value
.Y
= stof(f
.next(")"));
478 v3f
Settings::getV3F(const std::string
&name
) const
483 value
.X
= stof(f
.next(","));
484 value
.Y
= stof(f
.next(","));
485 value
.Z
= stof(f
.next(")"));
490 u32
Settings::getFlagStr(const std::string
&name
, const FlagDesc
*flagdesc
,
494 u32 mask_default
= 0;
497 // Read default value (if there is any)
498 if (getDefaultNoEx(name
, value
)) {
499 flags
= std::isdigit(value
[0])
501 : readFlagString(value
, flagdesc
, &mask_default
);
504 // Apply custom flags "on top"
507 u32 mask_user
= U32_MAX
;
508 flags_user
= std::isdigit(value
[0])
509 ? stoi(value
) // Override default
510 : readFlagString(value
, flagdesc
, &mask_user
);
516 *flagmask
= mask_default
| mask_user
;
522 bool Settings::getNoiseParams(const std::string
&name
, NoiseParams
&np
) const
524 return getNoiseParamsFromGroup(name
, np
) || getNoiseParamsFromValue(name
, np
);
528 bool Settings::getNoiseParamsFromValue(const std::string
&name
,
529 NoiseParams
&np
) const
533 if (!getNoEx(name
, value
))
536 // Format: f32,f32,(f32,f32,f32),s32,s32,f32[,f32]
539 np
.offset
= stof(f
.next(","));
540 np
.scale
= stof(f
.next(","));
542 np
.spread
.X
= stof(f
.next(","));
543 np
.spread
.Y
= stof(f
.next(","));
544 np
.spread
.Z
= stof(f
.next(")"));
546 np
.seed
= stoi(f
.next(","));
547 np
.octaves
= stoi(f
.next(","));
548 np
.persist
= stof(f
.next(","));
550 std::string optional_params
= f
.next("");
551 if (!optional_params
.empty())
552 np
.lacunarity
= stof(optional_params
);
558 bool Settings::getNoiseParamsFromGroup(const std::string
&name
,
559 NoiseParams
&np
) const
561 Settings
*group
= NULL
;
563 if (!getGroupNoEx(name
, group
))
566 group
->getFloatNoEx("offset", np
.offset
);
567 group
->getFloatNoEx("scale", np
.scale
);
568 group
->getV3FNoEx("spread", np
.spread
);
569 group
->getS32NoEx("seed", np
.seed
);
570 group
->getU16NoEx("octaves", np
.octaves
);
571 group
->getFloatNoEx("persistence", np
.persist
);
572 group
->getFloatNoEx("lacunarity", np
.lacunarity
);
575 if (!group
->getFlagStrNoEx("flags", np
.flags
, flagdesc_noiseparams
))
576 np
.flags
= NOISE_FLAG_DEFAULTS
;
582 bool Settings::exists(const std::string
&name
) const
584 MutexAutoLock
lock(m_mutex
);
586 return (m_settings
.find(name
) != m_settings
.end() ||
587 m_defaults
.find(name
) != m_defaults
.end());
591 std::vector
<std::string
> Settings::getNames() const
593 std::vector
<std::string
> names
;
594 for (const auto &settings_it
: m_settings
) {
595 names
.push_back(settings_it
.first
);
602 /***************************************
603 * Getters that don't throw exceptions *
604 ***************************************/
606 bool Settings::getGroupNoEx(const std::string
&name
, Settings
*&val
) const
609 val
= getGroup(name
);
611 } catch (SettingNotFoundException
&e
) {
617 bool Settings::getNoEx(const std::string
&name
, std::string
&val
) const
622 } catch (SettingNotFoundException
&e
) {
628 bool Settings::getDefaultNoEx(const std::string
&name
, std::string
&val
) const
631 val
= getDefault(name
);
633 } catch (SettingNotFoundException
&e
) {
639 bool Settings::getFlag(const std::string
&name
) const
642 return getBool(name
);
643 } catch(SettingNotFoundException
&e
) {
649 bool Settings::getFloatNoEx(const std::string
&name
, float &val
) const
652 val
= getFloat(name
);
654 } catch (SettingNotFoundException
&e
) {
660 bool Settings::getU16NoEx(const std::string
&name
, u16
&val
) const
665 } catch (SettingNotFoundException
&e
) {
671 bool Settings::getS16NoEx(const std::string
&name
, s16
&val
) const
676 } catch (SettingNotFoundException
&e
) {
682 bool Settings::getS32NoEx(const std::string
&name
, s32
&val
) const
687 } catch (SettingNotFoundException
&e
) {
693 bool Settings::getU64NoEx(const std::string
&name
, u64
&val
) const
698 } catch (SettingNotFoundException
&e
) {
704 bool Settings::getV2FNoEx(const std::string
&name
, v2f
&val
) const
709 } catch (SettingNotFoundException
&e
) {
715 bool Settings::getV3FNoEx(const std::string
&name
, v3f
&val
) const
720 } catch (SettingNotFoundException
&e
) {
726 bool Settings::getFlagStrNoEx(const std::string
&name
, u32
&val
,
727 const FlagDesc
*flagdesc
) const
730 if (!(flagdesc
= getFlagDescFallback(name
)))
731 return false; // Not found
735 val
= getFlagStr(name
, flagdesc
, nullptr);
738 } catch (SettingNotFoundException
&e
) {
748 bool Settings::setEntry(const std::string
&name
, const void *data
,
749 bool set_group
, bool set_default
)
751 Settings
*old_group
= NULL
;
753 if (!checkNameValid(name
))
755 if (!set_group
&& !checkValueValid(*(const std::string
*)data
))
759 MutexAutoLock
lock(m_mutex
);
761 SettingsEntry
&entry
= set_default
? m_defaults
[name
] : m_settings
[name
];
762 old_group
= entry
.group
;
764 entry
.value
= set_group
? "" : *(const std::string
*)data
;
765 entry
.group
= set_group
? *(Settings
**)data
: NULL
;
766 entry
.is_group
= set_group
;
775 bool Settings::set(const std::string
&name
, const std::string
&value
)
777 if (!setEntry(name
, &value
, false, false))
785 bool Settings::setDefault(const std::string
&name
, const std::string
&value
)
787 return setEntry(name
, &value
, false, true);
791 bool Settings::setGroup(const std::string
&name
, const Settings
&group
)
793 // Settings must own the group pointer
794 // avoid double-free by copying the source
795 Settings
*copy
= new Settings();
797 return setEntry(name
, ©
, true, false);
801 bool Settings::setGroupDefault(const std::string
&name
, const Settings
&group
)
803 // Settings must own the group pointer
804 // avoid double-free by copying the source
805 Settings
*copy
= new Settings();
807 return setEntry(name
, ©
, true, true);
811 bool Settings::setBool(const std::string
&name
, bool value
)
813 return set(name
, value
? "true" : "false");
817 bool Settings::setS16(const std::string
&name
, s16 value
)
819 return set(name
, itos(value
));
823 bool Settings::setU16(const std::string
&name
, u16 value
)
825 return set(name
, itos(value
));
829 bool Settings::setS32(const std::string
&name
, s32 value
)
831 return set(name
, itos(value
));
835 bool Settings::setU64(const std::string
&name
, u64 value
)
837 std::ostringstream os
;
839 return set(name
, os
.str());
843 bool Settings::setFloat(const std::string
&name
, float value
)
845 return set(name
, ftos(value
));
849 bool Settings::setV2F(const std::string
&name
, v2f value
)
851 std::ostringstream os
;
852 os
<< "(" << value
.X
<< "," << value
.Y
<< ")";
853 return set(name
, os
.str());
857 bool Settings::setV3F(const std::string
&name
, v3f value
)
859 std::ostringstream os
;
860 os
<< "(" << value
.X
<< "," << value
.Y
<< "," << value
.Z
<< ")";
861 return set(name
, os
.str());
865 bool Settings::setFlagStr(const std::string
&name
, u32 flags
,
866 const FlagDesc
*flagdesc
, u32 flagmask
)
869 if (!(flagdesc
= getFlagDescFallback(name
)))
870 return false; // Not found
873 return set(name
, writeFlagString(flags
, flagdesc
, flagmask
));
877 bool Settings::setNoiseParams(const std::string
&name
,
878 const NoiseParams
&np
, bool set_default
)
880 Settings
*group
= new Settings
;
882 group
->setFloat("offset", np
.offset
);
883 group
->setFloat("scale", np
.scale
);
884 group
->setV3F("spread", np
.spread
);
885 group
->setS32("seed", np
.seed
);
886 group
->setU16("octaves", np
.octaves
);
887 group
->setFloat("persistence", np
.persist
);
888 group
->setFloat("lacunarity", np
.lacunarity
);
889 group
->setFlagStr("flags", np
.flags
, flagdesc_noiseparams
, np
.flags
);
891 return setEntry(name
, &group
, true, set_default
);
895 bool Settings::remove(const std::string
&name
)
897 // Lock as short as possible, unlock before doCallbacks()
900 SettingEntries::iterator it
= m_settings
.find(name
);
901 if (it
!= m_settings
.end()) {
902 delete it
->second
.group
;
903 m_settings
.erase(it
);
915 void Settings::clear()
917 MutexAutoLock
lock(m_mutex
);
921 void Settings::clearDefaults()
923 MutexAutoLock
lock(m_mutex
);
924 clearDefaultsNoLock();
927 SettingsParseEvent
Settings::parseConfigObject(const std::string
&line
,
928 const std::string
&end
, std::string
&name
, std::string
&value
)
930 std::string trimmed_line
= trim(line
);
932 if (trimmed_line
.empty())
934 if (trimmed_line
[0] == '#')
936 if (trimmed_line
== end
)
939 size_t pos
= trimmed_line
.find('=');
940 if (pos
== std::string::npos
)
943 name
= trim(trimmed_line
.substr(0, pos
));
944 value
= trim(trimmed_line
.substr(pos
+ 1));
948 if (value
== "\"\"\"")
949 return SPE_MULTILINE
;
955 void Settings::updateNoLock(const Settings
&other
)
957 m_settings
.insert(other
.m_settings
.begin(), other
.m_settings
.end());
958 m_defaults
.insert(other
.m_defaults
.begin(), other
.m_defaults
.end());
962 void Settings::clearNoLock()
965 for (SettingEntries::const_iterator it
= m_settings
.begin();
966 it
!= m_settings
.end(); ++it
)
967 delete it
->second
.group
;
970 clearDefaultsNoLock();
973 void Settings::clearDefaultsNoLock()
975 for (SettingEntries::const_iterator it
= m_defaults
.begin();
976 it
!= m_defaults
.end(); ++it
)
977 delete it
->second
.group
;
981 void Settings::setDefault(const std::string
&name
, const FlagDesc
*flagdesc
,
984 m_flags
[name
] = flagdesc
;
985 setDefault(name
, writeFlagString(flags
, flagdesc
, U32_MAX
));
988 void Settings::overrideDefaults(Settings
*other
)
990 for (const auto &setting
: other
->m_settings
) {
991 if (setting
.second
.is_group
) {
992 setGroupDefault(setting
.first
, *setting
.second
.group
);
995 const FlagDesc
*flagdesc
= getFlagDescFallback(setting
.first
);
997 // Flags cannot be copied directly.
998 // 1) Get the current set flags
999 u32 flags
= getFlagStr(setting
.first
, flagdesc
, nullptr);
1000 // 2) Set the flags as defaults
1001 other
->setDefault(setting
.first
, flagdesc
, flags
);
1002 // 3) Get the newly set flags and override the default setting value
1003 setDefault(setting
.first
, flagdesc
,
1004 other
->getFlagStr(setting
.first
, flagdesc
, nullptr));
1007 // Also covers FlagDesc settings
1008 setDefault(setting
.first
, setting
.second
.value
);
1012 const FlagDesc
*Settings::getFlagDescFallback(const std::string
&name
) const
1014 auto it
= m_flags
.find(name
);
1015 return it
== m_flags
.end() ? nullptr : it
->second
;
1018 void Settings::registerChangedCallback(const std::string
&name
,
1019 SettingsChangedCallback cbf
, void *userdata
)
1021 MutexAutoLock
lock(m_callback_mutex
);
1022 m_callbacks
[name
].emplace_back(cbf
, userdata
);
1025 void Settings::deregisterChangedCallback(const std::string
&name
,
1026 SettingsChangedCallback cbf
, void *userdata
)
1028 MutexAutoLock
lock(m_callback_mutex
);
1029 SettingsCallbackMap::iterator it_cbks
= m_callbacks
.find(name
);
1031 if (it_cbks
!= m_callbacks
.end()) {
1032 SettingsCallbackList
&cbks
= it_cbks
->second
;
1034 SettingsCallbackList::iterator position
=
1035 std::find(cbks
.begin(), cbks
.end(), std::make_pair(cbf
, userdata
));
1037 if (position
!= cbks
.end())
1038 cbks
.erase(position
);
1042 void Settings::removeSecureSettings()
1044 for (const auto &name
: getNames()) {
1045 if (name
.compare(0, 7, "secure.") != 0)
1048 errorstream
<< "Secure setting " << name
1049 << " isn't allowed, so was ignored."
1055 void Settings::doCallbacks(const std::string
&name
) const
1057 MutexAutoLock
lock(m_callback_mutex
);
1059 SettingsCallbackMap::const_iterator it_cbks
= m_callbacks
.find(name
);
1060 if (it_cbks
!= m_callbacks
.end()) {
1061 SettingsCallbackList::const_iterator it
;
1062 for (it
= it_cbks
->second
.begin(); it
!= it_cbks
->second
.end(); ++it
)
1063 (it
->first
)(name
, it
->second
);