-
Notifications
You must be signed in to change notification settings - Fork 39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dungeon: add filter-stream operations to System #1565
Comments
Ich würde tatsächlich die Entität als diesen Tupel sehen. Aus private DSData buildDataObject(final Entity entity) {
DrawComponent dc =
entity
.fetch(DrawComponent.class)
.orElseThrow(() -> MissingComponentException.build(entity, DrawComponent.class));
PositionComponent pc =
entity
.fetch(PositionComponent.class)
.orElseThrow(() -> MissingComponentException.build(entity, PositionComponent.class));
return new DSData(entity, dc, pc);
}
private record DSData(Entity e, DrawComponent dc, PositionComponent pc) {} |
im prinzip bin ich da bei dir. aber bei entities bin ich prinzipiell unsicher, ob eine component vorhanden ist oder nicht, deshalb ja auch das optional in der rückgabe. beim filtern nun die gesuchten components in eine neue entität zu packen löst uns leider nicht von dem sperrigen umgang mit optional in java, auch wenn wir hier in diesem konkreten fall eigentlich wissen, dass die component/s da ist/sind ... und man könnte sich auch fragen, warum man hier in diesem fall nicht einfach die ursprüngliche entity zurück liefert... vielleicht macht das spezielle filtern nach components nur sinn für den fall, dass man nur nach einer component sucht? selbst wenn man eine einfache tupel- oder pair-klasse hätte, das problem mit der reihenfolge bliebe bei komplexeren abfragen. (obwohl man die gefundenen components darin so anordnen könnte wie im filteraufruf übergeben.) |
Eigentlich müsste man hier Ansetzen, oder nicht? |
Aus meiner Sicht ist das nicht wirklich der richtige Schritt, da wir hier verschiedene "Kreise" haben:
|
Nach der Analyse der Verwendung brauchen wir aktuell für die Filter drei verschiedene parametrische (innere) Records zum Halten der gewünschten Daten: record A(Entity entity, T component)
record B(Entity entity, T1 component1, T2 component2)
record C(Entity entity, T1 component1, T2 component2, T3 component3) (mit passend beschränkten Typ-Variablen) Diese Record-Klassen könnten als innere statische Klassen in Stream<A<T>> filteredEntityStream(T Component)
Stream<B<T1, T2>> filteredEntityStream(T1 Component, T2 Component)
Stream<C<T1, T2, T3>> filteredEntityStream(T1 Component, T2 Component, T3 Component) |
Hmmm. Lustig: In public void execute() {
filteredEntityStream(HealthComponent.class, DrawComponent.class)
...
.forEach(this::removeDeadEntities);
}
private void removeDeadEntities(final HSData hsd) {
...
Game.remove(hsd.e);
} Der Stream geht in Eigentlich möchte man den Stream nach "lebendigen" und "toten" Entitäten partitionieren => zwei Mengen:
Das sollte aber dann auf diesen beiden Mengen jeweils erfolgen, nicht auf dem Stream aus => #1578 |
@AMatutat ich habe in #1579 mal "laut überlegt" und ein paar optionen durchgespielt. option (1) ist die "traditionelle" variante, d.h. option (2) ist eine variante, die für eine, zwei oder drei components im filtervorgang in option (2a) ist eine variante von (2), bei der das neue pattern matching in java verwendet wird. leider geht das nur mit option (3) ist eine variante, wo nur eine component gesucht und benötigt wird - hier wird dann direkt ein die spielereien sind alle in system - dort gehören sie aber vermutlich nicht hin und es sollte (wenn überhaupt) nach game umgezogen werden. |
@AMatutat Ich bin grad am überlegen, ob die Rückgabe von Streams in |
Ich hinterlass hier mal ein "Ich habs gesehen" Kommentar. Hier muss ich mich mit ner dicken Tasse Kaffe nochmal reinwurschteln. |
aus #1564 (comment):
Ein häufiges Pattern ist das Aufrufen der
System#entityStream
-Methode (oder nach dem Mergen von #1564 derSystem#filteredEntityStream
-Methode) und Filtern aller Entitäten nach dem Vorhandensein einer bestimmten Component, um danach dann über alle Entitäten mit einer bestimmten Component zu itererieren. Dabei wird exakt diese Component anschließend extrahiert und damit dann irgendwas gemacht, ohne dass die Entität noch benötigt wird:Es wäre einfacher, wenn in
System
auch ein Filter fürStream<T extends Component>
angeboten würde für einfache Filter-Operationen.Bei komplexeren Filtern (mehrere Components gesucht) müsste ein Stream über passenden Tuples gebildet werden.
Edit: Das wird in dem folgenden Beispiel (aus #1567) besonders deutlich:
In diesem Fall ist offensichtlich, dass die Abstraktionen noch nicht passen: Es wird ja nicht nach den Entitäten gesucht, sondern ihren Components. Es sollte also ein Stream mit den richtigen Objekten, also den gewünschten Components geliefert werden, anstatt einen Stream mit den Container-Objekten zu erzeugen, aus dem man die Dinge dann nochmal separat extrahieren muss.
Edit: Hier ein Vergleich mit "professionellen" ECS-Systemen wie Dominion:
Ein System ist einfach eine Funktion, die zyklisch ausgeführt wird:
(Quelle: https://github.com/dominion-dev/dominion-ecs-java/tree/main?tab=readme-ov-file#ok-lets-start)
Dabei ist
hello
das ECS-System und hat in etwa die Rolle vonGame
bei uns.Interessant ist die Methode
findEntitiesWith
, die es fürDominion
(bei unsGame
) gibt. Diese macht in etwa das, was bei uns dasSystem#filteredEntityStream
macht (wieso ist das eigentlich inSystem
und nicht inGame
?!). Aber in der Rückgabe wird nicht einfach ein Entitäten-Stream erzeugt, sondern es wird direkt ein Stream mit Daten-Objekten erzeugt, in denen die gesuchten Components und auch die Entität drin sind. Wir definieren dieses Datenobjekt in der Regel in jedem einzelnen System neu und bauen es in jedem System in jedem Filter-Stream selbst. In Dominion gibt es dafür den Typinterface Results<T> extends Iterable<T>
, in dem dann verschiedene Daten-Tupel definiert werden, beispielsweiserecord With2<T1, T2>(T1 comp1, T2 comp2, Entity entity)
für zwei Components. Damit ist die Filter-Stream-Methode in der HauptklasseDominion
das sowas wie<T1, T2, T3> Results<With3<T1, T2, T3>> findEntitiesWith(Class<T1> type1, Class<T2> type2, Class<T3> type3);
... DieseWithX
-Records gibt es von 1 bis 6, bei uns würde das bei 3 oder 4 enden (vgl. Analyse unten).Seltsam finde ich, dass in
Results
mehrere Ergebnisse verpackt werden, d.h. ich kriege beim Filtern exakt einResults
-Objekt zurück. Ich kann darüber streamen, aber hier finde ich unsere API klarer - es kommt ein Stream (oder eine Collection) zurück.Darüber kann man gut nochmal nachdenken!
System#filteredEntityStream
sollte nachGame
verschwinden. Warum haben wir die Entity-Stream-Methoden an verschiedenen Stellen?Result
).Result<T>
,Result<T1, T2>
,Result<T1, T2, T3>
, ... damit würde man sich die innere Verschachtelung mit demWith1<T>
,With2<T1,T2>
, ... ersparen.Edit: Analyse der Stream-Methoden in
Game
undSystem
(warum sind die eigentlich nicht alle in einer Klasse?!):The text was updated successfully, but these errors were encountered: