Bir kod parçasının yalnızca bir kez çalışmasını nasıl sağlayabilirim?


19

Düz-se bile bu kodu tetikleyen koşullar birden çok kez olabilir, sadece bir kez çalıştırmak istiyorum bazı kod var.

Örneğin, kullanıcı fareyi tıklattığında, bir şeyi tıklatmak istiyorum:

void Update() {
    if(mousebuttonpressed) {
        ClickTheThing(); // I only want this to happen on the first click,
                         // then never again.
    }
}

Ancak, bu kodla, fareyi her tıkladığımda, şey tıklanır. Bunu sadece bir kez nasıl yapabilirim?


7
Aslında oyuna özgü programlama sorunları içinde olduğunu düşünmediğim için bu tür komik buluyorum. Ama bu kuralı hiç sevmedim.
ClassicThunder

3
@SlassicThunder Evet, kesinlikle daha çok şeylerin genel programlama tarafında. İsterseniz kapatmak için oy verin, soruyu daha fazla yayınladım, böylece buna benzer sorular sorduklarında insanları işaret edecek bir şeyimiz olacaktı. Bu amaçla açık veya kapalı olabilir.
MichaelHouse

Yanıtlar:


41

Boole bayrağı kullanın.

Gösterilen örnekte, kodu aşağıdaki gibi olacak şekilde değiştirirsiniz:

//a boolean flag that lets us "remember" if this thing has happened already
bool thatThingHappened = false;

void Update() {
    if(!thatThingHappened && mousebuttonpressed) {
        //if that thing hasn't happened yet and the mouse is pressed
        thatThingHappened = true;
        ClickTheThing();
    }
}

Ayrıca, eylemi tekrarlamak istiyorsanız, ancak eylemin sıklığını sınırlayın (yani, her eylem arasındaki minimum süre). Benzer bir yaklaşım kullanırsınız, ancak belirli bir süre sonra bayrağı sıfırlarsınız. Bkz burada cevabımı bu konuda daha fazla fikir için.


1
Sorunuzun açık cevabı :-) Siz veya başka biri biliyorsa alternatif yaklaşımlar görmek isterim. Bunun için küresel bir Boole kullanımı benim için her zaman kötü kokulu olmuştur.
Evorlor

1
@Evorlor Herkes için açık değil :). Ben de alternatif çözümlerle ilgileniyorum, belki çok açık olmayan bir şey öğrenebiliriz!
MichaelHouse

2
@Evorlor alternatif olarak, delegeleri (işlev işaretçileri) kullanabilir ve hangi eylemin gerçekleştirileceğini değiştirebilirsiniz. örneğin onStart() { delegate = doSomething; } ... if(mousebuttonpressed) { delegate.Execute(); }ve void doSomething() { doStuff(); delegate = funcDoNothing; }sonuçta tüm seçenekler ayarladığınız / ayarlayamadığınız bir tür bayrakla sonuçlanır ... ve bu durumda temsilci başka bir şey değildir (ikiden fazla seçeneğiniz varsa, ne yapmalı? hariç).
wondra

2
@wondra Evet, bu bir yol. Ekle. Bu soru üzerine olası çözümlerin bir listesini de yapabiliriz.
MichaelHouse

1
@JamesSnell Çok iş parçacıklıysa daha olasıdır. Ama yine de iyi bir uygulama.
MichaelHouse

21

Bool bayrağının yeterli olmaması veya void Update()yöntemdeki kodun okunabilirliğini * artırmak istiyorsanız, temsilci (işlev işaretçileri) kullanmayı düşünebilirsiniz :

public class InputController 
{
  //declare delegate type:
  //<accessbility> delegate <return type> <name> ( <parameter1>, <paramteter2>, ...)
  public delegate void ClickAction();

  //declare a variable of that type
  public ClickAction ClickTheThing { get; set; }

  void onStart()
  {
    //assign it the method you wish to execute on first click
    ClickTheThing = doStuff;
  }

  void Update() {
    if(mousebuttonpressed) {
        //you simply call the delegate here, the currently assigned function will be executed
        ClickTheThing();
     }
  }

