Wie stelle ich sicher, dass ein Code nur einmal ausgeführt wird?


18

Ich habe einen Code, den ich nur einmal ausführen möchte, obwohl die Umstände, die diesen Code auslösen, mehrmals auftreten können.

Wenn der Benutzer zum Beispiel mit der Maus klickt, möchte ich auf das Ding klicken:

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

Mit diesem Code wird jedoch jedes Mal, wenn ich mit der Maus klicke, das Ding angeklickt. Wie kann ich das nur einmal machen?


7
Eigentlich finde ich das irgendwie lustig, da ich nicht denke, dass es sich um "spielspezifische Programmierprobleme" handelt. Aber ich habe diese Regel nie wirklich gemocht.
ClassicThunder

3
@ClassicThunder Ja, es ist sicherlich mehr auf der allgemeinen Programmierseite der Dinge. Wenn Sie möchten, stimmen Sie ab, um den Vorgang abzuschließen. Ich habe die Frage mehr gepostet, damit wir Menschen darauf hinweisen können, wenn sie ähnliche Fragen stellen. Es kann zu diesem Zweck offen oder geschlossen sein.
MichaelHouse

Antworten:


41

Verwenden Sie eine Boolesche Flagge.

In dem gezeigten Beispiel würden Sie den Code folgendermaßen ändern:

//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();
    }
}

Außerdem, wenn Sie die Aktion wiederholen möchten, aber die Häufigkeit der Aktion begrenzen möchten (dh die Mindestzeit zwischen den einzelnen Aktionen). Sie würden einen ähnlichen Ansatz verwenden, aber das Flag nach einer bestimmten Zeit zurücksetzen. Siehe meine Antwort hier für weitere Ideen dazu.


1
Die offensichtliche Antwort auf Ihre Frage :-) Ich würde mich über alternative Ansätze freuen, wenn Sie oder jemand anderes davon wissen. Diese Verwendung eines globalen Booleschen Werts hat mir immer gestunken.
Evorlor

1
@Evorlor Nicht offensichtlich für alle :). Ich bin auch an alternativen Lösungen interessiert, vielleicht können wir etwas lernen, das nicht so offensichtlich ist!
MichaelHouse

2
@Evorlor Alternativ können Sie Delegates (Funktionszeiger) verwenden und die durchgeführten Aktionen ändern. ZB onStart() { delegate = doSomething; } ... if(mousebuttonpressed) { delegate.Execute(); }und void doSomething() { doStuff(); delegate = funcDoNothing; }aber am Ende haben alle Optionen eine Markierung, die Sie gesetzt / deaktiviert haben ... und delegieren ist in diesem Fall nichts anderes (außer wenn Sie mehr als zwei Optionen haben, was tun?).
Wondra

2
@wondra Ja, das ist ein Weg. Füge es hinzu. Genauso gut können wir eine Liste möglicher Lösungen für diese Frage erstellen.
MichaelHouse

1
@ JamesSnell Wahrscheinlicher, wenn es Multithreading war. Aber immer noch eine gute Übung.
MichaelHouse

21

Sollte das bool-Flag nicht ausreichen oder Sie möchten die Lesbarkeit * des Codes in der void Update()Methode verbessern , können Sie die Verwendung von Delegaten (Funktionszeigern) in Betracht ziehen :

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(){}
}

Für einfache "Execute Once" -Delegierte ist der Overkill, daher würde ich vorschlagen, stattdessen das Bool-Flag zu verwenden.
Wenn Sie jedoch komplexere Funktionen benötigen, sind die Delegierten wahrscheinlich die bessere Wahl. Wenn Sie beispielsweise mehrere verschiedene Aktionen hintereinander ausführen möchten: eine beim ersten Klicken, eine beim zweiten und eine beim dritten Klicken, können Sie einfach Folgendes tun:

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

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

anstatt Ihren Code mit Dutzenden verschiedener Flags zu quälen.
* zu Lasten einer geringeren Wartbarkeit des restlichen Codes


Ich habe ein paar Profile mit Ergebnissen erstellt, so wie ich es erwartet hatte:

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

Der erste Test wurde unter Unity 5.1.2 ausgeführt, gemessen mit einem System.Diagnostics.Stopwatch32-Bit-Projekt (nicht in Designer!). Die andere Version von Visual Studio 2015 (v140) wurde im 32-Bit-Veröffentlichungsmodus mit dem Flag / Ox kompiliert. Beide Tests wurden auf einer Intel i5-4670K-CPU mit 3,4 GHz und 10.000.000 Iterationen für jede Implementierung ausgeführt. Code:

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

