Segmentation: consider preliminary data structure
...and consider how that can be extended later into the full structure, which has to support a transactional switch
This commit is contained in:
parent
d58174db4d
commit
d73b316ead
10 changed files with 280 additions and 16 deletions
|
|
@ -296,6 +296,7 @@ namespace time {
|
|||
return reinterpret_cast<Duration const&> (maxDelta);
|
||||
}();
|
||||
|
||||
const TimeSpan TimeSpan::ALL {Time::MIN, Duration::MAX};
|
||||
|
||||
}} // namespace lib::Time
|
||||
|
||||
|
|
|
|||
|
|
@ -600,6 +600,8 @@ namespace time {
|
|||
|
||||
TimeSpan conform() const; ///< @return a copy conformed to time domain limits
|
||||
|
||||
static const TimeSpan ALL;
|
||||
|
||||
Duration&
|
||||
duration()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -77,11 +77,11 @@ namespace fixture {
|
|||
: util::NonCopyable
|
||||
{
|
||||
protected:
|
||||
/////////////////////////////////////////////////TODO: placeholder code
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #725 : placeholder code
|
||||
list<ExplicitPlacement> content_;
|
||||
unique_ptr<Segmentation> partitioning_;
|
||||
|
||||
/////////////////////////////////////////////////TICKET #573 who creates the fixture?
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #573 who creates the fixture?
|
||||
|
||||
public:
|
||||
list<ExplicitPlacement> & getPlaylistForRender () ;
|
||||
|
|
|
|||
|
|
@ -43,31 +43,38 @@ namespace steam {
|
|||
namespace fixture {
|
||||
|
||||
using mobject::ExplicitPlacement;
|
||||
using lib::time::TimeSpan;
|
||||
|
||||
/**
|
||||
* For the purpose of building and rendering, the fixture (for each timeline)
|
||||
* is partitioned such that each segment is <i>structurally constant</i>.
|
||||
* For each segment there is a RenderGraph (unit of the render engine) which
|
||||
* is able to render all ExitNodes for this segment.
|
||||
* is partitioned such that each segment is _structurally constant._
|
||||
* For each segment there is a RenderGraph (unit of the render engine)
|
||||
* which is able to render all ExitNodes for this segment.
|
||||
*
|
||||
* @ingroup fixture
|
||||
* @todo 1/2012 Just a Placeholder. The real thing is not yet implemented.
|
||||
* @todo WIP-WIP as of 4/2023 -- about to pull up the engine backbone
|
||||
* @see http://lumiera.org/wiki/renderengine.html#Fixture
|
||||
*/
|
||||
class Segment
|
||||
{
|
||||
protected:
|
||||
typedef lib::time::TimeSpan Span;
|
||||
|
||||
/** begin of this timeline segment. */
|
||||
Span span_;
|
||||
TimeSpan span_;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #725 : placeholder code
|
||||
/** relevant MObjects comprising this segment. */
|
||||
list<ExplicitPlacement> elements;
|
||||
// TODO: actually necessary??
|
||||
// TODO: ownership??
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #725 : placeholder code
|
||||
public:
|
||||
Segment (TimeSpan covered =TimeSpan::ALL)
|
||||
: span_{covered}
|
||||
{ }
|
||||
|
||||
/////////////////////////////////////////////////TODO: placeholder code
|
||||
// default copy acceptable
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@
|
|||
|
||||
|
||||
#include "steam/fixture/segment.hpp"
|
||||
#include "lib/nocopy.hpp"
|
||||
|
||||
#include <list>
|
||||
|
||||
|
|
@ -72,14 +73,28 @@ namespace fixture {
|
|||
* @see http://lumiera.org/wiki/renderengine.html#Fixture
|
||||
*/
|
||||
class Segmentation
|
||||
: util::NonCopyable
|
||||
{
|
||||
/////////////////////////////////////////////////TODO: placeholder code
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1243 : preliminary implementation
|
||||
|
||||
/** segments of the engine in ordered sequence. */
|
||||
list<Segment> segments_;
|
||||
|
||||
public:
|
||||
virtual ~Segmentation(); ///< this is an interface
|
||||
|
||||
protected:
|
||||
Segmentation() ///< there is always a single cover-all Segment initially
|
||||
: segments_{1}
|
||||
{ }
|
||||
|
||||
public:
|
||||
size_t
|
||||
size() const
|
||||
{
|
||||
return segments_.size();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,16 @@ return: 0
|
|||
END
|
||||
|
||||
|
||||
PLANNED "Setup for render job planning" JobPlanningSetup_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
||||
PLANNED "Mock support for render job planning" MockSupport_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
||||
PLANNED "Proc Node basics" NodeBasic_test <<END
|
||||
END
|
||||
|
||||
|
|
|
|||
|
|
@ -182,11 +182,11 @@ namespace test {
|
|||
|
||||
public:
|
||||
MockSegmentation()
|
||||
: MockSegmentation{MakeRec().genNode()}
|
||||
: tickets_{}
|
||||
{ }
|
||||
|
||||
MockSegmentation (std::initializer_list<GenNode> specs)
|
||||
: tickets_{}
|
||||
: MockSegmentation{}
|
||||
{
|
||||
UNIMPLEMENTED ("populate mock sequence structure");
|
||||
}
|
||||
|
|
|
|||
139
tests/core/steam/engine/mock-support-test.cpp
Normal file
139
tests/core/steam/engine/mock-support-test.cpp
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
MockSupport(Test) - verify test support for fixture and job dispatch
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2023, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
|
||||
* *****************************************************/
|
||||
|
||||
/** @file mock-support-test.cpp
|
||||
** unit test \ref MockSupport_test
|
||||
*/
|
||||
|
||||
|
||||
#include "lib/test/run.hpp"
|
||||
#include "lib/error.hpp"
|
||||
#include "steam/engine/mock-dispatcher.hpp"
|
||||
#include "vault/engine/dummy-job.hpp"
|
||||
|
||||
#include "lib/format-cout.hpp"///////////////////////TODO
|
||||
|
||||
//#include "steam/engine/job-planning.hpp"
|
||||
|
||||
//#include <ctime>
|
||||
|
||||
using test::Test;
|
||||
//using std::rand;
|
||||
|
||||
|
||||
namespace steam {
|
||||
namespace engine{
|
||||
namespace test {
|
||||
|
||||
using vault::engine::DummyJob;
|
||||
|
||||
namespace { // test fixture...
|
||||
|
||||
} // (End) test fixture
|
||||
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* @test validate test support for render job planning and dispatch.
|
||||
* - creating and invoking mock render jobs
|
||||
* - a mocked JobTicket, generating mock render jobs
|
||||
* - configurable test setup for a mocked Segmentation datastructure
|
||||
*
|
||||
* @todo WIP-WIP-WIP 4/2023
|
||||
*
|
||||
* @see JobPlanningSetup_test
|
||||
* @see Dispatcher
|
||||
* @see vault::engine::Job
|
||||
* @see steam::fixture::Segmentation
|
||||
*/
|
||||
class MockSupport_test : public Test
|
||||
{
|
||||
|
||||
virtual void
|
||||
run (Arg)
|
||||
{
|
||||
simpleUsage();
|
||||
verify_MockJob();
|
||||
verify_MockJobTicket();
|
||||
verify_MockSegmentation();
|
||||
}
|
||||
|
||||
|
||||
/** @test simple usage example of the test helpers */
|
||||
void
|
||||
simpleUsage()
|
||||
{
|
||||
TODO ("simple usage example");
|
||||
}
|
||||
|
||||
|
||||
/** @test document and verify usage of a mock render job */
|
||||
void
|
||||
verify_MockJob()
|
||||
{
|
||||
Time nominalTime = lib::test::randTime();
|
||||
int additionalKey = rand() % 5000;
|
||||
Job mockJob = DummyJob::build (nominalTime, additionalKey);
|
||||
CHECK (mockJob.getNominalTime() == nominalTime);
|
||||
CHECK (not DummyJob::was_invoked (mockJob));
|
||||
|
||||
mockJob.triggerJob();
|
||||
CHECK ( DummyJob::was_invoked (mockJob));
|
||||
CHECK (RealClock::wasRecently (DummyJob::invocationTime (mockJob)));
|
||||
CHECK (nominalTime == DummyJob::invocationNominalTime (mockJob) );
|
||||
CHECK (additionalKey == DummyJob::invocationAdditionalKey(mockJob));
|
||||
|
||||
Time prevInvocation = DummyJob::invocationTime (mockJob);
|
||||
mockJob.triggerJob();
|
||||
CHECK (prevInvocation < DummyJob::invocationTime (mockJob)); // invoked again, recorded new invocation time
|
||||
CHECK (nominalTime == DummyJob::invocationNominalTime (mockJob) ); // all other Job parameter recorded again unaltered
|
||||
CHECK (additionalKey == DummyJob::invocationAdditionalKey(mockJob));
|
||||
}
|
||||
|
||||
|
||||
/** @test document and verify usage of a mock JobTicket for frame dispatch */
|
||||
void
|
||||
verify_MockJobTicket()
|
||||
{
|
||||
MockJobTicket mockTick;
|
||||
CHECK (mockTick.discoverPrerequisites().empty());
|
||||
TODO ("cover details of MockJobTicket");
|
||||
}
|
||||
|
||||
|
||||
/** @test document and verify usage of a complete mocked Segmentation to back frame dispatch */
|
||||
void
|
||||
verify_MockSegmentation()
|
||||
{
|
||||
MockSegmentation mockSeg;
|
||||
CHECK (1 == mockSeg.size());
|
||||
TODO ("cover details of MockSegmentation");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** Register this test class... */
|
||||
LAUNCHER (MockSupport_test, "unit engine");
|
||||
|
||||
|
||||
|
||||
}}} // namespace steam::engine::test
|
||||
|
|
@ -2303,7 +2303,7 @@ Any feed corresponds to a specific ModelPort, which in turn typically correspond
|
|||
When starting playback or render, a play process (with a PlayController front-end for client code) is established to coordinate the processing. This ongoing data production might encompass multiple media streams, i.e. multiple feeds pulled from several model ports and delivered into several [[output slots|OutputSlot]]. Each feed in turn might carry structured MultichannelMedia, and is thus further structured into individual [[streams of calculation|CalcStream]]. Since the latter are //stateless descriptors,// while the player and play process obviously is stateful, it's the feed's role to mediate between a state-based (procedural) and a stateless (functional and parallelised) organisation model -- ensuring a seamless data feed even during modification of the playback parameters.
|
||||
</pre>
|
||||
</div>
|
||||
<div title="Fixture" modifier="Ichthyostega" created="200706220324" modified="201201281945" tags="def spec Builder Model">
|
||||
<div title="Fixture" modifier="Ichthyostega" created="200706220324" modified="202304271631" tags="def spec Builder Model" changecount="2">
|
||||
<pre>a specially configured view -- joining together high-level and low-level model.
|
||||
The Fixture acts as //isolation layer// between the two models, and as //backbone to attach the render nodes.//
|
||||
* all MObjects have their position, length and configuration set up ready for rendering.
|
||||
|
|
@ -2335,9 +2335,13 @@ The fixture is like a grid, where one dimension is given by the [[model ports|Mo
|
|||
;Exit Nodes
|
||||
:Each segment holds an ExitNode for each relevant ModelPort of the corresponding timeline.
|
||||
:Thus the exit nodes are keyed by ~Pipe-ID as well (and consequently have a distinct [[stream type|StreamType]]) -- each model port coresponds to {{{<number_of_segments>}}} separate exit nodes, but of course an exit node may be //mute//&nbsp; in some segmehts.
|
||||
|
||||
!!!Dependencies
|
||||
At architecture level, the Fixture is seen as //interface// between Steam-Layer and Vault-Layer.
|
||||
This raises the question: {{red{(WIP 4/23) where is the fixture data structure defined, in terms of code dependencies?}}}
|
||||
</pre>
|
||||
</div>
|
||||
<div title="FixtureDatastructure" modifier="Ichthyostega" created="201012162304" modified="201112171816" tags="spec Builder">
|
||||
<div title="FixtureDatastructure" modifier="Ichthyostega" created="201012162304" modified="202304271626" tags="spec Builder" changecount="4">
|
||||
<pre>Generally speaking, the datastructure to implement the ''Fixture'' (&rarr; see a more general description [[here|Fixture]]) is comprised of a ModelPortRegistry and a set of [[segmentations|Segmentation]] per Timeline.
|
||||
This page focusses on the actual data structure and usage details on that level. See also &rarr; [[storage|FixtureStorage]] considerations.
|
||||
|
||||
|
|
@ -2345,6 +2349,7 @@ This page focusses on the actual data structure and usage details on that level.
|
|||
A key point to note is the fact that the fixture is frequently [[re-built|BuildFixture]] by the [[Builder]], while render processes may be going on in parallel. Thus, when a build process is finished, a transactional ''commit'' happens to ''hot swap'' the new parts of the model. This is complemented by a clean-up of tainted render processes; finally, storage can be reclaimed.
|
||||
|
||||
To support this usage pattern, the Fixture implementation makes use of the [[PImpl pattern|http://c2.com/cgi/wiki?PimplIdiom]]
|
||||
Ongoing [[Builder]] activity especially can remould the Segmentation on a copy of the implementation structure, which is then swapped as a whole.
|
||||
|
||||
!Collecting usage scenarios {{red{WIP 12/10}}}
|
||||
* ModelPort access
|
||||
|
|
@ -2362,9 +2367,10 @@ To support this usage pattern, the Fixture implementation makes use of the [[PIm
|
|||
** commit a transaction
|
||||
|
||||
|
||||
!Conclusions about the structure {{red{WIP 12/10}}}
|
||||
* the ~PImpl needs to be a single (language) pointer. This necessitates having a monolithic Fixture implementation holder
|
||||
* moreover, this necessitates a tight integration down to implementation level, both with the clean-up and the render processes themselves
|
||||
!Conclusions about the structure {{red{WIP 12/10 … update 4/23}}}
|
||||
* the ~PImpl needs to be a single ''atomic pointer''. This necessitates having a monolithic Fixture implementation holder
|
||||
* consequently we need a tailored memory management -- requiring some (limited) knowledge about the usage pattern
|
||||
* this kind of information is available within the scheduling process ⟹ the [[Scheduler]] must support triggering on dependency events
|
||||
</pre>
|
||||
</div>
|
||||
<div title="FixtureStorage" modifier="Ichthyostega" created="201012140231" modified="201403071814" tags="Builder impl operational draft" changecount="22">
|
||||
|
|
|
|||
|
|
@ -69577,6 +69577,9 @@
|
|||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1681742836996" ID="ID_1978512771" MODIFIED="1681742860339" TEXT="scaffolding and mocking used for this test">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
<node CREATED="1681742863121" ID="ID_101929835" MODIFIED="1681742878203" TEXT="benötigte Mocks">
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1682611217967" ID="ID_792021380" MODIFIED="1682611222829" TEXT="Segmentation">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1681742879175" ID="ID_1375353236" MODIFIED="1681742901348" TEXT="Dispatcher">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
<node CREATED="1681743458393" ID="ID_891681445" MODIFIED="1681743469323" TEXT="extrahieren aus DispatcherInterface_test"/>
|
||||
|
|
@ -69687,6 +69690,44 @@
|
|||
<icon BUILTIN="full-3"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1682611316697" ID="ID_296140708" MODIFIED="1682614014464" TEXT="Vorschau auf die Fixture-Datenstruktur">
|
||||
<arrowlink COLOR="#735061" DESTINATION="ID_1136070257" ENDARROW="Default" ENDINCLINATION="-651;-968;" ID="Arrow_ID_1804652676" STARTARROW="None" STARTINCLINATION="-518;37;"/>
|
||||
<icon BUILTIN="idea"/>
|
||||
<node CREATED="1682613713646" ID="ID_1897977418" MODIFIED="1682613726321" TEXT="läuft auf eine Doppel-Hülle hinaus"/>
|
||||
<node CREATED="1682613727302" ID="ID_1816573739" MODIFIED="1682613737271" TEXT="wir implementieren im Moment nur einen Level"/>
|
||||
<node CREATED="1682613738020" ID="ID_3577348" MODIFIED="1682613823627" TEXT="⟹ Konsequenzen">
|
||||
<node CREATED="1682613762760" ID="ID_970667296" MODIFIED="1682613815597" TEXT="Datenstruktur kann (bedenkenlos) direkt manipuliert werden">
|
||||
<icon BUILTIN="yes"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1682613782480" ID="ID_178794461" MODIFIED="1682614001907" TEXT="Split-and-Splice-Operation implementieren">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
...das wird dann später die Basis für die Implementierung des Change-Builders
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
diese Operation untersucht die bestehende Segmentation
|
||||
</li>
|
||||
<li>
|
||||
und spaltet bestehende Segmente auf
|
||||
</li>
|
||||
<li>
|
||||
sie kann unterscheiden zwischen Umbau(=replacement) und Kürzen bzw. Klonen eines Segments
|
||||
</li>
|
||||
<li>
|
||||
Schlußfolgerung: ein Segment selbst darf nichts über seine Zeitspanne wissen
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1682384630010" ID="ID_1719484347" MODIFIED="1682385357439" TEXT="Struktur">
|
||||
<icon BUILTIN="info"/>
|
||||
<node CREATED="1682384645189" ID="ID_384048903" MODIFIED="1682384657607" TEXT="1 Record pro Segment">
|
||||
|
|
@ -69821,6 +69862,13 @@
|
|||
<node CREATED="1682385963140" ID="ID_1239158970" MODIFIED="1682385993315" TEXT="also MockSegmentation, MockJobTicket,..."/>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1682614081119" ID="ID_57760675" MODIFIED="1682614090109" TEXT="Interpretieren der jeweilgen Spec">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
<node CREATED="1682614092900" ID="ID_1252815318" MODIFIED="1682614105470" TEXT="Basis-Impl: ein Segment gibt es immer"/>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1682614117409" ID="ID_1176991982" MODIFIED="1682614138327" TEXT="splitSplice() implementieren">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1682385895644" ID="ID_89969390" MODIFIED="1682385902557" TEXT="Verifikation im Test unterstützen">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
<node CREATED="1682386003212" ID="ID_930954734" MODIFIED="1682386014176" TEXT="gibt die "Mock"-Variante der jeweiligen Objekte heraus"/>
|
||||
|
|
@ -70260,7 +70308,43 @@
|
|||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1681742018442" ID="ID_596503548" MODIFIED="1681742022940" TEXT="Addressierung JobTicket">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
</node>
|
||||
<node CREATED="1682611423253" ID="ID_1136070257" MODIFIED="1682611949376" TEXT="Vorschau: Datenstruktur">
|
||||
<linktarget COLOR="#735061" DESTINATION="ID_1136070257" ENDARROW="Default" ENDINCLINATION="-651;-968;" ID="Arrow_ID_1804652676" SOURCE="ID_296140708" STARTARROW="None" STARTINCLINATION="-518;37;"/>
|
||||
<node CREATED="1682611979955" ID="ID_853916933" MODIFIED="1682611992260">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
es gibt einen <b>commit</b>
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="yes"/>
|
||||
</node>
|
||||
<node CREATED="1682612009427" ID="ID_1994458742" MODIFIED="1682612053494" TEXT="vermutlich eine referentielle nicht-intrusive Linked-List">
|
||||
<node CREATED="1682612056884" ID="ID_303611553" MODIFIED="1682612072158">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
Einträge sind <i>woanders </i>alloziert
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node CREATED="1682612075770" ID="ID_1368353614" MODIFIED="1682612084845" TEXT="ein Eintrag kann in mehreren Instanzen der Liste liegen"/>
|
||||
<node CREATED="1682612094504" ID="ID_250365879" MODIFIED="1682612109226" TEXT="⟹ die umgebaute Liste kann auf einen Schlag aktualisiert werden"/>
|
||||
</node>
|
||||
<node CREATED="1682612125996" ID="ID_34027511" MODIFIED="1682612133736" TEXT="re-Partitionierung per Builder">
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1682611408260" ID="ID_408388335" MODIFIED="1682611410703" TEXT="Interface"/>
|
||||
<node CREATED="1680563454868" ID="ID_1187556686" MODIFIED="1680563459014" TEXT="Backbone"/>
|
||||
<node CREATED="1680563460649" ID="ID_127710483" MODIFIED="1680563474067" TEXT="MemManagement"/>
|
||||
</node>
|
||||
|
|
|
|||
Loading…
Reference in a new issue