Comments 69
Разрешите побухтеть. Свой самодельный движок это хорошо и даже интересно. Но зачем в статье столько веселых шуток, искрометного юмора и мемасов? Мне кажется надо все-таки определиться вы стендапер или программист. Это стендап или техническая статья. Потому что для стендапа можно наверное еще смешней написать. А если писать про движок, то лучше чтоб про движок было больше, а смешно чтоб было меньше.
Я совершенно не эксперт в этой области, но, мне кажется, автор в процессе разработки этого проекта и решения уже поставленных и будущих вопросов таки сможет рассказать нам всем потом в виде большой итоговой статьи о том, почему всё так тормозит вместо релиза этого проекта :)
«В будущем планирую обеспечить поддержку Dos, Android, iOS, macOS.»
Поддержку Здравствуймира сначала для всего этого реализуйте, а потом планируйте. А то как бы не оказалось что уже написали так, что подобное невозможно. Тысячу раз.
«Пока отсутствует документация, но она планируется после стабилизации графического API.»
Ничто не помогает увидеть что делаешь не то лучше, чем написание (черновика) документации.
«Но работа идёт.»
О, Вам платят за торможение прогресса и ретроградную эволюцию человечества? Если нет, не называйте это работой. Хинт: в SDL ещё есть чем заняться, можно даже улучшением.
О, Вам платят за торможение прогресса и ретроградную эволюцию человечества? Если нет, не называйте это работой.
Это устоявшееся выражение. Работа идёт, колесо крутится, вода течёт. :)
Поддержку Здравствуймира сначала для всего этого реализуйте, а потом планируйте. А то как бы не оказалось что уже написали так, что подобное невозможно. Тысячу раз.

Была мысль для старых версий компиляторов портировать из бородатых gcc STL. На данный момент библиотека успешно компилируется Visual C++ 6.0 из коробки. Скорость компиляции огонь, но и уровень оптимизации хромает. К примеру вывод картинки на CPU различается в три раза на одном железе но с современным компилятором.
Понравилась ясность мысли в приведенных примерах.
планирую обеспечить поддержку Dos, Android, iOS, macOS
Нафига Dos, лучше WebAssembly добавьте. Т.е. чтобы в самой главной ОС, которой сейчас однозначно является Web-браузер - можно было использовать. Зачем вчерашний день, когда для дня сегодняшнего нет решений?
И вопрос по Android/iOS. Работали ли вы с ними? Там все это можно реализовать технически? К примеру Android - системные библиотеки, разрешенные для использования обычным пользователем - на Java-машине, верно?
И вопрос по Android/iOS. Работали ли вы с ними? Там все это можно реализовать технически? К примеру Android - системные библиотеки, разрешенные для использования обычным пользователем - на Java-машине, верно?
Нет не работал. Но всегда есть куда посмотреть как сделано и сделать так же. Библиотеки SDL2 и SFML. Я как то ставил Android Studio, для обогрева комнаты:)
Нафига Dos, лучше WebAssembly добавьте. Т.е. чтобы в самой главной ОС, которой сейчас однозначно является Web-браузер - можно было использовать. Зачем вчерашний день, когда для дня сегодняшнего нет решений?
Со временем руки дойдут и до этого варианта. Dos привлекает своим ретро статусом. Конечно я понимаю, что оно нужно где то 1.5 человекам в мире:)
как 8-битник до мозга и костей приветствую вариант для DOS хотя бы для i386/87.
Для портирования буду юзать Open Watcom. Он позволяет писать 32 битный код для Dos.
Рассказывает много интересных моментов.
Да, я про Watcom и хотел спросить следом, а вы уже и ответ написали.
Было бы интересно разобраться с аппаратными функциями CGA/EGA/VGA и VESA. Это я в целом, о своих планах.
На ютубе есть дядя https://www.youtube.com@The8BitGuyу него своя игра портирована на кучу ПК, включая i8088 DOS в разных графических режимах. Но он не рассказывал на чем пишет.
Было бы интересно разобраться с аппаратными функциями CGA/EGA/VGA и VESA. Это я в целом, о своих планах.
Отличный ресурс с документацией и ссылками на статьи и сайты авторов, программирующих под Dos. Присутствуют статьи о CGA/EGA но в основном примеры для VGA.
https://github.com/balintkissdev/awesome-dos
На ютубе есть интересный плейлис по программированию в Dos. VGA режим Mode X.
На ютубе есть дядя https://www.youtube.com@The8BitGuyу него своя игра портирована на кучу ПК, включая i8088 DOS в разных графических режимах. Но он не рассказывал на чем пишет.
У него и видео есть о разработке данной игры. И он начинал писать игру на Commodore 64.
Symantec C++ 7.5 DOS/DOSx32/Pharlap32, win16/win32
Возьму на заметку. Спасибо.
Я писал об Open Watcom. Данный компилятор умеет компилировать С++ код на Windows (и Linux?) в бинарники для Dos'а. Поддерживает 32 битный режим и встраивает в исполняемый файл расширитель Dos4gw. Исполняемые файлы запускаю под dosbox.
Есть видео о портировании современной 2D игры под Dos. Видео на английском. Я смотрел через переводчик яндекса. На слух я английский не понимаю.
Промахнулся видео в предыдущем ответе.
посмотрю, спасибо.
с исходниками для меня всё плохо, я чужой код совсем не понимаю, так как у меня в голове всё строится от идеи, а её реализация в голове у меня никогда не задерживается, когда читаю чужое просто не понимаю это нагромождение кода, особенно когда используют твики или хитрые приёмы. (недавно тут же читал статью про то как формировались уровни в Pifall, тот кто в этом разобрался для меня уровня - Бог)
А чем не устраивает SDL? Впрочем, это скорее просто дидактический проект, чтобы самому разобраться, верно?
В большей мере да. Я специально скачивал старые исходники SDL версии 1.0 Посмотрев исходники вкралась мысль, а почему собственно не повторить. Да и закрыть внутренний гештальт заодно. Мне с детства нравилось разбираться в каких то штуках и узнавать как оно в унутрях работает.
И конечно хотелось бы сделать законченный проект. Пока да, проект выглядит как студенческая поделка, это мягко сказано. :)
если что, SDL умеется переключать CPU/GPU render в рантайме, без всякой условной компиляции
Отсутствие зависимости от внешних dll, все зависимости включены физически в проект.
А значит любой "проэкт" на этой библиотэке для Linux ни когда не попадёт ни в один репозиторий, единственный путь распространния flatpack и компания подобных.
То есть если берём дос, то самое длинное у нас становится 32 бита, верно?
Заход про производительность это конечно хорошо, но почему первый же пример не на ErrorCodes, а на исключениях?
Ну и если про раст понятно, то непонятно почему не Си?
То есть если берём дос, то самое длинное у нас становится 32 бита, верно?
Да.
Заход про производительность это конечно хорошо, но почему первый же пример не на ErrorCodes, а на исключениях?
Удобство. Исключения невозможно игнорировать, как коды ошибок.
Ну и если про раст понятно, то непонятно почему не Си?
Личное решение, для меня си уж слишком дубовый.
Удобство
Тогда можно смело выпиливать клейм про производительность. Думается мне, что ECS тоже не появится в пользу классов с классами.
Про производительность с исключениями спорно. Соглашусь, что могут быть проблемы на старых компиляторах. Скорее всего я перейду на коды ошибок, почитаю о приемлемых альтернативах. Я реализую базовый функционал 2d графики + переносимое взаимодействие с событиями ОС и т. д Тот кто будет юзать библиотеку для создания игры или движка, может встраивать ECS. Но в базовой библиотеке я использовать данный паттерн не вижу смысла.
То есть если берём дос, то самое длинное у нас становится 32 бита, верно?
Там наверняка придется уходить в unreal mode и ваять свои драйвера для графики, хотя бы для vesa. Так что самое длинное может быть абсолютно любое, если автор осилит.
Пока нацелен на VGA. После порта под Linux займусь портом под Dos. Сначала поставлю заглушки и просто добьюсь компиляции и запуска exe под Dos. После уже буду по туториалам допиливать до работоспособности.
Рекомендую ознакомиться с библиотекой Anti-Grain Geometry, для 2D субпиксельной графики.
Реализуя идиому pimpl, я буду хранить класс в статической памяти как в следующем примере
https://habr.com/ru/post/111602/
//GeneralSocket.h
Class GeneralSocket
{
public:
GeneralSocket();
void connect();
private:
static const size_t sizeOfImpl = 42;/* or whatever space needed*/
char socket [sizeOfImpl];
}
//GeneralSocket.cxx
#include “UnixSocketImpl.h”
GeneralSocket::GeneralSocket() : {
assert(sizeOfImpl >= sizeof (UnixSocketImpl));
new(&socket[0]) UnixSocketImpl;
}
GeneralSocket::~GeneralSocket() {
(reinterpret_cast<UnixSocketImpl *> (&socket[0]))->~UnixSocketImpl();
}
GeneralSocket::connect() {
socket->connectImpl();
}
Далее автор пишет
Проблемы с выравниванием памяти. Данный способ не гарантирует что память будет выравнена должным образом для всех членов UnixSocketImpl. Решение, которое не гарантирует полную переносимость, но все же работает в большинстве случаев – использование union:
Как сделать универсально, что бы работало на всех системах и могло быть реализовано на стандартам е С++ 98?
Простите, но нафига городить такое чудо, которое мало того что UB на UB, и UB погоняет, так еще и бессмысленно? PIMPL нужен для того, чтобы размеры объектов на стеке никогда не менялись, а здесь, стоит не дай бог чему-то серьезно поменяться, и уже нужно увеличивать ваш волшебный sizeOfImpl
, место на стеке стало требоваться другое, код, который был собран с предыдущей версией библиотеки, начал крашиться.
Чтобы не выделять через кучу.
Ну и второе -- чтобы вдоволь нажонглироваться указателями на память.
Чтобы не выделять через кучу.
Ну так оно же не работает так, как должно. Проблем не оберешься.
Так проблем там одна -- запрещено копирование по значению.
Т.е. передача объекта из/в ф-ию только через указатель.
Непонятно только зачем тут С++, менее громоздко наверно было бы структурами с указателями на ф-ии обойтись.
Хочется пользоваться неймспэйсами, ООП, конструкторами и деструкторами, исключениями, более строгой проверкой типов и т. д
Так проблем там одна -- запрещено копирование по значению.
Если вы про "стандартный" PIMPL через кучу, то да, увы. Жизнь могли бы скрасить конструкторы копирования и operator=()
, если бы не вопросы к стабильности ABI.
Есть ли вариант применить Pimpl и избежать динамического выделения через new? Возможно следует думать в сторону аллокатора для инициализации классов?
Сохранить преимущества Pimpl, но избежать его минусов.
Сомневаюсь, что можно полноценно использовать PIMPL со всеми его гарантиями, и совсем избежать динамического выделения памяти. Можно конечно выделять на стеке "заведомо побольше", но во-первых этого "побольше" тоже может не хватить, а во-вторых при копированиях объекта будет копироваться лишнее.
Выделите больше чем надо с учётом выравнивания
::align = alignof( UnixSocketImpl
) / 8; // зависит от компилятора
::sizeOfImpl = sizeof( UnixSocketImpl
) + align;
После разместите *SocketImpl в этом массиве с учётом выравнивания, т.е. со смещением от начала на размер выравнивания в байтах
new(&socket[align]) UnixSocketImpl;
В гитхабе почему-то написано "I'm using the C++11 standard. To support old and new platforms."
А если например дум захочется запилить под эту показывалку, где C API?
Поправлю редми, это копипаста из другого проекта.
Обертка на С, будет в будущем.
Обертка на С, будет в будущем.
По опыту скажу, что С++ обёртку над С API сделать легко, а вот С обёртку над С++ API (если она не была заложена изначально) уже вряд ли получится, ну или итоговый Франкенштейн будет реализовывать только малую часть.
Для меня пока это не приоритет. Но галочку для себя поставил.
Да ну. Даже проще чем кажется.
Ведь Сишные экстерны это всего лишь указания компоновщику и компилятору С++ не делать ОО расширение имён функций.
Прологи и эпилоги остаются тему же самыми. Конструкторы и деструкторы работают.
Если это "проще чем кажется" покажите на примере как использовать std::string, в С-коде.
Оберните все нужные вам операции над std::string в вызовы С с сырыми указателями на массивы байт.
Чем строки отличаются от любых других классов, которые можно завернуть во всё что угодно и потом передавать через void* в С? Правильно, ничем.
Если вы думаете что сможете манипулировать внутренним указателем строки из С, то извращения на другом этаже.
#include <stdio.h>
#include <string>
extern "C" void * new_string ( char * s ){
return new std::string( s );
}
extern "C" const char * string_c( void * std_str ){
std::string * s = reinterpret_cast< std::string* >( std_str );
return s->data();
}
extern "C" void * add_string( void * std_str, char * add_s ){
std::string * s = reinterpret_cast< std::string* >( std_str );
*s += std::string( add_s );
return s;
};
int main(int, char **)
{
void * cppstr = new_string( "Hello" );
printf( "%s\n", string_c( cppstr ) );
cppstr = add_string( cppstr, " from C!" );
printf( "%s\n", string_c( cppstr ) );
return 0;
}
-------------------------
Hello
Hello from C!
Консультации -- 1000 р/час.
extern "C" void * new_string ( char * s )
{ return new std::string( s ); }
и как с эти работать? Вы хоть раз API разрабатывали?
В примере со строками, я имел в виду что-то подобное (просто для примера):
// C++ API
// __attribute__((visibility("default"))) for Linux
__declspec(dllexport) class system_info {
public:
inline std::string version() {
return m_version;
}
...
};
// C API
int get_version(char *string_buffer, size_t buffer_length) {
...
strncpy(string_buffer, sys_info.version().c_str(), MAX_BUFFER_LEN);
...
}
и как с эти работать? Вы хоть раз API разрабатывали?
Просто до безобразия. А вот вы похоже что нет.
И не надо умничать.
Для особо непонятливых повторю -- оборачивание С интерфейсом С++ классов не представляет никакой проблемы.
#include <stdio.h>
#include <string>
#include <vector>
// type control
// no strict // strict
typedef void* string; // struct String { std::string s };
typedef void* strings; // struct Strings { std::vector< std::string > v; };
extern "C" string new_string ( const char * s ){
return new std::string( s );
}
extern "C" void delete_string ( string std_str ){
std::string * s = reinterpret_cast< std::string* >( std_str );
delete s;
}
extern "C" void delete_strings ( string * std_strs ){
std::string ** s = reinterpret_cast< std::string** >( std_strs );
delete[] s;
}
extern "C" void delete_string_vector ( strings std_strs ){
std::vector<std::string> * v = reinterpret_cast< std::vector<std::string> * >( std_strs );
delete v;
}
extern "C" const char * string_c( const string std_str ){
const std::string * s = reinterpret_cast< const std::string * >( std_str );
return s->data();
}
extern "C" string add_string( string std_str, const char * add_s ){
std::string * s = reinterpret_cast< std::string* >( std_str );
*s += std::string( add_s );
return s;
};
// так делать обёртку нежелательно -- лишнее копирование памяти
extern "C" string * split(const string std_str, const char * delim, int * n)
{
if( n == nullptr || delim == nullptr )
return nullptr;
const std::string * s = reinterpret_cast< const std::string * >( std_str );
const char * c_str = s->c_str();
std::vector<std::string> splitted;
char* tp = strdup( c_str );
char* chunk = strtok( tp, delim );
while( chunk != nullptr ) {
splitted.push_back( chunk );
chunk = strtok( nullptr, delim );
}
free( tp );
*n = splitted.size();
string * ret = new string[ *n ];
int i = 0;
for( std::vector<std::string>::iterator it = splitted.begin(), end = splitted.end(); it != end ; ++it )
ret[ i++ ] = new std::string( *it );
return ret;
}
extern "C" strings split2(const string std_str, const char * delim)
{
if( delim == nullptr )
return nullptr;
const std::string * s = reinterpret_cast< const std::string * >( std_str );
const char * c_str = s->c_str();
std::vector<std::string> * ret = new std::vector<std::string>;
char* tp = strdup( c_str );
char* chunk = strtok( tp, delim );
while( chunk != nullptr ) {
ret->push_back( chunk );
chunk = strtok( nullptr, delim );
}
free( tp );
return ret;
}
extern "C" int strings_size( const strings s ){
const std::vector<std::string> * v = reinterpret_cast< const std::vector<std::string> * >( s );
return v->size();
}
extern "C" string strings_get( strings s, int idx ){
std::vector<std::string> * v = reinterpret_cast< std::vector<std::string> * >( s );
return &( v->at( idx ) );
}
int main(int, char **)
{
string cppstr = new_string( "Hello" );
printf( "%s\n", string_c( cppstr ) );
cppstr = add_string( cppstr, " from C!" );
printf( "%s\n", string_c( cppstr ) );
int n = 0, i = 0;
string * sp = split( cppstr, " ", &n );
printf( "std::string \"%s\" splitted to %d chunks :\n", string_c( cppstr ), n );
for( i = 0; i < n; i++ )
printf( " -- \"%s\"\n", string_c( sp[ i ] ) );
delete_strings( sp );
strings sp2 = split2( cppstr, " " );
n = strings_size( sp2 );
if( n >= 2 )
add_string( strings_get( sp2, 1 ), " wroom" );
printf( "std::string \"%s\" splitted(2) to %d chunks :\n", string_c( cppstr ), n );
for( i = 0; i < n; i++ )
printf( " -- \"%s\"\n", string_c( strings_get( sp2, i ) ) );
delete_string_vector( sp2 );
delete_string( cppstr );
return 0;
}
-----------------
Hello
Hello from C!
std::string "Hello from C!" splitted to 3 chunks :
-- "Hello"
-- "from"
-- "C!"
std::string "Hello from C!" splitted(2) to 3 chunks :
-- "Hello"
-- "from wroom"
-- "C!"
Просто до безобразия. А вот вы похоже что нет.
Ещё раз, в ващем примере нет API в принципе, си код не имеет доступа к данным строки, то есть вы передаёте некий хендл, которые хранит данные, но собственно сишный код работать с этими данными не может - это не API, это именно обёртка.
Из вашего же примера - вызвать printf() без дополнительных телодвижений невозможно:
string cppstr = new_string( "Hello" );
printf( "%s\n", string_c( cppstr ) );
Помимо этого, весь этот код, как минимум, совершенно не безопасен (при таком "качестве" не удивительны постоянные статьи на Хабре про "небезопасность" С и С++ и "безопасность" Rust, видимо пишут их такие же как вы)
std::string ** s = ...
Если у вас на ревью пропускают что-то подобно - то я бы не хотел работать в вашей компании.
char* chunk = strtok( tp, delim );
И вся многопоточность идёт лесом, так как strtok использует внутренние глобальные переменные.
char* tp = strdup( c_str );
while( chunk != nullptr ) {
splitted.push_back( chunk );
chunk = strtok( nullptr, delim );
}
free( tp );
Потенциальная утечка памяти, разваленный стек в многопотоном приложении, ну и операции с сырыми указателями (там, где это севершенно не требуется) в современном С++
Можете общение не продолжать, я вас даже на позицию Junior не нанял бы.
Пока оставлю сборку из исходников. В проекте использую cmake, проблем подключить или собрать на поддерживаемых системах нет.
Добавил поддержку компилятора Open Watcom v2. Должен работать и с более старыми версиями, но я не проверял. Будет основа для порта Dos.
Разрешите побухтеть. Свой самодельный движок это хорошо и даже интересно. Но зачем в статье столько веселых шуток, искрометного юмора и мемасов? Мне кажется надо все-таки определиться вы стендапер или программист. Это стендап или техническая статья. Потому что для стендапа можно наверное еще смешней написать. А если писать про движок, то лучше чтоб про движок было больше, а смешно чтоб было меньше.
Но зачем в статье столько веселых шуток, искрометного юмора и мемасов?
Спасибо за оценку.
Мне кажется надо все-таки определиться вы стендапер или программист.
Мне на работе слово в слово так и говорят. :)
Потому что для стендапа можно наверное еще смешней написать.
В следующей статье о том, что ещё мне не нравится в софте. Постараюсь отточить данный навык:)
А если писать про движок, то лучше чтоб про движок было больше, а смешно чтоб было меньше.
Я постараюсь, но не обещаю. Постараюсь держаться середины и смешно и техническую часть раскрыть.
Пишу фреймворк LDL на С++ с поддержкой старых систем