Fazit: Während der Unity-Compiler bei der Optimierung von Funktionsaufrufen gute Arbeit leistet und sowohl für positive Flags als auch für Delegates ungefähr dasselbe Ergebnis liefert (21 bzw. 25 ms), ist die falsche Vorhersage des Zweigs oder der Funktionsaufruf immer noch recht teuer (Anmerkung: Als Delegate sollte der Cache angenommen werden in diesem Test).
Interessanterweise ist der Unity-Compiler nicht schlau genug, um den Zweig zu optimieren, wenn 99 Millionen aufeinanderfolgende Fehlvorhersagen vorliegen. Das manuelle Negieren des Tests führt also zu einer Leistungssteigerung, die das beste Ergebnis von 5 ms ergibt. Die C ++ - Version zeigt keine Leistungssteigerung bei negierender Bedingung, der Gesamtaufwand für Funktionsaufrufe ist jedoch erheblich geringer.
am wichtigsten: der unterschied ist so ziemlich irrelevat für jedes reale Szenario


4
AFAIK das ist auch unter Performance-Gesichtspunkten besser. Das Aufrufen einer No-Op-Funktion, vorausgesetzt, die Funktion befindet sich bereits im Anweisungscache, ist viel schneller als das Überprüfen von Flags, insbesondere wenn diese Prüfung unter anderen Bedingungen verschachtelt ist.
Ingenieur

2
Ich denke, Navin hat recht, ich habe wahrscheinlich eine schlechtere Leistung als das Überprüfen der Booleschen Flagge - es sei denn, Ihre JIT ist wirklich intelligent und ersetzt die gesamte Funktion durch praktisch nichts. Aber wenn wir die Ergebnisse nicht profilieren, können wir es nicht sicher wissen.
Wondra

2
Hmm, diese Antwort erinnert mich an die Entwicklung eines SW-Ingenieurs . Spaß beiseite, wenn Sie diesen Leistungsschub unbedingt brauchen (und sicher sind), machen Sie es. Wenn nicht, würde ich vorschlagen, KISS beizubehalten und eine boolesche Flagge zu verwenden, wie in einer anderen Antwort vorgeschlagen . +1 für die hier dargestellte Alternative!
Mucaho

1
Vermeiden Sie nach Möglichkeit Bedingungen. Ich spreche darüber, was auf Maschinenebene passiert, ob es sich um Ihren eigenen nativen Code, einen JIT-Compiler oder einen Interpreter handelt. Der L1-Cachetreffer entspricht in etwa einem Registertreffer, möglicherweise etwas langsamer, dh 1 oder 2 Zyklen, während die Verzweigungsfehlvorhersage, die seltener, aber immer noch zu häufig auftritt, Kosten in der Größenordnung von 10 bis 20 Zyklen verursacht. Siehe Jalfs Antwort: stackoverflow.com/questions/289405/… Und dies: stackoverflow.com/questions/11227809/…
Ingenieur

1
Denken Sie außerdem daran, dass diese Bedingungen in der Antwort von Byte56 jedes einzelne Update für die Lebensdauer Ihres Programms aufgerufen werden müssen und möglicherweise geschachtelt sind oder geschachtelte Bedingungen enthalten, was die Sache noch viel schlimmer macht. Funktionszeiger / Delegierte sind der richtige Weg.
Ingenieur

3

Zur Vollständigkeit

(Es wird nicht empfohlen, dies zu tun, da es eigentlich ziemlich einfach ist, einfach so etwas zu schreiben if(!already_called), aber es wäre "richtig", dies zu tun.)

Es ist nicht überraschend, dass der C ++ 11-Standard das eher triviale Problem des einmaligen Aufrufs einer Funktion identifiziert und sehr deutlich gemacht hat:

#include <mutex>

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

}

Zugegebenermaßen ist die Standardlösung der trivialen Lösung bei vorhandenen Threads etwas überlegen, da sie weiterhin garantiert, dass immer genau ein Aufruf erfolgt, niemals etwas anderes.

Normalerweise führen Sie jedoch keine Multithread-Ereignisschleife aus und drücken gleichzeitig die Tasten mehrerer Mäuse, sodass die Threadsicherheit für Ihren Fall etwas überflüssig ist.

In der Praxis bedeutet dies, dass die Standardversion neben der thread-sicheren Initialisierung eines lokalen Booleschen Werts (der in C ++ 11 ohnehin als thread-sicher gilt) auch eine atomare test_set-Operation ausführen muss, bevor der Aufruf oder Nichtaufruf erfolgt die Funktion.

Wenn Sie dennoch explizit sein möchten, gibt es die Lösung.

EDIT:
Huh. Jetzt, da ich hier sitze und einige Minuten auf diesen Code gestarrt habe, bin ich fast geneigt, ihn zu empfehlen.
Tatsächlich ist es nicht annähernd so albern, wie es auf den ersten Blick scheinen mag, tatsächlich kommuniziert es sehr klar und eindeutig Ihre Absicht ... wohl besser strukturiert und lesbarer als jede andere Lösung, die eine ifoder eine solche beinhaltet.


2

Einige Vorschläge variieren je nach Architektur.

Erstellen Sie clickThisThing () als Funktionszeiger / variant / DLL / so / etc ... und stellen Sie sicher, dass es für die gewünschte Funktion initialisiert wird, wenn das Objekt / die Komponente / das Modul / etc ... instanziiert wird.

Rufen Sie im Handler bei jedem Drücken der Maustaste den Funktionszeiger clickThisThing () auf und ersetzen Sie den Zeiger sofort durch einen anderen Funktionszeiger NOP (keine Operation).

Keine Notwendigkeit für Flags und zusätzliche Logik, damit jemand später Fehler macht. Rufen Sie es einfach auf und ersetzen Sie es jedes Mal. Da es sich um eine NOP handelt, unternimmt die NOP nichts und wird durch eine NOP ersetzt.

Oder Sie können das Flag und die Logik verwenden, um den Anruf zu überspringen.

Oder Sie können die Update () - Funktion nach dem Aufruf trennen, damit die Außenwelt das vergisst und es nie wieder aufruft.

Oder Sie lassen clickThisThing () selbst eines dieser Konzepte verwenden, um nur einmal mit nützlicher Arbeit zu antworten. Auf diese Weise können Sie ein ClickThisThingOnce () - Basisobjekt / eine Basiskomponente / ein Basismodul verwenden und es an jedem Ort instanziieren, an dem Sie dieses Verhalten benötigen, und keiner Ihrer Updater benötigt überall eine spezielle Logik.


2

Verwenden Sie Funktionszeiger oder Delegaten.

Die Variable, die den Funktionszeiger enthält, muss nicht explizit untersucht werden, bis eine Änderung vorgenommen werden muss. Und wenn Sie diese Logik nicht mehr benötigen, ersetzen Sie sie durch einen Verweis auf eine leere / no-op-Funktion. Compare with doing conditionals überprüft jeden Frame während der gesamten Lebensdauer Ihres Programms - auch wenn dieses Flag nur in den ersten Frames nach dem Start benötigt wurde! - Unrein und ineffizient. (Boreals Standpunkt zur Vorhersage wird jedoch in dieser Hinsicht anerkannt.)

Verschachtelte Bedingungen, die diese häufig darstellen, sind sogar teurer als die Prüfung einer einzelnen Bedingung pro Frame, da die Verzweigungsvorhersage in diesem Fall nicht mehr ihre Wirkung entfalten kann. Das bedeutet regelmäßige Verzögerungen in der Größenordnung von 10 Zyklen - Pipeline-Stände par excellence . Die Verschachtelung kann über oder unter dieser Booleschen Flagge erfolgen. Ich muss die Leser kaum daran erinnern, wie schnell komplexe Spielschleifen entstehen können.

Aus diesem Grund gibt es Funktionszeiger - verwenden Sie diese!


1
Wir denken in die gleiche Richtung. Der effizienteste Weg wäre, die Update () - Funktion von jedem zu trennen, der sie unermüdlich aufruft, sobald die Arbeit erledigt ist. Das OP hat die Laufzeitumgebung nicht spezifiziert, daher sind wir in Bezug auf bestimmte Optimierungen überfordert.
Patrick Hughes

1

In einigen Skriptsprachen wie Javascript oder Lua kann das Testen der Funktionsreferenz einfach durchgeführt werden. In 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

In einem Von Neumann Architecture- Computer befinden sich im Speicher die Anweisungen und Daten Ihres Programms. Wenn Code nur einmal ausgeführt werden soll, kann Code in der Methode die Methode nach Abschluss der Methode mit NOPs überschreiben. Auf diese Weise würde nichts passieren, wenn die Methode erneut ausgeführt würde.


