Library/Application: switch SubsystemRunner_test
This commit is contained in:
parent
d879ae7fbd
commit
6cd16a61a6
4 changed files with 141 additions and 76 deletions
|
|
@ -67,17 +67,17 @@ namespace thread{
|
|||
|
||||
|
||||
void
|
||||
ThreadWrapper::markThreadStart()
|
||||
ThreadWrapper::markThreadStart (string id)
|
||||
{
|
||||
TRACE (thread, "%s", lifecycleMsg ("start...", threadID_).c_str());
|
||||
TRACE (thread, "%s", lifecycleMsg ("start...", id).c_str());
|
||||
setThreadName();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ThreadWrapper::markThreadEnd()
|
||||
ThreadWrapper::markThreadEnd(string id)
|
||||
{
|
||||
TRACE (thread, "%s", lifecycleMsg ("finished.", threadID_).c_str());
|
||||
TRACE (thread, "%s", lifecycleMsg ("finished.", id).c_str());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -163,8 +163,8 @@ namespace lib {
|
|||
/** detect if the currently executing code runs within this thread */
|
||||
bool invokedWithinThread() const;
|
||||
|
||||
void markThreadStart();
|
||||
void markThreadEnd ();
|
||||
void markThreadStart(string);
|
||||
void markThreadEnd (string);
|
||||
void setThreadName ();
|
||||
void waitGracePeriod() noexcept;
|
||||
};
|
||||
|
|
@ -268,9 +268,10 @@ namespace lib {
|
|||
void
|
||||
invokeThreadFunction (ARGS&& ...args)
|
||||
{
|
||||
Policy::markThreadStart();
|
||||
string id{Policy::threadID_}; // local copy
|
||||
Policy::markThreadStart(id);
|
||||
Policy::perform_thread_function (forward<ARGS> (args)...);
|
||||
Policy::markThreadEnd();
|
||||
Policy::markThreadEnd(id);
|
||||
Policy::handle_end_of_thread();
|
||||
}
|
||||
|
||||
|
|
@ -347,7 +348,10 @@ namespace lib {
|
|||
using ThreadLifecycle::ThreadLifecycle;
|
||||
|
||||
/** allow to detach explicitly — independent from thread-function's state
|
||||
* @warning ensure that thread function only uses storage within its own scope
|
||||
* @warning this function is borderline dangerous; it might be acceptable
|
||||
* in a situation where the thread totally manages itself and the
|
||||
* thread object is maintained in a unique_ptr. You must ensure that
|
||||
* the thread function only uses storage within its own scope.
|
||||
*/
|
||||
void detach() { ThreadLifecycle::handle_end_of_thread(); }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@
|
|||
* *****************************************************/
|
||||
|
||||
/** @file subsystem-runner-test.cpp
|
||||
** unit test \ref SubsystemRunner_test
|
||||
** The \ref SubsystemRunner_test performs various scenarios
|
||||
** regarding start, stop and failure of _Subsystems._ Its primary
|
||||
** purpose is to cover the \ref SubsystemRunner.
|
||||
*/
|
||||
|
||||
|
||||
|
|
@ -32,22 +34,28 @@
|
|||
#include "common/option.hpp"
|
||||
|
||||
#include "lib/symbol.hpp"
|
||||
#include "vault/thread-wrapper.hpp"
|
||||
#include "lib/thread.hpp"
|
||||
#include "lib/sync-barrier.hpp"
|
||||
#include "lib/query-util.hpp"
|
||||
#include "lib/format-cout.hpp"
|
||||
#include "lib/error.hpp"
|
||||
#include "lib/util.hpp"
|
||||
#include "lib/sync.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
|
||||
using std::bind;
|
||||
using util::isnil;
|
||||
using util::cStr;
|
||||
using test::Test;
|
||||
using lib::Literal;
|
||||
using lib::query::extractID;
|
||||
using vault::Thread;
|
||||
using lib::Thread;
|
||||
using std::unique_ptr;
|
||||
using std::atomic_bool;
|
||||
using std::this_thread::sleep_for;
|
||||
using std::chrono::milliseconds;
|
||||
|
||||
|
||||
namespace lumiera {
|
||||
|
|
@ -95,45 +103,48 @@ namespace test {
|
|||
class MockSys
|
||||
: public lumiera::Subsys
|
||||
{
|
||||
Literal id_;
|
||||
const string id_;
|
||||
const string spec_;
|
||||
|
||||
volatile bool isUp_;
|
||||
volatile bool didRun_;
|
||||
volatile bool started_;
|
||||
volatile bool termRequest_;
|
||||
int running_duration_;
|
||||
atomic_bool isUp_{false};
|
||||
atomic_bool didRun_{false};
|
||||
atomic_bool started_{false};
|
||||
atomic_bool termRequest_{false};
|
||||
int running_duration_{0};
|
||||
|
||||
lib::SyncBarrier barrier_{};
|
||||
unique_ptr<Thread> thread_{};
|
||||
|
||||
bool
|
||||
shouldStart (lumiera::Option&) override
|
||||
{
|
||||
string startSpec (extractID ("start",spec_));
|
||||
return "true" ==startSpec
|
||||
|| "fail" ==startSpec
|
||||
|| "throw"==startSpec;
|
||||
or "fail" ==startSpec
|
||||
or "throw"==startSpec;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
start (lumiera::Option&, Subsys::SigTerm termination) override
|
||||
{
|
||||
CHECK (!(isUp_|started_|didRun_), "attempt to start %s twice!", cStr(*this));
|
||||
CHECK (not (isUp_ or started_ or didRun_), "attempt to start %s twice!", cStr(*this));
|
||||
|
||||
string startSpec (extractID ("start",spec_));
|
||||
CHECK (!isnil (startSpec));
|
||||
CHECK (not isnil (startSpec));
|
||||
|
||||
if ("true"==startSpec) //----simulate successful subsystem start
|
||||
{
|
||||
CHECK (!started_);
|
||||
CHECK (not started_);
|
||||
|
||||
Thread (id_, bind (&MockSys::run, this, termination))
|
||||
.sync(); // run-status handshake
|
||||
// start »Subsystem operation« in a dedicated thread....
|
||||
thread_.reset (new Thread{id_, &MockSys::run, this, termination});
|
||||
barrier_.sync(); //---run-status handshake
|
||||
|
||||
CHECK (started_);
|
||||
}
|
||||
else
|
||||
if ("fail"==startSpec) //----not starting, incorrectly reporting success
|
||||
if ("fail"==startSpec) //---not starting, incorrectly reporting success
|
||||
return true;
|
||||
else
|
||||
if ("throw"==startSpec) //---starting flounders
|
||||
|
|
@ -171,13 +182,15 @@ namespace test {
|
|||
run (Subsys::SigTerm termination)
|
||||
{
|
||||
string runSpec (extractID ("run",spec_));
|
||||
CHECK (!isnil (runSpec));
|
||||
CHECK (not isnil (runSpec));
|
||||
|
||||
// run-status handshake
|
||||
started_ = true;
|
||||
isUp_ = ("true"==runSpec || "throw"==runSpec);
|
||||
didRun_ = ("false"!=runSpec); // includes "fail" and "throw"
|
||||
lumiera_thread_sync ();
|
||||
|
||||
// coordinate startup with controlling thread
|
||||
barrier_.sync();
|
||||
|
||||
if (isUp_) //-------------actually enter running state for some time
|
||||
{
|
||||
|
|
@ -186,9 +199,9 @@ namespace test {
|
|||
|
||||
INFO (test, "thread %s now running....", cStr(*this));
|
||||
|
||||
while (!shouldTerminate())
|
||||
while (not shouldTerminate())
|
||||
{
|
||||
usleep (1000*TICK_DURATION_ms);
|
||||
sleep_for (milliseconds{TICK_DURATION_ms});
|
||||
running_duration_ -= TICK_DURATION_ms;
|
||||
}
|
||||
|
||||
|
|
@ -221,16 +234,11 @@ namespace test {
|
|||
|
||||
public:
|
||||
MockSys(Literal id, Literal spec)
|
||||
: id_(id),
|
||||
spec_(spec),
|
||||
isUp_(false),
|
||||
didRun_(false),
|
||||
started_(false),
|
||||
termRequest_(false),
|
||||
running_duration_(0)
|
||||
: id_(id)
|
||||
, spec_(spec)
|
||||
{ }
|
||||
|
||||
~MockSys() { }
|
||||
~MockSys() { }
|
||||
|
||||
operator string () const { return "MockSys(\""+id_+"\")"; }
|
||||
|
||||
|
|
@ -284,14 +292,14 @@ namespace test {
|
|||
|
||||
MockSys unit ("one", "start(true), run(true).");
|
||||
SubsystemRunner runner(dummyOpt);
|
||||
CHECK (!unit.isRunning());
|
||||
CHECK (!unit.didRun());
|
||||
CHECK (not unit.isRunning());
|
||||
CHECK (not unit.didRun());
|
||||
|
||||
runner.maybeRun (unit);
|
||||
bool emergency = runner.wait();
|
||||
|
||||
CHECK (!emergency);
|
||||
CHECK (!unit.isRunning());
|
||||
CHECK (not emergency);
|
||||
CHECK (not unit.isRunning());
|
||||
CHECK (unit.didRun());
|
||||
}
|
||||
|
||||
|
|
@ -317,22 +325,23 @@ namespace test {
|
|||
SubsystemRunner runner(dummyOpt);
|
||||
|
||||
runner.maybeRun (unit1); // this one doesn't start at all, which isn't considered an error
|
||||
CHECK (not unit1.didRun());
|
||||
|
||||
VERIFY_ERROR (TEST, runner.maybeRun (unit2) );
|
||||
VERIFY_ERROR (LOGIC, runner.maybeRun (unit3) ); // incorrect behaviour trapped
|
||||
VERIFY_ERROR (LOGIC, runner.maybeRun (unit4) ); // detected that the subsystem didn't come up
|
||||
|
||||
usleep (DELAY_FOR_FLOUNDERING_THRAD_ms * 1000); // preempt to allow unit4 to go away
|
||||
sleep_for (milliseconds{DELAY_FOR_FLOUNDERING_THRAD_ms}); // preempt to allow unit4 to go away
|
||||
runner.wait();
|
||||
|
||||
CHECK (!unit1.isRunning());
|
||||
CHECK (!unit2.isRunning());
|
||||
CHECK (!unit3.isRunning());
|
||||
CHECK (!unit4.isRunning());
|
||||
CHECK (!unit1.didRun());
|
||||
CHECK (!unit2.didRun());
|
||||
CHECK (!unit3.didRun());
|
||||
CHECK ( unit4.didRun()); // ...but it failed immediately
|
||||
CHECK (not unit1.isRunning());
|
||||
CHECK (not unit2.isRunning());
|
||||
CHECK (not unit3.isRunning());
|
||||
CHECK (not unit4.isRunning());
|
||||
CHECK (not unit1.didRun());
|
||||
CHECK (not unit2.didRun());
|
||||
CHECK (not unit3.didRun());
|
||||
CHECK (unit4.didRun()); // ...but it failed immediately
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -347,8 +356,8 @@ namespace test {
|
|||
runner.maybeRun (unit);
|
||||
bool emergency = runner.wait();
|
||||
|
||||
CHECK (emergency); // emergency state got propagated
|
||||
CHECK (!unit.isRunning());
|
||||
CHECK (emergency == true); // emergency state was propagated
|
||||
CHECK (not unit.isRunning());
|
||||
CHECK (unit.didRun());
|
||||
}
|
||||
|
||||
|
|
@ -376,11 +385,11 @@ namespace test {
|
|||
|
||||
bool emergency = runner.wait();
|
||||
|
||||
CHECK (!emergency);
|
||||
CHECK (!unit1.isRunning());
|
||||
CHECK (!unit2.isRunning());
|
||||
CHECK (!unit3.isRunning());
|
||||
CHECK (!unit4.isRunning());
|
||||
CHECK (not emergency);
|
||||
CHECK (not unit1.isRunning());
|
||||
CHECK (not unit2.isRunning());
|
||||
CHECK (not unit3.isRunning());
|
||||
CHECK (not unit4.isRunning());
|
||||
CHECK (unit1.didRun());
|
||||
CHECK (unit2.didRun());
|
||||
CHECK (unit3.didRun());
|
||||
|
|
@ -404,21 +413,21 @@ namespace test {
|
|||
SubsystemRunner runner(dummyOpt);
|
||||
|
||||
VERIFY_ERROR (STATE, runner.maybeRun (unit4) ); // failure to bring up prerequisites is detected
|
||||
CHECK ( unit1.isRunning());
|
||||
CHECK ( unit2.isRunning());
|
||||
CHECK (!unit3.isRunning());
|
||||
CHECK ( unit1.isRunning());
|
||||
CHECK ( unit2.isRunning());
|
||||
CHECK (not unit3.isRunning());
|
||||
// shutdown has been triggered for unit4, but may require some time
|
||||
|
||||
bool emergency = runner.wait();
|
||||
|
||||
CHECK (!emergency); // no problems with the subsystems actually running...
|
||||
CHECK (!unit1.isRunning());
|
||||
CHECK (!unit2.isRunning());
|
||||
CHECK (!unit3.isRunning());
|
||||
CHECK (!unit4.isRunning());
|
||||
CHECK ( unit1.didRun());
|
||||
CHECK ( unit2.didRun());
|
||||
CHECK (!unit3.didRun());
|
||||
CHECK (not emergency); // no problems with the subsystems actually running...
|
||||
CHECK (not unit1.isRunning());
|
||||
CHECK (not unit2.isRunning());
|
||||
CHECK (not unit3.isRunning());
|
||||
CHECK (not unit4.isRunning());
|
||||
CHECK ( unit1.didRun());
|
||||
CHECK ( unit2.didRun());
|
||||
CHECK (not unit3.didRun());
|
||||
// can't say for sure if unit4 actually did run
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -79295,7 +79295,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
|||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1695394188133" ID="ID_1229328590" MODIFIED="1696015383325" TEXT="Umbau">
|
||||
<icon BUILTIN="pencil"/>
|
||||
<node COLOR="#338800" CREATED="1695597011139" FOLDED="true" ID="ID_125138411" MODIFIED="1695859127749" TEXT="Kopie des bestehenden Thread-Wrappers umschreiben">
|
||||
<node COLOR="#338800" CREATED="1695597011139" FOLDED="true" ID="ID_125138411" MODIFIED="1696357528355" TEXT="Kopie des bestehenden Thread-Wrappers umschreiben">
|
||||
<linktarget COLOR="#6ebe5a" DESTINATION="ID_125138411" ENDARROW="Default" ENDINCLINATION="43;-18;" ID="Arrow_ID_195305087" SOURCE="ID_215388471" STARTARROW="None" STARTINCLINATION="-113;6;"/>
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node COLOR="#435e98" CREATED="1695597146529" ID="ID_938038908" MODIFIED="1695948178166" TEXT="std::thread liefert fast alle Funktionalität">
|
||||
|
|
@ -79531,7 +79531,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
|||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1695597337839" FOLDED="true" ID="ID_1776858144" MODIFIED="1695859098234" TEXT="detach() / join-Differenzierung">
|
||||
<node COLOR="#338800" CREATED="1695597337839" FOLDED="true" ID="ID_1776858144" MODIFIED="1696357531093" TEXT="detach() / join-Differenzierung">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node COLOR="#338800" CREATED="1695647408544" ID="ID_805151848" MODIFIED="1695651308867" TEXT="isValid() bzw. bool-Check">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
|
|
@ -79956,6 +79956,54 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
|||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1696357544203" FOLDED="true" ID="ID_1208501979" MODIFIED="1696358077856" TEXT="detach() API?">
|
||||
<icon BUILTIN="clanbomber"/>
|
||||
<node CREATED="1696357565724" ID="ID_332128317" MODIFIED="1696358075753" TEXT="ziemlich gefährlich — eigentlich sollte es das nicht geben">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
Hier zeigt sich ein Widerspruch in den Konzepten selber an
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
ein »launch-only«-Thread sollte sich eigentlich komplett abkoppeln
|
||||
</li>
|
||||
<li>
|
||||
aber andererseits soll das Thead-Objekt auch den zugehörigen State kapseln, muß also irgendwo existieren
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node CREATED="1696357587969" ID="ID_857278701" MODIFIED="1696357619392" TEXT="öffnet eine »Hintertür« : mann kann detac() aufrufen und dann das Thread-Objekt löschen">
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
<node CREATED="1696357622045" ID="ID_1238132894" MODIFIED="1696357864016" TEXT="genau das brauche ich für das bestehende Design vom Session-Thread (DispatcherLoop)">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
dieses habe ich als RAII-Objekt angelegt, und der zugehörige Unique-Ptr markiert gleichzeitig den Lifecycle-State; das hat zur Konsequenz, daß der Session-Thread am Ende <i>selber sein eigenes Objekt zerstören muß</i>
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<arrowlink COLOR="#8c91b2" DESTINATION="ID_876148747" ENDARROW="Default" ENDINCLINATION="521;-2055;" ID="Arrow_ID_638014906" STARTARROW="None" STARTINCLINATION="295;9;"/>
|
||||
<icon BUILTIN="smiley-oh"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#190f69" CREATED="1696357865684" FOLDED="true" ID="ID_1810679254" MODIFIED="1696357973110" TEXT="Aua: die threadID_ wird für das Shutdown-Log gebraucht">
|
||||
<icon BUILTIN="stop-sign"/>
|
||||
<node CREATED="1696357887177" ID="ID_1835825008" MODIFIED="1696357893004" TEXT="also dafür eine lokale Kopie"/>
|
||||
<node CREATED="1696357893592" ID="ID_1813261494" MODIFIED="1696357915937" TEXT="die threadImpl_ ist eigentlich auch dangling">
|
||||
<icon BUILTIN="smily_bad"/>
|
||||
</node>
|
||||
<node CREATED="1696357936249" ID="ID_854400443" MODIFIED="1696357959467" TEXT="aber sie wird zuletzt vom Thread-Objekt gebraucht"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1695597475293" FOLDED="true" ID="ID_1100470933" MODIFIED="1695859224044" TEXT="Aufruf der Thread-Funktion">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
|
|
@ -80829,7 +80877,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
|||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1696029465122" ID="ID_1515483274" MODIFIED="1696029516437" TEXT="subsystem-runner-test.cpp (/zLumi/tests/core/application)"/>
|
||||
<node COLOR="#338800" CREATED="1696029465122" ID="ID_1515483274" MODIFIED="1696357491529" TEXT="SubsystemRunner_test">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1696029465124" ID="ID_1556068052" MODIFIED="1696029514685" TEXT="typed-counter-test.cpp (/zLumi/tests/basics)"/>
|
||||
<node CREATED="1696029465120" ID="ID_1705540772" MODIFIED="1696029510893" TEXT="call-queue-test.cpp (/zLumi/tests/basics)"/>
|
||||
<node CREATED="1696029465120" ID="ID_1782746519" MODIFIED="1696029465120" TEXT="bus-term-test.cpp (/zLumi/tests/stage)"/>
|
||||
|
|
@ -80846,8 +80896,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
|||
</node>
|
||||
<node CREATED="1696029465121" ID="ID_786350998" MODIFIED="1696029583638" TEXT="gtk-lumiera.cpp"/>
|
||||
<node CREATED="1696029465122" ID="ID_871343805" MODIFIED="1696029593893" TEXT="output-director.cpp"/>
|
||||
<node CREATED="1696029465122" ID="ID_556208204" MODIFIED="1696171137865" TEXT="steam-dispatcher.cpp">
|
||||
<node COLOR="#338800" CREATED="1696029465122" FOLDED="true" ID="ID_556208204" MODIFIED="1696358985114" TEXT="steam-dispatcher.cpp">
|
||||
<linktarget COLOR="#71be6f" DESTINATION="ID_556208204" ENDARROW="Default" ENDINCLINATION="1038;-401;" ID="Arrow_ID_1257115855" SOURCE="ID_1718267866" STARTARROW="None" STARTINCLINATION="602;40;"/>
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node COLOR="#338800" CREATED="1696039703074" ID="ID_796238065" MODIFIED="1696171045349" TEXT="Refactoring wie üblich">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1696039723008" ID="ID_932291373" MODIFIED="1696039728651" TEXT="Thread wird ein Member-Feld"/>
|
||||
|
|
@ -80885,6 +80936,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
|||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<linktarget COLOR="#8c91b2" DESTINATION="ID_876148747" ENDARROW="Default" ENDINCLINATION="521;-2055;" ID="Arrow_ID_638014906" SOURCE="ID_1238132894" STARTARROW="None" STARTINCLINATION="295;9;"/>
|
||||
<icon BUILTIN="help"/>
|
||||
<node CREATED="1696172561670" ID="ID_620791350" MODIFIED="1696172587782" TEXT="running state ≙ DispatcherLoop-Objekt existiert"/>
|
||||
<node COLOR="#435e98" CREATED="1696172623600" ID="ID_1471623002" MODIFIED="1696175317007" TEXT="damit wollte ich eine sichere Kopplung herstellen">
|
||||
|
|
@ -92333,8 +92385,8 @@ class Something
|
|||
<node CREATED="1694795411496" ID="ID_560488135" MODIFIED="1694795415392" TEXT="Expanding / Collapsing"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696109921291" ID="ID_853481552" LINK="https://issues.lumiera.org/ticket/1341" MODIFIED="1696110190077" TEXT="#110 und #1341 Clarify Error handling scheme">
|
||||
<linktarget COLOR="#fdcfce" DESTINATION="ID_853481552" ENDARROW="Default" ENDINCLINATION="-959;56;" ID="Arrow_ID_275745687" SOURCE="ID_1712639748" STARTARROW="None" STARTINCLINATION="-1085;66;"/>
|
||||
<linktarget COLOR="#fdcfce" DESTINATION="ID_853481552" ENDARROW="Default" ENDINCLINATION="-959;56;" ID="Arrow_ID_1749710199" SOURCE="ID_1320347721" STARTARROW="None" STARTINCLINATION="-1130;86;"/>
|
||||
<linktarget COLOR="#fdcfce" DESTINATION="ID_853481552" ENDARROW="Default" ENDINCLINATION="-959;56;" ID="Arrow_ID_275745687" SOURCE="ID_1712639748" STARTARROW="None" STARTINCLINATION="-1085;66;"/>
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1694440562933" ID="ID_1529688085" LINK="https://issues.lumiera.org/ticket/1338" MODIFIED="1694795034628" TEXT="#1338 Non-standard Play-processing">
|
||||
|
|
|
|||
Loading…
Reference in a new issue