Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Help needed for serialisation / deserialisation #268

Open
rramillien opened this issue Aug 26, 2021 · 13 comments
Open

Help needed for serialisation / deserialisation #268

rramillien opened this issue Aug 26, 2021 · 13 comments

Comments

@rramillien
Copy link

Hello everyone,

I can't find a simple way to (de)serialize elements.
My need is standard, I want to have an "editor" that serialize the UI to any format (probably JSON)
And I want to deserialize it in the host.

I tried using nlohmann/json, but I'm stuck.
From what I can see, I can only build elements when a child already exists. For e:xample (gotten from basicSlidersAndKnobs)
return
margin({ 20, 10, 20, 10 },
vmin_size(400,
htile(
margin({ 20, 20, 20, 20 }, pane("Vertical Sliders", make_vsliders(), 0.8f)),
margin({ 20, 20, 20, 20 }, pane("Horizontal Sliders", make_hsliders(), 0.8f)),
hstretch(0.5, margin({ 20, 20, 20, 20 }, pane("Knobs", make_dials(), 0.8f)))
)
)
);

And classes seems to be some kind of "immutable" and do not allow to change children at runtime. Am I wrong ?
What I would have done is instanciate the parent, then add all its children while deserialising them.

Is it possible ? Or if not, does anybody already serialised/deserialised elements and have example or explanation ?

Best regards,

@rramillien
Copy link
Author

PS. I just discovered elements, but I really like what you've done :)

@djowel
Copy link
Member

djowel commented Aug 26, 2021

Elements is not designed this way. The main objectives, esp. 3 and 4 reinforces this:

  1. Should not rely on a “visual” GUI editor or code generator.
  2. It should have a declarative API using modern C++. Declarative C++ code tells you what rather than how (imperative). The GUI should be declared in C++ code.

So essentially the UI structure is defined in real c++ code, instead of an 'serializer' that builds the UI.

That being said, it is still possible to design a 'serializer', but it will require a lot of work to do right.

@rramillien
Copy link
Author

Ok, thank you. I was afraid of that.
But designing the UI without code is mandatory for me. So, I will try the "hard work".

Best regards,

@djowel djowel reopened this Aug 27, 2021
@djowel
Copy link
Member

djowel commented Aug 27, 2021

I'll keep this open. The first step is defining a declarative (perhaps json or yaml) structure for the UI. This data structure design is probably the difficult part. You do not want to go too low-level, but at the same time, you also do not want to go too much high level. If you want to take a jab at it, I'd be willing to offer advice, and can help with the code too.

@rramillien
Copy link
Author

rramillien commented Aug 27, 2021

Hello Joel,

Thanks a lot. I accept your proposal ;)
I don't really know elements right now. Just copied the "basic sliders and knobs" example to play a bit with it.

I was thinking of json too but yaml could do the trick. If you plane to reuse it, I'm opened to your choice. For the level of design, I wanted to conform as much as possible on the components you made. This could work too with custom components.

I've got 3 solutions in mind. Something like this:
"margin" : {
"left": ...,
"right": ...
"child": {
"pane": {
"pane attribute1": ...
etc
"child": {
"slider": {
}
}
}
}
}

It is the "most simple" way to describe it but I do not really like the "child" or "children", followed by the "real" child. It works but is pretty ugly.

Another solution could be:

"child": {
"type": "margin",
"attributes": {
"left": ...
}
"child": {
"type": "pane",
...
}
}

But this is very generic and this is not "type safe". Any attribute could be in any element event if not used.

