Jump to content
Unity Insider Forum

Liste über X Sekunden befüllen


Noob

Recommended Posts

Hallo zusammen,

ich versuche gerade vergebens eine Funktion (genau genommen nur eine Schleife innerhalb einer Funktion) eine bestimmte zeitlang aus auszuführen.

Konkret möchte ich:

Über einen Zeitraum von X Sekunden eine Liste mit Werten von einem Magnettracker füllen.

Das Füllen stellt nicht das Problem dar. Der benötigte Zeitraum schon.

So schaut der entsprechende Code bis hierher aus:

  public void Kalibrieren()
    {
        Listenfüller();
    }

    void Listenfüller()
    {
        
        // So oft ausführen bis 1000 Werte aufgenommen wurden
                while (DateTime.UtcNow - startTime < TimeSpan.FromSeconds(5))
        {
            listeX.Add(getVar2.X2);
            listeY.Add(getVar2.Y2);
            listeZ.Add(getVar2.Z2);
            listeA.Add(getVar2.A2);
            listeB.Add(getVar2.B2);
            listeC.Add(getVar2.C2);
            
        }
        
        ...
        }

"While(DateTime.Utc...)" war mein letzter Versuch. Leider klappt der aber gar nicht. Das werden Minuten, nicht nur Sekunden =D

Ich habe es auch mit einer Coroutine versucht. Allerdings hat mir immer das Bindeglied zwischen der Coroutine und der Schleife gefehlt.

Kann mir da jemand auf die Sprünge helfen?

Danke!

Link zu diesem Kommentar
Auf anderen Seiten teilen

Du möchtest auf keinen Fall deinen Main Thread dafür auf Eis legen, nimm auf jeden Fall eine Coroutine. In diesen kannst dann wieder einen Thread packen, aber das ist schon wieder die nächste Geschichte.

Eine Coroutine wird unterbrochen, indem man ein yield-Statement benutzt:

yield return null; // Einen Frame warten
yield return new WaitForSeconds(2); // 2 Sekunden warten
yield return new WaitUntil(() => IsDone()); // Warten, bis IsDone true zurück gibt

Du kannst jetzt in einer Schleife jeweils einmal einen Frame warten und dann mit Time.deltaTime schauen, wie lange der Frame gedauert hat.

Wenn du mehr Präzision brauchst, musst du mit Threading anfangen. Da solltest du dich dann mal einlesen.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Danke für deine Antwort.

Mit einer Coroutine habe ich schon geliebäugelt. Bin dann aber nicht weitergekommen. Ich weiß einfach nicht, wie ich dann die gefüllte Liste wieder in meine Funktion bringen soll.

In der Funktion weiter unten wird die Liste noch aufbereitet.

Dazu habe ich die Coroutine gestartet, ein WaitForSeconds eingebaut und nach dieser Zeit eine bool gesetzt. Allerdings bekommt das  ganze die Schleife in der Funktion ja nicht mit.

Die Liste soll 4 mal am Anfang vom Spiel gefüllt und weiter verarbeitet werden. Auf Performance kommts also nicht gerade an. 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Für mich klingt das nicht nach einem XY-Problem =D. Aber ich bin wahrscheinlich auch zu sehr drin.

Mal ein paar Hintergrundinfos und was ich mit den Listen mache:

Ich habe vor einen Magnettracker zu kalibrieren. Dafür will ich 5 Sekunden lang die Werte vom Sensor in eine Liste, bzw. in 6 Listen schreiben.

Über einen bestimmten Zeitraum deshalb: Zuerst habe ich es ohne Zeit versucht, sondern einfach 1000 Werte aufgenommen. Da der Sensor aber nur eine Aufnahmerate von 60 Hz hat, stand 1000x der selbe Wert drin. Eher unschön.

Also will ich das ganze über die Zeit machen. Bei 20ms und 5 Sekunden Aufnahmezeit müsste die Liste dann 300 Werte aufweisen. Das müsste reichen.

Die Listen werde ich dann sortieren (aufsteigend) sowie die ersten und letzten 20% der Werte abschneiden. Damit bekomme ich die Ausreißer zuverlässig raus. Anschließend bilde ich den Mittelwert der übriggebliebenen.

Diese Mittelwerte gehen wiederrum in eine neue Funktion, die ein paar Transformationsmatrixen bereithält.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ah, verstehe.

