Хочу разделить игру на 4 потока (пока что) . Пока что это чисто тестовые потоки, которые ничего не делают, хочу хоть как-то заставить их работать.
Идея такова: 1)инициализирую эти потоки перед вхождением в главный игровой цикл. 2)эти потоки внутри крутят бесконечный цикл, но вначале каждой итерации ждут оповещения от главного потока, чтобы он разрешил им работать 3)внутри главного игрового цикла каждую итерацию я оповещаю эти потоки о том, что они могут выполнить очередную итерацию по формированию кадра. 4)Тогда эти потоки снимаются с паузы и выполняют итерацию, после чего они должны оповестить главный поток о том, что они завершили работу и снова находятся на паузе. 5)главный поток ждёт оповещения ото всех запущенных потоков 6)следующая итерация главного игрового цикла.
Проблема: где-то получаю дедлок на рандомной итерации главного игрового цикла и в упор не понимаю, почему. Это мой первый опыт с многопоточкой, буду рад, если скажете, что у меня в коде не так и почему, потому что я ещё не совсем влился в тему.
Код делает дедлок даже если запустить 1 поток вместо 4-х, поэтому я 3 других даже закомментил, чтобы не мешались.
Главной функцией потока является ф-ция action() Она принимает аргументом ту функцию, которую должен вызывать поток собственно для работы. Внутри она крутит бесконечный цикл и ждёт разрешения на работу. Когда разрешение получено, она выполняет нужную функцию (например, render()), которая, в свою очередь, по завершении, даёт главному потоку информацию о том, что работа завершилась
Ф-ция update() вызывается из главного цикла и оповещает потоки о том, что они могут начинать работу, после чего ждёт завершения их выполнения.
Добавлено (11 ноября 2017, 17:49) --------------------------------------------- Кажись, решил проблему небольшим костылём Как было устроено: все потоки в начале фрейма ждут разрешение на работу, главный поток им его даёт. Потом он ждёт когда потоки оповестят его о завершении работы. При этом, для того чтобы обновить инфу о прогрессе работы потоков, использовался тот же мьютекс, который использовался в условной переменной, которая ждала результат. Возможно, потому я и ловил дедлок как решил: убрал условную переменную, которая ждала пока потоки не завершат кадр и не лочу в главном потоке соответствующий мьютекс. Вместо этого заставляю главный поток в цикле проверять, закончена ли работа и ждать. Так что, теперь итерация главного цикла (ф-ция update) выглядит так
Код
finishedFrameNum = 0;
for (int i = 0; i < THREADS_AMOUNT; i++) { std::unique_lock<std::mutex> iterationLock(mutIterationStarted[i]); threadReadyMap[i] = true; condIterationStarted[i].notify_one(); }
while (finishedFrameNum != THREADS_AMOUNT) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); }
Добавлено (11 ноября 2017, 18:02) --------------------------------------------- попробую теперь на этапе инициализации проверять, сколько ядер в компе юзера. Если 4+ - запускать 3-4 потока Если 2 - запускать 2 потока, в первом - рендер+что-то не слишком тяжёлое, во втором - всё остальное Если 1 - 2 потока, рендер в одном, всё остальное во втором
Добавлено (11 ноября 2017, 22:15) --------------------------------------------- Всё, обернул всё это дело в класс. Можно создать произвольное количество потоков, в каждый из потоков запихнуть произвольное количество последовательно исполняемых задач.
Только одна проблема есть: сделал синглтон, отнаследовался от него и попытался получать инстанс нужного класса, не получалось - психанул и удалил наследование от синглтона, всё равно я один над проектом сижу, и так знаю, что класс в единственном экземпляре должен быть))
Код
#pragma once #include <vector> #include <mutex> #include <thread> #include <condition_variable>
for (int i = 0; i < threadsNum_; i++) { std::unique_lock<std::mutex> iterationLock(*mutIterationStarted_[i]); threadReadyMap_[i] = true; condIterationStarted_[i]->notify_one(); }
while (finishedFrameNum_ != threadsNum_) { std::this_thread::sleep_for(std::chrono::microseconds(waitIntervalMicroseconds_)); }
postUpdateFunction_(deltaTime_); }
void Threading::action(int threadId) { while (true) { std::unique_lock<std::mutex> iterationLock(*mutIterationStarted_[threadId]); bool exit = false; while (true) { //wait for starting new game iteration condIterationStarted_[threadId]->wait(iterationLock);
if (!running) { exit = true; break; } else if (threadReadyMap_[threadId]) { //to avoid spurious wakeup threadReadyMap_[threadId] = false; break; } } if (exit) break;
//call all functions of the thread for (int i = 0; i < functions_[threadId].size(); i++) { functions_[threadId][i](deltaTime_); }
//increase counter of threads that finished making this frame notifyFinishFrame(); } }
void postUpdate(float dt) { static int a; a++; if (a > 1000) { threading.quit(); } }
void render(float dt) { int k = 0; for (int i = 0; i < 1000000; i++) { k++; } }
void physics(float dt) { int k = 0; for (int i = 0; i < 1000000; i++) { k++; } }
void AI(float dt) { int k = 0; for (int i = 0; i < 1000000; i++) { k++; } }
void misc(float dt) { int k = 0; for (int i = 0; i < 1000000; i++) { k++; } }
Добавлено (12 ноября 2017, 12:09) --------------------------------------------- упс, ещё один дедлок был, который случался довольно редко (тобишь прога стабильно работала несколько десятков секунд в полной нагрузке, прежде чем валилась) На этот раз связанный со "spurious wakeup"
Дело в том, что может так совпасть, что поток сорвётся по ложному прерыванию за павру мгновений до того, как главный поток оповестит его о начале работы. Таким макаром, когда главный поток его оповещает, тот всё ещё занят обработкой ложного прерывания и усыплением себя, а ведь оповещение из главного потока прошло мимо! А потом мы ждём завершения работы потока, в то время как он пропустил оповещение о начале работы. Вылечил так: в цикле спамим оповещения, разблокируя при этом мьютекс условной переменной до тех пор, пока не убедимся, что наше сообщение дошло
Код
for (int i = 0; i < threadsNum_; i++) { std::unique_lock<std::mutex> iterationLock(*mutIterationStarted_[i]); threadReadyMap_[i] = true; while (threadReadyMap_[i]) { condIterationStarted_[i]->notify_one();
if (iterationLock.owns_lock()) { iterationLock.unlock(); } } }
Byurrer, спасибо) Так прямо сходу не разберусь, но какие-то вещи вроде выглядят более-менее понятными. Но пока что трогать это дело сильно не буду, иначе это надолго) https://vk.com/beezoya
На самом деле особо ничего сложного в этом деле нет. Чтобы шарить в этих вещах достаточно знать: матрицы и операции над ними, вектора и операции над ними, локальные и глобальные координаты, преобразование координат, векторное произведение, скалярное произведение. В 3д проектах информацию об объектах (позиция, угол, масштаб) пакуют в матрицу, а если нужно что-то переместить\повернуть\смасштабировать, то просто умножают нашу матрицу, характеризующую объект на матрицу поворота\перемещения\масштабирования в нужном нам порядке. А как построить подобную преобразующую матрицу? В DirectX, например, есть для этого встроенные функции, так же, всё это дело можно нагуглить, да и думаю, и самому посидеть, подумать, написать не слишком-то и сложно будет. Скалярное произведение нужно, чтобы найти угол между векторами Векторное произведение нужно, чтобы найти нормаль к поверхности либо определить, с какой стороны от вектора (слева\справа) находится точка. Поначалу страшно, потом норм.
Думаю над тем, как лучше всего распараллелить игру. Раньше я параллелил только отдельные задачи, а остальное было линейным.
Думаю запускать потоки. Сначала хотел запускать 1 поток для рендера и ещё несколько потоков, которые бы делали одну и ту же работу для разных частей одних данных, после чего в главном потоке собрать результат их деятельности. Както так:
void redraw(){
}
void update(float dt, Data* ы, int count){ printf("дороу"); }
Но это будя очень сложно. Допустим, я симулирую физику. Допустим, что первый поток выполняет физику для первой четверти объектов, второй поток для второй четверти и тд. Но чтобы обработать физику, нужно знать инфу обо всех объектах, чтобы проверять пересечения (проверка каждого с каждым). Тогда получается нужно нехило так синхронизировать общие данные, сомнительная выгода. Можно делить объекты на потенциально столкнувшиеся независимые группы и делить нагрузку на потоки по этим группам. Но бяда, подобный препроцессинг надо делать каждый кадр, скорее всего, с использованием недружелюбных к кэшу и вечно теребящих динамическую память std::set'ов и прочих прочих делов. Сомнительный выигрыш. К тому же, если вдруг все объекты в мире столкнутся в единую кашу, эту кашу придётся разгребать одному потоку, а это тяжелейший случай из возможных. Имея притом тяжёленький препроцессинг, который ничем не помох)
Кроме того, хочу прибабасать к игре луа скрипты, а это дело не является потокобезопасным. А копировать одни и те же скрипты между потоками запрещает религия. Поясню: есть, грубо говоря, класс "пушка"; я создаю 400 экземпляров этой пушки, но к классу пушки присобачен луа скрипт (зачем грузить один и тот же скрипт для каждого инстанса пушки, если можно грузить скрипты отдельно, а объектам подсовывать хендл нужного скрипта). Но я не могу разделить по 100 пушек на каждый поток и обращаться из нескольких потоков по хендлам скрипта из пушек к скрипту, на который они все ссылаются. Получается, нужно хранить этот скрипт для каждого из потоков, то есть, по 4 одинаковых скрипта подряд? Бред.
Поэтому решил вместо распараллеливания данных распараллелить задачи. Поток для рендеринга, поток для ИИ, поток для физики и т.д. И тут же вопрос. Допустим, что рендеринг и физика намного легковеснее, чем скажем, ИИ (просто допустим). Тогда я хочу в потоке для ИИ для особо тяжкой внутренней задачи сделать дополнительный параллельный цикл Concurrency::parallel_for(бла бла бла), чтобы частично подгрузить и другие ядра, чтобы не было ситуации, что потоки для физики и рендеринга давно закончили обработку кадра, а ИИ даже половину работы не выполнил. Вопрос, компухтер не сойдёт с ума от такого распараллеливания внутри распараллеливания?
Если допустить, что у меня одно ядро в процессоре и я вынесу логику в один поток, а рендеринг в другой, производительность вырастет или упадёт? Допустим, что тяжёлой синхронизации не понадобится.
Далее, как лучше пускать потоки? 1) Запускать в начале игры и они, грубо говоря, фурычат на максимуме каждый своё с разной скоростью? То есть, допустим, поток для рендеринга успел перерисовать ажно 5 раз на основе предсказаний, пока поток для логики наярил только 1 круг. 2) (сейчас я хочу сделать именно так) Пускать потоки каждую итерацию главного игрового цикла и ждать, пока они все не завершатся. (тобишь время работы самого медлительного из этих потоков будет временем формирования кадра, остальные потоки, когда закончат обрабатывать свои задачи в текущем кадре, отдыхают и ждут самого неторопливого) Не будет ли сама процедура запуска потоков по 60 раз в секунду уничтожать всю выгоду от распараллеливания? Может быть, можно запустить эти потоки вначале, а внутри игрового цикла просто давать им сигнал продолжить работу, тогда как они сами будут нас уведомлять о том, что очередная итерация работы окончена и автоматически становиться на паузу (с помощью какихнибудь условных переменных чтоли) 3)ещё как-то?
Вот там чуть чуть не понял, но все же примерно понятно..Я попробую
делай через арктангенс
про скалярное произведение забудь, я затупил немного. Не подумал, что косинус можно и попроще получить в случае с вектором-осью)) Привык к произвольным векторам в пространстве)
А вообще, угол между произвольными векторами определяют с помощью скалярного произведения (находят косинус). Так, на будущее.
Добавлено (06 ноября 2017, 23:16) --------------------------------------------- И да, если будешь размещать гусеницы в глобальных координатах, предварительно нужно повернуть AB на угол поворота танка с обратным знаком, посчитать всё, что нужно и повернуть обратно. Так мы делаем преобразование системы координат из глобальной в локальную и наоборот.
Либо строить единичный вектор по иксу, поворачивать его на угол поворота танка, и тут уже через скалярное произведение этого вектора и вектора AB находится.
Извиняюсь, если сложно говорю. Просто я гамак в глаза не видел, поэтому хочу побольше теории впихнуть, на всякий случай.
А в гамаке можно спрайты растягивать? Если бы я писал такое на своих любимых плюсах, я бы сделал так: 1)берём нижнюю точку первого колеса, нижнюю точку второго, считаем, восколько раз это расстояние больше длины звена гусеницы 2)растягиваем гусеницу по оси X во столько же раз. 3)Считаем угол между горизонтальной осью и линией, соединяющей точки колёс. (можно найти косинус из скалярного произведения векторов или считать арктангенс) 4)Поворачиваем растянутую гусеницу на этот угол. 5)Ставим гусеницу в нужное положение. https://vk.com/beezoya
Сообщение отредактировал puksus - Понедельник, 06 Ноября 2017, 14:49
Щас же. Наш универ - бесполезная груда кирпича. С++ я выучил летом после 10 класса школы. Совершенствовал его впоследствии, выводя 3д графику на связке с++ и DirectX и так, по мелочи всякую фигню лениво пописывал, а после, пересев на 2д и SFML, учавствовал в конкурсах. Универ тут вообще ни при чём)) В нашем универе полезным можно назвать только 1, максимум, 2-й курс, там дали матан и приучили выкручиваться из полной попки за короткое время. https://vk.com/beezoya
Всё-таки насчёт курсов ещё раз. Может, попробуете записаться на курсы в такую контору, в которой вас не тошнило бы работать? Вот у нас с нашим универом связаны интернациональные компании Epam и IBA. Они (ну Epam, по крайней мере, точно) регулярно набирают студентов на БЕСПЛАТНЫЕ курсы. Длятся эти мучения год. Постепенно из этого потока народ отсеивается по тем или иным причинам, и в итоге из группы остаётся парочка человек, которых они берут на работу (короче говоря, самых способных и выносливых). Вы можете попробовать найти фирму со схожей политикой, пройти старательно до конца их курсы, и возможно, вас возьмут. А если у вас будет стаж работы программиста, то работу будет найти намного проще. Я вот ищу себе преддипломную практику, просматривал вакансии, везде работодатели орут, что нужно 1-3 года стажа на должность Junior. Без опыта ты никому не нужен. Пичалька. Так что, в начале карьеры программиста, возможно, главное - ХОТЬ КАК-ТО набить себе пару лет стажа, пусть даже не по интересу. Тогда в вашем распрекрасном резюме часть с опытом работы будет не живописным белым пятном.
ЦитатаMoonMay ()
начать учить C++, C# и Bluprint, или все же реально собрать команду для разработки браузерки?
Больше знаешь языков - больше возможностей. Мне тут позвонили из конторы, в которую слал резюме. Нет, плюсовик им не нужен, у них есть кандидаты поопытнее, чем я, но если я готов изучать питон, то я так понял, питонист им бы не помешал. В итоге прислали тестовое задание. Маленькая часть по питону, большая часть по с++. БольшУю часть по с++ решил в 2 раза быстрее и безболезненнее, чем маленькую часть по питону (Теперь жду, чо они мне ответят по поводу решённого тестового задания). Но они могли бы и даже не слать задание, а послать меня. И я мог бы заранее изучить питон и ещё дохренищу языков вместо того, чтобы страдать хернёй на младших курсах, о чём сейчас жалею. Так что неизвестно, когда припрёт знание определённого языка. Когда я глядел вакансии, даже если на программиста с++, то почти всегда ниже было написано чтонибудь типа "будет плюсом знание с#, Java, Brainfuck". Такия воть пирожки. https://vk.com/beezoya
Animan2010, спасибо, просмотрю как-нибудь потом для общего развития. Судя по её оглавлению, там не описано то, что мне конкретно сейчас надо. https://vk.com/beezoya
Это если ошибки допускать, потому что он довольно низкоуровневый. Он работает шустро отчасти потому, что оставляет на вашу совесть следить чтоб всё было в рамках приличного. Но ошибки в любых языках приводят, собсно, к ошибкам, так что не всё так уж плохо. Раньше я думал, что с++ - бох и что все остальные языки мусор, но через некоторое понял, что каждому языку своё применение. Для приложений, для которых производительность не шибко важна, использование с++ может быть нецелесообразным. В Java и c# есть крутые фичи типа сериализации, которую сложно добиться, не делая язык более высокоуровневым и медленным, также джава - прикольно смотрится в серверных приложениях. Lua хорош для скриптов. И т.д. https://vk.com/beezoya
Сообщение отредактировал puksus - Вторник, 31 Октября 2017, 02:24
PepsiDimon, мне с++ кажется одним из самых простых языков из-за своей прозрачности. Вот возьмём джаву, она работает на виртуальной машине, в ней есть сборщик мусора, в ней дженерики (аналог шаблонов в с++) сделаны кривейшим образом (из-за специфики виртуальной машины). В джаве есть ссылочные типы, а есть обычные, а в с++ всё просто, все типы наравне, а хочешь обращаться по ссылке - собсно явно это и указывай с помощью указателя(или ссылки, которую компилятор потом всё равно в указатель переварит). Если хочешь знать язык на профессиональном уровне, эти все тонкости нужно знать, как, где, что и почему работает. Так что лично для меня джава кажется куда сложнее, чем с++. Мне кажется, на неё нужно убабахать больше времени, чтобы полностью освоить.
Поэтому для меня эта тема - скорее о методах, которые будут общими для всех языков. Но поскольку я любитель с++, то было бы намного приятнее читать обо всём этом на с++, ибо понятнее. Но необязательно.
Да, в с++ есть свои маленькие попки, например, размеры стандартных типов не регламентированы и на разныхз компиляторах тот же int может быть разного размера, и свои кое-какие внутренние хитрости есть, но всё же... https://vk.com/beezoya
Сообщение отредактировал puksus - Понедельник, 30 Октября 2017, 20:38
Ну, не знаю, правила разные бывают. Вот такого кода я не видел вообще никогда. Чёт как-то стрёмно выглядит подчёркивание в конце мембера.
Венгерская нотация, например, орёт делать мемберы через m_ ,и я долгое время называл мемберы примерно так "int mVariable", но сколько ни пишу, в 50% случаев всё равно забываю поставить эту буквочку m, в итоге пишу код, спохватываюсь, что оной буквы нет и лезу исправлять. Кроме того, таким макаром очень неудобно вечно зажимать кнопку shift для первой буквы названия члена после m. Так шо теперь подумываю начать ставить подчёркивание после m и начинать с мелкой буквы. Так же оная нотация орёт называть классы, начиная с пуквы C. И я долго действительно так их называл, но чот надоело, неудобно и некрасиво, так что тут соглашусь со статьёй, что надо просто делать первую букву в названии класса большой и не париться - просто и понятно.
Ну и ещё там пара стрёмных моментов есть, мол, никогда не сокращайте initialize до init (всегда пишу только init), не используйте tab(помойму в сумме меньше нервов будет написать парсер, который заменит при надобности все табы на пробелы, чем истерично жать на пробел, когда надо делать отступы, и отступ с табом выглядит намного опрятнее, чем два пробела, а делать больше пробелов - вредно для большого пальца левой руки + меньше боли, например, при удалении отступов), никогда не полагаться на автоприведение типов (да ну, засорять читабельность кода лишними кастами), специально избегать циклов do-while тоже как-то бредово звучит, ну и ещё там есть несколько бредовых, на мой взгляд, идей.
Хочу привести движок своей игрухи в порядок (а если честнее, снести всё и переписать). Во-первых, мне подсказали вот эту очуменную книгу по паттернам, читаю с удовольствием, очень интересно, всем рекомендую, смотрю на собственный код уже со стороны, довольно забавно. ссылька
Кроме того, хочу реализовать многопоточность в игре и поддержку скриптового языка (скорее всего lua) Может кто подсказать книжек по игровой многопоточности? Крайне желательно чтобы книга (или может, цикл статей?) пробегалась по основным моментам, не слишком углубляясь в детали и была ориентирована именно на создание игр. Потому что читать двухтысячестраничную жирную книженцию, описывающую все тонкости работы винды заунывным языком, нет времени (и желания тоже)
Ну и про практику внедрения скриптовых языков в игры тоже хочется что-нибудь годное нарыть, не хочу велосипеды изобретать и спотыкаться. Потому что хардкодить логику отдельных персонажей, например, прямо в движок мне кажется ущербным. Да и не совсем понятно, до какой степени абстрагировать движок от скриптов. В общем, хочется на эту тему чёнибудь почитать и подумать.
Ну и ещё бы что-нибудь по алгоритмизации. Что лучше, читать Кормена или emaxx? Может, есть ещё какие ресурсы, где годно всё разъясняется?
Ну и по сетям что-нибудь желательно. Игра-то моя не будет поддерживать мультиплеер, но основные моменты в разработке сетевых игр, я считаю, нужно знать, ибо может в любой момент припереть.
PS. Тех литературу на английском читаю почти свободно, так что можно предлагать и чтиво на оном языке. PPS. Я понимаю, что нагуглить можно что угодно, но может, кто-то уже гуглил эти дела до меня и отсеял то, что похуже, и может поделиться ценными знаниями в оном достопримечательном вопросе PPPS. Пишу на с++, поэтому крайне желательно найти книги, завязанные именно на этот язык. https://vk.com/beezoya
Добавлено (22 октября 2017, 12:01) --------------------------------------------- На чём я пока что остановился: https://my.mail.ru/video/embed/1787807981232979969 персонаж управляется с клавиатуры, физически взаимодействует с уровнем, можно надевать разную эккипировку. Начата работа над боевой системой. https://vk.com/beezoya
Сообщение отредактировал puksus - Воскресенье, 22 Октября 2017, 12:03