Воскресенье, 28 Апреля 2024, 00:09

Приветствую Вас Гость

[ Новые сообщения · Игроделы · Правила · Поиск ]
  • Страница 1 из 2
  • 1
  • 2
  • »
Форум игроделов » Ваши проекты » Разработка движков и сред разработки » Разработка движка (детальное описание для новичков) [C++] (BADCOIQ Engine)
Разработка движка (детальное описание для новичков) [C++]
BADCOIQДата: Суббота, 01 Июля 2023, 16:07 | Сообщение # 1
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Все сообщения здесь
https://github.com/badcoiq/badcoiq/wiki

Хочу написать библиотеку которую буду использовать как для игр так и для простого и не очень ПО.
В данной теме буду публиковать детальное описание своим действиям.

Главная цель это показать действия тем людям кто хочет написать своё и не знает с чего начать.
Как говорят, кто ничего в жизни не добился, идут в учителя. Но тем не менее я считаю себя достаточно опытным.

Всё уже написано по миллиону раз. Это будет финальная итерация.

Код и архитектура не претендует на эталон. Но я напишу определённые моменты к которым я пришёл, и я считаю их истинно верными.

Репозиторий находится здесь https://github.com/badcoiq/badcoiq

Какой результат я ожидаю? Хочу сделать минимальный набор функций и набор редакторов (написанные на этой библиотеке).
Именно набор а не одну единственную программу всё-в-одном.
Графика будет в последнюю очередь, я её даже не включил в список дел.

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



Необходимо время для того чтобы я написал Начало...


"Do you think we can fly?
Well, I do."


Сообщение отредактировал BADCOIQ - Вторник, 11 Июля 2023, 17:34
gravemanДата: Суббота, 01 Июля 2023, 16:33 | Сообщение # 2
почетный гость
Сейчас нет на сайте

Пишу (очень медленно) лайтовый (то есть не все классы), но расширенный аналог STL. Расширенный в смысле новыми структурами данных и математикой (в т.ч. геометрией). Заточено под геймдев.
Ссылку пока не оставлю. Добью сначала основные контейнеры.
Цитата
Необходимо время для того чтобы я написал Начало...

Мне же больше необходим глубокий сон и энергия. Мля... не могу выспаться нормально. Поэтому, все очень долго тянется.



Цитата
Графика будет в последнюю очередь, я её даже не включил в список дел.


Сообщение отредактировал graveman - Суббота, 01 Июля 2023, 16:40
BADCOIQДата: Суббота, 01 Июля 2023, 16:36 | Сообщение # 3
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Цитата graveman ()
Ссылку пока не оставлю

Дай мне посмотреть.


"Do you think we can fly?
Well, I do."
gravemanДата: Суббота, 01 Июля 2023, 16:44 | Сообщение # 4
почетный гость
Сейчас нет на сайте
Цитата BADCOIQ ()
Дай мне посмотреть.

Да пока не хочу. Там только вспомогательное метапрограммирование есть. Существенный функционал локально на компе (ассерт, часть вектора). Могу только сказать, что делаю по всем канонам STL (стараюсь). Как будет выложен vector, shared_ptr, assert, string, то дам ссылку. Интерфейс будет тот же, только будет макрос для выключения эксепшнов.
Юнит-тесты только нужны будут на catch

Добавлено (01 Июля 2023, 16:58)
---------------------------------------------
Короче, я тоже пришел к определенным выводам. Многочисленные безуспешные и малоуспешные попытки написать движок у меня всегда сводились к написанию улучшенных или недостающих штук из STL, окна и вывода кубика-чайника или модели в окне. Осознав это, я решился писать эту библиотеку. Раз и навсегда. Вектор планировалось сделать еще в феврале. Но я споткнулся о метапрограммирование, был много времени в депрессии и хроническом недосыпе. Теперь я более-менее могу понимать исходный код STL.

Сообщение отредактировал graveman - Суббота, 01 Июля 2023, 16:48
BADCOIQДата: Суббота, 01 Июля 2023, 17:02 | Сообщение # 5
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Цитата graveman ()
Вектор

Мне по вектору, нужен такой, который методом clear просто сделает

Код

m_size = 0;


а не освободит память совсем.


"Do you think we can fly?
Well, I do."
gravemanДата: Суббота, 01 Июля 2023, 17:07 | Сообщение # 6
почетный гость
Сейчас нет на сайте
Цитата BADCOIQ ()
Мне по вектору, нужен такой, который методом clear просто сделает m_size = 0;
а не освободит память совсем.

Так он и не освобождает память. Он ее освободит только в деструкторе или же при достижении size() == capacity(). clear() вызывает деструкторы элементов. Вот не помню, как там насчет POD-типов. Но если уж не хочется их вообще вызывать, то сделаю специализированную версию. Но процентов 90, что если деструктор тривиальный, он не вызовется при clear().


Сообщение отредактировал graveman - Суббота, 01 Июля 2023, 17:10
BADCOIQДата: Суббота, 01 Июля 2023, 19:58 | Сообщение # 7
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Начало

Что необходимо сделать чтобы получился ДВИЖОК?
Сначала надо освоить создание библиотек.
Как только получится вызвать функцию из библиотеки, сразу придёт понимание что такое этот ваш ДВИЖОК - это просто библиотека, или их набор.

Библиотеки могут быть динамическими или статическими.
Динамическую библиотеку можно использовать во множестве языков программирования. Она имеет вид отдельного файла.

Статические библиотеки это набор кодов, которые становятся частью программы, они вшиваются в исполняемый файл.
Эти библиотеки нельзя использовать в иных ЯП. Наверно.

Здесь, я буду использовать статические библиотеки. Почему:
- всё быстро компилируется
- их проще подключить (для Visual Studio)

Минус статической библиотеки в том что нужно использовать конкретную версию компилятора, это касается VS, на GCC их не писал.
Поэтому при начале работы, лучше скомпилировать библиотеки тем компилятором что есть.

Мысли о коде
Я не имею опыт современного C++. Даже не знаю есть ли C++23 и далее, и вообще что там было после C++17, даже не интересно.
Что-то увижу полезное, приму. Но никаких концептов, модулей, жёсткого STL и т.д.

* Запрещено использовать STL в API.
Необходимо написать свои вещи.

* Общий стиль кода должен быть простым в стиле СИ.
Имена функций и пользовательских типов должны иметь префикс, в моём случае я буду использовать `bq`.
Сишные префиксы гораздо удобнее использования namespace.

* Вообще namespace даёт кучу неудобностей, поэтому запрещено использовать его в API. Допускается его использование для внутренних вещей.
Код

namespace bq
{
    namespace internal
    {
    }
}


* Фигурные скобки тел функций и пользовательских типов должны быть на уникальной строке.
* Тела if for и прочих могут находится на той-же строке что и инструкция.

* Имена для API нужно писать в CamelCase. Код проще редактировать, не нужно тратить время на ввод симола `_`
Получается, имена будут выглядить так: bqPolygonMesh

* Использование имён_такого_типа допускается только в классах которые схожи с классами в STL.

* В коде лучше использовать сишные\сырые массивы. Ненужно использовать динамические если без них не обойтись.

* Допускается использование макросов, но не сложных.
Например, запрещено использовать макросы для генерации функций
Код

DECLARE_FUNC(One)
DECLARE_FUNC(Two)
DECLARE_FUNC(Three)


где на выходе мы получаем
Код

что_то_там_One_что_то_там
что_то_там_Two_что_то_там
что_то_там_Three_что_то_там


* Использовать строки char* в API или свой класс bqString.
Использовать wchar_t* для открытия файла не имеет смысла. char* работает подобно UTF-8.

