C/C ++ Гибкий, но быстрый способ связывания элементов пользовательского интерфейса с функциями

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

Я планирую построить все мои элементы GUI (кнопки, слайдеры, регуляторы, флажки) из Quads с дополнительной текстурой и цветом. Эти квадратики также будут служить границей для обнаружения текста и мыши.

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

struct Button{
    unsigned int quadIndex; //an index into the array of quads, which themselves store indices into arrays of positions and sizes
    void (*func)(int); //a pointer to the function to be called when this button is being interacted with. the parameter of the function will be the state of the relevant mouse button
}

Но таким образом я мог только сделать кнопку вызова функции void func (int) , которая часто заставляла меня писать функцию-обертку для каждой отдельной функциональности кнопки. Так ли это так, или есть лучший, более гибкий способ?

Редактировать: Поскольку производительность/высокая отзывчивость очень важны для меня, я бы хотел избежать указателей, виртуальных функций и производных классов. Я ищу прозрачное, конкретное решение, которое фокусируется на минимальных данных, которые должны вступить в игру для функционального и гибкого решения

0
То, что я думаю, что Ян Янг получает, заключается в том, что событие click не является узким местом в вашей реакции. Даже если нам нужно погрузиться через несколько уровней косвенности, чтобы перейти к выполнению действия, нам нужно сделать это только один раз за клик. Это не код, который выполняется в плотном цикле от сотен до миллионов раз за каждый кадр. Это тот «горячий» код, который мы делаем неоднократно, что требует такого требовательного внимания к производительности, чтобы избежать заиканий, лагов и капель кадров. Обработчик клика сам по себе не приведет к значимому влиянию на производительность, если вы не выполните что-то very неправильно.
добавлено автор DMGregory, источник
Указатели и виртуальные функции не будут заметно влиять на производительность в случае элементов пользовательского интерфейса. Вы должны создать что-то читаемое, настраиваемое и повторно используемое, а также сосредоточьте свои усилия по оптимизации на более интенсивных операциях с процессором/памятью в другом месте вашей большей базы кода.
добавлено автор Ian Young, источник
@DMGregory Да, это именно то, что я получаю. Такие вещи, как обнаружение столкновений, физика и т. Д., Намного лучше подходят для оптимизации.
добавлено автор Ian Young, источник
Я думаю, что взаимодействие с пользовательским интерфейсом должно быть сильно оптимизировано, поскольку это прямой контакт, который пользователь имеет в программе, и он действительно устанавливает «ощущение» приложения. Несмотря на то, что объектно-ориентированный подход может быть достаточным на этапах жидкостной программы, могут возникнуть неприятные зависания, чаще в моменты высокого взаимодействия с пользователем/программой, что особенно расстраивает игры. Конечно, у меня нет доказательств того, что эти несколько киосков доступа к указателям на самом деле будут значительными для видимой производительности, но теперь, когда я уже нахожусь, я просто хотел бы найти идеальное решение.
добавлено автор Victor, источник

6 ответы

Во-первых, ваша роль в том, чтобы избегать указателей и виртуальных функций из-за быстроты нажатия кнопок, ужасно ошибочна - вы слушали кого-то, кого вы не должны слушать. Если мы обсуждаем указатели на производительность, мы говорим о наносекундах ( наихудший случай 65 нс, согласно одному источнику ) , и если вы хотите быстро реагировать на нажатие кнопки, чтобы ее можно было воспринимать как отзывчивую, у вас есть 100'000'000 этих наносекунд, чтобы тратить, как вам будет угодно.

Тем не менее, вы либо смотрите на виртуальные функции (вероятно, в сочетании с std :: unique_ptr ), на std :: function , или если вы находитесь на старой версии C ++ на странице boost :: function . Все это позволяет хранить  любое количество дополнительных аргументов, а также разрешить корректное управление ресурсами своими деструкторами. Выполнено правильно, std :: function с lambdas должен быть самым чистым кодом для чтения, но реализация с виртуальными функциями будет легче отлаживать.

