diff --git a/src/lib/fun-hash-dispatch.hpp b/src/lib/fun-hash-dispatch.hpp index 428b096f0..09ec20787 100644 --- a/src/lib/fun-hash-dispatch.hpp +++ b/src/lib/fun-hash-dispatch.hpp @@ -14,7 +14,17 @@ /** @file fun-hash-dispatch.hpp ** Service to register and dispatch opaque functions. + ** Under the hood, the implementation is a hash table holding function pointers. + ** An instance is thus always tied to one specific function signature. Yet due + ** to the implicit conversion, simple capture-less λ can be attached as well. + ** + ** The purpose for such a setup is to provide a simple per-signature backend for + ** some advanced registration scheme involving specific function patterns. The + ** hash-IDs may be tied to target properties, which sometimes allows to limit + ** the number of actual functions in the dispatcher tables and can thus be + ** superior to a classic OO interface when subclasses would be templated. ** @see FunHashDispatch_test + ** @see proc-id.hpp "usage example" */ @@ -25,26 +35,17 @@ #include "lib/error.hpp" #include "lib/nocopy.hpp" #include "lib/hash-value.h" -#include "lib/meta/function.hpp" #include "lib/util.hpp" -//#include #include -//#include -//#include namespace lib { - namespace {// implementation details - - /** @internal mix-in for self-destruction capabilities - */ - }//(End)implementation details - - - /************************************************//** + /** + * Dispatcher-table for state-less functions with a given signature. + * Entries are keyed by hashID and can not be changed, once entered. */ template class FunHashDispatch @@ -67,6 +68,7 @@ namespace lib { return util::contains (dispatchTab_, key); } + /** retrieve entry, which can be invoked directly */ SIG* select (HashVal key) { diff --git a/src/steam/engine/proc-node.cpp b/src/steam/engine/proc-node.cpp index f8d16e22a..77dead69b 100644 --- a/src/steam/engine/proc-node.cpp +++ b/src/steam/engine/proc-node.cpp @@ -26,7 +26,7 @@ #include "lib/format-util.hpp" #include "lib/util.hpp" -#include +#include /////////////////////////////////////////////////////TICKET #1391 is boost-hash the proper tool for this task? #include #include @@ -104,9 +104,9 @@ namespace engine { HashVal hash_value (ProcID const& procID) { - HashVal hash = boost::hash_value (procID.nodeSymb_); + HashVal hash = boost::hash_value (procID.nodeSymb_); ///////////////////////////////////////////////////TICKET #1391 : which technology to use for processing-ID hashes -> cache keys? if (not isnil(procID.portQual_)) - hash_combine (hash, procID.portQual_); + hash_combine (hash, procID.portQual_); ////////////////////////////////////////////////////////TICKET #1391 : should use lib/hash-combine.hpp (stable, but not portable!) hash_combine (hash, procID.argLists_); return hash; } diff --git a/tests/core/steam/engine/node-meta-test.cpp b/tests/core/steam/engine/node-meta-test.cpp index 53849ad86..070a1e6df 100644 --- a/tests/core/steam/engine/node-meta-test.cpp +++ b/tests/core/steam/engine/node-meta-test.cpp @@ -17,10 +17,16 @@ #include "lib/test/run.hpp" +#include "steam/engine/proc-node.hpp" +#include "steam/engine/node-builder.hpp" +//#include "steam/engine/test-rand-ontology.hpp" ///////////TODO +#include "lib/test/diagnostic-output.hpp"/////////////////TODO //#include "lib/util.hpp" +#include //using std::string; +using std::abs; namespace steam { @@ -35,10 +41,95 @@ namespace test { */ class NodeMeta_test : public Test { - virtual void run(Arg) + virtual void + run (Arg) { - UNIMPLEMENTED ("render node pulling source data from vault"); - } + verify_ID_specification(); + verify_ID_properties(); + } + + + /** @test TODO evaluation of processing-spec for a ProcID + * @todo WIP 1/25 🔁 define ⟶ implement + */ + void + verify_ID_specification() + { + auto& p1 = ProcID::describe("N1","(arg)"); + auto& p2 = ProcID::describe("N1","(a1,a2)"); + auto& p3 = ProcID::describe("N1","(in/3)(o1,o2/2)"); + UNIMPLEMENTED ("parse and evaluate"); + } + + + /** @test TODO aspects of node definition relevant for the ProcID + * @todo WIP 1/25 🔁 define ⟶ implement + */ + void + verify_ID_properties() + { + // This operation emulates a data source + auto src_opA = [](int param, int* res) { *res = param; }; + auto src_opB = [](ulong param, ulong* res){ *res = param; }; + + // A Node with two (source) ports + ProcNode nA{prepareNode("srcA") + .preparePort() + .invoke("a(int)", src_opA) + .setParam(5) + .completePort() + .preparePort() + .invoke("b(int)", src_opA) + .setParam(23) + .completePort() + .build()}; + + // A different Node with three ports + ProcNode nB{prepareNode("srcB") + .preparePort() + .invoke("a(ulong)", src_opB) + .setParam(7) + .completePort() + .preparePort() + .invoke("b(ulong)", src_opB) + .setParam(13) + .completePort() + .preparePort() + .invoke("c(ulong)", src_opB) + .setParam(17) + .completePort() + .build()}; + + // This operation emulates fading of two source chains + auto fade_op = [](double mix, tuple src, uint64_t* res) + { + auto [srcA,srcB] = src; + *res = uint64_t(abs(*srcA * mix + (1-mix) * int64_t(*srcB))); + }; + + // Wiring for the Mix, building up three ports + // Since the first source-chain has only two ports, + // for the third result port we'll re-use the second source + ProcNode nM{prepareNode("fade") + .preparePort() + .invoke("A_mix(int,ulong)(uint64_t)", fade_op) + .connectLead(nA) + .connectLead(nB) + .completePort() + .preparePort() + .invoke("B_mix(int,ulong)(uint64_t)", fade_op) + .connectLead(nA) + .connectLead(nB) + .completePort() + .preparePort() + .invoke("C_mix(int,ulong)(uint64_t)", fade_op) + .connectLeadPort(nA,1) + .connectLead(nB) + .setParam(0.5) + .completePort() + .build()}; + UNIMPLEMENTED ("verify connectivity"); + } }; diff --git a/tests/library/fun-hash-dispatch-test.cpp b/tests/library/fun-hash-dispatch-test.cpp index 9ccbb468b..192db7daf 100644 --- a/tests/library/fun-hash-dispatch-test.cpp +++ b/tests/library/fun-hash-dispatch-test.cpp @@ -33,26 +33,17 @@ namespace lib { namespace test{ -// using util::isSameAdr; -// using std::rand; - using util::toString; using std::string; - -// namespace error = lumiera::error; -// using error::LUMIERA_ERROR_FATAL; -// using error::LUMIERA_ERROR_STATE; - - - namespace { -// #define Type(_EXPR_) lib::test::showType() - } - - + using util::toString; /***********************************************************************************//** * @test Verify generic helper to provide a hash-based function dispatch table. + * - instances are tied to one specific function signature + * - entries are keyed by a hash-ID + * - given that ID, the registered functions can be invoked + * - once enrolled, entries can not be replaced * @see fun-hash-dispatch.hpp * @see NodeMeta_test */ @@ -71,15 +62,12 @@ namespace test{ auto* res = dispatch.enrol (1, one); CHECK (res == one); CHECK (dispatch.contains(1)); -SHOW_EXPR(dispatch.select(1)(42)); CHECK (dispatch.select(1)(42) == "42"_expect); dispatch.enrol (2, two); CHECK (dispatch.contains(1)); CHECK (dispatch.contains(2)); -SHOW_EXPR(dispatch.select(1)(5)); CHECK (dispatch.select(1)(5) == "5"_expect); -SHOW_EXPR(dispatch.select(2)(5)); CHECK (dispatch.select(2)(5) == "*****"_expect); res = dispatch.enrol (1,two); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index a9ba2daa6..4f7969983 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -98903,9 +98903,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - + + + @@ -98926,9 +98926,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - + + + @@ -99621,8 +99621,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) jedes konkrete Projekt hat seine eigene kohärente Domain Ontology