* Использование uint8_t и подобных имён для стандартных типов.

* Запрещено кидать исключения.

* В пользовательских типах, имена переменных должны иметь префикс 'm_',
имена открытых методов должны начинаться с Большой буквы,
имена закрытых методов должны начинаться с символа `_`.

* Наследование используется только для классов.

Мысль об архитектуре
Не нужно писать супер класс который всё сможет сделать.
Лучше написать простой класс, потом поверх него чуть сложнее, потом ещё сложнее если надо. Типа слой за слоем.
И эти сложные слои должны находиться на стороне программы.
Например, работа со сценой, рисование теней и т.д. Всё должно делаться на стороне программы.

Создаваемые вещи должны быть максимально независимыми. Если делаем рендер с уникальными технологиями, то более простые рендеры всё равно должны работать.

Шаблоны проектирования под сомнением. Их не будет до той поры пока я не осознаю их пользу.

Внутри основной библиотеки будет главный класс Framework. Он будет содержать все необходимые вещи, будет инициализировать библиотеки, иметь загрузчики ресурсов и т.д.
Для простоты будет глобальный указатель на объект данного класса. Простая глобальная переменная. И ненужны никакие заумные вещи.

В общем, главное простота, а не выпендрёж.

Для простых вещей будут использоваться классы со статическими методами.

Структура проекта
Я использую Visual Studio 2022. Буду использовать его возможности.

Папки:
`build` - там лежат файлы проектов
`inc` - #include файлы движка
`libs` - туда прилетят скомпилированные библиотеки
`src` - тут лежат исходные коды движка и сторонних библиотек.

По ходу прогресса появятся папки
`data` - ресурсы для демки
`bin32\bin64` - демка и инструмены
`tools` - исходные коды для инструментов

Создание проекта Visual Studio
Я всегда создаю пустые (Empty) проекты, и настраиваю их.
Некоторые проекты я просто копирую в проводнике, редактирую в Notepad и кидаю в Visual Studio.



После создания проекта, надо добавить какой нибудь файл с исходным кодом.
В папке 'src' надо создать папку для исходного кода движка.
Далее создать текстовый файл и переименовать его установив расширение .cpp
Далее кидаем этот файл в Visual Studio.



Это надо сделать, потому что Visual Studio не показывает C/C++ параметры если в проекте нет файла с исходным кодом.

Содержимое данного файла


В каждом файле (.h .c .c++ .cxx и т.д.) должна быть указана лицензия.
Так-же практически в каждом файле (.c .cpp) потребуется минимальный набор include файлов.
Все эти файлы указаны в `badcoiq.h`

Надо создать badcoiq.h
Он будет находится в папке `inc`.
Просто копирую badcoiq.cpp и изменю расширение.



Теперь настрою параметры проекта.



Параметр Target Name. В результате имя библиотеки получиться типа такое

badcoiq_v143_x86_Debug.lib

Укажу include папку



Для Release укажу оптимизацию



Можно изменить Floating Point Model
Fast даст чуть скорости.



Указываю NODEFAULTLIB
Возможно они и так будут игнориться, но укажу на всякий случай.



Всё, настройка завершена.

Далее нажимаю в редакторе правой кнопкой по badcoiq.h и выбираю Go To Document
Для быстроты я привык нажимать просто Ctrl+Shift+G


Откроется badcoiq.h и можно приступить к его редактированию.

В данном моменте там должна быть лицензия и include guard

Код

#pragma once
#ifndef __BQ_BADCOIQ_H__
#define __BQ_BADCOIQ_H__

#endif


Указываю #pragma once тоже, первее include guard

Всё, можно строить.

В папке libs будут такие файлы



В Visual Studio можно включить кнопку для построения. Правой кнопкой по GUI и выбираем Build.
Добавлю кнопку Compile



Осталось отредактировать .gitignore и сделать коммит

Код


build/badcoiq/.vs
build/badcoiq/x64
build/badcoiq/Debug
build/badcoiq/Release
*.idb
*.pdb
*.lib



github


"Do you think we can fly?
Well, I do."


Сообщение отредактировал BADCOIQ - Суббота, 01 Июля 2023, 20:14
BaadumsooДата: Суббота, 01 Июля 2023, 23:52 | Сообщение # 8
был не раз
Сейчас нет на сайте
Цитата BADCOIQ ()
Эти библиотеки нельзя использовать в иных ЯП. Наверно.
- экспертиза на уровне...
Цитата BADCOIQ ()
* Вообще namespace даёт кучу неудобностей, поэтому запрещено использовать его в API
, а как же сокрытие/инкапсуляция?
Цитата BADCOIQ ()
Шаблоны проектирования под сомнением. Их не будет до той поры пока я не осознаю их пользу.
, мда, движкописатель...
BADCOIQДата: Воскресенье, 02 Июля 2023, 06:58 | Сообщение # 9
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Макросы

Добавлю файл, который будет подключать стандартные файлы, и в котором будут макросы.
Что такое макрос и для чего они нужны.

Макросы это то что создаётся используя слово define

Код

#define ABC


Они могут быть в виде функций.

Их используют чтобы добиться мультиплатформенного кода, для краткости написания кода и для много чего.
Вставляешь макрос, и на его месте будет то что было справа от него.

Новый файл будет иметь имя

inc/badcoiq/common/bqDefines.h

Туда добавляю

Код

#include <stddef.h> // NULL, size_t
#include <stdlib.h>
#include <assert.h>
#include <stdint.h> // int32_t uint32_t и другие
#include <ctype.h>  // isspace
#include <utility>  // std::move


Возможно ненужная вещь. Пережиток прошлого и всё такое. Многие библиотеки используют это.
`inline` не гарантирует что функция будет inline. Предположу что "не гарантирует" если функция написана в .c .cpp файлах(или файл инклюдится в них), ведь такие функции не встраиваемые, по умолчанию.
Вместо inline теперь надо писать BQ_FORCEINLINE
Код

#ifdef _MSC_VER
#define BQ_FORCEINLINE __forceinline
#else
#define BQ_FORCEINLINE inline
#endif


Для координат в 3D, матриц и т.д. надо дать возможность указать что использовать, double или float.
Такому типу обычно дают имя `real`
Код

#define bqReal double

Можно было написать using bqReal = double; но ведь это файл с макросами + Visual Studio окрашивает макросы в особый цвет.

Определяем платформу
Код

#if defined(WIN32) | defined(_WIN64) | defined(_WIN32)
#define BQ_PLATFORM_WINDOWS
#else
#error Только для Windows
#endif


Для использования Си (функций из .c файлов) внутри C++
Код

#ifdef BQ_PLATFORM_WINDOWS
#define BQ_CDECL _cdecl
#else
#error Только для Windows
#endif


Свой макрос для дебага
Код

#ifdef _DEBUG
#define BQ_DEBUG
#endif


Упаковка 4х байтов в один uint32_t
// uint32_t magic = BQ_MAKEFOURCC('‰', 'P', 'N', 'G');
Код

#define BQ_MAKEFOURCC( ch0, ch1, ch2, ch3 )\
    ((uint32_t)(uint8_t)(ch0)|((uint32_t)(uint8_t)(ch1)<<8)|\
    ((uint32_t)(uint8_t)(ch2)<<16)|((uint32_t)(uint8_t)(ch3)<<24))


Надо узнать, является ли конфигурация 64бит
Код

#ifdef BQ_PLATFORM_WINDOWS
#if defined _WIN64 || defined __x86_64__
#define BQ_BIT_64
#endif
#else
#error Только для Windows
#endif