Hierfür ist ein Thread wohl wirklich die bessere Variante, mit der zeitlichen Präzision. Zum Glück kann man beides gut kombinieren. Ich hatte dafür sogar mal eine Klasse geschrieben. Die kannst du in diesem Fall so benutzen:

public void MeasureData()
{
  StartCoroutine(MeasureData_Coroutine());
}

private IEnumerator MeasureData_Coroutine()
{
  yield return new CoroutineThread(() =>
  {
    for(var i = 0; i < numberOfMeasurements; i++)
    {
      data[i] = GetCurrentData();
      Thread.Sleep(timeBetweenMeasurements);
    }
  });
  Debug.Log("I am done measuring!");
}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 5 Minuten schrieb Sascha:

Ah, verstehe.

Hierfür ist ein Thread wohl wirklich die bessere Variante, mit der zeitlichen Präzision. Zum Glück kann man beides gut kombinieren. Ich hatte dafür sogar mal eine Klasse geschrieben. Die kannst du in diesem Fall so benutzen:


public void MeasureData()
{
  StartCoroutine(MeasureData_Coroutine());
}

private IEnumerator MeasureData_Coroutine()
{
  yield return new CoroutineThread(() =>
  {
    for(var i = 0; i < numberOfMeasurements; i++)
    {
      data[i] = GetCurrentData();
      Thread.Sleep(timeBetweenMeasurements);
    }
  });
  Debug.Log("I am done measuring!");
}

 

Danke =)

Allerdings versteh ich die Corountine nicht.

In der Coroutine sollte ich dann die Listen füllen? Wie bekomm ich dann aber die Listen aus der Coroutine wieder raus?

Link zu diesem Kommentar
Auf anderen Seiten teilen

Du kannst keine Funktion bauen, die etwas nach einer längeren Zeit zurück gibt, ohne in der Zwischenzeit den ganzen Thread anzuhalten. Eine Coroutine wird beim ersten yield-Statement unterbrochen und der Code läuft an der Stelle weiter, wo sie aufgerufen wurde:

public void StartStuff()
{
  StartCoroutine(Stuff());
  print("zwei");
}

private IEnumerator Stuff()
{
  print("eins");
  yield return null;
  print("drei (verzögert)");
}

Also ist die Methode noch gar nicht fertig, wenn der aufrufende Code schon längst das Ergebnis bräuchte. Darum kann man kein Ergebnis zurück geben.

Was mein Code jetzt aber macht ist, ein Array namens "data" zu befüllen und an der Stelle vom Debug.Log ist die Funktion damit fertig. An diese Stelle kannst du beliebigen Code einfügen, der alles mögliche mit dem Ergebnis macht.

Wenn du die Messfunktion an mehreren Stellen benutzen willst und jedes Mal mit dem Ergebnis etwas anderes machen willst, kannst du eine Action reinreichen, aber wenn nicht, führt das gerade zu weit.

Link zu diesem Kommentar
Auf anderen Seiten teilen

so hab ichs jetzt mal zusammenkopiert.

public void Kalibrieren()
    {
        StartCoroutine(Listenfüller());
    }

    private IEnumerator Listenfüller()
    {
        yield return new CoroutineThread ( () =>
            {
                
                listeX.Add(getVar2.X2);
                listeY.Add(getVar2.Y2);
                listeZ.Add(getVar2.Z2);
                listeA.Add(getVar2.A2);
                listeB.Add(getVar2.B2);
                listeC.Add(getVar2.C2);

            });
        // Sortieren, zurechtschneiden...

        // Mittelwert bilden
        float averageX = listeX.Average();
        float averageY = listeY.Average();
        float averageZ = listeZ.Average();
        float averageA = listeA.Average();
        float averageB = listeB.Average();
        float averageC = listeC.Average();

        // Listen für den nächsten Aufruf leeren
        listeX.Clear();
        listeY.Clear();
        listeZ.Clear();
        listeA.Clear();
        listeB.Clear();
        listeC.Clear();

        // nächste Methoden aufrufen und Parameter übergeben
        Transformation(averageX, averageY, averageZ, averageA, averageB, averageC);
    }

Was mir jetzt noch fehlt ist die eigentliche Zeitmessung. Imho versteh ich noch nicht so recht was hier gemacht wird:

  yield return new CoroutineThread(() =>
  {
    for(var i = 0; i < numberOfMeasurements; i++)
    {
      data[i] = GetCurrentData();
      Thread.Sleep(timeBetweenMeasurements);
    }
  });

