mirror of
https://github.com/InoriRus/Kyty.git
synced 2026-08-28 05:06:40 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
file(GLOB core_src
|
||||
"src/*.cpp"
|
||||
)
|
||||
|
||||
if (MSVC AND CLANG)
|
||||
set_source_files_properties(${core_src} PROPERTIES COMPILE_FLAGS "-Wno-pragma-pack")
|
||||
endif()
|
||||
|
||||
add_library(core_obj OBJECT ${core_src})
|
||||
#add_library(core STATIC ${core_src})
|
||||
add_library(core STATIC $<TARGET_OBJECTS:core_obj>)
|
||||
|
||||
target_link_libraries(core sys)
|
||||
target_link_libraries(core math)
|
||||
target_link_libraries(core sdl2)
|
||||
target_link_libraries(core sqlite)
|
||||
target_link_libraries(core lzma)
|
||||
target_link_libraries(core zstd)
|
||||
|
||||
|
||||
get_property(inc_headers TARGET core PROPERTY INCLUDE_DIRECTORIES)
|
||||
list(APPEND inc_headers
|
||||
${CMAKE_SOURCE_DIR}/3rdparty/sdl2/include
|
||||
${CMAKE_SOURCE_DIR}/3rdparty/sqlite/include
|
||||
${CMAKE_SOURCE_DIR}/3rdparty/lzma/include
|
||||
${CMAKE_SOURCE_DIR}/3rdparty/zstd/lib
|
||||
)
|
||||
|
||||
target_include_directories(core PRIVATE ${inc_headers})
|
||||
target_include_directories(core_obj PRIVATE ${inc_headers})
|
||||
|
||||
list(APPEND check_headers
|
||||
${CMAKE_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
clang_tidy_check(core_obj "" "${check_headers}" "${inc_headers}")
|
||||
|
||||
include_what_you_use(core_obj "${inc_headers}")
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,35 @@
|
||||
#include "Kyty/Core/Core.h"
|
||||
|
||||
#include "Kyty/Core/ArrayWrapper.h" // IWYU pragma: associated
|
||||
#include "Kyty/Core/ByteBuffer.h" // IWYU pragma: associated
|
||||
#include "Kyty/Core/Common.h" // IWYU pragma: associated
|
||||
#include "Kyty/Core/Database.h"
|
||||
#include "Kyty/Core/Debug.h"
|
||||
#include "Kyty/Core/File.h"
|
||||
#include "Kyty/Core/Hash.h" // IWYU pragma: associated
|
||||
#include "Kyty/Core/Language.h"
|
||||
#include "Kyty/Core/LinkList.h" // IWYU pragma: associated
|
||||
#include "Kyty/Core/MagicEnum.h" // IWYU pragma: associated
|
||||
#include "Kyty/Core/MemoryAlloc.h"
|
||||
#include "Kyty/Core/RefCounter.h" // IWYU pragma: associated
|
||||
#include "Kyty/Core/SafeDelete.h" // IWYU pragma: associated
|
||||
#include "Kyty/Core/SimpleArray.h" // IWYU pragma: associated
|
||||
#include "Kyty/Core/Singleton.h" // IWYU pragma: associated
|
||||
#include "Kyty/Core/Vector.h" // IWYU pragma: associated
|
||||
|
||||
namespace Kyty::Core {
|
||||
|
||||
KYTY_SUBSYSTEM_INIT(Core)
|
||||
{
|
||||
core_memory_init();
|
||||
core_file_init();
|
||||
core_debug_init(parent->GetArgv()[0]);
|
||||
Language::Init();
|
||||
Database::Init();
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Core) {}
|
||||
|
||||
KYTY_SUBSYSTEM_DESTROY(Core) {}
|
||||
|
||||
} // namespace Kyty::Core
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,883 @@
|
||||
#include "Kyty/Core/DateTime.h"
|
||||
|
||||
#include "Kyty/Core/DbgAssert.h"
|
||||
#include "Kyty/Math/MathAll.h"
|
||||
#include "Kyty/Sys/SysTimer.h"
|
||||
|
||||
namespace Kyty::Core {
|
||||
|
||||
static void ymd_to_jd(int year, int month, int day, jd_t* jd)
|
||||
{
|
||||
jd_t t_year = year;
|
||||
jd_t t_month = month;
|
||||
jd_t t_day = day;
|
||||
|
||||
if (t_year < 0)
|
||||
{
|
||||
t_year++;
|
||||
}
|
||||
|
||||
jd_t a = Math::floordiv<jd_t>(14 - t_month, 12);
|
||||
jd_t y = t_year + 4800 - a;
|
||||
jd_t m = t_month + 12 * a - 3;
|
||||
*jd = t_day + Math::floordiv<jd_t>(153 * m + 2, 5) + 365 * y + Math::floordiv<jd_t>(y, 4) - Math::floordiv<jd_t>(y, 100) +
|
||||
Math::floordiv<jd_t>(y, 400) - 32045;
|
||||
}
|
||||
|
||||
static void jd_to_ymd(int* year, int* month, int* day, jd_t jd)
|
||||
{
|
||||
jd_t a = jd + 32044;
|
||||
jd_t b = Math::floordiv<jd_t>(4 * a + 3, 146097);
|
||||
jd_t c = a - Math::floordiv<jd_t>(146097 * b, 4);
|
||||
jd_t d = Math::floordiv<jd_t>(4 * c + 3, 1461);
|
||||
jd_t e = c - Math::floordiv<jd_t>(1461 * d, 4);
|
||||
jd_t m = Math::floordiv<jd_t>(5 * e + 2, 153);
|
||||
|
||||
int t_day = e - Math::floordiv<jd_t>(153 * m + 2, 5) + 1;
|
||||
int t_month = m + 3 - 12 * Math::floordiv<jd_t>(m, 10);
|
||||
int t_year = 100 * b + d - 4800 + Math::floordiv<jd_t>(m, 10);
|
||||
|
||||
if (t_year <= 0)
|
||||
{
|
||||
t_year--;
|
||||
}
|
||||
|
||||
if (year != nullptr)
|
||||
{
|
||||
*year = t_year;
|
||||
}
|
||||
if (month != nullptr)
|
||||
{
|
||||
*month = t_month;
|
||||
}
|
||||
if (day != nullptr)
|
||||
{
|
||||
*day = t_day;
|
||||
}
|
||||
}
|
||||
|
||||
static void hms_to_ms(int hour, int minute, int second, int msec, int* ms)
|
||||
{
|
||||
*ms = hour * 60 * 60 * 1000 + minute * 60 * 1000 + second * 1000 + msec;
|
||||
}
|
||||
|
||||
static void ms_to_hms(int* hour, int* minute, int* second, int* msec, int ms)
|
||||
{
|
||||
if (hour != nullptr)
|
||||
{
|
||||
*hour = ms / (60 * 60 * 1000);
|
||||
}
|
||||
if (minute != nullptr)
|
||||
{
|
||||
*minute = (ms % (60 * 60 * 1000)) / (60 * 1000);
|
||||
}
|
||||
if (second != nullptr)
|
||||
{
|
||||
*second = (ms / 1000) % 60;
|
||||
}
|
||||
if (msec != nullptr)
|
||||
{
|
||||
*msec = ms % 1000;
|
||||
}
|
||||
}
|
||||
|
||||
Date::Date(int year, int month, int day)
|
||||
{
|
||||
Set(year, month, day);
|
||||
}
|
||||
|
||||
bool Date::IsValid(int year, int month, int day)
|
||||
{
|
||||
if (year == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return (day >= 1) && (day <= DaysInMonth(month) || (day == 29 && month == 2 && IsLeapYear(year)));
|
||||
}
|
||||
|
||||
int Date::DaysInMonth() const
|
||||
{
|
||||
if (IsInvalid())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int year = 0;
|
||||
int month = 0;
|
||||
jd_to_ymd(&year, &month, nullptr, m_jd);
|
||||
|
||||
if (month == 2 && IsLeapYear(year))
|
||||
{
|
||||
return 29;
|
||||
}
|
||||
|
||||
return DaysInMonth(month);
|
||||
}
|
||||
|
||||
bool Date::IsLeapYear() const
|
||||
{
|
||||
if (IsInvalid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int year = 0;
|
||||
jd_to_ymd(&year, nullptr, nullptr, m_jd);
|
||||
|
||||
return IsLeapYear(year);
|
||||
}
|
||||
|
||||
int Date::DaysInMonth(int month)
|
||||
{
|
||||
static const int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||
|
||||
if (month < 1 || month > 12)
|
||||
{
|
||||
month = 0;
|
||||
}
|
||||
|
||||
return days[month];
|
||||
}
|
||||
|
||||
void Date::Set(int year, int month, int day)
|
||||
{
|
||||
if (IsValid(year, month, day))
|
||||
{
|
||||
ymd_to_jd(year, month, day, &m_jd);
|
||||
} else
|
||||
{
|
||||
m_jd = DATE_JD_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
void Date::Get(int* year, int* month, int* day) const
|
||||
{
|
||||
if (IsInvalid())
|
||||
{
|
||||
if (year != nullptr)
|
||||
{
|
||||
*year = 0;
|
||||
}
|
||||
if (month != nullptr)
|
||||
{
|
||||
*month = 0;
|
||||
}
|
||||
if (day != nullptr)
|
||||
{
|
||||
*day = 0;
|
||||
}
|
||||
} else
|
||||
{
|
||||
jd_to_ymd(year, month, day, m_jd);
|
||||
}
|
||||
}
|
||||
|
||||
bool Date::IsLeapYear(int year)
|
||||
{
|
||||
if (year < 1)
|
||||
{
|
||||
year++;
|
||||
}
|
||||
|
||||
return ((year % 4) == 0 && (year % 100) != 0) || (year % 400) == 0;
|
||||
}
|
||||
|
||||
int Date::Year() const
|
||||
{
|
||||
if (IsInvalid())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int year = 0;
|
||||
jd_to_ymd(&year, nullptr, nullptr, m_jd);
|
||||
|
||||
return year;
|
||||
}
|
||||
|
||||
int Date::Month() const
|
||||
{
|
||||
if (IsInvalid())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int month = 0;
|
||||
jd_to_ymd(nullptr, &month, nullptr, m_jd);
|
||||
|
||||
return month;
|
||||
}
|
||||
|
||||
int Date::Day() const
|
||||
{
|
||||
if (IsInvalid())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int day = 0;
|
||||
jd_to_ymd(nullptr, nullptr, &day, m_jd);
|
||||
|
||||
return day;
|
||||
}
|
||||
|
||||
int Date::DaysInYear() const
|
||||
{
|
||||
if (IsInvalid())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return IsLeapYear() ? 366 : 365;
|
||||
}
|
||||
|
||||
int Date::DayOfWeek() const
|
||||
{
|
||||
if (IsInvalid())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (m_jd >= 0)
|
||||
{
|
||||
return (m_jd % 7) + 1;
|
||||
}
|
||||
|
||||
return ((m_jd + 1) % 7) + 7;
|
||||
}
|
||||
|
||||
int Date::DayOfYear() const
|
||||
{
|
||||
if (IsInvalid())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
jd_t d = 0;
|
||||
ymd_to_jd(Year(), 1, 1, &d);
|
||||
|
||||
return m_jd - d + 1;
|
||||
}
|
||||
|
||||
int Date::DaysInYear(int year)
|
||||
{
|
||||
return IsLeapYear(year) ? 366 : 365;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-non-const-parameter)
|
||||
static bool format_date(const Date* d, const String& f, uint32_t* out_i, String* out_r, LanguageId lang_id)
|
||||
{
|
||||
auto& i = *out_i;
|
||||
auto& r = *out_r;
|
||||
if (f.Mid(i, 4) == U"YYYY")
|
||||
{
|
||||
int y = d->Year();
|
||||
EXIT_IF(y < 0 || y > 9999);
|
||||
r += String::FromPrintf("%04d", y);
|
||||
i += 3;
|
||||
} else if (f.Mid(i, 3) == U"YYY")
|
||||
{
|
||||
int y = d->Year();
|
||||
EXIT_IF(y < 0 || y > 9999);
|
||||
r += String::FromPrintf("%04d", y).Mid(1);
|
||||
i += 2;
|
||||
} else if (f.Mid(i, 2) == U"YY")
|
||||
{
|
||||
int y = d->Year();
|
||||
EXIT_IF(y < 0 || y > 9999);
|
||||
r += String::FromPrintf("%04d", y).Mid(2);
|
||||
i += 1;
|
||||
} else if (f.Mid(i, 1) == U"Y")
|
||||
{
|
||||
int y = d->Year();
|
||||
EXIT_IF(y < 0 || y > 9999);
|
||||
r += String::FromPrintf("%04d", y).Mid(3);
|
||||
} else if (f.Mid(i, 1) == U"Q")
|
||||
{
|
||||
r += String::FromPrintf("%d", d->QuarterOfYear());
|
||||
} else if (f.Mid(i, 2) == U"MM")
|
||||
{
|
||||
r += String::FromPrintf("%02d", d->Month());
|
||||
i += 1;
|
||||
} else if (f.Mid(i, 5) == U"MONTH")
|
||||
{
|
||||
r += Language::GetNameOfMonth(d->Month(), lang_id);
|
||||
i += 4;
|
||||
} else if (f.Mid(i, 3) == U"MON")
|
||||
{
|
||||
r += Language::GetNameOfMonthShort(d->Month(), lang_id);
|
||||
i += 2;
|
||||
} else if (f.Mid(i, 3) == U"DDD")
|
||||
{
|
||||
r += String::FromPrintf("%03d", d->DayOfYear());
|
||||
i += 2;
|
||||
} else if (f.Mid(i, 2) == U"DD")
|
||||
{
|
||||
r += String::FromPrintf("%02d", d->Day());
|
||||
i += 1;
|
||||
} else if (f.Mid(i, 2) == U"DY")
|
||||
{
|
||||
r += Language::GetNameOfDayShort(d->DayOfWeek(), lang_id);
|
||||
i += 1;
|
||||
} else if (f.Mid(i, 3) == U"DAY")
|
||||
{
|
||||
r += Language::GetNameOfDay(d->DayOfWeek(), lang_id);
|
||||
i += 2;
|
||||
} else if (f.Mid(i, 1) == U"D")
|
||||
{
|
||||
r += String::FromPrintf("%d", d->DayOfWeek());
|
||||
} else if (f.Mid(i, 1) == U"J")
|
||||
{
|
||||
r += String::FromPrintf("%d", d->JulianDay());
|
||||
} else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* YYYY - 4-digit year
|
||||
* YYY, YY, Y - Last 3, 2, or 1 digit(s) of year.
|
||||
* Q - Quarter of year (1, 2, 3, 4; JAN-MAR = 1).
|
||||
* MM - Month (01-12; JAN = 01).
|
||||
* MON - Abbreviated name of month.
|
||||
* MONTH - Name of month
|
||||
* D - Day of week (1-7).
|
||||
* DAY - Name of day.
|
||||
* DY - Abbreviated name of day.
|
||||
* DD - Day of month (01-31).
|
||||
* DDD - Day of year (001-366).
|
||||
* J - Julian day
|
||||
*/
|
||||
String Date::ToString(const char* format, LanguageId lang_id) const
|
||||
{
|
||||
if (IsInvalid())
|
||||
{
|
||||
return U"";
|
||||
}
|
||||
|
||||
String r;
|
||||
String f = String::FromUtf8(format);
|
||||
|
||||
FOR (i, f)
|
||||
{
|
||||
if (!format_date(this, f, &i, &r, lang_id))
|
||||
{
|
||||
r += f.At(i);
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int Date::QuarterOfYear() const
|
||||
{
|
||||
if (IsInvalid())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (Month() - 1) / 3 + 1;
|
||||
}
|
||||
|
||||
Date Date::FromMacros(const String& date)
|
||||
{
|
||||
StringList lst = date.Split(U" ", String::SplitType::SplitNoEmptyParts);
|
||||
|
||||
if (lst.Size() != 3)
|
||||
{
|
||||
return Date();
|
||||
}
|
||||
|
||||
static const char* month_str[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
||||
|
||||
int month = -1;
|
||||
for (int i = 0; i < 12; i++)
|
||||
{
|
||||
if (lst.At(0).EqualAscii(month_str[i]))
|
||||
{
|
||||
month = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (month < 0)
|
||||
{
|
||||
return Date();
|
||||
}
|
||||
|
||||
return Date(lst.At(2).ToInt32(), month, lst.At(1).ToInt32());
|
||||
}
|
||||
|
||||
Date Date::FromSystem()
|
||||
{
|
||||
SysTimeStruct t {};
|
||||
sys_get_system_time(t);
|
||||
|
||||
return Date(t.Year, t.Month, t.Day);
|
||||
}
|
||||
|
||||
Date Date::FromSystemUTC()
|
||||
{
|
||||
SysTimeStruct t {};
|
||||
sys_get_system_time_utc(t);
|
||||
|
||||
return Date(t.Year, t.Month, t.Day);
|
||||
}
|
||||
|
||||
Time::Time(int hour, int minute, int second, int msec)
|
||||
{
|
||||
Set(hour, minute, second, msec);
|
||||
}
|
||||
|
||||
Time Time::FromSystem()
|
||||
{
|
||||
SysTimeStruct t {};
|
||||
sys_get_system_time(t);
|
||||
|
||||
return Time(t.Hour, t.Minute, t.Second, t.Milliseconds);
|
||||
}
|
||||
|
||||
Time Time::FromSystemUTC()
|
||||
{
|
||||
SysTimeStruct t {};
|
||||
sys_get_system_time_utc(t);
|
||||
|
||||
return Time(t.Hour, t.Minute, t.Second, t.Milliseconds);
|
||||
}
|
||||
|
||||
void Time::Set(int hour, int minute, int second, int msec)
|
||||
{
|
||||
if (IsValid(hour, minute, second, msec))
|
||||
{
|
||||
hms_to_ms(hour, minute, second, msec, &m_ms);
|
||||
} else
|
||||
{
|
||||
m_ms = TIME_MS_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
void Time::Get(int* hour, int* minute, int* second, int* msec) const
|
||||
{
|
||||
if (IsInvalid())
|
||||
{
|
||||
if (hour != nullptr)
|
||||
{
|
||||
*hour = -1;
|
||||
}
|
||||
if (minute != nullptr)
|
||||
{
|
||||
*minute = -1;
|
||||
}
|
||||
if (second != nullptr)
|
||||
{
|
||||
*second = -1;
|
||||
}
|
||||
if (msec != nullptr)
|
||||
{
|
||||
*msec = -1;
|
||||
}
|
||||
} else
|
||||
{
|
||||
ms_to_hms(hour, minute, second, msec, m_ms);
|
||||
}
|
||||
}
|
||||
|
||||
bool Time::IsValid(int hour, int minute, int second, int msec)
|
||||
{
|
||||
return (hour >= 0 && hour <= 23) && (minute >= 0 && minute <= 59) && (second >= 0 && second <= 59) && (msec >= 0 && msec <= 999);
|
||||
}
|
||||
|
||||
int Time::Hour24() const
|
||||
{
|
||||
if (IsInvalid())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int h = 0;
|
||||
ms_to_hms(&h, nullptr, nullptr, nullptr, m_ms);
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
int Time::Hour12() const
|
||||
{
|
||||
if (IsInvalid())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int h = 0;
|
||||
ms_to_hms(&h, nullptr, nullptr, nullptr, m_ms);
|
||||
|
||||
h = h % 12;
|
||||
|
||||
return h == 0 ? 12 : h;
|
||||
}
|
||||
|
||||
int Time::Minute() const
|
||||
{
|
||||
if (IsInvalid())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int m = 0;
|
||||
ms_to_hms(nullptr, &m, nullptr, nullptr, m_ms);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
int Time::Second() const
|
||||
{
|
||||
if (IsInvalid())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int s = 0;
|
||||
ms_to_hms(nullptr, nullptr, &s, nullptr, m_ms);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
int Time::Msec() const
|
||||
{
|
||||
if (IsInvalid())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int m = 0;
|
||||
ms_to_hms(nullptr, nullptr, nullptr, &m, m_ms);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
bool Time::IsAM() const
|
||||
{
|
||||
if (IsInvalid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int h = 0;
|
||||
ms_to_hms(&h, nullptr, nullptr, nullptr, m_ms);
|
||||
|
||||
return h < 12;
|
||||
}
|
||||
|
||||
bool Time::IsPM() const
|
||||
{
|
||||
if (IsInvalid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int h = 0;
|
||||
ms_to_hms(&h, nullptr, nullptr, nullptr, m_ms);
|
||||
|
||||
return h >= 12;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-non-const-parameter)
|
||||
static bool format_time(const Time* t, const String& f, uint32_t* out_i, String* out_r)
|
||||
{
|
||||
auto& i = *out_i;
|
||||
auto& r = *out_r;
|
||||
if (f.Mid(i, 4) == U"HH24")
|
||||
{
|
||||
r += String::FromPrintf("%02d", t->Hour24());
|
||||
i += 3;
|
||||
} else if (f.Mid(i, 4) == U"HH12")
|
||||
{
|
||||
r += String::FromPrintf("%02d", t->Hour12());
|
||||
i += 3;
|
||||
} else if (f.Mid(i, 2) == U"HH")
|
||||
{
|
||||
r += String::FromPrintf("%02d", t->Hour12());
|
||||
i += 1;
|
||||
} else if (f.Mid(i, 2) == U"MI")
|
||||
{
|
||||
r += String::FromPrintf("%02d", t->Minute());
|
||||
i += 1;
|
||||
} else if (f.Mid(i, 5) == U"SSSSS")
|
||||
{
|
||||
r += String::FromPrintf("%05d", t->MsecTotal() / 1000);
|
||||
i += 4;
|
||||
} else if (f.Mid(i, 2) == U"SS")
|
||||
{
|
||||
r += String::FromPrintf("%02d", t->Second());
|
||||
i += 1;
|
||||
} else if (f.Mid(i, 3) == U"FFF")
|
||||
{
|
||||
r += String::FromPrintf("%03d", t->Msec());
|
||||
i += 2;
|
||||
} else if (f.Mid(i, 2) == U"AM")
|
||||
{
|
||||
r += t->IsAM() ? U"AM" : U"PM";
|
||||
i += 1;
|
||||
} else if (f.Mid(i, 4) == U"A.M.")
|
||||
{
|
||||
r += t->IsAM() ? U"A.M." : U"P.M.";
|
||||
i += 3;
|
||||
} else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* HH - Hour of day (1-12)
|
||||
* HH12 - Hour of day (1-12)
|
||||
* HH24 - Hour of day (0-23)
|
||||
* MI - Minute (0-59)
|
||||
* SS - Second (0-59)
|
||||
* SSSSS - Seconds past midnight (0-86399)
|
||||
* FFF - Milliseconds
|
||||
* AM - AM or PM
|
||||
* A.M. - A.M. or P.M.
|
||||
*/
|
||||
String Time::ToString(const char* format) const
|
||||
{
|
||||
if (IsInvalid())
|
||||
{
|
||||
return U"";
|
||||
}
|
||||
|
||||
String r;
|
||||
String f = String::FromUtf8(format);
|
||||
|
||||
FOR (i, f)
|
||||
{
|
||||
if (!format_time(this, f, &i, &r))
|
||||
{
|
||||
r += f.At(i);
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
Time Time::operator+(int secs) const
|
||||
{
|
||||
int ms_secs = secs * 1000;
|
||||
|
||||
EXIT_IF(ms_secs > TIME_MS_IN_DAY || ms_secs < -TIME_MS_IN_DAY);
|
||||
|
||||
if (IsInvalid())
|
||||
{
|
||||
return Time();
|
||||
}
|
||||
|
||||
int r = m_ms + ms_secs;
|
||||
|
||||
if (r < 0)
|
||||
{
|
||||
r += TIME_MS_IN_DAY;
|
||||
}
|
||||
if (r >= TIME_MS_IN_DAY)
|
||||
{
|
||||
r -= TIME_MS_IN_DAY;
|
||||
}
|
||||
|
||||
return Time(r);
|
||||
}
|
||||
|
||||
Time Time::operator-(int secs) const
|
||||
{
|
||||
int ms_secs = secs * 1000;
|
||||
|
||||
EXIT_IF(ms_secs > TIME_MS_IN_DAY || ms_secs < -TIME_MS_IN_DAY);
|
||||
|
||||
if (IsInvalid())
|
||||
{
|
||||
return Time();
|
||||
}
|
||||
|
||||
int r = m_ms - ms_secs;
|
||||
|
||||
if (r < 0)
|
||||
{
|
||||
r += TIME_MS_IN_DAY;
|
||||
}
|
||||
if (r >= TIME_MS_IN_DAY)
|
||||
{
|
||||
r -= TIME_MS_IN_DAY;
|
||||
}
|
||||
|
||||
return Time(r);
|
||||
}
|
||||
|
||||
Time Time::operator+=(int secs)
|
||||
{
|
||||
int ms_secs = secs * 1000;
|
||||
|
||||
EXIT_IF(ms_secs > TIME_MS_IN_DAY || ms_secs < -TIME_MS_IN_DAY);
|
||||
|
||||
if (!IsInvalid())
|
||||
{
|
||||
m_ms += ms_secs;
|
||||
|
||||
if (m_ms < 0)
|
||||
{
|
||||
m_ms += TIME_MS_IN_DAY;
|
||||
}
|
||||
if (m_ms >= TIME_MS_IN_DAY)
|
||||
{
|
||||
m_ms -= TIME_MS_IN_DAY;
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Time Time::operator-=(int secs)
|
||||
{
|
||||
int ms_secs = secs * 1000;
|
||||
|
||||
EXIT_IF(ms_secs > TIME_MS_IN_DAY || ms_secs < -TIME_MS_IN_DAY);
|
||||
|
||||
if (!IsInvalid())
|
||||
{
|
||||
m_ms -= ms_secs;
|
||||
|
||||
if (m_ms < 0)
|
||||
{
|
||||
m_ms += TIME_MS_IN_DAY;
|
||||
}
|
||||
if (m_ms >= TIME_MS_IN_DAY)
|
||||
{
|
||||
m_ms -= TIME_MS_IN_DAY;
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
DateTime DateTime::FromSystem()
|
||||
{
|
||||
SysTimeStruct t {};
|
||||
sys_get_system_time(t);
|
||||
|
||||
if (t.is_invalid)
|
||||
{
|
||||
return DateTime();
|
||||
}
|
||||
|
||||
return DateTime(Date(t.Year, t.Month, t.Day), Time(t.Hour, t.Minute, t.Second, t.Milliseconds));
|
||||
}
|
||||
|
||||
DateTime DateTime::FromSystemUTC()
|
||||
{
|
||||
SysTimeStruct t {};
|
||||
sys_get_system_time_utc(t);
|
||||
|
||||
if (t.is_invalid)
|
||||
{
|
||||
return DateTime();
|
||||
}
|
||||
|
||||
return DateTime(Date(t.Year, t.Month, t.Day), Time(t.Hour, t.Minute, t.Second, t.Milliseconds));
|
||||
}
|
||||
|
||||
/**
|
||||
* YYYY - 4-digit year
|
||||
* YYY, YY, Y - Last 3, 2, or 1 digit(s) of year.
|
||||
* Q - Quarter of year (1, 2, 3, 4; JAN-MAR = 1).
|
||||
* MM - Month (01-12; JAN = 01).
|
||||
* MON - Abbreviated name of month.
|
||||
* MONTH - Name of month
|
||||
* D - Day of week (1-7).
|
||||
* DAY - Name of day.
|
||||
* DY - Abbreviated name of day.
|
||||
* DD - Day of month (01-31).
|
||||
* DDD - Day of year (001-366).
|
||||
* J - Julian day
|
||||
* HH - Hour of day (1-12)
|
||||
* HH12 - Hour of day (1-12)
|
||||
* HH24 - Hour of day (0-23)
|
||||
* MI - Minute (0-59)
|
||||
* SS - Second (0-59)
|
||||
* SSSSS - Seconds past midnight (0-86399)
|
||||
* FFF - Milliseconds
|
||||
* AM - AM or PM
|
||||
* A.M. - A.M. or P.M.
|
||||
*/
|
||||
String DateTime::ToString(const char* format, LanguageId lang_id) const
|
||||
{
|
||||
if (IsInvalid())
|
||||
{
|
||||
return U"";
|
||||
}
|
||||
|
||||
String r;
|
||||
String f = String::FromUtf8(format);
|
||||
|
||||
FOR (i, f)
|
||||
{
|
||||
if (!format_date(&m_date, f, &i, &r, lang_id) && !format_time(&m_time, f, &i, &r))
|
||||
{
|
||||
r += f.At(i);
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
uint64_t DateTime::DistanceMs(const DateTime& other) const
|
||||
{
|
||||
EXIT_IF(IsInvalid() || other.IsInvalid());
|
||||
|
||||
if (other == *this)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (other > *this)
|
||||
{
|
||||
return other.DistanceMs(*this);
|
||||
}
|
||||
|
||||
jd_t j1 = other.m_date.JulianDay();
|
||||
jd_t j2 = m_date.JulianDay();
|
||||
|
||||
return int64_t(j2 - j1 - 1) * int64_t(TIME_MS_IN_DAY) + (int64_t(TIME_MS_IN_DAY) - int64_t(other.m_time.MsecTotal())) +
|
||||
int64_t(m_time.MsecTotal());
|
||||
}
|
||||
|
||||
DateTime DateTime::FromSQLiteJulian(double jd)
|
||||
{
|
||||
double i = NAN;
|
||||
double f = modf(jd + 0.5, &i);
|
||||
return DateTime(Date(jd_t(i)), Time(int(f * double(TIME_MS_IN_DAY))));
|
||||
}
|
||||
|
||||
double DateTime::ToSQLiteJulian() const
|
||||
{
|
||||
return -0.5 + double(GetDate().JulianDay()) + double(GetTime().MsecTotal()) / double(TIME_MS_IN_DAY);
|
||||
}
|
||||
|
||||
int64_t DateTime::ToSQLiteJulianInt64() const
|
||||
{
|
||||
return -(static_cast<int64_t>(TIME_MS_IN_DAY)) / 2 + int64_t(GetDate().JulianDay()) * (static_cast<int64_t>(TIME_MS_IN_DAY)) +
|
||||
int64_t(GetTime().MsecTotal());
|
||||
}
|
||||
|
||||
DateTime DateTime::FromUnix(double seconds)
|
||||
{
|
||||
return FromSQLiteJulian(2440587.5 + seconds / 86400.0);
|
||||
}
|
||||
|
||||
double DateTime::ToUnix() const
|
||||
{
|
||||
return (ToSQLiteJulian() - 2440587.5) * 86400.0;
|
||||
}
|
||||
|
||||
} // namespace Kyty::Core
|
||||
@@ -0,0 +1,84 @@
|
||||
#include "Kyty/Core/DbgAssert.h"
|
||||
|
||||
#include "Kyty/Core/Common.h"
|
||||
#include "Kyty/Core/Debug.h"
|
||||
#include "Kyty/Core/Subsystems.h"
|
||||
|
||||
#include <cstdarg>
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#include <windows.h> // IWYU pragma: keep
|
||||
// IWYU pragma: no_include <debugapi.h>
|
||||
#endif
|
||||
|
||||
namespace Kyty::Core {
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_BUILD == KYTY_BUILD_DEBUG && KYTY_COMPILER == KYTY_COMPILER_CLANG
|
||||
constexpr int PRINT_STACK_FROM = 4;
|
||||
#else
|
||||
constexpr int PRINT_STACK_FROM = 2;
|
||||
#endif
|
||||
|
||||
void dbg_print_stack()
|
||||
{
|
||||
DebugStack s;
|
||||
DebugStack::Trace(&s);
|
||||
|
||||
printf("--- Stack Trace ---\n");
|
||||
s.Print(PRINT_STACK_FROM);
|
||||
}
|
||||
|
||||
int dbg_assert_handler(char const* expr, char const* file, int line)
|
||||
{
|
||||
dbg_print_stack();
|
||||
KYTY_LOGE("--- Fatal Error ---\n");
|
||||
KYTY_LOGE("Assertion (%s) failed in %s:%d\n", expr, file, line);
|
||||
SubsystemsListSingleton::Instance()->ShutdownAll();
|
||||
return 1;
|
||||
}
|
||||
|
||||
int dbg_exit_if_handler(char const* expr, char const* file, int line)
|
||||
{
|
||||
dbg_print_stack();
|
||||
KYTY_LOGE("--- Fatal Error ---\n");
|
||||
KYTY_LOGE("Error: condition (%s) is true in %s:%d\n", expr, file, line);
|
||||
SubsystemsListSingleton::Instance()->ShutdownAll();
|
||||
return 1;
|
||||
}
|
||||
|
||||
int dbg_not_implemented_handler(char const* expr, char const* file, int line)
|
||||
{
|
||||
dbg_print_stack();
|
||||
KYTY_LOGE("--- Fatal Error ---\n");
|
||||
KYTY_LOGE("Not implemented (%s) in %s:%d\n", expr, file, line);
|
||||
SubsystemsListSingleton::Instance()->ShutdownAll();
|
||||
return 1;
|
||||
}
|
||||
|
||||
int dbg_exit_handler(char const* file, int line, const char* f, ...)
|
||||
{
|
||||
va_list args {};
|
||||
va_start(args, f);
|
||||
|
||||
dbg_print_stack();
|
||||
|
||||
KYTY_LOGE("--- Error ---\n");
|
||||
vprintf(f, args);
|
||||
KYTY_LOGE(" in %s:%d\n", file, line);
|
||||
|
||||
SubsystemsListSingleton::Instance()->ShutdownAll();
|
||||
|
||||
va_end(args);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool dbg_is_debugger_present()
|
||||
{
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
return !(IsDebuggerPresent() == 0);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Kyty::Core
|
||||
@@ -0,0 +1,917 @@
|
||||
#include "Kyty/Core/Debug.h"
|
||||
|
||||
#include "Kyty/Core/Common.h"
|
||||
#include "Kyty/Core/DbgAssert.h"
|
||||
#include "Kyty/Core/File.h"
|
||||
#include "Kyty/Core/Hashmap.h"
|
||||
#include "Kyty/Core/SafeDelete.h"
|
||||
#include "Kyty/Core/Vector.h"
|
||||
#include "Kyty/Sys/SysDbg.h"
|
||||
#include "Kyty/Sys/SysStdlib.h"
|
||||
|
||||
// IWYU pragma: no_include <sec_api/string_s.h>
|
||||
|
||||
#if 0 && (KYTY_COMPILER == KYTY_COMPILER_MSVC || KYTY_COMPILER == KYTY_COMPILER_MINGW)
|
||||
String unDName(const String &mangled,
|
||||
void* (*memget)(size_t), void (*memfree)(void*),
|
||||
unsigned short int flags);
|
||||
#endif
|
||||
|
||||
namespace Kyty::Core {
|
||||
|
||||
#ifndef KYTY_FINAL
|
||||
#define DEBUG_MAP_ENABLED
|
||||
#endif
|
||||
|
||||
constexpr bool DBG_PRINTF = false;
|
||||
|
||||
static DebugMap* g_dbg_map = nullptr;
|
||||
static String* g_exe_name = nullptr;
|
||||
|
||||
struct DebugFunctionInfo
|
||||
{
|
||||
uintptr_t addr;
|
||||
uintptr_t length;
|
||||
String::Utf8 name;
|
||||
String::Utf8 obj;
|
||||
|
||||
bool operator<(const DebugFunctionInfo& f) const { return addr < f.addr; }
|
||||
};
|
||||
|
||||
struct DebugFunctionInfo2
|
||||
{
|
||||
uintptr_t addr;
|
||||
uintptr_t length;
|
||||
const char* name;
|
||||
const char* obj;
|
||||
|
||||
bool operator<(const DebugFunctionInfo2& f) const { return addr < f.addr; }
|
||||
};
|
||||
|
||||
using DebugMapType = Hashmap<uintptr_t, uint32_t>;
|
||||
using DebugDataType = Vector<DebugFunctionInfo>;
|
||||
using DebugDataType2 = Vector<DebugFunctionInfo2>;
|
||||
|
||||
struct DebugMapPrivate
|
||||
{
|
||||
DebugMapPrivate() = default;
|
||||
~DebugMapPrivate()
|
||||
{
|
||||
if (buf != nullptr)
|
||||
{
|
||||
DeleteArray(buf);
|
||||
}
|
||||
}
|
||||
|
||||
void FixBaseAddress();
|
||||
|
||||
static const DebugFunctionInfo* FindFunc(DebugMap* map, uintptr_t addr);
|
||||
|
||||
KYTY_CLASS_NO_COPY(DebugMapPrivate)
|
||||
|
||||
DebugDataType data;
|
||||
DebugDataType2 data2;
|
||||
DebugMapType map;
|
||||
char* buf {nullptr};
|
||||
};
|
||||
|
||||
static void exception_filter(void* /*addr*/)
|
||||
{
|
||||
EXIT("Exception!!!");
|
||||
}
|
||||
|
||||
void core_debug_init(const char* app_name)
|
||||
{
|
||||
sys_set_exception_filter(exception_filter);
|
||||
|
||||
g_exe_name = new String(String::FromUtf8(app_name));
|
||||
g_dbg_map = new DebugMap;
|
||||
// g_dbg_map->LoadMap();
|
||||
g_dbg_map->LoadCsv();
|
||||
}
|
||||
|
||||
DebugMap::DebugMap(): m_p(new DebugMapPrivate) {}
|
||||
|
||||
void DebugMap::LoadMap()
|
||||
{
|
||||
#ifdef DEBUG_MAP_ENABLED
|
||||
printf("exe_name = %s\n", g_exe_name->utf8_str().GetData());
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
String linker = Debug::GetLinker();
|
||||
String map_file =
|
||||
g_exe_name->FilenameWithoutExtension() + U"_" + Debug::GetCompiler() + U"_" + linker + U"_" + Debug::GetBitness() + U".map";
|
||||
|
||||
if (linker == U"ld")
|
||||
{
|
||||
LoadGnuLd(map_file, KYTY_BITNESS);
|
||||
} else if (linker == U"link" || linker == U"lld_link")
|
||||
{
|
||||
LoadMsvcLink(map_file, KYTY_BITNESS);
|
||||
} else if (linker == U"lld")
|
||||
{
|
||||
LoadLlvmLld(map_file, KYTY_BITNESS);
|
||||
} else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#elif KYTY_PLATFORM == KYTY_PLATFORM_ANDROID
|
||||
|
||||
#if KYTY_ABI == KYTY_ABI_ARMEABI
|
||||
LoadGnuLd(U"armeabi/fc_android.map", 32);
|
||||
#elif KYTY_ABI == KYTY_ABI_ARM64_V8A
|
||||
LoadGnuLd(U"arm64-v8a/fc_android.map", 64);
|
||||
#elif KYTY_ABI == KYTY_ABI_ARMEABI_V7A
|
||||
LoadGnuLd(U"armeabi-v7a/fc_android.map", 32);
|
||||
#elif KYTY_ABI == KYTY_ABI_MIPS
|
||||
LoadGnuLd(U"mips/fc_android.map", 64);
|
||||
#elif KYTY_ABI == KYTY_ABI_MIPS64
|
||||
LoadGnuLd(U"mips64/fc_android.map", 64);
|
||||
#elif KYTY_ABI == KYTY_ABI_X86
|
||||
LoadGnuLd(U"x86/fc_android.map", 32);
|
||||
#elif KYTY_ABI == KYTY_ABI_X86_64
|
||||
LoadGnuLd(U"x86_64/fc_android.map", 64);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
DumpMap(map_file.FilenameWithoutExtension() + U".csv");
|
||||
|
||||
if (linker == U"lld")
|
||||
{
|
||||
m_p->FixBaseAddress();
|
||||
}
|
||||
|
||||
#elif KYTY_PLATFORM == KYTY_PLATFORM_ANDROID
|
||||
DumpMap(U"_map.csv");
|
||||
#endif
|
||||
|
||||
// EXIT("1");
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void DebugMap::LoadCsv()
|
||||
{
|
||||
#ifdef DEBUG_MAP_ENABLED
|
||||
|
||||
printf("exe_name = %s\n", g_exe_name->utf8_str().GetData());
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
String linker = Debug::GetLinker();
|
||||
String map_file =
|
||||
g_exe_name->FilenameWithoutExtension() + U"_" + Debug::GetCompiler() + U"_" + linker + U"_" + Debug::GetBitness() + U".csv";
|
||||
|
||||
LoadCsv(map_file);
|
||||
|
||||
if (linker == U"lld")
|
||||
{
|
||||
m_p->FixBaseAddress();
|
||||
}
|
||||
|
||||
#elif KYTY_PLATFORM == KYTY_PLATFORM_ANDROID
|
||||
|
||||
#if KYTY_ABI == KYTY_ABI_ARMEABI
|
||||
LoadCsv(U"armeabi/fc_android.csv");
|
||||
#elif KYTY_ABI == KYTY_ABI_ARM64_V8A
|
||||
LoadCsv(U"arm64-v8a/fc_android.csv");
|
||||
#elif KYTY_ABI == KYTY_ABI_ARMEABI_V7A
|
||||
LoadCsv(U"armeabi-v7a/fc_android.csv");
|
||||
#elif KYTY_ABI == KYTY_ABI_MIPS
|
||||
LoadCsv(U"mips/fc_android.csv");
|
||||
#elif KYTY_ABI == KYTY_ABI_MIPS64
|
||||
LoadCsv(U"mips64/fc_android.csv");
|
||||
#elif KYTY_ABI == KYTY_ABI_X86
|
||||
LoadCsv(U"x86/fc_android.csv");
|
||||
#elif KYTY_ABI == KYTY_ABI_X86_64
|
||||
LoadCsv(U"x86_64/fc_android.csv");
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
// EXIT("1");
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
DebugMap::~DebugMap()
|
||||
{
|
||||
Delete(m_p);
|
||||
}
|
||||
|
||||
KYTY_ARRAY_DEFINE_SWAP(DebugMapSortSwapFunc, DebugFunctionInfo)
|
||||
{
|
||||
DebugMapType& map = *static_cast<DebugMapType*>(arg);
|
||||
map[array[i].addr] = j;
|
||||
map[array[j].addr] = i;
|
||||
|
||||
// DebugMap::DataType &data = *(DebugMap::DataType*)arg;
|
||||
//
|
||||
// printf("swap %d, %d\n", i, j);
|
||||
// for(uint32_t ii = 0; ii < data.Size(); ii++)
|
||||
// {
|
||||
// const DebugMap::FunctionInfo& f = data.At(ii);
|
||||
//
|
||||
// printf("\t0x%016I64x;%I64u;%s;%s\n", f.addr, f.length, f.name.utf8_str().GetData(), f.obj.utf8_str().GetData());
|
||||
// }
|
||||
|
||||
DebugFunctionInfo t = array[i];
|
||||
array[i] = array[j];
|
||||
array[j] = t;
|
||||
}
|
||||
|
||||
void DebugMap::LoadMsvcLink(const String& name, int mode)
|
||||
{
|
||||
File pf(name, File::Mode::Read);
|
||||
if (pf.IsInvalid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto* buf = new uint8_t[pf.Size()];
|
||||
pf.Read(buf, pf.Size());
|
||||
File f;
|
||||
f.OpenInMem(buf, pf.Size());
|
||||
f.SetEncoding(File::Encoding::Utf8);
|
||||
|
||||
pf.Close();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (f.IsEOF())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
String s = f.ReadLine();
|
||||
s = s.RemoveChar(U'\n');
|
||||
|
||||
StringList list = s.Split(U" ");
|
||||
|
||||
if (list.Size() >= 5 && list[3] == U"f")
|
||||
{
|
||||
EXIT_IF(!((list.Size() == 5 && list[3] == U"f") || (list.Size() == 6 && list[3] == U"f" && list[4] == U"i")));
|
||||
|
||||
uintptr_t addr = (mode == 32 ? list[2].ToUint32(16) : list[2].ToUint64(16));
|
||||
const String func = list.At(1);
|
||||
const String obj = list.Size() == 5 ? list[4] : list[5];
|
||||
|
||||
if (DBG_PRINTF)
|
||||
{
|
||||
printf("%016" PRIx64 "; %s; %s\n", static_cast<uint64_t>(addr), func.utf8_str().GetData(), obj.utf8_str().GetData());
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
DebugFunctionInfo inf = {addr, 0, func.utf8_str(), obj.utf8_str()};
|
||||
|
||||
if (m_p->map.Contains(addr))
|
||||
{
|
||||
String name1(m_p->data.At(m_p->map[addr]).name);
|
||||
if (name1.StartsWith(U"_"))
|
||||
{
|
||||
name1 = name1.Mid(1);
|
||||
}
|
||||
// EXIT_IF(data.At(map[addr]).name != func_name);
|
||||
if (name1 != func && !name1.ContainsStr(func) && !func.ContainsStr(name1))
|
||||
{
|
||||
if (DBG_PRINTF)
|
||||
{
|
||||
printf("warning: name1: %s, name2: %s\n", name1.utf8_str().GetData(), func.utf8_str().GetData());
|
||||
}
|
||||
// exit(1);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// if (mem_alloc_obj.Contains(inf.obj, String::CASE_INSENSITIVE)
|
||||
// && func.ContainsAny(mem_alloc_names))
|
||||
// {
|
||||
// inf.is_mem_alloc = true;
|
||||
// }
|
||||
|
||||
m_p->data.Add(inf);
|
||||
m_p->map.Put(addr, m_p->data.Size() - 1);
|
||||
|
||||
} else
|
||||
{
|
||||
EXIT_IF(list.Size() >= 5 && list[0].ContainsStr(U":") && list[3] != U"f");
|
||||
}
|
||||
}
|
||||
|
||||
f.Close();
|
||||
|
||||
DeleteArray(buf);
|
||||
|
||||
m_p->data.Sort(DebugMapSortSwapFunc, &m_p->map);
|
||||
|
||||
for (uint32_t i = 0; i < m_p->data.Size(); i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
m_p->data[i - 1].length = m_p->data[i].addr - m_p->data[i - 1].addr;
|
||||
}
|
||||
|
||||
// UNDNAME_COMPLETE
|
||||
#if 0 && (KYTY_COMPILER == KYTY_COMPILER_MSVC || KYTY_COMPILER == KYTY_COMPILER_MINGW)
|
||||
if (String(data[i].name).ContainsStr("?"))
|
||||
{
|
||||
String n = String(data[i].name).Mid(String(data[i].name).FindIndex("?"));
|
||||
//char name[1024*16];
|
||||
//char name_all[1024*16];
|
||||
//UnDecorateSymbolName(data[i].name.utf8_str().GetData(), name, sizeof(name) - 1, UNDNAME_COMPLETE | UNDNAME_32_BIT_DECODE | UNDNAME_TYPE_ONLY);
|
||||
|
||||
String name = unDName(n, malloc, free, 0x1000);
|
||||
String name_all = unDName(n, malloc, free, 0);
|
||||
|
||||
//UnDecorateSymbolName(n.utf8_str().GetData(), name, sizeof(name) - 1, 0x1000);
|
||||
//UnDecorateSymbolName(n.utf8_str().GetData(), name_all, sizeof(name_all) - 1, 0);
|
||||
|
||||
if (name.At(0) == '?' || name_all.At(0) == '?')
|
||||
{
|
||||
n = n.Left(n.FindLastIndex("$"));
|
||||
|
||||
name = unDName(n, malloc, free, 0x1000);
|
||||
name_all = unDName(n, malloc, free, 0);
|
||||
|
||||
//UnDecorateSymbolName(n.utf8_str().GetData(), name, sizeof(name) - 1, 0x1000);
|
||||
//UnDecorateSymbolName(n.utf8_str().GetData(), name_all, sizeof(name_all) - 1, 0);
|
||||
}
|
||||
|
||||
//String name_str = String(name);
|
||||
//String name_all_str = String(name_all);
|
||||
|
||||
//if (name_all_str.Contains(name_str))
|
||||
{
|
||||
uint32_t first = name_all.FindIndex("(");
|
||||
uint32_t last = name_all.FindLastIndex(")");
|
||||
//printf("%08x, %u, %u, %s, %s, %s\n", (uint32_t)data[i].addr, first, last, name_str.utf8_str().GetData(), name_all_str.utf8_str().GetData(), n.utf8_str().GetData());
|
||||
data[i].name = (name + name_all.Mid(first, last - first + 1)).utf8_str();
|
||||
}// else
|
||||
//{
|
||||
// data[i].name = String(name_all);
|
||||
//}
|
||||
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void DebugMap::LoadLlvmLld(const String& name, int bitness)
|
||||
{
|
||||
if (bitness != 64)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
File pf(name, File::Mode::Read);
|
||||
if (pf.IsInvalid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto* buf = new uint8_t[pf.Size()];
|
||||
pf.Read(buf, pf.Size());
|
||||
File f;
|
||||
f.OpenInMem(buf, pf.Size());
|
||||
f.SetEncoding(File::Encoding::Utf8);
|
||||
|
||||
pf.Close();
|
||||
|
||||
String header = f.ReadLine().RemoveChar(U'\n');
|
||||
String in;
|
||||
|
||||
if (header == U"Address Size Align Out In Symbol")
|
||||
{
|
||||
uint32_t prev_addr = 0;
|
||||
for (;;)
|
||||
{
|
||||
if (f.IsEOF())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
String s = f.ReadLine().RemoveChar(U'\n');
|
||||
uint32_t address = 0;
|
||||
|
||||
if (s.At(8) == U' ')
|
||||
{
|
||||
address = s.Mid(0, 8).ToUint32(16);
|
||||
} else
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (address != prev_addr)
|
||||
{
|
||||
if (s.At(24) != U' ')
|
||||
{
|
||||
if (!s.Mid(24).StartsWith(U".text"))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (s.At(32) != U' ')
|
||||
{
|
||||
in = s.Mid(32);
|
||||
in = in.FixFilenameSlash().FilenameWithoutDirectory();
|
||||
in = in.Mid(0, in.FindIndex(U':'));
|
||||
} else
|
||||
{
|
||||
String sym = s.Mid(40);
|
||||
|
||||
DebugFunctionInfo inf = {address, 0, sym.utf8_str(), in.utf8_str()};
|
||||
|
||||
m_p->data.Add(inf);
|
||||
m_p->map.Put(address, m_p->data.Size() - 1);
|
||||
|
||||
prev_addr = address;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f.Close();
|
||||
|
||||
DeleteArray(buf);
|
||||
|
||||
m_p->data.Sort(DebugMapSortSwapFunc, &m_p->map);
|
||||
|
||||
for (uint32_t i = 0; i < m_p->data.Size(); i++)
|
||||
{
|
||||
if (i > 0 && m_p->data[i - 1].length == 0)
|
||||
{
|
||||
m_p->data[i - 1].length = m_p->data[i].addr - m_p->data[i - 1].addr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void DebugMap::LoadGnuLd(const String& name, int bitness)
|
||||
{
|
||||
File pf(name, File::Mode::Read);
|
||||
if (pf.IsInvalid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto* buf = new uint8_t[pf.Size()];
|
||||
pf.Read(buf, pf.Size());
|
||||
File f;
|
||||
f.OpenInMem(buf, pf.Size());
|
||||
f.SetEncoding(File::Encoding::Utf8);
|
||||
|
||||
pf.Close();
|
||||
|
||||
int mode = 0;
|
||||
|
||||
String object_name;
|
||||
uintptr_t object_addr = 0;
|
||||
uintptr_t object_size = 0;
|
||||
bool object_start = false;
|
||||
bool object_unsorted = false;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (f.IsEOF())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
String s = f.ReadLine();
|
||||
s = s.RemoveChar(U'\n');
|
||||
|
||||
if (mode == 1)
|
||||
{
|
||||
if (s.StartsWith(U" "))
|
||||
{
|
||||
uintptr_t addr = bitness == 32 ? s.Mid(16, 10).ToUint32(16) : s.Mid(16, 18).ToUint64(16);
|
||||
String func_name = bitness == 32 ? s.Mid(42) : s.Mid(50);
|
||||
|
||||
if (DBG_PRINTF)
|
||||
{
|
||||
printf("\t%08" PRIx32 ", %s\n", static_cast<uint32_t>(addr), func_name.utf8_str().GetData());
|
||||
}
|
||||
|
||||
DebugFunctionInfo inf = {addr, object_size, func_name.utf8_str(), object_name.utf8_str()};
|
||||
|
||||
if (m_p->map.Contains(addr))
|
||||
{
|
||||
String name1(m_p->data.At(m_p->map[addr]).name);
|
||||
if (name1.StartsWith(U"_"))
|
||||
{
|
||||
name1 = name1.Mid(1);
|
||||
}
|
||||
// EXIT_IF(data.At(map[addr]).name != func_name);
|
||||
if (name1 != func_name && !name1.ContainsStr(func_name) && !func_name.ContainsStr(name1))
|
||||
{
|
||||
if (DBG_PRINTF)
|
||||
{
|
||||
printf("warning: name1: %s, name2: %s\n", name1.utf8_str().GetData(), func_name.utf8_str().GetData());
|
||||
}
|
||||
// exit(1);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (object_unsorted)
|
||||
{
|
||||
inf.length = 0;
|
||||
} else if (m_p->data.Size() > 0 && String(m_p->data.At(m_p->data.Size() - 1).obj) == object_name && !object_start)
|
||||
{
|
||||
// EXIT_IF(addr <= data.At(data.Size() - 1).addr);
|
||||
|
||||
if (addr <= m_p->data.At(m_p->data.Size() - 1).addr)
|
||||
{
|
||||
object_unsorted = true;
|
||||
m_p->data[m_p->data.Size() - 1].length = 0;
|
||||
inf.length = 0;
|
||||
} else
|
||||
{
|
||||
uintptr_t l = addr - m_p->data.At(m_p->data.Size() - 1).addr;
|
||||
m_p->data[m_p->data.Size() - 1].length = l;
|
||||
|
||||
EXIT_IF(object_size < l);
|
||||
|
||||
object_size -= l;
|
||||
inf.length -= l;
|
||||
}
|
||||
const DebugFunctionInfo& func = m_p->data.At(m_p->data.Size() - 1);
|
||||
if (DBG_PRINTF)
|
||||
{
|
||||
printf(" -- > %016" PRIx64 "; %" PRIu64 "; %s; %s\n", static_cast<uint64_t>(func.addr),
|
||||
static_cast<uint64_t>(func.length), func.name.GetData(), func.obj.GetData());
|
||||
}
|
||||
} else if (inf.addr != object_addr)
|
||||
{
|
||||
// EXIT_IF(inf.addr < object_addr);
|
||||
if (inf.addr < object_addr)
|
||||
{
|
||||
object_unsorted = true;
|
||||
inf.length = 0;
|
||||
} else
|
||||
{
|
||||
uintptr_t l = addr - object_addr;
|
||||
object_size -= l;
|
||||
inf.length -= l;
|
||||
}
|
||||
}
|
||||
|
||||
// if (mem_alloc_obj.Contains(inf.obj, String::CASE_INSENSITIVE)
|
||||
// && func_name.ContainsAny(mem_alloc_names))
|
||||
// {
|
||||
// inf.is_mem_alloc = true;
|
||||
// }
|
||||
|
||||
m_p->data.Add(inf);
|
||||
m_p->map.Put(addr, m_p->data.Size() - 1);
|
||||
|
||||
const DebugFunctionInfo& func = m_p->data.At(m_p->data.Size() - 1);
|
||||
if (DBG_PRINTF)
|
||||
{
|
||||
printf(" -- > %016" PRIx64 "; %" PRIu64 "; %s; %s\n", static_cast<uint64_t>(func.addr),
|
||||
static_cast<uint64_t>(func.length), func.name.GetData(), func.obj.GetData());
|
||||
}
|
||||
|
||||
object_start = false;
|
||||
|
||||
} else
|
||||
{
|
||||
mode = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (mode == 2)
|
||||
{
|
||||
StringList l = s.Split(U" ");
|
||||
|
||||
if (l.Size() == 3 && l[0].StartsWith(U"0x"))
|
||||
{
|
||||
s = U".text " + s;
|
||||
mode = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (mode == 0)
|
||||
{
|
||||
if (s.TrimLeft().StartsWith(U".text"))
|
||||
{
|
||||
s = s.RemoveChar(U'\n');
|
||||
StringList l = s.Split(U" ");
|
||||
if (l.Size() == 4 && l[0] == U".text")
|
||||
{
|
||||
mode = 1;
|
||||
|
||||
object_name = l[3].Mid(l[3].FindLastIndex(U"/") + 1);
|
||||
object_addr = l[1].ToUint64(16);
|
||||
object_size = l[2].ToUint64(16);
|
||||
|
||||
object_start = true;
|
||||
object_unsorted = false;
|
||||
|
||||
if (DBG_PRINTF)
|
||||
{
|
||||
printf("%08" PRIx32 ", %08" PRIx32 ", %s\n", static_cast<uint32_t>(object_addr), static_cast<uint32_t>(object_size),
|
||||
object_name.utf8_str().GetData());
|
||||
}
|
||||
} else if (l.Size() == 1 && l[0] != U".text")
|
||||
{
|
||||
mode = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f.Close();
|
||||
|
||||
DeleteArray(buf);
|
||||
|
||||
m_p->data.Sort(DebugMapSortSwapFunc, &m_p->map);
|
||||
// data.Sort();
|
||||
|
||||
for (uint32_t i = 0; i < m_p->data.Size(); i++)
|
||||
{
|
||||
if (i > 0 && m_p->data[i - 1].length == 0)
|
||||
{
|
||||
m_p->data[i - 1].length = m_p->data[i].addr - m_p->data[i - 1].addr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DebugMap::DumpMap(const String& name)
|
||||
{
|
||||
File::CreateDirectories(name.DirectoryWithoutFilename());
|
||||
|
||||
File file(name);
|
||||
|
||||
if (file.IsInvalid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t size = m_p->data.Size();
|
||||
|
||||
file.Printf("Addr;Size;Func;Obj\n");
|
||||
|
||||
for (uint32_t i = 0; i < size; i++)
|
||||
{
|
||||
const DebugFunctionInfo& f = m_p->data.At(i);
|
||||
|
||||
file.Printf("0x%016" PRIx64 ";%" PRIu64 ";%s;%s\n", static_cast<uint64_t>(f.addr), static_cast<uint64_t>(f.length),
|
||||
f.name.GetData(), f.obj.GetData());
|
||||
}
|
||||
|
||||
file.Close();
|
||||
}
|
||||
|
||||
static const DebugFunctionInfo* GetFunc(const DebugStack& s, int i)
|
||||
{
|
||||
uintptr_t addr = s.GetAddr(i);
|
||||
|
||||
if (g_dbg_map != nullptr)
|
||||
{
|
||||
return DebugMapPrivate::FindFunc(g_dbg_map, addr);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
static const T* find_info(const T* data, uintptr_t addr, uint32_t low, uint32_t high)
|
||||
{
|
||||
|
||||
// AGAIN:
|
||||
for (;;)
|
||||
{
|
||||
if (low == high)
|
||||
{
|
||||
const T* f = data + low;
|
||||
if (addr >= f->addr && addr < f->addr + f->length)
|
||||
{
|
||||
return f;
|
||||
}
|
||||
} else
|
||||
{
|
||||
uint32_t mid = (low + high) >> 1u;
|
||||
|
||||
const T* f = data + mid;
|
||||
if (addr < f->addr)
|
||||
{
|
||||
// return find_info(data, addr, low, mid);
|
||||
high = mid;
|
||||
// goto AGAIN;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (addr >= f->addr + f->length)
|
||||
{
|
||||
// return find_info(data, addr, mid + 1, high);
|
||||
low = mid + 1;
|
||||
// goto AGAIN;
|
||||
continue;
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void DebugMapPrivate::FixBaseAddress()
|
||||
{
|
||||
uintptr_t code_base = 0;
|
||||
size_t code_size = 0;
|
||||
sys_get_code_info(&code_base, &code_size);
|
||||
|
||||
if (data.Size() != 0)
|
||||
{
|
||||
for (auto& p: data)
|
||||
{
|
||||
p.addr += code_base;
|
||||
}
|
||||
}
|
||||
|
||||
if (data2.Size() != 0)
|
||||
{
|
||||
for (auto& p: data2)
|
||||
{
|
||||
p.addr += code_base;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const DebugFunctionInfo* DebugMapPrivate::FindFunc(DebugMap* map, uintptr_t addr)
|
||||
{
|
||||
static DebugFunctionInfo s {};
|
||||
|
||||
if (map->m_p->data.Size() == 0)
|
||||
{
|
||||
if (map->m_p->data2.Size() == 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const DebugFunctionInfo2* f2 = find_info(map->m_p->data2.GetData(), addr, 0, map->m_p->data2.Size() - 1);
|
||||
|
||||
if (f2 != nullptr)
|
||||
{
|
||||
s.addr = f2->addr;
|
||||
s.length = f2->length;
|
||||
s.name = String::FromUtf8(f2->name).utf8_str();
|
||||
s.obj = String::FromUtf8(f2->obj).utf8_str();
|
||||
|
||||
return &s;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return find_info(map->m_p->data.GetData(), addr, 0, map->m_p->data.Size() - 1);
|
||||
}
|
||||
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
static char* strtok_s(char* _Str, const char* _Delim, char** /*_Context*/)
|
||||
{
|
||||
return strtok(_Str, _Delim);
|
||||
}
|
||||
#endif
|
||||
|
||||
void DebugMap::LoadCsv(const String& name)
|
||||
{
|
||||
File pf(name, File::Mode::Read);
|
||||
if (pf.IsInvalid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t size = pf.Size();
|
||||
m_p->buf = new char[size + 1];
|
||||
pf.Read(m_p->buf, size);
|
||||
pf.Close();
|
||||
m_p->buf[size] = 0;
|
||||
char* context = nullptr;
|
||||
|
||||
[[maybe_unused]] char* p = strtok_s(m_p->buf, "\r\n;", &context);
|
||||
EXIT_IF(String::FromUtf8(p) != U"Addr");
|
||||
p = strtok_s(nullptr, "\r\n;", &context);
|
||||
EXIT_IF(String::FromUtf8(p) != U"Size");
|
||||
p = strtok_s(nullptr, "\r\n;", &context);
|
||||
EXIT_IF(String::FromUtf8(p) != U"Func");
|
||||
p = strtok_s(nullptr, "\r\n;", &context);
|
||||
EXIT_IF(String::FromUtf8(p) != U"Obj");
|
||||
|
||||
for (;;)
|
||||
{
|
||||
char* s1 = strtok_s(nullptr, "\r\n;", &context);
|
||||
char* s2 = strtok_s(nullptr, "\r\n;", &context);
|
||||
char* s3 = strtok_s(nullptr, "\r\n;", &context);
|
||||
char* s4 = strtok_s(nullptr, "\r\n;", &context);
|
||||
|
||||
if ((s1 == nullptr) || (s2 == nullptr) || (s3 == nullptr) || (s4 == nullptr))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
DebugFunctionInfo2 inf = {static_cast<uintptr_t>(sys_strtoui64(s1 + 2, nullptr, 16)),
|
||||
static_cast<uintptr_t>(sys_strtoui64(s2, nullptr, 10)), s3, s4};
|
||||
|
||||
m_p->data2.Add(inf);
|
||||
}
|
||||
}
|
||||
|
||||
void DebugStack::Print(int from, bool with_name) const
|
||||
{
|
||||
for (int i = from; i < depth; i++)
|
||||
{
|
||||
const DebugFunctionInfo* f = with_name ? GetFunc(*this, i) : nullptr;
|
||||
if (sizeof(uintptr_t) == 4)
|
||||
{
|
||||
printf("[%d] %08" PRIx32 ", %08" PRIx32 ", %s, %s\n", i - from, static_cast<uint32_t>(GetAddr(i)),
|
||||
f != nullptr ? static_cast<uint32_t>(f->addr) : 0, f != nullptr ? f->obj.GetData() : "unknown",
|
||||
f != nullptr ? f->name.GetData() : "unknown");
|
||||
} else
|
||||
{
|
||||
printf("[%d] %016" PRIx64 ", %016" PRIx64 ", %s, %s\n", i - from, static_cast<uint64_t>(GetAddr(i)),
|
||||
f != nullptr ? static_cast<uint64_t>(f->addr) : 0, f != nullptr ? f->obj.GetData() : "unknown",
|
||||
f != nullptr ? f->name.GetData() : "unknown");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DebugStack::Trace(DebugStack* stack)
|
||||
{
|
||||
stack->depth = DEBUG_MAX_STACK_DEPTH;
|
||||
sys_stack_walk(stack->stack, &stack->depth);
|
||||
}
|
||||
|
||||
void DebugStack::PrintAndroid(int from, bool with_name) const
|
||||
{
|
||||
KYTY_LOGI("---stack---\n");
|
||||
for (int i = from; i < depth; i++)
|
||||
{
|
||||
const DebugFunctionInfo* f = with_name ? GetFunc(*this, i) : nullptr;
|
||||
if (sizeof(uintptr_t) == 4)
|
||||
{
|
||||
KYTY_LOGI("[%d] %08" PRIx32 ", %08" PRIx32 ", %s, %s\n", i - from, static_cast<uint32_t>(GetAddr(i)),
|
||||
f != nullptr ? static_cast<uint32_t>(f->addr) : 0, f != nullptr ? f->obj.GetData() : "unknown",
|
||||
f != nullptr ? f->name.GetData() : "unknown");
|
||||
} else
|
||||
{
|
||||
KYTY_LOGI("[%d] %016" PRIx64 ", %016" PRIx64 ", %s, %s\n", i - from, static_cast<uint64_t>(GetAddr(i)),
|
||||
f != nullptr ? static_cast<uint64_t>(f->addr) : 0, f != nullptr ? f->obj.GetData() : "unknown",
|
||||
f != nullptr ? f->name.GetData() : "unknown");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String Debug::GetCompiler()
|
||||
{
|
||||
#if KYTY_COMPILER == KYTY_COMPILER_MSVC
|
||||
return U"msvc";
|
||||
#elif KYTY_COMPILER == KYTY_COMPILER_CLANG
|
||||
return U"clang";
|
||||
#elif KYTY_COMPILER == KYTY_COMPILER_MINGW
|
||||
return U"mingw";
|
||||
#elif KYTY_COMPILER == KYTY_COMPILER_GCC
|
||||
return U"gcc";
|
||||
#else
|
||||
return U"????";
|
||||
#endif
|
||||
}
|
||||
|
||||
String Debug::GetLinker()
|
||||
{
|
||||
#if KYTY_LINKER == KYTY_LINKER_LD
|
||||
return U"ld";
|
||||
#elif KYTY_LINKER == KYTY_LINKER_LLD
|
||||
return U"lld";
|
||||
#elif KYTY_LINKER == KYTY_LINKER_LINK
|
||||
return U"link";
|
||||
#elif KYTY_LINKER == KYTY_LINKER_LLD_LINK
|
||||
return U"lld_link";
|
||||
#else
|
||||
return U"??";
|
||||
#endif
|
||||
}
|
||||
|
||||
String Debug::GetBitness()
|
||||
{
|
||||
#if KYTY_BITNESS == 32
|
||||
return U"32";
|
||||
#elif KYTY_BITNESS == 64
|
||||
return U"64";
|
||||
#else
|
||||
return U"??";
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Kyty::Core
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,507 @@
|
||||
#include "Kyty/Core/Hashmap.h"
|
||||
|
||||
#include "Kyty/Core/DbgAssert.h"
|
||||
#include "Kyty/Core/Hash.h"
|
||||
#include "Kyty/Core/SafeDelete.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace Kyty::Core {
|
||||
|
||||
constexpr uint32_t HASH_MAX_KEY_SIZE = 32;
|
||||
constexpr uint32_t HASH_MAX_VALUE_SIZE = 16;
|
||||
|
||||
#define KYTY_HASH_DEFINE_INT(type) \
|
||||
KYTY_HASH_DEFINE_CALC(type) \
|
||||
{ \
|
||||
switch (sizeof(type)) \
|
||||
{ \
|
||||
case 8: return hash64(uint64_t(*key)); \
|
||||
case 4: return hash32(uint32_t(*key)); \
|
||||
case 2: return hash16(uint16_t(*key)); \
|
||||
case 1: return hash8(uint8_t(*key)); \
|
||||
} \
|
||||
return hash((void*)key, sizeof(type)); \
|
||||
} \
|
||||
KYTY_HASH_DEFINE_EQUALS(type) { return (*key_a) == (*key_b); }
|
||||
|
||||
#define KYTY_HASH_DEFINE_PTR(type) \
|
||||
KYTY_HASH_DEFINE_CALC(type) \
|
||||
{ \
|
||||
switch (sizeof(type)) \
|
||||
{ \
|
||||
case 8: return hash64(uint64_t(*key)); \
|
||||
case 4: return hash32(uint32_t(uintptr_t(*key))); \
|
||||
} \
|
||||
return hash((void*)key, sizeof(type)); \
|
||||
} \
|
||||
KYTY_HASH_DEFINE_EQUALS(type) { return (*key_a) == (*key_b); }
|
||||
|
||||
KYTY_HASH_DEFINE_INT(int8_t);
|
||||
KYTY_HASH_DEFINE_INT(uint8_t);
|
||||
KYTY_HASH_DEFINE_INT(int16_t);
|
||||
KYTY_HASH_DEFINE_INT(uint16_t);
|
||||
KYTY_HASH_DEFINE_INT(int32_t);
|
||||
KYTY_HASH_DEFINE_INT(uint32_t);
|
||||
KYTY_HASH_DEFINE_INT(int64_t);
|
||||
KYTY_HASH_DEFINE_INT(uint64_t);
|
||||
KYTY_HASH_DEFINE_INT(char16_t);
|
||||
KYTY_HASH_DEFINE_INT(char32_t);
|
||||
KYTY_HASH_DEFINE_PTR(int8_t*);
|
||||
KYTY_HASH_DEFINE_PTR(uint8_t*);
|
||||
KYTY_HASH_DEFINE_PTR(int16_t*);
|
||||
KYTY_HASH_DEFINE_PTR(uint16_t*);
|
||||
KYTY_HASH_DEFINE_PTR(int32_t*);
|
||||
KYTY_HASH_DEFINE_PTR(uint32_t*);
|
||||
KYTY_HASH_DEFINE_PTR(int64_t*);
|
||||
KYTY_HASH_DEFINE_PTR(uint64_t*);
|
||||
KYTY_HASH_DEFINE_PTR(char16_t*);
|
||||
KYTY_HASH_DEFINE_PTR(char32_t*);
|
||||
KYTY_HASH_DEFINE_PTR(void*);
|
||||
|
||||
class HashmapPrivate
|
||||
{
|
||||
public:
|
||||
struct Entry
|
||||
{
|
||||
uint8_t key[HASH_MAX_KEY_SIZE];
|
||||
uint32_t hash;
|
||||
uint8_t value[HASH_MAX_VALUE_SIZE];
|
||||
Entry* next;
|
||||
};
|
||||
|
||||
KYTY_CLASS_NO_COPY(HashmapPrivate);
|
||||
|
||||
HashmapPrivate(uint32_t initial_capacity, uint32_t key_size, uint32_t value_size, hash_calc_func_t hash, hash_key_equals_func_t equals,
|
||||
hash_key_copy_func_t key_copy, hash_value_copy_func_t value_copy, hash_key_free_func_t key_free,
|
||||
hash_value_free_func_t value_free)
|
||||
: bucket_count_initial(initial_capacity), m_bucket_count(bucket_count_initial), size_max((m_bucket_count * 3) / 4),
|
||||
m_key_size(key_size), m_value_size(value_size), hash_func(hash), equals_func(equals), key_copy_func(key_copy),
|
||||
value_copy_func(value_copy), key_free_func(key_free), value_free_func(value_free)
|
||||
{
|
||||
EXIT_IF(hash == nullptr);
|
||||
EXIT_IF(equals == nullptr);
|
||||
EXIT_IF(key_copy == nullptr);
|
||||
EXIT_IF(value_copy == nullptr);
|
||||
EXIT_IF(key_free == nullptr);
|
||||
EXIT_IF(value_free == nullptr);
|
||||
EXIT_IF(initial_capacity & (initial_capacity - 1));
|
||||
EXIT_IF(key_size > HASH_MAX_KEY_SIZE);
|
||||
EXIT_IF(value_size > HASH_MAX_VALUE_SIZE);
|
||||
}
|
||||
|
||||
virtual ~HashmapPrivate() { Clear(); }
|
||||
|
||||
void Clear()
|
||||
{
|
||||
if (size > 0)
|
||||
{
|
||||
for (uint32_t i = 0; i < m_bucket_count; i++)
|
||||
{
|
||||
Entry* entry = buckets[i];
|
||||
while (entry != nullptr)
|
||||
{
|
||||
Entry* next = entry->next;
|
||||
value_free_func(entry->value);
|
||||
key_free_func(entry->key);
|
||||
Delete(entry);
|
||||
entry = next;
|
||||
}
|
||||
}
|
||||
DeleteArray(buckets);
|
||||
m_bucket_count = bucket_count_initial;
|
||||
size_max = (m_bucket_count * 3) / 4;
|
||||
buckets = nullptr;
|
||||
size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline uint32_t HashKey(const void* key) const
|
||||
{
|
||||
auto h = hash_func(key);
|
||||
return h;
|
||||
}
|
||||
|
||||
inline bool EqualKeys(const void* key_a, uint32_t hash_a, const void* key_b, uint32_t hash_b) const
|
||||
{
|
||||
if (key_a == key_b)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hash_a != hash_b)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return equals_func(key_a, key_b);
|
||||
}
|
||||
|
||||
static inline uint32_t CalcIndex(uint32_t bucket_count, uint32_t hash) { return hash & (bucket_count - 1); }
|
||||
|
||||
uint32_t Size() const { return size; }
|
||||
|
||||
Entry* CreateEntry(const void* key, uint32_t hash, const void* value) const
|
||||
{
|
||||
auto* entry = new Entry;
|
||||
key_copy_func(entry->key, key);
|
||||
entry->hash = hash;
|
||||
value_copy_func(entry->value, value);
|
||||
entry->next = nullptr;
|
||||
return entry;
|
||||
}
|
||||
|
||||
void ExpandIfNecessary()
|
||||
{
|
||||
if (size > size_max)
|
||||
{
|
||||
uint32_t new_bucket_count = m_bucket_count << 1u;
|
||||
auto** new_buckets = new Entry*[new_bucket_count];
|
||||
// NOLINTNEXTLINE(bugprone-sizeof-expression)
|
||||
std::memset(new_buckets, 0, sizeof(Entry*) * new_bucket_count);
|
||||
|
||||
// Move over existing entries.
|
||||
for (uint32_t i = 0; i < m_bucket_count; i++)
|
||||
{
|
||||
Entry* entry = buckets[i];
|
||||
|
||||
while (entry != nullptr)
|
||||
{
|
||||
Entry* next = entry->next;
|
||||
size_t index = CalcIndex(new_bucket_count, entry->hash);
|
||||
entry->next = new_buckets[index];
|
||||
new_buckets[index] = entry;
|
||||
entry = next;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy over internals.
|
||||
DeleteArray(buckets);
|
||||
buckets = new_buckets;
|
||||
m_bucket_count = new_bucket_count;
|
||||
size_max = (m_bucket_count * 3) / 4;
|
||||
}
|
||||
}
|
||||
|
||||
void Put(const void* key, const void* value)
|
||||
{
|
||||
uint32_t hash = HashKey(key);
|
||||
uint32_t index = CalcIndex(m_bucket_count, hash);
|
||||
|
||||
if (buckets == nullptr)
|
||||
{
|
||||
buckets = new Entry*[m_bucket_count];
|
||||
// NOLINTNEXTLINE(bugprone-sizeof-expression)
|
||||
std::memset(buckets, 0, sizeof(Entry*) * m_bucket_count);
|
||||
}
|
||||
|
||||
Entry** p = &(buckets[index]);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
Entry* current = *p;
|
||||
|
||||
// Add a new entry.
|
||||
if (current == nullptr)
|
||||
{
|
||||
*p = CreateEntry(key, hash, value);
|
||||
size++;
|
||||
ExpandIfNecessary();
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace existing entry.
|
||||
if (EqualKeys(current->key, current->hash, key, hash))
|
||||
{
|
||||
value_free_func(current->value);
|
||||
value_copy_func(current->value, value);
|
||||
return;
|
||||
}
|
||||
|
||||
// Move to next entry.
|
||||
p = ¤t->next;
|
||||
}
|
||||
}
|
||||
|
||||
const void* Get(const void* key) const
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint32_t hash = HashKey(key);
|
||||
uint32_t index = CalcIndex(m_bucket_count, hash);
|
||||
|
||||
Entry* entry = buckets[index];
|
||||
|
||||
while (entry != nullptr)
|
||||
{
|
||||
if (EqualKeys(entry->key, entry->hash, key, hash))
|
||||
{
|
||||
return entry->value;
|
||||
}
|
||||
|
||||
entry = entry->next;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* OperatorSqBr(const void* key, const void* default_value)
|
||||
{
|
||||
uint32_t hash = HashKey(key);
|
||||
uint32_t index = CalcIndex(m_bucket_count, hash);
|
||||
|
||||
if (buckets == nullptr)
|
||||
{
|
||||
buckets = new Entry*[m_bucket_count];
|
||||
// NOLINTNEXTLINE(bugprone-sizeof-expression)
|
||||
std::memset(buckets, 0, sizeof(Entry*) * m_bucket_count);
|
||||
}
|
||||
|
||||
Entry** p = &(buckets[index]);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
Entry* current = *p;
|
||||
|
||||
// Add a new entry.
|
||||
if (current == nullptr)
|
||||
{
|
||||
current = *p = CreateEntry(key, hash, default_value);
|
||||
size++;
|
||||
ExpandIfNecessary();
|
||||
return current->value;
|
||||
}
|
||||
|
||||
// Replace existing entry.
|
||||
if (EqualKeys(current->key, current->hash, key, hash))
|
||||
{
|
||||
return current->value;
|
||||
}
|
||||
|
||||
// Move to next entry.
|
||||
p = ¤t->next;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Remove(const void* key)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t hash = HashKey(key);
|
||||
uint32_t index = CalcIndex(m_bucket_count, hash);
|
||||
|
||||
// Pointer to the current entry.
|
||||
Entry** p = &(buckets[index]);
|
||||
Entry* current = nullptr;
|
||||
while ((current = *p) != nullptr)
|
||||
{
|
||||
if (EqualKeys(current->key, current->hash, key, hash))
|
||||
{
|
||||
key_free_func(current->key);
|
||||
value_free_func(current->value);
|
||||
*p = current->next;
|
||||
Delete(current);
|
||||
size--;
|
||||
if (size == 0)
|
||||
{
|
||||
DeleteArray(buckets);
|
||||
m_bucket_count = bucket_count_initial;
|
||||
size_max = (m_bucket_count * 3) / 4;
|
||||
buckets = nullptr;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
p = ¤t->next;
|
||||
}
|
||||
}
|
||||
|
||||
void Start(uint32_t start_from) const
|
||||
{
|
||||
loop_entry = nullptr;
|
||||
|
||||
if (size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (loop_index = start_from; loop_index < m_bucket_count; loop_index++)
|
||||
{
|
||||
loop_entry = buckets[loop_index];
|
||||
|
||||
if (loop_entry != nullptr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool End() const { return loop_entry == nullptr || loop_index >= m_bucket_count; }
|
||||
|
||||
void Next() const
|
||||
{
|
||||
EXIT_IF(loop_entry == nullptr);
|
||||
|
||||
loop_entry = loop_entry->next;
|
||||
|
||||
if (loop_entry == nullptr)
|
||||
{
|
||||
Start(loop_index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
const void* Value() const
|
||||
{
|
||||
EXIT_IF(loop_entry == nullptr);
|
||||
|
||||
return loop_entry->value;
|
||||
}
|
||||
|
||||
const void* Key() const
|
||||
{
|
||||
EXIT_IF(loop_entry == nullptr);
|
||||
|
||||
return loop_entry->key;
|
||||
}
|
||||
|
||||
void ForEach(hash_callback_func_t callback, void* arg) const
|
||||
{
|
||||
for (Start(0); !End(); Next())
|
||||
{
|
||||
if (!callback(Key(), Value(), arg))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t CollisionsCount() const
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t collisions = 0;
|
||||
for (uint32_t i = 0; i < m_bucket_count; i++)
|
||||
{
|
||||
Entry* entry = buckets[i];
|
||||
while (entry != nullptr)
|
||||
{
|
||||
if (entry->next != nullptr)
|
||||
{
|
||||
collisions++;
|
||||
}
|
||||
entry = entry->next;
|
||||
}
|
||||
}
|
||||
return collisions;
|
||||
}
|
||||
|
||||
mutable uint32_t loop_index = 0;
|
||||
mutable Entry* loop_entry = nullptr;
|
||||
Entry** buckets = nullptr;
|
||||
uint32_t bucket_count_initial;
|
||||
uint32_t m_bucket_count;
|
||||
uint32_t size = 0;
|
||||
uint32_t size_max;
|
||||
uint32_t m_key_size;
|
||||
uint32_t m_value_size;
|
||||
hash_calc_func_t hash_func;
|
||||
hash_key_equals_func_t equals_func;
|
||||
hash_key_copy_func_t key_copy_func;
|
||||
hash_value_copy_func_t value_copy_func;
|
||||
hash_key_free_func_t key_free_func;
|
||||
hash_value_free_func_t value_free_func;
|
||||
};
|
||||
|
||||
HashmapBase::HashmapBase(uint32_t key_size, uint32_t value_size, hash_calc_func_t hash, hash_key_equals_func_t equals,
|
||||
hash_key_copy_func_t key_copy, hash_value_copy_func_t value_copy, hash_key_free_func_t key_free,
|
||||
hash_value_free_func_t value_free)
|
||||
: m_p(new HashmapPrivate(8, key_size, value_size, hash, equals, key_copy, value_copy, key_free, value_free))
|
||||
{
|
||||
}
|
||||
|
||||
HashmapBase::~HashmapBase()
|
||||
{
|
||||
Delete(m_p);
|
||||
}
|
||||
|
||||
uint32_t HashmapBase::Size() const
|
||||
{
|
||||
return m_p->Size();
|
||||
}
|
||||
|
||||
void HashmapBase::Put(const void* key, const void* value)
|
||||
{
|
||||
m_p->Put(key, value);
|
||||
}
|
||||
|
||||
const void* HashmapBase::Get(const void* key) const
|
||||
{
|
||||
return m_p->Get(key);
|
||||
}
|
||||
|
||||
void HashmapBase::Remove(const void* key)
|
||||
{
|
||||
m_p->Remove(key);
|
||||
}
|
||||
|
||||
void HashmapBase::Start() const
|
||||
{
|
||||
m_p->Start(0);
|
||||
}
|
||||
|
||||
bool HashmapBase::End() const
|
||||
{
|
||||
return m_p->End();
|
||||
}
|
||||
|
||||
void HashmapBase::Next() const
|
||||
{
|
||||
m_p->Next();
|
||||
}
|
||||
|
||||
const void* HashmapBase::Value() const
|
||||
{
|
||||
return m_p->Value();
|
||||
}
|
||||
|
||||
const void* HashmapBase::Key() const
|
||||
{
|
||||
return m_p->Key();
|
||||
}
|
||||
|
||||
void HashmapBase::ForEach(hash_callback_func_t callback, void* arg) const
|
||||
{
|
||||
m_p->ForEach(callback, arg);
|
||||
}
|
||||
|
||||
void* HashmapBase::OperatorSquareBrackets(const void* key, const void* default_value)
|
||||
{
|
||||
void* r = m_p->OperatorSqBr(key, default_value);
|
||||
|
||||
EXIT_IF(r == nullptr);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void HashmapBase::Clear()
|
||||
{
|
||||
m_p->Clear();
|
||||
}
|
||||
|
||||
uint32_t HashmapBase::CollisionsCount() const
|
||||
{
|
||||
return m_p->CollisionsCount();
|
||||
}
|
||||
|
||||
} // namespace Kyty::Core
|
||||
@@ -0,0 +1,477 @@
|
||||
#include "Kyty/Core/JsonReader.h"
|
||||
|
||||
#include "Kyty/Core/DbgAssert.h"
|
||||
#include "Kyty/Core/SafeDelete.h"
|
||||
|
||||
namespace Kyty::Core {
|
||||
|
||||
static String* g_json_error = nullptr;
|
||||
static Json* g_null = nullptr;
|
||||
|
||||
static void set_error(const String& str)
|
||||
{
|
||||
if (g_json_error == nullptr)
|
||||
{
|
||||
g_json_error = new String;
|
||||
}
|
||||
*g_json_error = str;
|
||||
}
|
||||
|
||||
static const char32_t* skip(const char32_t* in)
|
||||
{
|
||||
if (in == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
while ((*in != 0u) && Char::IsSpace(*in))
|
||||
{
|
||||
in++;
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
static const char32_t* skip_number(const char32_t* in)
|
||||
{
|
||||
if (in == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
while ((*in != 0u) && (Char::IsDecimal(*in) || *in == U'-' || *in == U'+' || *in == U'e' || *in == U'E' || *in == U'.'))
|
||||
{
|
||||
in++;
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
const char32_t* Json::parse_value(const char32_t* value)
|
||||
{
|
||||
EXIT_IF(!value);
|
||||
|
||||
switch (*value)
|
||||
{
|
||||
case U'n':
|
||||
{
|
||||
if (Char::EqualAsciiN(value, "null", 4))
|
||||
{
|
||||
m_type = JsonNULL;
|
||||
return value + 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case U'f':
|
||||
{
|
||||
if (Char::EqualAsciiN(value, "false", 5))
|
||||
{
|
||||
m_type = JsonBool;
|
||||
return value + 5;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case U't':
|
||||
{
|
||||
if (Char::EqualAsciiN(value, "true", 4))
|
||||
{
|
||||
m_type = JsonBool;
|
||||
m_value_bool = true;
|
||||
m_value_int = 1;
|
||||
return value + 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case U'\"': return parse_string(value);
|
||||
case U'[': return parse_array(value);
|
||||
case U'{': return parse_object(value);
|
||||
case U'-':
|
||||
case U'0':
|
||||
case U'1':
|
||||
case U'2':
|
||||
case U'3':
|
||||
case U'4':
|
||||
case U'5':
|
||||
case U'6':
|
||||
case U'7':
|
||||
case U'8':
|
||||
case U'9': return parse_number(value);
|
||||
default: return value; break;
|
||||
}
|
||||
|
||||
set_error(value);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char32_t* Json::parse_array(const char32_t* value)
|
||||
{
|
||||
if (*value != U'[')
|
||||
{
|
||||
set_error(value);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
m_type = JsonArray;
|
||||
|
||||
value = skip(value + 1);
|
||||
|
||||
if (*value == U']')
|
||||
{
|
||||
return value + 1;
|
||||
}
|
||||
|
||||
Json* child = new Json();
|
||||
|
||||
value = skip(child->parse_value(skip(value)));
|
||||
|
||||
if (value == nullptr)
|
||||
{
|
||||
Delete(child);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
m_list.Add(child);
|
||||
|
||||
while (*value == U',')
|
||||
{
|
||||
child = new Json();
|
||||
|
||||
value = skip(child->parse_value(skip(value + 1)));
|
||||
|
||||
if (value == nullptr)
|
||||
{
|
||||
Delete(child);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
m_list.Add(child);
|
||||
}
|
||||
|
||||
if (*value == ']')
|
||||
{
|
||||
return value + 1;
|
||||
}
|
||||
|
||||
set_error(value);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char32_t* Json::parse_object(const char32_t* value)
|
||||
{
|
||||
if (*value != U'{')
|
||||
{
|
||||
set_error(value);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
m_type = JsonObject;
|
||||
|
||||
value = skip(value + 1);
|
||||
|
||||
if (*value == U'}')
|
||||
{
|
||||
return value + 1;
|
||||
}
|
||||
|
||||
Json* child = new Json();
|
||||
|
||||
value = skip(child->parse_value(skip(value)));
|
||||
|
||||
if (value == nullptr)
|
||||
{
|
||||
Delete(child);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
child->m_name = child->m_value_string;
|
||||
child->m_value_string = U"";
|
||||
|
||||
if (*value != U':')
|
||||
{
|
||||
Delete(child);
|
||||
set_error(value);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
value = skip(child->parse_value(skip(value + 1)));
|
||||
|
||||
if (value == nullptr)
|
||||
{
|
||||
Delete(child);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
m_list.Add(child);
|
||||
|
||||
while (*value == U',')
|
||||
{
|
||||
child = new Json();
|
||||
|
||||
value = skip(child->parse_value(skip(value + 1)));
|
||||
|
||||
if (value == nullptr)
|
||||
{
|
||||
Delete(child);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
child->m_name = child->m_value_string;
|
||||
child->m_value_string = U"";
|
||||
|
||||
if (*value != U':')
|
||||
{
|
||||
Delete(child);
|
||||
set_error(value);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
value = skip(child->parse_value(skip(value + 1)));
|
||||
|
||||
if (value == nullptr)
|
||||
{
|
||||
Delete(child);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
m_list.Add(child);
|
||||
}
|
||||
|
||||
if (*value == U'}')
|
||||
{
|
||||
return value + 1;
|
||||
}
|
||||
|
||||
set_error(value);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char32_t* Json::parse_number(const char32_t* value)
|
||||
{
|
||||
const char32_t* end = skip_number(value);
|
||||
|
||||
uint32_t len = end - value;
|
||||
|
||||
if (len != 0u)
|
||||
{
|
||||
String str(U' ', len);
|
||||
std::memcpy(str.GetData(), value, len * sizeof(char32_t));
|
||||
m_type = JsonNumber;
|
||||
m_value_double = str.ToDouble();
|
||||
m_value_int = static_cast<int64_t>(m_value_double);
|
||||
m_value_string = str;
|
||||
return end;
|
||||
}
|
||||
|
||||
set_error(value);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char32_t* Json::parse_string(const char32_t* str)
|
||||
{
|
||||
if (*str != U'\"')
|
||||
{
|
||||
set_error(str);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char32_t* ptr = str + 1;
|
||||
|
||||
uint32_t len = 0;
|
||||
for (;; len++, ptr++)
|
||||
{
|
||||
if (*ptr == U'\"' || *ptr == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (*ptr == U'\\')
|
||||
{
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
|
||||
String out(' ', len);
|
||||
|
||||
ptr = str + 1;
|
||||
char32_t* ptr2 = out.GetData();
|
||||
while (*ptr != '\"' && *ptr != 0)
|
||||
{
|
||||
if (*ptr != '\\')
|
||||
{
|
||||
*ptr2++ = *ptr++;
|
||||
} else
|
||||
{
|
||||
ptr++;
|
||||
switch (*ptr)
|
||||
{
|
||||
case U'b': *ptr2++ = U'\b'; break;
|
||||
case U'f': *ptr2++ = U'\f'; break;
|
||||
case U'n': *ptr2++ = U'\n'; break;
|
||||
case U'r': *ptr2++ = U'\r'; break;
|
||||
case U't': *ptr2++ = U'\t'; break;
|
||||
case U'u':
|
||||
set_error(ptr);
|
||||
return nullptr;
|
||||
break;
|
||||
default: *ptr2++ = *ptr; break;
|
||||
}
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
|
||||
if (*ptr == U'\"')
|
||||
{
|
||||
ptr++;
|
||||
}
|
||||
|
||||
len = ptr2 - out.GetDataConst();
|
||||
|
||||
m_value_string = out.Left(len);
|
||||
m_type = JsonString;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// Json::Json()
|
||||
//{
|
||||
// m_value_int = 0;
|
||||
// m_value_double = 0.0;
|
||||
// m_value_bool = false;
|
||||
// m_type = JsonNULL;
|
||||
//}
|
||||
|
||||
Json::~Json()
|
||||
{
|
||||
FOR (i, m_list)
|
||||
{
|
||||
Delete(m_list[i]);
|
||||
}
|
||||
}
|
||||
|
||||
const Json* Json::Create(const String& str)
|
||||
{
|
||||
Json* c = new Json();
|
||||
|
||||
const char32_t* value = c->parse_value(skip(str.GetDataConst()));
|
||||
|
||||
if (value == nullptr)
|
||||
{
|
||||
Delete(c);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
String Json::GetError()
|
||||
{
|
||||
if (g_json_error != nullptr)
|
||||
{
|
||||
return *g_json_error;
|
||||
}
|
||||
|
||||
return U"";
|
||||
}
|
||||
|
||||
void Json::Init()
|
||||
{
|
||||
if (g_null == nullptr)
|
||||
{
|
||||
g_null = new Json();
|
||||
}
|
||||
}
|
||||
|
||||
// const Json* Json::GetItem(const String& string) const
|
||||
const Json* Json::GetItem(const char* string) const
|
||||
{
|
||||
FOR (i, m_list)
|
||||
{
|
||||
const Json* n = m_list.At(i);
|
||||
if (n->m_name.EqualAsciiNoCase(string))
|
||||
{
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
Init();
|
||||
|
||||
return g_null;
|
||||
}
|
||||
|
||||
// String Json::GetString(const String& name, const String& default_value) const
|
||||
String Json::GetString(const char* name, const String& default_value) const
|
||||
{
|
||||
const Json* n = GetItem(name);
|
||||
if (!n->IsNull())
|
||||
{
|
||||
return n->m_value_string;
|
||||
}
|
||||
return default_value;
|
||||
}
|
||||
|
||||
String Json::GetString(const char* name) const
|
||||
{
|
||||
const Json* n = GetItem(name);
|
||||
if (!n->IsNull())
|
||||
{
|
||||
return n->m_value_string;
|
||||
}
|
||||
return U"";
|
||||
}
|
||||
|
||||
// double Json::GetFloat(const String& name, double default_value) const
|
||||
double Json::GetFloat(const char* name, double default_value) const
|
||||
{
|
||||
const Json* n = GetItem(name);
|
||||
if (!n->IsNull())
|
||||
{
|
||||
return n->m_value_double;
|
||||
}
|
||||
return default_value;
|
||||
}
|
||||
|
||||
// int64_t Json::GetInt(const String& name, int64_t default_value) const
|
||||
int64_t Json::GetInt(const char* name, int64_t default_value) const
|
||||
{
|
||||
const Json* n = GetItem(name);
|
||||
if (!n->IsNull())
|
||||
{
|
||||
return n->m_value_int;
|
||||
}
|
||||
return default_value;
|
||||
}
|
||||
|
||||
bool Json::GetBool(const char* name, bool default_value) const
|
||||
{
|
||||
const Json* n = GetItem(name);
|
||||
if (!n->IsNull())
|
||||
{
|
||||
return n->m_value_bool;
|
||||
}
|
||||
return default_value;
|
||||
}
|
||||
|
||||
StringList Json::DbgCheckList(const StringList& required, const StringList& optional) const
|
||||
{
|
||||
StringList errors;
|
||||
|
||||
FOR (i, required)
|
||||
{
|
||||
if (GetItem(required.At(i).C_Str())->m_name.IsEmpty())
|
||||
{
|
||||
errors.Add(U"missing: " + required.At(i));
|
||||
}
|
||||
}
|
||||
|
||||
FOR (i, m_list)
|
||||
{
|
||||
const Json* n = m_list.At(i);
|
||||
if (!n->m_name.IsEmpty() && !(required.Contains(n->m_name) || optional.Contains(n->m_name)))
|
||||
{
|
||||
errors.Add(U"unknown: " + n->m_name);
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
} // namespace Kyty::Core
|
||||
@@ -0,0 +1,342 @@
|
||||
#include "Kyty/Core/Language.h"
|
||||
|
||||
#include "Kyty/Core/DbgAssert.h"
|
||||
#include "Kyty/Core/Hashmap.h"
|
||||
#include "Kyty/Core/Vector.h"
|
||||
|
||||
namespace Kyty::Core {
|
||||
|
||||
static const char32_t* g_list_numeric = U"0123456789";
|
||||
static const char32_t* g_list_punctuation = U" !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
|
||||
static const char32_t* g_list_punctuation_2 = U"ªº¡¿";
|
||||
|
||||
static const char32_t* g_alphabet_english_1 = U"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
static const char32_t* g_alphabet_english_2 = U"abcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
static const char32_t* g_alphabet_russian_1 = U"АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯ";
|
||||
static const char32_t* g_alphabet_russian_2 = U"абвгдеёжзийклмнопрстуфхцчшщьыъэюя";
|
||||
|
||||
static const char32_t* g_alphabet_german_1 = U"ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜẞ";
|
||||
static const char32_t* g_alphabet_german_2 = U"abcdefghijklmnopqrstuvwxyzäöüß";
|
||||
|
||||
static const char32_t* g_alphabet_french_1 = U"ABCDEFGHIJKLMNOPQRSTUVWXYZÀÂÆÈÉÊËÎÏÔŒÙÛÜŸÇ";
|
||||
static const char32_t* g_alphabet_french_2 = U"abcdefghijklmnopqrstuvwxyzàâæèéêëîïôœùûüÿç";
|
||||
|
||||
static const char32_t* g_alphabet_italian_1 = U"ABCDEFGHILMNOPQRSTUVÀÈÉÌÍÏÒÓÙÚ";
|
||||
static const char32_t* g_alphabet_italian_2 = U"abcdefghilmnopqrstuvàèéìíïòóùú";
|
||||
|
||||
static const char32_t* g_alphabet_spanish_1 = U"ABCDEFGHIJKLMNÑOPQRSTUVWXYZÁÉÍÓÚÜ";
|
||||
static const char32_t* g_alphabet_spanish_2 = U"abcdefghijklmnñopqrstuvwxyzáéíóúü";
|
||||
|
||||
static const char32_t* g_alphabet_portuguese_1 = U"ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÉÊÍÒÓÔÕÚÜÇ";
|
||||
static const char32_t* g_alphabet_portuguese_2 = U"abcdefghijklmnopqrstuvwxyzàáâãéêíòóôõúüç";
|
||||
|
||||
static const char32_t* g_short_month_english[] = {U"Jan", U"Feb", U"Mar", U"Apr", U"May", U"Jun",
|
||||
U"Jul", U"Aug", U"Sep", U"Oct", U"Nov", U"Dec"};
|
||||
|
||||
static const char32_t* g_month_english[] = {U"January", U"February", U"March", U"April", U"May", U"June",
|
||||
U"July", U"August", U"September", U"October", U"November", U"December"};
|
||||
|
||||
static const char32_t* g_short_month_russian[] = {U"Янв", U"Фев", U"Мар", U"Апр", U"Май", U"Июн",
|
||||
U"Июл", U"Авг", U"Сен", U"Окт", U"Ноя", U"Дек"};
|
||||
|
||||
static const char32_t* g_month_russian[] = {U"Январь", U"Февраль", U"Март", U"Апрель", U"Май", U"Июнь",
|
||||
U"Июль", U"Август", U"Сентябрь", U"Октябрь", U"Ноябрь", U"Декабрь"};
|
||||
|
||||
static const char32_t* g_short_day_english[] = {U"Mon", U"Tue", U"Wed", U"Thu", U"Fri", U"Sat", U"Sun"};
|
||||
|
||||
static const char32_t* g_day_english[] = {U"Monday", U"Tuesday", U"Wednesday", U"Thursday", U"Friday", U"Saturday", U"Sunday"};
|
||||
|
||||
static const char32_t* g_short_day_russian[] = {U"Пнд", U"Втн", U"Срд", U"Чтв", U"Птн", U"Сбт", U"Вск"};
|
||||
|
||||
static const char32_t* g_day_russian[] = {U"Понедельник", U"Вторник", U"Среда", U"Четверг", U"Пятница", U"Суббота", U"Воскресенье"};
|
||||
|
||||
Hashmap<String, LanguageId>* g_lang_map = nullptr;
|
||||
|
||||
void Language::Init()
|
||||
{
|
||||
g_lang_map = new Hashmap<String, LanguageId>;
|
||||
|
||||
g_lang_map->Put(U"de", LanguageId::German);
|
||||
g_lang_map->Put(U"en", LanguageId::English);
|
||||
g_lang_map->Put(U"fr", LanguageId::French);
|
||||
g_lang_map->Put(U"it", LanguageId::Italian);
|
||||
g_lang_map->Put(U"pt", LanguageId::Portuguese);
|
||||
g_lang_map->Put(U"ru", LanguageId::Russian);
|
||||
g_lang_map->Put(U"es", LanguageId::Spanish);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static String string_remove_duplicates(const String& s)
|
||||
{
|
||||
Vector<char32_t> r;
|
||||
|
||||
for (auto ch: s)
|
||||
{
|
||||
if (!r.Contains(ch))
|
||||
{
|
||||
r.Add(ch);
|
||||
}
|
||||
}
|
||||
|
||||
r.Sort();
|
||||
|
||||
r.Add(U'\0');
|
||||
|
||||
return r.GetDataConst();
|
||||
}
|
||||
|
||||
String Language::GetLettersList(LanguageId lang_id)
|
||||
{
|
||||
String ret = U"";
|
||||
|
||||
switch (lang_id)
|
||||
{
|
||||
case LanguageId::English:
|
||||
ret += g_alphabet_english_1;
|
||||
ret += g_alphabet_english_2;
|
||||
break;
|
||||
|
||||
case LanguageId::Russian:
|
||||
ret += g_alphabet_russian_1;
|
||||
ret += g_alphabet_russian_2;
|
||||
break;
|
||||
|
||||
case LanguageId::German:
|
||||
ret += g_alphabet_german_1;
|
||||
ret += g_alphabet_german_2;
|
||||
break;
|
||||
|
||||
case LanguageId::French:
|
||||
ret += g_alphabet_french_1;
|
||||
ret += g_alphabet_french_2;
|
||||
break;
|
||||
|
||||
case LanguageId::Italian:
|
||||
ret += g_alphabet_italian_1;
|
||||
ret += g_alphabet_italian_2;
|
||||
break;
|
||||
|
||||
case LanguageId::Spanish:
|
||||
ret += g_alphabet_spanish_1;
|
||||
ret += g_alphabet_spanish_2;
|
||||
break;
|
||||
|
||||
case LanguageId::Portuguese:
|
||||
ret += g_alphabet_portuguese_1;
|
||||
ret += g_alphabet_portuguese_2;
|
||||
break;
|
||||
|
||||
case LanguageId::Unknown: EXIT("unknown language\n");
|
||||
}
|
||||
|
||||
return string_remove_duplicates(ret);
|
||||
}
|
||||
|
||||
String Language::GetLettersList(const String& id)
|
||||
{
|
||||
return GetLettersList(GetId(id));
|
||||
}
|
||||
|
||||
String Language::GetNumericList(const String& /*id*/)
|
||||
{
|
||||
String ret = U"";
|
||||
|
||||
ret += g_list_numeric;
|
||||
|
||||
return string_remove_duplicates(ret);
|
||||
}
|
||||
|
||||
String Language::GetPunctuationList(const String& id)
|
||||
{
|
||||
String ret = U"";
|
||||
|
||||
ret += g_list_punctuation;
|
||||
|
||||
if (GetId(id) == LanguageId::Spanish)
|
||||
{
|
||||
ret += g_list_punctuation_2;
|
||||
}
|
||||
|
||||
return string_remove_duplicates(ret);
|
||||
}
|
||||
|
||||
String Language::GetCharList(const String& id)
|
||||
{
|
||||
String ret = U"";
|
||||
|
||||
ret += g_list_numeric;
|
||||
ret += g_list_punctuation;
|
||||
|
||||
switch (GetId(id))
|
||||
{
|
||||
case LanguageId::English:
|
||||
ret += g_alphabet_english_1;
|
||||
ret += g_alphabet_english_2;
|
||||
break;
|
||||
|
||||
case LanguageId::Russian:
|
||||
ret += g_alphabet_russian_1;
|
||||
ret += g_alphabet_russian_2;
|
||||
break;
|
||||
|
||||
case LanguageId::German:
|
||||
ret += g_alphabet_german_1;
|
||||
ret += g_alphabet_german_2;
|
||||
break;
|
||||
|
||||
case LanguageId::French:
|
||||
ret += g_alphabet_french_1;
|
||||
ret += g_alphabet_french_2;
|
||||
break;
|
||||
|
||||
case LanguageId::Italian:
|
||||
ret += g_alphabet_italian_1;
|
||||
ret += g_alphabet_italian_2;
|
||||
break;
|
||||
|
||||
case LanguageId::Spanish:
|
||||
ret += g_list_punctuation_2;
|
||||
ret += g_alphabet_spanish_1;
|
||||
ret += g_alphabet_spanish_2;
|
||||
break;
|
||||
|
||||
case LanguageId::Portuguese:
|
||||
ret += g_alphabet_portuguese_1;
|
||||
ret += g_alphabet_portuguese_2;
|
||||
break;
|
||||
|
||||
case LanguageId::Unknown: EXIT("unknown language\n");
|
||||
}
|
||||
|
||||
return string_remove_duplicates(ret);
|
||||
}
|
||||
|
||||
String Language::GetCharListAll()
|
||||
{
|
||||
String ret = U"";
|
||||
|
||||
ret += g_list_numeric;
|
||||
ret += g_list_punctuation;
|
||||
ret += g_list_punctuation_2;
|
||||
ret += g_alphabet_english_1;
|
||||
ret += g_alphabet_english_2;
|
||||
ret += g_alphabet_russian_1;
|
||||
ret += g_alphabet_russian_2;
|
||||
ret += g_alphabet_german_1;
|
||||
ret += g_alphabet_german_2;
|
||||
ret += g_alphabet_french_1;
|
||||
ret += g_alphabet_french_2;
|
||||
ret += g_alphabet_italian_1;
|
||||
ret += g_alphabet_italian_2;
|
||||
ret += g_alphabet_spanish_1;
|
||||
ret += g_alphabet_spanish_2;
|
||||
ret += g_alphabet_portuguese_1;
|
||||
ret += g_alphabet_portuguese_2;
|
||||
|
||||
return string_remove_duplicates(ret);
|
||||
}
|
||||
|
||||
String Language::GetNameOfMonth(int month, LanguageId lang_id)
|
||||
{
|
||||
EXIT_IF(month < 1 || month > 12);
|
||||
|
||||
switch (lang_id)
|
||||
{
|
||||
case LanguageId::English: return g_month_english[month - 1]; break;
|
||||
|
||||
case LanguageId::Russian: return g_month_russian[month - 1]; break;
|
||||
|
||||
case LanguageId::Unknown:
|
||||
case LanguageId::German:
|
||||
case LanguageId::French:
|
||||
case LanguageId::Italian:
|
||||
case LanguageId::Portuguese:
|
||||
case LanguageId::Spanish: EXIT("unknown language\n");
|
||||
}
|
||||
|
||||
return U"";
|
||||
}
|
||||
|
||||
String Language::GetNameOfMonthShort(int month, LanguageId lang_id)
|
||||
{
|
||||
EXIT_IF(month < 1 || month > 12);
|
||||
|
||||
switch (lang_id)
|
||||
{
|
||||
case LanguageId::English: return g_short_month_english[month - 1]; break;
|
||||
|
||||
case LanguageId::Russian: return g_short_month_russian[month - 1]; break;
|
||||
|
||||
case LanguageId::Unknown:
|
||||
case LanguageId::German:
|
||||
case LanguageId::French:
|
||||
case LanguageId::Italian:
|
||||
case LanguageId::Portuguese:
|
||||
case LanguageId::Spanish: EXIT("unknown language\n");
|
||||
}
|
||||
|
||||
return U"";
|
||||
}
|
||||
|
||||
String Language::GetNameOfDay(int day, LanguageId lang_id)
|
||||
{
|
||||
EXIT_IF(day < 1 || day > 7);
|
||||
|
||||
switch (lang_id)
|
||||
{
|
||||
case LanguageId::English: return g_day_english[day - 1]; break;
|
||||
|
||||
case LanguageId::Russian: return g_day_russian[day - 1]; break;
|
||||
|
||||
case LanguageId::Unknown:
|
||||
case LanguageId::German:
|
||||
case LanguageId::French:
|
||||
case LanguageId::Italian:
|
||||
case LanguageId::Portuguese:
|
||||
case LanguageId::Spanish: EXIT("unknown language\n");
|
||||
}
|
||||
|
||||
return U"";
|
||||
}
|
||||
|
||||
LanguageId Language::GetId(const String& id)
|
||||
{
|
||||
return g_lang_map->Get(id, LanguageId::Unknown);
|
||||
}
|
||||
|
||||
StringList Language::GetLanguages()
|
||||
{
|
||||
StringList ret;
|
||||
|
||||
FOR_HASH (*g_lang_map)
|
||||
{
|
||||
ret.Add(g_lang_map->Key());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
String Language::GetNameOfDayShort(int day, LanguageId lang_id)
|
||||
{
|
||||
EXIT_IF(day < 1 || day > 7);
|
||||
|
||||
switch (lang_id)
|
||||
{
|
||||
case LanguageId::English: return g_short_day_english[day - 1]; break;
|
||||
|
||||
case LanguageId::Russian: return g_short_day_russian[day - 1]; break;
|
||||
|
||||
case LanguageId::Unknown:
|
||||
case LanguageId::German:
|
||||
case LanguageId::French:
|
||||
case LanguageId::Italian:
|
||||
case LanguageId::Portuguese:
|
||||
case LanguageId::Spanish: EXIT("unknown language\n");
|
||||
}
|
||||
|
||||
return U"";
|
||||
}
|
||||
|
||||
} // namespace Kyty::Core
|
||||
@@ -0,0 +1,619 @@
|
||||
#include "Kyty/Core/MemoryAlloc.h"
|
||||
|
||||
#include "Kyty/Core/Common.h"
|
||||
#include "Kyty/Core/DateTime.h" // IWYU pragma: keep
|
||||
#include "Kyty/Core/Debug.h" // IWYU pragma: keep
|
||||
#include "Kyty/Core/Hashmap.h" // IWYU pragma: keep
|
||||
#include "Kyty/Sys/SysHeap.h"
|
||||
#include "Kyty/Sys/SysSync.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <new>
|
||||
|
||||
namespace Kyty::Core {
|
||||
|
||||
#if !defined(KYTY_FINAL) && !defined(KYTY_SHARED_DLL)
|
||||
#define MEM_TRACKER
|
||||
#endif
|
||||
|
||||
#define MEM_ALLOC_ALIGNED
|
||||
|
||||
#ifdef MEM_ALLOC_ALIGNED
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_ANDROID
|
||||
constexpr int MEM_ALLOC_ALIGN = 8;
|
||||
#else
|
||||
constexpr int MEM_ALLOC_ALIGN = 16;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_BITNESS == 64
|
||||
[[maybe_unused]] constexpr int STACK_CHECK_FROM = 5;
|
||||
#elif KYTY_PLATFORM == KYTY_PLATFORM_ANDROID
|
||||
[[maybe_unused]] constexpr int STACK_CHECK_FROM = 4;
|
||||
#else
|
||||
[[maybe_unused]] constexpr int STACK_CHECK_FROM = 2;
|
||||
#endif
|
||||
|
||||
static SysCS* g_mem_cs = nullptr;
|
||||
static bool g_mem_initialized = false;
|
||||
static sys_heap_id_t g_default_heap = nullptr;
|
||||
static size_t g_mem_max_size = 0;
|
||||
|
||||
#ifdef MEM_TRACKER
|
||||
|
||||
using pattern_t = uint32_t;
|
||||
|
||||
constexpr size_t PATTERN_SIZE = (sizeof(pattern_t));
|
||||
constexpr size_t PATTERNS_NUM = 4;
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
|
||||
struct MemBlockInfoT
|
||||
{
|
||||
uintptr_t addr;
|
||||
size_t size;
|
||||
int state;
|
||||
DebugStack stack;
|
||||
pattern_t left_pattern;
|
||||
pattern_t right_pattern;
|
||||
};
|
||||
|
||||
thread_local int g_mem_depth = 0;
|
||||
thread_local bool g_mem_tracker_enabled = true;
|
||||
|
||||
static Hashmap<uintptr_t, MemBlockInfoT*>* g_mem_map = nullptr;
|
||||
static int g_mem_state = 0;
|
||||
static size_t g_total_allocated = 0;
|
||||
|
||||
#define KYTY_MDBG(str, ptr) \
|
||||
{ \
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
class MemLock
|
||||
{
|
||||
public:
|
||||
MemLock()
|
||||
{
|
||||
g_mem_cs->Enter();
|
||||
|
||||
#ifdef MEM_TRACKER
|
||||
g_mem_depth++;
|
||||
#endif
|
||||
}
|
||||
|
||||
~MemLock()
|
||||
{
|
||||
#ifdef MEM_TRACKER
|
||||
g_mem_depth--;
|
||||
#endif
|
||||
|
||||
g_mem_cs->Leave();
|
||||
}
|
||||
|
||||
KYTY_CLASS_NO_COPY(MemLock);
|
||||
|
||||
#ifdef MEM_TRACKER
|
||||
[[nodiscard]] bool IsRecursive() const { return g_mem_depth > 1; } // NOLINT(readability-convert-member-functions-to-static)
|
||||
#endif
|
||||
};
|
||||
|
||||
#if KYTY_COMPILER == KYTY_COMPILER_MSVC
|
||||
#pragma code_seg(push)
|
||||
#endif
|
||||
|
||||
#if KYTY_COMPILER == KYTY_COMPILER_MSVC
|
||||
#pragma code_seg(".mem_a")
|
||||
#endif
|
||||
|
||||
#if KYTY_COMPILER == KYTY_COMPILER_MSVC
|
||||
#pragma code_seg(".mem_b")
|
||||
#endif
|
||||
|
||||
#ifdef MEM_TRACKER
|
||||
|
||||
static const Array<pattern_t, 4> g_mem_patterns = {0xAAAAAAAA, 0xCCCCCCCC, 0x55555555, 0x33333333};
|
||||
|
||||
static int g_pattern_id = 0;
|
||||
|
||||
static pattern_t pattern_next()
|
||||
{
|
||||
return g_mem_patterns[g_pattern_id++ % g_mem_patterns.Size()];
|
||||
}
|
||||
|
||||
static void pattern_write(MemBlockInfoT* info)
|
||||
{
|
||||
auto* ptr = reinterpret_cast<uint8_t*>(info->addr);
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
(reinterpret_cast<pattern_t*>(ptr - PATTERN_SIZE * PATTERNS_NUM))[i] = info->left_pattern;
|
||||
(reinterpret_cast<pattern_t*>(ptr + info->size))[i] = info->right_pattern;
|
||||
}
|
||||
}
|
||||
|
||||
static bool pattern_check(MemBlockInfoT* info)
|
||||
{
|
||||
auto* ptr = reinterpret_cast<uint8_t*>(info->addr);
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
if ((reinterpret_cast<pattern_t*>(ptr - PATTERN_SIZE * PATTERNS_NUM))[i] != info->left_pattern ||
|
||||
(reinterpret_cast<pattern_t*>(ptr + info->size))[i] != info->right_pattern)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void mem_init()
|
||||
{
|
||||
if (g_mem_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
g_mem_initialized = true;
|
||||
|
||||
#ifdef MEM_TRACKER
|
||||
srand(DateTime::FromSystemUTC().GetTime().MsecTotal());
|
||||
g_pattern_id = static_cast<int>(rand() % g_mem_patterns.Size()); // NOLINT(cert-msc30-c,cert-msc50-cpp)
|
||||
#endif
|
||||
|
||||
#ifdef MEM_TRACKER
|
||||
g_mem_depth++;
|
||||
|
||||
#endif
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc,hicpp-no-malloc)
|
||||
g_mem_cs = new (std::malloc(sizeof(SysCS))) SysCS;
|
||||
g_mem_cs->Init();
|
||||
#ifdef MEM_TRACKER
|
||||
g_mem_map = new Hashmap<uintptr_t, MemBlockInfoT*>;
|
||||
#endif
|
||||
|
||||
g_default_heap = sys_heap_create();
|
||||
|
||||
#ifdef MEM_TRACKER
|
||||
g_mem_depth--;
|
||||
#endif
|
||||
}
|
||||
|
||||
void core_memory_init()
|
||||
{
|
||||
mem_init();
|
||||
}
|
||||
|
||||
void* mem_alloc_check_alignment(void* ptr)
|
||||
{
|
||||
#ifdef MEM_ALLOC_ALIGNED
|
||||
if ((uintptr_t(ptr) & uintptr_t(MEM_ALLOC_ALIGN - 1)) != 0u)
|
||||
{
|
||||
EXIT("mem alloc not aligned!\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void* mem_alloc(size_t size)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
EXIT("size == 0\n");
|
||||
}
|
||||
|
||||
if ((g_mem_max_size != 0u) && size > g_mem_max_size)
|
||||
{
|
||||
EXIT("mem_alloc(): size(%" PRIu64 ") > max(%" PRIu64 ")\n", uint64_t(size), uint64_t(g_mem_max_size));
|
||||
}
|
||||
|
||||
mem_init();
|
||||
MemLock lock;
|
||||
|
||||
#ifdef MEM_TRACKER
|
||||
DebugStack stack;
|
||||
DebugStack::Trace(&stack);
|
||||
|
||||
if (lock.IsRecursive() || !g_mem_tracker_enabled)
|
||||
{
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc,hicpp-no-malloc)
|
||||
void* r = std::malloc(size);
|
||||
KYTY_MDBG("- std alloc -", r);
|
||||
return mem_alloc_check_alignment(r);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MEM_TRACKER
|
||||
auto* ptr_p = static_cast<pattern_t*>(sys_heap_alloc(g_default_heap, size + PATTERN_SIZE * PATTERNS_NUM * 2));
|
||||
void* ptr = ptr_p + PATTERNS_NUM;
|
||||
#else
|
||||
void* ptr = sys_heap_alloc(g_default_heap, size);
|
||||
#endif
|
||||
|
||||
if (ptr == nullptr)
|
||||
{
|
||||
EXIT("mem_alloc(): can't alloc %" PRIu64 " bytes\n", uint64_t(size));
|
||||
}
|
||||
|
||||
#ifdef MEM_TRACKER
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc,hicpp-no-malloc)
|
||||
auto* info = static_cast<MemBlockInfoT*>(std::malloc(sizeof(MemBlockInfoT)));
|
||||
|
||||
info->addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
info->size = size;
|
||||
info->state = g_mem_state;
|
||||
stack.CopyTo(&info->stack);
|
||||
|
||||
info->left_pattern = pattern_next();
|
||||
info->right_pattern = pattern_next();
|
||||
|
||||
pattern_write(info);
|
||||
|
||||
g_mem_map->Put(reinterpret_cast<uintptr_t>(ptr), info);
|
||||
|
||||
g_total_allocated += size;
|
||||
|
||||
KYTY_MDBG("- mem_alloc -", ptr);
|
||||
|
||||
#endif
|
||||
|
||||
return mem_alloc_check_alignment(ptr);
|
||||
}
|
||||
|
||||
void* mem_realloc(void* ptr, size_t size)
|
||||
{
|
||||
EXIT_IF(size == 0);
|
||||
|
||||
if ((g_mem_max_size != 0u) && size > g_mem_max_size)
|
||||
{
|
||||
EXIT("mem_realloc(): size(%" PRIu64 ") > max(%" PRIu64 ")\n", uint64_t(size), uint64_t(g_mem_max_size));
|
||||
}
|
||||
|
||||
mem_init();
|
||||
MemLock lock;
|
||||
|
||||
#ifdef MEM_TRACKER
|
||||
DebugStack stack;
|
||||
DebugStack::Trace(&stack);
|
||||
|
||||
if (lock.IsRecursive() || !g_mem_tracker_enabled)
|
||||
{
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc,hicpp-no-malloc)
|
||||
void* ptr2 = std::realloc(ptr, size);
|
||||
KYTY_MDBG("- std realloc old -", ptr);
|
||||
KYTY_MDBG("- std realloc new -", ptr2);
|
||||
return mem_alloc_check_alignment(ptr2);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MEM_TRACKER
|
||||
auto* ptr2_b = static_cast<pattern_t*>(sys_heap_realloc(
|
||||
g_default_heap, ptr != nullptr ? (static_cast<pattern_t*>(ptr)) - PATTERNS_NUM : nullptr, size + PATTERN_SIZE * PATTERNS_NUM * 2));
|
||||
void* ptr2 = ptr2_b + PATTERNS_NUM;
|
||||
#else
|
||||
void* ptr2 = sys_heap_realloc(g_default_heap, ptr, size);
|
||||
#endif
|
||||
|
||||
if (ptr2 == nullptr)
|
||||
{
|
||||
EXIT("mem_realloc(): can't alloc %" PRIu64 " bytes\n", uint64_t(size));
|
||||
}
|
||||
|
||||
#ifdef MEM_TRACKER
|
||||
if (ptr != nullptr)
|
||||
{
|
||||
MemBlockInfoT* const* info_p = g_mem_map->Find(reinterpret_cast<uintptr_t>(ptr));
|
||||
|
||||
// EXIT_IF(info == 0);
|
||||
if (info_p == nullptr)
|
||||
{
|
||||
printf("error %016" PRIx64 "\n", reinterpret_cast<uint64_t>(ptr));
|
||||
EXIT_IF(info_p == nullptr);
|
||||
}
|
||||
|
||||
MemBlockInfoT* info = *info_p;
|
||||
|
||||
g_total_allocated -= info->size;
|
||||
g_total_allocated += size;
|
||||
|
||||
if (ptr == ptr2)
|
||||
{
|
||||
info->size = size;
|
||||
info->state = g_mem_state;
|
||||
stack.CopyTo(&info->stack);
|
||||
} else
|
||||
{
|
||||
info->addr = reinterpret_cast<uintptr_t>(ptr2);
|
||||
info->size = size;
|
||||
info->state = g_mem_state;
|
||||
stack.CopyTo(&info->stack);
|
||||
g_mem_map->Put(reinterpret_cast<uintptr_t>(ptr2), info);
|
||||
g_mem_map->Remove(reinterpret_cast<uintptr_t>(ptr));
|
||||
}
|
||||
|
||||
pattern_write(info);
|
||||
} else
|
||||
{
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc,hicpp-no-malloc)
|
||||
auto* info = static_cast<MemBlockInfoT*>(std::malloc(sizeof(MemBlockInfoT)));
|
||||
info->addr = reinterpret_cast<uintptr_t>(ptr2);
|
||||
info->size = size;
|
||||
info->state = g_mem_state;
|
||||
stack.CopyTo(&info->stack);
|
||||
|
||||
info->left_pattern = pattern_next();
|
||||
info->right_pattern = pattern_next();
|
||||
|
||||
pattern_write(info);
|
||||
|
||||
g_total_allocated += size;
|
||||
|
||||
g_mem_map->Put(reinterpret_cast<uintptr_t>(ptr2), info);
|
||||
}
|
||||
|
||||
KYTY_MDBG("- mem_realloc old -", ptr);
|
||||
KYTY_MDBG("- mem_realloc new -", ptr2);
|
||||
#endif
|
||||
|
||||
// g_mem_cs->Leave();
|
||||
|
||||
return mem_alloc_check_alignment(ptr2);
|
||||
}
|
||||
|
||||
void mem_free(void* ptr)
|
||||
{
|
||||
|
||||
EXIT_IF(!g_mem_initialized);
|
||||
|
||||
MemLock lock;
|
||||
|
||||
#ifdef MEM_TRACKER
|
||||
MemBlockInfoT* const* info = g_mem_map->Find(reinterpret_cast<uintptr_t>(ptr));
|
||||
|
||||
if (info != nullptr)
|
||||
{
|
||||
|
||||
if (!pattern_check(*info))
|
||||
{
|
||||
EXIT("memory overflow\n");
|
||||
}
|
||||
|
||||
g_total_allocated -= (*info)->size;
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc,hicpp-no-malloc)
|
||||
std::free(*info);
|
||||
g_mem_map->Remove(reinterpret_cast<uintptr_t>(ptr));
|
||||
// printf("heap_free: %x\n", uintptr_t(ptr));
|
||||
#endif
|
||||
|
||||
#ifdef MEM_TRACKER
|
||||
sys_heap_free(g_default_heap, ptr != nullptr ? (static_cast<pattern_t*>(ptr)) - PATTERNS_NUM : nullptr);
|
||||
#else
|
||||
sys_heap_free(g_default_heap, ptr);
|
||||
#endif
|
||||
|
||||
#ifdef MEM_TRACKER
|
||||
} else
|
||||
{
|
||||
if (ptr != nullptr)
|
||||
{
|
||||
// printf("free: %x\n", uintptr_t(ptr));
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc,hicpp-no-malloc)
|
||||
std::free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
KYTY_MDBG("- mem_free -", ptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool mem_check([[maybe_unused]] const void* ptr)
|
||||
{
|
||||
#ifdef MEM_TRACKER
|
||||
EXIT_IF(!g_mem_initialized);
|
||||
|
||||
MemLock lock;
|
||||
|
||||
MemBlockInfoT* const* info = g_mem_map->Find(reinterpret_cast<uintptr_t>(ptr));
|
||||
|
||||
return info != nullptr && pattern_check(*info);
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef MEM_TRACKER
|
||||
static bool KYTY_HASH_CALL mem_map_print_callback(const uintptr_t* /*key*/, MemBlockInfoT* const* value, void* arg)
|
||||
{
|
||||
int state = static_cast<int>(reinterpret_cast<intptr_t>(arg));
|
||||
|
||||
if (state <= (*value)->state)
|
||||
{
|
||||
if (sizeof(uintptr_t) == 4)
|
||||
{
|
||||
printf("\n%08" PRIx32 ", %" PRIu32 ", %d\n", static_cast<uint32_t>((*value)->addr), static_cast<uint32_t>((*value)->size),
|
||||
(*value)->state);
|
||||
(*value)->stack.Print(STACK_CHECK_FROM);
|
||||
} else
|
||||
{
|
||||
printf("\n%016" PRIx64 ", %" PRIu64 ", %d\n", static_cast<uint64_t>((*value)->addr), static_cast<uint64_t>((*value)->size),
|
||||
(*value)->state);
|
||||
(*value)->stack.Print(STACK_CHECK_FROM);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool KYTY_HASH_CALL mem_map_stat_callback(const uintptr_t* /*key*/, MemBlockInfoT* const* value, void* arg)
|
||||
{
|
||||
auto* s = static_cast<MemStats*>(arg);
|
||||
|
||||
if (s->state <= (*value)->state)
|
||||
{
|
||||
s->total_allocated += (*value)->size;
|
||||
s->blocks_num++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void mem_get_stat(MemStats* s)
|
||||
{
|
||||
#ifdef MEM_TRACKER
|
||||
if (!g_mem_initialized)
|
||||
{
|
||||
#endif
|
||||
s->total_allocated = 0;
|
||||
s->blocks_num = 0;
|
||||
#ifdef MEM_TRACKER
|
||||
return;
|
||||
}
|
||||
|
||||
MemLock lock;
|
||||
|
||||
if (s->state == 0)
|
||||
{
|
||||
s->total_allocated = g_total_allocated;
|
||||
s->blocks_num = g_mem_map->Size();
|
||||
} else
|
||||
{
|
||||
s->total_allocated = 0;
|
||||
s->blocks_num = 0;
|
||||
|
||||
g_mem_map->ForEach(mem_map_stat_callback, static_cast<void*>(s));
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
int mem_new_state()
|
||||
{
|
||||
#ifdef MEM_TRACKER
|
||||
if (!g_mem_initialized)
|
||||
{
|
||||
#endif
|
||||
return 0;
|
||||
#ifdef MEM_TRACKER
|
||||
}
|
||||
|
||||
MemLock lock;
|
||||
|
||||
g_mem_state++;
|
||||
|
||||
return g_mem_state;
|
||||
#endif
|
||||
}
|
||||
|
||||
void mem_print(int from_state)
|
||||
{
|
||||
#ifdef MEM_TRACKER
|
||||
if (!g_mem_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MemLock lock;
|
||||
|
||||
intptr_t s = from_state;
|
||||
|
||||
g_mem_map->ForEach(mem_map_print_callback, reinterpret_cast<void*>(s));
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Kyty::Core
|
||||
|
||||
#ifndef KYTY_SHARED_DLL
|
||||
void* operator new(size_t size)
|
||||
{
|
||||
return Kyty::Core::mem_alloc(size);
|
||||
}
|
||||
|
||||
void* operator new(std::size_t size, const std::nothrow_t& /*nothrow_value*/) noexcept
|
||||
{
|
||||
return Kyty::Core::mem_alloc(size);
|
||||
}
|
||||
|
||||
void* operator new[](size_t size)
|
||||
{
|
||||
return Kyty::Core::mem_alloc(size);
|
||||
}
|
||||
|
||||
void* operator new[](std::size_t size, const std::nothrow_t& /*nothrow_value*/) noexcept
|
||||
{
|
||||
return Kyty::Core::mem_alloc(size);
|
||||
}
|
||||
|
||||
void operator delete(void* block) noexcept
|
||||
{
|
||||
Kyty::Core::mem_free(block);
|
||||
}
|
||||
|
||||
void operator delete[](void* block) noexcept
|
||||
{
|
||||
Kyty::Core::mem_free(block);
|
||||
}
|
||||
#else
|
||||
//#error "haha"
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
void* mem_alloc_c(size_t size)
|
||||
{
|
||||
return Kyty::Core::mem_alloc(size);
|
||||
}
|
||||
|
||||
void* mem_realloc_c(void* ptr, size_t size)
|
||||
{
|
||||
return Kyty::Core::mem_realloc(ptr, size);
|
||||
}
|
||||
|
||||
void mem_free_c(void* ptr)
|
||||
{
|
||||
Kyty::Core::mem_free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
namespace Kyty::Core {
|
||||
|
||||
bool mem_tracker_enabled()
|
||||
{
|
||||
#ifdef MEM_TRACKER
|
||||
return g_mem_tracker_enabled;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void mem_tracker_enable()
|
||||
{
|
||||
#ifdef MEM_TRACKER
|
||||
g_mem_tracker_enabled = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void mem_tracker_disable()
|
||||
{
|
||||
#ifdef MEM_TRACKER
|
||||
g_mem_tracker_enabled = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if KYTY_COMPILER == KYTY_COMPILER_MSVC
|
||||
#pragma code_seg(".mem_c")
|
||||
#endif
|
||||
|
||||
#if KYTY_COMPILER == KYTY_COMPILER_MSVC
|
||||
#pragma code_seg(pop)
|
||||
#endif
|
||||
|
||||
void mem_set_max_size(size_t size)
|
||||
{
|
||||
g_mem_max_size = size;
|
||||
}
|
||||
|
||||
} // namespace Kyty::Core
|
||||
@@ -0,0 +1,71 @@
|
||||
#include "Kyty/Core/SDLSubsystem.h"
|
||||
|
||||
#include "Kyty/Core/Common.h"
|
||||
#include "Kyty/Core/MemoryAlloc.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
// IWYU pragma: no_include <intrin.h>
|
||||
// IWYU pragma: no_include "SDL_error.h"
|
||||
// IWYU pragma: no_include "SDL_platform.h"
|
||||
// IWYU pragma: no_include "SDL_stdinc.h"
|
||||
|
||||
#include "SDL.h"
|
||||
|
||||
namespace Kyty::Core {
|
||||
|
||||
void* SDLCALL game_malloc_func(size_t size)
|
||||
{
|
||||
return Core::mem_alloc(size);
|
||||
}
|
||||
|
||||
void* SDLCALL game_calloc_func(size_t nmemb, size_t size)
|
||||
{
|
||||
void* p = Core::mem_alloc(nmemb * size);
|
||||
std::memset(p, 0, nmemb * size);
|
||||
return p;
|
||||
}
|
||||
|
||||
void* SDLCALL game_realloc_func(void* mem, size_t size)
|
||||
{
|
||||
return Core::mem_realloc(mem, size);
|
||||
}
|
||||
|
||||
void SDLCALL game_free_func(void* mem)
|
||||
{
|
||||
Core::mem_free(mem);
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_INIT(SDL)
|
||||
{
|
||||
int sdl_alloc = SDL_GetNumAllocations();
|
||||
|
||||
if (sdl_alloc != 0)
|
||||
{
|
||||
printf("warning: SDL static alloc: %d blocks\n", sdl_alloc);
|
||||
} else
|
||||
{
|
||||
if (SDL_SetMemoryFunctions(game_malloc_func, game_calloc_func, game_realloc_func, game_free_func) != 0)
|
||||
{
|
||||
KYTY_SUBSYSTEM_FAIL("%s\n", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
#if SDL_DYNAMIC_API != 0
|
||||
#error "SDL_DYNAMIC_API"
|
||||
#endif
|
||||
|
||||
if (SDL_Init(0) < 0)
|
||||
{
|
||||
KYTY_SUBSYSTEM_FAIL("%s\n", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(SDL) {}
|
||||
|
||||
KYTY_SUBSYSTEM_DESTROY(SDL)
|
||||
{
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
} // namespace Kyty::Core
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,370 @@
|
||||
#include "Kyty/Core/Subsystems.h"
|
||||
|
||||
#include "Kyty/Core/DbgAssert.h"
|
||||
#include "Kyty/Core/SafeDelete.h"
|
||||
#include "Kyty/Sys/SysStdio.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
|
||||
namespace Kyty::Core {
|
||||
|
||||
class SubsystemPrivate
|
||||
{
|
||||
public:
|
||||
SubsystemPrivate() = default;
|
||||
|
||||
virtual ~SubsystemPrivate()
|
||||
{
|
||||
if (fail_msg != nullptr)
|
||||
{
|
||||
DeleteArray(fail_msg);
|
||||
}
|
||||
}
|
||||
|
||||
KYTY_CLASS_NO_COPY(SubsystemPrivate);
|
||||
|
||||
bool failed {false};
|
||||
char* fail_msg {nullptr};
|
||||
};
|
||||
|
||||
class SubsystemsListPrivate
|
||||
{
|
||||
public:
|
||||
explicit SubsystemsListPrivate(SubsystemsList* p): parent(p) {}
|
||||
|
||||
virtual ~SubsystemsListPrivate()
|
||||
{
|
||||
SubsListStruct* n = list;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (n == nullptr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
SubsListStruct* nn = n;
|
||||
|
||||
DepsListStruct* dl = n->deps;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (dl == nullptr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
DepsListStruct* ddl = dl;
|
||||
|
||||
dl = dl->next;
|
||||
|
||||
Delete(ddl);
|
||||
}
|
||||
|
||||
n = n->next;
|
||||
|
||||
Delete(nn);
|
||||
}
|
||||
}
|
||||
|
||||
void SetArgs(int argc, char** argv)
|
||||
{
|
||||
this->m_argc = argc;
|
||||
this->m_argv = argv;
|
||||
}
|
||||
|
||||
void Add(Subsystem* s, std::initializer_list<Subsystem*> deps)
|
||||
{
|
||||
EXIT_IF(!s);
|
||||
|
||||
const char* name = s->Id();
|
||||
|
||||
auto* nl = new SubsListStruct;
|
||||
|
||||
nl->s = s;
|
||||
nl->name = name;
|
||||
nl->deps = nullptr;
|
||||
nl->next = list;
|
||||
nl->prev_init = nullptr;
|
||||
|
||||
list = nl;
|
||||
|
||||
for (auto* dep: deps)
|
||||
{
|
||||
const char* str = dep->Id();
|
||||
|
||||
auto* l = new DepsListStruct;
|
||||
l->dep_name = str;
|
||||
l->next = nl->deps;
|
||||
|
||||
nl->deps = l;
|
||||
}
|
||||
|
||||
nl->initialized = false;
|
||||
}
|
||||
|
||||
bool InitAll(bool print_msg)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
SubsListStruct* n = FindNextToInitialize();
|
||||
|
||||
if (n == nullptr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
n->s->Init(parent);
|
||||
|
||||
if (n->s->m_p->failed)
|
||||
{
|
||||
fail_msg = n->s->m_p->fail_msg;
|
||||
fail_name = n->name;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (print_msg)
|
||||
{
|
||||
printf("Initialized: %s\n", n->name);
|
||||
}
|
||||
|
||||
n->initialized = true;
|
||||
|
||||
SubsListStruct* last = last_init;
|
||||
last_init = n;
|
||||
n->prev_init = last;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DestroyAll(bool print_msg)
|
||||
{
|
||||
SubsListStruct* n = last_init;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (n == nullptr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
n->s->Destroy(parent);
|
||||
n->initialized = false;
|
||||
|
||||
if (print_msg)
|
||||
{
|
||||
printf("Destroyed: %s\n", n->name);
|
||||
}
|
||||
|
||||
n = n->prev_init;
|
||||
}
|
||||
|
||||
last_init = nullptr;
|
||||
}
|
||||
|
||||
void ShutdownAll()
|
||||
{
|
||||
SubsListStruct* n = last_init;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (n == nullptr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
n->s->UnexpectedShutdown(parent);
|
||||
n->initialized = false;
|
||||
|
||||
n = n->prev_init;
|
||||
}
|
||||
|
||||
last_init = nullptr;
|
||||
}
|
||||
|
||||
struct DepsListStruct
|
||||
{
|
||||
const char* dep_name;
|
||||
DepsListStruct* next;
|
||||
};
|
||||
|
||||
struct SubsListStruct
|
||||
{
|
||||
Subsystem* s;
|
||||
const char* name;
|
||||
DepsListStruct* deps;
|
||||
SubsListStruct* next;
|
||||
SubsListStruct* prev_init;
|
||||
bool initialized;
|
||||
};
|
||||
|
||||
[[nodiscard]] SubsListStruct* FindByName(const char* name) const
|
||||
{
|
||||
SubsListStruct* n = list;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if ((n == nullptr) || std::strcmp(n->name, name) == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
n = n->next;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
[[nodiscard]] SubsListStruct* FindNextToInitialize() const
|
||||
{
|
||||
SubsListStruct* n = list;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (n == nullptr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (!n->initialized)
|
||||
{
|
||||
DepsListStruct* d = n->deps;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (d == nullptr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
SubsListStruct* s = FindByName(d->dep_name);
|
||||
|
||||
if ((s == nullptr) || !s->initialized)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
d = d->next;
|
||||
}
|
||||
|
||||
if (d == nullptr)
|
||||
{
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
n = n->next;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
KYTY_CLASS_NO_COPY(SubsystemsListPrivate);
|
||||
|
||||
SubsListStruct* list = nullptr;
|
||||
SubsListStruct* last_init = nullptr;
|
||||
int m_argc = 0;
|
||||
char** m_argv = nullptr;
|
||||
const char* fail_msg = nullptr;
|
||||
const char* fail_name = nullptr;
|
||||
SubsystemsList* parent;
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc,hicpp-no-malloc)
|
||||
SubsystemsList::SubsystemsList(): m_p(static_cast<SubsystemsListPrivate*>(std::malloc(sizeof(SubsystemsListPrivate))))
|
||||
{
|
||||
new (m_p) SubsystemsListPrivate(this);
|
||||
}
|
||||
|
||||
SubsystemsList::~SubsystemsList()
|
||||
{
|
||||
m_p->~SubsystemsListPrivate();
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc,hicpp-no-malloc)
|
||||
std::free(m_p);
|
||||
// delete p;
|
||||
}
|
||||
|
||||
void SubsystemsList::Add(Subsystem* s, std::initializer_list<Subsystem*> deps)
|
||||
{
|
||||
m_p->Add(s, deps);
|
||||
}
|
||||
|
||||
bool SubsystemsList::InitAll(bool print_msg)
|
||||
{
|
||||
return m_p->InitAll(print_msg);
|
||||
}
|
||||
|
||||
void SubsystemsList::DestroyAll(bool print_msg)
|
||||
{
|
||||
m_p->DestroyAll(print_msg);
|
||||
}
|
||||
|
||||
int* SubsystemsList::GetArgc()
|
||||
{
|
||||
return &m_p->m_argc;
|
||||
}
|
||||
|
||||
char** SubsystemsList::GetArgv()
|
||||
{
|
||||
return m_p->m_argv;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc,hicpp-no-malloc)
|
||||
Subsystem::Subsystem(): m_p(static_cast<SubsystemPrivate*>(std::malloc(sizeof(SubsystemPrivate))))
|
||||
{
|
||||
new (m_p) SubsystemPrivate;
|
||||
}
|
||||
|
||||
Subsystem::~Subsystem()
|
||||
{
|
||||
m_p->~SubsystemPrivate();
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc,hicpp-no-malloc)
|
||||
std::free(m_p);
|
||||
// delete p;
|
||||
}
|
||||
|
||||
void Subsystem::Fail(const char* format, ...)
|
||||
{
|
||||
va_list args {};
|
||||
va_start(args, format);
|
||||
|
||||
uint32_t len = sys_vscprintf(format, args);
|
||||
|
||||
if (len != 0)
|
||||
{
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc,hicpp-no-malloc)
|
||||
char* d = static_cast<char*>(std::malloc(len + 1));
|
||||
std::memset(d, 0, len + 1);
|
||||
/*len = */ sys_vsnprintf(d, len, format, args);
|
||||
m_p->fail_msg = d;
|
||||
m_p->failed = true;
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
const char* SubsystemsList::GetFailName() const
|
||||
{
|
||||
return m_p->fail_name;
|
||||
}
|
||||
|
||||
const char* SubsystemsList::GetFailMsg() const
|
||||
{
|
||||
return m_p->fail_msg;
|
||||
}
|
||||
|
||||
void SubsystemsList::SetArgs(int argc, char* argv[])
|
||||
{
|
||||
m_p->SetArgs(argc, argv);
|
||||
}
|
||||
|
||||
void SubsystemsList::ShutdownAll()
|
||||
{
|
||||
m_p->ShutdownAll();
|
||||
}
|
||||
|
||||
} // namespace Kyty::Core
|
||||
@@ -0,0 +1,305 @@
|
||||
#include "Kyty/Core/Threads.h"
|
||||
|
||||
#include "Kyty/Core/DbgAssert.h"
|
||||
#include "Kyty/Core/SafeDelete.h"
|
||||
#include "Kyty/Core/String.h"
|
||||
|
||||
//#define THREADS_SDL
|
||||
|
||||
#ifdef THREADS_SDL
|
||||
#include "SDL.h"
|
||||
#else
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#endif
|
||||
|
||||
namespace Kyty::Core {
|
||||
|
||||
#ifdef THREADS_SDL
|
||||
typedef uint64_t thread_id_t;
|
||||
#else
|
||||
using thread_id_t = std::thread::id;
|
||||
#endif
|
||||
|
||||
struct Thread::ThreadPrivate
|
||||
{
|
||||
ThreadPrivate(thread_func_t func, void* arg)
|
||||
: finished(false), auto_delete(false),
|
||||
#ifdef THREADS_SDL
|
||||
m_func(func), m_arg(arg)
|
||||
{
|
||||
sdl = SDL_CreateThread(thread_run, "sdl_thread", this);
|
||||
}
|
||||
#else
|
||||
m_thread(func, arg)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
bool finished;
|
||||
bool auto_delete;
|
||||
#ifdef THREADS_SDL
|
||||
SDL_Thread* sdl;
|
||||
thread_func_t m_func;
|
||||
void* m_arg;
|
||||
|
||||
static int thread_run(void* data)
|
||||
{
|
||||
ThreadPrivate* t = (ThreadPrivate*)data;
|
||||
t->m_func(t->m_arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
std::thread m_thread;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct Mutex::MutexPrivate
|
||||
{
|
||||
#ifdef THREADS_SDL
|
||||
SDL_mutex* sdl;
|
||||
#else
|
||||
std::recursive_mutex m_mutex;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct CondVar::CondVarPrivate
|
||||
{
|
||||
#ifdef THREADS_SDL
|
||||
SDL_cond* sdl;
|
||||
#else
|
||||
std::condition_variable_any m_cv;
|
||||
#endif
|
||||
};
|
||||
|
||||
static thread_id_t g_main_thread;
|
||||
static int g_main_thread_int;
|
||||
static std::atomic<int> g_thread_counter = 0;
|
||||
|
||||
KYTY_SUBSYSTEM_INIT(Threads)
|
||||
{
|
||||
#ifdef THREADS_SDL
|
||||
g_main_thread = SDL_ThreadID();
|
||||
#else
|
||||
g_main_thread = std::this_thread::get_id();
|
||||
g_main_thread_int = Thread::GetThreadIdUnique();
|
||||
#endif
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Threads) {}
|
||||
|
||||
KYTY_SUBSYSTEM_DESTROY(Threads) {}
|
||||
|
||||
Thread::Thread(thread_func_t func, void* arg): m_thread(new ThreadPrivate(func, arg)) // @suppress("Symbol is not resolved")
|
||||
{
|
||||
}
|
||||
|
||||
Thread::~Thread()
|
||||
{
|
||||
EXIT_IF(!m_thread->finished && !m_thread->auto_delete);
|
||||
|
||||
Delete(m_thread);
|
||||
}
|
||||
|
||||
void Thread::Join()
|
||||
{
|
||||
EXIT_IF(m_thread->finished || m_thread->auto_delete);
|
||||
|
||||
#ifdef THREADS_SDL
|
||||
int status = -1;
|
||||
SDL_WaitThread(m_thread->sdl, &status);
|
||||
EXIT_IF(status != 0);
|
||||
#else
|
||||
m_thread->m_thread.join();
|
||||
#endif
|
||||
|
||||
m_thread->finished = true;
|
||||
}
|
||||
|
||||
void Thread::Detach()
|
||||
{
|
||||
EXIT_IF(m_thread->finished || m_thread->auto_delete);
|
||||
|
||||
m_thread->auto_delete = true;
|
||||
#ifdef THREADS_SDL
|
||||
SDL_DetachThread(m_thread->sdl);
|
||||
#else
|
||||
m_thread->m_thread.detach();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Thread::Sleep(uint32_t millis)
|
||||
{
|
||||
#ifdef THREADS_SDL
|
||||
SDL_Delay(millis);
|
||||
#else
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(millis));
|
||||
#endif
|
||||
}
|
||||
|
||||
void Thread::SleepMicro(uint32_t micros)
|
||||
{
|
||||
#ifdef THREADS_SDL
|
||||
SDL_Delay(micros / 1000);
|
||||
#else
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(micros));
|
||||
#endif
|
||||
}
|
||||
|
||||
void Thread::SleepNano(uint64_t nanos)
|
||||
{
|
||||
#ifdef THREADS_SDL
|
||||
SDL_Delay(nanos / 1000000);
|
||||
#else
|
||||
std::this_thread::sleep_for(std::chrono::nanoseconds(nanos));
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Thread::IsMainThread()
|
||||
{
|
||||
#ifdef THREADS_SDL
|
||||
return g_main_thread == thread_id_t(SDL_ThreadID());
|
||||
#else
|
||||
return g_main_thread == std::this_thread::get_id();
|
||||
#endif
|
||||
}
|
||||
|
||||
String Thread::GetId() const
|
||||
{
|
||||
#ifdef THREADS_SDL
|
||||
return String::FromPrintf("%" PRIu64, (uint64_t)SDL_GetThreadID(m_thread->sdl));
|
||||
#else
|
||||
std::stringstream ss;
|
||||
ss << m_thread->m_thread.get_id();
|
||||
return String::FromUtf8(ss.str().c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
String Thread::GetThreadId()
|
||||
{
|
||||
#ifdef THREADS_SDL
|
||||
return String::FromPrintf("%" PRIu64, (uint64_t)SDL_ThreadID());
|
||||
#else
|
||||
std::stringstream ss;
|
||||
ss << std::this_thread::get_id();
|
||||
return String::FromUtf8(ss.str().c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
Mutex::Mutex(): m_mutex(new MutexPrivate)
|
||||
{
|
||||
#ifdef THREADS_SDL
|
||||
m_mutex->sdl = SDL_CreateMutex();
|
||||
EXIT_IF(!m_mutex->sdl);
|
||||
#endif
|
||||
}
|
||||
|
||||
Mutex::~Mutex()
|
||||
{
|
||||
#ifdef THREADS_SDL
|
||||
SDL_DestroyMutex(m_mutex->sdl);
|
||||
#endif
|
||||
Delete(m_mutex);
|
||||
}
|
||||
|
||||
void Mutex::Lock()
|
||||
{
|
||||
#ifdef THREADS_SDL
|
||||
SDL_LockMutex(m_mutex->sdl);
|
||||
#else
|
||||
m_mutex->m_mutex.lock();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Mutex::Unlock()
|
||||
{
|
||||
#ifdef THREADS_SDL
|
||||
SDL_UnlockMutex(m_mutex->sdl);
|
||||
#else
|
||||
m_mutex->m_mutex.unlock();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Mutex::TryLock()
|
||||
{
|
||||
#ifdef THREADS_SDL
|
||||
int status = SDL_TryLockMutex(m_mutex->sdl);
|
||||
if (status == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
return m_mutex->m_mutex.try_lock();
|
||||
#endif
|
||||
}
|
||||
|
||||
CondVar::CondVar(): m_cond_var(new CondVarPrivate)
|
||||
{
|
||||
#ifdef THREADS_SDL
|
||||
m_cond_var->sdl = SDL_CreateCond();
|
||||
EXIT_IF(!m_cond_var->sdl);
|
||||
#endif
|
||||
}
|
||||
|
||||
CondVar::~CondVar()
|
||||
{
|
||||
#ifdef THREADS_SDL
|
||||
SDL_DestroyCond(m_cond_var->sdl);
|
||||
#endif
|
||||
Delete(m_cond_var);
|
||||
}
|
||||
|
||||
void CondVar::Wait(Mutex* mutex)
|
||||
{
|
||||
#ifdef THREADS_SDL
|
||||
SDL_CondWait(m_cond_var->sdl, mutex->m_mutex->sdl);
|
||||
#else
|
||||
std::unique_lock<std::recursive_mutex> cpp_lock(mutex->m_mutex->m_mutex, std::adopt_lock_t());
|
||||
m_cond_var->m_cv.wait(cpp_lock);
|
||||
cpp_lock.release();
|
||||
#endif
|
||||
}
|
||||
|
||||
void CondVar::WaitFor(Mutex* mutex, uint32_t micros)
|
||||
{
|
||||
#ifdef THREADS_SDL
|
||||
SDL_CondWaitTimeout(m_cond_var->sdl, mutex->m_mutex->sdl, (micros < 1000 ? 1 : micros / 1000));
|
||||
#else
|
||||
std::unique_lock<std::recursive_mutex> cpp_lock(mutex->m_mutex->m_mutex, std::adopt_lock_t());
|
||||
m_cond_var->m_cv.wait_for(cpp_lock, std::chrono::microseconds(micros));
|
||||
cpp_lock.release();
|
||||
#endif
|
||||
}
|
||||
|
||||
void CondVar::Signal()
|
||||
{
|
||||
#ifdef THREADS_SDL
|
||||
SDL_CondSignal(m_cond_var->sdl);
|
||||
#else
|
||||
m_cond_var->m_cv.notify_one();
|
||||
#endif
|
||||
}
|
||||
|
||||
void CondVar::SignalAll()
|
||||
{
|
||||
#ifdef THREADS_SDL
|
||||
SDL_CondBroadcast(m_cond_var->sdl);
|
||||
#else
|
||||
m_cond_var->m_cv.notify_all();
|
||||
#endif
|
||||
}
|
||||
|
||||
int Thread::GetThreadIdUnique()
|
||||
{
|
||||
static thread_local int tid = ++g_thread_counter;
|
||||
return tid;
|
||||
}
|
||||
|
||||
} // namespace Kyty::Core
|
||||
@@ -0,0 +1,106 @@
|
||||
#include "Kyty/Core/Timer.h"
|
||||
|
||||
#include "Kyty/Core/DbgAssert.h"
|
||||
#include "Kyty/Sys/SysTimer.h"
|
||||
|
||||
namespace Kyty::Core {
|
||||
|
||||
Timer::Timer() noexcept
|
||||
{
|
||||
sys_query_performance_frequency(&m_Frequency);
|
||||
}
|
||||
|
||||
void Timer::Start()
|
||||
{
|
||||
sys_query_performance_counter(&m_StartTime);
|
||||
m_is_paused = false;
|
||||
}
|
||||
|
||||
void Timer::Pause()
|
||||
{
|
||||
EXIT_IF(m_is_paused);
|
||||
|
||||
sys_query_performance_counter(&m_PauseTime);
|
||||
m_is_paused = true;
|
||||
}
|
||||
|
||||
void Timer::Resume()
|
||||
{
|
||||
EXIT_IF(!m_is_paused);
|
||||
|
||||
uint64_t current_time = 0;
|
||||
sys_query_performance_counter(¤t_time);
|
||||
|
||||
m_StartTime += current_time - m_PauseTime;
|
||||
|
||||
m_is_paused = false;
|
||||
}
|
||||
|
||||
bool Timer::IsPaused() const
|
||||
{
|
||||
return m_is_paused;
|
||||
}
|
||||
|
||||
// return time in milliseconds
|
||||
double Timer::GetTimeMs() const
|
||||
{
|
||||
if (m_is_paused)
|
||||
{
|
||||
return 1000.0 * (static_cast<double>(m_PauseTime - m_StartTime)) / static_cast<double>(m_Frequency);
|
||||
}
|
||||
|
||||
uint64_t current_time = 0;
|
||||
sys_query_performance_counter(¤t_time);
|
||||
|
||||
return 1000.0 * (static_cast<double>(current_time - m_StartTime)) / static_cast<double>(m_Frequency);
|
||||
}
|
||||
|
||||
// return time in seconds
|
||||
double Timer::GetTimeS() const
|
||||
{
|
||||
if (m_is_paused)
|
||||
{
|
||||
return (static_cast<double>(m_PauseTime - m_StartTime)) / static_cast<double>(m_Frequency);
|
||||
}
|
||||
|
||||
uint64_t current_time = 0;
|
||||
sys_query_performance_counter(¤t_time);
|
||||
|
||||
return (static_cast<double>(current_time - m_StartTime)) / static_cast<double>(m_Frequency);
|
||||
}
|
||||
|
||||
// return time in ticks
|
||||
uint64_t Timer::GetTicks() const
|
||||
{
|
||||
if (m_is_paused)
|
||||
{
|
||||
return (m_PauseTime - m_StartTime);
|
||||
}
|
||||
|
||||
uint64_t current_time = 0;
|
||||
sys_query_performance_counter(¤t_time);
|
||||
|
||||
return (current_time - m_StartTime);
|
||||
}
|
||||
|
||||
// return ticks frequency
|
||||
uint64_t Timer::GetFrequency() const
|
||||
{
|
||||
return m_Frequency;
|
||||
}
|
||||
|
||||
uint64_t Timer::QueryPerformanceFrequency()
|
||||
{
|
||||
uint64_t ret = 0;
|
||||
sys_query_performance_frequency(&ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint64_t Timer::QueryPerformanceCounter()
|
||||
{
|
||||
uint64_t ret = 0;
|
||||
sys_query_performance_counter(&ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Kyty::Core
|
||||
Reference in New Issue
Block a user