  private void doStuff()
  {
    //some logic here
    ClickTheThing = doNothing; //and set the delegate to other(empty) funtion
  }

  //or do something else
  private void doNothing(){}
}

Basit bir kez "yürütmek" için delegeler aşırı, bu yüzden yerine bool bayrağı kullanmanızı öneririm.
Ancak, daha karmaşık işlevselliklere ihtiyacınız varsa, delegeler muhtemelen daha iyi bir seçimdir. Örneğin, zincirleme daha farklı eylemler yürütmek istiyorsanız: birincisi ilk tıklamayla, diğeri ikincisiyle ve diğeri de üçüncü ile şunları yapabilirsiniz:

func1() 
{
  //do logic 1 here
  someDelegate = func2;
}

func2() 
{
  //do logic 2 here
  someDelegate = func3;
}
//etc...

kodunuzu onlarca farklı bayrakla boğmak yerine.
* kodun geri kalanının daha düşük bakım maliyeti


Beklediğim gibi sonuçlarla biraz profilleme yaptım:

----------------------------------------
|     Method     |  Unity   |    C++   |
| -------------------------------------|
| positive flag  |  21 ms   |   6 ms   |
| negative flag  |  5 ms    |   7 ms   |
| delegate       |  25 ms   |   14 ms  |
----------------------------------------

İlk test Unity 5.1.2 üzerinde gerçekleştirildi. System.Diagnostics.Stopwatch 32 bit inşa edilmiş projeyle (tasarımcıda değil!) . Visual Studio 2015'teki (v140) diğeri / Ox bayrağıyla 32 bit yayın modunda derlenmiştir. Her iki test de her uygulama için 10.000.000 yineleme ile 3.4GHz'de Intel i5-4670K CPU üzerinde gerçekleştirildi. kod:

//positive flag
if(flag){
    doClick();
    flag = false;
}
//negative flag
if(!flag){ }
else {
    doClick();
    flag = false;
}
//delegate
action();

Sonuç: Unity derleyicisi işlev çağrılarını optimize ederken iyi bir iş çıkarsa da, hem pozitif bayrak hem de delegeler için kabaca aynı sonuç veren (sırasıyla 21 ve 25 ms) şube yanlış tahmin veya işlev çağrısı hala oldukça pahalıdır (not: temsilci önbellek olarak kabul edilmelidir) bu testte).
İlginçtir ki, Unity derleyici 99 milyon ardışık yanlış tahmin olduğunda dalı optimize edecek kadar akıllı değildir , bu nedenle testin manuel olarak reddedilmesi, 5 ms'lik en iyi sonucu veren bir performans artışı sağlar. C ++ sürümü, olumsuzlama koşulu için herhangi bir performans artışı göstermez, ancak işlev çağrısının genel yükü önemli ölçüde daha düşüktür.
en önemlisi: fark oldukça irrelevat herhangi bir gerçek dünya senaryosu için


4
AFAIK bu performans açısından da daha iyidir. Operasyon önbelleğinde zaten varsayılarak, işlem yapmama işlevini çağırmak, bayrakları denetlemekten çok daha hızlıdır, özellikle bu denetimin diğer koşulların altında iç içe olduğu yerlerde .
Mühendis

2
Navin haklı olduğunu düşünüyorum, ben Boole bayrağı kontrol daha kötü bir performans var - JIT gerçekten akıllı ve tam anlamıyla hiçbir şey yerine sürece. Ancak sonuçları profillendirmedikçe kesin olarak bilemeyiz.
wondra

2
Hmm, bu cevap bana bir SW mühendisinin evrimini hatırlatıyor . Şaka bir yana, bu performans artışına kesinlikle ihtiyacınız varsa (ve eminseniz), bunun için gidin. Değilse, onu ÖPÜP tutmanızı ve başka bir cevapta önerildiği gibi bir boole bayrağı kullanmanızı öneririm . Ancak, burada temsil edilen alternatif için +1!
mucaho

1
Mümkünse koşullardan kaçının. Kendi yerel kodunuz, bir JIT derleyici veya bir tercüman olsun, makine düzeyinde neler olduğu hakkında konuşuyorum. L1 önbellek isabetinin bir kayıt isabeti ile hemen hemen aynı, muhtemelen biraz daha yavaş yani 1 veya 2 döngü, oysa daha az sıklıkta ama yine de ortalama olarak çok fazla olan şube yanlış tahminleri 10-20 döngü sırasına göre maliyetlidir. Jalf'ın cevabına bakınız: stackoverflow.com/questions/289405/… Ve bu: stackoverflow.com/questions/11227809/…
Mühendis

1
Ayrıca, Byte56'nın cevabındaki bu koşulların programınızın ömrü boyunca her bir güncelleme olarak adlandırılması gerektiğini ve konuların daha iyi iç içe yerleştirilmiş veya iç içe geçmiş koşullara sahip olması gerektiğini unutmayın. İşlev göstergeleri / Delegeler bu şekilde kullanılabilir.
Mühendis

3

Tamamlamak için

(Aslında böyle bir şey yazmak oldukça basit olduğu için bunu yapmanızı tavsiye etmiyorum if(!already_called) , ancak bunu yapmak "doğru" olacaktır.)

Şaşırtıcı olmayan bir şekilde, C ++ 11 standardı bir işlevi bir kez çağırmanın oldukça önemsiz problemini deyim haline getirdi ve onu süper açık hale getirdi:

#include <mutex>

void Update() {
if(mousebuttonpressed) {
    static std::once_flag f;
    std::call_once(f, ClickTheThing);
}

}

Kuşkusuz, standart çözüm biraz her zaman tam olarak bir çağrının gerçekleştiğini, asla farklı bir şeyin olmadığını garanti ettiğinden, iş parçacığı varlığında önemsiz olandan daha üstündür.

Ancak, normalde çok iş parçacıklı bir olay döngüsü çalıştırmazsınız ve aynı anda birkaç farenin düğmelerine basmazsınız, böylece iplik güvenliği sizin durumunuz için biraz gereksiz olur.