Hier wird ein neuer Threah geöffnet und ein array gefüllt. Mir fehlt hier die Zeit. "numberOfMesaurements" will ich ja nicht vorgeben, sondern es eben zeitabhängig machen. Was meinst du denn mit "timeBetweenMeasurements"? Kann ich das vorgeben, soll ich das irgendwo "messen"?

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor einer Stunde schrieb Noob:

Ob der MainThread gestoppt wird, ist eigentlich irrelevant. Das komplette Main MUSS auf die Listen warten.

Der Main Thread ist in Unity leider der Thread, der das Bild rendert. Egal, wer da vor dem Rechner sitzt - wenn der Render Thread für drei Sekunden einfriert, mag das keiner. Zumindest eine kleine Ladeanimation sollte schon drin sein.

vor einer Stunde schrieb Noob:

Hier wird ein neuer Threah geöffnet und ein array gefüllt. Mir fehlt hier die Zeit. "numberOfMesaurements" will ich ja nicht vorgeben, sondern es eben zeitabhängig machen. Was meinst du denn mit "timeBetweenMeasurements"? Kann ich das vorgeben, soll ich das irgendwo "messen"?

Ja. Ohne Thread.Sleep arbeitet der Thread allerdings alles so schnell wie möglich ab und wird damit wertlos. Thread.Sleep lässt den aktuellen Thread x Millisekunden warten. Da es allerdings nicht der Main Thread, also der Render Thread ist, macht das nichts, denn dieser läuft einfach weiter.

numberOfMeasurements und timeBetweenMeasurements sind Platzhalter. Du kannst entweder Variablen mit diesen Namen bauen oder sie mit Zahlen ersetzen. Du hast hier drei Stellschrauben: Die Gesamtzeit für alle Messungen, die Anzahl der Messungen und die Zeit zwischen zwei Messungen. Zwei davon musst du definieren, die dritte ergibt sich daraus. Wenn du Die Anzahl der Messungen nicht definiert sein soll, dann musst du angeben, wie lange gemessen werden soll und sie lange die Pause zwischen zwei Messungen sein soll. Nur die Gesamtzeit zu definieren reicht nicht - der Computer kann nicht einfach für dich entscheiden, wie viele Messungen oder wie viel Zeit zwischen zwei Messungen es sein soll.

In meiner Lösung gibst du daher die Anzahl der Messungen und die die Zeit jeweils dazwischen an. Daraus ergibt sich eine Gesamtmesszeit von (Anzahl - 1) * Zwischenzeit. (Die -1 nur, wenn man noch eine kleine Anpassung im Code macht, die ich vergessen hatte).

Link zu diesem Kommentar
Auf anderen Seiten teilen

Es funktioniert! Danke, Sascha!

Eine Sache hätte ich aber noch:

Ich schreibe die Liste mit

using (StreamWriter writer = new StreamWriter(@"C:\Users\Timo\Desktop\Test.csv"))
        {
            foreach (double item in listeC)
            {
                writer.WriteLine(item.ToString("##.####"));
            }
        }

in eine .csv.

Dort habe ich allerdings ein paar Ausreißer. Der Wert liegt bei ca. -25 nur ein paar mal eben bei -2,5. Kann das am parsen von float zu double und string kommen? Oder an was anders? Der Richtige Wert ist auch -2,5

Theoretisch auch am Sensor. Den will ich aber zu letzt prüfen. Das ist mit mehr Arbeit verbunden.
 

Test.csv

 

mit

writer.WriteLine(item.ToString("#.###"));

kommt nun immer -2,5 raus. Wobei oft dann nur 2 Nachkommastellen angegeben sind. Die meiste Zeit jedoch 3 Nachkommastellen

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 11 Minuten schrieb Sascha:

Lass dir mal die Zahlen zusätzlich alle in der Konsole ausgeben und schau, ob es an ToString liegt.

Es liegt tatsächlich nur am ToString. In der Console schauen alle Werte tutti aus. Ist aber auch nicht tragisch, da ich die .csv nur zum Kontrollieren benutze und später keine Verwendung mehr findet

Link zu diesem Kommentar
Auf anderen Seiten teilen

Archiviert

Dieses Thema ist jetzt archiviert und für weitere Antworten gesperrt.

×
×
  • Neu erstellen...