C/C ++ Гнучкий, але швидкий спосіб зв'язування елементів інтерфейсу з функціями

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

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

Але у мене виникають проблеми з ефективним способом зв'язування фактичної функціональності з тригерами, як кнопки. Я подумав про визначення кнопки:

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
Я думаю, що Ян Янг отримує те, що подія кліків не є вузьким місцем у реагуванні на вашу гру. Навіть якщо нам доведеться зануритися через кілька шарів непрямого поширення, щоб дістатися до дії, яку потрібно виконати, потрібно зробити це лише один раз за клік. Це не код, який виконується в тісному циклі від сотень до мільйонів разів. Це той "гарячий" код, який ми робимо неодноразово, що потребує цієї уважної уваги до продуктивності, щоб уникнути заїкань, відставань і частоти кадрів. Обробник кліків сам по собі не призведе до значного впливу на продуктивність, якщо ви не зробите щось дуже неправильне.
додано Автор DMGregory, джерело
Покажчики та віртуальні функції не будуть помітно впливати на продуктивність у випадку елементів UI. Ви повинні побудувати щось читабельне, налаштувати і повторно використовувати, а також зосередити свої зусилля на оптимізацію на більш великих операціях процесора/пам'яті в інших місцях у більшій кодовій базі.
додано Автор Ian Young, джерело
@DMGregory Так, саме це я отримую. Такі речі, як виявлення зіткнень, фізика тощо, є набагато кращими кандидатами для оптимізації.
додано Автор Ian Young, джерело
Я думаю, що взаємодія з інтерфейсом користувача повинна бути високо оптимізована, оскільки це безпосередній контакт, який користувач має до програми, і він дійсно встановлює "відчуття" програми. Незважаючи на те, що об'єктно-орієнтований підхід може бути достатнім під час стадій програми, неприємні зависання можуть з'явитися частіше в моменти високої взаємодії з користувачем/програмою, що особливо розчаровує ігри. Звичайно, я не маю доказів, що ці кілька кіосків доступу до покажчика дійсно будуть значними для видимої продуктивності, але тепер, коли я вже на ньому, я б просто хотів знайти ідеальне рішення.
додано Автор Victor, джерело

6 Відповіді

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

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