A third solution could be to "declare" all items first then to declare the hierarchy after them.
{
"definitions": {
"panes": [
{
"id": "00000000-0000-0000-0000-00000000"
"title": ...
...
},
{
}
],
"margins": [
{
"id": "11111111-1111-1111-1111-11111111",
"left": ""
}
]
},
"hierarchy": {
"root": {
"id": "11111111-1111...",
"children": [
{
"id": 00000000-0000...",
"children": ...
]
}
}
}

I prefer this third solution. Elements are strongly typed in the "definitions" part . But the hierarchy is not typed and an element with only one child allowed will still have a "children" with potentially more than one child.
Even if this is my preferred solution it still needs refining.

From a coding POV, I think all solutions are equivalent. I will need to first create a model in memory then create objects in reverse order from child to parent.

If you (or someone) already think about it or have a prefered or another solution, please let me know.

Just in case you are curious. The goal is to create an open source "customisable" VST.

Best regards,

@djowel
Copy link
Member

djowel commented Aug 27, 2021

You should use escapes to format your code here. Example:

   struct foo
   {
      int bar;
   };

@djowel
Copy link
Member

djowel commented Aug 27, 2021

Anyway, you've barely touched the surface. The front-end data structure will be a major undertaking in and of itself, in the standpoint of design. As I said, it can't be too low-level. It is possible to map element's structures one-to-one, but then I'm afraid it will not be performant due to type erasure (assuming you know what that means).

But again, the real question is: why bother to write it in JSON if you can write it more elegantly in c++? You still have not explained why designing the UI without code is mandatory for you. JSON is code. YAML is code.

@rramillien
Copy link
Author

mmh, yes, effectively... you're right about type erasure. There is many years I had not really coded in C++ and I didn't think of this.... But I will make some tests and go back to you if I'm stuck.

About my need, it is simple. I want to create a open-source VST with is parameterable. Think of it as a simple reaktor or simple kontakt. So the user will be able to design its own elements and bind the controls to functions.
So because user have to design is own view, I need to serialize/deserialize it. I don't want the user to code and compile its own view... I want to make it simple for user. First step will be deserialization to build a view of elements from json.

@rramillien
Copy link
Author

rramillien commented Aug 28, 2021

Mmmmh. Perhaps a good solution could be to create my own "standard" high level widgets based on your elements ?
JSON will then be based on these widgets ? As you said, it's not too low level and it could fulfill most of my requirements.

But the shame is that the size of elements is parameterized for some (or every ?) elements (slider, knobs). This means I would have to create a "widget" for each size... Not so good idea in fact...

I could use preprocessor to quickly create multiple classes of the same elements with differents sizes... That would be ugly but it'd work.

Unless you have better advices, I will try this. Creating widgets "slider2" "slider3", "slider5", "10", etc...

@rramillien
Copy link
Author

rramillien commented Aug 28, 2021

That could lead to someting like this:

#pragma once

#include <elements.hpp>

#include "UiConstants.h"
#include "BasicTrack.hpp"

using namespace cycfi::elements;

#define SLIDER_MARKS(className, track, size) namespace live::tritone::vie::ui::model { \
    struct className { \
        std::string trackId; \
\
        using elementsMap = std::map<std::string const, void*>; \
        static auto deserialize(elementsMap& elements) { \
            auto track = track::deserialize(elements); \
            return slider_marks<size>(track); \
        } \
    }; \
}

#define SLIDER_MARKS_10 SLIDER_MARKS(SliderMarks10, VerticalBasicTrack5, 10)
#define SLIDER_MARKS_20 SLIDER_MARKS(SliderMarks20, VerticalBasicTrack5, 20)
#define SLIDER_MARKS_30 SLIDER_MARKS(SliderMarks30, VerticalBasicTrack5, 30)
#define SLIDER_MARKS_40 SLIDER_MARKS(SliderMarks40, VerticalBasicTrack5, 40)

SLIDER_MARKS_10
SLIDER_MARKS_20
SLIDER_MARKS_30
SLIDER_MARKS_40

@djowel
Copy link
Member

djowel commented Aug 29, 2021

Yes! That is certainly possible! Use shared and element_ptr to build your dynamic controls.

https://github.com/cycfi/elements/blob/master/lib/include/elements/element/element.hpp#L71-L81

For the dynamic parts, I suggest making custom elements. For this particular case, you can make a version of slider_marks_element that is customizable via ctor parameters. It's straightforward if you look at the source:

https://github.com/cycfi/elements/blob/master/lib/include/elements/element/slider.hpp#L319-L364

Maybe slider_marks_element_d and slider_marks _d(...) and contribute it back to elements.

@djowel
Copy link
Member

djowel commented Aug 29, 2021

But the shame is that the size of elements is parameterized for some (or every ?) elements (slider, knobs). This means I would have to create a "widget" for each size... Not so good idea in fact...

Nah, there are many cases where the parameters are supplied dynamically (runtime). We can talk about it, case by case, if you see something that you need to be dynamic, is not currently static.

I could use preprocessor to quickly create multiple classes of the same elements with differents sizes... That would be ugly but it'd work.

I'd avoid that. Not good. See my comment above about customizing the default classes. (Heck, I might even consider making 'slider_marks_element' totally dynamic, if it makes better sense.

@rramillien
Copy link
Author

Thanks Joel,

Ok, I will try your solutions this week.
I will tell you if I need help.

Best regards,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants