diff --git a/projects/kiwano/kiwano.vcxproj b/projects/kiwano/kiwano.vcxproj
index 06fa487e..8883793a 100644
--- a/projects/kiwano/kiwano.vcxproj
+++ b/projects/kiwano/kiwano.vcxproj
@@ -96,9 +96,9 @@
+
-
@@ -174,8 +174,8 @@
+
-
diff --git a/projects/kiwano/kiwano.vcxproj.filters b/projects/kiwano/kiwano.vcxproj.filters
index 9a950f94..9d5619f2 100644
--- a/projects/kiwano/kiwano.vcxproj.filters
+++ b/projects/kiwano/kiwano.vcxproj.filters
@@ -105,9 +105,6 @@
2d\action
-
- utils
-
utils
@@ -351,6 +348,9 @@
base\component
+
+ utils
+
@@ -401,9 +401,6 @@
2d\action
-
- utils
-
utils
@@ -575,6 +572,9 @@
base\component
+
+ utils
+
diff --git a/src/kiwano/core/String.h b/src/kiwano/core/String.h
index cdb45b54..2307a5f7 100644
--- a/src/kiwano/core/String.h
+++ b/src/kiwano/core/String.h
@@ -20,17 +20,24 @@
#pragma once
#include
+#include
namespace kiwano
{
+/// \~chinese
+/// @brief 基础字符串容器
+template
+using BasicString = std::basic_string;
+
/// \~chinese
/// @brief 字符串容器
-using String = std::string;
+using String = BasicString;
/// \~chinese
/// @brief 宽字符串容器
-using WideString = std::wstring;
+using WideString = BasicString;
+
namespace strings
{
@@ -53,4 +60,351 @@ WideString NarrowToWide(const String& str);
}
+
+/// \~chinese
+/// @brief 基础常字符串视图
+template
+class BasicStringView
+{
+public:
+ using value_type = CharTy;
+ using pointer = CharTy*;
+ using const_pointer = const CharTy*;
+ using reference = CharTy&;
+ using const_reference = const CharTy&;
+ using traits_type = std::char_traits;
+ using size_type = std::size_t;
+ using string_type = BasicString;
+
+ BasicStringView()
+ : ptr_(nullptr)
+ , count_(0)
+ {
+ }
+
+ BasicStringView(const_pointer c_str)
+ {
+ ptr_ = c_str;
+ if (c_str)
+ {
+ count_ = traits_type::length(c_str);
+ }
+ else
+ {
+ count_ = 0;
+ }
+ }
+
+ BasicStringView(const_pointer c_str, size_type count)
+ {
+ ptr_ = c_str;
+ if (c_str)
+ {
+ count_ = count;
+ }
+ else
+ {
+ count_ = 0;
+ }
+ }
+
+ BasicStringView(const string_type& str)
+ : ptr_(str.c_str())
+ , count_(str.length())
+ {
+ }
+
+ BasicStringView(const BasicStringView& rhs)
+ : ptr_(rhs.ptr_)
+ , count_(rhs.count_)
+ {
+ }
+
+ inline const value_type* Data() const
+ {
+ return ptr_;
+ }
+
+ inline bool IsEmpty() const
+ {
+ return !ptr_ || !count_;
+ }
+
+ inline size_type Find(value_type ch) const
+ {
+ const auto ptr = traits_type::find(ptr_, count_, ch);
+ if (ptr)
+ {
+ return ptr - ptr_;
+ }
+ return string_type::npos;
+ }
+
+ inline BasicStringView SubStr(size_type pos, size_type count = string_type::npos) const
+ {
+ if (pos >= count_)
+ return BasicStringView();
+
+ if (count == string_type::npos)
+ return BasicStringView(ptr_ + pos, count_ - pos);
+
+ KGE_ASSERT(pos + count <= count_);
+ return BasicStringView(ptr_ + pos, count);
+ }
+
+ inline size_type GetLength() const
+ {
+ return count_;
+ }
+
+ inline value_type At(size_type index) const
+ {
+ return operator[](index);
+ }
+
+ inline value_type operator[](size_type index) const
+ {
+ if (IsEmpty() || index >= count_)
+ throw std::out_of_range("operator[] out of index");
+ return ptr_[index];
+ }
+
+ inline operator string_type() const
+ {
+ return string_type(ptr_, count_);
+ }
+
+ inline BasicStringView& operator=(const BasicStringView& rhs)
+ {
+ ptr_ = rhs.ptr_;
+ count_ = rhs.count_;
+ return *this;
+ }
+
+public:
+ //
+ // Iterators for BasicStringView
+ //
+ class Iterator
+ {
+ const value_type* ptr_;
+ size_type pos_;
+ size_type count_;
+
+ public:
+ using iterator_category = std::random_access_iterator_tag;
+ using value_type = value_type;
+ using pointer = value_type*;
+ using reference = value_type&;
+ using difference_type = ptrdiff_t;
+
+ inline Iterator(pointer ptr, size_type pos, size_type count)
+ : ptr_(ptr)
+ , pos_(pos)
+ , count_(count)
+ {
+ }
+
+ inline Iterator(const Iterator& rhs)
+ : ptr_(rhs.ptr_)
+ , pos_(rhs.pos_)
+ , count_(rhs.count_)
+ {
+ }
+
+ inline Iterator& operator=(const Iterator& rhs)
+ {
+ ptr_ = rhs.ptr_;
+ pos_ = rhs.pos_;
+ count_ = rhs.count_;
+ return *this;
+ }
+
+ inline const value_type& operator*() const
+ {
+ KGE_ASSERT(pos_ < count_);
+ return ptr_[pos_];
+ }
+
+ inline const value_type* operator->() const
+ {
+ return std::pointer_traits::pointer_to(**this);
+ }
+
+ inline Iterator& operator+=(size_type count)
+ {
+ KGE_ASSERT(pos_ + count >= 0 && pos_ + count <= count_);
+ pos_ += count;
+ return (*this);
+ }
+
+ inline Iterator& operator-=(size_type count)
+ {
+ KGE_ASSERT(pos_ - count >= 0 && pos_ - count <= count_);
+ pos_ -= count;
+ return (*this);
+ }
+
+ inline const Iterator operator+(size_type count) const
+ {
+ Iterator iter(*this);
+ iter += count;
+ return iter;
+ }
+
+ inline const Iterator& operator-(size_type count) const
+ {
+ Iterator iter(*this);
+ iter -= count;
+ return iter;
+ }
+
+ inline Iterator& operator++()
+ {
+ KGE_ASSERT(pos_ < count_);
+ ++pos_;
+ return (*this);
+ }
+
+ inline Iterator operator++(int)
+ {
+ Iterator old = (*this);
+ ++(*this);
+ return old;
+ }
+
+ inline Iterator& operator--()
+ {
+ KGE_ASSERT(pos_ > 0);
+ --pos_;
+ return (*this);
+ }
+
+ inline Iterator operator--(int)
+ {
+ Iterator old = (*this);
+ --(*this);
+ return old;
+ }
+
+ inline const value_type& operator[](size_type index) const
+ {
+ Iterator iter = (*this + index);
+ return iter.ptr_[iter.pos_];
+ }
+
+ inline difference_type operator-(const Iterator& other) const
+ {
+ KGE_ASSERT(ptr_ == other.ptr_ && count_ == other.count_);
+ return static_cast(pos_ - other.pos_);
+ }
+
+ inline bool operator==(const Iterator& other) const
+ {
+ return ptr_ == other.ptr_ && pos_ == other.pos_ && count_ == other.count_;
+ }
+
+ inline bool operator!=(const Iterator& other) const
+ {
+ return !(*this == other);
+ }
+
+ inline bool operator<(const Iterator& other) const
+ {
+ return ptr_ < other.ptr_ || pos_ < other.pos_ || count_ < other.count_;
+ }
+
+ inline bool operator<=(const Iterator& other) const
+ {
+ return (*this < other) || (*this == other);
+ }
+
+ inline bool operator>(const Iterator& other) const
+ {
+ return !(*this <= other);
+ }
+
+ inline bool operator>=(const Iterator& other) const
+ {
+ return !(*this < other);
+ }
+
+ inline operator bool() const
+ {
+ return ptr_ != nullptr && pos_ != count_;
+ }
+ };
+
+ using const_iterator = Iterator;
+ using iterator = const_iterator;
+ using const_reverse_iterator = std::reverse_iterator;
+ using reverse_iterator = const_reverse_iterator;
+
+ inline const_iterator begin() const
+ {
+ return const_iterator(ptr_, 0, count_);
+ }
+
+ inline const_iterator cbegin() const
+ {
+ return begin();
+ }
+
+ inline const_iterator end() const
+ {
+ return const_iterator(ptr_, count_, count_);
+ }
+
+ inline const_iterator cend() const
+ {
+ return end();
+ }
+
+ inline const_reverse_iterator rbegin() const
+ {
+ return const_reverse_iterator(end());
+ }
+
+ inline const_reverse_iterator crbegin() const
+ {
+ return rbegin();
+ }
+
+ inline const_reverse_iterator rend() const
+ {
+ return const_reverse_iterator(begin());
+ }
+
+ inline const_reverse_iterator crend() const
+ {
+ return rend();
+ }
+
+ inline const value_type& front() const
+ {
+ if (IsEmpty())
+ throw std::out_of_range("front() called on empty list");
+ return ptr_[0];
+ }
+
+ inline const value_type& back() const
+ {
+ if (IsEmpty())
+ throw std::out_of_range("back() called on empty list");
+ return ptr_[count_];
+ }
+
+private:
+ const value_type* ptr_;
+ size_type count_;
+};
+
+/// \~chinese
+/// @brief 字符串视图
+using StringView = BasicStringView;
+
+/// \~chinese
+/// @brief 宽字符串视图
+using WideStringView = BasicStringView;
+
} // namespace kiwano
diff --git a/src/kiwano/core/Time.h b/src/kiwano/core/Time.h
index 1cf09285..10b0ce37 100644
--- a/src/kiwano/core/Time.h
+++ b/src/kiwano/core/Time.h
@@ -263,17 +263,17 @@ inline void Duration::SetMilliseconds(int64_t ms)
inline void Duration::SetSeconds(float seconds)
{
- milliseconds_ = static_cast(seconds * 1000.f);
+ milliseconds_ = static_cast(seconds * 1000.f);
}
inline void Duration::SetMinutes(float minutes)
{
- milliseconds_ = static_cast(minutes * 60 * 1000.f);
+ milliseconds_ = static_cast(minutes * 60 * 1000.f);
}
inline void Duration::SetHours(float hours)
{
- milliseconds_ = static_cast(hours * 60 * 60 * 1000.f);
+ milliseconds_ = static_cast(hours * 60 * 60 * 1000.f);
}
inline bool Time::IsZero() const
diff --git a/src/kiwano/kiwano.h b/src/kiwano/kiwano.h
index 50ef674e..1bd37f0f 100644
--- a/src/kiwano/kiwano.h
+++ b/src/kiwano/kiwano.h
@@ -127,7 +127,6 @@
//
#include
-#include
#include
#include
#include
@@ -135,3 +134,4 @@
#include
#include
#include
+#include
diff --git a/src/kiwano/platform/Application.cpp b/src/kiwano/platform/Application.cpp
index 52a1533f..3aeec82c 100644
--- a/src/kiwano/platform/Application.cpp
+++ b/src/kiwano/platform/Application.cpp
@@ -42,9 +42,6 @@ Application::Application()
Use(Renderer::GetInstance());
Use(Input::GetInstance());
Use(Director::GetInstance());
-
- ticker_ = Ticker::Create(0);
- ticker_->Tick();
}
Application::~Application()
@@ -76,11 +73,27 @@ void Application::Run(RunnerPtr runner, bool debug)
while (running_)
{
- if (ticker_->Tick())
+ if (!frame_ticker_)
{
- if (!runner->MainLoop(ticker_->GetDeltaTime()))
+ frame_ticker_ = Ticker::Create(0);
+ }
+
+ if (frame_ticker_->Tick())
+ {
+ // Execute main loop
+ if (!runner->MainLoop(frame_ticker_->GetDeltaTime()))
running_ = false;
}
+ else
+ {
+ // Releases CPU
+ Duration total_dt = frame_ticker_->GetDeltaTime() + frame_ticker_->GetErrorTime();
+ Duration sleep_dt = frame_ticker_->GetInterval() - total_dt;
+ if (sleep_dt.Milliseconds() > 1LL)
+ {
+ sleep_dt.Sleep();
+ }
+ }
}
this->Destroy();
@@ -89,19 +102,17 @@ void Application::Run(RunnerPtr runner, bool debug)
void Application::Pause()
{
is_paused_ = true;
- if (ticker_)
- {
- ticker_->Pause();
- }
+
+ if (frame_ticker_)
+ frame_ticker_->Pause();
}
void Application::Resume()
{
is_paused_ = false;
- if (ticker_)
- {
- ticker_->Resume();
- }
+
+ if (frame_ticker_)
+ frame_ticker_->Resume();
}
void Application::Quit()
diff --git a/src/kiwano/platform/Application.h b/src/kiwano/platform/Application.h
index 10765c55..f893e2bb 100644
--- a/src/kiwano/platform/Application.h
+++ b/src/kiwano/platform/Application.h
@@ -78,6 +78,19 @@ public:
*/
void Quit();
+ /**
+ * \~chinese
+ * @brief 获取暂停状态
+ */
+ bool IsPaused() const;
+
+ /**
+ * \~chinese
+ * @brief 添加模块
+ * @param[in] module 模块
+ */
+ void Use(Module& module);
+
/**
* \~chinese
* @brief 获取程序运行器
@@ -92,22 +105,15 @@ public:
/**
* \~chinese
- * @brief 获取报时器
+ * @brief 获取帧报时器
*/
- TickerPtr GetTicker() const;
+ TickerPtr GetFrameTicker() const;
/**
* \~chinese
- * @brief 获取暂停状态
+ * @brief 设置帧报时器
*/
- bool IsPaused() const;
-
- /**
- * \~chinese
- * @brief 添加模块
- * @param[in] module 模块
- */
- void Use(Module& module);
+ void SetFrameTicker(TickerPtr ticker);
/**
* \~chinese
@@ -166,7 +172,7 @@ private:
bool is_paused_;
float time_scale_;
RunnerPtr runner_;
- TickerPtr ticker_;
+ TickerPtr frame_ticker_;
List modules_;
std::mutex perform_mutex_;
Queue> functions_to_perform_;
@@ -183,9 +189,14 @@ inline WindowPtr Application::GetMainWindow() const
return runner_->GetMainWindow();
}
-inline TickerPtr Application::GetTicker() const
+inline TickerPtr Application::GetFrameTicker() const
{
- return ticker_;
+ return frame_ticker_;
+}
+
+inline void Application::SetFrameTicker(TickerPtr ticker)
+{
+ frame_ticker_ = ticker;
}
inline bool Application::IsPaused() const
diff --git a/src/kiwano/platform/win32/WindowImpl.cpp b/src/kiwano/platform/win32/WindowImpl.cpp
index be5ae065..1aba29ef 100644
--- a/src/kiwano/platform/win32/WindowImpl.cpp
+++ b/src/kiwano/platform/win32/WindowImpl.cpp
@@ -34,6 +34,8 @@
#include // GET_X_LPARAM, GET_Y_LPARAM
#include // ImmAssociateContext
#pragma comment(lib, "imm32.lib")
+#include // timeBeginPeriod, timeEndPeriod
+#pragma comment(lib, "winmm.lib")
namespace kiwano
{
@@ -185,6 +187,8 @@ WindowWin32Impl::WindowWin32Impl()
// F1 - F12
for (size_t i = 0; i < 12; ++i)
key_map_[VK_F1 + i] = KeyCode(size_t(KeyCode::F1) + i);
+
+ ::timeBeginPeriod(0);
}
WindowWin32Impl::~WindowWin32Impl()
@@ -194,6 +198,8 @@ WindowWin32Impl::~WindowWin32Impl()
::DestroyWindow(handle_);
handle_ = nullptr;
}
+
+ ::timeEndPeriod(0);
}
void WindowWin32Impl::Init(const String& title, uint32_t width, uint32_t height, uint32_t icon, bool resizable)
diff --git a/src/kiwano/utils/ConfigIni.cpp b/src/kiwano/utils/ConfigIni.cpp
new file mode 100644
index 00000000..1b70c758
--- /dev/null
+++ b/src/kiwano/utils/ConfigIni.cpp
@@ -0,0 +1,372 @@
+// Copyright (c) 2016-2018 Kiwano - Nomango
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include
+#include
+#include // std::ifstream, std::ofstream
+#include // std::sort, std::for_each
+#include // std::isspace
+
+#define KGE_DEFAULT_INI_SECTION_NAME "default"
+
+namespace kiwano
+{
+
+StringView Trim(StringView str)
+{
+ if (!str.IsEmpty())
+ {
+ std::size_t start = 0, end = str.GetLength();
+ while (std::isspace(str[start]))
+ ++start;
+ while (std::isspace(str[end - 1]))
+ --end;
+
+ if (end - start)
+ return StringView(str.Data() + start, end - start);
+ }
+ return StringView();
+}
+
+class IniParser
+{
+ StringView line_;
+public:
+ IniParser(StringView line)
+ : line_(line)
+ {
+ }
+
+ bool ClearComment()
+ {
+ auto pos = line_.Find(';');
+ if (pos != String::npos)
+ {
+ if (pos == 0)
+ return true;
+
+ if (std::isspace(line_[pos - 1]))
+ {
+ line_ = Trim(line_.SubStr(0, pos - 1));
+ return line_.IsEmpty();
+ }
+ }
+ return false;
+ }
+
+ bool IsSection() const
+ {
+ return line_[0] == '[' && line_.GetLength() > 2 && line_[line_.GetLength() - 1] == ']';
+ }
+
+ StringView GetSectionName() const
+ {
+ return Trim(line_.SubStr(1, line_.GetLength() - 2));
+ }
+
+ bool GetKeyValue(StringView* key, StringView* value)
+ {
+ auto pos = line_.Find('=');
+ if (pos == String::npos)
+ return false;
+
+ *key = Trim(line_.SubStr(0, pos));
+ *value = Trim(line_.SubStr(pos + 1));
+
+ return !(*key).IsEmpty() && !(*value).IsEmpty();
+ }
+};
+
+ConfigIniPtr ConfigIni::Create(const String& file_path)
+{
+ ConfigIniPtr ptr = memory::New();
+ if (ptr)
+ {
+ if (!ptr->Load(file_path))
+ {
+ return nullptr;
+ }
+ }
+ return ptr;
+}
+
+bool ConfigIni::Load(const String& file_path)
+{
+ std::ifstream ifs(file_path);
+
+ if (ifs.is_open())
+ {
+ return Load(ifs);
+ }
+ return false;
+}
+
+bool ConfigIni::Load(std::istream& istream)
+{
+ try
+ {
+ String section = KGE_DEFAULT_INI_SECTION_NAME;
+ for (String line; std::getline(istream, line);)
+ {
+ ParseLine(line, §ion);
+ }
+ return true;
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ return false;
+}
+
+bool ConfigIni::Save(const String& file_path)
+{
+ std::ofstream ofs(file_path);
+
+ if (ofs.is_open())
+ {
+ return Save(ofs);
+ }
+ return false;
+}
+
+bool ConfigIni::Save(std::ostream& os)
+{
+ // Get all keys
+ Vector keys;
+ keys.reserve(sections_.size());
+ std::for_each(sections_.begin(), sections_.end(), [&](SectionMap::value_type& pair) { keys.push_back(pair.first); });
+
+ // Sort for keys
+ std::sort(keys.begin(), keys.end());
+
+ // Output to ini
+ for (const auto& key : keys)
+ {
+ os << '[' << key << ']' << std::endl;
+ for (const auto& pair : sections_[key])
+ {
+ os << pair.first << " = " << pair.second << std::endl;
+ }
+ os << std::endl;
+ }
+ return false;
+}
+
+ConfigIni::SectionMap ConfigIni::GetSectionMap() const
+{
+ return sections_;
+}
+
+ConfigIni::ValueMap ConfigIni::GetSection(const String& section) const
+{
+ auto iter = sections_.find(section);
+ if (iter != sections_.end())
+ return iter->second;
+ return ValueMap();
+}
+
+String ConfigIni::GetString(const String& section_name, const String& key) const
+{
+ if (HasSection(section_name))
+ {
+ const auto& section = sections_.at(section_name);
+
+ auto iter_key = section.find(key);
+ if (iter_key != section.end())
+ return iter_key->second;
+ }
+ return String();
+}
+
+float ConfigIni::GetFloat(const String& section, const String& key, float default_value) const
+{
+ String str = GetString(section, key);
+ if (str.empty())
+ return default_value;
+
+ try
+ {
+ std::size_t pos = 0;
+ float value = std::stof(str, &pos);
+ if (pos == str.size())
+ return value;
+ }
+ catch (std::invalid_argument)
+ {
+ return default_value;
+ }
+ return default_value;
+}
+
+double ConfigIni::GetDouble(const String& section, const String& key, double default_value) const
+{
+ String str = GetString(section, key);
+ if (str.empty())
+ return default_value;
+
+ try
+ {
+ std::size_t pos = 0;
+ double value = std::stod(str, &pos);
+ if (pos == str.size())
+ return value;
+ }
+ catch (std::invalid_argument)
+ {
+ return default_value;
+ }
+ return default_value;
+}
+
+int ConfigIni::GetInt(const String& section, const String& key, int default_value) const
+{
+ String str = GetString(section, key);
+ if (str.empty())
+ return default_value;
+
+ try
+ {
+ std::size_t pos = 0;
+ int value = std::stoi(str, &pos);
+ if (pos == str.size())
+ return value;
+ }
+ catch (std::invalid_argument)
+ {
+ return default_value;
+ }
+ return default_value;
+}
+
+bool ConfigIni::GetBool(const String& section, const String& key, bool default_value) const
+{
+ String str = GetString(section, key);
+ if (!str.empty())
+ {
+ if (str == "true" || str == "1")
+ return true;
+ else if (str == "false" || str == "0")
+ return false;
+ }
+ return default_value;
+}
+
+bool ConfigIni::HasSection(const String& section) const
+{
+ return !!sections_.count(section);
+}
+
+bool ConfigIni::HasValue(const String& section, const String& key) const
+{
+ if (HasSection(section))
+ {
+ return !!sections_.at(section).count(section);
+ }
+ return false;
+}
+
+void ConfigIni::SetSectionMap(const SectionMap& sections)
+{
+ sections_ = sections;
+}
+
+void ConfigIni::SetSection(const String& section, const ValueMap& values)
+{
+ sections_.insert(std::make_pair(section, values));
+}
+
+void ConfigIni::SetString(const String& section, const String& key, const String& value)
+{
+ if (HasSection(section))
+ sections_[section].insert(std::make_pair(key, value));
+ else
+ SetSection(section, ValueMap{ { key, value } });
+}
+
+void ConfigIni::SetFloat(const String& section, const String& key, float value)
+{
+ String str = std::to_string(value);
+ SetString(section, key, str);
+}
+
+void ConfigIni::SetDouble(const String& section, const String& key, double value)
+{
+ String str = std::to_string(value);
+ SetString(section, key, str);
+}
+
+void ConfigIni::SetInt(const String& section, const String& key, int value)
+{
+ String str = std::to_string(value);
+ SetString(section, key, str);
+}
+
+void ConfigIni::SetBool(const String& section, const String& key, bool value)
+{
+ SetString(section, key, value ? "true" : "false");
+}
+
+ConfigIni::ValueMap& ConfigIni::operator[](const String& section)
+{
+ if (!HasSection(section))
+ {
+ sections_.insert(std::make_pair(section, ValueMap()));
+ }
+ return sections_[section];
+}
+
+const ConfigIni::ValueMap& ConfigIni::operator[](const String& section) const
+{
+ if (!HasSection(section))
+ {
+ const_cast(sections_).insert(std::make_pair(section, ValueMap()));
+ }
+ return sections_.at(section);
+}
+
+void ConfigIni::ParseLine(StringView line, String* section)
+{
+ line = Trim(line);
+ if (line.IsEmpty())
+ return;
+
+ IniParser parser(line);
+ if (parser.ClearComment())
+ return;
+
+ if (parser.IsSection())
+ {
+ auto name = parser.GetSectionName();
+ if (name.IsEmpty())
+ throw Exception("Empty section name");
+ *section = name;
+ return;
+ }
+
+ StringView key, value;
+ if (!parser.GetKeyValue(&key, &value))
+ {
+ throw Exception("Parse key-value failed");
+ }
+ SetString(*section, key, value);
+}
+
+} // namespace kiwano
diff --git a/src/kiwano/utils/ConfigIni.h b/src/kiwano/utils/ConfigIni.h
new file mode 100644
index 00000000..e83434f2
--- /dev/null
+++ b/src/kiwano/utils/ConfigIni.h
@@ -0,0 +1,179 @@
+// Copyright (c) 2016-2018 Kiwano - Nomango
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#pragma once
+#include
+#include
+
+namespace kiwano
+{
+
+KGE_DECLARE_SMART_PTR(ConfigIni);
+
+/// \~chinese
+/// @brief ini格式文件
+class KGE_API ConfigIni : public ObjectBase
+{
+public:
+ /// \~chinese
+ /// @brief 加载 ini 文件
+ /// @param file_path 文件路径
+ static ConfigIniPtr Create(const String& file_path);
+
+ /// \~chinese
+ /// @brief 键值字典
+ typedef Map ValueMap;
+
+ /// \~chinese
+ /// @brief Section字典
+ typedef UnorderedMap SectionMap;
+
+ /// \~chinese
+ /// @brief 加载 ini 文件
+ /// @param file_path 文件路径
+ bool Load(const String& file_path);
+
+ /// \~chinese
+ /// @brief 加载 ini 文件
+ /// @param is 输入流
+ bool Load(std::istream& is);
+
+ /// \~chinese
+ /// @brief 保存至 ini 文件
+ /// @param file_path 文件路径
+ bool Save(const String& file_path);
+
+ /// \~chinese
+ /// @brief 保存至 ini 文件
+ /// @param os 输出流
+ bool Save(std::ostream& os);
+
+ /// \~chinese
+ /// @brief 获取所有section
+ SectionMap GetSectionMap() const;
+
+ /// \~chinese
+ /// @brief 获取section
+ /// @param section section的名称
+ ValueMap GetSection(const String& section) const;
+
+ /// \~chinese
+ /// @brief 获取值
+ /// @param section section的名称
+ /// @param key key的名称
+ String GetString(const String& section, const String& key) const;
+
+ /// \~chinese
+ /// @brief 获取值
+ /// @param section section的名称
+ /// @param key key的名称
+ /// @param default_value 不存在时的默认值
+ float GetFloat(const String& section, const String& key, float default_value = 0.0f) const;
+
+ /// \~chinese
+ /// @brief 获取值
+ /// @param section section的名称
+ /// @param key key的名称
+ /// @param default_value 不存在时的默认值
+ double GetDouble(const String& section, const String& key, double default_value = 0.0) const;
+
+ /// \~chinese
+ /// @brief 获取值
+ /// @param section section的名称
+ /// @param key key的名称
+ /// @param default_value 不存在时的默认值
+ int GetInt(const String& section, const String& key, int default_value = 0) const;
+
+ /// \~chinese
+ /// @brief 获取值
+ /// @param section section的名称
+ /// @param key key的名称
+ /// @param default_value 不存在时的默认值
+ bool GetBool(const String& section, const String& key, bool default_value = false) const;
+
+ /// \~chinese
+ /// @brief 是否存在section
+ /// @param section section的名称
+ bool HasSection(const String& section) const;
+
+ /// \~chinese
+ /// @brief 是否存在值
+ /// @param section section的名称
+ /// @param key key的名称
+ bool HasValue(const String& section, const String& key) const;
+
+ /// \~chinese
+ /// @brief 设置所有section
+ /// @param sections section字典
+ void SetSectionMap(const SectionMap& sections);
+
+ /// \~chinese
+ /// @brief 设置section
+ /// @param section section的名称
+ /// @param values 键值字典
+ void SetSection(const String& section, const ValueMap& values);
+
+ /// \~chinese
+ /// @brief 设置值
+ /// @param section section的名称
+ /// @param key key的名称
+ /// @param value 值
+ void SetString(const String& section, const String& key, const String& value);
+
+ /// \~chinese
+ /// @brief 设置值
+ /// @param section section的名称
+ /// @param key key的名称
+ /// @param value 值
+ void SetFloat(const String& section, const String& key, float value);
+
+ /// \~chinese
+ /// @brief 设置值
+ /// @param section section的名称
+ /// @param key key的名称
+ /// @param value 值
+ void SetDouble(const String& section, const String& key, double value);
+
+ /// \~chinese
+ /// @brief 设置值
+ /// @param section section的名称
+ /// @param key key的名称
+ /// @param value 值
+ void SetInt(const String& section, const String& key, int value);
+
+ /// \~chinese
+ /// @brief 设置值
+ /// @param section section的名称
+ /// @param key key的名称
+ /// @param value 值
+ void SetBool(const String& section, const String& key, bool value);
+
+ ValueMap& operator[](const String& section);
+
+ const ValueMap& operator[](const String& section) const;
+
+private:
+ void ParseLine(StringView line, String* section);
+
+private:
+ SectionMap sections_;
+};
+
+} // namespace kiwano
diff --git a/src/kiwano/utils/LocalStorage.cpp b/src/kiwano/utils/LocalStorage.cpp
deleted file mode 100644
index 6e98d9ab..00000000
--- a/src/kiwano/utils/LocalStorage.cpp
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright (c) 2016-2018 Kiwano - Nomango
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-#include
-
-namespace kiwano
-{
-
-LocalStorage::LocalStorage(const String& file_path, const String& field)
-{
- SetFilePath(file_path);
- SetFieldName(field);
-}
-
-bool LocalStorage::Exists(const String& key) const
-{
- char temp[256] = { 0 };
- ::GetPrivateProfileStringA(field_name_.c_str(), key.c_str(), "", temp, 255, file_path_.c_str());
- return temp[0] == '\0';
-}
-
-bool LocalStorage::SaveInt(const String& key, int val) const
-{
- BOOL ret =
- ::WritePrivateProfileStringA(field_name_.c_str(), key.c_str(), std::to_string(val).c_str(), file_path_.c_str());
- return ret == TRUE;
-}
-
-bool LocalStorage::SaveFloat(const String& key, float val) const
-{
- BOOL ret =
- ::WritePrivateProfileStringA(field_name_.c_str(), key.c_str(), std::to_string(val).c_str(), file_path_.c_str());
- return ret == TRUE;
-}
-
-bool LocalStorage::SaveDouble(const String& key, double val) const
-{
- BOOL ret =
- ::WritePrivateProfileStringA(field_name_.c_str(), key.c_str(), std::to_string(val).c_str(), file_path_.c_str());
- return ret == TRUE;
-}
-
-bool LocalStorage::SaveBool(const String& key, bool val) const
-{
- BOOL ret = ::WritePrivateProfileStringA(field_name_.c_str(), key.c_str(), (val ? "1" : "0"), file_path_.c_str());
- return ret == TRUE;
-}
-
-bool LocalStorage::SaveString(const String& key, const String& val) const
-{
- BOOL ret = ::WritePrivateProfileStringA(field_name_.c_str(), key.c_str(), val.c_str(), file_path_.c_str());
- return ret == TRUE;
-}
-
-int LocalStorage::GetInt(const String& key, int default_value) const
-{
- return ::GetPrivateProfileIntA(field_name_.c_str(), key.c_str(), default_value, file_path_.c_str());
-}
-
-float LocalStorage::GetFloat(const String& key, float default_value) const
-{
- char temp[32] = { 0 };
- String default_str = std::to_string(default_value);
- ::GetPrivateProfileStringA(field_name_.c_str(), key.c_str(), default_str.c_str(), temp, 31, file_path_.c_str());
- return std::stof(temp);
-}
-
-double LocalStorage::GetDouble(const String& key, double default_value) const
-{
- char temp[32] = { 0 };
- String default_str = std::to_string(default_value);
- ::GetPrivateProfileStringA(field_name_.c_str(), key.c_str(), default_str.c_str(), temp, 31, file_path_.c_str());
- return std::stod(temp);
-}
-
-bool LocalStorage::GetBool(const String& key, bool default_value) const
-{
- int nValue = ::GetPrivateProfileIntA(field_name_.c_str(), key.c_str(), default_value ? 1 : 0, file_path_.c_str());
- return nValue == TRUE;
-}
-
-String LocalStorage::GetString(const String& key, const String& default_value) const
-{
- char temp[256] = { 0 };
- ::GetPrivateProfileStringA(field_name_.c_str(), key.c_str(), default_value.c_str(), temp, 255, file_path_.c_str());
- return temp;
-}
-
-} // namespace kiwano
diff --git a/src/kiwano/utils/LocalStorage.h b/src/kiwano/utils/LocalStorage.h
deleted file mode 100644
index d3475cab..00000000
--- a/src/kiwano/utils/LocalStorage.h
+++ /dev/null
@@ -1,163 +0,0 @@
-// Copyright (c) 2016-2018 Kiwano - Nomango
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-#pragma once
-#include
-#include
-
-namespace kiwano
-{
-
-KGE_DECLARE_SMART_PTR(LocalStorage);
-
-/// \~chinese
-/// @brief 本地存储
-/// @details LocalStorage是一个简易的持久化工具,存放(字符串-值)的键值对
-/// 支持的数据类型包括 (bool | int | float | double | String)
-/// 例如, 保存游戏最高分, 以便下次进行游戏时读取:
-/// @code
-/// LocalStorage data; // 创建数据对象
-/// data.SaveInt("best-score", 20); // 保存最高分 20
-/// int best = data.GetInt("best-score"); // 读取之前储存的最高分
-/// @endcode
-class KGE_API LocalStorage : public ObjectBase
-{
-public:
- /// \~chinese
- /// @brief 构建本地存储对象
- /// @param file_path 文件储存路径
- /// @param field 字段名
- LocalStorage(const String& file_path = "data.ini", const String& field = "defalut");
-
- /// \~chinese
- /// @brief 获取文件储存路径
- const String& GetFilePath() const;
-
- /// \~chinese
- /// @brief 设置文件储存路径
- void SetFilePath(const String& file_path);
-
- /// \~chinese
- /// @brief 获取字段名
- const String& GetFieldName() const;
-
- /// \~chinese
- /// @brief 设置字段名
- void SetFieldName(const String& field);
-
- /// \~chinese
- /// @brief 判断键对应的数据是否存在
- bool Exists(const String& key) const;
-
- /// \~chinese
- /// @brief 保存 int 类型的值
- /// @param key 键
- /// @param val 值
- /// @return 操作是否成功
- bool SaveInt(const String& key, int val) const;
-
- /// \~chinese
- /// @brief 保存 float 类型的值
- /// @param key 键
- /// @param val 值
- /// @return 操作是否成功
- bool SaveFloat(const String& key, float val) const;
-
- /// \~chinese
- /// @brief 保存 double 类型的值
- /// @param key 键
- /// @param val 值
- /// @return 操作是否成功
- bool SaveDouble(const String& key, double val) const;
-
- /// \~chinese
- /// @brief 保存 bool 类型的值
- /// @param key 键
- /// @param val 值
- /// @return 操作是否成功
- bool SaveBool(const String& key, bool val) const;
-
- /// \~chinese
- /// @brief 保存 String 类型的值
- /// @param key 键
- /// @param val 值
- /// @return 操作是否成功
- bool SaveString(const String& key, const String& val) const;
-
- /// \~chinese
- /// @brief 获取 int 类型的值
- /// @param key 键
- /// @param default_value 值不存在时返回的默认值
- /// @return 值
- int GetInt(const String& key, int default_value = 0) const;
-
- /// \~chinese
- /// @brief 获取 float 类型的值
- /// @param key 键
- /// @param default_value 值不存在时返回的默认值
- /// @return 值
- float GetFloat(const String& key, float default_value = 0.0f) const;
-
- /// \~chinese
- /// @brief 获取 double 类型的值
- /// @param key 键
- /// @param default_value 值不存在时返回的默认值
- /// @return 值
- double GetDouble(const String& key, double default_value = 0.0) const;
-
- /// \~chinese
- /// @brief 获取 bool 类型的值
- /// @param key 键
- /// @param default_value 值不存在时返回的默认值
- /// @return 值
- bool GetBool(const String& key, bool default_value = false) const;
-
- /// \~chinese
- /// @brief 获取 字符串 类型的值
- /// @param key 键
- /// @param default_value 值不存在时返回的默认值
- /// @return 值
- String GetString(const String& key, const String& default_value = String()) const;
-
-private:
- String file_path_;
- String field_name_;
-};
-
-inline const String& LocalStorage::GetFilePath() const
-{
- return file_path_;
-}
-
-inline const String& LocalStorage::GetFieldName() const
-{
- return field_name_;
-}
-
-inline void LocalStorage::SetFilePath(const String& file_path)
-{
- file_path_ = file_path;
-}
-
-inline void LocalStorage::SetFieldName(const String& field_name)
-{
- field_name_ = field_name;
-}
-} // namespace kiwano
diff --git a/src/kiwano/utils/Ticker.cpp b/src/kiwano/utils/Ticker.cpp
index d44dc5ac..bbb40fdd 100644
--- a/src/kiwano/utils/Ticker.cpp
+++ b/src/kiwano/utils/Ticker.cpp
@@ -65,9 +65,16 @@ bool Ticker::Tick(Duration dt)
if (ticked_count_ == total_tick_count_)
return false;
+ if (interval_.IsZero())
+ {
+ delta_time_ = dt;
+ ++ticked_count_;
+ return true;
+ }
+
elapsed_time_ += dt;
- if (elapsed_time_ + error_time_ > interval_)
+ if (elapsed_time_ + error_time_ >= interval_)
{
delta_time_ = elapsed_time_;
error_time_ = (elapsed_time_ + error_time_) - interval_;
diff --git a/src/kiwano/utils/Ticker.h b/src/kiwano/utils/Ticker.h
index 815bfe8c..09d9df2b 100644
--- a/src/kiwano/utils/Ticker.h
+++ b/src/kiwano/utils/Ticker.h
@@ -87,6 +87,10 @@ public:
/// @brief 设置报时间隔
void SetInterval(Duration interval);
+ /// \~chinese
+ /// @brief 获取时间误差
+ Duration GetErrorTime() const;
+
/// \~chinese
/// @brief 获取计时器
TimerPtr GetTimer();
@@ -140,4 +144,9 @@ inline void Ticker::SetInterval(Duration interval)
interval_ = interval;
}
+inline Duration Ticker::GetErrorTime() const
+{
+ return error_time_;
+}
+
} // namespace kiwano