У меня есть код, который я хочу запустить только один раз, хотя обстоятельства, которые вызывают этот код, могут возникать несколько раз.
Например, когда пользователь щелкает мышью, я хочу нажать на вещь:
void Update() {
if(mousebuttonpressed) {
ClickTheThing(); // I only want this to happen on the first click,
// then never again.
}
}
Однако с этим кодом каждый раз, когда я щелкаю мышью, происходит щелчок мышью. Как я могу сделать это только один раз?
architecture
events
MichaelHouse
источник
источник
Ответы:
Используйте логический флаг.
В показанном примере вы изменили бы код следующим образом:
Далее, если вы хотите иметь возможность повторить действие, но ограничить частоту действия (т.е. минимальное время между каждым действием). Вы бы использовали аналогичный подход, но сбросили флаг через определенное время. Смотрите мой ответ здесь для большего количества идей на этот счет.
источник
onStart() { delegate = doSomething; } ... if(mousebuttonpressed) { delegate.Execute(); }
и ,void doSomething() { doStuff(); delegate = funcDoNothing; }
но в конце концов все варианты в конечном итоге с флагом некоторых видов вы установили / неустановленные ... и делегатом в данном случае не что иное , ( за исключением , если у вас есть более двух вариантов , что делать , может быть?).Если флага bool недостаточно, или вы хотите улучшить читабельность * кода в
void Update()
методе, вы можете использовать делегаты (указатели на функции):Для простого «выполнить один раз» делегаты излишни, поэтому я бы предложил вместо этого использовать флаг bool.
Однако, если вам нужны более сложные функциональные возможности, делегаты, вероятно, являются лучшим выбором. Например, если вы хотите, чтобы цепочка выполняла больше разных действий: одно при первом нажатии, другое при втором и еще одно при третьем, вы можете просто выполнить:
вместо того, чтобы мучить ваш код десятками различных флагов.
* за счет снижения удобства сопровождения остальной части кода
Я немного профилировал результаты, как и ожидал:
Первый тест был запущен на Unity 5.1.2, измеренный
System.Diagnostics.Stopwatch
на 32-битном построенном проекте (не в дизайнере!). Другой в Visual Studio 2015 (v140) скомпилирован в 32-разрядном режиме выпуска с флагом / Ox. Оба теста выполнялись на процессоре Intel i5-4670K с частотой 3,4 ГГц, с 10 000 000 итераций для каждой реализации. код:вывод: хотя компилятор Unity хорошо справляется с оптимизацией вызовов функций, давая примерно одинаковый результат как для положительного флага, так и для делегатов (21 и 25 мс соответственно), ошибочное прогнозирование ветвления или вызов функции по-прежнему довольно дороги (примечание: делегат следует считать кэшем в этом тесте).
Интересно, что компилятор Unity не достаточно умен, чтобы оптимизировать ветку, когда существует 99 миллионов последовательных неправильных предсказаний, поэтому ручное отрицание теста дает некоторое повышение производительности, дающее лучший результат в 5 мс. Версия C ++ не демонстрирует какого-либо повышения производительности для условия отрицания, однако общие накладные расходы на вызов функции значительно ниже.
самое главное: разница в значительной степени иррелеват для любого реального сценария
источник
Для полноты
(На самом деле не рекомендуется делать это, так как на самом деле довольно просто написать что-то вроде этого
if(!already_called)
, но было бы «правильно» сделать это.)Неудивительно, что стандарт C ++ 11 обозначил довольно тривиальную проблему однократного вызова функции и сделал ее супер-явной:
}
Следует признать, что стандартное решение несколько превосходит тривиальное при наличии потоков, поскольку оно по-прежнему гарантирует, что всегда происходит ровно один вызов, а не что-то другое.
Однако вы обычно не запускаете многопоточный цикл обработки событий и одновременно нажимаете кнопки нескольких мышей, так что безопасность потоков для вашего случая немного излишня.
На практике это означает, что в дополнение к поточно-безопасной инициализации локального логического значения (которое C ++ 11 гарантирует в любом случае поточнобезопасность), стандартная версия также должна выполнить атомарную операцию test_set перед вызовом или не вызовом функция.
Тем не менее, если вам нравится быть явным, есть решение.
РЕДАКТИРОВАТЬ:
Да. Теперь, когда я сидел здесь и смотрел на этот код несколько минут, я почти склонен рекомендовать его.
На самом деле это не так глупо, как может показаться на первый взгляд, на самом деле, оно очень четко и недвусмысленно выражает ваши намерения ... возможно, лучше структурировано и более читабельно, чем любое другое решение, в котором используется то
if
или иное .источник
Некоторые предложения будут варьироваться в зависимости от архитектуры.
Создайте clickThisThing () как указатель на функцию / вариант / DLL / так / и т. Д. И убедитесь, что он инициализирован для требуемой функции при создании экземпляра объекта / компонента / модуля / и т. Д.
В обработчике всякий раз, когда нажимается кнопка мыши, вызывается указатель функции clickThisThing (), а затем немедленно заменяется указатель на другой указатель функции NOP (без операции).
Нет необходимости в флагах и дополнительной логике для того, чтобы кто-то напортачил позже, просто вызывайте его и заменяйте его каждый раз, и, поскольку это NOP, NOP ничего не делает и заменяется NOP.
Или вы можете использовать флаг и логику, чтобы пропустить вызов.
Или вы можете отключить функцию Update () после вызова, чтобы внешний мир забыл об этом и никогда больше не вызывал.
Или у вас есть метод clickThisThing (), который использует одну из этих концепций, чтобы ответить полезной работой только один раз. Таким образом, вы можете использовать базовый объект clickThisThingOnce () / компонент / модуль и создавать его в любом месте, где вам нужно это поведение, и ни одному из ваших средств обновления не нужна специальная логика повсюду.
источник
Используйте указатели на функции или делегаты.
Переменная, содержащая указатель функции, не должна быть явно проверена, пока не потребуется внести изменения. И когда вам больше не понадобится указанная логика для запуска, замените на ref пустую / no-op функцию. Сравните с выполнением условных проверок каждого кадра на протяжении всей жизни вашей программы - даже если этот флаг был необходим только в первые несколько кадров после запуска! - нечисто и неэффективно. (Однако в этом отношении мнение Бореала о предсказании признается.)
Вложенные условные выражения, которые они часто составляют, даже более дорогостоящи, чем проверка одного условного выражения в каждом кадре, поскольку предсказание ветвлений больше не может действовать в этом случае. Это означает , что регулярные задержки по порядку 10s циклов - трубопровод киосков по преимуществу . Вложенность может происходить выше или ниже этого логического флага. Мне вряд ли нужно напоминать читателям о том, как сложные игровые циклы могут быстро стать.
По этой причине существуют указатели на функции - используйте их!
источник
В некоторых языках сценариев, таких как javascript или lua, можно легко выполнить тестирование ссылки на функцию. В Lua ( love2d ):
источник
В компьютере с архитектурой фон Неймана в памяти хранятся инструкции и данные вашей программы. Если вы хотите, чтобы код выполнялся только один раз, вы можете сделать так, чтобы код в методе перезаписывал метод на NOP после завершения метода. Таким образом, если метод будет запущен снова, ничего не произойдет.
источник
В Python, если вы планируете быть придурком для других людей в проекте:
этот скрипт демонстрирует код:
источник
Вот еще один вклад, он немного более общий / многократно используемый, немного более читаемый и обслуживаемый, но, вероятно, менее эффективный, чем другие решения.
Давайте инкапсулируем нашу некогда выполняемую логику в классе Once:
Использование, как показано в примере, выглядит следующим образом:
источник
Вы можете рассмотреть использование статических переменных.
Вы можете сделать это в
ClickTheThing()
самой функции или создать другую функцию и вызвать ееClickTheThing()
в теле if.Это похоже на идею использования флага, как предложено в других решениях, но флаг защищен от другого кода и не может быть изменен в другом месте из-за его локальной функции.
источник
Я действительно столкнулся с этой дилеммой с вводом контроллера Xbox. Хотя не совсем то же самое, это чертовски похоже. Вы можете изменить код в моем примере, чтобы удовлетворить ваши потребности.
Изменить: Ваша ситуация будет использовать это ->
https://docs.microsoft.com/en-us/windows/desktop/api/winuser/ns-winuser-tagrawmouse
И вы можете узнать, как создать необработанный входной класс через ->
https://docs.microsoft.com/en-us/windows/desktop/inputdev/raw-input
Но ... теперь на супер удивительный алгоритм ... не совсем, но эй .. это довольно круто :)
* Итак ... мы можем хранить состояния каждой кнопки, которые нажаты, отпущены и удержаны !!! Мы также можем проверить время удержания, но для этого требуется один оператор if и можно проверить любое количество кнопок, но некоторые правила см. Ниже для получения этой информации.
Очевидно, что если мы хотим проверить, нажалось ли что-то, отпущено и т. Д., Вы бы сделали «If (This) {}», но это показывает, как мы можем получить состояние нажатия, а затем отключить его в следующем кадре, чтобы ваш » ismousepressed "на самом деле будет ложным при следующей проверке.
Полный код здесь: https://github.com/JeremyDX/DX_B/blob/master/DX_B/XGameInput.cpp
Как это устроено..
Поэтому я не уверен, что значения, которые вы получаете, когда изображаете, нажата ли кнопка или нет, но в основном, когда я загружаю в XInput, я получаю 16-битное значение в диапазоне от 0 до 65535, это имеет 15-битные возможные состояния для «Нажата».
Проблема заключалась в том, что каждый раз, когда я проверял это, он просто давал мне текущее состояние информации. Мне нужен был способ конвертировать текущее состояние в значения Pressed, Released и Hold.
Так что я сделал следующее.
Сначала мы создаем переменную "CURRENT". Каждый раз, когда мы проверяем эти данные, мы устанавливаем «CURRENT» в переменную «PREVIOUS», а затем сохраняем новые данные в «Current», как показано здесь ->
С этой информацией вот где она становится захватывающей!
Теперь мы можем выяснить, если кнопка удерживается!
Это в основном сравнивает два значения, и любые нажатия кнопок, показанные на обоих, останутся равными 1, а все остальное будет установлено на 0.
Т.е. (1 | 2 | 4) & (2 | 4 | 8) даст (2 | 4).
Теперь, когда у нас есть какие кнопки "HELD" вниз. Мы можем получить отдых.
Нажать просто ... мы берем наше состояние "CURRENT" и удаляем все удерживаемые кнопки.
Выпущено то же самое, только мы сравниваем его с нашим последним состоянием.
Итак, глядя на пресс-ситуацию. Если скажем В настоящее время у нас было 2 | 4 | 8 нажат. Мы обнаружили, что 2 | 4, где проводится. Когда мы удаляем удерживаемые биты, у нас остается только 8. Это недавно нажатый бит для этого цикла.
То же самое можно применить для выпущенных. В этом сценарии «LAST» был установлен в 1 | 2 | 4. Итак, когда мы удаляем 2 | 4 бита У нас осталось 1. Итак, кнопка 1 была отпущена с момента последнего кадра.
Этот сценарий является, пожалуй, самой идеальной ситуацией, которую вы можете предложить для сравнения битов, и он предоставляет 3 уровня данных без операторов if или для циклов, всего 3 быстрых вычисления битов.
Я также хотел документировать данные о хранении, так что, хотя моя ситуация не идеальна ... что мы делаем, мы в основном устанавливаем уровни ожидания, которые мы хотим проверять.
Таким образом, каждый раз, когда мы устанавливаем наши данные Press / Release / Hold, мы проверяем, равны ли данные удержания текущей проверке бит удержания. Если это не так, мы сбрасываем время на текущее время. В моем случае я настраиваю его на индексы кадров, чтобы я знал, сколько кадров он удерживал.
Недостатком этого подхода является то, что я не могу получить индивидуальное время удержания, но вы можете проверить несколько битов одновременно. Т.е. если я установлю бит удержания на 1 | 16, если 1 или 16 не удерживаются, это потерпит неудачу. Таким образом, требуется, чтобы все эти кнопки были нажаты, чтобы продолжить тикать.
Затем, если вы посмотрите в коде, вы увидите все аккуратные вызовы функций.
Таким образом, ваш пример сводится к простой проверке того, произошло ли нажатие кнопки, и нажатие кнопки может произойти только один раз с этим алгоритмом. При следующей проверке нажать не будет, так как вы не можете нажать больше, чем один раз, когда вам нужно будет отпустить, прежде чем вы сможете нажать снова.
источник
Глупый дешевый выход, но вы можете использовать цикл for
источник