Files
Michael Schimmel 494bf554d2 Old Docs added
2026-02-20 10:09:22 +01:00

5.9 KiB


Projektplan: Atome und das "Builder/Snapshot"-Pattern

  • Datum: 09. Oktober 2025
  • Uhrzeit: 16:07

Motivation

Die bisherige Diskussion hat zwei grundlegende Anforderungen an die Zustandsverwaltung herausgearbeitet:

  1. Allgemeine Sicherheit: Für generische Datenstrukturen (Maps, Vektoren) bieten persistente Datenstrukturen durch ihre Immutabilität und lock-freie Lesbarkeit die höchste Sicherheit und Flexibilität bei konkurrierendem Zugriff.
  2. Spezialisierte Performance: Für bestimmte Anwendungsfälle (z.B. große, append-only Time-Series) ist die Performance einer In-Place-Mutation überlegen, aber das Teilen des Zustands zwischen Threads erfordert eine sichere Synchronisation.

Die Motivation ist daher, ein standardisiertes, generisches Muster zu definieren, das die rohe Performance einer mutierbaren "Arbeitskopie" mit der Sicherheit von unveränderlichen, lesbaren "Snapshots" für die konkurrierende Analyse kombiniert.

Ziel

  1. Etablierung des "Builder/Snapshot"-Patterns als idiomatisches Kernkonzept der Sprache für den Umgang mit hoch-performantem, geteiltem, mutablem Zustand.
  2. Definition von generischen Basis-Interfaces (IBuilder, IImmutable), die dieses Pattern im Typensystem verankern.
  3. Nahtlose Integration dieses Patterns mit den bestehenden atom- (für geteilten Zustand) und var- (für lokalen Zustand) Konstrukten.

Ergebnis

Das Ergebnis ist ein klares, zweigleisiges Modell für die Zustandsverwaltung, das auf dem generischen "Builder/Snapshot"-Pattern aufbaut.

1. Das generische Pattern: IBuilder / IImmutable

Wir definieren zwei konzeptionelle Basis-Interfaces, die den Vertrag des Patterns abbilden:

  • IImmutable: Ein Marker-Interface, das einen sicheren, unveränderlichen "Produkt"- oder Snapshot-Typ kennzeichnet.
  • IBuilder: Ein Interface, das einen "Builder" kennzeichnet. Es definiert die Fähigkeit, ein IImmutable-Produkt zu erzeugen.

2. Die Spezialisierung (Beispiel: Series-Interfaces)

Konkrete Datenstrukturen wie die Series spezialisieren diese generischen Rollen.

// --- Generische Basis-Interfaces ---

// Kennzeichnet ein sicheres, unveränderliches Lese-Objekt (Snapshot).
IImmutable = interface(IInterface)
end;

// Kennzeichnet ein mutierbares Builder-Objekt, das Snapshots von sich erzeugen kann.
IBuilder = interface(IInterface)
    function CreateSnapshot: IImmutable;
end;

// --- Spezialisierte Interfaces für die 'Series' ---

// ISeries ist die spezialisierte, lesbare Form von IImmutable.
ISeries = interface(IImmutable)
    function GetCount: Int64;
    function GetItems(Idx: Integer): TScalar;
    property Count: Int64 read GetCount;
    property Items[Idx: Integer]: TScalar read GetItems; default;
end;

// IWriteableSeries ist der spezialisierte Builder.
// Wichtig: Er implementiert den IBuilder-Vertrag, indem er eine CreateSnapshot-Methode
// anbietet, die den spezialisierten Snapshot-Typ ISeries zurückgibt.
IWriteableSeries = interface(IBuilder)
    function GetCount: Int64;
    property Count: Int64 read GetCount;
    procedure Add(const Item: TScalar.TValue; Lookback: Int64 = -1);
    function CreateSnapshot: ISeries; // Covarianter Return-Type zum Basis-Interface
end;

3. Die Anwendungsbeispiele in der Sprache

Das Pattern wird auf zwei Arten verwendet, je nach Kontext (atom oder var). Die RTL-Funktionen arbeiten mit den generischen IBuilder- und IImmutable-Konzepten.

Beispiel 1: Geteilter Zustand (via atom)

Der atom schützt den IBuilder vor konkurrierenden Schreibzugriffen.

(*
 * Szenario: Ein Ticker-Modul (Thread A) empfängt Live-Daten,
 * während eine UI (Thread B) die Daten zur Analyse anfordert.
 *)

; Ein globaler 'atom' wird erstellt. Er hält die einzige Instanz des Builders.
(atom live-ticker (create-series-builder))

; -- Thread A (Produzent) --
; Modifiziert den Builder sicher über eine generische, synchronisierte RTL-Funktion,
; die auf jedem IBuilder in einem Atom funktioniert.
(builder-update! live-ticker series-add new-tick-value)

; -- Thread B (Konsument) --
; Fordert einen sicheren, unveränderlichen Snapshot an.
; Die generische 'snapshot'-Funktion ruft intern 'CreateSnapshot' auf.
(let chart-data (snapshot live-ticker))

; 'chart-data' ist nun eine sichere ISeries (IImmutable), die ohne Locks gelesen werden kann.
(calculate-indicators chart-data)

Beispiel 2: Lokaler Zustand (via var)

Dies ist der "Maschinenraum"-Modus für maximale Single-Thread-Performance.

(*
 * Szenario: Innerhalb einer Funktion wird eine komplexe Datenreihe
 * temporär aufgebaut, um ein einmaliges Ergebnis zu berechnen.
 *)
(do
  ; Erzeuge einen lokalen, unsynchronisierten Builder.
  (var local-builder (create-series-builder))

  ; Befülle den Builder direkt und ohne Lock-Overhead über typspezifische,
  ; unsynchronisierte RTL-Funktionen.
  (repeat 1000000
    (series-add! local-builder (get-next-value)))

  ; Erzeuge einen finalen Snapshot für die Weiterverarbeitung.
  (let final-data (series-create-snapshot local-builder))

  ; Gib das Ergebnis der Analyse zurück.
  (run-analysis-on final-data))

Todo

  • Generische IBuilder- und IImmutable-Interfaces in Delphi definieren.
  • Finale ISeries- und IWriteableSeries-Interfaces (als Spezialisierungen) implementieren.
  • Eine konkrete TSeries-Klasse implementieren, die IWriteableSeries implementiert.
  • Die generischen, synchronisierten RTL-Funktionen (builder-update! ...) und (snapshot ...) für atoms erstellen, die auf IBuilder operieren.
  • Die spezifischen, unsynchronisierten RTL-Funktionen (series-add! ...) und (series-create-snapshot ...) für die TSeries-Implementierung erstellen.
  • Unit-Tests für beide Anwendungsfälle schreiben.