Entwicklung / ROBLOX
Roblox-Ereignisverbindungen: Beim Öffnen eines Menüs keine zusätzlichen Listener anlegen
Verfolge eine eigene Verbindung von der Erstellung über erneutes Öffnen bis zum Trennen und Neuaufbau. Ein ursprünglicher Studio-Test vergleicht drei aktive Listener mit einer verwalteten Verbindung und prüft Once getrennt.
Wähle einen Fehlermechanismus aus #
Stell dir ein vorübergehendes Hilfefenster vor, das auf ein Ereignis reagiert. Sein Controller verbindet bei jedem Öffnen eine neue Funktion, lässt aber die bisherige Verbindung bestehen. Nach mehreren Öffnungen kann ein Ereignis mehrere Funktionen ausführen. Das ist ein möglicher Fehlermechanismus und keine Diagnose jeder vorhandenen Schaltfläche. Ermittle zunächst, welche Funktion die Verbindung erzeugt und wer sie beenden muss.
Die Übung erstellt kein echtes Menü und klickt keine Schaltfläche. openPanel und closePanel bezeichnen den Lebenszyklus eines gedachten Controllers; ein eigener BindableEvent liefert das Signal. Damit prüfen wir die Verantwortung für Verbindungen ohne Gestaltung einer Oberfläche und ohne Änderungen veröffentlichter Spiele. Die Prüfung eines tatsächlichen Fensters bleibt ein eigener Integrationsschritt, auch wenn diese Assertions bereits erfolgreich sind.
Trenne Ereignis und Verbindung #
Die offizielle Dokumentation beschreibt Connect als Möglichkeit, eine Funktion mit einem Ereignis zu verbinden. Das Ergebnis ist ein RBXScriptConnection-Objekt. Mit einer gespeicherten Referenz kann der Eigentümer genau diese Verbindung über Disconnect trennen. Signal, Callback und zurückgegebenes Verbindungsobjekt sind verschiedene Dinge. Eine Variable mit der Funktion ist nicht automatisch eine Referenz auf deren Verbindung.
Im Panel-Beispiel besitzt der Controller currentConnection. Er kennt die Erstellungsstelle und verwendet dieselbe gespeicherte Referenz beim Schließen. Verteile die Erstellung nicht ohne klaren Eigentümer auf unverbundene Funktionen. Für unsere Vereinbarung genügt ein Feld: Es enthält die aktuelle Verbindung oder wird nach der Bereinigung nil. So lässt sich die Verantwortung vom Anfang bis zum Ende nachvollziehen.
Stelle drei unerwünschte Listener nach #
Der erste Teil des eigenen Tests erstellt drei Verbindungen zu einem BindableEvent. Jeder Callback erhöht den gemeinsamen Zähler hits. Anschließend wird das Ereignis einmal ausgelöst. Nach dem Warten auf die Verarbeitung muss hits drei sein. Das bestätigt drei Callback-Aufrufe für drei aktive Verbindungen in diesem isolierten Fall. Es simuliert keine kaputte Schaltfläche eines bestimmten vorhandenen Spiels.
Bewahre jedes zurückgegebene Verbindungsobjekt in der Tabelle duplicates auf. Danach können alle Listener des Vergleichs getrennt werden. Dieser Ausgangsfall macht den Unterschied zur verwalteten Lösung wiederholbar. Nenne die Zahl der Verbindungen nicht die Zahl der Spielerklicks. Es gibt einen Ereignisaufruf und drei separat verbundene Funktionen; diese Zahlen beantworten unterschiedliche Fragen über den Ablauf.
-- Original isolated engine experiment, not an existing-game script.
local signalOwner = Instance.new("BindableEvent")
local hits = 0
local duplicates = {}
for i = 1, 3 do
duplicates[i] = signalOwner.Event:Connect(function()
hits += 1
end)
end
signalOwner:Fire()
task.wait()
assert(hits == 3, "Three live subscriptions must make three callbacks")
for _, connection in duplicates do
connection:Disconnect()
assert(connection.Connected == false)
end
signalOwner:Fire()
task.wait()
assert(hits == 3, "Disconnected listeners must not receive a new fire")
local currentConnection
local function closePanel()
if currentConnection then
currentConnection:Disconnect()
currentConnection = nil
end
end
local function openPanel()
closePanel()
currentConnection = signalOwner.Event:Connect(function()
hits += 1
end)
end
openPanel()
openPanel()
openPanel()
signalOwner:Fire()
task.wait()
assert(hits == 4, "Reopening must leave only one listener")
closePanel()
closePanel()
signalOwner:Fire()
task.wait()
assert(hits == 4, "Repeated cleanup must be safe and stop new callbacks")
openPanel()
signalOwner:Fire()
task.wait()
assert(hits == 5, "A fresh panel must work after cleanup")
closePanel()
local onceHits = 0
local onceConnection = signalOwner.Event:Once(function()
onceHits += 1
end)
signalOwner:Fire()
signalOwner:Fire()
task.wait()
assert(onceHits == 1, "Once must handle only the first invocation")
assert(onceConnection.Connected == false)
signalOwner:Destroy()
print("GUIDEBOOK_CONNECTIONS_ENGINE_PASS hits=5 once=1")Prüfe das ausdrückliche Trennen #
Nach dem ersten Aufruf führt der Test Disconnect für alle drei gespeicherten Objekte aus. Für jede Verbindung wird Connected==false geprüft. Ein weiterer Fire mit anschließender Wartephase darf hits nicht verändern: Der Wert bleibt drei. Damit prüfen wir, dass ein späteres Ereignis die getrennten Listener nicht erreicht. Nur eine Variable auf nil zu setzen wäre eine andere Operation.
Trenne zunächst die eigene Verbindung des Controllers und gib anschließend ihre gespeicherte Referenz frei. Trenne nicht beliebige Verbindungen anderer Systeme. In einer realen Oberfläche hilft eine Notiz zum Eigentümer direkt an der Erstellungsstelle. Bereinigung soll die Arbeit einer bestimmten Komponente beenden und nicht allen Ereignisempfängern des gesamten Projekts grundsätzlich weitere Signale verbieten.
Verwaltet erneut öffnen #
Unsere openPanel ruft zuerst closePanel auf. Erst danach entsteht eine neue Verbindung, die in currentConnection gespeichert wird. Drei aufeinanderfolgende openPanel-Aufrufe lassen deshalb einen aktiven Listener übrig. Der nächste Fire erhöht hits von drei auf vier. Geprüft wird ein zusätzlicher Callback und nicht die Behauptung, im ganzen Experiment sei insgesamt nur ein Callback ausgeführt worden.
Das passt zur Vereinbarung eines einzigen aktuellen Panel-Controllers. Es ist keine allgemeine Architektur für jede Oberfläche: Mehrere unabhängige Fenster können getrennte Eigentümer und Referenzen brauchen. Lege die beabsichtigte Zahl aktiver Listener zuerst fest. Prüfe sie dann mit einem beobachtbaren Ergebnis. Kurzer Code oder ein vertrauter Funktionsname beweisen nicht, dass die passende Zahl von Verbindungen existiert.
Wiederholte Bereinigung sicher gestalten #
Die Funktion closePanel prüft, ob currentConnection vorhanden ist. Falls ja, trennt die Funktion die Verbindung und setzt das Feld auf nil. Ein zweiter Aufruf benutzt keine freigegebene Referenz mehr. Der Test führt closePanel zweimal aus und prüft danach, dass ein neues Ereignis hits nicht verändert. Das überprüft den wiederholten Abbau unseres Controllers, nicht sämtliche möglichen Lebenszyklusfehler einer fertigen Oberfläche.
Anschließend wird openPanel erneut ausgeführt. Ein Fire muss hits vor der abschließenden Bereinigung auf fünf erhöhen. Diese Prüfung ist nötig, weil das Ende alter Reaktionen wenig hilft, wenn der Controller danach nicht mehr aufgebaut werden kann. Prüfe beide Richtungen: Bereinigung beendet die bisherige Reaktion, ein neu geöffneter Controller reagiert nach der Vereinbarung wieder genau einmal.
Once für den ersten Aufruf verwenden #
Der offizielle Leitfaden bietet Once an, wenn eine Funktion nur beim ersten Ereignisaufruf benötigt wird. Ein eigener Testabschnitt erstellt onceConnection und einen neuen onceHits-Zähler. Das Ereignis wird zweimal ausgelöst. Nach dem Warten muss onceHits eins und Connected false sein. Dieses Ergebnis wurde tatsächlich in Roblox Studio mit einem isolierten BindableEvent erhalten, nicht mit einem synchronen Ersatzmodell.
Das erste Signal ist nicht unbedingt der erste passende Vorgang deiner Fachlogik. Prüft der Callback eine weitere Bedingung, entscheide vorher, ob er nach einer ungeeigneten Eingabe weiter zuhören soll. Verstecke diese Entscheidung nicht hinter dem Namen Once. Ein einmaliger Erfolg und das erste beliebige Signal können andere Entwürfe sowie andere Erwartungen an die Testfälle benötigen.
Die Verarbeitungsreihenfolge beachten #
Nach Fire prüft unser Test das Ergebnis erst hinter task.wait. Er setzt nicht voraus, dass der Callback in der direkt folgenden Zeile fertig ist. Die offizielle Dokumentation zu aufgeschobenen Ereignissen behandelt Warteschlangen und Fortsetzungspunkte getrennt. Eine synchrone Zähleränderung in einem selbstgebauten Modell würde kein identisches Engine-Verhalten beweisen. Deshalb verwenden wir für bestätigte Ergebnisse einen echten Roblox-API-Test.
Gehe auch nicht davon aus, dass Destroy und Disconnect in jeder Situation mit wartenden Callbacks gleich wirken. Die offizielle Deferred-Beschreibung unterscheidet ausdrückliches Trennen und Zerstören bei bereits eingereihten Aufrufen. Unsere Suite prüft neue Ereignisse nach der Bereinigung sowie Once. Sie deckt nicht alle Warteschlangen, bereits laufende Funktionen oder parallele Verarbeitung ab. Solche Fälle benötigen eigene Beispiele und Prüfungen.
Das echte Hilfefenster getrennt testen #
Notiere für die spätere Integration eine Folge: Controller erstellen, mehrfach öffnen, Ereignis auslösen, zweimal schließen, nach dem Schließen auslösen und erneut öffnen. Behandle die Zerstörung des UI-Objekts und schon begonnene Arbeit als zusätzliche Fälle. Weichen Reaktionszahlen ab, vergleiche die Zahl erzeugter Verbindungen mit dem vorgesehenen Lebenszyklus, bevor du eine Verzögerung oder Debounce einbaust.
Ein Rate-Limit beantwortet, wie viele Aktionen in einer Zeitspanne zulässig sind. Verbindungsbesitz beantwortet, wie viele Listener existieren und wann deren Arbeit endet. Die Mechanismen ersetzen sich nicht. In dieser Übung gibt es keine Netzwerkanfragen, Belohnungen, gespeicherten Datensätze oder Käufe. Ergänze dafür später eigene Ergebnisprüfungen, statt den lokalen Test als Beweis für das gesamte Spiel zu behandeln.
| Phase | Erwartung |
|---|---|
| Drei Listener | hits = 3 |
| Nach Disconnect | hits bleibt 3 |
| Erneutes Öffnen | hits = 4 |
| Doppelte Bereinigung | hits bleibt 4 |
Das bestätigte Ergebnis wiederholbar sichern #
Der korrigierte Test endete mit GUIDEBOOK_CONNECTIONS_ENGINE_PASS hits=5 once=1 in Studio Output. Diese Zeile folgt auf Assertions zu drei Listenern, ihrem Trennen, verwaltetem Wiederöffnen, sicherer Bereinigung und Once. Der Lehrquelltext hält die genauen Operationen fest; der Prüfbericht enthält seine Prüfsumme. Der Gesamtwert fünf gehört zu aufeinanderfolgenden Phasen des Experiments und nicht zu einer einzelnen Spieleraktion.
Bewahre für andere Entwickler Quelle, erwartete Ausgabe, Aufrufreihenfolge und Prüfgrenzen gemeinsam auf. Bezeichne den Versuch nicht als fertigen Panel-Test: Ein Panel existiert darin nicht. Das separate Testprojekt verändert keine veröffentlichten Spiele, und die Prüfung der realen Oberfläche bleibt der nächste Schritt. Eine genaue Aufzeichnung macht Fehlerursache und vorgeschlagenen Lebenszyklus verständlich, ohne mehr zu behaupten als tatsächlich geprüft wurde.
| Grenze | Abdeckung |
|---|---|
| Neues Panel | hits = 5 |
| Once | Ein Callback |
| Oberfläche | GUI separat prüfen |
| Warteschlangen | Kein vollständiger Nachweis |
Originalquellen
Roblox Creator Hub — EventsRoblox — RBXScriptConnection
Roblox — RBXScriptSignal
Roblox — Deferred engine events