Для показа имени файла, строки и функции
// printf("%s %i %s\n", BQ_FILE, BQ_LINE, BQ_FUNCTION);
Код

#ifdef BQ_PLATFORM_WINDOWS
#define BQ_FILE __FILE__
#define BQ_LINE __LINE__
#define BQ_FUNCTION __FUNCTION__
#else
#error Только для Windows
#endif


Для быстрого поключения .lib файлов в Visual Studio
Код

#ifdef _MSC_VER

// Версия компилятора Visual Studio
#if _MSC_VER >= 1930 && _MSC_VER <= 1936
#define BQ_LINK_LIBRARY_CMP "_v143"
#elif _MSC_VER >= 1920 && _MSC_VER <= 1929
#define BQ_LINK_LIBRARY_CMP "_v142"
#elif _MSC_VER >= 1910 && _MSC_VER <= 1916
#error Не проверено.
#define BQ_LINK_LIBRARY_CMP "_v141"
#endif

#ifdef BQ_BIT_64
#define BQ_LINK_LIBRARY_ARCH "_x64"
#else
#define BQ_LINK_LIBRARY_ARCH "_x86"
#endif

#ifdef BQ_DEBUG
#define BQ_LINK_LIBRARY_CONF "_Debug"
#else
#define BQ_LINK_LIBRARY_CONF "_Release"
#endif

// Использование : BQ_LINK_LIBRARY("libname")
#define BQ_LINK_LIBRARY(n) \
    __pragma(comment(lib, n BQ_LINK_LIBRARY_CMP BQ_LINK_LIBRARY_ARCH BQ_LINK_LIBRARY_CONF ".lib"))

#endif


Полезная вещь. Например имеем 0xAABBCCDD
Можно получить 0xAABB и 0xCCDD
Код

#define BQ_LO32(l) ((uint16_t)(((uint32_t)(l)) & 0xffff))
#define BQ_HI32(l) ((uint16_t)((((uint32_t)(l)) >> 16) & 0xffff))





Подключаю файл в главном .h файле
Код

#include "badcoiq/common/bqDefines.h"


github

Добавлено (02 Июля 2023, 09:14)
---------------------------------------------
Память

Я всегда делал DLL библиотеки, и требовалось чтобы выделение и освобождение памяти происходило в одном месте.
Если в DLL память была выделена используя new, а в EXE она будет delete, то может так случиться что эти new и delete будут из разных мест (различные Run Time).
От этого произойдёт ошибка и программа упадёт.
Решение проблемы - создать место которое будет выделять\освобождать память.

Возможно для статических библиотек это не проблема. Но я планирую использовать .dll как плагины.
Поэтому надо это создать и использовать только эти функции.

inc/badcoiq/common/bqMemory.h

Простой класс.
Код

class bqMemory
{
public:
    static void* malloc(size_t size);
    static void* calloc(size_t size);
    static void  free(void*);
    static void* realloc(void*, size_t size);
};


Его реализация

src/badcoiq/common/bqMemory.cpp

Код

#include "badcoiq.h"

#ifdef BQ_PLATFORM_WINDOWS
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
// Windows функции для работы с памятью требуют Process Heap
// Ничего страшного в глобальной переменной нет.
static HANDLE g_processHeap = GetProcessHeap();
#endif

void* bqMemory::malloc(size_t size)
{
#ifdef BQ_PLATFORM_WINDOWS
    return HeapAlloc(g_processHeap, 0, size);
#else
    return ::malloc(size);
#endif
}

void* bqMemory::calloc(size_t size)
{
#ifdef BQ_PLATFORM_WINDOWS
    return HeapAlloc(g_processHeap, HEAP_ZERO_MEMORY, size);
#else
    return ::calloc(1, size);
#endif
}

void bqMemory::free(void* ptr)
{
#ifdef BQ_PLATFORM_WINDOWS
    HeapFree(g_processHeap, 0, ptr);
#else
    ::free(ptr);
#endif
}

void* bqMemory::realloc(void* ptr, size_t size)
{
#ifdef BQ_PLATFORM_WINDOWS
    return HeapReAlloc(g_processHeap, HEAP_ZERO_MEMORY, ptr, size);
#else
    return ::realloc(ptr, size);
#endif
}

Вызываем функции Windows напрямую.

Возвращаюсь в bqMemory.h

Данные функции неудобно использовать для замены new и delete
Потому что для объектов классов нужно вызывать конструктор и деструктор.
Добавлю две функции которые будут выделять\освобождать память и вызывать конструктор\деструктор.
Код

template<typename _type, typename... Args>
_type* bqCreate(Args&&... args)
{
    _type* ptr = (_type*)bqMemory::malloc(sizeof(_type));
    if (ptr)
  ::new(ptr) _type(std::forward<Args>(args)...);// вызов конструктора
    return ptr;
}

template<typename _type>
void bqDestroy(_type* ptr)
{
    assert(ptr);
    ptr->~_type();// вызов деструктора
    bqMemory::free(ptr);
}

Для шаблонных функций force inline не нужен, так как они все получаются встраиваемыми.

Используется так
Код

MyClass* o = bqCreate<MyClass>(param1, param2, param3);
if(o)
    bqDestroy(o);


Тоже не очень удобно.
Можно добавить placement new\delete. Это для классов.
Код

#define BQ_PLACEMENT_ALLOCATOR(c) void* operator new(std::size_t count){ return (c*)bqMemory::malloc(count);}\
void operator delete(void* ptr){if (ptr) bqDestroy(ptr);}


Используется так
Код

class MyClass
{
public:
    MyClass(){
  printf("C1\n");
    }

    MyClass(int a, int b, int c){
  printf("C2\n");
    }

    ~MyClass(){
  printf("D\n");
    }

    BQ_PLACEMENT_ALLOCATOR(MyClass);
};

void f()
{
    /*MyClass* o = bqCreate<MyClass>();
    if (o)
  bqDestroy(o);*/

    MyClass* o = new MyClass(1,2,3);
    if (o)
  delete o;
}


github

Добавлено (02 Июля 2023, 11:22)
---------------------------------------------
DLL

Изначально был план дать возможность использовать библиотеку как DLL.
Добавил функции для работы с DLL.
Потом отказался от поддержки DLL версии, так как много возни.
Но сами функции пригодятся в будущем.

Новый include файл

inc/badcoiq/system/bqDLL.h

Подобно bqMemory, это класс со статическими методами.

Код

// надо завести свои имена для хэндла и указателя на функцию
typedef void* bqDLLHandle;   // HMODULE в Windows
typedef void* bqDLLFunction;

class bqDLL
{
public:

    // Загрузить динамическую библиотеку
    // Вернётся указатель если загрузилось.
    // При завершении работы надо вызвать free и передать bqDLLHandle
    static bqDLLHandle load(const char* libraryName);

    // Выгрузить библиотеку
    static void free(bqDLLHandle library);

    // Получить функцию из библиотеки
    // Вернёт NULL если была какая-то ошибка.
    static bqDLLFunction get_proc(bqDLLHandle library, const char* functionName);
};


Реализация
src/badcoiq/system/bqDLL.cpp

Код

#include "badcoiq.h"

#ifdef BQ_PLATFORM_WINDOWS
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#endif

bqDLLHandle bqDLL::load(const char* libraryName)
{
    BQ_ASSERT_ST(libraryName);
#ifdef BQ_PLATFORM_WINDOWS
    return (bqDLLHandle)LoadLibraryA(libraryName);
#else
#error Для Windows
#endif
}