6
Dadurch werden einige Architekturen beschädigt, die den Programmspeicher schützen oder vom ROM ausgeführt werden. Je nachdem, was installiert ist, werden möglicherweise auch zufällige Antiviren-Warnungen ausgegeben.
Patrick Hughes

0

In Python, wenn Sie vorhaben, anderen Leuten am Projekt einen Ruck zuzufügen:

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

Dieses Skript demonstriert den Code:

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

Hier ist ein weiterer Beitrag: Er ist etwas allgemeiner / wiederverwendbarer, etwas lesbarer und wartbarer, aber wahrscheinlich weniger effizient als andere Lösungen.

Lassen Sie uns unsere einmal ausgeführte Logik in einer Klasse zusammenfassen:

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; }
}

Die Verwendung wie im Beispiel sieht folgendermaßen aus:

Once clickTheThingOnce = new Once(ClickTheThing);

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

0

Sie können statische Variablen verwenden.

void doOnce()
{
   static bool executed = false;

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

Sie können dies in der ClickTheThing()Funktion selbst tun oder eine andere Funktion erstellen und ClickTheThing()den if-Body aufrufen .

Es ähnelt der Idee, ein Flag zu verwenden, wie in anderen Lösungen vorgeschlagen, aber das Flag ist vor anderem Code geschützt und kann nicht an anderer Stelle geändert werden, da es für die Funktion lokal ist.


1
... dies verhindert jedoch, dass der Code einmal pro Objektinstanz ausgeführt wird. (Wenn es das ist, was der Benutzer braucht ..;))
Vaillancourt

0

Ich bin tatsächlich mit Xbox Controller Input auf dieses Dilemma gestoßen. Obwohl es nicht genau das gleiche ist, ist es ziemlich ähnlich. Sie können den Code in meinem Beispiel an Ihre Bedürfnisse anpassen.

Bearbeiten: Ihre Situation würde dies verwenden ->

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

Und Sie können lernen, wie Sie eine rohe Eingabeklasse über -> erstellen

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

Aber ... jetzt zu dem super genialen Algorithmus ... nicht wirklich, aber hey ... es ist ziemlich cool :)

* Also ... wir können die Zustände jeder Taste speichern, die gedrückt, losgelassen und gedrückt gehalten werden !!! Wir können auch die Haltezeit überprüfen. Dies erfordert jedoch eine einzelne if-Anweisung und kann eine beliebige Anzahl von Schaltflächen überprüfen. Für diese Informationen gelten jedoch einige Regeln (siehe unten).

Wenn wir überprüfen möchten, ob etwas gedrückt, freigegeben usw. ist, würden Sie natürlich "If (This) {}" ausführen. Dies zeigt jedoch, wie wir den Druckstatus abrufen und ihn dann beim nächsten Frame ausschalten können, damit Ihr " "ismousepressed" wird beim nächsten Überprüfen tatsächlich falsch sein.

Vollständiger Code hier: https://github.com/JeremyDX/DX_B/blob/master/DX_B/XGameInput.cpp

Wie es funktioniert..

Ich bin mir also nicht sicher, welche Werte Sie erhalten, wenn Sie angeben, ob eine Taste gedrückt wurde oder nicht, aber wenn ich in XInput lade, erhalte ich einen 16-Bit-Wert zwischen 0 und 65535. Dies hat 15-Bit-Zustände für "Gedrückt".

Das Problem war jedes Mal, wenn ich dies überprüfte. Es gab mir einfach den aktuellen Stand der Informationen. Ich brauchte eine Möglichkeit, den aktuellen Status in "Gedrückt", "Freigegeben" und "Werte halten" umzuwandeln.

Also, was ich getan habe, ist das Folgende.

Zuerst erstellen wir eine "CURRENT" -Variable. Jedes Mal, wenn wir diese Daten überprüfen, setzen wir den "CURRENT" auf eine "PREVIOUS" -Variable und speichern die neuen Daten unter "Current", wie hier zu sehen ->

uint64_t LAST = CURRENT;
CURRENT = gamepad.wButtons;

Mit diesen Informationen wird es hier spannend !!

Wir können jetzt herausfinden, ob ein Button heruntergedrückt wird!

BUTTONS_HOLD = LAST & CURRENT;

Im Grunde werden die beiden Werte verglichen, und alle in beiden angezeigten Tastendrücke bleiben auf 1 und alle anderen auf 0 gesetzt.