2
додано
Те, що більшість людей тут має на увазі, полягає в тому, що код для виконання обробника кнопки є лише часткою складності самого обробника (наносекунди проти сотень наносекунд). Оптимізація коду завжди велика, але якщо код починається з наносекунд, виграш буде незначним. Подумайте про це так: ви оптимізуєте пари процедур виклику обробників (наприклад, 3 нс) і самі обробники (наприклад, 300 нс). У кращому випадку ви будете прискорювати комбінований процес на 1% (1 нс + 300 нс). Ваше ставлення до пошуку оптимізованих рішень є фантастичним, але я буду стежити за тим, де корисна оптимізація.
додано Автор Fleshgrinder, джерело
@stimulate Я розумію, що ви намагаєтеся мінімізувати пропуски в кеші за кліками користувачів. Це означає, що ви намагаєтеся оптимізувати наносекунди на операції, яку потрібно виконати протягом 100 мс. Я не буду навіть замислюватися, чому це насправді не буде працювати, тому що навіть якщо б це спрацювало, прискорення було б на 100% нерелевантним
додано Автор George, джерело
@stimulate Ні, ці наносекунди не складаються. Ви не будете мати більше, ніж одну кнопку натискання кожні 100 мс. І я сказав вам, як це можна зробити, у другому пункті моєї відповіді. Я не звертаюся до незв'язаних деталей, оскільки вони не пов'язані між собою, і ви зараз відволікаєтеся на них. Розмови про незв'язані деталі не усунуть цю відволікання.
додано Автор George, джерело
@stimulate Ласкаво просимо. Моя порада була специфічна для кнопок, resp. взаємодіючі елементи інтерфейсу. Для інших речей, зокрема, даних, які повторюються одним і тим же кодом, послідовно і в критичному шляху (наприклад, ефекти частинок), локалізація кешу є абсолютно чимось на що звернути увагу. Але пов'язані з користувачем інтерфейси повинні бути простими у використанні, легко змінюватися, а продуктивність просто не має значення, якщо ви досить швидко (що зазвичай становить <100 мс, а в деяких випадках <10 мс порядків величин більше, ніж кеш-промаху).
додано Автор George, джерело
@JellevanCampen Щоб бути справедливим, пропустіть кеш - це те, що турбує ОП, - легко може досягти 100 нс. stackoverflow.com/a/29188516/1612743 Але ці 0,0001 мілісекунди потрібно розглядати не по відношенню до загального часу обробника кнопки, але щодо часу, доступного для надання відгуку користувача (100 мс), і відносно часу процесора, доступного в поточного кадру (15 мс). Ваша точка залишається, це лише цифри, які змінюються.
додано Автор George, джерело
У основному я хочу уникати використання покажчиків, тому що я хочу уникати використання new . Покажчики на екземпляри, створені за допомогою new , завжди повинні мати доступ до випадкової пам'яті (RAM), що призводить до зависання CPU (якщо запитаний екземпляр не знаходиться в рядку кешу). Це те, що я прочитав, і тому я зберігаю всі екземпляри будь-яких матеріалів безпосередньо в масивах. Коли я обробляю ці екземпляри, я повторюю всі з них один раз або отримую доступ до масивів через індекси. Це, звичайно, може бути зроблено також з вказівниками (до локальної пам'яті), але, зрештою, індексація масивів є такою ж, як і розіменування покажчиків на локальну пам'ять.
додано Автор Victor, джерело
Ці наносекунди складаються. За один кадр потрібно обробляти більше, ніж тільки кнопки. Я хотів би використовувати стільки часу, скільки я можу, і я просто шукаю стиль програмування, який звертає на це увагу. Я поважаю, що у вас більше досвіду, ніж я, але просто кажучи: "Його не варто в будь-якому випадку, я навіть не кажу вам, як це може бути зроблено" не переконує мене.
додано Автор Victor, джерело
Кнопка є лише прикладом. Моя програма буде швидко розвивається шутер, тому вона повинна бути в змозі впоратися з багатьма натисканнями клавіш, натискання кнопок і змінити стан легко. Може бути, я занадто зацікавлений, але я тільки намагаюся зробити цю річ власне. Я буду дивитися на функцію std :: і випробувати кілька представлених рішень. Спасибі за вашу відповідь
додано Автор 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
додано
Мова йде не тільки про простоту використання. Це також стосується повторного використання та гнучкості. Крім того, вартість продуктивності натискання кнопки в інтерфейсі користувача є незначною. Виклик вказівника з загорнутою функцією через віртуальну функцію все одно відбуватиметься швидше, ніж можна натиснути на кнопку, якщо натискання кнопки не активує щось інтенсивне процесора, в цьому випадку, це те, що слід оптимізувати, а не кнопку.
додано Автор Ian Young, джерело
Я думаю, що це гарна ідея розглядати поведінку як щось окреме від кнопки, таким чином я можу перенести поведінку до різних кнопок. Хоча я все ще дійсно не люблю ідею віртуальних функцій і похідних класів. Я не впевнений, чому, але я думаю, це тому, що він знімає прозорість речей, і я відчуваю, що це компроміс продуктивності для легкого використання
додано Автор Victor, джерело

Ви можете перевернути все навколо, перевертаючи свій графічний інтерфейс кожної рамки і роблячи

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

де кнопка (int) шукатиме квад і перевірятиме, чи гравець натиснув у цій області. У той же час він також запланує його залучення.

Для прапорців ви передаєте покажчик на буль, що представляє стан

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

if(selected){
    //...
}

Цей шаблон іноді називається графічним інтерфейсом користувача негайного режиму.

0
додано

Зробити Button абстрактним класом за допомогою віртуальної функції click .

Потім створіть похідний клас для кожної кнопки, яка реалізує click , виконуючи те, що повинна робити кнопка на події кліків.

0
додано
@stimulate Чому ви позначали питання C + +, якщо ви хочете отримати рішення C? І міркування щодо низького рівня продуктивності навряд чи мають значення тут. Накладні витрати на виклик віртуального методу не матимуть значення, якщо ви не будете обробляти тисячі подій кліків на секунду.
додано Автор Philipp, джерело
Я хотів би уникати віртуальних функцій і похідних класів, тому що я хочу дотримуватися конкретного, функціонального стилю програмування. Особливо в цій частині моєї програми, яка повинна бути дуже чуйною.
додано Автор Victor, джерело
IT KPI C/С++ новым годом
IT KPI C/С++ новым годом
747 учасників

Чат обсуждения С/С++. - Вопросы "напишите за меня лабу" - это оффтоп. - Оффтоп, флуд, оскорбления и вбросы здесь не приняты. - За нарушение - предупреждение или mute на неделю. - За спам и рекламу - ban. Все чаты IT KPI: https://t.me/itkpi/1147