void bqDLL::free(bqDLLHandle library)
{
    BQ_ASSERT_ST(library);
#ifdef BQ_PLATFORM_WINDOWS
    FreeLibrary((HMODULE)library);
#else
#error Для Windows
#endif
}

bqDLLFunction bqDLL::get_proc(bqDLLHandle library, const char* functionName)
{
    BQ_ASSERT_ST(library);
    BQ_ASSERT_ST(functionName);
#ifdef BQ_PLATFORM_WINDOWS
    return (bqDLLFunction)GetProcAddress((HMODULE)library, functionName);
#else
#error Для Windows
#endif
}

Просто обёртка для функций ОС. Более сложной штуки и не надо.

В данном месте начинает использоваться свой assert.

github

Добавлено (02 Июля 2023, 13:02)
---------------------------------------------
Assert

assert выполняет проверку на истинность
Это макрос, который содержит if. Если что-то плохо, то выполняется код в теле этого if.

Код

#ifdef BQ_DEBUG
#define BQ_ASSERT(expression) if((expression) == 0){bq::internal::OnAssert(#expression, BQ_FILE, BQ_LINE, 0);}
#define BQ_ASSERT_ST(expression) if((expression) == 0){bq::internal::OnAssert(#expression, BQ_FILE, BQ_LINE, bq::internal::flag_onAssert_useDefaultSystemWindow | bq::internal::flag_onAssert_useStackTrace);}
#else
#define BQ_ASSERT(expression) ((void)0)
#define BQ_ASSERT_ST(expression) ((void)0)
#endif


