On Wed, 31 Jan 2024 18:41:41 +0100
ichthyo <p...@ichthyostega.de> wrote:
Meanwhile, I've started working on a sketch for integration.
The first problem to solve is how to "bootstrap" a connection into
the GUI through the new GuiDataExchange facility.

Hi Will,

after quite some tinkering and re-arranging, this idea worked as expected.
On the "guiconnect" branch in my repo is now a version of Yoshimi, which
starts up the GUI using the new method. I'd say, that change is a bit bold,
but only changes one aspect, namely how we create the MasterUI object.


Let me point out some notable aspects to help understanding the changes.


First, I should recapitulate what I want to achieve.
(1) open the ability for /arbitrary/ parts of the GUI to have a virtual
    "private" data connection to the core. Changes are pushed by the core.
(2) A first step towards getting rid of the synth* pointers; the idea is
    to separate the accesses into distinct topics, where each should be
    handled by a different interface or data record, and thus abstract
    away the SynthEngine as far as the UI is concerned.


Based on my analysis regarding Synth instances and UI bootstrap, I decided
that the new communication system "GuiDataExchange" should be a part
(a sub component) of InterChange. In theory, we could equally well make
this new system a global component, but this would break the underlying
pattern with those Synth-Instances, where each one is a completely
self contained unit. Having a single global GuiDataExchange would save us
some memory (since it has a data buffer with the tendency to become large),
but the downside would be that then we'd get an asymmetry and more complex
dependencies between the Synth instances. So I think it is preferable
when each Synth instance has its own InterChange and GuiDataExchange.


The next observation was that a lot of the accesses to the synth* pointer
in the GUI actually want to either access InterChange, or the MasterUI.
Both are valid concerns, but actually not connected to "the Engine" as such.

Thus it seems that turning around those dependencies could be an interesting
path ahead. Of course we have to proceed carefully and in incremental steps
(and at some point we'll get into the inevitable chainsaw massacre).

So I've done the following

- from the implementation side in the core, most remains the same.
  InterChange is still a sub-component embedded into the SynthEngine

- but the GUI is now no longer hooked up from the Synth
- rather the GUI now boots up from a reference to InterChange
- moreover, I have moved the MasterUI object from SynthEngine into InterChange

Why this? My reasoning is, InterChange is some kind of an Interface object
or communication system. If we split up the system into several processes,
there would still be such a communication hub, it would just be split up
in the implementation (into two communication end points).
Thus InterChange looks like a good entrance point.

However, to make the transition seamless, I retained the getters on SynthEngine
and I also retained the synth* on MasterUI for now, thus most of the code
continues to work without any change at the moment.


The next new thing I've introduced is some kind of a root connectivity record.
I choose the name InterfaceAnchor. It is a copyable data class and allows to
transport some base configuration into the UI. Most notably this will the place,
where we can add individual connection IDs.

You may recall from my stand-alone prototype test, that I built the new
GuiDataExchange around the idea of a connection object, which is in fact
implemented as an unique connection ID under the hood. In C++ terms, this
is a /class template/, i.e. a class that can be parametrised to various
arbitrary /payload types/. Thus, each sub component which needs to push
data into the UI just uses a connection parametrised to the specific data
type to transport. Internally, this type information is also worked into
a /type ID/, and thus we can use a safety check on the other side to
ensure that the receiver picks up the correct data type.

Which brings us to the /bootstrap problem/ : in order to use such a connection,
both partners must hold an connection object, which embeds the correct ID.
And these IDs are allocated dynamically and are basically just opaque
lookup-IDs.

Obviously this implies: we have to transport those connection IDs somehow
into the GUI. An this is where the InterfaceAnchor comes into play.
All relevant connection IDs for any sub components of the SynthEngine,
which need to push data into the GUI, will place some connection ID
into some field in this data type.

And then -- in the famous tradition of chickens and eggs -- we use
the GuiDataExchange system to push that data block into the UI....

Whenever a new Synth instance is created through mainCreateNewInstance()

(1) the new Synth and MusicClient are created and initialised
(2) now a new function is invoked: SynthEngine::publishGuiAnchor()
    This function picks up all required connection IDs from the
    Synth internals, and then places the InterfaceAnchor into
    the buffer of GuiDataExchange (which is a part of InterChange)
(3) next a FLTK message is sent from the primary thread to the
    »main thread« (which is actually the UI event processing thread)
    I have changed this message: now it transports an InterChange*
    and in addition, it transports the slot-Index-Number for
    GuiDataExchange, denoting the buffer into which the
    InterfaceAnchor has been placed
(4) on the receiving side in the UI thread, now the passed InterChange
    reference is invoked and instructed to create the MasterUI object.
    You may recall that I relocated this UI root from SynthEngine
(5) in the base class, GuiUpdates (MiscGui.h|cpp), I integrated a
    MirrorData block as receiver for the InterfaceAnchor object.
    The constructor now takes the slot-index-Nr and instructs
    GuiDataExchange (accessible as part of InterChange) to perform
    a push update. After that, the InterfaceAnchor object with all
    the actual connection IDs can be found in the embedded buffer
(6) the Init() method of MasterUI can now access and use this
    information to create the windows and do whatever else...


My planned next step would be to use a private data connection
to push the EQ graph... for this I'll still have to work out
how the EQ instances in the core (the arrays for the system
effects and insert effects) are actually mapped. It is clear
that the UI currently gets an EffectMgr* from the core,
but there is a special function on this API used solely for
the EQ effect. And this one would be replaced by a push
data connection for the EQ graph data.

Hopefully these explanations are not too confusing; I'd be
happy to explain any further questions or discuss further
ramifications. In the end it doesn't make sense to push
such a change ahead if we aren't confident we actually
understand what's going on and we agree to be on a
good path forward.

-- Hermann





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

Reply via email to