Привет, Хабр!

Во время разработки ядра игрового движка Case Engine нам понадобились различные утилиты — от парсера INI файлов до удобных инструментов для работы с потоками. Чтобы всё это было удобно и лаконично, а другие разработчики могли легко интегрировать эти решения в свои проекты, мы создали кроссплатформенную коллекцию утилит CUtils. На данный момент она достигла версии 2.0.0!

В этой статье мы не будем подробно останавливаться на компонентах первой версии CUtils, о которых мы рассказывали в предыдущем материале. Вместо этого мы сосредоточимся на новых возможностях, которые появились в версии 2.0.0.

Статья о первой версии CUtilshttps://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, так как его использование оказалось не слишком удобным. Однако вместо этого добавили множество других компонентов:

  1. Coroutine

  2. DataTime

  3. Encryption

  4. Filesystem

  5. Fusion

  6. Macroses

  7. Multithread

  8. Plugins

  9. Random

  10. 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

Комментарии (0)