AUA: Henne oder Ei?

denn:

  • Nexus verwendet CoreService als "upstream", um alle sonstigen Nachriten dorthin zuzustellen
  • CoreService hat Nexus als Upstream, um mit dem restlichen System kommunizieren zu können

gemeint ist: im ctor

Es speichert nur die Referenz

Ganz anders Model::Tangible: dieses registriert sich bei der Konstruktion

oder anders herum,

aber so herum macht es mehr Sinn

CoreService hat keine volle Bus-Connection

...das hab ich mir jetzt explizit so überlegt und es ist sinnvoll.

Nur ein Tangible kann eine volle Bus-Connection haben, und das heißt,

es kann downlink-Nachrichten bekommen. Dagegen hat CoreService lediglich ein "freistehendes"

BusTerm, das damit Nachrichten an den Nexus schicken kann.

UI: GuiNotification

4/18 inzwischen hier alles geklärt.

Wartet nur noch auf proof-of-concept (DemoGuiRoundtrip)

...hängt am UI-Bus,

aber nur via einfacher "uplink"-Verbindung

Sicht "von unten"

...weil es dadurch passieren könnte,

daß die Konstruktion des GuiRunners schon scheitert, bevor der Rumpf des ctors aufgerufen wird.

In einem solchen Fall wird leider auch der Rumpf des dtors nicht aufgerufen, wodurch das

Term-Signal nicht ausgesendet würde.

gemeint ist: keine volle bidirektionale Connection,

denn CoreService ist kein Tangible. Das macht Sinn so, habe darüber nachgedacht.

Anmerkung: ein "frestehendes" BusTerm ist valide und zugelassen, es hat halt nur eine uplink-Connection.

nur ein Tangible kann downlink-Nachrichten sinnvoll empfangen;

es muß dazu auch jede Menge Methoden implementieren.

GtkLumiera in Konstruktion

NotificationFacade noch nicht offen

müssen eigens aktiviert werden

...weil unser Thread-Framework

tatsächlich erzwingt, daß der neue Thrad zu laufen beginnt, bevor die

startende Funktion zurückkehrt.

Daher können wir zumindest annehmen, daß die ganze Initialisierung

bereits läuft, wenn die start()-Funktion mit true (Erfolg) zurückkommt.

Allerdings ist definitiv ein Race gegeben, und wenn

direkt beim Starten anderer Subsysteme nach dem GUI etwas schiefläuft,

dann kann der Shutdown-Prozeß den Start des GUI überholen.

wirkt alles mehr oder weniger beliebig...

0000000937: ERR: core-service.hpp:111: worker_3: ~CoreService: Some UI components are still connected to the backbone.

Speicherzugriffsfehler

...muß diejenigen Bus-Verbindungen abziehen, die von Members dieser Klasse stammen

  • CoreService selber
  • der NotificationService

wenn ich per Value capture, dann gibts schon

beim Start des GUI einen SEGFAULT

Und noch schlimmer: im Debugger gibts keinen

die Closure eines Lambdas hängt am Kontext

konkret:

der Kontext ist hier nämlich ein anderes Lambda, das dem Aufruf des GUI-Plugins mitgegeben wurde.

Dagegen die alte Lösung erzeugte an dieser Stelle einen Bind-Ausdruck, und das war offenbar genug,

um nicht mehr von dem direkten Kontext abhängig zu sein, in dem der Thread gestartet wurde.

Denn dieser Kontext (auf dem Stack) ist natürlich lange schon weg, wenn der Thread

terminiert und dann tatsächlich den Fuktor aufrufen möchte

Debug: nur ein Element connected

weil die Abstraktion "UI-Element" eben grade

die UI-internen Framework-Aspekte ausklammert.

Die Elemente stellen eine Abbildung der Strukturen aus der Session dar,

und ihre "Methoden" sind Commands auf der Session!

muß eigens aktiviert werden

Fehlerlog-Anzeige vorläufig irgendwo....

...das ist nützlich zur Diagnose,

  • aber läßt sich das überhaupt auf IterSource übertragen?
  • war es überhaupt je gerechtferigt? zu starke Annahme über den Diff-Erzeuger!

ist schon schlimm genug....

...denn eigentlich geht es nur um ein einfaches Producer-Interface,

das einen Element-Pointer durchreicht. Das einzige Problem, das ich sehe,

ist, daß hier ownership übertragen wird.

nämlich

  • ein sehr theoretisches und anspruchsvolles Konzept
  • der Zwang, das auf jedem Empfänger umzusetzen
  • die hablseidene Trickserei mit der konkreten Puffergröße
  • den double-dispatch im Diff-Framework selber
  • das Variant-basierte GenNode-Framework

das wird sowiso ein Desaster

das heißt, die Diff-Implementierung muß länger leben

faktisch erfolgt somit ein Callback aus einem anderen Thread

um das Diff zu pullen

jede Facade-Funktion brauch einen Dispatcher

Das wird eine ganze Me

bevor die Facade geöffnet wir

und arbeitet asynchron

Argument-Storage

organisieren

brauche dedizierten Dispatcher

kann mich so vague erinnern

daß hier zwar schon ein catch eingebaut war,

aber noch irgend ein Hund begraben liegt

...und insofern ist auch die Behandlung einer Folge-Exception noch offen

Im Moment loggen wir nur ins textuelle Konsole-Log (NoBug)

...könnte das am Ende nicht sinnvoll sein,

speziell den UI-Shutdown-Trigger über den neuen Mechanismus laufen zu lassen,

obwohl jener doch genau der Anlaß war, diesen neuen Mechanismus zu bauen.

wenn die Queue voll ist

wird erst alles Andere abgearbeitet

wenn UI-Thread blockt/verhungert,

kommt der rettende Shutdown gar nicht durch

das ist schon die endgültige Lösung

...in den eigentlich kaputten DockManager

es ist schon bekannt, daß dieser Service der ViewLocator sein wird,

und daß man ihn via InteractionDirector erreicht

weil so sichergestellt ist, daß er stets existiert,

und er trotzdem vom konkreten Widget entkoppelt bleibt

besserer Name: NotificationHub

bisher können wir das GUI nur aktiv intern schließen,

indem wir ein GTK-Signal erzeugen, das das Hauptfenster schließt

...das war genau der Kern der "Plugin-Debatte".

Eine solche globale, flache, dynamisch gebundene Ebene

klingt nach wahnsinnigen Möglichkeiten, aber nur solange, bis man sich

eine einzige Funktion konkret durchdenkt: es läuft auf Spaghetti-Code hinaus

...indem der NotificatonService nun vom UI-Manager gemanaged wird :)

zieht komplett-Umbau

des Gui-top-Level nach sich

...nur eine heuristische Vermutung von mir

stützt sich auf folgenden Quellcode

Application::Application(const Glib::ustring& application_id, Gio::ApplicationFlags flags)

:

  // Mark this class as non-derived to allow C++ vfuncs to be skipped.

  //Note that GApplication complains about "" but allows NULL (0), so we avoid passing "".

  Glib::ObjectBase(0),

  Gio::Application(Glib::ConstructParams(custom_class_init(), "application_id", (application_id.empty() ? 0 : application_id.c_str()), "flags", GApplicationFlags(flags), static_cast<char*>(0))),

  m_argc(0),

  m_argv(0)

{

  gtk_init(0, 0);

}

Proc: SessionCommand

setzt aktivierten Dispatcher zwingend voraus

es genügt definitiv nicht, nur die Dispatcher-Komponente(Schnittstelle) erreichen zu können.

Jede Operation, die über dieses externe Interface bereitsteht, benötigt zur Implementierung

eine aktiv laufende Dispatcher-Queue.

Daher macht es Sinn, den Interface-Lebenszyklus ganz starr an den Disspatcher zu binden

...und zwar wirklich sehr implizit,

nämlich über die Identität (IDs) der Command-Parameter.

Das heißt, ein eingehendes Command paßt nur zu einer bestimmten Session-Instanz,

was zwar jederzeit (via statisches/internes Session-API) verifizierbar ist, jedoch nicht offensichtlich

das folgt einfach aus den logischen Eigenschaften der beteiligten Komponenten,

welche eben autonom sind.

Das heißt im Klartext, alle Clients müssen darauf vorbereitet sein, daß diese Schnittstelle

jederzeit wegbrechen kann, was dann heißt, daß irgend ein Aufruf eine Exception wirft

wer besitzt die

Implementierung

meint: zwei gekoppelte Statusvariable

muß alle Operationen durchschleifen

oder muß PImpl als Interface exponieren

meint: zwei gekoppelte Statusvariable

Shutdown tricky