Если ошибка произойдёт, то в функцию передадутся выражение используемое в if (#expression), имя файла и строка где находится BQ_ASSERT.

assert работает только в debug режиме. Его нужно добавлять в код везде где можно, в релизной версии там будет пустота.

BQ_ASSERT вызвет функцию bq::internal::OnAssert()
Эта функция должна быть реализована в библиотеке.
Она должна использовать указатель на функцию - дефолтная OnAssert
Дефолтную функцию можно изменить, послав свой указатель функцией SetOnAssert.
...этот код должен быть выше макросов BQ_ASSERT
Код

// Скрою в неймспейсах вспомогательные функции
namespace bq
{
    namespace internal
    {
        enum flag_onAssert
        {
            // использовать системное окно которое показывается как при использовании assert()
            flag_onAssert_useDefaultSystemWindow = 0x1,

            // использовать stack tracer
            flag_onAssert_useStackTrace = 0x2,
        };

        // макросы BQ_ASSERT будут вызывать эту функцию
        void BQ_CDECL OnAssert(const char* message, const char* file, uint32_t line, uint32_t flags = 0);
  
  // установить свою функцию
        void BQ_CDECL SetOnAssert(void(*)(const char* message, const char* file, uint32_t line, uint32_t flags));
    }
}


Теперь реализация
src/badcoiq/common/bqAssert.cpp

Код

#include "badcoiq.h"

#ifdef BQ_PLATFORM_WINDOWS
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#endif

// Дефолтная функция для assert
// Надо раздокументировать когда будут готовы log, stacktracer и string
void bqOnAssert_default(const char* message, const char* file, uint32_t line, uint32_t flags)
{
#ifdef BQ_DEBUG
    if ((flags & bq::internal::flag_onAssert_useStackTrace) == bq::internal::flag_onAssert_useStackTrace)
    {
        //bqStackTracer::Print();
    }

    if ((flags & bq::internal::flag_onAssert_useDefaultSystemWindow) == bq::internal::flag_onAssert_useDefaultSystemWindow)
    {
        //bqString str_msg(message);
        //bqString str_file(file);

        //bqStringW str1;
        //str_msg.to_utf16(str1);

        //bqStringW str2;
        //str_file.to_utf16(str2);

#ifdef BQ_PLATFORM_WINDOWS
        //_wassert((const wchar_t*)str1.m_data, (const wchar_t*)str2.m_data, line);
#endif
    }
    else
    {
        //printf("%s %s %i\n", message, file, line);
       //bqlLog::PrintError("%s %s %i\n", message, file, line);
#ifdef BQ_PLATFORM_WINDOWS
        DebugBreak();
#endif
    }
#endif
}

// Указатель на функцию. Можно будет изменять функцию OnAssert используя функцию SetOnAssert
static void(*g_onAssert)(const char* message, const char* file, uint32_t line, uint32_t flags) = bqOnAssert_default;

namespace bq
{
    namespace internal
    {
        // просто вызываем g_onAssert
        void BQ_CDECL OnAssert(const char* message, const char* file, uint32_t line, uint32_t flags)
        {
            g_onAssert(message, file, line, flags);
        }
    
        // Изменяем g_onAssert. Или устанавливаем дефолтную функцию.
        void BQ_CDECL SetOnAssert(void(*f)(const char* message, const char* file, uint32_t line, uint32_t flags))
        {
            g_onAssert = f ? f : bqOnAssert_default;
        }
    }
}



github


"Do you think we can fly?
Well, I do."


Сообщение отредактировал BADCOIQ - Воскресенье, 02 Июля 2023, 09:35
Storm54Дата: Воскресенье, 02 Июля 2023, 15:43 | Сообщение # 10
постоянный участник
Сейчас нет на сайте
Главное доделать до 1 сентября.
BADCOIQДата: Воскресенье, 02 Июля 2023, 15:56 | Сообщение # 11
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Строки

В своём assert я хотел иметь возможность использовать дефолтную Visual Studio функцию _wassert, но она требует wchar_t строку.
Когда-то я замарочился и сделал 100% рабочий конвертер UTF-8\16\32
Я подумал и решил, что не нужен мне сам конвертер. Мне нужна UTF-32 строка.
Эта строка должна иметь методы для конвертации.

Эту строку лучше использовать для текста который мы будем рисовать.

Для внутреннего использования необходимы более простые строки.
Начну с них.

Полностью описывать классы будет очень долго. В коде есть комментарии.
В общем всё элементарно.
Можно объяснить основные принципы.

Класс строки содержит массив символов + 1 символ для завершающего нуля.
При создании объекта надо заранее выделить немного памяти.
При добавлении символов, если необходимо, перевыделяем память.
Код

void _reallocate(size_t new_allocated)
{
    // выделение новой памяти, копирование содержимого, освобождение старой, сохранение нового указателя
    char_type* new_data = (char_type*)bqMemory::malloc(new_allocated * sizeof(char_type));
    if (m_data)
    {
  memcpy(new_data, m_data, m_size * sizeof(char_type));
  bqMemory::free(m_data);
    }
    else
    {
  memset(new_data, 0, new_allocated);
    }
    m_data = new_data;
    m_allocated = new_allocated;
}


Главное реализовать функцию push_back.
Все остальные функции (append assign operator=) и конструкторы в итоге будут вызывать push_back.
Код

template<typename other_type>
bqString_base(const other_type* str)
{
    _reallocate(m_allocated);
    assign(str); // просто присвоение
}
...
// присвоить содержимое другой строки
template<typename other_type>
void assign(other_type str)
{
    clear();
    if (str)
  append(str);
}
...
template<typename other_type>
void append(const other_type* str)
{
    append(str, bqStringLen(str));
}
...
// добавить в конец
template<typename other_type>
void append(const other_type* str, size_t l)
{
    for (size_t i = 0; i < l; ++i)
    {
  push_back((char_type)str[i]);
    }
}
...
// добавить в конец символ
void push_back(char_type c)
{
    size_t new_size = m_size + 1u;
    if ((new_size + 1u) > m_allocated)
  _reallocate((new_size + 1u) + (1 + (uint32_t)(m_size * 0.5f)));
    m_data[m_size] = c;
    m_size = new_size;
    m_data[m_size] = 0;
}


Очень необходимо реализовать добавление чисел. Например float:
Код

void append(float f)
{
    char buf[32];
    int nob = sprintf_s(buf, 32, "%.4f", f);
    if (nob)
    {
  for (int i = 0; i < nob; ++i)
  {
   push_back((char_type)buf[i]);
  }
    }
}


Полный код шаблонной строки не влезает в сообщение.
Реализовано куча полезных методов.


В конце были созданы type alias: bqStringW и bqStringA

Теперь Unicode строка.
Класс огромный, и будет иметь реализацию в .cpp файле.
Используется тип char32_t


Теперь реализация
src/badcoiq/string/bqString.cpp

Для конвертации в UTF-8 и UTF-16 LE я использую таблицу, заранее вычисленную.
Код

struct slUnicodeCharNode
{
    uint32_t m_utf8; // код для UTF-8
    uint32_t m_utf16;// код для UTF-16
};
static slUnicodeCharNode g_UnicodeChars[0x32000] =
{
#include "UnicodeChars.inl" // коды здесь
};


Добавление char* и wchar_t* строк. По сути конвертация UTF-8 и UTF-16 в UTF-32
Код

// UTF-8 to UTF-32
void bqString::append(const char* str)
{
    size_t len = bqStringLen(str);

    unsigned char c1 = 0;
    unsigned char c2 = 0;
    unsigned char c3 = 0;
    unsigned char c4 = 0;
    for (size_t i = 0; i < len; ++i)
    {
  c1 = str[i];

  if (c1 <= 0x7F)
  {
   push_back((char32_t)c1);
  }
  else
  {
   if ((c1 & 0xE0) == 0xC0) //2 bytes
   {
    ++i;
    if (i < len)
    {
     c2 = str[i];
     if ((c2 & 0xC0) == 0x80)
     {
      char16_t wch = (c1 & 0x1F) << 6;
      wch |= (c2 & 0x3F);
      push_back((char32_t)wch);
     }
    }
   }
   else if ((c1 & 0xF0) == 0xE0) //3
   {
    ++i;
    if (i < len)
    {
     c2 = str[i];
     if ((c2 & 0xC0) == 0x80)
     {
      ++i;
      if (i < len)
      {
       c3 = str[i];
       if ((c3 & 0xC0) == 0x80)
       {
        char16_t wch = (c1 & 0xF) << 12;
        wch |= (c2 & 0x3F) << 6;
        wch |= (c3 & 0x3F);
        push_back((char32_t)wch);
       }
      }
     }
    }
   }
   else if ((c1 & 0xF8) == 0xF0) //4
   {
    ++i;
    if (i < len)
    {
     c2 = str[i];
     if ((c2 & 0xC0) == 0x80)
     {
      ++i;
      if (i < len)
      {
       c3 = str[i];
       if ((c3 & 0xC0) == 0x80)
       {
        ++i;
        if (i < len)
        {
         c4 = str[i];
         if ((c4 & 0xC0) == 0x80)
         {
                              uint32_t u = (c1 & 0x7) << 18;
                              u |= (c2 & 0x3F) << 12;
                              u |= (c3 & 0x3F) << 6;
                              u |= (c4 & 0x3F);

                              push_back((char32_t)u);
         }
        }
       }
      }
     }
    }
   }
  }
    }
}

// UTF-16 to UTF-32
void bqString::append(const wchar_t* str)
{
    size_t len = bqStringLen(str);
    for (size_t i = 0u; i < len; ++i)
    {
  char16_t ch16 = str[i];
  if (ch16 < 0x80)
  {
   push_back((char32_t)ch16);
  }
  else if (ch16 < 0x800)
  {
   push_back((char32_t)ch16); //????
  }
  else if ((ch16 & 0xFC00) == 0xD800)
  {
   ++i;
   if (i < len)
   {
    char16_t ch16_2 = str[i];
    if ((ch16_2 & 0xFC00) == 0xDC00)
    {
     uint32_t u = (ch16 - 0xD800) * 0x400;
     u += (ch16_2 - 0xDC00) + 0x10000;

     push_back((char32_t)u);
    }
   }
  }
  else
  {
   push_back((char32_t)ch16);
  }
    }
}


Конвертация в UTF-8 и UTF-16
Код

// для to_utf8 to_utf16.
union UC
{
    uint8_t bytes[4];
    uint16_t shorts[2];
    uint32_t integer;
};
...
void bqString::to_utf8(bqStringA& stra) const
{
    stra.clear();
    UC uc;
    auto ut = &g_UnicodeChars[0];
    for (size_t i = 0; i < m_size; ++i)
    {
  char32_t c = m_data[i];
  if (c >= 0x32000)
   c = '?';

  uc.integer = ut[c].m_utf8;

  if (uc.bytes[3]) stra.push_back(uc.bytes[3]);
  if (uc.bytes[2]) stra.push_back(uc.bytes[2]);
  if (uc.bytes[1]) stra.push_back(uc.bytes[1]);
  if (uc.bytes[0]) stra.push_back(uc.bytes[0]);
    }
}

void bqString::to_utf16(bqStringW& strw) const
{
    strw.clear();
    UC uc;
    auto ut = &g_UnicodeChars[0];
    for (size_t i = 0; i < m_size; ++i)
    {
  char32_t c = m_data[i];
  if (c >= 0x32000)
   c = '?';

  uc.integer = ut[c].m_utf16;

  if (uc.shorts[1]) strw.push_back(uc.shorts[1]);
  if (uc.shorts[0]) strw.push_back(uc.shorts[0]);
    }
}


Полный код не влезает в сообщение. В общем-то там много дублирования простой строки. Ещё есть дополнительные методы типа to_upper.

github

Добавлено (02 Июля 2023, 17:51)
---------------------------------------------
Stacktracer
Stacktracer покажет текущее место выполнения программы.

Например:

Код

C:\Code\badcoiq\src\badcoiq\system\bqStacktracer.cpp(190):'bqStackTracerImpl::GetStackTrace'
C:\Code\badcoiq\src\badcoiq\system\bqStacktracer.cpp(200):'bqStackTracer::Print'
C:\Code\badcoiqConsoleApplication1\badcoiqConsoleApplication1.cpp(9):'main'
D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl(78):'invoke_main'
D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl(288):'__scrt_common_main_seh'
D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl(331):'__scrt_common_main'
D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_main.cpp(17):'mainCRTStartup'
(null)(0):'BaseThreadInitThunk'
(null)(0):'RtlGetAppContainerNamedObjectPath'
(null)(0):'RtlGetAppContainerNamedObjectPath'
(null)(0):'(unknown)'


Обычно используется во время ошибки.

Почему-то корректно работает только со статическими библиотеками.

inc/badcoiq/system/bqStacktracer.h

Код

class bqStackTracer
{
public:
    static void Print();
};


Реализация
src/badcoiq/system/bqStacktracer.cpp

Код взят из Havok
Постарался чуть изменить. В общем ничего особенного нет в плане авторского права, одни вызовы системных функций.

Грузим системные библиотеки, получаем указатели на функции, вызываем эти функции.
Детали слишком специфичны для ОС, не буду их описывать. Если почитать MSDN то всё станет понятно.

Код

#include "badcoiq.h"

#ifdef BQ_PLATFORM_WINDOWS
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <dbghelp.h>
typedef BOOL(__stdcall* tSymInitialize)(HANDLE hProcess, PSTR UserSearchPath, BOOL fInvadeProcess);
typedef BOOL(__stdcall* tSymCleanup)(HANDLE hProcess);
typedef DWORD(__stdcall* tSymGetOptions)();
typedef DWORD(__stdcall* tSymSetOptions)(DWORD SymOptions);
typedef BOOL(__stdcall* tSymFromAddr)(HANDLE hProcess, DWORD64 dwAddr, PDWORD64 pdwDisplacement, PSYMBOL_INFO Symbol);
typedef BOOL(__stdcall* tSymGetLineFromAddr64)(HANDLE hProcess, DWORD64 dwAddr, PDWORD64 pdwDisplacement, PIMAGEHLP_LINE64 Line);
typedef void(__stdcall* tRtlCaptureContext)(PCONTEXT context);
typedef USHORT(__stdcall* tRtlCaptureStackBackTrace)(ULONG FramesToSkip, ULONG FramesToCapture, PVOID* BackTrace, PULONG BackTraceHash);
#endif

class bqStackTracerImpl
{
    void _init();
public:
    bqStackTracerImpl();
    ~bqStackTracerImpl();

    void DumpStackTrace(const size_t* trace, int numtrace);
    int  GetStackTrace(size_t* trace, int maxtrace, int framesToSkip = 0);

    CRITICAL_SECTION m_lock;
    bool m_lockInitialized = false;
    bool m_initialized = false;
    HINSTANCE m_DbgHelpDll = 0;
    HINSTANCE m_Kernel32Dll = 0;
    tSymInitialize m_SymInitialize = 0;
    tSymCleanup m_SymCleanup = 0;
    tSymGetOptions m_SymGetOptions = 0;
    tSymSetOptions m_SymSetOptions = 0;
    tSymFromAddr m_SymFromAddr = 0;
    tSymGetLineFromAddr64 m_SymGetLineFromAddr64 = 0;
    tRtlCaptureContext m_RtlCaptureContext = 0;
    tRtlCaptureStackBackTrace m_RtlCaptureStackBackTrace = 0;
};

#define LOAD_FUNCTION(A) if(1) { m_##A = (t##A) GetProcAddress(m_DbgHelpDll, #A); } else
#define LOAD_FUNCTION_WARN(A) if(1) { m_##A = (t##A) GetProcAddress(m_DbgHelpDll, #A); NATIVE_WARN_IF( m_##A == NULL, "Could not load symbol " #A " from dbghelp.dll, version too old, but will continue without it."); } else

bqStackTracerImpl::bqStackTracerImpl()
{
    InitializeCriticalSection(&m_lock);
    m_lockInitialized = true;
}

bqStackTracerImpl::~bqStackTracerImpl()
{
    m_lockInitialized = false;
    DeleteCriticalSection(&m_lock);
    m_SymCleanup(GetCurrentProcess());
    if (m_DbgHelpDll)
  FreeLibrary(m_DbgHelpDll);
    if (m_Kernel32Dll)
  FreeLibrary(m_Kernel32Dll);
}

void bqStackTracerImpl::_init()
{
    if (!m_initialized)
    {
  EnterCriticalSection(&m_lock);
  if (!m_initialized)
  {
   m_DbgHelpDll = LoadLibraryA("dbghelp.dll");
   if (m_DbgHelpDll)
   {
    m_Kernel32Dll = LoadLibraryA("kernel32.dll");
    if (m_Kernel32Dll)
    {
     LOAD_FUNCTION(SymInitialize);
     LOAD_FUNCTION(SymCleanup);
     LOAD_FUNCTION(SymGetOptions);
     LOAD_FUNCTION(SymSetOptions);
     LOAD_FUNCTION(SymFromAddr);
     LOAD_FUNCTION(SymGetLineFromAddr64);

     m_RtlCaptureContext = (tRtlCaptureContext)GetProcAddress(m_Kernel32Dll, "RtlCaptureContext");
     m_RtlCaptureStackBackTrace = (tRtlCaptureStackBackTrace)GetProcAddress(m_Kernel32Dll, "RtlCaptureStackBackTrace");

     DWORD symOptions = m_SymGetOptions();
     symOptions |= SYMOPT_LOAD_LINES | SYMOPT_DEBUG | SYMOPT_DEFERRED_LOADS;
     m_SymSetOptions(symOptions);

     UINT prevErrMode = GetErrorMode();
     SetErrorMode(SEM_FAILCRITICALERRORS);
     BOOL initsymbols = m_SymInitialize(GetCurrentProcess(), NULL, TRUE);
     if (initsymbols == TRUE)
     {
      SetErrorMode(prevErrMode);
      m_initialized = true;
     }
    }
   }
  }
  LeaveCriticalSection(&m_lock);
    }

    if (!m_initialized)
    {
  if(m_DbgHelpDll)
   FreeLibrary(m_DbgHelpDll);
  if(m_Kernel32Dll)
   FreeLibrary(m_Kernel32Dll);
  m_DbgHelpDll = 0;
  m_Kernel32Dll = 0;
    }
}

void bqStackTracerImpl::DumpStackTrace(const size_t* trace, int maxtrace)
{
    _init();

    SYMBOL_INFO_PACKAGE sym;
    sym.si.SizeOfStruct = sizeof(sym.si);
    sym.si.MaxNameLen = sizeof(sym.name);

    HANDLE curproc = GetCurrentProcess();

    for (int i = 0; i < maxtrace; ++i)
    {
  uint64_t symaddress = trace[i];

  DWORD64 displacement = 0;
  BOOL ok_symbol = m_SymFromAddr(
   curproc,
   DWORD64(symaddress),
   &displacement,
   &sym.si);

  if (!ok_symbol)
   ::strcpy_s(sym.si.Name, 2000, "(unknown)");
  IMAGEHLP_LINE64 line;
  ::memset(&line, 0, sizeof(line));
  line.SizeOfStruct = sizeof(line);

  m_SymGetLineFromAddr64(
   curproc,
   DWORD64(symaddress),
   &displacement,
   &line);

  char trace[2048];
  _snprintf_s(trace, 2048, "%s(%i):'%s'\n", line.FileName, line.LineNumber, sym.si.Name);
  printf(trace);
  //bqLog::Print("%s\n", trace);
    }
}

int bqStackTracerImpl::GetStackTrace(size_t* trace, int maxtrace, int framesToSkip)
{
    _init();
    if (m_RtlCaptureStackBackTrace)
  return m_RtlCaptureStackBackTrace(framesToSkip, maxtrace, (PVOID*)trace, NULL);
    return 0;
}

static bqStackTracerImpl g_stackTracer;

void bqStackTracer::Print()
{
    size_t t[100];
    g_stackTracer.DumpStackTrace(t, g_stackTracer.GetStackTrace(t, 100, 0));
}



Сама реализация находится в отдельном классе (bqStackTracerImpl). Пока чисто Windows версия.
Имеем глобальный объект (g_stackTracer). Статический метод(bqStackTracer::Print) использует его.

Можно создать тестовую программу:
Код

// не забыть указать inc и libs папки в параметрах проекта
#include "badcoiq.h"

BQ_LINK_LIBRARY("badcoiq");

int main()
{
    bqStackTracer::Print();
    
    return EXIT_SUCCESS;
}


github

Добавлено (02 Июля 2023, 19:25)
---------------------------------------------
Logging

Надо выводить текстовую информацию, ошибки и предупреждения.
Нужно сделать так чтобы можно было указать свою функцию для вывода. Например, чтобы выводить в файл, или в игровую консоль.

+
inc/badcoiq/common/bqLog.h

Код

// Класс для вывода текстового сообщения
// Можно установить коллбэки
class bqLog
{
public:
    static void Print(const char* s, ...);
    static void Print(const wchar_t* s, ...);

    static void PrintInfo(const char* s, ...);
    static void PrintInfo(const wchar_t* s, ...);

    static void PrintWarning(const char* s, ...);
    static void PrintWarning(const wchar_t* s, ...);

    static void PrintError(const char* s, ...);
    static void PrintError(const wchar_t* s, ...);

    static void SetCallbackA(void(*)(const char*));
    static void SetCallbackW(void(*)(const wchar_t*));
};


реализация
src/badcoiq/common/bqLog.cpp

Дефолтные колбэки
Код

#include <stdarg.h>

void bqLogDefaultCallbackA(const char* s)
{
    printf(s);
}

void bqLogDefaultCallbackW(const wchar_t* s)
{
    wprintf(s);
}


Класс который будет всё выполнять
Код

class bqLogImpl
{
public:
    bqLogImpl()
    {
  m_cba = bqLogDefaultCallbackA;
  m_cbw = bqLogDefaultCallbackW;
    }

    void vprintf(const char* s, va_list vl);
    void vwprintf(const wchar_t* s, va_list vl);

    void(*m_cba)(const char*);
    void(*m_cbw)(const wchar_t*);
};


Пишем в буфер и вызываем коллбэки
Код

void bqLogImpl::vprintf(const char* s, va_list vl)
{
    char buffer[0xFFFF];
    vsnprintf_s(buffer, 0xFFFF, s, vl);
    m_cba(buffer);
}

void bqLogImpl::vwprintf(const wchar_t* s, va_list vl)
{
    wchar_t buffer[0xFFFF];
    vswprintf_s(buffer, 0xFFFF, s, vl);
    m_cbw(buffer);
}


Добавляю глобальный объект
Код

static bqLogImpl g_log;


Теперь класс bqLog
Код

void bqLog::Print(const char* s, ...)
{
    va_list vl;
    va_start(vl, s);
    g_log.vprintf(s, vl);
    va_end(vl);
}

void bqLog::Print(const wchar_t* s, ...)
{
    va_list vl;
    va_start(vl, s);
    g_log.vwprintf(s, vl);
    va_end(vl);
}

void bqLog::PrintInfo(const char* s, ...)
{
    Print("%s", "Info: ");

    va_list vl;
    va_start(vl, s);
    g_log.vprintf(s, vl);
    va_end(vl);
}

void bqLog::PrintInfo(const wchar_t* s, ...)
{
    Print("%s", "Info: ");
    va_list vl;
    va_start(vl, s);
    g_log.vwprintf(s, vl);
    va_end(vl);
}

void bqLog::PrintWarning(const char* s, ...)
{
    Print("%s", "Warning: ");
    va_list vl;
    va_start(vl, s);
    g_log.vprintf(s, vl);
    va_end(vl);
}

void bqLog::PrintWarning(const wchar_t* s, ...)
{
    Print("%s", "Warning: ");
    va_list vl;
    va_start(vl, s);
    g_log.vwprintf(s, vl);
    va_end(vl);
}

void bqLog::PrintError(const char* s, ...)
{
    Print("%s", "Error: ");
    va_list vl;
    va_start(vl, s);
    g_log.vprintf(s, vl);
    va_end(vl);
}

void bqLog::PrintError(const wchar_t* s, ...)
{
    Print("%s", "Error: ");
    va_list vl;
    va_start(vl, s);
    g_log.vwprintf(s, vl);
    va_end(vl);
}

void bqLog::SetCallbackA(void(*cb)(const char*))
{
    if (cb)
  g_log.m_cba = cb;
    else
  g_log.m_cba = bqLogDefaultCallbackA;
}

void bqLog::SetCallbackW(void(*cb)(const wchar_t*))
{
    if (cb)
  g_log.m_cbw = cb;
    else
  g_log.m_cbw = bqLogDefaultCallbackW;
}


Однажды потребовалось вывести wchar_t*, проще было добавить W версию методов.

github


"Do you think we can fly?
Well, I do."
gravemanДата: Воскресенье, 02 Июля 2023, 20:08 | Сообщение # 12
почетный гость
Сейчас нет на сайте
Цитата BADCOIQ ()
* Запрещено использовать STL в API.
Необходимо написать свои вещи.

Я изучал исходный код некоторых контейнеров из STL в gcc 8.0. Не увидел там ничего такого, что способно вызвать тормоза.
Да, в STL есть ряд неудобных вещей, являющихся частью реализации.
Чем хорош STL - тем, что он отлично документирован и практик изучает его сразу с изучением C++. То есть это вещь хорошо знакома программисту на C++. Можно сказать, что STL откладывается в подкорке.
Ну есть еще (где-то читал-слышал и отложилось в памяти, что якобы исключения C++ - это зло для консольных игр).
Таким образом, с точки зрения интерфейса, STL - это лучшая из открытых свободных библиотек. Но с точки зрения реализации - да, нужно или писать его аналог (что я и делаю в рамках тех базовых структур данных, что часто употребляются в сегменте геймдева), или же писать свое. Я выбрал писать свою реализацию, потому что не хочу тратить ресурсы на создание документации и поддержание в уме еще одного набора интерфейсов.

Добавлено (02 Июля 2023, 20:15)
---------------------------------------------
В целом неплохо написано, мышление процентов на 60 совпадает с моим. Мотивируешь.

Добавлено (02 Июля 2023, 20:18)
---------------------------------------------
Да, насчет исключений. Я в библиотеке своей завел макрос, который выключает исключения и меняет реализацию и интерфейс (с точки зрения возвращаемого значения) классов. Так что больше я в STL кроме этого зла не вижу. Потоки, конечно, использовать не стоит для работы с файлами, лучше писать свой контролируемый код.

BADCOIQДата: Понедельник, 03 Июля 2023, 20:14 | Сообщение # 13
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Все те действия что были описаны выше НЕОБХОДИМЫ.
Ошибки надо показывать, для этого нужна своя строка (ибо херня из STL однажды может дать 0, где нибудь в конвертации юникода). Строку нужно написать чтобы удовлетворяла, например чтобы тупо можно было добавить число. Для строки нужны функции для работы с памятью.

Это минимум который должен быть.

Перед тем как продолжить, надо добавить ещё один .h файл
inc/badcoiq/common/bqForward.h

Там будут forward declarations.
Код

class bqWindow;
class bqWindowCallback;


bqForward.h так-же должен быть указан в badcoiq.h

Что делает forward declarations?
Если мы делаем класс и в нём есть объект
Код

class CClass{
...
    MyObject m_object;
};

То компилятору нужно знать сколько памяти выделить для этого объекта. Компилятор должен видеть описание MyObject.
Можно вставить #include, но в итоге время компиляции увеличится. Давно я делал ДВИЖЁК где все include были в одном файле. Это плохой подход.

Нужно использовать указатель.
Код

class MyObject; //forward declaration
class CClass{
...
    MyObject* m_object = 0;
};

Компилятор знает размер указателя и проблем не будет. Нужно всего лишь вставить forward declaration - добавить class ИмяКласса.

В будущем все классы которые не будут входить в минимальный набор (указанный в badcoiq.h) будут добавляться в forward declaration.

Теперь ДВИЖЁК

Framework

Будет внутренний класс, в котором будут находиться все объекты необходимые для работы ДВИЖКА.
Управление фреймворком будет происходить через класс со статическими методами

+
inc/badcoiq/framework/bqFramework.h

Код

// Предполагаю что фреймворк должен посылать какие-то сообщения.
// Я это ещё не реализовал
class bqFrameworkCallback
{
public:
    bqFrameworkCallback() {}
    virtual ~bqFrameworkCallback() {}

    virtual void OnMessage() = 0;
};

// API для фреймворка.
class bqFramework
{
public:
    static void Start(bqFrameworkCallback*);
    static void Stop();
    static void Update();

    static float* GetDeltaTime();
    static bqWindow* SummonWindow(bqWindowCallback*);
};


Когда нам нужно запустить ДВИЖЁК, вызываем Start. Он выделит память для внутреннего класса. Stop для завершения работы, он освободит ту память.
Update - например, он вычислит DeltaTime, обновит состояния ввода, GUI, окна.
Delta Time - время между двумя вызывами Update. Update надо вызывать каждую итерацию главного цикла. Соответственно, это время затраченное на одну итерацию(так-же можно сказать время на рисование 1го кадра).
SummonWindow - создать окно. Имя CreateWindow занято WinAPI, как и многие другие. Я решил использовать слово Summon(призвать) вместо Create(создать).

Теперь реализация.
src/badcoiq/framework/bqFramework.cpp

Класс который будет хранить всё необходимое.
Код

class bqFrameworkImpl
{
public:
    bqFrameworkImpl() {}
    ~bqFrameworkImpl() {}

    float m_deltaTime = 0.f;
    bqFrameworkCallback* m_frameworkCallback = 0;
    
    BQ_PLACEMENT_ALLOCATOR(bqFrameworkImpl);
};


Добавлю глобальную переменную.
Код

bqFrameworkImpl* g_framework = 0;


Реализовываю статические методы bqFramework
Код

// Выделяю память
void bqFramework::Start(bqFrameworkCallback* cb)
{
    BQ_ASSERT_ST(cb);
    BQ_ASSERT_ST(!g_framework);
    if (!g_framework)
    {
  g_framework = new bqFrameworkImpl();
    }
}

// Освобождаю
void bqFramework::Stop()
{
    BQ_ASSERT_ST(g_framework);
    if (g_framework)
    {
  delete g_framework;
  g_framework = 0;
    }
}

void bqFramework::Update()
{
    BQ_ASSERT_ST(g_framework);
}

float* bqFramework::GetDeltaTime()
{
    BQ_ASSERT_ST(g_framework);
    return &g_framework->m_deltaTime;
}

bqWindow* bqFramework::SummonWindow(bqWindowCallback* cb)
{
    BQ_ASSERT_ST(g_framework);
    BQ_ASSERT_ST(cb);
    return 0;
}


Есть шанс что Stop не будет вызван. Надо добавить класс который при закрытии программы в деструкторе вызовет Stop
Код

class bqFrameworkDestroyer
{
public:
    bqFrameworkDestroyer() {}
    ~bqFrameworkDestroyer()
    {
  if (g_framework)
   bqFramework::Stop();
    }
};
// При закрытии программы этот объект будет уничтожен - будет вызван деструктор
//   в котором будет вызван bqFramework::Stop();
bqFrameworkDestroyer g_frameworkDestroyer;


Ну вот и всё.
Далее необходимо создать окно, и реализовать Update

github


"Do you think we can fly?
Well, I do."
afqДата: Вторник, 04 Июля 2023, 02:50 | Сообщение # 14
Разработчик
Сейчас нет на сайте
О, ты молодец. Развитие в движкостроении это похвально. Я тоже делаю движки, только на opengl. Никак не могу на vulkan пересесть.
mishkagamesДата: Вторник, 04 Июля 2023, 14:32 | Сообщение # 15
участник
Сейчас нет на сайте
Что только народ не делает лишь бы не думать и не реализовывать хорошие игры...
GC-VicДата: Вторник, 04 Июля 2023, 16:59 | Сообщение # 16
GcUp.ru
Сейчас нет на сайте
Зачем этот велосипед? (риторический вопрос)

Закон Мерфи: "Если вы уверены, что ваш поступок встретит всеобщее одобрение, кому-то он обязательно не понравится".
afqДата: Вторник, 04 Июля 2023, 17:26 | Сообщение # 17
Разработчик
Сейчас нет на сайте
Ну вообще движки делают уже состоявшиеся разработчики. Таким людям уже 30+ это точно. Это молодежь 16+ всегда задается вопросом зачем делать движок. Это как отдельное творчество, когда тебе нужно не только указать персонажу сходить по готовому алгоритму к другому персонажу и что-то сделать. Такая простота если честно может добить разработчика, который любит решать сложные вещи. Да, который любит решать сложные вещи. Я каждый свой движок почти что пишу заново, только хорошие части копирую, так как невозможно придумать сразу хорошо, когда мало опыта и большой проект. Обычно ввязываются в конфликт насчет движков молодые неокрепшие умы, которые только любят игры и больше ничего не умеют делать, кроме как на construct или unity задать логику для передвижения персонажа.
BADCOIQДата: Вторник, 04 Июля 2023, 18:23 | Сообщение # 18
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
afq, абсолютно согласен.

Игрострой это не только сюжет, дизайн и т.д. Это ещё и технологии, то что скрыто под капотом.

Ну вот если вспомнить детство, во что играли, денди, сега, кто-то играл на более древних консолях. Я лично всегда задавался вопросом "как сделано то, как сделано это".

Когда ковыряешься в этих вещах, приходят мысли "с этой вещью можно сделать то-то". Изучаешь всё это, и рамки, границы возможностей расширяются.

Моя граница упёрлась в физику. Всё сделано по списку, осталось только она. Я хочу переписать всё, документируя что возможно, и написать свою физику. И я опишу начальные шаги, и все свои ошибки, и 100% всё сделаю, с оптимизациями. Книжку прочитал, оказывается это элементарщина. Будет интересно реализовать.

Как только будет реализовано (всё по списку), буду делать игрульку. Ресурсы есть. Главное не напрягаться и заниматься тем что нравится.


"Do you think we can fly?
Well, I do."
Storm54Дата: Вторник, 04 Июля 2023, 18:37 | Сообщение # 19
постоянный участник
Сейчас нет на сайте
Цитата afq ()
Ну вообще движки делают уже состоявшиеся разработчики.

Рад прочитать комментарий матерого состоявшегося разработчика, который уже в своем следующем предложении себе противоречит и пишет, что не может продумать свой код и каждый раз заново пишет один и тот же 2D движок.
Если серьезно, то читая эту портянку (да и портянки в других темах), как раз таки и сложилось впечатление, что прочитал текст 15-ти летнего школьника, который ни разу не создавал законченный продукт, не говоря уже об огромном солюшине с десятками вложенных проектов, сотнями тысяч строк кода и кучей второстепенных проектов.

Добавлено (04 Июля 2023, 18:38)
---------------------------------------------

Цитата BADCOIQ ()
Книжку прочитал, оказывается это элементарщина. Будет интересно реализовать.

Жду в репозитории. А то тут PhysX уже делают лет 20, но все никак не доделают, все выходят новые и новые версии, а вроде бы элементарные вещи, да?!
BADCOIQДата: Вторник, 04 Июля 2023, 18:46 | Сообщение # 20
Отец-основатель BADCOIQ Corporation © 2010
Сейчас нет на сайте
Цитата Storm54 ()
А то тут PhysX

И хрен с ними.

Механика - элементарно. Я говорил про раздел физики.
Математикой владеем.


"Do you think we can fly?
Well, I do."
Форум игроделов » Ваши проекты » Разработка движков и сред разработки » Разработка движка (детальное описание для новичков) [C++] (BADCOIQ Engine)
  • Страница 1 из 2
  • 1
  • 2
  • »
Поиск:

Все права сохранены. GcUp.ru © 2008-2024 Рейтинг