- - + @@ -99630,8 +99629,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) Das folgt unter den Annahme, daß ein konkretes Projekt funktionieren muß. Also macht man nur Dinge, die kohärent aufgehen. Für Zweideutigkeiten findet man eine praktische Konvention, oder man weiß sie unsichtbar zu machen. Daher sollte dieses Problem für jedes konkrete Projekt in dem Sinn »lösbar« sein, daß die Cache-Steuerung fehlerfrei funktioniert. Der Beitrag der Lumiera-Infrastruktur dazu ist, für die notwendige Über-Differenzierung zu sorgen, so daß es niemals zu einer Kollision kommen kann zwischen Beiträgen, welche in disjunkten Libraries und Domain-Ontologies verankert sind.

- -
+
@@ -99668,14 +99666,14 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - + + - + @@ -99702,8 +99700,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) um mit einer solchen Spezialisierung zu binden?

- - + @@ -99717,8 +99714,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) Lösung: per Registrierung

- - + @@ -99733,8 +99729,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) ...wegen Template bloat. Umso schlimmer, wenn diese Funktionen dann fast nur von Unit-Tests genutzt werden. Aber selbst eine einzige zusätzliche Funktion für einen Hash oder konkreten Descriptor würde ich gerne vermeiden wollen, denn man generiert zwangsläufig eine große Menge redundanten codes, der vermutlich niemals aufgerufen wird (was man aber nie wissen kann...)