Pratik terimlerle, bu, yerel bir booleanın iş parçacığı güvenli başlatılmasına ek olarak (C ++ 11'in her durumda iş parçacığı için güvenli olmasını garanti eder), standart sürümün, çağırmadan veya çağırmadan önce bir atomik test_set işlemi yapması gerektiği anlamına gelir. işlev.

Yine de, açık olmayı seviyorsanız, çözüm var.

DÜZENLEME:
Ha. Şimdi burada oturuyordum, birkaç dakika bu koda bakarak, neredeyse tavsiye etme eğilimindeyim.
Aslında ilk başta göründüğü kadar aptalca değil, aslında niyetinizi çok net ve açık bir şekilde iletiyor ... tartışmasız daha iyi yapılandırılmış ve bir ifveya daha fazlasını içeren diğer çözümlerden daha okunabilir .


2

Bazı öneriler mimariye bağlı olarak değişecektir.

Bir işlev işaretçisi / varyant / DLL / so / etc ... olarak clickThisThing () öğesini oluşturun ve nesne / bileşen / modül / etc ... somutlaştırıldığında gerekli işlev için başlatıldığından emin olun.

Fare düğmesine her basıldığında işleyicide, clickThisThing () işlev işaretçisini çağırın ve hemen işaretçiyi başka bir NOP (işlem yok) işlev işaretçisiyle değiştirin.

Birinin daha sonra vidalaması için bayraklara ve ekstra mantığa gerek yok, sadece her seferinde arayın ve değiştirin ve bir NOP olduğu için NOP hiçbir şey yapmaz ve bir NOP ile değiştirilir.

Veya aramayı atlamak için bayrağı ve mantığı kullanabilirsiniz.

Veya çağrıdan sonra Update () işlevinin bağlantısını kesebilirsiniz, böylece dış dünya bunu unutur ve bir daha asla çağırmaz.

Veya clickThisThing () öğesinin kendisi, yalnızca yararlı çalışmalarla bir kez yanıt vermek için bu kavramlardan birini kullanır. Bu şekilde bir temel clickThisThingOnce () nesnesi / bileşen / modülünü kullanabilir ve bu davranışa ihtiyaç duyduğunuz her yerde başlatabilirsiniz ve güncelleyicilerinizin hiçbiri özel bir mantık gerektirmez.


2

İşlev işaretçileri veya delegeler kullanın.

İşlev işaretçisini tutan değişkenin, bir değişiklik yapılması gerekene kadar açıkça incelenmesi gerekmez. Ve artık söz konusu mantığın çalışması için ihtiyacınız olmadığında, boş / op olmayan bir işlev için bir ref ile değiştirin. Koşullu yapmakla karşılaştırın, programın tüm ömrü boyunca her kareyi kontrol eder - bu bayrak sadece başlangıçtan sonraki ilk birkaç karede gerekli olsa bile! - Kirli ve verimsiz.(Bununla birlikte, Boreal'ın tahminde bulunma noktası bu bağlamda kabul edilmektedir.)

Bunların genellikle oluşturduğu iç içe koşullu koşullar, her karede tek bir koşullu denetimi denetlemekten daha maliyetlidir, çünkü şube tahmini artık bu durumda sihrini yapamaz. Bu, 10 saniyelik döngü sırasında düzenli gecikmeler anlamına gelir - boru hattı tezgahları mükemmel . Yuvalama yukarıda veya bu boole bayrağının altında . Okuyuculara karmaşık oyun döngülerinin nasıl hızlı bir şekilde olabileceğini hatırlatmaya ihtiyacım yok.

Bu nedenle işlev işaretçileri vardır - bunları kullanın!


1
Aynı çizgileri düşünüyoruz. En etkili yol, Qt stili sinyal + yuva kurulumlarında çok yaygın olan işi yaptıktan sonra Güncelleme () nesnesini acımasızca çağırmaya devam eden kişiyle olan bağlantısını kesmek olacaktır. OP çalışma zamanı ortamını belirtmedi, bu nedenle belirli optimizasyonlar söz konusu olduğunda hamstung.
Patrick Hughes

1

Javascript veya lua gibi bazı kod dillerinde fonksiyon referansını test etmek kolayca yapılabilir. İçinde Lua ( love2d ):

function ClickTheThing()
      .......
end
....

local ctt = ClickTheThing  --I make a local reference to function

.....

function update(dt)
..
    if ctt then
      ctt()
      ctt=nil
    end
..
end

0

Bir de Von Neumann Mimarisi programınızın talimatları ve verilerin depolandığı yerlerde bilgisayar, hafızadır. Kodun yalnızca bir kez çalışmasını istiyorsanız, yöntem tamamlandıktan sonra NOP'larla yöntemin üzerine yazma yönteminde kodunuz olabilir. Bu şekilde yöntem tekrar çalıştırılsaydı hiçbir şey olmazdı.


6
Bu, program belleğini koruyan veya ROM'dan çalışan birkaç mimariyi kırar. Ayrıca, yüklü olana bağlı olarak rastgele antivirüs uyarıları da başlatır.
Patrick Hughes

0

Python'da, projedeki diğer insanlara pislik olmayı planlıyorsanız:

code = compile('main code'+'del code', '<string>', 'exec')
exec code

bu komut dosyası kodu gösterir:

from time import time as t

code = compile('del code', '<string>', 'exec')

print 'see code object:'
print code

while True:
    try:
        user = compile(raw_input('play with it (variable name is code) (type done to be done:\t'), '<user_input>', 'exec')
        exec user
    except BaseException as e:
        print e
        break

exec code

print 'no more code object:'
try:
    print code
except BaseException as e:
    print e

while True:
    try:
        user = compile(raw_input('try to play with it again (type done to be done:\t'), '<user_input>', 'exec')
        exec user
    except BaseException as e:
        print e
        break

0

İşte başka bir katkı, biraz daha genel / tekrar kullanılabilir, biraz daha okunabilir ve bakım yapılabilir, ancak muhtemelen diğer çözümlerden daha az verimli.

Bir zamanlar yürütülen mantığımızı bir sınıfta kapsülleyelim:

class Once
{
    private Action body;
    public bool HasBeenCalled { get; private set; }
    public Once(Action body)
    {
        this.body = body;
    }
    public void Call()
    {
        if (!HasBeenCalled)
        {
            body();
        }
        HasBeenCalled = true;
    }
    public void Reset() { HasBeenCalled = false; }
}

Verilen örnek gibi kullanmak aşağıdaki gibidir:

Once clickTheThingOnce = new Once(ClickTheThing);

void Update() {
    if(mousebuttonpressed) {
        clickTheThingOnce.Call(); // ClickTheThing is only called once
                                  // or until .Reset() is called
    }
}

0

Statik bir değişken kullanmayı düşünebilirsiniz.

void doOnce()
{
   static bool executed = false;

   if(executed == false)
   {
      ...Your code here
      executed = true;
   }
}

Bunu ClickTheThing()işlevin kendisinde yapabilir veya başka bir işlev oluşturabilir ve ClickTheThing()if gövdesini çağırabilirsiniz .

Diğer çözümlerde önerildiği gibi bir bayrağı kullanma fikrine benzer, ancak bayrak diğer kodlardan korunur ve işleve yerel olması nedeniyle başka bir yerde değiştirilemez.


1
... ancak bu, kodun 'nesne örneği başına' bir kez çalıştırılmasını engeller. (Kullanıcının ihtiyacı olan
buysa

0

Aslında Xbox Controller Input ile bu ikilemle karşılaştım. TAM olarak aynı olmasa da oldukça benzer. Örneğimdeki kodu ihtiyaçlarınıza göre değiştirebilirsiniz.

Düzenleme: Durumunuz bunu kullanır ->

https://docs.microsoft.com/en-us/windows/desktop/api/winuser/ns-winuser-tagrawmouse

Ham giriş sınıfının nasıl oluşturulacağını ->

https://docs.microsoft.com/en-us/windows/desktop/inputdev/raw-input

Ama .. şimdi süper müthiş algoritmaya ... gerçekten değil, ama hey .. çok güzel :)

* Yani ... her düğmenin durumlarını kaydedebiliriz ve Basılan, Serbest Bırakılan ve Tutulan !!! Bekleme Süresini de kontrol edebiliriz, ancak bu tek bir if ifadesi gerektirir ve herhangi bir sayıda düğmeyi kontrol edebilir, ancak bu bilgi için aşağıda bazı kurallar vardır.

Açıkçası, eğer bir şeye basıldığını, serbest bırakıldığını vb. Kontrol etmek istiyorsak, "If (This) {}" yaparsınız, ancak bu, basın durumunu nasıl alıp bir sonraki kareyi nasıl kapatacağımızı gösteriyor. bir dahaki sefere kontrol ettiğinizde aslında yanlış olacaktır.

Tam Kodu Burada: https://github.com/JeremyDX/DX_B/blob/master/DX_B/XGameInput.cpp

Nasıl çalışır..

Bu nedenle, bir düğmeye basıldığını veya basmadığını tasvir ettiğinizde aldığınız değerlerden emin değilim, ancak temel olarak XInput'a yüklediğimde, 0 ile 65535 arasında 16 bitlik bir değer elde ediyorum, bu "Basılı" için 15 bit olası durumlara sahip.

Sorun bunu her kontrol ettiğimde sadece bana bilginin mevcut durumunu verecekti. Mevcut durumu Preslenmiş, Serbest Bırakılan ve Tutulan Değerlere dönüştürmek için bir yola ihtiyacım vardı.

Yani yaptığım şey şu.

Önce bir "CURRENT" değişkeni yaratırız. Bu verileri her kontrol ettiğimizde, "AKIM" i "ÖNCEKİ" bir değişkene ayarlıyoruz ve ardından yeni verileri burada görüldüğü gibi "Güncel" olarak saklıyoruz ->

uint64_t LAST = CURRENT;
CURRENT = gamepad.wButtons;

Bu bilgi ile burada heyecan verici olsun !!

Şimdi bir Düğme AŞAĞIYI YAPILIR MIYORUZ!

BUTTONS_HOLD = LAST & CURRENT;

Bunun temelde iki değeri karşılaştırmasıdır ve her ikisinde de gösterilen herhangi bir düğmeye basıldığında 1 kalır ve diğer her şey 0'a ayarlanır.

Yani (1 | 2 | 4) & (2 | 4 | 8) (2 | 4) verimini verecektir.

Şimdi hangi düğmelerin "HELD" (aşağı). Gerisini biz alabiliriz.

Basılı basittir ... "AKIM" durumumuzu alır ve basılı düğmeleri kaldırırız.

BUTTONS_PRESSED = CURRENT ^ BUTTONS_HOLD;

Sadece LAST durumumuzla karşılaştırdığımızda aynı çıktı.

BUTTONS_RELEASED = LAST ^ BUTTONS_HOLD;

Preslenmiş duruma bakalım. Eğer diyelim Şu anda 2 | 4 | 8 basıldı. Biz buldum 2 | 4 nerede tutulur. Tutulan Bitleri kaldırdığımızda sadece 8 kaldı. Bu, bu döngü için yeni basılan bit.

Aynısı Released için de uygulanabilir. Bu senaryoda "SON" 1 | 2 | 4. Yani 2'yi kaldırdığımızda | 4 bit. 1 ile kaldık. Düğme 1 son kareden beri serbest bırakıldı.

Yukarıdaki senaryo, muhtemelen bit karşılaştırması için sunabileceğiniz en ideal durumdur ve if ifadeleri olmadan 3 seviye veri veya döngüler için sadece 3 hızlı bit hesaplaması sağlar.

Bekletme verilerini de belgelemek istedim, bu yüzden durumum mükemmel olmasa da ... temelde kontrol etmek istediğimiz bekletmeleri ayarladık.

Bu nedenle, Basın / Bırak / Beklet verilerimizi her ayarladığımızda, bekletme verilerinin halen geçerli bekletme bit kontrolüne eşit olup olmadığını kontrol ederiz. Eğer yapmazsak, zamanı şimdiki zamana sıfırlıyoruz. Benim durumumda kare dizinleri için ayarlıyorum, bu yüzden kaç kare tutulduğunu biliyorum.

Bu yaklaşımın dezavantajı, bireysel tutma süreleri alamıyorum, ancak aynı anda birden fazla biti kontrol edebilirsiniz. Yani tutma biti 1 | 16 1 veya 16 tutulmazsa bu başarısız olur. Bu yüzden TÜM bu düğmelerin işaretlemeye devam etmesini gerektirir.

Eğer koda bakarsanız, tüm düzgün fonksiyon çağrılarını göreceksiniz.

Böylece, örneğiniz basitçe bir tuşa basmanın gerçekleşip gerçekleşmediğini ve bir tuşa basmanın bu algoritma ile yalnızca bir kez gerçekleşip gerçekleşmeyeceğini kontrol etmek için azaltılacaktır. Bir sonraki basışınızda, tekrar basmadan önce bırakmanız gerekenden daha sonra basamayacağınız için mevcut olmayacaktır.


Yani temel olarak diğer cevaplarda belirtildiği gibi bool yerine bir bit alanı mı kullanıyorsunuz?
Vaillancourt

Evet oldukça çok lol ..
Jeremy Trifilo

Aşağıdaki msdn yapısı ile "usButtonFlags" tüm düğme durumlarının tek bir değerini içeriyor olsa da gereksinimleri için kullanılabilir. docs.microsoft.com/tr-tr/windows/desktop/api/winuser/…
Jeremy Trifilo

Denetleyici düğmeleriyle ilgili tüm bunları söyleme gerekliliğinin nereden geldiğinden emin değilim. Cevabın temel içeriği, dikkat dağıtan birçok metnin altında gizlidir.
Vaillancourt

Evet, sadece ilgili bir kişisel senaryo ve bunu gerçekleştirmek için ne yapıyordum. Daha sonra olsa temizleyeceğim ve belki Klavye ve Fare girişi ekleyeceğim ve buna bir yaklaşım vereceğim.
Jeremy Trifilo

-3

Aptal ucuz çıkış yolu ama bir for döngüsü kullanabilirsiniz

for (int i = 0; i < 1; i++)
{
    //code here
}

Döngüdeki kod her zaman döngü çalıştırıldığında yürütülür. Orada hiçbir şey kodun bir kez yürütülmesini engellemiyor. Döngü veya döngü yok tam olarak aynı sonucu verir.
Vaillancourt
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.