On 26.11.24 14:33, Will Godfrey wrote:
By 500mS steps I increased the time-out and it completed startup when this
was set to 2.5S. After the first-time startup, I could then reduce the
time-out to 1.5S for it to reliably start. This was under XFCE with no other
significant programs running.

these are interesting insights indeed.

One of the major delays is caused by loading the banks. It's much faster
than it used to be but still has to do a lot of filer access. To make
matters worse, each Yoshimi instance has to load it's own identical copy -
something I would *very* much like to find a way of avoiding!
Currently, we are doing this work after the SynthEngine was created and
initialised, but before the MusicClient (with the processing threads) is
started. This seems adequate, since at this point there are not yet any
tight timing constraints and moreover, this way both the GUI thread and the
processing threads (to be started afterwards) will see this data without
the need for further thread synchronisation.



On 26.11.24 20:01, Kristian Amlie wrote:
... Oh man, this level of architecture discussions is when I wish we were in
 a room with a whiteboard.

:-D

On 25.11.2024 19:53, ichthyo wrote:
Kristian: are you encountering this problems from LV2 or from standalone?
On 26.11.24 20:01, Kristian Amlie wrote:
Only standalone.

This could be because my host doesn't launch the GUI when I load the plugin,
I have to launch it manually afterwards. So it's probably doing less stuff
in parallel then.

This seems to confirm my suspicion. LV2 is indeed different here,
as the UI connection is launched from a second plugin object, the UI-plugin.
And this launch can happen several times, while the core engine is already
running. I.e. you can completely close the UI (instead of just hiding it)
and thus unload the UI-plugin. Than, later, a new UI-plugin can be launched.

This is an important complication, which I discovered only very late in the
process of adapting the intstances start-up. But that might be very relevant
for the discussion of UI bootstrap.

On 25.11.2024 19:53, ichthyo wrote:
Previously, just the SynthEngine* for the Instance was sent by a FLTK
message. Now we need to send an assortment of connection IDs, and these
will become more and more,....
On 26.11.24 20:01, Kristian Amlie wrote:
I get that the DTO connections are there to avoid reaching directly into the
structs across threads, that's fine. But in order to get the DTO connections
in the first place, they are sent, and received, both using the SynthEngine
object.....
But the other connections also live in SynthEngine, so why can't they just
be grabbed directly?

Is it because you are eventually going to break the dependency on the
SynthEngine object for the GUI? But if so, what will it be replaced with?
How can any connection be sent without some shared object to start from?

Kristian, you are an attentive observer and have managed to read between the
lines quite well ;-)

The fact that each and everyone has a SynthEngine* is troublesome.
And luckily, we are just using basically one engine-thread (per instance),
so the major part of the sound computations is effectively single threaded.
IMHO this is the reason why we get away with that structure reasonably well.

Anyway, when investigating what is done with that SynthEngine pointer,
it turns out that access falls into various categories:

- many accesses actually need to know some runtime parameters,
  like the sample rate or (very common!) the buffer size

- there is a (a smaller number) which need similar, config-like stuff.

- then there are a lot of accesses, which reach through the SynthEngine
  into the InterChange, actually to communicate via the ringbuffers.

- a lot of further accesses (especially from the GUI) actually go
  from the SynthEngine to the MasterUI and navigate to some sub component
  of the UI. So in fact we'd need some kind of internal wiring / DI; so
  this seems like something that can be sorted out without any major changes
  to the overall structure of the UI. (IMHO this structure is fine)

- and then there are the evil ones, those accesses which drill down
  into internal stuff, like parts, effects, filters.


These observations brought me to the idea of introducing a "root-anchor" DTO.

Because: assuming that we manage to get rid of the "evil" accesses,
all the other ones do not actually need the SynthEngine.

- most notably, the InterChange is accessed. But this is benign.
  It is a communication service, and thus it is made to be accessed
  from all threads. At some point, this could be an interface, not
  the implementation. And it could be one communication service,
  instead of having one for each Synth-Instance. Because in fact
  the InterChange is stateless to a large degree; internally it
  only talks to the appropriate set of ringbuffers.

- all the other things like sample rate and buffer size or
  other config params are plain and harmless data elements,
  and the UI-thread could use a local copy, residing
  in the rootAnchor DTO, which I placed into MasterUI.


You are absolutely right. In the current state of affairs, we ignore
issues of cross-thread visibility and just access the SynthEngine.
So the GUI could as well grab into the Synth and also get the new
connection-objects, more precisely the routing-tags, which are
embedded into these. Because the UI (receiving side) actually
needs only to know the routing tags in order to transfer a
received push update to the proper listener in the UI.

In fact, even those tags could be managed manually as constants
in a header. But this is a solution I immediately rejected, because
it is error-prone, and requires a maintainer to understand quite
subtle and complex connections, and fine distinctions of data type,
which are easy to handle automatically.

(4) Build a completely new and different communication channel
for the bootstrap message, don't use the GUI data exchange.....

This is perhaps related to what I asked above about how the connections are
passed initially. I think having a separate channel is perhaps better. It is
different from other messages in many respects: runs only once, is
mandatory,
and not very timing dependent (this is during load, after all). So it is
a different class of message than the purely informational messages.

From your suggestions I would vote for suggestion 4, I think the others
are more bandaid solutions, and number 4 is the proper one.

After thinking it over for a while, I also tend more and more to see this
initial connection as somewhat special. On the other hand, having it fully
integrated with GuiDataExchange has the benefit that it would allow
push-updates, e.g. when some config parameter changes.

But possibly both can be combined.
Because all those push-updates are processed in two steps
1) the core plants the data in the slot and sends a notification message
   through the toGUI ringbuffer
2) later, asynchronously, the GUI processes this message, picks up the
   data and dispatches it to the listener in the GUI, which was connected
   using the appropriate routing-tag

Thus, we could indeed send the /initial/ rootAnchor DTO over a different
channel, as long as this ensures the data (which belongs to the synth-thread)
is properly synched and visible for the UI-thread. In the GUI, the data
could then be dispatched with the existing publisher-subscriber setup.

Actually, this kind of handover looks like a promise/future pattern.
But there is the twist with the LV2 plugin. If I recall correct, a
promise can not be re-initialised. So this kind of usage only works
well when the promise is created before the other thread is started.
Because then both sides see the promise. From the promise object,
a future can then be created later, and used to get the data.

But if the other thread is already running, we're again on slippery ground.
The C++ standard does not specify how the promise works internally and
if it is safe to just grab it from another thread.


So basically we're in the situation that the code that wants to initiate
a new UI-bootstrap runs in the (prospective) UI thread. But we can not
be sure at that point that we can see data from the Synth-thread.

However, we do know the Synth-ID at that point, so we could go to the
InstanceManager singleton (this is a new service I introduced to manage
all access to engine instance and in fact this bootstrap code is part
of the InstanceManager). Maybe this would be a good location where we
could also place a new service to retrieve the GUI-bootstrap-info?

-- Hermann



_______________________________________________
Yoshimi-devel mailing list
Yoshimi-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/yoshimi-devel

Reply via email to