Dh (1 | 2 | 4) & (2 | 4 | 8) ergibt (2 | 4).

Nun, da wir welche Tasten haben, sind "HELD" gedrückt. Wir können den Rest besorgen.

Gedrückt ist einfach. Wir nehmen unseren "CURRENT" -Zustand an und entfernen alle gedrückten Knöpfe.

BUTTONS_PRESSED = CURRENT ^ BUTTONS_HOLD;

Released ist dasselbe, nur dass wir es stattdessen mit unserem LAST-Status vergleichen.

BUTTONS_RELEASED = LAST ^ BUTTONS_HOLD;

Also schaut euch die Pressed-Situation an. Sagen wir mal Derzeit hatten wir 2 | 4 | 8 gedrückt. Wir fanden, dass 2 | 4 wo gehalten. Wenn wir die gehaltenen Bits entfernen, bleiben uns nur noch 8 übrig. Dies ist das neu gedrückte Bit für diesen Zyklus.

Gleiches gilt für Freigegeben. In diesem Szenario wurde "LAST" auf 1 | gesetzt 2 | 4. Wenn wir also die 2 | entfernen 4 Bits. Wir bleiben mit 1 zurück. Also wurde die Taste 1 seit dem letzten Frame losgelassen.

Dieses obige Szenario ist wahrscheinlich die idealste Situation, die Sie für den Bitvergleich anbieten können, und bietet 3 Datenebenen ohne if-Anweisungen oder für Schleifen nur 3 schnelle Bitberechnungen.

Ich wollte auch Haltedaten dokumentieren, obwohl meine Situation nicht perfekt ist. Wir haben im Grunde genommen die Haltebits festgelegt, auf die wir prüfen möchten.

Jedes Mal, wenn wir unsere Press / Release / Hold-Daten einstellen, prüfen wir, ob die Hold-Daten immer noch der aktuellen Hold-Bit-Prüfung entsprechen. Wenn dies nicht der Fall ist, wird die Zeit auf die aktuelle Zeit zurückgesetzt. In meinem Fall setze ich es auf Frame-Indizes, damit ich weiß, für wie viele Frames es gedrückt wurde.

Der Nachteil dieses Ansatzes ist, dass ich keine einzelnen Haltezeiten erhalte, aber Sie können mehrere Bits gleichzeitig prüfen. Dh wenn ich das Haltebit auf 1 | setze 16, wenn entweder 1 oder 16 nicht gehalten werden, würde dies fehlschlagen. Daher müssen ALLE diese Schaltflächen gedrückt gehalten werden, um das Ticking fortzusetzen.

Wenn Sie dann in den Code schauen, sehen Sie alle ordentlichen Funktionsaufrufe.

Ihr Beispiel würde sich also darauf beschränken, einfach zu überprüfen, ob ein Tastendruck stattgefunden hat und ein Tastendruck mit diesem Algorithmus nur einmal auftreten kann. Bei der nächsten Überprüfung ist dies nicht der Fall, da Sie nicht mehr als einmal drücken können, bevor Sie erneut drücken können.


Verwenden Sie also im Grunde ein Bitfeld anstelle eines Bools, wie in den anderen Antworten angegeben?
Vaillancourt

Ja ziemlich lol ..
Jeremy Trifilo

Es kann für seine Anforderungen verwendet werden, obwohl mit der folgenden msdn-Struktur "usButtonFlags" einen einzelnen Wert aller Schaltflächenzustände enthält. docs.microsoft.com/en-us/windows/desktop/api/winuser/…
Jeremy Trifilo

Ich bin mir nicht sicher, woher die Notwendigkeit kommt, all dies über Controller-Tasten zu sagen. Der Kerninhalt der Antwort ist unter vielen ablenkenden Texten verborgen.
Vaillancourt

Ja, ich habe nur ein persönliches Szenario gegeben und was ich getan habe, um es zu erreichen. Ich werde es aber später aufräumen, und vielleicht werde ich Tastatur- und Mauseingaben hinzufügen und einen Ansatz dafür geben.
Jeremy Trifilo

-3

Blöder billiger Ausweg, aber man könnte eine for-Schleife benutzen

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

Der Code in der Schleife wird immer ausgeführt, wenn die Schleife ausgeführt wird. Nichts hindert den Code daran, einmal ausgeführt zu werden. Die Schleife oder keine Schleife ergibt genau das gleiche Ergebnis.
Vaillancourt
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.