2
добавлено
То, что большинство людей здесь означает, заключается в том, что код для выполнения обработчика кнопок - это всего лишь часть сложности самого обработчика (наносекунды против сотен наносекунд). Оптимизация кода всегда велика, но если для начала требуется только наносекунды, выигрыши будут незначительными. Подумайте об этом так: вы оптимизируете пары обработчиков-обработчиков (например, 3 нс) и самих обработчиков (например, 300 нс). В лучшем случае вы ускорите комбинированный процесс на 1% (1 нс + 300 нс). Ваше отношение к поиску оптимизированных решений является фантастическим, но я буду следить за тем, какая оптимизация полезна.
добавлено автор Fleshgrinder, источник
@imimulate Я понимаю, что вы пытаетесь минимизировать промахи в кэше при нажатии на пользователя. Это означает, что вы пытаетесь оптимизировать наносекунды при выполнении операции, которая должна завершиться в течение 100 мс. Я даже не пойду, почему это на самом деле не работает, потому что, даже если это сработает, ускорение будет на 100% неуместным
добавлено автор George, источник
@imimulate Нет, эти наносекунды не складываются. У вас не будет более одного нажатия кнопки каждые 100 мс. И я рассказал вам, как это можно сделать во втором абзаце моего ответа. Я не вхожу в несвязанные детали, потому что они не связаны, и вы в настоящее время отвлекаетесь на них. Разговор о несвязанных деталях не устранит этого отвлечения.
добавлено автор George, источник
@imimulate Добро пожаловать. Мой совет касался кнопок, соответственно. интерактивные элементы пользовательского интерфейса. Для других вещей, в частности, данные, которые повторяются одним и тем же кодом, последовательно и в критическом пути (например, эффекты частиц), кэш-локация абсолютно заслуживает внимания. Но связанный с UI материал должен быть простым в использовании, легко меняющимся, а производительность просто не имеет значения, если вы достаточно быстро (обычно <100 мс, а в некоторых особых случаях <10 мс, несколько порядки величин, превышающие пропуски кеша).
добавлено автор George, источник
@JellevanCampen Справедливости ради следует отметить, что кеш-мисс - о чем беспокоит OP - может легко достигнуть до 100 нс stackoverflow.com/a/29188516/1612743 Но эти 0,0001 миллисекунды необходимо рассматривать не относительно общего времени обработчика кнопок, а относительно времени, доступного для обеспечения обратной связи с пользователем (100 мс), и относительно времени процессора, доступного в текущий кадр (15 мс). Ваша точка остается, это просто цифры, которые меняются.
добавлено автор George, источник
В основном я хочу избегать использования указателей, потому что я хочу избежать использования new . Указатели на экземпляры, созданные с помощью new , всегда имеют доступ к случайной памяти (ОЗУ), из-за чего CPU останавливается (если только запрашиваемый экземпляр не находится в строке кэша). Это то, что я прочитал, и именно поэтому я храню все экземпляры чего-либо непосредственно в массивах. Когда я обрабатываю эти экземпляры, я перебираю их все один раз или получаю доступ к массивам через индексы. это, конечно же, можно было бы сделать с помощью указателей (в локальную память), но в конце концов, массивы индексирования такие же, как указатели разыменования в локальную память.
добавлено автор Victor, источник
Эти наносекунды складываются. В течение одного кадра нужно обрабатывать больше, чем только кнопки. Я хотел бы использовать как можно больше времени, и я просто ищу стиль программирования, который обращает на это внимание. Я уважаю, что у вас больше опыта, чем у меня, но просто говорю: «Его ничего не стоит, я даже не говорю, как это можно сделать» не убеждает меня.
добавлено автор Victor, источник
Кнопка является лишь примером. Моя программа будет быстро развивающейся игрой шутера, поэтому она должна иметь возможность обрабатывать многие нажатия клавиш, щелчки кнопок и легко изменять состояние. Может быть, я слишком обеспокоен, но я только пытаюсь сделать это правильно. Я посмотрю на std :: function и попробую несколько представленных решений. Спасибо за ваш ответ
добавлено автор Victor, источник

С каким стандартом C ++ вы пишете? C ++ 11 Внесена поддержка Lambdas, которые в основном неназванные функции, которые могут «захватывать» переменные.

Таким образом, вы можете просто определить Lambda вместо вашей функции, а затем написать любую функцию, которую вы на самом деле хотите вызвать там. Вы все еще создаете функциональные адаптеры, но поскольку вы можете написать их в режиме онлайн, это гораздо более компактно:

myButton.func = [someExternalVariable](int idx){ doMagic( buttons[idx], someExternalVariable, 42 ); };
2
добавлено

Вы можете использовать шаблоны и скрыть тип функции в интерфейсе:

class ICallback
{
public:
    virtual void Execute() = 0;
protected:
private:
}

template  ButtonCallback : public ICallback
{
public:
    ButtonCallback(T c_) : c(c_) {}

    void Execute() override
    {
        c();
    }
protected:
private:
    const T c;
}

class Button
{
public:
    Button() 
    {
        clickCallback = new ButtonCallback(/* any function pointer/std::function here. */);
    }
private:
    ICallback* clickCallback;
}

Все переменные для обратных вызовов должны быть типа ICallback , что означает, что вам нужно «удвоить» любой интерфейс, который вы хотите, с помощью обратных вызовов.

1
добавлено

Create a ButtonBehaviour abstract class with a virtual execute(Button& b)

Тогда ваша кнопка может иметь несколько типов поведения:

class Button
{
public:
    void parseMouse(Mouse& m){
        if(isInside(m.pos)) {
            if(m.clicked) {
              m_on_click->execute(*this);
            }
            else {
                if (!m_mouse_inside) {
                    m_mouse_inside = true;
                    m_mouse_enter->execute(*this);
                }
            }
            else {
                if (m_mouse_inside) {
                   m_mouse_inside = false;
                   m_mouse_leave->execute(*this);
                }
            }
        }

    }
private:
    ButtonBehaviour* m_on_mouse_enter;
    ButtonBehaviour* m_on_mouse_leave;
    ButtonBehaviour* m_on_click;
    bool m_mouseinside;
};

Затем мышь вызывает соответствующий объект в зависимости от состояния мыши.

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