- - +
@@ -99744,8 +99739,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) Man würde also ein System von Hash-IDs aufbauen, und dann würde jede Instanz des Turnout-Template bei der Generierung noch entsprechende Handler für die Diagnostik-Funktionen regisrieren. Allerdings nur, wenn die Funktion nicht bereits existiert. Dieser Ansatz würde darauf spekulieren, daß die meisten Diagnostik-Funktionen gar nicht anhand der konkreten Buffer- und Parameter-Typen differenziert sind. Beispielswese Funktionen, die auf Vorgänger-Ports zugreifen, müssen nur die Beschränkung auf die Anzahl der Vorgänger beachten. Aus der Processing-Spec, welche ja exakt den ausreichenden Differenzierungsgrad haben muß (sonst funktioniert das Caching nicht richtig) ließe sich ein Sub-Key für bestimmte Klassen von Diagnostik-Zugriffen gewinnen. Dieser würde dann ein Diagnostic-Objekt aus einer Hashtable holen (was dadurch dedupliziert wäre).

- -
+
@@ -99757,8 +99751,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) ...das wäre durchaus denkbar für »operational control«, wenn es um spezielle Instrumentierung geht, also detailierte Zeitmessungen aufgezeichnet werden müssen, die sich nicht über einen einzigen Kamm scheren lassen

- - +
@@ -99769,8 +99762,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) ...aber immerhin habe ich mich damals so nobel geschlagen, daß ich daraus noch eine generische Library-Funktion gemacht habe, was mir jetzt zugute kommen könnte

- - +
@@ -99799,8 +99791,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - + + @@ -99828,8 +99820,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) Und das muß auch so sein, denn sonnst wäre das kein freier Extension-Point. Jede Abweichung davon bedeutet effektiv eine Einschränkung der Implementierung — oder anders ausgedrückt, eine Erweiterung des Port-Interfaces. Das wäre durchaus denkbar; hier geht es allerdings um den sekundären Belang, wie man eine solche Erweiterung mit möglichst geringen Kosten realisiert

- - + @@ -99851,8 +99842,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) Vorschlag wäre eine Klasse PortConnectivity, und eine Alternative als ProxyPort. Davon könnten dann die beiden (derzeit definierten) generischen Weaving-Patterns erben. Dadurch wäre dann eine uniforme Implementierung für gewisse Verhaltensmuster möglich, ohne für jede Detail-Typisierung erneut Code generieren zu müssen

- - +
@@ -99887,8 +99877,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) Egal ob der Compiler de-Duplizieren könnte — wir machen es einfach explizit: Die jeweilige Basisklasse dürfte dann aber keine Abhängikgeit von Template-Parametern haben ⟹ für die beiden bisher definierten Fälle wäre komplette de-Duplikation so realisierbar. Der Preis dafür wäre aber leider eine deutliche Verschlechterung der Lesbarkeit im Weaving-Pattern selber; nicht nur daß man nun eine Basisklasse „im Kopf haben“ müßte, zudem wären dann alle Zugriffe auf diese Basis-Parameter mit dem self-Typ im Code zu qualifizieren. Deswegen habe ich eine starke Abneigung gegenüber dieser Lösung. Es ist der typische Pragmatismus, der den Code für alle nachfolgende Wartung und Erweiterung versaut....