Session-Subsystem implementieren (#318)

....das ist schon mehr ein Meta-Ticket,

und es hängt wohl zu viel darunter, um es gleich ganz abschließen zu können.

Aber ich akzeptiere es und verwende es jetzt als Treiber

ist nicht "die Session

das Lock sorgt hier für konsistenten Zustand und Sichtbarkeit (memory barrier)

Lock ist hier das Dispatcher-Lock

...wenn jemand zugreift

grundlegende Design-Enscheidung

  • wir haben Komponenten mit Dependency-Injection
  • da beide Komponenten nur nach ihren eigenen Hinsichten funktionieren,
    wird das System insgesamt einfacher

muß SessionCommandService schließen

bevor die Dispatcher-loop angehalten wird

1/2017 Review durchgeführt und Logik überarbeitet.

Einziger Risikofaktor ist nun, wenn beim Schließen des SessionCommand-Intertfaces

oder beim Signalisieren an den Thread eine Exception fliegt; denn dann loggen wir zwar,

aber die Shutdown-Rückmeldung kommt u.U niemals an, und damit bleiben wir

am Ende von main() einfach hängen.

Ich halte diese Fälle aber für in der Praxis nicht relevant,  und verzichte daher auf eine Spezialbehandlung

Einfachen Aufruf

implementieren

will sagen: in den Visitor-Methoden-Implementierungen

aber ist nicht auf dem Visitor-Interface darstellbar

....über einen GenNode-Visitor nachdenken

aber

  • nicht jetzt
  • das Problem müßte mehrfach auftreten
  • könnte zu Switch-On-Type-programming führen

Idee: Zusammenarbeit

...das wäre eine Protokoll-Erweiterung

vertagt; Ticket #1058

...das heißt,

die mehrfachen Indirektionen und das Ein-/Auspacken der Argumente

es wäre denkbar, an dieser Stelle

unvollständige Argument-Tupel zu akzeptieren

und die Argumente von links her zu schließen (currying)

denn es erzwingt,

daß die betreffenden Commands schon erzeugt und registriert sein müssen,

wenn in der UI ein InvocationTrail angelegt wird.

Architektur-Entscheidung

kann offen bleiben

reine ID-Wirtschaft wäre möglich

...wartet noch darauf,

daß die alte, obsolete Timeline zurückgebaut ist

siehe guifacade.cpp

Problem ist: wenn das triggerShutdown kommt,

bevor die Notification-Facade geöffnet werden konnte

Aufgabe: docking panels global

Begründung:

Das neue System ist anscheinend fest integriert in Gio::Application.

Mir ist nicht klar, wieso ein Fenster/Widget das Interface Gio::Actionable implementieren muß.

Ich werde den Verdacht nicht los, daß hier das Ziel verfolgt wird, eine "Action" von den

Grenzen der Applikation zu befreien und direkt in den Desktop zu integrieren.

Mit Desktop ist natürlich der Gnome-Desktop gemeint. Was diesen Verdacht bestärkt,

ist, daß Gio::Application sofort auch gleich eine dBus-Verbindung hochfährt.

eigentlich wollen wir "das aktuelle"

Lösung: schwebende Bindung

arbeitet dann freischwebend

...meint:

wir müssen zur Aufrufzeit einer Aktion

an den aktuellen Kontext ankoppeln können.

Das heißt, der UiManager muß im Stande sein,

diesen "aktuellen Kontext" irgendwo aufzufischen

dort wird der Kontext aufgegriffen

...und der wird in der Tat an vielen Stellen includiert

und verwendet, und das ist auch gut so

Njet

InvocationTrail ist tot

generisches Öffnen

...um die Entwicklung des Designs zu erzwingen

und den Teufelskreis zu durchbrechen!

...stattdessen einen Fehler-Indikator auslösen

(Beispiel "in-point fehlt")

...das ist eine Reaktion,

die von einem managing Ui-Element ausgeführt wird,

aber von einem externen State-Change getriggert wird

damit UNDO funktionieren kann,

müssen wir schon beim capture wissen,

welches Objekt (ID) hinzugefügt werden wird.

Denn sonst müßten wir uns den gegenwärtigen Inhalt speichern

und das wäre unsinnig.

wir können den größten Teil dieser Einzeiler-Funktionen loswerden,

da es nur darum geht, via globalCtx auf den passenden Controller zuzugreifen

das verschiebt das Problem nur

UI-Koordinaten (UICoord)

...and this anchorage can be covered and backed by the currently existing UI configuration

...by interpolation of some wildcards

...need to be extended to allow anchoring

we may construct the covered part of a given spec, including automatic anchoring.

navigate to the real UI component

designated by the given coordinate spec

...halten wir besser raus aus diesem Design.

Denn es würde stärkere Annahmen über die "Zielelemente" erforderlich machen,

und diese dann doch wieder in ein Korsett zwängen. Im Moment (10/17) habe ich

stark den Verdacht, daß wir das nur in wenigen Spezialfällen brauchen werden,

und dann kann man es auch extern belassen.

...was für verschiedene Arten von Zugriff

sind denkbar und müssen in der Strategy konfigurierbar sein?

mögliche

Komponenten

wie funktioniert

Pfad-Navigation?

habe diese Analyse 2017/2018 ein Stück weit vorangetrieben.

Ergebnis war die Schaffung von UI-Koordinaten und die ViewSpec-DSL

Damit ist das Thema aber bei Weitem noch nicht ausgeschöpft,

jedoch genügend aufgeschlossen, um die konkrete Implementierung fortzusetzen

betrachte ich als ungesund

eine reverse resolution

zentrales Problem

...der beim Erstellen des Elements

mit den zu diesem Zeitpunkt bekannten UI-Korrdinaten bestückt wird

auf welche Eigenschaften

stützen wir uns?

wer bestimmt,

was "Kind" ist?

oder den Spot verschieben

verbleibende

Probleme

VariadicArgumentPicker_test

welche Operationen

sind wirklich notwendig

...denn wir verlangen, daß wir nach dem Interpolieren über eine Lücke

immer noch mindestens ein explizt gegebenes Element im Pfad haben,

welches auch von der UI-Topologie bestätigt wird.

Grund: wir wollen vermeiden, abschließende Wildcards bloß irgendwie zu binden

nämlich wenn der Pfad mit einem explizit gegebenen Präfix anfängt,

dann aber Wildcards enthält, die nicht nach den verschärften Bedingungen gecovert werden können.

beispielsweise

  • wenn schon das Präfix nicht paßt
  • wenn das erste Element nach dem Gap nirgends im realen UI in der tiefe existiert
  • wenn mehr Wildcards da sind, als restliche Tiefe zum Matchen

letzten Endes war es nahezu gleich schwer zu implementieren,

aber von der Aufruf-Logik her einfacher, stets nach partieller zu suchen

und totale Coverage nur nachträglich durch Längenvergleich festzustellen

wo der Spot ist

Innerhalb einer Kinder-Folge gibt es keine duplikaten IDs.

Das heißt, es genügt, den ersten Match zu nehmen.

Warnung: diese Konvention ist besonders tückisch,

denn eine Verletzung kann weithin unbemerkt bleiben

der Resolver macht kein Memory-Management,

sondern speichert einfach Zeiger.

Es wird erwartet, daß diese gültig bleiben,

solange irgend jemand auf den Resolver oder den

daraus resultierenden Pfad zugreift

die Topologie, aber auch der Fokus-Zustand

ändern sich nicht während der aktiven Lebensdauer eines Resolvers

Hierbei ist aktive Lebensdauer wie bei einem Iterator zu verstehen.

aber es gibt Konsistenzchecks + Exceptions

wenn die Auswertung aufgrund einer gebrochenen Konvention entgleist

Tiefensuche über die reale UI-Topologie

Ziel ist, den Pfad bestmöglich zu covern

Es gilt die erste maximal abdeckende Lösung

Tiefe, bis zu der dieser Pfad gedeckt ist.

Sofern der Pfad bereits explizit ist, genügt diese Info allein

heap-allozierter expliziter Pfad.

  • wird notwendig, wenn *this wildcards enthält
  • Lösung wird unter Alternativen ausgewählt (nach maximaler Tiefe)

Ha! das ist eine Monade!!!!!1!11!

...aber im Moment der Lösung brauche ich den Pfad aufwärts.

Das heißt, wir müssen ihn bereits im Aufrufkontext bereit liegen haben

Puffer ab Tiefe

vom Pfad initialisieren

monadische Lösung

möglich?

echte Expand-Funktion notwendig

mathematische Monaden sind viel mehr...

Im Besonderen sind es Typen höherer Ordnung,

also mehr als bloß parametrisierte Typen (Templates)!

alles mit einer Form des IterExplorer machbar ist

...da IterExplorer einen Template-Template-Parameter nimmt,

ist er eigentlich ein Meta-Template, und es gibt diverse

Ausprägungen, die alle subtile Seiteneffekte ausnutzen....

...was nicht grade zur Verständlichkeit des Ganzen beiträgt

das Fortschreiten der Berechnung dargestellt werden kann

Problem: Layer sind verkoppelt

dann aber als State Monad

m >>= f = \r -> let (x, s) = m r

                            in (f x) s

wende die resultierende State-Monade

auf den Zwischenzustand x aus (1) an

sind Monaden

wirklich hilfreich?

alternatives

Ziel

Fazit:

brauche...

das impliziert grundsätzlich einen Stack

expand() ruft eine vorbereitete Parametrisierung

 für diesen Expand-Mechanismus auf

...selbst wenn man ihn für eine triviale Implementierung

eigentlich überhaupt nicht braucht.

Das kann zwar zu einem gewissen Grad abgemildert werden,

indem man einen speziellen Inline-Stack mit Heap-Overflow nutzt

ich brauche ihn nicht

die Builder-Operationen moven den bisherigen Iterator-compound weg.

Ich könnte mir vorstellen, daß das einen naiven User ziemlich schockiert....

Lösung wäre, das Iterator-API erst nach einem expliziten terminalen Aufruf freizuschalten

weil...

  • sich zwar die Logik syntaktisch anschreiben läßt
  • aber beide Zweige u.U nicht auf den gleichen Typ hinauslaufen
  • und erst in der Anwendung dieses Ausdruckes werden die Typen gleichgestellt

_fun<FUN>::Sig scheitert

das ist aber unpraktisch....

....also ist es gradezu natürlich,

einen Expand-Funktor als generisches Lambda zu schreiben!

aber eine falsche Template-Instantiierung

ist ein Compile-Fehler, kein Substitutions-Fehler

oder man fällt auf eine mögliche Substitution zurück

...und wenn die Scheitert, ist das ein compile-Fehler

denn das ist der sinnvollste Fall für ein generisches Lambda

....und das ist alarmierend,

denn Debugging ist mindestens doppelt so schwer...

TreeExplorer per slicing move entfernen

wir haben bisher viel zu naiv angenommen,

daß der parent-Iterator immer auch ein TreeExploer ist.

Dem ist nicht so, ab dem Moment, wo wir mehrere Decorator-Layer haben!!

Aufrufpunkt: invokeTransformation()

In instantiation of 'lib::{anonymous}::_ExpansionTraits<FUN, SRC>::Res lib::{anonymous}::_ExpansionTraits<FUN, SRC>::Functor::operator()(ARG&) [with ARG = long int; FUN = lib::test::IterTreeExplorer_test::verify_transformOperation()::<lambda(auto:2)>&; SRC = lib::iter_explorer::IterableDecorator<long int, lib::iter_explorer::WrappedIteratorCore<lib::TreeExplorer<lib::iter_explorer::StlRange<std::vector<long int>&> > > >; lib::{anonymous}::_ExpansionTraits<FUN, SRC>::Res = std::basic_string<char>]':

src/lib/iter-tree-explorer.hpp:426:50: error: no match for call to '(std::function<std::basic_string<char>(lib::iter_explorer::IterableDecorator<long int, lib::iter_explorer::WrappedIteratorCore<lib::TreeExplorer<lib::iter_explorer::StlRange<std::vector<long int>&> > > >&)>) (long int&)'

also IterableDecorator aufgesetzt auf die Core im Transformer

wir gehen davon aus,

daß der Optimizer solche inline-Accessor-Funktionen

ohnehin restlos wegoptimieren wird

und zwar in dem Moment, wo man die Layer zusammensetzt.

...denn wenn mal ein Layer "nur" Iterator wäre,

dann könnte es eine Kombination geben, die einen solchen Layer übersrpingt

...nur was billig ist,

denn im Moment brauchen wir das überhaupt nicht

...setzt eigentliche Expand-Operation darunter voraus

...also Programmierung analog zum Filter

  • sofort aus dem Konstruktor heraus die Invariante etablieren
  • nach jedem Iterations-Schritt die Invariante erneut wiederherstellen

der Natur der Dinge folgen,

nicht den technischen Möglichkeiten

....und dann könnten diese Transformer in der Kette

nicht mehr die expandChildren aufrufen

...das heißt, es ist nicht möglich,

daß ein Layer irgendwo in der Mitte einen solchen generischen Hook aufruft.

Es sei denn, man speichert einen Funktion-Pointer in der Basis,

oder nimmt eben gleich eine virtuelle Funktion.

Das ist aber hier aus grundsätzlichen Überlegungen heraus keine Option

das IterStateWrapper-API ist optimal

unter der Annahme, daß wir beim Lumiera Forward Iterator - Konzept bleiben

-- das heißt, beliebig oft yield, und Iterations-Ende per bool()-Test

Unter dieser Annahme kommt yield stets vor iterNext (wenn überhaupt).

Und yield muß (a) einen Status liefern, (b) einen Wert liefern.

Einziger Ausweg wäre, wie das IterAdapter macht, einen Pointer rauszugeben.

Das ist eigentlich keine gute Lösung, weil die Implementierung dann sehr tricky wird.

Siehe IterAdapter als abstoßendes Beispiel.

Und als weitere Alternative bleibt nur die Einführung von State, und das bedeutet,

sich im Iterator oder in der Implementierung irgendwo noch eine zusätzliche bool-Flag zu speichern.

implementieren ebenfalls expandChildren()

ganz anders als bei IterAdapter, wo das Sinn macht...

...ohne daß die Funktionen auch virtuell sind,

können wir nicht sicherstellen

  • daß die hereingereichte Implementierung die Funktionen überschreibt

Konzept funktioniert nicht

aus processing-Function

innen heraus

will sagen, das ist ja auch eine durchgeknallte Idee....

wenn eine Funktion in einem Layer expanded

...d.h, entweder man gibt aus dem Functor das zurück,

was vor dem expand anstand (=der Vater), oder man verwirft diesen

und liefert das, was nach dem expand erscheind

  • asIterSource() verwendet ein eigenes Interface, um diesen call an die Implementierung durchzureichen
  • in dieses Interface habe ich nun einen Rückgabewert eingebaut
  • damit kann ich das IterSource-Front-End refreshen
  • trotzdem hässlich...

an welcher Stelle wird diese Mechanik

an einen bestehenden Iterator angeschlossen

Stichwort HierarchyOrientationIndicator

...will sagen, bin nun schon mehrfach in dieses Problem gelaufen,

nachdem ich dachte, alles so schön gelöst zu haben.

Das Problem ist, daß eben auch der Konsument irgendwie

von den verschachtelten Strukturen mit gesteuert wird.

Das Ergebnis ist eben nicht rein linear.

...das ist nämlich der triviale Workaround

wir hatten bisher eine auto-Aufräum-Routine in iterNext(),

welche dazu führt, daß ein erschöpfter Vater sofort weggeräumt wird,

noch bevor wir dazu kommen, die Kinder zu pushen

jeder Zugriff auf ein Sub-Objekt muß durch eine VTable

Stichwort: virtual base offset

...da gibt es eine explizite Ausnahme-Regel.

Die Copy-Konstruktoren werden aus der Kandidaten-Menge entfernt.

Grund ist, daß die default-Initialisierung der Member-Felder noch nicht hinrechend geklärt war.

C++17 holt das nach

...dort wird einfach on-demand in der Basisklasse nachgeschaut.

Wenn dabei ein Basis-Copy-Ctor gezogen wird, dann wird eben default-Init für die Felder im abgeleiteten Objekt gemacht.

Es gibt dann eine neue, explizite Regel, die verhindert, daß zufällig ein aus der Basis geerbter Ctor

die Signatur eines Copy-ctors überdeckt

Allerdings genügt es, dies an einer Stelle in der Kette zu ergänzen

...und zwar genau dort, wo erstmals ein Basis-Objekt akzeptiert wird.

Das ist bei uns im BaseAdapter, also der ersten Ebene über dem zu initialisierenden Basis-Objekt.

Alle anderen Layer darüber reichen dann korrekt mit dem geerbten Ctor diese Initialisierung nach Unten.

übrigens ist es im IterSource<T>::iterator nicht  notwendig

...das war nur ein unnötiger Fix nach dem Gießkannen-Prinzip.

Denn dieser Iterator soll niemals mit einem Basis-Objekt initialisiert werden,

sondern stets von der IterSource-Builder-Funktion konstruiert.

Und wenn man selber keinen Ctor in eine Klasse schreibt, sondern nur ctor-erbt,

dann werden auch die Copy-Konstruktoren korrekt automatisch generiert.

Ticket machen: #1125

...daß man ein Ding komplett in einen Iterator packt,

und dieser es dann auch managed

ist das #190

...ist partiell diese Idee.

Nur auch das auf einem etwas anderem Level,

und immer mit einem Heap-allozierten vector

versehentlich wurde auch der an std::forward gegeben

habs mit FormatUtils_test bewiesen

Dazu in NumIter einen explizit tracenden move-ctor eingebaut

weil der Aufruf von join(&&) selber wasserdicht ist

D.h. er frisst keine Werte.

Deshalb fällt dieses doppelte Problem nicht auf

...weil man den konkreten Typ der Core kennen muß

verschiedendste Pipeline-Konstruktionen

können nun hinter dem gleichen Interface sitzen

rein ein Problem mit der Test-Fixture.

Da die Quelle nun von einem shared-ptr gehalten wird,

erzeugt eine Kopie des Iterator-Front-End

nun nicht mehr eine Kopie des ganzen Zustandes.

das wäre aber bequem für den Test.

Frage: ist das überhaupt eine gute Idee, vom Design her??

siehe std::shuffle

vorgegebene Zahlenfolge
in untendlichem Zufalls-Baum finden

man kann move(iter) verwenden,

wenn man konsumieren möchte

rLet(40878 < 18446744073709551615) → S

|↯| S ... 40878

rLet(40879 < 18446744073709551615) → F

|!| expand 40879

rLet(0 < 4) → A

rLet(40880 < 18446744073709551615) → Q

|.| A -->> 40879

|!| expand 40879

rLet(0 < 4) → F

rLet(1 < 4) → N

|.| F -->> 40879

|↯| F ... 40879

rLet(1 < 4) → W

|↯| W ... 40879-0-1

rLet(2 < 4) → N

|↯| N ... 40879-0-2

rLet(3 < 4) → T

|↯| T ... 40879-0-3

rLet(4 < 4) → F

|↯| N ... 40879-1

rLet(2 < 4) → F

|↯| F ... 40879-2

rLet(3 < 4) → A

|!| expand 40879-3

rLet(0 < 4) → J

rLet(4 < 4) → Y

|.| J -->> 40879-3

|↯| J ... 40879-3

rLet(1 < 4) → H

|↯| H ... 40879-4

rLet(2 < 4) → H

|↯| H ... 40879-5

rLet(3 < 4) → F

|↯| F ... 40879-6

rLet(4 < 4) → V

|↯| Q ... 40880

rLet(40881 < 18446744073709551615) → A

|↯| A ... 40881

rLet(40882 < 18446744073709551615) → X

|↯| X ... 40882

rLet(77943 < 18446744073709551615) → R

|↯| R ... 77943

rLet(77944 < 18446744073709551615) → X

|!| expand 77944

rLet(0 < 4) → U

rLet(77945 < 18446744073709551615) → I

|.| U -->> 77944-0

|↯| U ... 77944-0

rLet(1 < 4) → X

|!| expand 77944-1

rLet(0 < 4) → K

rLet(2 < 4) → Z

|.| K -->> 77944-1-0

|!| expand 77944-1-0

rLet(0 < 4) → V

rLet(1 < 4) → Y

|.| V -->> 77944-1-0-0

|↯| V ... 77944-1-0-0

rLet(1 < 4) → I

|↯| I ... 77944-1-0-1

rLet(2 < 4) → I

|↯| I ... 77944-1-0-2

rLet(3 < 4) → Z

|!| expand 77944-1-0-3

rLet(0 < 4) → Q

rLet(4 < 4) → X

|.| Q -->> 77944-1-0-3-0

|↯| Q ... 77944-1-0-3-0

rLet(1 < 4) → M

Protocol of the search: 77944-1-0-3-1

...weil es mutmaßlich

im realen UI in ähnlicher Form auch auftreten wird:

die Menge der Top-Level-Fenster ist eben etwas anderes,

als die Menge der Tracks in der Timeline.

Erst nach einer Transformation wird daraus eine Menge von Strings

Expand-Funktor hat einen Rückgabe-Typ

wenn es sie gäbe könnte man sie hier nutzen

...das heißt:

diese Struktur muß bereits beim Aufbauen des GUI

nebenbei mit aufgebaut werden, und über alle

mutierenden Aktionen hinweg automatisch konsistent bleiben

möglicherwese aber notwendig

...will sagen

wenn ich mir heute so die Situation vorstelle,

könnte es darauf hinauslaufen, daß man das braucht.

Und zwar, zumindest die Eigenschaft, von gegebenem Element

die Koordinaten zu ermitteln.

Das ist aber dann pratkisch auch schon eine "Up"-Funktion,

selbst wenn man sie nur indirekt implementiert

vorläufige MInimal-Lösung

bis jetzt kommen wir ohne Pos-Abstraktion aus

das Nav-Interface könnte daraus entstehen

es werden jetzt keine weiteren Features für TreeExplorer gebaut....

...mal sehen, ob wir jemals daran anstoßen...

gemeint ist,

zusätzlich zu dem Eintrag im Stack,

der ohnehin selbst Heap-alloziert ist

Was ist Nav und was ist Iteration-control?

Ist es sinnvoll, beide in einem gemeinsamen API zu haben,

oder delegieren wir besser?

Was sind die Kosten dafür?

IterSource muß insgesamt besser erweiterbar werden....

der Expander sitzt nun doch dahinter, in der Implementierung

das Ergebnis ist der konkrete Iterator-Typ

Wire-Tap-Implementierung

das heißt, depth ist aktuelle Tiefe!

ist keine Lösung

ist partielle Lösung

Ergebnis-Ausgabe ist die jeweilige mögliche Coverage

...insofern wir nur eine (partielle) Lösung signalisieren,

wenn wir einen direkten Match erziehlen.

Ein wildcard-Match führt nur dazu, daß wir zu den Kindern absteigen,

aber zählt erst mal für sich nicht als Lösung

Support für elided element

  • nur für internen Gebrauch
  • protected im Builder

Lösungen müssen

am Ende des Patterns liegen

YAGNI

  • die Standard-Implementierung von std::swap macht einen Dreiecks-Move
  • wir haben effiziente Move-Konstruktoren

...und nicht den möglichen Zustand.

Denn für letzteren gibt es die "canXX"-Prädikate

macht es Sinn, dafür einen expliziten Testfall zu konstruieren,

oder verfangen wir uns da sofort zu sehr in der Implementierungs-Technik?

realer Pfad endet mit elided nach Wildcard

kann jedoch demonstrieren,

daß der Algorithmus solche Lösungen verwirft

neue Einsicht 31.12.17

totale Coverage ist das, was man naiverweise erwartet.

Also sollte das auf dem API der default sein

...das heißt: keine Wildcards, keine pseudo-Specs (currentWindow)

Zweck ist vor allem, meta-Specs wie firstWindow, currentWindow aufzulösen

...verwendet einen GenNode-Tree

als Repräsentation des real-existierenden UI

Integration ViewLocator

Resolver / Navigator

aber auch: Resolver

die Regel ist:

Bei zyklischen Abhängigkeiten erfolgt der Ringschluß

an einer Stelle über eine allgemeine Abstraktion

implementiert LocationQuery

...als Namespace-globale Variable mit externer Linkage

Ein Lookup-Vorgang ist schon ehr aufwendig,

jedoch harmlos im Vergleich zu einer einzigen Frame-Berechnung.

  • im ersten Schritt machen wir eine Tiefensuche potentiell über die ganze UI-Topologie
  • im zweiten Schritt wiederholen wir noch mal den Abstiegspfad zur Lösung des ersten Schrittes

Grundsätzlich gilt hier die Einschätzung: Klarheit der Schnittstelle hat Vorrang

ViewLocator ruft die DSL auf

...welcher wiederum von ViewLocator betrieben

sonst bekommen wir eine versteckte

zweite hart-gecodete Fallback-Konfig

existing() sollte default sein und create() explizit anzufordern

weil der größte Teil aller real anzugebenden Regel-Klauseln

von der Bedeutung her "existing" meint

komplett definierter Pfad incl Zielobjekt

ggfs wird höchstes ein abschließendes Element hinzugefügt

dieser Pfad ist stets anchored und partially covered

zwar könnte es (später mal) sein,

daß wir mehrere Perspektiven gleichzeitig in die UI-Topologie abbilden...

...weil die Perspektive eigentlich als etwas Orthogonales empfunden wird,

das nicht dirket zur "harten" Topologie gehört, sondern vielmehr bestehende Elemente umgruppiert.

Andererseits möchte man eben doch manchmal eine View-Spec eigens auf eine bestimmte Perspektive beschränken,

und deshalb habe ich die Perspektive zu den UI-Koordinaten hinzugenommen

...und zwar genau dann, wenn bereits die nächste Komponente unterhalb der Perspektive,

also das Pannel, nicht oder nicht in dieser Form existiert, also erzeugt werden müßte.

Unser Kriterium für Lösungen jedoch verlangt mindestens einen Match jenseits der Wildcards,

um den Match eindeutig zu machen.

...sofern es stets eine Perspektive geben muß

...und zwar zwingend notwendig, weil es (viele) Views geben wird,

welche keine mehrfachen TABs unterstützen. In solchen Fällen brauchen wir

ein Konstrukt, mit dem sich eine Ebene im Baum überspringen läßt

"muß" ist relativ, denn mit den bisherigen Anforderungen

hätte es genügt, den "elided"-Platzhalter nur in den konkreten UI-Koordinaten

zu verwenden, und ihn dann jeweils per Wildcard zu matchen (was automatisch passiert,

einfach wenn die betreffende Komponente in der Angabe fehlt)

Die korrekte Semantik fällt uns hier wirklich in den Schoß,

es ist nur eine weitere Zeile in dem Test, ob ein Match vorliegt

wobei einer, nämlich '*' sehr offensichtlich und bekannt ist,

während der andere (eben dieses '.' == elided) eigens erklärt werden muß

...weil es eigentlich ein Wildcard ist,

aber vom gesamten sonstigen Algorithmus nicht als Wildcard behandelt wird.

Damit kann man alle Einschränkungen unterlaufen

Korrekter Gebrauch setzt eigentlich voraus,

daß es an dieser Stelle auch tatsächlich "gar nichts" oder nur "ein stets festes Element" gibt.

Für die Perspektive ist das (nach jetztiger Planung) stets gegeben.

Wenn man allerdings diese Bedingung verletzt, dann matcht der "elided"-Platzhalter

in mehreren alternativen Zweigen wie ein Wildcard, und es hängt dann von

zufälligen Umständen ab, ob man die erwartete Lösung bekommt

bloß würde sich die Signatur der DSL-Bausteine ändern:

Allocator = std::function<UICoord(UICoordResolver)>;

...denn Reolver ist ein UICoord::Builder und als Solcher non-copyable.

Also würde das ganze Gefrickel mit Referenzen losgehen,

in einem Stück Metaprogramming-Code, das ohnenhin schon ziemlich "dicht" ist....

Der Punkt ist: das ist eine reine Lauzeit/Effzienz-Überlegung.

Nachdem das Pfad-Matching in der DSL für die Location die passende Lösung gewählt hat,

wäre -- im UICoordResolver eben -- auch schon die effektive Coverage bekannt.

Da aber unser API nach (reinen) UI-Coord verlangt, werden diese aus der berechneten Lösung

herausbewegt. Und der Allokator muß sich dann erneut einen UICoordResolver bauen,

oder zumindest das LocationQuery-Interface bemühen, welches dann nochmal den Baum

traversiert um die Coverage festzustellen.

  • Ja, das kostet und ist verschwenderisch...
  • und Ja, vermutlich sind die paar CPU-Zyklen komplett egal

das ist der klassische Fall, wo man wegen einer solchen Optimierung

sich ein Interface versaut und ziemliche zusätzliche Komplexitäten an Bord zieht.

...insofern auch dort

die jeweilige generische Regel parametrisiert / instantiiert wird

gegen den Kontext, mit dem sie matchen soll

das ist eine typische, rein lokale Optimierung (Speicher vs CPU)

Ein solcher View-Index sollte dann ebenfalls via LocationQuery exponiert werden

es gibt nur einen "Locator"

ein LocationSolver

LocationQuery qua Navigator

...die es hinten herum bekommt

sehen ViewLocator-API

Regel-Parametrisierung

Kontextualisierung

Man möchte, daß für spezielle Sub-Elemente,

die aus einem fremden Kontext heraus geöffnet werden,

zunächst versucht wird, einen irgendwo im UI schon bestehenden TAB

für speziell diesen Element-Typ wiederzuverwenden; das erlaubt dem User,

sich einen Platz für sehr spezielle Sachen beiseite zu setzen.

z.B. sehr spezielle Assets oder ein virtueller Clip.

Erst wenn so ein Ort nicht gefunden wird, möchte man auf einen

generischen Ort zurückfallen, und erst als letzte default-Lösung

im aktuellen Fenster einen völlig neuen UI-Elementkontext schaffen.

...statt die gesamte Matching-Engine mit einer Art

halbgaren Unifikation aufzubrezln

nur eingeschränkt auf die TypID?

Preprocessing beim Anlegen der Klausel

bleibt dem Charakter nach imperativ

was ist der Locator?

der Level im UI ist noch offen

fast immer ist das aber UIC_VIEW

im Moment fällt mir überhaupt keine Ausnahme ein

aber man soll niemals nie sagen;

jedenfalls ist der LocationSolver komplett generisch geschrieben,

wäre ja auch dämlich, den auf einen Level festzunageln

kann man den Level erschließen?

es ist nicht klar, ob die pattern bereits das fragliche View-Element mit einschließen,

oder ob das View-Element noch angehängt werden soll. Diese Variation ist essentiell,

um Regeln auszudrücken, die explizit nur eine schon existierende UI-Komponente greifen

auto locate = matchView(

                          panel("blah")

                          or currentWindow().panel("blubb").create() )

LocatorSpec<UIC_VIEW> locate = panel("blah")

                                                     or currentWindow().panel("blubb").create()

ViewSpec locate = panel("blah")

                                or currentWindow().panel("blubb").create()

Anwendung delegiert an einen Serivce

für LocationQuery

für LocationSolver

Schreibweise für create Clauses

technische Lösug diskutierbar

...will sagen,

man kann das erheblich tief und generisch ausbauen

Perspective elided

...denn durch overwrite kann man denormalisierte Pattern erzeugen.

Also muß jeder Anwender dieser Funktion sicherstellen, daß dies

entweder nicht passieren kann, oder explizit normalise() aufrufen.

...das würde bedeuten, daß man sogar ein neues Hauptfenster erzeugt.

Also in diesem Fall würde überhaupt nichts mit dem existierenden UI matchen...

Komponente falls nötig anhängen

...weil wir keinen Zustand sammeln

und daher jede Klausel von Grund auf neu lösen.

könnte man zulassen

not empty? UND

Create ODER totalyCovered?

Komma heißt "and then" in der Logikprogrammierung

nur "hinten herum" über die verwendete LocationQuery

klassischer Fall von »premature optimisation«

wer interpretiert

UI-Koordinaten

um eine Position zu kennzeichnen

nämlich in lib::meta::func::PApply::bindBack

// generic lambda, operator() is a template with one parameter

auto vglambda = [](auto printer) {

    return [=](auto&&... ts) // generic lambda, ts is a parameter pack

    {

        printer(std::forward<decltype(ts)>(ts)...);

        return [=] { printer(ts...); }; // nullary lambda (takes no parameters)

    };

};

Vermutung: muß Lambda instantiieren...

als Subklasse von UICoord

...denn das eigentliche Problem ist,

daß ich noch keinerlei Implementierung schreiben kann.

Mithin schiebe ich mir Platzhalter von der linken in die rechte Tasche

und zwar für die abstrahierte GUI-Location

  • einmal symbolisch als UI-Koordinaten
  • einmal opaque als eingekapselte Lösung

Und sowas ist verwirrend und verlockt gradezu, die Schachtel aufzumachen

und an der Implementierung zu kleben

...was bis jetzt nicht gegeben ist!

Bis jetzt haben wir einen "Durchlauf-Erhitzer": letztlich will man nur die Referenz

auf das GUI-Element haben, und die dazwischenliegende symbolische Schicht

dient nur der Konfiguration und Lösungs-Suche.

Wenn allerdings später mal diese UILocation == bereits decodierte UI-Koordinaten

ein eigenständiges Token wird, welches über mehrere Schnittstellen hinweg geschoben wird,

dann und nur dann würde die zusätzliche API-Komplexität Sinn machen.

die verfickte Performance wird ignoriert

....jaaaaa, das ist verschwenderisch

kann dazu führen,

daß etwas Bestehendes zurückgeliefert wird

...merke

die Spezialbehandlung für const& gilt nur, wenn wir direkt auch diesen Typ nehmen.

Im vorliegenden fall wird aber der conversion-Operator aufgerufen, um den Initializser zu erzeugen.

Daher denkt der Compiler, er kann das Ursprungsobjekt jezt wergwerfen.

Spezialbehandlung

Perspektive

...um zu prüfen, ob das allgemeine Design

mit solchen Asymetrien umgehen kann,

welche ziemlich sicher noch viel mehr

bei der Navigation in einem realen GUI auftreten

...und ich hab mir letzte Woche noch solche Vorwürfe gemacht,

daß ich mich wieder mal "akademisch" verspielt habe.... :-P

...und die IterSource dann nur über WrapIter definieren.

Schichten-Prinzip...

das ist immer schon korrekt erledigt

...und das heißt.

ein Value wird auch sofort konstruiert,

egal, ob man den dann gleich wegwirft.

nur einen Satz Klauseln

...denn irgendwann wird's lächerlich mit der Unit-Testerei.

Oder zumindest Hexagonal.

Bedingt durch die ganzen rausgezogenen Interfaces hat jetzt bereits ViewLocator überhaupt keinen Gehalt mehr.

Wenn ich jetzt auch noch die einzige verbleibende Methode rausziehe, um sie testen zu können,

drehe ich mich komplett im Kreis. Schließlich kann ich diese Methode ja, genau genommen,

im Moment auch noch nicht wirklich testen, aus genau den gleichen Gründen,

warum ViewLocator so nebulös bleibt: es gibt noch kein Lumiera GUI

Das ursprüngliche Ziel für diesen Test

ist in unserem Test-Framework nicht realisierbar

Policy: Unit-Tests dürfen keine GTK-Abhängigkeit haben

  • wird mäßig häufig aufgerufen
  • beim "Öffnen" und zur Navigation
  • im Interaktions-Kontext
  • keinen Speicherdruck erzeugen

...es ist im Rahmen;

denn wir akzeptieren double dispatch sogar in der Diff-Anwendung,

welche viel häufiger läuft, als dieser View-Zugriff hier.

Allerdings, die doppelte Indirektion ist nicht grundsätzlich notwendig hier,

da wir nur einen einzigen Anwendungsfall haben. Die zweite Indirektion in jedem Aufruf

bewirkt nur eine Entkoppelung vom Implementierungs-Kontext

wir brauchen keine Token

....anders als im Diff-Framework

senden wir hier keine beliebigen Nachrichten,

sondern interpretieren jeweils nur eine einzige feste Konfiguration

Allokator pro Typ

...wir brauchen eine Repräsentation,

um auszudrücken, daß gewissen Angaben ausgelassen wurden

Alternative: wrap UI-Coord,

thin augmentation layer

alloc = unlimited

locate = panel(timeline)

alloc = onlyOne

locate = external(beamer)

               or perspective(mediaView).panel(viewer)

               or existingPanel(viewer)

               or firstWindow().panel(viewer)

alloc = limitPerWindow(2)

locate = perspective(edit).existingPanel(viewer)

               or currentWindow().existingPanel(viewer)

               or existingPanel(viewer)

               or panel(viewer)

im Asset-Panel der jeweiligen Gruppe hinzufügen

alloc = unlimited

locate = currentWindow().perspective(edit).existingPanel(asset).existingGroup()

               or perspective(asset)panel(asset)

               or firstWindow().panel(asset)

alloc = limitPerWindow(1)

locate = currentWindow().existingPanel(infobox)

               or firstWindow().panel(infobox)

Voraussetzung: Anwendbarkeit erkennen

was hier vielleicht der Fall sein könnte

wir haben nicht einfach UI-Coordinaten als DSl-Elemente,

sondern einzelne Klauseln, die allerdings jeweils eine UI-Coord wrappen.

Wenn man jedoch, rein syntaktisch zu schreiben beginnt "UICoord::window()"

dann bekommt man einen UICoord::Builder  und das ist noch keine Klausel!

Schicht unter dem ViewLocator

model::Tangible ist schön,

aber ich weiß nicht, ob das nicht zu eingeschränkt ist.

Beispielsweise werden Panel oder WorkspaceWindow ganz sicher keine Tangibles sein,

aber es könnte durchaus sein, daß man auf sie generisch zugreifen möchte

Zwei Fälle sind hier zu unterscheiden:

  1. der gewünschte Wert existiert nicht, und das ist auch das Ergebnis der Anfrage
  2. es liegt eine Fehleinschätzung der Situation vor, insofern fest mit einem Ergebnis gerechnet wurde

In Fall-1 wird man eine bool-Abfrage machen wollen, und man kann auch mit einer false-Antwort umgehen. In Fall-2 dagegen bleibt nur noch der Tod. Und davon ist im Regelfall nicht auszugehen. Im Moment sehe ich Fall-2 als den standard-use-Case

  • im Fall-1 weiß der Client, daß er prüfen muß
  • im Fall-2 marschiert der Client einfach durch

das mag überraschend klingen,

aber in der (zu erwartenden) Nutzsituation interessiert sich keine Sau dafür,

was denn nun konkret gemacht wurde, um den Dienst zu erbringen.

Die einzig interessante Information ist, ob es gelungen ist

aber genau das ist hier jeweils nur in einem Fall gegeben

Frage: wieviel Interaction Control

müssen wir sofort jetzt implementieren

brauche ein aktuelles Modell-Element

Problem: Zusammenarbeit

mit docking panels

Grundlagen für InteractionControl

wird der Link zwischen CoreService und UI-State dangling

...mit der Ausnahme des Automatismus,

der es selbst vom Bus abkoppelt

in generischer UI-Struktur bewegen

...denn das ist das vereinfachte Setup für "einfache" Applikationen

muß kein Manager sein

Abstraktion zur Steuerung schaffen

wie bestimmt?

das Diff wird auf den Platzhalter angewendet

wenn das Diff ein Element aus einer Kind-Menge wegfallen läßt,

dann muß dieses automatisch deregistriert werden

vermittelt über den ViewLocator (InteractionDirector)

Brücke: gemeinsamer Controller

sets für eine feste session::Timeline

Verwaltung autmatisch via ViewLocator -> PanelLocator

es gibt eine EmptyTimeline

Frage ist, wie viel des Verhaltens programmieren wir selber explizit aus,

und welchen Teil des Verhaltens überlassen wir GTK

Das war zwar schon meine Bauchgefühl,

habe aber sicherheitshalber diese Analyse nochmal gemacht.

Details im  TiddlyWiki....

braucht feste Speicher-Addresse

..d.h. der Controller muß wieder auf das Widget zugreifen

und sei es auch bloß über ein Interface!

aber: Binding im Diff-System durchaus möglich

...denn:

das Diff-System verlangt nicht, daß Kinder in der Collection auch Tangible sind.

Es verlangt nur

  • daß wir wissen, wie wir Kinder machen
  • daß wir für ein gegebenes Kind ein DiffMutable beschaffen können

Problem: Slave-Timeline

grundsätzliches

Problem

speziell die Umordnungen ergeben sich

...und der Dekorator würde die beobachteten Operationen

an diese Notifikations-Schnittstelle senden.

Implementiert würde sie vom jeweiligen Widget

korrekt wäre, die Diff-Verben mitzulesen.

Das geht aber nicht, weil wir intern (aktiv) iterieren.

Wollten wir das doch, müßten wir das gesamte Diff-Applikator-Design wegwerfen.

Da aber eigentlich eine 1:1-Zuordnung zwischen Diff-Verben und Operations-Primitiven besteht,

könnte man trotzdem (mit etwas Hängen und Würgen) noch hinkommen.

Der Dekorator würde also auf dem TreeMutator sitzen...

Weil wir die "skip"-Operation für zwei Zwecke verwenden,

und man im Skip nicht weiß, ob man das Element überhaupt noch anfassen darf,

denn es könnte ja auch ein von "find" zurückgelassener Müll sein.

Daher gibt es die matchSrc-Operation. Effektiv wird die aber nur bei einem Delete aufgerufen...

  • man sitzt mit dem Detektor unter dem API
  • dadurch entstehen "ungeschriebene Regeln", wie das API auzurufen ist
  • alternativ könnten wir die Operationen komplett 1:1 definieren, also eine explizite delete-Operation einführen
  • dafür würde dann die matchSrc wegfallen, was praktisch alle sinnvollen Unit-Tests stark beschränkt.

nach der Mutation erfolgt Display-Neubewertung

interagiert mit den Presentern

d.h. eine LUID

wir lassen es offen, welche Art von ID das ist.

Irgend eine BareEntryID genügt

...abstraktes Interface

latürnich

...den muß jeder individuell implementieren,

um die Bindung herzustellen

theoretisch könnte man eine Timeline ohne Sequenz

oder eine Sequenz ohne root-Fork zulassen

Thema "Darstellung von Objekt-Feldern im Diff"

da hab ich mir ausgiebig Gedanken darüber gemacht (in dieser Mindmap)

  • entweder ein Feld ist wirklich optional belegbar, dann kann es mit dem Diff kommen
  • wenn dagegen ein Feld zwingend befüllt sein soll, muß man das über den Konstruktor erzwingen
    in diesem Fall müssen alle Daten bereits mit dem vorangehenden INS kommen,
    welches den Konstruktor-Aufruf auslöst

die betreffenden Felder sind echt optional.

Der Ctor belegt sie mit einem sinnvollen Leerwert

Das Objekt muß so geschrieben werden, daß es mit den Leerwerten umgehen kann,

was typischerweise heitß, daß es verschiedene Betriebsmodi bekommt.

Das Diff kann dann später die konkreten Werte für die Attribute nachliefern;

typischerweise wird es das in einem Populationsdiff sofort als Nächstes machen.

zwei mögliche

Konsequenzen

funktioniert fast immer

"was kann denn schon passieren??"

Betriebsart "partiell initialisiert"

..hier das Widget, das ebenfalls

  • nur partiell aufgebaut existieren können muß
  • später sich dynamisch erweitern können muß
  • in der Behandlung der UI-Signale ebenfalls checks einbauen muß

einen Fall, der praktisch nie auftritt

und zwar interessanterweise über Kreuz gegliedert

  • die Ctor-Lösung (hat aber etwas mehr Umsetzungsaufwand)
  • die "wird schon klappen"-Lösung

wenn alle Objekte wirklich auf partiell initialisierten Zustand vorbereitet sind,

und auch über ihre APIs dem Nutzer diese Unterscheidnung mit aufzwingen

...welche darin besteht,

daß man überall, in der Fläche, sich um Zustandsabhöngigkeit kümmern muß,

und deshalb dazu neigt, das Problem jeweils wegzutricksen.

Es besteht also die große Gefahr, zu "sündigen" und

heimlich in den "wird schon nix passieren" Fall zu geraten.

das heißt, nur diese Lösung gründet in der Natur der behandelten Sachverhalte.

Wenn etwas seinem Wesen nach nicht optional ist, dann wird es auch nicht optional behandelt.

Es ist keine weitere Argumentation notwendig.

...nach allen gängigen Prinzipien der instrumentellen Vernunft.

KISS

YAGNI

"fokussiere Dich"

hier hab ich endlich mal die Gelegenheit, sauber zu arbeiten

hey, es ist mein Leben

...hab ich mich je anders entschieden?

wenn ich mich überhaupt entscheiden konnte...

...nochmal zusammengefaßt

  • immer wenn ein Feld seinem Wesen nach zwingend gesetzt sein muß (und aus keinem anderen Grund)
  • dann wird dies per Konstruktor so erzwungen
  • daher muß dann im Diff bereits im INS-Verb die notwendige Information transportiert werden
  • das heißt, bei der Diff-Erzeugung muß man aufpassen und an dieser Stelle bereits einen Record mit den Daten liefern

wie in Kopf und Rumpf injizieren

...sie verwenden dann ein LabelWidget zur Darstellung

Ein Clip hat verschiedene Erscheinungsformen im UI

Verwende das als Leitgedanke, um das Layout zu entwickeln

UI-Bus gilt nur für globale Belange

es geht nur um Rollen

das lokale Element muß nur als View fungieren

kann sich selbst

transformieren

...um mal was im UI anzeigen zu können

...wird zwar vom Skript ausgelesen,

aber nicht weiterverwendet.

Die Icon-Größen ergeben sich aus den Boxes auf 'plate'

Docks enthalten Component Views

...nur enabled wenn

mehr als ein top-level Fenster offen

A Gtk::UIManager constructs a user interface (menus and toolbars) from one or more UI definitions,

which reference actions from one or more action groups.

realisiert Vererbung zu fuß

...anstatt eine auf den konkreten Typ getemplatete Subklasse zu verwenden,

wird eine "CreatePanelProc" in einen PanelDescriptor eingewickelt.

Letzten Endes wird dieser dann per Match auf die Typ-ID ausgewählt.

AUA!

wie komme ich da drauf?

Ich wollte untersuchen, ob Gtk::manage( ptr ) korrekt die übergebenen Objekte aufräumt.

Wie sich nun zeigt, passiert das Aufräumen im dtor desjenigen Widget, dem das zu managende Objekt als Kind gegeben wurde.

Im vorliegenden Fall wäre das der dtor des umschließenden ScrolledWindow. Der aber wird offensichtlich nicht aufgerufen,

auch nicht im Application-Shutdown!

....erzeugt wird das hier:

dock_.add_item(timelinePanel->getDockItem(),Gdl::DOCK_BOTTOM);

Helper to build the menu and for registering and handling of user action events

es sieht so aus, als wäre es "das" WorkspaceWindow

aber es kann davon mehrere geben

erinnere mich, diverse Mechanismen gesehen zu haben,

die erlauben, eine Init-Aktion in die Loop zu schedulen

betrifft aber nur Framework-Funktionalität

also kein Initialisieren des Toolkit,

sondern Sachen wie verallgemeinerte "Files", D-Bus-Connection etc etc

  • ein Fenster als Solches braucht noch keine Event-Loop
  • ohne ein Fenster macht die event-Loop keinen Sinn

  //We cannot add and show the window until the GApplication::activate signal

  //has been emitted, or we will crash because the application has not been

  //registered. (At least if window is an ApplicationWindow.)

The signal_activate() signal is emitted on the primary instance

when an activation occurs. See g_application_activate().

g_application_activate()

suche (case insensitive) nach application_activate

  • treffer auf APPLICATION_ACTIVATE in g_application_activate()
  • die Treffer in Gtk::Application
  • diverse false positives mit anderen "Activation"-Signalen, z.B. in Aktionen oder Buttons

Main erbt nicht von Gio::Application

...für Signale, die nicht automatisch detached werden können

@deprecated: 3.4: Key snooping should not be done. Events should  be handled by widgets

wohl einzige verbleibende Verwendung

  • finde keine andere mehr, bei suche nach GtkMainConnectionNode
  • auch der Kommentar zur Klasse sagt dieses (gtk_init_add wohl nicht mehr!)

...und das ist explizit so gewünscht

  • keine Kommandozeilen-Behandlung
  • keine "Registrierung" jedweder Art
  • keine D-Bus-connection
  • kein Application-Lifecycle
  • folglich auch kein Aktivierungs-Signal

das ist ein akzidentelles Problem

denn es ist gradezu der Sinn von Glib::Dispatcher,

schon vor der Loop verfügbar zu sein (?)

nur bei laufender Event-Loop

...das ist für mich eine neue Einsicht.

Die Anzeige eines Fensters und die Event-Verarbeitung sind zunächst einmal unabhängig voneinander.

Sie sind aber aufeinander angewiesen. Die Events machen das Fenster reaktiv, aber ohne

Fenster gibt es gar keine Benutzer-Events.

aber wir brauchen ein laufendes UI

...dann kann sich das Sytem erst mal beruhigen

nach der Lastspitze zum Programmstart, für den viel Code geladen werden mußte.

Außerdem hatten dann die anderen Subsysteme schon Zeit, ihre Grunddatenstrukturen aufzubauen;

im Besonderen spekulieren wir darauf, daß die Session-Daten bereits geladen sind,

und daher der Diff direkt und kompakt in einem Durchgang emittiert werden kann

innere

Struktur

(GlobalCtx)->InteractionDirector (=Model Root)

(GlobalCtx)->InteractionDirector->Navigator

(GlobalCtx)->WindowLocator->UIComponentAccessor

(GlobalCtx)->InteractionDirector->ViewLocator

(GlobalCtx)->WindowLocator->UIComponentAccessor

man muß die Implementierungs-Details jeder einzelnen Komponente kennen,

um damit überhaupt etwas anfangen zu können. Es gibt hier keine schematische Ordnung.

Selbst die Frage, ob es sich um ein Blatt handelt, oder um einen inneren Knoten,

erfordert bereits Kenntnis der Innereien

(GlobalCtx)->WindowLocator

(GlobalCtx)->WindowLocator->PanelLocator

heißt: Element registriert sich am UI-Bus

heißt: Element deregistriert sich am UI-Bus

...ist immer ein tangible

presentation

state

vom tangible initiiert

dafür genügt der normale Reset

mark "clearMsg"

mark "clearErr"

mark "reset"

Nachricht an irgend ein Wurzel-Element

generisch

sinnvoll?

was haben alle UI-Elemente wirklich gemeinsam?

die Frage ist, wie generisch ist eigentlich ein Command-Aufruf selber?

Macht es daher Sinn, ein generisches API allgemein sichtbar zu machen,

oder handelt es sich nur um ein Implementierungsdetail der UI-Bus-Anbindung?

...wird sinnvoll im Rahmen von InteractionControl

ich wollte explizit kein generisch-introspektives UI,

weil das die Tendenz hat, sich zu einem Framework auszuwachsen.

Für die UI-Programmierung muß man Spaghetticode akzeptieren.

gemeint, eine ENUM von verschiedenen Graden der Aufgeklappt-heit

Dann mußte das allerdigns jeweils für alle Elemente sinnvoll sein

und der muß vom konkreten Widget implementiert werden

dann wird eine state mark ausgesendet

need to bubble up

support ist optional

nach Broadcast von "reset"

sollte logischerweise der PresentationStateManager leer sein

ist er aber nicht notwendig,

denn er kann Zustand von nicht mehr existierenden Elementen aufgezeichnet haben.

Nur Elemente, die im Moment angeschlossen sind, bekommen die "reset"-Nachricht mit;

sofern sie tatsächlich abweichenden Zustand haben, sollten sie sich resetten

und eine state mark "reset" zurückschicken...

....so harmlos hat alles angefangen

Denn das heißt, ich muß konkret ausarbeiten,

wie man einen Diff gegen eine opaque Implementierungs-Datenstruktur aufspielt.

Und ich muß das in einem Test zumindest emulieren können!

muß DiffApplicationStrategy

noch einmal implementieren

das mag überraschen --

ist aber im Sinne des Erfinders

  • DiffApplicationStrategy war von Anfang an als technisches Binding konzipiert
  • es ist besser, die gleiche Semantik der Sprache X-mal herunterzucoden
  • cleverer Code-re-Use zahlt sich i.d.R. nicht aus

dies setzt volle Implementierung

des Tree-Mutators voraus

der schwierigste Teil, das Mutieren von Attributen,

ist jedoch schon prototypisch implementiert

Mutator verwendet einen Binder

Diff kennt keine Zuweisung

Nein

aber was dann wenn out-of-order

eindeutig überlegen

  • faktorisiert sauber
  • Zustand delegiert auf die jeweilige Kinder-Sammlung
  • diese wird damit auch zum generischen Element

schlechter....

  • sammelt viel technische Komplexität auf top-level
  • wir müssen eine meta-Repräsentation aufbauen
  • wir müssen Adapter zentral generieren, anstatt uns vom Installieren von Closures treiben zu lassen

Primitive

(impl-ops)

of questionable use

with multiple layers

since skipSrc performs both the `del` and the `skip` verb, it can not perform the match itself...

...because it is also used to discard garbage after a findSrc operation.

Thus we need to avoid touching the actual data in the src sequence, because this might lead to SEGFAULT.

For this reason, the implementation of the `del` verb has to invoke matchSrc explicitly beforehand,

and this is the very reason `matchSrc` exists. Moreover, `matchSrc` must be written such

as to ensure to invoke the Selector before performing a local match. And skipSrc has to

proceed in precisely the same way. Thus, if the selector denies responsibility, we'll delegate

to the next lower layer in both cases, and the result and behaviour depends on this next lower layer solely

then move into target

since, on interface level, we're pretending that this mutator is a single collection like thing,

while in fact the implementation might bind to several opaque target structures.

Thus, internally we'll have a selector to determine which onion layer is responsible for

handling an element as designated by the argument. It is then the responsibility

of this specific onion layer to accept forward until meeting this element.

warning: messed-up state in case of failure

this is (probably) the only operation which entirely messes up the mutator state

when the designated target does not exist. The assumption is that a diff application front-end

will check the bool return value and throw an exception in that case

move into target

throw when

insufficent space

...in Fällen, in denen der konkrete onion-layer

überhaupt nicht im Stande ist, das zu beurteilen.

Wichtigster solcher Fall ist die Bindung auf Objekt-Felder

invoke mutateChild

NOTE: mutator need to be written in such a way

to be just discarded when done with the alterations.

That is, the mutator must not incorporate the target data, rather it is expected

to construct the new target data efficiently in place.

Mutator enthält die Bindung auf die konkreten Daten

stellt sich u.U erst während der Verarbeitung heraus:

bei "offenen Datenstrukturen" entscheided jeder Typ selber,

welchen Mutator er erzeugt

aber: Aufrufprinzip

Verb muß den

Diff bekommen

und delegiert iterativ

an die Verben

...denn in dem Moment, wo wir den top-level TreeMutator erzeugen,

können wir rekursiv abfragen, wie groß alle möglichen Kind-Mutatoren werden können

nur Zuweisung einiger Referenzen

....denn der liegt (mind) einmal vor,

eingebettet in ein Selektor-Prädikat,

welches bestimmt, ob dieses Attribut angesprochen wird

was man konventionellerweise auch macht.

Ich verstehe nun, warum. Es ist der vernünftigste Weg.

Leider scheidet das aber für uns hier genau aus,

denn das gesamte Projekt entstand, aufgrund der inhärenten Limitierungen

der "vernünftigen" (=pragmatischen) Lösung.

dieser Ansatz löst tatsächlich das Problem,

aber zu dem Preis, daß er die Strukturen von innen her zersetzt.

Auf lange Sicht wird das System wuchern wie ein Krebsgeschwühr,

und man kann das nur mit Disziplin eindämmen, was realistisch gesprochen meint,

daß es vergeblich ist. Einen Kampf gegen das Menschliche, Allzumenschliche kann man nicht gewinnen.

das ist die schlankeste Lösung, die ästhetisch befriedigt.

Sie hat aber das Problem, daß dadurch die Kollaboration im Kern ausgelöscht wird.

Wir haben eine Seite, die absolute Macht hat, und einen "Partner", der tatsächlich nur ferngesteuert ist.

Wir müssen dafür auf die Subsidiarität verzichten, und damit auf die Möglichkeit zur Entkoppelung.

Dazu kommt, daß die notwendige Fern-Wirkung stets eine zusätzliche Last bedeutet.

Denn wir müssen auf Umstände und Strukturen einwirken, die von dem Ort, an dem die

Steuerung stattfindet, entfernt ist, entfernt in einen anderen Kontext.

und nur letztere sind tangibel

um den Binde- bzw. Anknüpfungs-Punkt in den real-Daten überhaupt zu finden,

müssen IDs aus dem DOM innerhalb der real-Daten nochmal wiederholt, also redundant vorliegen

damit das DOM ein echtes DOM ist, muß es die relevanten real-Daten duplizieren,

um sie in einem abstrahierten Kontext zugänglich zu machen

noch zusätzlich zur genannten Duplikation muß

die Abblidung der Strukturen aufeinander

an irgend einer Stelle repräsentiert werden.

man kann versuchen, die beiden Elemente der Duplikation aufzulösen.

Allerdings gibt es dafür überhaupt nur zwei mögliche Richtungen.

  • man löst die Parallel-Strukturen auf
  • man ersetzt das DOM durch reine Bindungs-Strukturen

Beide Ansätze laufen aber auf eine der schon genanten, anderen Alternativen hinaus.

Wenn man die Parallel-Strukturen beseitigt, enden wir bei irgend einer Form von Fernsteuerung.

Wenn man die Modell-Natur aus dem DOM entfernt, das heißt, dort nur noch reine
Binde-Strukturen speichert, dann endet man bei einer Form von Introspektion. Entweder,

das Rückgrat und die Navigation verbleibt bei dieser Introspektion; dann haben wir eines

der typischen Objekt-Systeme. Oder die Binde-Daten werden zu einem reinen Anhang

an eine selbständig bestehende Datenstruktur; dann enden wir bei klassischer Introspektion.

reflektiert die Zahl der Struktur-Element

...will sagen:

für die habe ich bereits eine effiziente Implementierung,

die darauf beruht, den Content beiseite zu schieben.

Ich brauche also nur ein Container-Frontend (z.B. einen Vector ohne Inhalt) zusätzlich,

um den verschobenen Inhalt erst mal aufzunehmen.

Also zählen Kinder-Collections nur als ein Strukturelement.

rekursiv,

duch Bindung bestimmt

das ist der wesentliche Kniff,

durch den das Problem mit der "absrakten, opaquen" Position entschärft wird

  • Diff-Anwendung wird massiv und in der Breite stattfinden
  • sie wird als Reaktion auf UI-Events auftreten
  • sie dient dazu, andere UI-Operationen einzusparen
  • also muß speziell das Traversieren bis an den Anwendungsort bedacht werden

...d.h. die bis jetzt geschriebene TreeApplikator-Implementierung

ist erstaunlich leichtgewichtig. Zu den zwei Indirektionien der Sprache

kommt nur entweder ein weiterer aus der GenNode bzw stattedessen ein dynamic cast hinzu.

Alles andere steckt in dem expliziten Mutator-Typ

 -- das gibt einen wichtigen Hinweis --

...da wir eine verb-basierte Sprache implementieren,

also einen double-dispatch haben

weil wir den Anwendungs-Kontext noch überhaupt nicht kennen.

Man könnte also später, wenn das ganze System "steht",

das Diff-System noch einmal reimplementieren, dann mit einem vorgegebenen Diff-Typ

Beschluß: akzeptiert

im Sinn von "polymorpic value" ist das Backend virtuell

....wenngleich auch dieser aus einem Template generiert wird

(will sagen, es ist nicht sofort offensichtlich, daß wir jeweils einen Interpreter generieren)

wir verzichten auf Introspektion der Elemente

denn genau zu diesem Zweck haben wir die "External Tree Description"

...d.h,

kann zusätzlich zu einem anderen Adaptor

in die Mutator-Dekorator-Kette gehängt werden

und protokolliert somit "nebenbei" was an Anforderungen an ihm vorbeigeht

streng genommen ist es nur erlaubt, das ID-Symbol auszuwerten

Visitor bedeutet zwei Indirektionen

...und das ist nicht akzeptabel für ein reines Selektor-Prädikat!

denkbar nur bei Sub-Objekten

gilt für alle praktischen Anwendungen

....auch wenn man zehnmal meinen könnte,

Kinder eines reinen Wert-Typs wären sinnvoll --

sie sind es nicht!

Jede sinnvolle Entität hat mehr als ein Attribut!

denn es macht keinen Sinn, Entitäten und reine Wert-Elemente

auf der gleichen Ebene in der gleichen Sammlung zu mischen.

D.h., entweder man hat ein Objekt, das als Kinder z.B. eine Liste von Strings hat,

oder man hat eine Entität, die z.b. zwei getypte Objekt-Kinder-Sammlungen hat,

wie z.B: eine Spur mit Labels und Clips

"target matches spec"

aber existiert nominell und kontext-abhängig

das sind die konkreten Implementierungen

für spezifische Arten von Bindings

kann niemals geschachtelte sub-Mutatoren modellieren

ja wirklich, das wäre nicht sinnvoll!!!!!

auch wenn man meinen könnte, es geht.

Grund ist nämlich, es kann jeweils nur ein Onion-Layer für ein gegebenes Element "zuständig" sein.

Und aus Gründen der logischen Konsistenz darf dieser Diagnose-Layer niemals für ein Element zuständig sein,

denn sonst würde er es für darunter liegende Layer verschatten.

immer Mitwirkung des Elements

weil beim Assignment die Spec (=GenNode) eben

zwar die ID des Zieles, aber den neu zuzuweisenden Wert enthält.

Also wird sich das Ziel nicht anhand des neuen Wertes finden lassen,

weil es eben grade noch nicht diesen neuen Wert trägt.

generische Repräsentaton ist so gewählt,

daß sich alle relevanten Eigenschaften darstellen lassen

wenn also ein Teil der diff-Funkttionalität nicht verfügbar ist,

dann wird es wohl so sein, daß sie auch nicht gebraucht wird

zwar erscheint es nicht sonderlich sinnvoll,

als target auch eine Menge von primitiven Werten zuzulassen.

Es gibt aber auch keinen wirklichen Grund, dies zu verbieten,

sofern es gelingt, die Funktionalität gutmütig zu degradieren.

...will sagen,

da sind mehrere Layer an praktisch ungebundenem Template-Code dazwischen,

so daß zu befürchten steht, daß ein unpassendes Lambda erst weit entfernt

eine womöglich irreführende Meldung generiert

erfordert wirklich Kooperation

...denn wir verwenden hier als "private" Datenstruktur

eine etwas komische Collection von Strings,

in die wir die String-Repräsentation der Spec-Payload schreiben.

In der Praxis dagegen würde man wirklich einen privaten Datentyp verwenden,

und dann auch voraussetzen, daß man nur Kinder dieses Typs (oder zuweisungskompatibel) bekommt.

Mein Poblem hier ist, daß ich in dieser Demonstrations-Datenstruktur keine nested scopes repräsentieren kann.

Aber hey!, es ist meine private Datenstruktur -- also kann ich einfach eine Map von nested scopes

daneben auf die grüne Wiese stellen. Ist ja nur ein Test :-D

...dankenswerterweise hat der subscript-Operator von std::Map

die nette Eigenschaft, beim ersten Zugriff auf einen neuen Key

dessen payload per default-konstruktor zu erzeugen.

der Builder in der nested DSL generiert einen sonderbar falschen "this"-Typ,

genauer gesagt, eine TYPID die falsch ist.

Und zwar kommt es da zum "Übersprechen" von einem Typ-Parameter in den anderen.

Im Besonderen hab ich beobachtet, daß, wenn man auf den 3.Typparameter ein Lambda gibt,

dann auf dem 4. oder 5. Typparameter der bisherige /alte Typ des 3.Typparameters auftaucht,

u.U auch eingeschachtelt als ein Argument.

Habe mich aber davon überzeugt, daß die eigentlichen Typ-Parameter in Ordnung sind.

Und zwar habe ich das verifiziert

  • durch Ausgeben der Typen im Konstruktor (mithilfe meiner typeStr<TY>()
  • durch Einbauen einer Static-Assertion mit Signatur-Match

gemeint ist:

die native Datenstruktur ist eine Collection von Elementen,

welche ohne Weiteres direkt in eine GenNode gepackt werden könnten. Denn dann läßt

sich eine einfache Default-Implementierung des Matchers angeben

Typisches Beispiel: eine STL-Collection von Strings.

wir integrieren Attribute nicht, weil es so schön symmetrisch ist,

sondern weil sie essentiell zum Wesen von Objekten gehören.

Wenn wir Änderungen an Objekt-Strukturen als Diff erfassen wollen,

dann müssen Attribute irgendwie sinnvoll integriert sein

immer in der Klasse verankert

⟹ es geht eigentlich nur um den Wert des Attributes

manche Felder sind optional

unter der Maßgabe,

wie ETD ein Objekt repäsentiert

"Anwendung" : meint das Anwenden eines Diffs auf ein Ziel-Objekt

"nicht nutzen" : meint ignorieren und verwerfen der Information

meint: ETD -> Objekt und dann später Objekt -> ETD

warum?

Weil sich in der ETD die Reihenfolge ändern kann,

und aber das Aufspielen eines Diffs auf beiden Seiten

zwingend die gleiche Reihenfolge erfordert!

Objekt -> ETD -> Objekt

warum?

weil das Quellobjekt keinen Diff erzeugen wird,

der sich letztlich nicht auf das Zielobjekt aufspielen läßt

abweisen, was das Kriterium sicher verletzt

mandatory : Wert muß per Konstruktor gegeben sein

default : es gibt einen ausgezeichneten Standardwert

das heißt, in dem ins-Verb ist dann ein komplettes Objekt enthalten,

nicht nur eine leere Record-Hülle, die nachfolgend populiert werden kann (aber nicht muß)

Konstruktor befüllt das Feld halt irgendwie.

Ab dem Punkt verhält es sich aber wie ein normales (mandatory) Feld

das Objekt selber kann erkennen, ob das Feld sich im "default-Zustand" befindet

ohne Prüfen ist emptySrc nicht implementierbar

...weil es für emptySrc keine neutrale Antwort gibt.

Denn dieses Prädikat wird von der typischen Implementierung des Diff-Applikators

in beiden Richtungen verwendet, also sowohl Prüfung auf empty ("expect no further elements"),

alsauch der Check, daß überhaupt noch Quellelemente anstehen

d.h., man kann nur global auf Prüfung verzichten 

und da habe ich mich bereits dagegen entschieden

Feld unterstützt default-Wert

Auslegung der

Primitiven

rationale: object fields are hard wired,

thus always available

Einschränkung: accept_until END

...nämlich indem alle Attribute als "berührt" und akzeptiert markiert werden.

Somit könnten sofort Zuweisungen als Nächstes passieren

analog wie assignElm

das heißt, es findet keine Verifikation statt

zu bindende

Operationen

sieht nach Ober-engineering aus,

zumal das erhebliche Statefulness bewirkt

unterstelle Ziel als konstruierbar aus Payload

da effektiv bereits der Setter diese Funktionalität enthalten kann und muß,

denn der Setter nimmt eine GenNode

injectNew tolerieren

....man könnte genausogut auch beim ersten Mal zuweisen

denn die Diff-Anwendung auf GenNode unterstützt Zuweisung

ausschließlich bei schon existierenden Elementen. Demnach muß dort auch jedes Attribut

  • entweder schon mit dem Konstruktor mit gegeben worden sein
  • oder vorher einmal explizit eingefügt

...denn wir vermeiden dadurch Komplexität.

Der gesendete Diff muß einfach passen!

Genau deshalb haben wir auch in GenNode verschiedene Varianten des gleichen Grundtyps,

damit wir nicht in die ganzen Ungewissheiten der widening conversions laufen!

d.h. der Attributwert hat Wertsemantik und wird einfach zugewiesen

...d.h. der Attributwert ist ein Objekt und damit ein nested Scope

Problem: immutable values

das alles passiert dann im Lambda

Dilemma: defaultable fields

....mit der ETD,

bzw der Anwendung des selben Diffs auf eine GenNode-Struktur.

Konsequenz: wenn ein feld defaulted war, und nun explizit gesetzt wird,

muß dies als INS geschehen, denn eine Zuweisung an nicht aufgeführtes Element ist verboten

folglich ein Problem,

zu erkennen, wenn wir fertig sind

....weil das defaultable field noch nicht vom Diff berührt wurde.

Aber es ist kein optional field, d.h. wir haben keine Flag, die es als "defaulted" kennzeichnet

Lösung: alles immer explizit

diese Lösung war zunächst mein Favorit.

Sie erscheint sehr elegant, weil man im TreeMutator überhaupt nichts dafür tun muß.

Und die Zusatz-Forderung, daß dann eben das Diff richtig gesendet werden muß,

erscheint "geschenkt", da wir ohnehin zunächst einmal die Diffs explizit im Code erzeugen.

Aber, nach längerer Überlegung wurde mir der Ansatz mehr und mehr zweifelhaft.

Das ist die Art von Verkoppelungen, hier die implizite Annahme einer bestimmten Implementierung,

die ein System unerklärbar und schwer wartbar machen. Das ist die Art von "Features",

für die man sich nach einiger Zeit entschuldigen muß.

Und noch schlimmer: eigentlich läuft dieser Ansatz darauf hinaus, die Konsistenzprüfung

am Ende zu deaktivieren. Nur wir machen das nicht explizit, sondern durch die Hintertür.

Also dann besser klar und ehrlich!

...denn unter dem Strich würden wir hiermit volle Unterstützung für opitonale Attribute einführen,

also eine Attribut-Semantik auf eine Feld-Semantik draufpflanzen.

Aber in der vorausgegangenen Analyse habe ich mich schon davon überzeugt,

daß wir keine Attribut-Semantik brauchen. Und wenn doch, dann bietet das Diff-System

immer noch die Möglichkeit, die Attribute explizit als Sammlung darzustellen.

auf die empty-Prüfung am Ende verzichten

denn in den meisten, wichtigsten Fällen get es um einen non-empty-check,

bevor ein anderes Verifikations-Prädikat angewendet wird.

jedwede "bessere" Implementierung muß zwingend einen Container verwenden,

der dann die Lambdas für die einzelnen Setter auf den Heap legt.

Das ist hier tatsächlich viel schlechter, als das bischen lineare Suche

....durch meinen allerersten Draft,

für den ich damals gezwungen war, die GenNode zu erfinden :)

gleiches Argument...

...damit unterstellen wir, daß später eine Symbol-Tabelle aufgebaut wird.

Dann kann man sich immer noch überlegen, ob man dann an dieser Stelle bereinigt

in einem Fall kann man sie aus der Closure abgreifen

im anderen Fall muß es doch der Client leisten.

Keine klare Linie

...das heißt, es gibt nur minimale, themantische Überlappung.

Also ist die Verwendung von Vererbung hier sogar die beste Lösung

Geschachtelte Typdefs lassen sich vermeiden:

BareEntryID speichern!

...das heißt, wie rum man es auch auflöst, wird die Lösung auf einer Seite schlechter

  • wenn wir für den Payload-Typ einen Typ-Parameter nehmen, blähen wir den Standard-Fall (Setter) auf
  • andererseits ist es unbstreitbar einfach so, daß für den Mutator-Builder die Typisierung komplett implizit ist, das muß die Closure mit sich selbst ausmachen, einfach indem in der Closure ein geschachtelter TreeMutator konstruiert wird, der eben mit diesem impliziten Kind-Typ umgehen kann.
  • wenn wir stattdessen nur einen Key-String speichern, wird zum Einen die Match-Prüfung aufwendiger (Stringvergleich statt Vergleich von Hashes), und außerdem wird ein Typ-Mismatch nicht mehr auf der Ebene der Verb-Anwendung entdeckt und entsprechend gekennzeichnet, sondern wir hoffen, daß es dann innerhalb der Closure zu einem Fehlzugriff auf die Payload der GenNode kommt. Noch schlimmer im Mutator-Fall, da sind wir dann schon im geschachtelten Scope und hoffen, daß dann der eingeschachtelte Mutator irgendwo auf Widerspruch läuft.

...gedacht für verschiedene UseCases.

  • Fall 1: String-Key und der Typ muß irgendwie implizit/explizit gegeben sein
  • Fall2: GenNodeID

...die offensichtlichsten Dinge übersieht man nur zu leicht!!!!!

Da es ein nested scope ist, ist es immer ein Objekt,

also repräsentiert als Rec<GenNode>

zwei Bindings

zwei Collection-Bindings

...diese Abkürzung ist nur auf den Konstruktur aufgepflanzt,

nicht aber in der eigentlichen Implementierung verankert.

Das wollte ich nicht, weil ich längerfristig doch davon ausgehe,

daß es einfach einen Metadaten-Scope gibt

Die Inkonsequenz nun ist, daß im Rec::Mutator keine Magie dafür vorgesehen ist

...mit den Lambdas kann ich nur die Sicht auf die Werte steuern,

nicht aber das eigentliche Verhalten des Bindings.

Denn die Lambdas haben keinen Zugriff auf die Ziel-Datenstruktur!

...wir wollen mehrfach geschichtete TreeMutator-Subklassen,

aber tatsächlich liefert jeder DSL-Aufruf einen Builder<TreeMutator<...>>.

Die normalen DSL-Aufrufe sind eben genau so gestrickt, daß jeweils der oberste Builder entfernt wird,

ein neuer Layer darübergebaut und das Ganze wieder in einen Builder eingewickelt wird.

Dadurch ist es schwer bis unmöglich (wg. den Lambdas), den resultierenden Typ anzuschreiben.

Daher bin ich zwingend auf Wrapper-Funktionen angewiesen, die diesen resultierenden Typ

vom konkreten Aufruf wieder "abgreifen". Ich kann daher nicht die DSL-Notation verwenden,

um den Dekorator für die Behandlung des Typ-Feldes einzubringen.

Mut -> Rekursion

Problem: partielle Ordnung

...das heißt,

das AFTER-Verb wird übersetzt in ein skip_until,

und das läuft dann entweder in jedem Layer

oder nur in dem Layer, der auf die Spec paßt.

In jedem Fall gerät dadurch die relative Verzahnung der Elemente untereinander aus dem Takt

...das heißt also, es wird stets der zuerst gebundene Layer komplett durchgespult,

gefolgt dann von dem nächsten Layer.

Die Konsequenz ist, daß es keine Mischung der Typen geben kann.

Es müssen immer zwingend alle Elemente eines Typs von einem Layer behandelt werden

und diese Elemente müssen geschlossen hintereinander in der Reihenfolge liegen

auf Basis des neu geschaffenen TreeMutators

....man könnte später geeignete Automatismen schaffen,

die sich diesen TreeMutator beschaffen

  • indem erkannt wird, daß das eigentliche Zielobjekt ein bestimmtes API bietet
  • indem andere relevante Eigenschaften des Zielobjekts erkannt werden

...das so häufig in C++ auftretende Problem:

wie baue und verwalte ich eine konkrete Implementierung,

ohne gleich ein ganzes Management-Framework einführen zu müssen.

Letzten Endes lief  das auch in diesem Fall auf inline-Storage hinaus...

....daß ein unbedarfter client diesen Trick übershieht

und daher den Rückgabewert wegwirft.

Argument: we soweit einsteigt, die Metaprogramming-Lösung zu nutzen,

sollte auch intelligent genug sein, die API-Doc zu lesen.

Standard == Interface DiffMutable implementieren

Client soll direkt mutatorBinding bieten

nicht generisch: mutatorBinding

Lösungsversuch: extern template

...im Klartext: diesen Zugriff von der generischen Implementierung

auf den eingebauten Stack-Mechanismus benötigen wir nur...

  • einmal zu Beginn, bei der Konstruktion
  • wenn wir in einen geschachtelten Scope eintreten
  • wenn wir einen Solchen verlassen

Zwar sind indirekte Calls aufwendiger, aber letzten Endes auch wieder nicht soooo aufwendig,

daß sie uns im gegebenen Kontext umbringen...

intern: eingebaute initDiffApplication()

...wird automatisch vor Konsumieren eines Diff aufgerufen

Widerspruch: TreeMutator ist Wegwerf-Objekt

Lösungsversuch: doppelte Hülle

kann daher TreeMutator konstruieren

...und zwar per mutatorBinding

implementiert somit initDiffApplication()

TODO: Namensgebung

TreeMutator-Binding muß opaque bleiben

Buffer-Größen-Management vorsehen

das heißt

  • ein sinnvoller Startwert wird heuristisch vorgegeben
  • wenn die Allokation scheitert, die Exception fangen und die tatsächlich benötigte Größe merken

...das heißt:

gegeben ein syntaktisch sinnvoller top-level-Aufruf ("wende das Diff an")

-- wie bzw. von wem bekommen wir dann ein Binding, das einen passenden TreeMutator konstruiert?

erscheint mir die am wenigsten überraschende Lösung.

und zwar per handle.get()

erscheint mir fehleranfällig und irreführend für den Nutzer der Schnittstelle.

Denn er muß zwar das Objekt in das Handle platzieren, dann aber auch noch einen Pointer zurückgeben,

der dann auch noch NULL sein kann, zum Signalisieren von Fehlern.

Ich empfinde das als schlechten Stil

naja, das wäre billig, aber auch wieder beliebig.

Es macht keinen Sinn vom API-Design her, sondern man müßte es halt machen,

weil die Implementierung den Zeiger auf den geschachtelen sub-Mutator umsetzen muß.

die Diff-Sprache verlangt,

daß vor dem Öffnen des geschachtelten Scopes

dieser zumindest einmal per ins "angelegt" wurde.

...das ist ein Versuch, den Code für den Leser verständlich zu halten.

Die Idee ist, daß es einen high-level Unit-Test gibt, der die gesamte Diff-Anwendung durchspielt

und dazu passend einen low-level Unit-Test, der analog die gleichen Operationen macht,

allerdings direkt auf dem TreeDiff-Interface durch Aufruf der passenden Primitiv-Operaionen.

Letztere müssen für jede Art von "onion-layer" (konkretes Binding) erneut implementiert

und daher auch jeweils eigens per Unit-Test abgedeckt werden.

das ist hier sinnvoll. Das Binding sollte komplexer sein,

als in der Praxis auftretende Bindings. Warum? Weil letztere immer etwas einseitg sind

und damit Abkürzungen im Code-Pfad ausnützen. Die Gefahr schlummert aber im Zusammenspiel

der konkreten Bindings mit mehreren "onion layers"!

...denn es ist sehr verwirrend, welche Signatur denn nun die Lambdas haben müssen

...denn es kann keinen Default-Matcher geben....

...sonst wird niemand Lambdas bereitstellen können, oder gar Diff-Nachrichten erzeugen.

Das ist nun kein spezielles Problem der gewählten Implementierungs-Technik, sondern rührt daher,

daß der Client hier eigentlich ein Protokoll implementieren muß.

wenn überhaupt, dann im Matcher im Binding-Layer implementieren

...denn wir haben nun mehrere Layer,

und der Selector kann einfach anhand von Ref::THIS keine sinnvolle Entscheidung treffen.

Daher versuchen dann alle Layer dieses Element zu behandeln, oder gar keiner

Und da der Selector nur die Spec anschauen darf, läßt sich das auch nachher nicht mehr korrigieren

Daher habe ich mich entschlossen, dieses Sprachkonstrukt zu entfernen

entfernt, da schlechtes Design

entfernt, da schlechtes Design

anscheinend nicht notwendig

  • diese wird im Nexus behandelt, in dem die Tangible::mark()-Methode aktiviert wird
  • in dieser wiederum steckt eine Default-Handler-Sequenz, plus ein Strategy-Pattern

Marker-Typ MutationMessage

....denn dann müßte der Benutzer die Mechanik sehr genau verstehen, und stets eine auto-Variable definieren.

Sinnvoll wäre dieser Ansatz nur, wenn das UI-Bus-API eine MutationMessage const& nehmen würde,

denn dann könnte man den Builder-Aufruf inline schreiben.

Da wir aber stets den Diff moven und dann iterieren, scheidet const& aus

Und für eine reine Ref erzeugt C++ niemals eine anonyme Instanz!

...und diesen mit VTable bestücken.

Dafür wird die äußere Hülle non-virtual

und kann noncopyable gemacht werden..

Das erlaubt dem Benutzer, einfach zu schreiben

MutationMessage(blaBlubb())

Abstraktion

nach beiden Setien

...aus gutem Grund!

Der Nexus speichert nämlich eine direkte Referenz in der Routingtabelle

  • Map hat kein emplace_back
  • Map hat kein back()

Beides ist erst mal sinnvoll. Map hat zwar ein emplace, aber das fügt eben irgendwo ein

Und es gibt nicht sowas wie das "zuletzt behandelte" Element

Reihenfolge

erhalten!

...hat eine "zufällige" Reihenfolge, die von den Hash-Werten der gespeicherten Daten abhängt.

Das bricht mit unserem grundsätzlichen Konzept der kongruenten  Daten-Strukturen

Ein Diff, das von einer ETD gezogen wurde,

läßt sich nicht auf eine Map-Implementierung aufspielen

Entscheidung

...zum Beispiel wie grade hier, beim MockElm

das wird vermutlich niemals wirklich in einem vollen Diff-Zusammenhang gebraucht.

Und dann ist unbestreitbar eine Map eine sehr einfache Implementierung

und auch im Diff-Applikator nicht wirklich schwierig zu unterstützen

Problem: InteractionControl

...was andernfalles komplett vermeidbar wäre,

da im Übrigen das UI-Modell nur mit LUIDs und generischen Namen arbeitet

Idee: context-bound

Command und Verhaltensmuster

bleiben zusammen

...was ich einen Monat später schon wieder vergessen hatte...

hier geht es darum, eine Regel zu generieren,

die dann den zugrundeliegenden Command-Prototyp automatisch mit konkreten Aufrufparametern binden kann,

sobald bestimmte Umstände im UI einschlägig werden

Das ist ein erweiterter / komplexerer Anwendungsfall.

Der einfache Standard-Anwendungsfall ist, direkt die Command-ID zu senden

das reicht für die erste Integrationsrunde völlig aus

Instanz-Management ist automatisch

Focus/Spot wird mitbewegt

act, note: Nachricht upstream

mark: Nachricht downstream

Bus-Design is selbstähnlich

Kennzeichen ist die EntryID des zugehörigen Elements

Die Lösung für diese wecheslseitige Abhängigkeit

ist, den Nexus als Member im CoreService zu haben,

weil man dann seine Addresse schon weiß, bevor er erzeugt ist.

Dummerweise rettet mich dieser Trick nicht im Shutdown,

denn hier nun läuft tatsächlicher Code aus dem Destruktor heraus!

bei einem echten Downstream könnte man dafür sorgen,

daß er grundsätzlich vor dem Nexus weggeht. Aber nun kommt, auf dem Umweg

über den Core-Service, der Nexus nach dem Nexus....

ich will nicht damit anfangen, daß man einen Zeiger umsetzen kann....

beendet Deregistrierung,

wenn ein BusTerm sich selbst deregistriert

Mechanismus, der es erlaubt

  • log-Nachrichten aus Mocks zu hinterlassen
  • in der Test-Fixture auf diese zu matchen

Beispiel: Aktionen, die im globalen Menü stehen.

"Add Sequence"

  • wer bildet daraus ein Command?
  • auf welchen Kontext bezieht sich das
  • wen kann die Menü-Registrierung konkret ansprechen (Verdrahtung ist statisch)

konzeptionell: fertig

Implementierung der real-world-Variante fehlt!

Dienste im UI, erreichbar über den Bus.

Sie stellen die Verbindung zu zentralen Belangen her

wie Session- und State-Managment, Commands etc.

Compiler-Bug Gcc-#63723

gelöst in GCC-5 -- backport unwahrscheinlich

eine virtuelle Funktion

pro möglichem Umwandlungs-Pfad

wir verwenden die Basis-VTable

und layern nur die tatsächlich möglichen Umwandlungen drüber

empfängt alle state mark notificatons

nach Perspektive

nach work site

einer könnte für

mehrere Commands zuständig sein

mehrere könnten für

ein Command zuständig sein

....denn wir wollen ja grade

den Widget-Code vom Control-System abstrahieren

und ebenso die Gesten abstrahieren

muß Instanzen einsetzen

...und zwar zwingend, sobald

  • das command Argumente hat, die gebunden sein wollen
  • mehrere Invocations des gleichen Grund-Commands "gleichzeitig" unterwegs sein könnten

...vom Linken her nicht, da wir Gui gegen Proc linken

vom Bauen auch nicht, und außerdem...

...coden wir ja nicht gegen die Implementierung,

sondern gegen eine Abstraction (Command), die eigens dafür geschaffen wurde

...wegen

  • Command-Message via UI-Bus
  • Durchreichen durch das Interface-System

...da die DispatcherQueue direkt Command-Objekte (=frontend handle) speichert

...denn das GUI läuft ja synchron.

D.h. wir wissen, wenn wir das Air-Gap überstanden haben.

Ab diesem Punkt hält der Eintrag in der DispatcherQueue das Command am Leben,

und wenn es stirbt, dann stirbt es halt...

aufruf direkt mit Command-ID -> erzeugt automatisch eine Klon-Kopie

GUI: CmdAccessor

Proc: CmdInstanceManager

es könnte z.B. sein, daß man vom InteractionState

direkt einen Record<GenNode> bezieht, und bei diesem Zugriff

automatisch die Kontext-Accessor-Ausdrücke ausgewertet werden

...da dies ein pervasiv genutzter Service ist,

und wir nicht wollen, daß jedes Widget

mit dem InteractionDirector verdrahtet sein muß!

hat überhaupt nichts mit dem Zugang zu Commands zu tun,

und auch nichts mit der Trennung zwischen Layern und Subsystemen

es geht um Service-Dependencies

aka DependencyInjection + Lifecycle Management

  • man hat ein statisches Front-End, d.h. by-name access
  • hinter dem liegt eine Factory
  • die Instanz kann von innen her wieder geschlossen werden
  • wenn geschlossen, dann Fehler werfen

...nicht klar, ob das notwendig (und gut) ist

es könnte auch ausreichen, einfach die passende InteractionStateManager-Impl zu verwenden

denn InteractionStateManager ist ein Interface!

...das UI weiß,

wer das konkret immer sein wird.

D.h. beim Start des UI wird eine Verbindung irgendwo hinterlegt

Das könnte ein Advice sein

vom Command her ist der Typ festgelegt

auf das "aktuelle Element" wir eine Art Typ-Match gemacht.

Wenn der paßt, kann das aktuelle Element verwendet werden.

In diesem Fall wird das Command enabled

eine Argumentliste mit mehreren Parametern wir Schritt für Schritt geschlossen

wenn mehrere Objekte als Argumente in Frage kommen,

wird das gemäß Scope "nächstgelegne" genommen

die Instanz kommt nicht in der Fixture-Queue an

der Umstand, daß Commands auch ausgeführt werdern können,

gehört nicht zum Thema "Instanz-Management"

...denn ein Command geht dann in die Queue

und kann noch ausgeführt werden, während ein weiteres

schon "in der Mache ist"

...aus gutem Grund

(kann mich erinnern, daß ich mir das überlegt hatte).

Sofern Definitionen wirklich concurrent geändert oder gelöscht werden,

könnte es sein, daß jemand auf einer stale reference arbeitet,

denn das Lock schützt nur den Aufruf innerhalb der CommandRegistry.

Sicher ist der Zugriff nur, wenn im Schutzbereich dieses Locks ein

neues Command-Objekt kopiert wird. Was allerdings den RefCount erhöht.

aber sich mit einem Refcount verrückt machen.....

....künftige Weiterung:

auch in EntryID könnte ein Symbol-Stecken,

mithin in der GenNode::ID

da dieser Zugriff wirklich für jedes Command passiert,

möchte ich mit dem Minimum an Hashtable-Zugriffen auskommen.

Daher prüfen wir als erstes den CommandInstanceManager,

da dies der Regelfall ist. Wenn dies scheitert, suchen wir noch

in der globalen Registry

d.h. wir müßten dann auch noch das Interface brechen

und die Form der ID-Dokoration zur Konvention machen

das heißt, für das ganze Thema InteractionControl

schwebt mir eine Zwischenschicht unabhängig von den Widgets vor

Wenn nun aber das Anfordern einer neuen Instanz über den Bus laufen soll,

dann würde es wohl ehr direkt von den Tangibles (Widget / Controller) ausgehen.

Das wollte ich genau nicht

Tangible sollte InteractionState verwenden

....und demnach sollte InteractionState eben grade nicht von Tangible wissen

Demnach müßte sich InteractionState irgendwo "hinten rum" an den Bus ranmachen,

z.B. über den InteractionDirector. Das ist aber nun wirklich absurrd,

da es letztlich nur darum geht ein ohnehin öffentliches  Interface aufzurufen

...sonst wird die ganze Sache absurd

und unsinnigerweise aufwendig

Instanz öffnen

CommandID und Argumente gegeben

SessionCommandService::trigger

SessionCommandService::bindArg

SessionCommandService::invoke

managed diese Komponente nicht

aufwendiges Nebenthema

...das war der erste Entwurf

  • overengineered
  • am Bedarf vorbei

Symbol ADD_CLIP = CmdAccess::to (cmd::scope_addClip, INTO_FORK);

prepareCommand (cmdAccess(ADD_CLIP).bind (scope(HERE), element(RECENT)))

issueCommand (cmdAccess(ADD_CLIP).execute());

die DSL muß so konstruiert werden,

daß die Syntax-Elemente nahtlos simplifiziert werden können,

in eine Form, die sich unmittelbar jetzt implementieren läßt

und mit einfachen, direkt gegebenen Objekten

...stattdessen einen Fehler-Indikator auslösen

(Beispiel "in-point fehlt")

...das ist eine Reaktion,

die von einem managing Ui-Element ausgeführt wird,

aber von einem externen State-Change getriggert wird

invocationTral wurde aufgegeben.

Insofern löst sich dieser Knoten langsam

allerdings, wenn man eine explizite Instanz-ID angibt,

bleibt es bei der stringenten Fehlerbehandlung

das heißt, es geht um die Haupt-Registry für Commands.

Wenn wir eine Instanz machen, um Parameter zu binden und sie dann schließlich auszuführen,

könnte man dieser Instanz einen Namen geben, und sie in die Haupt-Registry eintragen..

Oder man könnte sie anonym verarbeiten, weil Command selber ein smart-Handle ist.

...ist jetzt geklärt.

InteractionState == Kontext

CommandID.KontextID == Instanz

...wenn es doch offenbar für den "fire-and-forget"-Fall

genauso gut möglich ist, über eine zentrale Stelle zu triggern.

Nebenläufigkeit ist kein Argument (da das UI single-threaded läuft)

aber: Parametrisierung könnte partiell sein

sie wird nicht zum Parameter-Sammeln verwendet

...eine Instanz wird dann erzeugt, wenn sie notwendig wird.

Sie kann vom UI-Command-Framework erzeugt werden,

sie wird automatisch erzeugt, sofern Parameter gebunden werden,

oder ein Command an den Dispatcher übergeben...

grundlegender Widerspruch

zwischen Command-Control-Interface

und Messaging

...und kaum erkennbarer Nutzen.

Der einzige Nutzfall wäre ein "this"-Parameter.

Den kann man aber mit geeigneter Syntax auch direkt angeben

hier müßte der InvocationTrail die aufgesammelten Argumente transportieren.

allein dafür genügt eine GenNode

...weil es zu jedem InvocationPath

zu jeder Zeit nur eine "offene" Instanz gibt.

Also genügt es, einen anonymen Klon dieser Instanz zu halten

gemeint ist:

Ein UI-Control wird aktivierbar, weil das zugrundeliegende Command

alle seine Argumente aus dem aktuellen Kontext befriedigen kann

Beispiel: Menü-Eintrag "create duplicate"

die Idee ist hier,

daß diese generischen Rollen bereits in der Einrichtung der Command-Definition verwendet werden.

Das heißt, für einen bestimmten Invocation-Trail legt man fest,

daß ein bestimmtes Argument an eine gewisse Rolle gebunden wird,

oder andernfalls einen bestimmten Namen bekommt

...eben!

Diese Frage hat dann dazu geführt,

daß ich das ganze Konzept "InvocationTrail"

wieder komplett zurückgebaut habe

vermutlich läuft es immer darauf hinaus

  • daß cmd.hpp die Implementierungs-Einheiten includiert
  • oder daß in einer ausgezeichneten Impl-Einheit das marker-Makro gesetzt wird und dann cmd.hpp includiert wird

...also ist eine Instanz durchaus noch am Leben,

während bereits die nächste Instanz für das GUI ausgegeben wurde.

...damit die Nummer erhalten bleibt

...implementiert "für die Zukunft",

wenn wir context-bound -Commands verwenden

das ist ein grundlegender Beschluß.

InteractionControl ist eine eigene Schicht;

deshalb ist auch der UI-Bus nicht das Universal-Interface schlechthin

d.h. der usage context entscheidet, ob wir einen Wert,

eine Referenz oder einen konstanten Wert verwenden

Record selber ist immuable

aber hat eine Builder-Mechanik

eigentlich fehlte nur die get()-Operation

erledigt... ähm vertagt

aber nicht wirklich; der workaround könnte schon die Lösung sein #963

ich hatte damals beim Variant und zugehörigen Buffer die Sorge,

daß ich die Implikationen einer generischen Lösung nicht durchdringen kann.

Und ich wollte keine Zeit auf einen exzessiven Unit-Test verwenden

generische Lösung verschoben #963

C++11 erlaubt =default

nicht klar, ob wir das überhaupt brauchen

  • entweder nur die unmittelbaren Kinder -> komplexe Logik fällt auf den Client
  • oder nur die Blätter -> man kann die Baum-Struktur nicht wirklich nutzen

Entscheidung

was wir brauchen

geht nicht:

rekursiver Abstieg in der Mitte eines Iterators

das war die Quintessenz der ganzen Entwicklung zum IterExplorer

Nachdem ich die depth-first / breadth-first -Strategien systematisch aufgebaut hatte,

habe ich das dann reduziert und kompakt nochmal geschrieben.

Sehr schön!

übrigens: genau den verwenden wir auch zur Job-Planung

...denn wir müssen den Weg zurück finden.

Wenn also eine Datenstruktur nur einfach verzeigert ist, oder direkt rekursiv (wie bei uns),

dann ist es absolut unmöglich, eine Traversierung mit konstantem Speicher zu machen.

Das geht nur bei einer Struktur mit Rückreferenzen -- diese enthalten dann nämlich genau den Speicher,

der während dem Einstieg in die einfach verzeigerte Struktur auf dem Stack liegt. Aber letztere

braucht nur eine logarithmische Menge an Speicher, und das auch nur während der Traversierung.

Dies ist die Abwägung, und darunter läßt sich nichts weghandeln.

Der einzige verbleibende Freiheitsgrad ist, bei einer unmittelbaren rekursiven Programmierung

direkt den Prozessor-Stack für die Speicherung des Rückweges mitzuverwenden;

in dem Moment, wo ich mich für einen Iterator entscheide, ist diese Möglichkeit weg.

kann genauso effizient werden

aber nur, wenn man die Initialisierung hinbekommt

oder diese Logik

fest verdrahten

da es sich um einen disjunktiven Typ (entweder-oder-Typ) handelt,

könnte man die Storage mit beiden Bedeutungen überlagern.

Voraussetzung wäre, daß man anhand der konkreten Daten gefahrlos  jeweils herausfinden kann,

welcher Zweig grade gilt. Da wir aber keine Introspektion haben (und auch nicht wollen!),

würde das auf Taschenspielertricks mit der Implementierung hinauslaufen

  • GenNode und Record beginnen beide fraktisch mit einem String. Man müßte diesen interpretieren können
  • oder man nutzt die letzten Bits des Pointers, um sich dort eine Flag zu speichern...

Damit ist schon klar: sowas macht man nicht ohne Grund

Entscheidung: falls eingebetteter Record

Begründung: das Durchlaufen und Rekonstruieren eines Baumes

ist letztlich doch ein sehr spezieller Fall, und rechtfertigt nicht,

den HierarchyOrientationIndicator in jeden Iterator einzubetten.

Zumal -- wenn der level zugänglich ist -- kann man diese Mechanik genauso gut

dort direkt ansiedeln, wo sie gebraucht wird.

also keine Monade

Gleichheit

kombiniert den Wert-Match mit der Iteration

Zweck: kompaktes Anschreiben

von literalen Daten

Object builder

Problem ist, wir definieren den Typ Record generisch,

verwenden dann aber nur die Spezialisierung Record<GenNode>

Und die Builder-Funktionen brauchen eigentlich spezielles Wissen über den zu konstruierenden Zieltyp

Mutator selber is noncopyable

Ergebnis move

pro / contra

Move ist gefährlich

aber auch deutlich effizienter,

denn wir müssen sonst das ganze erzeugte Ergebnis einmal kopieren.

Nicht sicher, ob der Optimiser das hinbekommt

nur auf dem Mutator

dieser ist nicht kopierbar

und muß dediziert erstellt werden

möglicherweise schon gelöst,

denn Record ist insgesamt immutable.

Also können wir einen Find mit einem const_iterator machen

was sinnvoll ist,

hängt vom Payload-Typ ab

bei einer 'key = value' -Syntax mit strings

ist nur ein Value-Rückgabewert sinnvoll

...auch kann man auf diesem Weg die Storage konfigurierbar machten

da wir einen IterAdapter verwenden, können wir nur eine 'pos' (einen Quell-Iterator)

als Zustands-Markierung verwenden; die gleiche 'pos' wird aber auch inkrementiert und dereferenziert.

Daher ist die einzige praktikable Lösung, daß die Typ-ID in einem weiteren Vektor gespeichert wird.

Das könnte dann ein Metadaten-Vektor sein.

Natürlich ist dieser Ansatz nur sinnvoll, wenn wir wirklich Metadaten brauchen.

Denn jeder Record zahlt den Preis für die komplexere (zusätzliche) Datenstruktur!

scheidet aus, wegen Wertsemantik

mit speziellem Ref-Typ

-- im DataCap

heißt: in der Diff-Verarbeitung wird dieser spezielle check verwendet

m.E. die einzig saubere Desgin-Variante!

gemeint ist:

  • man kann alternativ auch eine RecordRef direkt in eine elementare GenNode packen
  • diese verhält sich dann nicht transparent, denn sie hat eine andere Identität als ihr Ziel
  • das kann aber als spezielles Ausdrucksmittel genutzt werden

heißt: wird direkt von standard-equality so behandelt

brauche speziellen Builder,

der das so fabriziert

bekomme einen

"ungenutzten" DataCap

Idee: Ref-GenNode

als Ref erkennbar

(Prädikat)

hash-identische

Ziel-ID ableitbar

Verarbeiten

von Teilbäumen

Interpreter definiert Sprache

ROOT

INIT

leeres

Objekt

pick(Ref::CHILD)

würde sagen: ja, aber auch nur für das after-Verb!

allgemein halte ich einen wrap-around für keine gute Idee,

weil er zu Zweideutigekeigen führt und daher Struktur oder Konsistenzfehler überspielt

läßt sich stets duch eine inverse Folge von find und pick  emulieren

vorerst verworfen, da zusätzlicher Prüf-Aufwand

...Grund: sie werden durch einen jeweils komplett anderen Ansatz implementiert

  • "Liste" beruht auf dem Attribut-Iterator und dem Aufbauen einer neuen Attribut-Sammlung
  • "Map" beruht darauf, alle Operationen an die Storage zu delegieren

das heißt, man kann Attribute in einer "sinnvoll lesbaren" Ordnung anschreiben

und später angefügte Attribute bleiben so erkennbar.

Vorteilhaft für Version-Management

profitiert also von allen Verbesserungen des allgemeinen Algorithmus

"hoch effizient", unter der Annahme, daß fast immer nur konforme Änderungen kommen.

Weil dann nämlich die in unserer Implementierung ggfs. kostspieligen Umordnungen entfallen,

kommen wir auf lineare Komplexität für die Verarbeitung

+ NlogN für den Index zur Diff-Erzeugung

unsere Impl der Diff-Erzeugung (!)

baut einen Index auf (N*logN), um Einfügungen/Entfernungen zu erkennen und Umordnungs-Suche zu unterstützen.

Wenn wir aber von ausschließlich konformen Operationen ausgehen,

wird dieser Index nicht benötigt. Leider können wir das aber nicht garantieren, denn

es könnte ja zwischenzeitlich ein Attribut gelöscht und dann später (am Ende) wieder

angehängt worden sein, was dann eben doch einen Index erfordert, um einen

korrekten Listen-Diff zu erzeugen

d.h. wenn die Storage hoch-optimiert ist,

dann überträgt sich das auf die Diff-Behandlung

da wir Attribute in einer Liste speichern,

müssen wir für jede Einfügung eine vollständige Suche machen

...gemeint ist: extra, anders als die normale Listenverarbeitung.

Auch wenn diese andere Implementierung nur delegiert

danach noch auftretende Attribute

erfordern Sonder-Behandlung,

indem sie an die Attributs-Liste angehängt werden

wegen Entscheidung für das "Listen"-Modell zur Attribut-Handhabung

das heißt:

  • es wird einfach vom zuständigen Layer (der für die Attribute) aufgegriffen
  • es hat keinen Einfluß auf die nach außen sichtbare Reihenfolge
  • diese Reihenfolge bleibt gruppiert nach Attributen / Kindern

...da das Kind in der Liste der Attribute nämlich garnicht gefunden wird

...wenn wir am Ende der Attribut-Zone stehen,

und die nächste Operation ein fetch eines Kindes ist, müssen wir implizit den

Wechsel in den Scope vollziehen und die Operation dort ausführen.

Aber an allen anderen Stellen in der Attribut-Zone ist ein solcher Fetch ein Fehler!

standardmäßig strikt

List-Diff

als Spezialfall

kann auch nicht

wegen dem Interpreter

leicht auf generischen Container

zu verallgemeinern

Erkennung hat die Sprache als Parameter,

und verwendet sie zur Token-Generierung

man kann auch dem List-Detector

eine Tree-Diff-Language geben

Frage: in-Place?

entscheidende Frage: wie addressieren?

und wird durch die Diff-Anwendung konsumiert

Immutablility erzwingt

  • persistente Datenstrukturen
  • garbage-collector

Lösung: wir arbeiten auf einem Mutator

auf dem Umweg über einen ContentMutator

Innereien des alten Record verbrauchen

Problem: Rekursion

wenn ein MUT kommt

erzeugt man lokal einen DiffApplikator für den geschachtelten Kontext

und gibt ihm rekursiv den Diff hinein. Wenn dieser Aufruf zurückkehrt

ist der gesammte Diff für den eingeschachtelten Kontext konsumiert

wenn ein MUT kommt,

pusht der Applikator seinen privaten Zustand

auf einen explizit im Heap verwalteten std::stack

und legt einen neuen Mutator an für den nested scope

Entscheidung:

interner Stack

....begründet duch die generische Architektur.

Die Trennung von Diff-Iteration und dem Interpreter ermöglicht verschiedene Sprach-Ebenen.

Allerdings werde ich für die Anwendung auf konkrete Datenstrukturen,

also den TreeMutator, vermutlich das andere Modell (rekursiv konsumieren) verwenden.

Problem sind mal wieder die automatisch generierten IDs.

Die sind natürlich anders, wenn wir die ganze Testsuite ausführen...

Diff ist eine abstrakte Quelle,

die nur einmal verbraucht werden kann

Dekorator-Prinzip.

Paßt hier, da IterSource genau dieses Vorgehen nahelegt

MutationMessage::updateDiagnostics()

...diejenige, die zum Zeitpunkt des updateDiagnostics() noch anstand

schmerzloses C++ API

Performance: guter Schnitt (etw. besser als boost spirit)

hat ein DOM-API und ein SAX-artiges API

das heißt: nicht einmal abhängig von der STL

wie gson

vjson war Google Code;

nach dem Umzug auf Github heißt es gason

lt. eigenen Benchmakrs deutlich schneller als rapidjson, welches eigentlich immer als der "schnelle" JSON-Parser gilt.

d.h. das Parsen schreibt den Eingabepuffer um, und Strings bleiben einfach liegen

kein Repo auffindbar

ich will nicht noch ein Objekt-System

man hätte genausogut std::future und std::async verwenden können.

Vorteil von unseren Framework:

  • wir haben es schon, und wir werden es verwenden, wegen den Thradpools
  • man baut ein Objekt für einen Thread. Das ist explizit und sauber
  • wir haben eine eingebaute Barriere und können unseren Objekt-Monitor nutzen

habe einen usleep(1000) getimed

daher messen wir die Loop als Ganzes.

Es gibt daher keine Möglichkeit, den Loop-Overhead selber zu messen.

Er sollte sich aber bei einer Wiederholung im Millionenbereich gut amortisieren

Außerdem ist ja auch noch der Aufruf des Funktors mit im Spiel, wenngleich der auch typischerweise geinlined wird

volatile Variable außen, im Aufrufkontext

...was sehr schön beweist,

daß x86_64 tatsächlich cache-kohärent ist

war im Einsatz seit Beginn der Lumiera-Projektes.

Wurde aufgegeben da

  • die Policies komplexe Lösungsvarianten implementierten, die nie gebraucht wurden
  • die Implementierung einen tückischen Fehler in CLang aufgedeckt hat

Im Einsatz seit der Behebung des CLang-Problems bis heute (3/2018).

Wird nun aufgegeben, da sich auf dieser Basis keine DI implementieren läßt,

welche auf einem Service mit explizitem Lebenszyklus beruht.

Außerdem stellte sich diese Lösung als ziemlich fragil heraus

und benötigt diverse Laufzeit-Konsistenzchecks, die den Implementierungscode schwer lesbar machen

Bei diesem Wunsch-Profil bleibt nur eine Variante von Lösung-2

...denn nur eine dynamische Laufzeit-Factory ermöglicht, jederzeit  den Konstruktionsmodus zu wechseln

...denn nur ein Instanz-Pointer kann umgebogen oder auf NULL zurückgesetzt werden.

Der einzige Ausweg aus diesem Dilemma wäre eine statische Lösung,

in der bereits durch den #include von lib/depend.hpp endgültig klar wäre,

was für eine Art von Dependency-Factory zum Einsatz kommen soll. Denn nur auf diesem

Weg könnte der Optimiser unmittelbar auf eine Singleton-Instanz im statischen Speicher

zugreifen, nach einem Check auf ein atomic<bool>.

Eine solche statische Lösung allerdings widerspricht nicht nur meinen Wünschen,

sondern wäre auch architektonisch ungünstig, denn dadurch

  • entsteht eine zentrale DI-Konfiguration
  • erfolgt eine Rückverkopplung von lib/depend.hpp auf die Applikations-Struktur

...denn er muß komplett generisch sein, und lib/depend.hpp darf keinerlei Kenntnis

über die konkrete DependencyFactory voraussetzen. Denn sonst würden wir die

Freiheit der dynamischen Laufzeit-Konfiguration verlieren.

...denn ein Lambda kann in dieser Lage grundsätzlich keinen optimierungs-Vorteil bringen,

und die konventionelle Lösung hat demgegenüber den Vorteil, daß sie Struktur und Kontrakt explizit macht.

...denn es läuft darauf hinaus, daß die Nutzung eines Dienstes zwar on demand erfolgt,

jedoch stets erst nach seiner Bereitstellung. So etwas läßt sich niemals über einzelne

technische Einrichtungen lösen, denn es ist eine Frage des Aufbaues der gesamten Applikation.

Es gibt hierfür nur zwei Lösungswege

  • dynamisch: man definiert alle Abhängigkeiten durch Regeln und zieht das System nach Bedarf konsistent hoch, was bedingt, daß alle Abhängigkeiten über diesen Mechanismus laufen müssen
  • statisch: man strukturiert das System so, daß Nutzer erst nach dem Hochfahren der von ihnen benötigten Subsysteme aktiv sein können

scheidet für unser Nutzungsmuster aus

...denn es bedutet effektiv, daß viele Instanz-Zeiger "herumfliegen",

welche man in einer zentralen Registry erfassen müßte, um sie bei Bedarf

wieder auf NULL zurücksetzen zu können.

...d.h. Service-Zugang wird automatisch geschlossen,

wenn die DependInject-Instanz stirbt

sie werden ja sofort ausgewertet, da die Service-Instanz unmittelbar gebaut wird

aber Lebenszyklus ist an die Factory gebunden

Folgendes Szenario ignorieren wir:

  • eine Dependency ist als Service konfiguriert
  • für einen Testmock wird eine Local-Konfiguration darübergelegt
  • der Service wechselt seinen Lebenszyklus-Status (aktiv/inaktiv)

⟹ der Service zerschießt den Local, oder der Local restauriert am Ende den bereits toten Service.

Har Har Har! Selber schuld wer sowas macht.

statische Storage ist irgendwie cool

das gilt im Besonderen für eine default-Storage als Singleton.

Wofern wir dynamisch konfigurieren (wollen), muß dieser Default stets statisch bereitgestellt werden,

selbst wenn die dynamische Konfiguration so angelegt ist, daß die Storage nie benötigt wird

...aber dann eben nicht mehr elegant.

Und das hängt nur von den Umständen ab.

In einem einfachen statisch gelinkten Executable entfernt gcc die gesamte Variable sogar ohne Optimierung.

In Zukunft könnten Compiler/Linker noch "schlauer" werden...

daher muß sein dtor vor dem dtor von DependencyFactory laufen

denn während dem dtor existiert das Lock nicht mehr.

Man kann aber die Logik so umordnen, daß der instance-Ptr nach dem eigentlichen Deleter auf NULL gesetzt wird.

Das ist nicht threadsafe, was aber hier akzeptabel ist (Shutdown läuft überwiegend

single-threaded, sofern man keine thread_locals einsetzt. Aber diese werden vor alle anderen

Destruktoren gesequenced. Sollte passen.

denn nun wird das "singleton" schon ziemlich gehaltlos,

und es ist einigermaßen undurchsichtig, wo nun die Instanz erzeugt wird.

Allerdings gibt es auch kein stichhaltiges Argument, dieses Feature nicht zu implementieren.

Es ist halt einfach nahheliegend, daß man mal eine Subklasse mit abweichenden Parametern

konstruieren wollen könnte, und es ist von der Implementierung her "quasi geschenkt".

Thema: Memory access order constraints

Grundidee: synchronizes-with-Beziehung herstellen auf Guard-Variable

...das meint zweierlei

  • wir brauchen keine volle sequentielle Konsistenz
  • eigentlich würde consume statt acquire genügen,
    aber wir verzichten auf diesen ehr theoretischen Performance-Gewinn,
    welcher nur relevant wäre, wenn wir auf ARM einen modernen Compiler einsetzen

essentiell ist, im Mutex-geschützten Bereich

auf einer temporären lokalen Instanz-Variable zu arbeiten

warum?

weil per Definitionem dieses gesamte Konfigurations-Thema

als nicht performance-kritisch eingestuft wird -- und ich mehr Wert darauf lege,

die verschiedenen Belange im Quelltext nicht zu vermischen

es gilt schlichtweg als Architektur-Fehler, wenn hier eine Kollision geschiet.

Und es gibt keinen sinnvollen Weg, wie die Applikation dann weiterarbeiten kann.

Daher werfen wir ja auch error::Fatal

...denn sonst könnte genau das gleiche Desaster passieren,

das auch in fehlerhaftem Double-Checked-Locking auftritt

...d.h. das ganze Locking und die memory-order schützt uns hier überhaupt nicht!

Es kann sehr wohl passieren, daß ein anderer Thread grade eben noch

sicht den Pointer auf den Service geholt hat, und wir dann den Service zerstören, während

der andere Thread ihn grade nutzt.

unsere Architektur stellt aber sicher,

daß dieser Fall nicht relevant ist

warum?

Weil der "andere Thread" nur von zwei Subsystemen her kommen kann

  • dem Subsystem selber, das auch den Service erzeugt.
    Beispiel ist eine UI-Interaktion aus dem Event-Loop thread
  • aus einem anderen Subsystem, das vom Serivce-Provider abhängt

In beiden Fällen stellen unsere Prinzipien zum Betreiben von Subsystemen sicher,

daß dieser "andere Thread" nicht (mehr) aktiv sein darf, wenn der Shutdown erfolgt.

hier ist ein Segfault möglich

...denn wir sind auf x86_64 -- und diese Plattform ist per default fast überall sequentially coherent

...man könnte ihn aber genausogut auch machen.

Das Argument ist: wenn wir kaskadierend aufrufen, dann ist das Ergebnis in jedem Fall korrekt,

und wird auch durch das Installieren dieses (zweiten) Zuganges in keinster Weise beeinträchtigt.

Sollte Depend<SUB> bereits instantiiert sein, dann auch gut.

Der kaskadierende Aufruf liest dann einfach dessen Instanz-Pointer

hier notwendig, weil wir eine neue Factory-Funktion ablegen.

Das könnte mit dem Factory-Management einer bereits installierten Konfiguration kollidieren

zwar wird beim Löschen des Mock

die ursprüngliche Factory wieder an ihren Platz zurückgeschoben,

aber niemand sagt, daß ein Move auch wirklich ein Move (swap) ist.

Muß daher diese leer gewordene temporäre Factory explizit auf Default-Zustand zurücksetzen,

damit nicht doch noch der Deleter läuft.

...und man stattdessen explizit eine gefährliche Funktion  aufrufen muß

...da es sich ja nur um die Factory handelt,

nicht um das AppState-Singleton selber, welches ja ohnehin nur von main.cpp

verwendet werden sollte. Aber das deutet darauf hin, daß irgend etwas mit der

Initialisierung von Statics "faul" ist, wenn shared objects dynamisch geladen werden.

Konsequenz: das ist keine Library-Implementierung

die von der alten DependencyFactory abhängen

...wenn man nämlicht Lumiera's Lösung nicht genau kennt,

könnte sich das so lesen, als wäre Depend<X> ein Mixin,

welches einer Klasse magisch eine Dependency als protected-Feld zugänglich macht.

Und dann wäre es ziemlich pervasiv, sowas zum Freund zu erklären.

DependencyFactory ist viel besser geeignet

den habe ich nicht mehr über das Interface-System gemappt

weil mir das ganze C-gefrickel zu blöd geworden ist.

Also hat das hier Prototyp-Charakter!

der Interface-Anbieter implementiert einen konkreten Proxy

Modus der Definition

und Instantiierung

...wir müssen immer, für jeden Proxy

explizit eine Template-Instaniierung triggern, und zwar für

brauchen eigenen Zugriffs-Mechanismus auf ein weiteres Handle-Objekt

...oder sogar den ctor, das ist egal --

denn das Problem ist, ctor (oder activate) werden aus demjenigen Kontext heraus aufgerufen,

der die Service-Implementierung startet und damit die Erzeugung des Proxy triggert.

Dieser Kontext darf den konkreten Typ des Proxy aber genau nicht kennen (wegen Entkopplung)…

Genau aus diesem Grunde leiten wir ja ctor/dtor der ServiceInstance(Handle)-Klasse in eine ander TU um.

Problem ist nun, diese umgeleiteten Funktionsdefinitionen können nur einen Typ (Template-Parameter) bekommen, und dieser Typ wiederum muß -- zumindest als abstrakter Platzhalter -- in allen TU sichtbar sein.

obwohl es das sollte.

Aber anscheinend macht der Compiler das nur partiell,

denn es wird ja nur eine const& an den ctor von Binding übergeben.

Allerdings erscheint es mir nicht sinnvoll, hier mit Tricksereien zu arbeiten!

sie ist zwar nicht schön -- aber was ist an einem Interface-Binding schon "schön"?

Sie ist hinreichend wartbar, sofern man sie per copy-n-pate vervielfältigt.

Es wäre sogar denkbar, in diesen Rump eine generierte Proxy-Klasse zu kleben,

da nur wenige Variable erstetzt werden müßten.

das ist ein konzeptionelles Problem.

Eigentlich möchte man durch ein Interface Entkoppelung erreichen.

Nun ist es so, daß

  • das Binding BusinessInterface -> InterfaceSystem
  • InterfaceSystem -> Service-Implementierung

an der gleichen Stelle erfolgen

...und lib::Depend so umarrangiert,

daß re-entrant-Aufrufe während dem dtor erlaubt sind

so wie das Advice-System geschrieben ist,

kann und muß man das "durchwinken"

denn grundsätzlich ist das ganze Advice-System bewußt "billig" implementiert;

Verbesserungen später willkommen....

Der Destructor räumt alle AdviceProvisions weg.

Diese werden in einen statischen Kontext geschoben, damit sie unabhängig vom Advisor weiterleben.

Daher müssen wir aus diesem statischen Kontext heraus wieder zurück in's AdviceSystem kommen.

Alternative wäre, eine komplett spezielle De-Allokations-Routine zu schreiben,

welche die Datenstrukturen direkt traversiert und freigibt, und dann die Provisions

abfischt und ebenfalls alles wegwirft. Machbar, erscheint aber im Mißverhältnis

zum Level der gesamten übrigen Implementierung (welche nämlich um einiges

elaborierter sein könnte, incl. Verwendung von Atomics und einem besseren

Memory-Management.

Aber wie gesagt, das ganze Advice-System ist eine Skizze

...weil Nobug-Init ON_BASIC_INIT braucht,

und lib::Depend wiederum von Nobug-Init abhängig ist.

Also würde DependencyFactory<LifecycleRegistry> aufgerufen,

bevor es statisch initialisiert sein kann...

und heute würde ich den Code so nicht mehr schreiben

genauer:

er ist nicht kaputt, sondern hat sehr gut funktioniert und diesen Unfug festgestellt.

Nämlich daß unser lib::Depend ein ClassLock braucht, um einen Fehler zu melden.

Das ist, für sich betrachtet, eigentlich unvermeidlich, aber verlangt dann auch

nach einer grundlegenden Lösung. D.h. einem echten Schwartz-Counter.

Nicht einem, der in einem Meyer's Singleton steckt...

...es ruft sich selbst rekursiv auf, via Depend<AdviceSystem>

...nämlich eine ganz spezielle, dedzierte Aufräum-Routine schreiben

...und das kann ziemlich indirekt passieren.

Beispiel ist das ClassLock. Das ist ein Front-End, und verwendet verdeckt wieder einen Static.

Und genau dafür gibt es anscheinend keine Garantieren

C++ hällt die Erzeugungs/Zerstörungs-Reihenfolge exakt ein

d.h. wenn das local static später erzeugt wird, wird es vor  dem Hauptobjekt zerstört

Statische Initialisierung funktioniert präzise, korrekt und zuverlässig

Der Aufruf von Konstrukturen statischer Objekte konstituiert eine (dynamische) Reihenfolge.

Desktuktoren werden exakt rückwärts in dieser Reihenfolge aufgerufen.

Statische Objektfelder werden vor der ersten Verwendung der Klassendefinition  initialisiert

Dagegen Funktions-lokale statische Variablen werden initialisiert, wenn der Kontrollfluß sie zum ersten mal berührt.

Wenn ein Konstruktor ein statisches Feld verwendet, dann wird dieses Feld vor dem Konstruktor erzeugt.

Beachte: in jedem dieser Fälle wird auch die o.g. Reihenfolge konstituiert.

Corollar: wenn man ein Meyer's Singleton erst indirekt aus dem Implementierungs-Code verwendet,

so wird es garantiert zerstört, bevor der Destruktor des aufrufenden Objekts läuft.

Hallo ClassLock...

...necessary when closing the session;

we need to wait for the current command or builder run to be completed

...noch nicht implementiert 1/17

Guard beim Zugang über das Interface

nur sie ist atomar

nur ein Thread für Commands und Builder

Ticket #1054

...und "self" == LumieraThrea* == "handle" (im Wrapper).

D.h. solange der Wrapper lebt (!), kann er selber leicht feststellen, ob die aktuelle Ausführung

auch in einem Thread stattfindet, der

  • von unserem Threadpool gestartet wurde
  • ein Thread-Handle hat, das mit dem Handle dieses Wrappers identisch ist.

Das Schöne bei diesem Ansatz ist, daß man dafür weder das Handle exponieren muß,

noch irgendwelche komischen Policies aufmachen. Solange es das Objekt gibt, klappt das.

OO rocks!

Kontrollfluß ist nicht in einer Arbeitsfunktion

...und dann nur noch

  • auf Shutdown reagieren
  • mitbekommen wenn die Sperre aufgehoben wird

billig: unsere Zeit-Lib nutzen

Logik im Looper auf Basis

generischer Überlegungen implementiert

Thema: Monaden

gehört zu dem Themenkomplex "itertools"

Python hat das auch, Java neuerdings auch

...will sagen, es ist klar, wie man sowas machen kann.

Seinerzeit war mir das auch klar, aber ich wollte es nicht gleich ausprogrammieren.

Inzwischen kam dann das Thema UI-Coordinaten, und dort habe ich es ausprogrammiert,

und zwar direkt in die Low-Level-Schicht integriert, was nicht schlecht ist,

da eine Abstraktion hier sehr technisch werden würde

...bindet die Betrachtung auf einen technischen Level,

und führt dazu, daß die Abstraktion undicht wird

genau der Umstand,

daß funktionale Sprachen von einer Zustands-Phobie getrieben sind,

macht Monaden nützlich, um inhärenten Zustand wegzuabstrahieren.

Das kann genutzt werden, um den Zustand einer Wechselwirkung

nach einer Seite der Glieder auszukoppeln.

saugeil

...nicht mehr das klassische gtk::Main

Wozu das?

  • Design: Main war ein Singleton; aber sein dtor hat auch Plattform-Aufräum-Arbeiten gemacht
  • Framework: anscheinend ist hier eine Tendenz in Richtung auf ein integriertes Framework im Gange; im Besonderen will man "Aktionen" direkt aus dem Desktop aufrufen können

...und erzeugt diesen on demand auch neu

warum?

nur wegen ApplictationWindow!

Denn dieses setzte eine "Registrierung" voraus.

Alles in ein Framework zwingen. Alternativlos, capisce?

...allerdings eingepackt in eine vfunc,

welche ggfs C++ - Exceptions fängt

...nur wegen dem ganzen Registrierungs-Glump.

Ein "GTK-Application-Window" ist auch irgendwie registriert und hängt am Bus.

Frag mich nicht wie. Jedenfalls kann man das nicht im Konstruktor von Gtk::Application machen.

Nur das ist der Grund. Es geht gar nicht um die Event-Loop

suche (case insensitive) nach application_activate

  • treffer auf APPLICATION_ACTIVATE in g_application_activate()
  • die Treffer in Gtk::Application
  • diverse false positives mit anderen "Activation"-Signalen, z.B. in Aktionen oder Buttons

nicht durch gtk_main

wichtige Einsicht:

  • gtk_main ist Toolit ohne Framework
  • bietet keine solchen Lebenszyklus-Signale

beachte: ruft nicht gtk_main sondern macht das Äquivalent

initialisiert das

Framework

gboolean

gtk_init_check (int    *argc,

                char ***argv)

{

  gboolean ret;

  if (!gtk_parse_args (argc, argv))

    return FALSE;

  ret = GDK_PRIVATE_CALL (gdk_display_open_default) () != NULL;

  if (gtk_get_debug_flags () & GTK_DEBUG_INTERACTIVE)

    gtk_window_set_interactive_debugging (TRUE);

  return ret;

}

void Main::init_gtkmm_internals()

{

  static bool init_done = false;

  if(!init_done)

  {

    Glib::init();

    Gio::init();

    // Populate the map of GTypes to C++ wrap_new() functions.

    Pango::wrap_init();

#ifdef GTKMM_ATKMM_ENABLED

    Atk::wrap_init();

#endif //GTKMM_ATKMM_ENABLED

    Gdk::wrap_init();

    Gtk::wrap_init();

    init_done = true;

  }

}

...und das ist nicht gtk_main,

aber macht in etwa die gleichen Operationen

...das heißt, es wurde "retrofitted".

die Lib Gio bietet ein generisches "Main-Loop-Framework",

in dem ein Main-Context gepollt wird, solange, bis ein use-count auf Null geht.

Gtk-Main verwendet inzwischen den gleichen Mechanismus

der springende Punkt mit sigc::trackable ist,

daß Desktuktoren automatisch die Signale abkoppeln.

Dieser Vorgang ist nicht threadsafe. Folglich müssen

auch die Destruktoen im GUI-Thread laufen.

Das ist eine subtile Falle.

alles was von sigc::trackable erbt

für alles aus GTKmm zu verwenden

...gemeint ist:

alles das nicht aus dem GUI-Thread heraus geschieht

...können vom CSS-Stylesheet aus gesetzt werden.

Siehe Beschreibung im Beispiel/Tutorial

....how does the event dispatching deal with partially covered widgets

...for embedded widgets

...meaning, "this event is not yet fully processed",

i.e. the enclosing parent widget also gets a chance to redraw itself

Warning: allocation is the visible area

asked on stackoverflow...

...as can be observed

by printing values from the on_draw() callback

...otherwise adjustment values will cummulate,

causing us to adjust too much

...die anderen, die noch in Frage kommen würden,

sind nur für den Fall, daß ein Widget neu instantiiert wird

oder neu in das Window-System gemappt wird.

on_check_resize() wird nicht aufgerufen

...keine Ahnung, was ich beim ersten Mal falsch gemacht habe.

jedenfalls hab ich da sofort beim ersten Aufruf der Closure einen SEGFAULT bekommen.

Auch im zweiten Anlauf habe ich ein Lambda verwendet.

Möglicherweise ist der einzige Unterschied, daß ich es nun aus dem draw-callback

aufrufe, und daß demgegenüber bei der ersten Verwendung die Allocation des jeweiligen

Kind-Widgets noch gar nicht festgelegt war (denn das passiert erst beim draw).

in der Implementierung, mywidget.cc

ist eine komplette Sequenz, wie man einen CSS-StyleProvider setzt

und auch ein Signal für Parse-Fehler anschließt

Beispiel im Guide

  • left gravity: Marker bleibt beim Einfügen an dieser Stelle links von der Einfügung stehen
  • right gravity: Marker wird durch Einfügen an dieser Stelle nach rechts geschoben

Beachte: der Text-Cursor (Marker "insert") hat right gravity

Multithreded-Beispiel

im Guide demonstriert das

Ticket #886

Lessons

learned

auf incomplete type achten

Vorsicht bei

mutually dependent templates

kann eines der Templates im Zyklus vorrübergehend als "incomplete" gelten.

...wenn man dummerweise auf verschlungenen Pfaden

genau in dieser Phase die Metafunktion anfragt,

kann der betreffende Check stillschweigend scheitern.

Konsequenz: man wählt dann z.B. eine subtil falsche Spezialisierung.

wenn ein Template ein statisches member-Feld hat,

dann ist zusätzlich eine getemplatete Definition dieses Feldes notwendig.

Diese wird erst generiert, wenn der erste odr-use des statischen Member-Feldes passiert.

Dieser odr-use kann nun z.B. aus einer Funktion des Template heraus erfolgen

Allerdings beobachte ich, daß dann der ctor-Aufruf zur Initialisierung erst nach dem Zugriff auf

das member-Feld passiert, sofern der Aufruf und damit die Instanz des umschließenden Template

selber aus einem statischen Initialisierungs-Kontext heraus erfolgt.

#include <iostream>

using std::cout;

using std::endl;

template<typename T>

class Factory

  {

  public:

    T val;

   

    Factory()

      : val{}

      {

        cout << "Factory-ctor  val="<<val<<endl;

      }

  };

template<typename T>

class Front

  {

  public:

    static Factory<T> fac;

   

    Front()

      {

        cout << "Front-ctor    val="<<fac.val<<endl;

        fac.val += 100;

      }

   

    T&

    operate ()

      {

        cout << "Front-operate val="<<fac.val<<endl;

        ++ fac.val;

        return fac.val;

      }

  };

template<typename T>

Factory<T> Front<T>::fac;

namespace {

  Front<int> front;

  int global_int = front.operate();

}

int

main (int, char**)

  {

    Front<int> fint;

   

    int& i = fint.operate();

    cout << "main:         val="<<i<<endl;

    cout << "global_int.......="<<global_int<<endl;

   

    return 0;

  }

das sind verschiedene Blickwinkel auf das gleiche Thema

Bedeutung dieser Schreibweise:

  • der erste Zugriff liegt vor der Barriere, der zweite danach
  • die Barriere garantiert jeweils nur, daß der zweitgenannte Zugriff nicht vor den erstgenannten verschoben werden kann

eine Solche konstituiert die synchronizes-with-Beziehuung

Grundbeziehung: synchronizes-with

das gilt nur im Rahmen der synchronizes-with-Beziehung

das heißt, nur für einen vom gleichen Mutex geschützen  Bereich!

In der naiven Implementierung greift der prüfende Thread auf die instance-Variable

ohne jedwede Beziehung zum anderen Thread zu; er verwendet keinen Mutex und keinen Atomic.

Und genau deshalb kann er das Setzen des Instanz-Pointers sehen, ohne daß eine

Ordnungsbeziehung zur der restlichen Initialisierung oder lazy computation besteht.

Fix: die Beziehung herstellen. Das ist verursacht stets zusätzliche Kosten.

Allerdinsg nicht auf einer Plattform, die ohnehin sequentiell-konsistent ist. Wie "zum Beispiel" x86/64

...haben wir es hier mit einem Pattern zu tun

ich nenne es "synchronised visibility cones"

Dieses errichtet die Fiktion,

als würden wir nur auf einer gemeinsamen (shared) Instanz arbeiten.

In Realität arbeiten mehrere Threads/Cores mit mehreren Entitäten

...und andernfalls überhaupt vermeiden,

die shared zone anzufassen!

wenn eine getemplatete Klasse zum Qualifizieren eines Feldes verwendet wird,

dann müssen die formalen Template-Parameter in spitzen Klammern mit angegeben werden.

D.h. Doxygen ist hier genauso penibel wie C++ selber

Beispiel

Query<RES>::resolveBy

@param hat stets einen Parameternamen als Argument

...der ist nicht optional

vielmehr wird blindlings immer das erste Wort genommen.

Wenn der Parameter selber nicht benannt ist (z.B. pure virtual function),

kann man ersatzweise einfach einen Typnamen angeben.

Sofern alle Parameter dokumentiert sind, klappt das.

sonst kommt Doxygen durcheinander

....haben in ihrem @file-Kommentar

einen Verweis \ref DieserUnit_test

Und obwohl das der exakte Klassennahme ist,

und obwohl genau diese Klasse im Klassenindex zu finden ist

wird hier kein Link erzeugt

...im Besonderen die guten Diagramme für Pulse, ALSA und Jack

bekannter Bug binutils #16936

Lumiera-Ticket #965

gelöst in 4e8e63ebe

...man "hilft" dem Linker mit

"-Wl,-rpath-link=target/modules"

laufen wieder alle

test.sh Zeile 138

Debian-Bug #724461

nebenbei ohweh:

ulimit -t 1 ist wirkungslos

Christian:  bash -c "ulimit -t 1; while :; do :; done"

und wir verbringen unsere Zeit mit contention

ist klar, hab ich gebrochen

siehe Ticket #587

Kollisionen jetzt bereits nach 4000 lfd. Nummern

Vorher hatte ich erste Kollisionen nach 25000 Nummern

erinnere mich an den

guten alten "Knuth-Trick"

wow: es genügt,

die letzten beiden Zeichen mit der Knuth-Konstante zu spreizen,

und ich komme locker auf 100000 Nummern ohne Kollision

Aug 10 04:51:39 flaucher kernel: gdb[8234]: segfault at 7ffe3fa79f50 ip 0000000000718b95 sp 00007ffe3fa79f40 error 6 in gdb[400000+574000]

Aug 10 04:51:39 flaucher kernel: traps: test-suite[8249] trap int3 ip:7ffff7deb241 sp:7fffffffe5c8 error:0

function gebunden an ein lambda

wobei ein Argument-Typ als vom Template-Argument

der umschließenden Funktion aufgegriffen wird

Bugreport für Debian/Jessie #795445

Git: debBild/Gdb_DEB.git

bison dejagnu flex gobjc libncurses5-dev libreadline-dev liblzma-dev libbabeltrace-dev libbabeltrace-ctf-dev python3-dev

dutzende Tests scheitern

verräterrischer Code im debian/rules

check-stamp:

ifeq ($(run_tests),yes)

        $(MAKE) $(NJOBS) -C $(DEB_BUILDDIR)/gdb check \

          || echo "**Tests failed, of course.**"

endif

        touch $@

au weia LEUTE!

speziell: unused-function bei dem Trick mit dem std::hash macht mir Sorgen.

und tatsächlich: das ist daneben, GCC hat Recht!

aktualisieren und neu bauen

standard hardening-flags setzen #971

wähle Kompatibiltät genau so, daß Ubuntu-Trusty noch unterstützt wird.

...damit man auch im Paketbau-Build-Output wenigstens einmal alle  generischen Platform-Schalter sieht

Ich meine also: zu Beginn vom Build sollte das Buildsystem einmal eine Infozeile ausgeben

...denn die stören jeweils beim erzeugen eines Hotfix/Patch im Paketbau per dpkg --commit

deprecated: auto_ptr

Tests mit TypeIDs scheitern

Doku durchkämmen nach Müll

hier nach offensichtlich obsoleter Info checken

WICHTIG: keine vorgreifende Infor publizieren!!!!!

die explizit angegebenen Paketnamen schon mal vorchecken

die Abschnitte zu den LIbraries prüfen / umschreiben

insgesamt sorgfältig durchlesen

knappe Kennzeichnung des Releases in den Kommentar

hier geht es darum, Konsistenz im Git herzustellen.

Wenn alles korrekt gemacht wurde, dürfte es hier keinen Rückfluß von Änderungen geben.

Bitte auch daran denken, zuerst den DEB-Zweig zu prüfen. Diesen aber nicht zurückmergen,

denn wir wollen keine DEB-Info im Master haben!

einzeilige Kennzeichnung wiederholen

die unmittelbaren Release-Dokumente durchgehen

Merge-commit auf den Release-Zweig.

Sollte konfliktfrei sein

...das heißt bauen und hochladen

Referenz: Debian/Jessie (stable) : i386 and x86_64

Probleme mit der Compile-Reihenfolge  #973

...führt sowohl eine README, alsauch ein Verzeichnis /usr/share/doc/lumiera/html auf, das (noch) nicht existiert

unter Debian/Jessie wird das ignoriert

stelle fest: Fehler auf Trusty,

nur Warnung auf Mint

das heißt, daß ich versuchen kann, das Problem erst mal "unter den Teppich zu kehren"

Die Wahrscheinlichkeit, daß irgend jemand Lumiera unter Ubuntu/Trusty installieren möchte, erscheint mir akademisch

bauen mit gcc-5 scheitert

in lib/hash-standard.hpp

mit gcc-5 gebaute Tests scheitern

bauen mit gcc-4.9 nicht möglich

es gibt Probleme beim Linken mit den Boost-Libraries, die auf Ubuntu/wily mit gcc-5 gebaut sind.

Wichtig: hier nur was wirklich gebaut ist und funktioniert!

eigentlich war die nur notwendig für das Video-Viewer Widget,

was nun leider tot ist. Wir haben noch keinen Ersatz. Deshalb lasse ich die Abhängigkeit

bestehen, aber irgendwann müssen wir das schon glattziehen

hardening-flags! #971

Ticket #722

seit gcc-4.8 ist kein static_assert mehr in der STDlib

Probleme mit der Compile-Reihenfolge  #973

TEST Dispatch functors into other threads: CallQueue_test .. FAILED

unexpected return value 134, expected 0

stderr was:

0000000459: INFO: suite.cpp:180: thread_1: invokeTestCase: ++------------------- invoking TEST: CallQueue_test

0000003117: CHECK: call-queue-test.cpp:251: thread_1: verify_ThreadSafety: (globalProducerSum == globalConsumerSum)

0000003127: BACKTRACE: call-queue-test.cpp:251: thread_1: verify_ThreadSafety: /Werk/devel/lumi/target/modules/libtest-basics.so(_ZN3lib4test14CallQueue_test19verify_ThreadSafetyEv+0x24a) [0x7fdfce328a20]

0000003128: BACKTRACE: call-queue-test.cpp:251: thread_1: verify_ThreadSafety: /Werk/devel/lumi/target/modules/libtest-basics.so(_ZN3lib4test14CallQueue_test3runERSt6vectorISsSaISsEE+0x34) [0x7fdfce32764e]

0000003129: BACKTRACE: call-queue-test.cpp:251: thread_1: verify_ThreadSafety: /Werk/devel/lumi/target/modules/liblumierasupport.so(+0x1d7cb0) [0x7fdfcb7dfcb0]

0000003130: BACKTRACE: call-queue-test.cpp:251: thread_1: verify_ThreadSafety: /Werk/devel/lumi/target/modules/liblumierasupport.so(_ZN4test5Suite3runERSt6vectorISsSaISsEE+0x38f) [0x7fdfcb7e0249]

0000003131: BACKTRACE: call-queue-test.cpp:251: thread_1: verify_ThreadSafety: ./test-suite() [0x40625e]

0000003132: BACKTRACE: call-queue-test.cpp:251: thread_1: verify_ThreadSafety: /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf5) [0x7fdfc9029b45]

0000003133: BACKTRACE: call-queue-test.cpp:251: thread_1: verify_ThreadSafety: ./test-suite() [0x4060a9]

END

for I in `seq 1 50`; do target/test-suite CallQueue_test; done

habe gleichzeitig erst die Testsuite gebaut mit -j 36 und dann laufen lassen.

Gleichzeitig aber auch noch das ./build-website-Skript

und eine Doxygen-Seite im Browser geladen

weil sich die Threads gegenseitig ihre Counter inkrementieren.

alle anderen (mit Ausnahme von BusTerm_test)

verwenden globale Variable oder überhaupt keine Objektfelder