1
добавлено
Дело не только в простоте использования. Это также касается возможности повторного использования и гибкости. Кроме того, производительность нажатия кнопки в пользовательском интерфейсе незначительна. Вызов завернутого указателя функции с помощью виртуальной функции будет происходить быстрее, чем вы можете нажать кнопку, если кнопка не запускает что-то интенсивное CPU, и в этом случае это то, что нужно оптимизировать, а не кнопку.
добавлено автор Ian Young, источник
Я думаю, что это хорошая идея, чтобы относиться к поведению как к чему-то отдельному от кнопки, таким образом, я могу реагировать на поведение на разные кнопки. Хотя мне по-прежнему не нравится идея виртуальных функций и производных классов. Я не уверен, почему, но я думаю, что это потому, что это отвлекает от прозрачности вещей, и я чувствую, что это компромисс производительности для легкого использования
добавлено автор Victor, источник

Вы можете обойти все вокруг, перейдя по вашему гию каждый кадр и сделав

if(button(quadIndex)){
   //handle click
}

где кнопка (int) будет искать квадрат и посмотреть, щелкнул ли плеер в этой области. В то же время он также планирует, что он будет нарисован.

Для флажков вы передаете указатель на bool, который представляет состояние

if(checkBox(quadIndex, &selected)){
   //handle that selected changed
}

if(selected){
    //...
}

Этот шаблон иногда называют графическим интерфейсом немедленного режима.

0
добавлено

Создайте Button абстрактный < с помощью virtual function click .

Затем создайте производный класс для каждой кнопки, который реализует click , делая то, что кнопка должна делать на событие click.

0
добавлено
@imimulate Почему вы отметили вопрос C ++, если хотите решение C? И вряд ли здесь будут иметь значение проблемы с низким уровнем производительности. Накладные расходы на производительность при вызове виртуального метода не будут иметь значения, если вы не будете обрабатывать тысячи событий щелчка в секунду.
добавлено автор Philipp, источник
Я бы хотел избежать виртуальных функций и производных классов, потому что я хочу придерживаться конкретного, функционального стиля программирования. Особенно в этой части моей программы, которая должна быть очень отзывчивой.
добавлено автор Victor, источник
pro.cxx
pro.cxx
3 049 участник(ов)

C/C++ chat 0. Простые вопросы, лабы и о IDE — в чат новичков @supapro 1. Не хамим, не переходим на личности, не вбрасываем утверждения без доказательств 2. No Ads, offtop, flood Объявления о вакансиях и евенты - в лс @AlexFails https://t.me/ProCxx/259155

supapro.cxx
supapro.cxx
1 925 участник(ов)

Чат для тех, кто немного знает C++, простые вопросы по реализации, синтаксису и ide – сюда, а для другого есть: /Главный чат по серьезным вопросам — @ProCxx /Чат по обсуждению всего — @fludpac

Веб Дизайн/UI/UX
Веб Дизайн/UI/UX
1 495 участник(ов)

Правила группы: • Запрещено оскорбления участников. • Запрещена реклама своих услуг • Пиар"групп, сайтов, форумов" • Мат, флуд. За нарушение правил - БАН! Группа Вк: vk.com/web_structure Freelancer: @web_fl Верстка сайтов: @web_structure Автор: @M_Boroda

Сообщество UX / UI - дизайнеров
Сообщество UX / UI - дизайнеров
975 участник(ов)

uxchat.live - сообщество дизайнеров интерфейсов в Telegram. 18+ Правила сообщества http://telegra.ph/Pravila-uxchatlive-20-07-19

UI/UX chat
UI/UX chat
656 участник(ов)

Для всех кто дизайнит и интересуется дизайном UX, UI, Web, продуктовым и тд. Пожалуйста, воздержитесь от спама, мата и флуда! Админ бдит и метает банхаммер. По размещению вакансий к @pilot_bbk Наши чаты: @seochat @ppcchat @devschat @smmhell

C++ Russia
C++ Russia
384 участник(ов)

Сообщество разработчиков C++ в Telegram.

cxx.Дискуссионная
cxx.Дискуссионная
298 участник(ов)

это не двач, общайтесь вежливо; разговор на почти любые темы; Не согласны с баном? В лс @AlexFails, @ivario

Веб-Технологи: UI/UX, Вёрстка, Фронтенд
Веб-Технологи: UI/UX, Вёрстка, Фронтенд
167 участник(ов)

Всё про веб-дизайн и вёрстку. А также: HTML, CSS, флекс и бутстрапы, шаблонизаторы, препроцессоры, методологии, аглифаеры, улучшаторы и обфускаторы. Обсуждаем темы юзабилити, устраиваем А/В тесты лендингов, и проводим аудит.

DTP :: @DTPublish
DTP :: @DTPublish
147 участник(ов)

Обсуждаемые темы: полиграфия, препресс, верстка, дизайн, иллюстрации, скрипты, плагины. Канал - @DTPublishing

C++ для маленьких и тупых
C++ для маленьких и тупых
105 участник(ов)

Лоу левел (по среднему IQ участников) чатик ExtremeCode @extremecode Флудилка @extremecode_rest