- - +
@@ -99927,8 +99916,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) ...wenn es allerdings nur darum ginge, (also um die Cache-keys), könnte man genauso gut eine HashID direkt in die ProcID legen (die ja ohnehin embedded im Port liegt). Ich spekuliere also schon explizit auf das Einspar-Potential durch zusätzliche Operationen...

- - +
@@ -99938,8 +99926,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) Aktuell geht es nur um Zusatz-Operationen, die rein für das Testing gebraucht werden; deshalb sollte auch über einen Lazy-Init-Ansatz nochmal nachgedacht werden. Längerfristig kommen Aufgaben für Instrumentierung und Job-Planung hinzu. Diese laufen nicht in der innersten, hoch-performanten Zone. Man sollte das Thema trotzdem im Auge behalten; möglicherweise kann man Ergebniswerte für einen ganzen Render-Chain in der Exit-Node als Abkürzung speichern, und so die re-Traversierung einsparen.

- -
+
@@ -102296,8 +102283,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) zum Einen ist das komplex, und außerdem birgt es die Gefahr der Korruption durch Updates

- - +
@@ -102310,8 +102296,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) keine offene Lösung

- - + @@ -102319,8 +102304,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) eine »offene« Lösung würde einer Erweiterung von Lumiera (plug-in) oder einer speziellen Library-Integration ermöglichen, Attribute durch das low-level-Model hindurch zu »tunneln«, ohne spezielle Unterstützung der Core-Implementierung. Ich halte das jedoch aktuell für einen nicht naheliegenden Ansatz, da es keinen »Rück-Kanal« vom low-level-Model in die Erweiterungen/Plug-ins gibt; generell hat sich das low-level-Model zu einer internen Struktur entwickelt und kann nicht verstanden werden ohne implizite Kenntnis mancher Entwicklungs-Aspekte

- -
+
@@ -102346,8 +102330,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) Normalerweise verwendet man die klassischen OO-Interfaces (oder einen generischen aber getypten Kontext) auch grade dazu, die passenden Voraussetzungen durch einen Kontrakt sicherzustellen. Das ganze Unterfangen hier aber umgeht Interfaces und Kontrakt, um die Kosten dafür einzusparen. Stattdessen hinterlegen wir einen Direkt-Zugangsschlüssel, der ohne Prüfung angewendet wird.....

- - +
@@ -102357,8 +102340,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) ...also ist der einzige Schutz, den Schlüssel inhaltlich korrekt an die Features der ID zu binden. Diese Bindung ist aber nicht formal-logisch, sondern für eine bestimmte Intention vorkonfiguriert; ob diese Zuordnung korrekt ist und zu einem validen Aufruf führt, hängt allein daran, daß bei der Erstellung die dazu passenden Merkmale in der ID hinterlegt wurden. Da diese Klassifikation zusammen mit der Registrierung der Funktion erfolgt, ist diese Verbindung so sicher wie die Logik, die bei der Registrierung zum Tragen kommt. Das heißt, wenn es — bedingt durch eine Lücke im logischen Argument — zur Registrierung zweier inkompatibler Funktionen unter der gleichen ID kommen könnte, gibt es keinen weiteren Schutzmechanismus mehr

- -
+
@@ -102369,8 +102351,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) der Aufruf ist ein blinder cast

- - +
@@ -102384,8 +102365,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) ...weil eine offene Lösung zwar ohne Weiteres mögilch wäre, aber mit erheblich größerem Storage-Overhead einhergeht, der sich dann erst mal durch de-Duplikation amortisieren müßte. Daher sage ich erst einmal YAGNI!  und realisiere die einfache Lösung mit direkter Kollaboration, deren Overhead und Amortisierung leicht abschätzbar ist

- - +
@@ -102395,15 +102375,19 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
- + + - + + + + @@ -102418,7 +102402,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -102432,9 +102416,24 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + + + + + + + + + + + + + + + @@ -103123,7 +103122,10 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + + + @@ -103139,6 +103141,31 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + + + + + + + + + + + + + + + + + + + + + +