Es gibt eine Geschichte über die Erfahrung mit Actor Model in einem interessanten Projekt zur Entwicklung eines automatischen Steuerungssystems für ein Theater. Unten erzähle ich meine Eindrücke, nicht mehr als das.
Vor nicht allzu langer Zeit nahm ich an einer aufregenden Aufgabe teil: der Modernisierung des automatischen Steuerungssystems (ACS) für Lattenzüge, aber tatsächlich war es die Entwicklung eines neuen ACS.
Ein modernes Theater (besonders wenn es ein großes ist) ist eine sehr komplexe Organisation. Es gibt viele Menschen, verschiedene Mechanismen und Systeme. Eines dieser Systeme ist ACS für die Handhabung des Hebens und Abstellens der Landschaft. Moderne Aufführungen wie Opern und Ballette setzen Jahr für Jahr immer mehr technische Mittel ein. Die Szenerie wird von den Regisseuren der Show aktiv genutzt und spielt sogar eine wichtige Rolle. Es war faszinierend zu entdecken, was hinter den Vorhängen passiert, da gewöhnliche Zuschauer nur Aktionen vor Ort sehen können.
Dies ist jedoch ein technischer Artikel, und ich möchte meine Erfahrungen mit der Verwendung des Akteurmodells zum Schreiben eines Steuerungssystems teilen. Und teilen Sie meine Eindrücke von der Verwendung eines der Actor Frameworks für C ++: SObjectizer .
Warum haben wir diesen Rahmen gewählt? Wir haben es uns schon lange angesehen. Es gibt viele Artikel in russischer Sprache, und es gibt wunderbare Dokumentationen und viele Beispiele. Das Projekt sieht aus wie ein ausgereiftes. Ein kurzer Blick auf Beispiele hat gezeigt, dass die Entwickler von SObjectizer dieselben Begriffe verwenden (Status, Timer, Ereignisse usw.), und wir haben keine großen Probleme beim Studieren und Verwenden erwartet. Und noch ein wichtiger Faktor: Das Team von SObjectizer ist hilfsbereit und immer bereit, uns zu helfen. Also beschlossen wir es zu versuchen.
Was machen wir
Lassen Sie uns über das Ziel unseres Projekts sprechen. Das System der Lattenzüge hat 62 Latten (Metallrohre). Jede Latte ist so lang wie die gesamte Bühne. Sie werden parallel zu Abständen von 30-40 cm ausgehend von der Vorderkante der Bühne an Seilen aufgehängt. Jede Latte kann angehoben oder abgesenkt werden. Einige von ihnen werden in einer Show für Landschaften verwendet. Die Szenerie ist auf einer Latte fixiert und wird während der Aufführung nach oben / unten bewegt. Die Befehle von Bedienern initiieren die Bewegung. Ein System des "Motor-Seil-Gegengewichts" ähnelt einem System, das in Aufzügen in Wohngebäuden verwendet wird. Motoren werden außerhalb der Bühne platziert, damit die Zuschauer sie nicht sehen. Alle Motoren sind in 8 Gruppen unterteilt, und jede Gruppe verfügt über 3 Frequenzumrichter (FC). Es können höchstens drei Motoren gleichzeitig in einer Gruppe verwendet werden, von denen jeder mit einem separaten FC verbunden ist. Wir haben also ein System mit 62 Motoren und 24 FCs, und wir müssen dieses System steuern.
Unsere Aufgabe war es, eine Mensch-Maschine-Schnittstelle (HMI) zur Steuerung dieses Systems zu entwickeln und Steuerungsalgorithmen zu implementieren. Das System umfasst drei Kontrollstationen. Zwei davon befinden sich direkt über der Bühne und einer im Maschinenraum (diese Station wird von einem diensthabenden Elektriker benutzt). Es gibt auch Steuerblöcke mit Steuerungen im Maschinenraum. Diese Steuerungen führen Steuerbefehle aus, führen eine Pulsweitenmodulation (PWM) durch, schalten Motoren ein oder aus und steuern die Position von Latten. Zwei Kontrollstationen über der Bühne verfügen über Displays, Systemeinheiten und Trackballs als Zeigegeräte. Die Kontrollstationen sind über Ethernet verbunden. Jede Kontrollstation ist über einen RS485-Kanal mit Steuerblöcken verbunden. Beide Stationen über der Bühne können gleichzeitig zur Steuerung des Systems verwendet werden, es kann jedoch nur eine Station aktiv sein. Die aktive Station wird von einem Bediener ausgewählt. Die zweite Station ist passiv. Bei der passiven Station ist der RS485-Kanal deaktiviert.
Warum Schauspieler?
Aus Sicht der Algorithmen baut das System auf Ereignissen auf. Daten von Sensoren, Aktionen des Bedieners, Ablauf von Timern ... Dies sind alles Beispiele für Ereignisse. Das Akteurmodell eignet sich gut für solche Algorithmen: Akteure verarbeiten eingehende Ereignisse und bilden abhängig von ihrem aktuellen Status einige ausgehende Aktionen. Diese Mechanik ist in SObjectizer sofort verfügbar.
Die Grundprinzipien für solche Systeme sind: Akteure interagieren über asynchrone Nachrichten, Akteure haben Zustände und wechseln von einem Zustand in einen anderen, es werden nur Nachrichten behandelt, die für den aktuellen Zustand von Bedeutung sind.
Es ist interessant, dass Schauspieler in SObjectizer von Arbeitsthreads entkoppelt sind. Dies bedeutet, dass Sie Ihre Akteure zuerst implementieren und debuggen können und erst dann entscheiden, welcher Arbeitsthread für jeden Akteur verwendet wird. Es gibt "Dispatcher", die verschiedene Thread-bezogene Richtlinien implementieren. Beispielsweise gibt es einen Dispatcher, der für jeden Akteur einen separaten Arbeitsthread bereitstellt. Es gibt einen Thread-Pool-Dispatcher, der einen Pool von Worker-Threads mit fester Größe bereitstellt. Es gibt einen Dispatcher, der alle Akteure auf demselben Thread ausführt.
Die Anwesenheit von Dispatchern bietet eine sehr flexible Möglichkeit, ein Akteursystem auf unsere Bedürfnisse abzustimmen. Wir können einige Akteure gruppieren, um an demselben Kontext zu arbeiten. Wir können den Dispatcher-Typ durch nur eine einzige Codezeile ändern. Die Entwickler von SObjectizer sagen, dass das Schreiben eines benutzerdefinierten Dispatchers keine komplexe Aufgabe ist. In diesem Projekt musste jedoch kein eigener Dispatcher geschrieben werden. Alles, was wir brauchten, wurde in SObjectizer gefunden.
Ein weiteres interessantes Merkmal ist die Zusammenarbeit von Schauspielern. Eine Kooperation ist eine Gruppe von Akteuren, die nur dann existieren können, wenn alle Akteure erfolgreich gestartet sind. Eine Zusammenarbeit kann nicht begonnen werden, wenn mindestens einer ihrer Akteure nicht begonnen hat. Es scheint, dass es eine Analogie zwischen den Kooperationen von SObjectizer und Pods von Kubernetes gibt, aber es scheint auch, dass die Kooperationen von SObjectizer früher aufgetreten sind ...
Wenn ein Akteur erstellt wird, wird er zur Zusammenarbeit hinzugefügt (die Zusammenarbeit kann nur einen Akteur enthalten) und ist an einen Dispatcher gebunden. Es ist einfach, Kooperationen und Akteure dynamisch zu erstellen, und die Entwickler von SObjectizer sagen, dass dies eine ziemlich billige Operation ist.
Alle Akteure interagieren über "Message Boxes" (mbox) miteinander. Es ist ein weiteres interessantes und leistungsstarkes Konzept von SObjectizer. Es bietet eine flexible Möglichkeit der Nachrichtenverarbeitung.
Zunächst kann sich hinter einer mbox mehr als ein Nachrichtenempfänger befinden. Es ist sehr hilfreich. Beispielsweise kann es eine mbox geben, die von Sensoren zum Veröffentlichen neuer Daten verwendet wird. Akteure können Abonnements für diese mbox erstellen, und abonnierte Akteure erhalten die gewünschten Daten. Dies ermöglicht das Arbeiten auf "Veröffentlichen / Abonnieren" -Methode.
Zweitens haben die Entwickler des SObjectizers die Möglichkeit einer benutzerdefinierten Mbox-Erstellung ins Auge gefasst. Es ist relativ einfach, eine benutzerdefinierte Mbox mit spezieller Verarbeitung eingehender Nachrichten zu erstellen (z. B. Filtern oder Verteilen auf mehrere Abonnenten basierend auf dem Inhalt der Nachricht).
Es gibt auch eine persönliche Mbox für jeden Schauspieler, und Schauspieler können in Nachrichten an andere Akteure einen Verweis auf diese Mbox übergeben (wodurch direkt auf einen bestimmten Schauspieler geantwortet werden kann).
In unserem Projekt haben wir alle kontrollierten Objekte in acht Gruppen aufgeteilt (eine Gruppe für jede Kontrollbox). Für jede Gruppe wurden drei Worker-Threads erstellt (da nur drei Engines gleichzeitig arbeiten können). Dies ermöglichte uns die Unabhängigkeit zwischen Motorgruppen. Es erlaubte auch, asynchron mit Engines innerhalb jeder Gruppe zu arbeiten.
Es muss erwähnt werden, dass SObjectizer-5 keine Mechanismen für Interprozess- oder / und Netzwerkinteraktion besitzt. Dies ist eine bewusste Entscheidung der Entwickler von SObjectizer. Sie wollten SObjectizer so leicht wie möglich machen. Darüber hinaus bestand die transparente Unterstützung für das Netzwerk in einigen früheren Versionen von SObjectizer, wurde jedoch entfernt. Es hat uns nicht gestört, da ein Mechanismus für die Vernetzung stark von einer Aufgabe, den verwendeten Protokollen und anderen Bedingungen abhängt. Es gibt keine einheitliche universelle Lösung für alle Fälle.
In unserem Fall haben wir unsere alte Bibliothek libuniset2 für die Netzwerk- und Interprozesskommunikation verwendet. Infolgedessen unterstützt libuniset2 die Kommunikation mit Sensoren und Steuerblöcken, und SObjectizer unterstützt Akteure und Interaktionen zwischen Akteuren innerhalb eines einzelnen Prozesses.
Wie ich bereits sagte, gibt es 62 Motoren. Jeder Motor kann an einen FC (Frequenzumrichter) angeschlossen werden; Für die entsprechende Latte kann eine Zielkoordinate angegeben werden. Die Geschwindigkeit der Bewegung der Latte kann ebenfalls angegeben werden. Darüber hinaus hat jeder Motor die folgenden Zustände:
- bereit zu arbeiten;
- verbunden;
- arbeiten;
- Fehlfunktion;
- Verbinden (ein Übergangszustand);
- Trennen (ein Übergangszustand);
Jede Engine wird im System durch einen Akteur dargestellt, der den Übergang zwischen Zuständen implementiert, Daten von Sensoren verarbeitet und Befehle ausgibt. Es ist nicht schwer, in SObjectizer einen Akteur zu erstellen: Erben Sie einfach Ihre Klasse vom Typ so_5::agent_t
. Das erste Argument des Konstruktors des Akteurs sollte vom Typ context_t
. Alle anderen Argumente können nach Wunsch eines Entwicklers definiert werden.
class Drive_A: public so_5::agent_t { public: Drive_A( context_t ctx, ... ); ... }
Ich werde die detaillierte Beschreibung der Klassen und Methoden nicht anzeigen, da es sich nicht um ein Tutorial handelt. Ich möchte nur zeigen, wie einfach das alles in SObjectizer geht (in wenigen Zeilen buchstäblich). Ich möchte Sie daran erinnern, dass SObjectizer eine hervorragende Dokumentation und viele Beispiele enthält.
Was ist der "Zustand" eines Schauspielers? Worüber reden wir?
Die Verwendung von Zuständen und der Übergang zwischen ihnen ist ein "natives Thema" für Steuerungssysteme. Dieses Konzept ist sehr gut für die Ereignisbehandlung. Dieses Konzept wird in SObjectizer auf API-Ebene unterstützt. Zustände werden innerhalb der Klasse des Schauspielers deklariert:
class Drive_A final: public so_5::agent_t { public: Drive_A( context_t ctx, ... ); virtual ~Drive_A();
und dann werden Event-Handler für jeden Zustand definiert. Manchmal ist es notwendig, beim Betreten oder Verlassen eines Staates etwas zu tun. Dies wird auch in SObjectizer über die Handler on_enter / on_exit unterstützt. Es scheint, dass die Entwickler von SObjectizer Hintergrundwissen in der Entwicklung von Steuerungssystemen haben.
Ereignishandler
Ein Ereignishandler ist ein Ort, an dem Ihre Anwendungslogik implementiert ist. Wie bereits erwähnt, wird ein Abonnement für eine bestimmte mbox und einen bestimmten Status erstellt. Wenn ein Akteur keine explizit angegebenen Zustände hat, befindet er sich in einem speziellen "Standardzustand".
Verschiedene Handler können für dasselbe Ereignis in verschiedenen Zuständen definiert werden. Wenn Sie für ein Ereignis keinen Handler definieren, wird dieses Ereignis ignoriert (ein Schauspieler weiß nichts davon).
Es gibt eine einfache Syntax zum Definieren von Ereignishandlern. Sie geben eine Methode an, und es müssen keine zusätzlichen Typen oder Vorlagenparameter angegeben werden. Zum Beispiel:
so_subscribe(drv->so_mbox()) .in(st_base) .event( &Drive_A::on_get_info ) .event( &Drive_A::on_control ) .event( &Drive_A::off_control );
Dies ist ein Beispiel für ein Abonnement von Ereignissen aus einer bestimmten mbox im Status st_base. Es ist erwähnenswert, dass st_base ein Basiszustand für einige andere Zustände ist und dass das Abonnement von abgeleiteten Zuständen geerbt wird. Dieser Ansatz ermöglicht das Kopieren und Einfügen für ähnliche Ereignishandler in verschiedenen Zuständen. Der geerbte Ereignishandler kann jedoch für einen bestimmten Status neu definiert oder ein Ereignis vollständig deaktiviert ("unterdrückt") werden.
Eine andere Möglichkeit, Ereignishandler zu definieren, ist die Verwendung von Lambda-Funktionen. Dies ist eine sehr bequeme Methode, da Ereignishandler häufig nur ein oder zwei Codezeilen enthalten: Senden von etwas an einen Ort oder Statusänderung:
so_subscribe(drv->so_mbox()) .in(st_disconnecting) .event([this](const msg_disconnected_t& m) { ... st_off.activate(); }) .event([this]( const msg_failure_t& m ) { ... st_protection.activate(); });
Diese Syntax sieht am Anfang komplex aus, wird aber erst nach ein paar Tagen aktiver Codierung bekannt und Sie beginnen sie sogar zu mögen. Dies liegt daran, dass die gesamte Logik eines Schauspielers präzise und auf einem Bildschirm platziert werden kann. In dem oben gezeigten Beispiel gibt es Übergänge von st_disconnected zu st_off oder st_protection. Dieser Code ist leicht zu lesen.
Übrigens gibt es für einfache Fälle, in denen nur ein Zustandsübergang erforderlich ist, eine spezielle Syntax:
auto mbox = drv->so_mbox(); st_off .just_switch_to<msg_connected_t>(mbox, st_connected) .just_switch_to<msg_failure_t>(mbox, st_protection) .just_switch_to<msg_on_limit_t>(mbox, st_protection) .just_switch_to<msg_on_t>(mbox, st_on);
Die Kontrolle
Wie ist die Kontrolle organisiert? Wie oben erwähnt, gibt es zwei Kontrollstationen zur Steuerung der Bewegung von Latten. Jede Kontrollstation verfügt über ein Display, ein Zeigegerät (Trackball) und einen Geschwindigkeitssetzer (und wir zählen keinen Computer in der Station und kein zusätzliches Zubehör).
Es gibt zwei Steuermodi: manuell und "Szenariomodus". "Szenariomodus" wird später erläutert, und jetzt sprechen wir über den manuellen Modus. In diesem Modus wählt ein Bediener eine Latte aus, bereitet sie für die Bewegung vor (verbindet den Motor mit einem FC), setzt die Zielmarke für die Latte und wenn die Geschwindigkeit über Null eingestellt ist, beginnt sich die Latte zu bewegen.
Der Geschwindigkeitssetzer ist ein physisches Zubehör in Form eines "Potentiometers mit Griff", aber es wird auch ein virtuelles auf dem Display der Station angezeigt. Je mehr gedreht wird, desto höher ist die Bewegungsgeschwindigkeit. Die Höchstgeschwindigkeit ist auf 1,5 Meter pro Sekunde begrenzt. Der Geschwindigkeitssetzer ist einer für alle Latten. Dies bedeutet, dass sich alle ausgewählten Latten mit der gleichen Geschwindigkeit bewegen. Latten können sich in entgegengesetzte Richtungen bewegen (dies hängt von der Auswahl des Bedieners ab). Es ist offensichtlich, dass es für einen Menschen schwierig ist, mehr als ein paar Latten zu kontrollieren. Aus diesem Grund werden im manuellen Modus nur kleine Gruppen von Latten behandelt. Bediener können Latten gleichzeitig von zwei Kontrollstationen aus steuern. Es gibt also für jede Station einen eigenen Geschwindigkeitsregler.
Aus Sicht der Implementierung gibt es im manuellen Modus keine spezifische Logik. Ein "connect engine" -Befehl geht von der grafischen Oberfläche aus, wird in eine entsprechende Nachricht an einen Akteur umgewandelt und dann von diesem Akteur verarbeitet. Der Akteur wechselt vom Zustand "Aus" in den Zustand "Verbinden" und dann in den Zustand "Verbunden". Ähnliches passiert mit Befehlen zum Positionieren einer Latte und zum Einstellen der Bewegungsgeschwindigkeit. Alle diese Befehle werden in Form von Nachrichten an einen Akteur übergeben. Es ist jedoch erwähnenswert, dass "grafische Oberfläche" und "Steuerungsprozess" separate Prozesse sind und libuniset2 für IPC verwendet wird.
Der Szenariomodus (gibt es wieder Schauspieler?)
In der Praxis wird der manuelle Modus nur für sehr einfache Fälle oder während der Proben verwendet. Der Hauptsteuermodus ist "Szenariomodus". In diesem Modus wird jede Latte gemäß den Szenarioeinstellungen mit einer bestimmten Geschwindigkeit an eine bestimmte Position bewegt. In diesem Modus stehen einem Bediener zwei einfache Befehle zur Verfügung:
- vorbereiten (eine Gruppe von Motoren wird an FC angeschlossen);
- go (Bewegung der Gruppe beginnt).
Das gesamte Szenario ist in "Agenden" unterteilt. Eine "Agenda" beschreibt eine einzelne Bewegung einer Gruppe von Latten. Dies bedeutet, dass eine "Agenda" einige Latten enthält und Zielziele und Geschwindigkeiten für diese enthält. In Wirklichkeit besteht ein Szenario aus Handlungen, Handlungen bestehen aus Bildern, Bilder bestehen aus Agenden und die Agenda besteht aus Zielen für Latten. Aus Sicht der Steuerung spielt dies jedoch keine Rolle, da nur die Tagesordnungen die genauen Parameter der Bewegung der Latte enthalten.
Das Actor Model passt perfekt zu diesem Fall. Wir haben einen "Szenario-Spieler" entwickelt, der eine Gruppe spezieller Schauspieler hervorbringt und sie startet. Wir haben zwei Arten von Akteuren entwickelt: Vollstrecker-Akteure (sie steuern die Bewegung von Latten) und Koordinator-Akteure (sie verteilen Aufgaben zwischen Vollstreckern). Executoren werden nach Bedarf erstellt: Wenn keine freien Executoren vorhanden sind, wird ein neuer Executor erstellt. Der Koordinator verwaltet den Pool der verfügbaren Ausführenden. Infolgedessen sieht das Steuerelement ungefähr so aus:
- ein Bediener lädt ein Szenario;
- "scrollt" es bis zur erforderlichen Tagesordnung;
- drückt die Taste "Vorbereiten" zum richtigen Zeitpunkt. In diesem Moment wird eine Nachricht an einen Koordinator gesendet. Diese Nachricht enthält Daten für jede Latte von der Tagesordnung.
- Der Koordinator überprüft seinen Pool von Testamentsvollstreckern und verteilt die Aufgaben auf freie Testamentsvollstrecker (bei Bedarf werden neue Testamentsvollstrecker erstellt).
- Jeder Executor empfängt eine Aufgabe und führt Vorbereitungsaktionen aus (verbindet eine Engine mit einem FC und wartet dann auf den Befehl "go").
- Der Bediener drückt zum richtigen Zeitpunkt die Taste "Los".
- Der Befehl "go" geht an den Koordinator und verteilt den Befehl auf alle derzeit verwendeten Executoren.
Es gibt einige zusätzliche Parameter in den Tagesordnungen. Wie "Bewegung erst nach N Sekunden Verzögerung starten" oder "Bewegung nur nach einem zusätzlichen Befehl eines Bedieners starten". Aus diesem Grund ist die Liste der Zustände für einen Testamentsvollstrecker ziemlich lang: "Bereit für den nächsten Befehl", "Bereit für die Bewegung", "Verzögerung der Bewegung", "Warten auf den Bedienerbefehl", "Bewegen", "Abgeschlossen", "Misserfolg".
Wenn eine Latte die Zielmarke erfolgreich erreicht hat (oder ein Fehler vorliegt), meldet der Executor dem Koordinator den Abschluss der Aufgabe. Der Koordinator antwortet mit einem Befehl zum Ausschalten der Engine (wenn die Latte nicht mehr an der Agenda teilnimmt) oder sendet eine neue Aufgabe an den Executor. Der Executor schaltet entweder die Engine aus und wechselt in den Wartezustand oder beginnt mit der Verarbeitung des neuen Befehls.
Da SObjectizer über eine sehr durchdachte und bequeme API für die Arbeit mit Status verfügt, erwies sich der Implementierungscode als recht präzise. Zum Beispiel wird eine Verzögerung vor der Bewegung nur durch eine Codezeile beschrieben:
st_delay.time_limit( std::chrono::milliseconds{target->delay()}, st_moving ); st_delay.activate(); ...
Die Methode time_limit
gibt an, wie lange im Status st_moving
soll und welcher Status dann aktiviert werden soll (in diesem Beispiel st_moving
).
Schutzakteure
Natürlich können Fehler auftreten. Es gibt Anforderungen, um diese Fehler korrekt zu behandeln. Schauspieler werden auch für solche Aufgaben verwendet. Schauen wir uns einige Beispiele an:
- Überstromschutz;
- Schutz vor Fehlfunktionen des Sensors;
- Schutz vor Bewegung in die entgegengesetzte Richtung (dies kann passieren, wenn mit Sensoren oder Aktoren etwas nicht stimmt);
- Schutz vor spontanen Bewegungen (ohne Befehl);
- Befehlsausführungskontrolle (die Bewegung einer Latte sollte überprüft werden).
Wir können sehen, dass all diese Fälle autark sind, aber sie sollten gleichzeitig gemeinsam kontrolliert werden. Dies bedeutet, dass ein Fehler auftreten kann. Aber jede Prüfung hat ihre Logik: Manchmal muss eine Zeitüberschreitung überprüft werden, manchmal müssen einige vorherige Werte eines Sensors analysiert werden. Aus diesem Grund wird der Schutz in Form kleiner Akteure umgesetzt. Diese Akteure werden zur Zusammenarbeit mit dem Hauptakteur hinzugefügt, der die Steuerlogik implementiert. Dieser Ansatz ermöglicht das einfache Hinzufügen neuer Schutzfälle: Fügen Sie der Zusammenarbeit einfach einen weiteren Schutzakteur hinzu. Der Code eines solchen Akteurs ist normalerweise kurz und leicht zu verstehen, da er nur eine Funktion implementiert.
Beschützer haben auch mehrere Staaten. Normalerweise werden sie eingeschaltet, wenn ein Motor eingeschaltet wird oder wenn eine Latte ihre Bewegung beginnt. Wenn ein Protektor einen Fehler / eine Fehlfunktion feststellt, veröffentlicht er eine Benachrichtigung (mit Schutzcode und einigen zusätzlichen Details). Der Hauptakteur reagiert auf diese Benachrichtigung und führt die erforderlichen Aktionen aus (z. B. Abstellen des Motors und Umschalten in den geschützten Zustand).
Als Fazit ...
... dieser Artikel ist natürlich kein Durchbruch. Das Akteurmodell wird seit geraumer Zeit in mehreren verschiedenen Systemen eingesetzt. Aber es war meine erste Erfahrung mit dem Actor Model zum Aufbau eines automatischen Steuerungssystems in einem eher kleinen Projekt. Und diese Erfahrung erwies sich als recht erfolgreich. Ich hoffe, ich habe gezeigt, dass Schauspieler gut zu Steuerungsalgorithmen passen: Es gibt buchstäblich überall Orte für Schauspieler.
Wir hatten in früheren Projekten etwas Ähnliches implementiert (ich meine Zustände, Nachrichtenaustausch, Verwaltung von Arbeitsthreads usw.), aber es war kein einheitlicher Ansatz. Durch die Verwendung von SObjectizer haben wir ein kleines, leichtes Tool erhalten, das viele Probleme löst. Wir müssen keine Synchronisationsmechanismen auf niedriger Ebene (wie Mutexe) mehr (explizit) verwenden, es gibt keine manuelle Thread-Verwaltung, keine handgeschriebenen Zustandsdiagramme mehr. All dies wird vom Framework bereitgestellt, logisch verbunden und in Form einer praktischen API ausgedrückt, aber Sie verlieren nicht die Kontrolle über Details. Es war also eine aufregende Erfahrung. Wenn Sie immer noch Zweifel haben, empfehle ich Ihnen, sich insbesondere das Actor Model und den SObjectizer anzusehen . Es hinterlässt positive Emotionen.
Das Actor Model funktioniert wirklich! Besonders im Theater.
Originalartikel in russischer Sprache