Привет, Хабр!
Во время разработки ядра игрового движка Case Engine нам понадобились различные утилиты — от парсера INI файлов до удобных инструментов для работы с потоками. Чтобы всё это было удобно и лаконично, а другие разработчики могли легко интегрировать эти решения в свои проекты, мы создали кроссплатформенную коллекцию утилит CUtils. На данный момент она достигла версии 2.0.0!
В этой статье мы не будем подробно останавливаться на компонентах первой версии CUtils, о которых мы рассказывали в предыдущем материале. Вместо этого мы сосредоточимся на новых возможностях, которые появились в версии 2.0.0.
Статья о первой версии CUtils — https://habr.com/ru/articles/857398/
Собранная библиотека для Windows — https://github.com/case-tech/CUtils-Win
Исходники библиотеки — https://github.com/case-tech/CUtils-Sources
Приятного чтения)
Что нового?
В версии 2.0.0 CUtils мы исключили компонент Hashed.hpp
, так как его использование оказалось не слишком удобным. Однако вместо этого добавили множество других компонентов:
Coroutine
DataTime
Encryption
Filesystem
Fusion
Macroses
Multithread
Plugins
Random
-
StringHelper
Common
Для того чтобы подключение заголовков в компоненты выглядело аккуратнее и удобнее, мы выделили единый заголовочный файл — Common.hpp
. Он уже подключён ко многим компонентам, и вам нужно будет подключить его вручную только при использовании компонента Macroses.hpp
.
Содержимое Common.hpp
:
// The MIT License(MIT)
// Copyright © 2024 Case Technologies
#pragma once
// C++ libraries
#include <coroutine>
#include <iostream>
#include <string>
#include <filesystem>
#include <fstream>
#include <map>
#ifdef _WIN32 || _WIN64
#include <windows.h>
#else
#include <dlfcn.h>
#endif
#include <future>
#include <random>
#include <typeindex>
#include <vector>
#include <iomanip>
#include <cstring>
#include <variant>
#include <thread>
#include <mutex>
Coroutine
Для удобной работы с сопрограммами мы решили написать компонент Coroutine.hpp
.
Пример использования:
#include <Coroutine.hpp>
// Пример корутины, которая выполняет несколько шагов и приостанавливается
CUtils::Coroutine exampleCoroutine()
{
// Вывод сообщения о начале выполнения корутины
std::cout << "Coroutine started" << std::endl;
// Приостановка корутины с помощью co_await
// std::suspend_always{} - это встроенный awaiter, который всегда приостанавливает выполнение
co_await std::suspend_always{};
// Вывод сообщения о возобновлении корутины
std::cout << "Coroutine resumed" << std::endl;
// Еще одна приостановка
co_await std::suspend_always{};
// Вывод сообщения о завершении корутины
std::cout << "Coroutine finished" << std::endl;
}
int main()
{
// Создаем корутину, вызывая функцию exampleCoroutine
// Обратите внимание, что exampleCoroutine возвращает объект типа Coroutine
CUtils::Coroutine coroutine = exampleCoroutine();
// Цикл для управления выполнением корутины
// Метод done() возвращает true, если корутина завершена
while (!coroutine.done())
{
// Вывод сообщения о том, что мы возобновляем корутину
std::cout << "Resuming coroutine..." << std::endl;
// Метод resume() возобновляет выполнение корутины
// Если корутина приостановлена, она продолжит выполнение с того места, где была остановлена
coroutine.resume();
}
// Вывод сообщения о завершении работы с корутиной
std::cout << "Coroutine completed" << std::endl;
return 0;
}
DataTime
Для получения и форматирования даты и времени в CUtils есть компонент DataTime.hpp
.
Пример использования:
#include "DateTime.hpp"
int main()
{
// Создаем объект с текущим временем
CUtils::DateTime now;
// Получаем текущую дату и время
std::string currentDateTime = now.GetCurrentDateTime();
std::string currentDate = now.GetCurrentDate();
std::string currentTime = now.GetCurrentTime();
// Выводим на экран
std::cout << "Current DateTime: " << currentDateTime << std::endl;
std::cout << "Current Date: " << currentDate << std::endl;
std::cout << "Current Time: " << currentTime << std::endl;
// Устанавливаем конкретное время
time_t specificTime = 1633024800; // Например, 1 октября 2021 года
now.SetTime(specificTime);
// Получаем время в формате time_t
time_t currentTime_ = now.GetTime();
std::cout << "Current Time (time_t): " << currentTime << std::endl;
// Форматируем время в произвольном формате
std::string formattedTime = now.Format("%Y/%m/%d %H-%M-%S");
std::cout << "Formatted Time: " << formattedTime << std::endl;
// Сравниваем объекты DateTime
CUtils::DateTime date1;
CUtils::DateTime date2;
if (date1 == date2)
{
std::cout << "date1 and date2 are the same." << std::endl;
}
else
{
std::cout << "date1 and date2 are different." << std::endl;
}
return 0;
}
Encryption
В CUtils есть компонент Encryption.hpp
, который предоставляет 5 алгоритмов для шифрования текста.
Пример использования:
#include "Encryption.hpp"
int main()
{
std::string plaintext = "Hello, World!";
// Алгоритм Цезаря
CUtils::Encryption caesarEncryptor(CUtils::Alg::Caesar);
std::string caesarCipher = caesarEncryptor.Encrypt(plaintext);
std::cout << "Caesar Encrypted: " << caesarCipher << std::endl;
std::cout << "Caesar Decrypted: " << caesarEncryptor.Decrypt(caesarCipher) << std::endl;
// Алгоритм Виженера
CUtils::Encryption vigenereEncryptor(CUtils::Alg::Vigenere, "key");
std::string vigenereCipher = vigenereEncryptor.Encrypt(plaintext);
std::cout << "Vigenere Encrypted: " << vigenereCipher << std::endl;
std::cout << "Vigenere Decrypted: " << vigenereEncryptor.Decrypt(vigenereCipher) << std::endl;
// Алгоритм XOR
CUtils::Encryption xorEncryptor(CUtils::Alg::XOR, "key");
std::string xorCipher = xorEncryptor.Encrypt(plaintext);
std::cout << "XOR Encrypted: " << xorCipher << std::endl;
std::cout << "XOR Decrypted: " << xorEncryptor.Decrypt(xorCipher) << std::endl;
// Алгоритм подстановки
CUtils::Encryption substitutionEncryptor(CUtils::Alg::Substitution);
std::string substitutionCipher = substitutionEncryptor.Encrypt(plaintext);
std::cout << "Substitution Encrypted: " << substitutionCipher << std::endl;
std::cout << "Substitution Decrypted: " << substitutionEncryptor.Decrypt(substitutionCipher) << std::endl;
// Алгоритм транспозиции
CUtils::Encryption transpositionEncryptor(CUtils::Alg::Transposition, "key");
std::string transpositionCipher = transpositionEncryptor.Encrypt(plaintext);
std::cout << "Transposition Encrypted: " << transpositionCipher << std::endl;
std::cout << "Transposition Decrypted: " << transpositionEncryptor.Decrypt(transpositionCipher) << std::endl;
return 0;
}
Filesystem
Компонент Filesystem.hpp
предоставляет полный функционал для работы с файловой системой.
Пример использования:
#include "Filesystem.hpp"
int main()
{
CUtils::Filesystem fs;
// Создание директории
fs.CreateDirectory("test_dir");
// Проверка существования
if (fs.Exists("test_dir"))
{
std::cout << "Directory exists." << std::endl;
}
// Создание файла (например, с помощью std::ofstream)
std::ofstream file("test_dir/test_file.txt");
file << "Hello, World!";
file.close();
// Проверка, является ли путь файлом
if (fs.IsFile("test_dir/test_file.txt"))
{
std::cout << "This is a file." << std::endl;
}
// Получение размера файла
try
{
uintmax_t size = fs.FileSize("test_dir/test_file.txt");
std::cout << "File size: " << size << " bytes." << std::endl;
}
catch (const std::runtime_error& e)
{
std::cerr << e.what() << std::endl;
}
// Копирование файла
fs.Copy("test_dir/test_file.txt", "test_dir/test_file_copy.txt");
// Удаление файла
fs.Remove("test_dir/test_file.txt");
// Удаление директории
fs.Remove("test_dir");
return 0;
}
Fusion
Данный компонент предоставляет полный функционал для удобной работы с кортежами.
Пример использования:
#include "Fusion.hpp"
int main()
{
// Создаем два кортежа
CUtils::Fusion::Tuple<int, double> t1 = { 1, 2.5 };
CUtils::Fusion::Tuple<char, std::string> t2 = { 'a', "hello" };
// Объединяем кортежи
auto merged = CUtils::Fusion::Merge(t1, t2);
std::cout << "Merged tuple: ";
CUtils::Fusion::Print(merged); // Выводит: (1, 2.5, a, hello)
// Получаем элемент по индексу
auto elementByIndex = CUtils::Fusion::Get<1>(merged); // Получаем элемент с индексом 1(2.5)
std::cout << "Element at index 1: " << elementByIndex << std::endl; // Выводит: 2.5
// Получаем элемент по типу
auto elementByType = CUtils::Fusion::GetByType<std::string>(merged); // Получаем элемент
типа std::string ("hello")
std::cout << "Element of type std::string: " << elementByType << std::endl; // Выводит: hello
// Получаем элемент по динамическому индексу
size_t index = 2;
auto elementDynamicIndex = CUtils::Fusion::GetByIndex(merged, index); // Получаем элемент с индексом 2 ('a')
std::visit([](auto&& arg) { std::cout << "Element at index 2: " << arg << std::endl; }, elementDynamicIndex); // Выводит: a
// Вычисляем размер кортежа
std::cout << "Size of merged tuple: " << CUtils::Fusion::Size(merged) << std::endl; // Выводит: 4
// Сравниваем кортежи
CUtils::Fusion::Tuple<int, double> t3 = { 1, 2.5 };
CUtils::Fusion::Tuple<int, double> t4 = { 1, 3.0 };
std::cout << "t1 == t3: " << CUtils::Fusion::AreEqual(t1, t3) << std::endl; // Выводит: true
std::cout << "t1 == t4: " << CUtils::Fusion::AreEqual(t1, t4) << std::endl; // Выводит: false
// Печатаем кортеж
std::cout << "Printing merged tuple: ";
CUtils::Fusion::Print(merged); // Выводит: (1, 2.5, a, hello)
return 0;
}
Macroses
В заголовочный файл Macroses.hpp
мы поместили множество полезных макросов. При использовании данного компонента нужно помнить, что подключение файла Common.hpp обязательно.
Пример использования:
#include <Common.hpp>
#include <Macroses.hpp>
// Пример использования FORCE_INLINE
FORCE_INLINE int add(int a, int b)
{
return a + b;
}
// Пример структуры с выравниванием
struct ALIGNAS(16) MyStruct
{
int a;
int b;
};
// Пример функции с отключением предупреждений
DISABLE_WARNINGS(4267) // Отключение предупреждения 4267
void someFunction()
{
// Код, который может вызвать предупреждение 4267
std::cout << "Warning 4267 is disabled here." << std::endl;
}
ENABLE_WARNINGS // Включение предупреждений обратно
int main()
{
// Вывод информации о компиляторе и платформе
std::cout << "Compiler: " << COMPILER_NAME << std::endl;
std::cout << "Platform: " << PLATFORM_NAME << std::endl;
// Условные проверки для компилятора и платформы
#if defined(COMPILER_MSVC)
std::cout << "Using MSVC compiler." << std::endl;
#elif defined(COMPILER_GCC)
std::cout << "Using GCC compiler." << std::endl;
#elif defined(COMPILER_CLANG)
std::cout << "Using Clang compiler." << std::endl;
#endif
#if defined(PLATFORM_WINDOWS)
std::cout << "Running on Windows." << std::endl;
#elif defined(PLATFORM_MAC)
std::cout << "Running on macOS." << std::endl;
#elif defined(PLATFORM_LINUX)
std::cout << "Running on Linux." << std::endl;
#endif
// пример использования FORCE_INLINE
int result = add(3, 4);
std::cout << "Result of add(3, 4): " << result << std::endl;
// Пример структуры с выравниванием
MyStruct obj;
std::cout << "Size of MyStruct: " << sizeof(obj) << std::endl;
// Пример отключения предупреждений
someFunction();
// Пример использования ASSERT
int x = 5;
ASSERT(x > 0); // Условие выполняется
// ASSERT(x < 0); // Условие не выполняется, программа завершится с ошибкой
// Пример определения размера массива
int arr[] = { 1, 2, 3, 4, 5 };
std::cout << "Array size: " << ARRAY_SIZE(arr) << std::endl;
// Пример объединения токенов и преобразования в строку
#define MY_MACRO(x) CONCATENATE(my_prefix_, x)
std::cout << TO_STRING(MY_MACRO(value)) << std::endl; // Выведет "my_prefix_value"
return 0;
}
Multithread
В данный компонент мы добавили функционал для удобной работы с потоками.
Пример использования:
#include "Multithread.hpp"
int main()
{
CUtils::Multithread multithread;
// Добавляем задачи
multithread.AddTask([]()
{
std::cout << "Task 1 is running" << std::endl;
});
multithread.AddTask([]()
{
std::cout << "Task 2 is running" << std::endl;
});
// Запускаем задачи в отдельных потоках
multithread.Run();
// Ожидаем завершения всех задач
multithread.JoinAll();
std::cout << "All tasks completed" << std::endl;
return 0;
}
Plugins
Заголовочный файл Plugins.hpp
имеет полный функционал для создания и работы с плагинами.
1. Создание плагина
Для начала вам нужно создать плагин, который будет загружаться и выполняться вашей системой. Плагин должен реализовывать интерфейс PluginInterface, который определен в файле Plugins.hpp
.
Пример реализации плагина:
// MyPlugin.hpp
#pragma once
#include "Plugins.hpp"
class MyPlugin : public CUtils::PluginInterface
{
public:
void execute() override
{
std::cout << "MyPlugin is executing!" << std::endl;
}
};
// MyPlugin.cpp
#include "MyPlugin.hpp"
extern "C" CUtils::PluginInterface* createPlugin()
{
return new MyPlugin();
}
2. Компиляция плагина
Вам нужно скомпилировать этот плагин в динамическую библиотеку (DLL на Windows или SO на Linux).
3. Использование компонента в основном приложении
Теперь вы можете использовать компонент Plugins.hpp
в вашем основном приложении для загрузки и выполнения плагина.
Пример использования:
#include "Plugins.hpp"
int main()
{
CUtils::Plugins plugins;
// Укажите путь к скомпилированному плагину
std::wstring pluginPath = L"path/to/libMyPlugin.so"; // или L"path/to/MyPlugin.dll" на Windows
if (plugins.LoadPlugin(pluginPath))
{
std::cout << "Plugin loaded and executed successfully!" << std::endl;
}
else
{
std::cerr << "Failed to load plugin." << std::endl;
}
return 0;
}
Random
Заголовочный файл Random.hpp
содержит в себе множество рандомайзеров.
Пример использования:
#include "Random.hpp"
int main()
{
CUtils::Random randomGenerator;
std::cout << "Random Integer: " << randomGenerator.RandomInt(1, 100) << std::endl;
std::cout << "Random Double: " << randomGenerator.RandomDouble(0.0, 1.0) << std::endl;
std::cout << "Normal Double: " << randomGenerator.NormalDouble(5.0, 2.0) << std::endl;
std::cout << "Binomial Integer: " << randomGenerator.BinomialInt(10, 0.5) << std::endl;
std::cout << "Bernoulli Bool: " << (randomGenerator.BernoulliBool(0.7) ? "true" : "false") << std::endl;
std::cout << "Poisson Integer: " << randomGenerator.PoissonInt(4.0) << std::endl;
return 0;
}
StringHelper
Заголовочный файл StringHelper.hpp
содержит в себе весь функционал для работы со строковой информацией.
Пример использования:
#include "StringHelper.hpp"
int main()
{
// Создаем объект StringHelper с исходной строкой
CUtils::StringHelper str(" Hello, World! ");
// Убираем пробелы в начале и конце строки
std::string trimmed = str.Trim();
std::cout << "Trimmed: " << trimmed << std::endl;
// Преобразуем строку в нижний регистр
std::string lower = str.ToLowerCase();
std::cout << "Lower: " << lower << std::endl;
// Преобразуем строку в верхний регистр
std::string upper = str.ToUpperCase();
std::cout << "Upper: " << upper << std::endl;
// Разделяем строку на подстроки по пробелу
std::vector<std::string> tokens = str.Split(' ');
std::cout << "Tokens: ";
for (const auto& token : tokens)
{
std::cout << token << " ";
}
std::cout << std::endl;
// Проверяем, содержит ли строка подстроку "World"
bool contains = str.Contains("World");
std::cout << "Contains 'World': " << contains << std::endl;
// Проверяем, начинается ли строка с подстроки "Hello"
bool startsWith = str.StartsWith("Hello");
std::cout << "StartsWith 'Hello': " << startsWith << std::endl;
// Проверяем, заканчивается ли строка на подстроку "!"
bool endsWith = str.EndsWith("!");
std::cout << "EndsWith '!': " << endsWith << std::endl;
// Заменяем все вхождения "World" на "Universe"
std::string replaced = str.Replace("World", "Universe");
std::cout << "Replaced: " << replaced << std::endl;
// Находим индекс первого вхождения подстроки "World"
int index = str.IndexOf("World");
std::cout << "Index of 'World': " << index << std::endl;
// Находим индекс последнего вхождения символа 'o'
int lastIndex = str.LastIndexOf("o");
std::cout << "Last index of 'o': " << lastIndex << std::endl;
// Извлекаем подстроку, начиная с индекса 7 длиной 5 символов
std::string substr = str.Substring(7, 5);
std::cout << "Substring: " << substr << std::endl;
// Извлекаем числовую часть из строки (если она есть)
std::string numericPart = str.ExtractNumericPart();
std::cout << "Numeric part: " << numericPart << std::endl;
// Проверяем, является ли строка пустой
bool isEmpty = str.IsEmpty();
std::cout << "Is empty: " << isEmpty << std::endl;
// Получаем длину строки
size_t length = str.Length();
std::cout << "Length: " << length << std::endl;
// Преобразуем строку в целое число
std::optional<int> intValue = str.ToInt();
std::cout << "ToInt: " << (intValue.has_value() ? std::to_string(intValue.value()) : "Invalid") << std::endl;
// Преобразуем строку в число с плавающей точкой
std::optional<double> doubleValue = str.ToDouble();
std::cout << "ToDouble: " << (doubleValue.has_value() ? std::to_string(doubleValue.value()) : "Invalid") << std::endl;
// Преобразуем строку в логическое значение
std::optional<bool> boolValue = str.ToBool();
std::cout << "ToBool: " << (boolValue.has_value() ? (boolValue.value() ? "true" : "false") : "Invalid") << std::endl;
return 0;
}
Конец
Кроссплатформенная коллекция утилитных компонентов CUtils распространяется под лицензией MIT. Вы можете делать с её исходным кодом абсолютно всё!
Ссылки
Наш сайт - https://case-technologies.ru/
Наш GitHub - https://github.com/case-tech