Fork me on GitHub
#membrane
<
2024-03-18
>
Omer ZAK11:03:42

Another hurdle on the way to a functioning Babashka pod for membrane. Whenever the user does something with membrane, membrane functions return to him objects. When the user is a Babashka pod, the pod serializes the objects into strings and sends them to Babashka. Once serialized, those objects are strings with the format of tag objects. Babashka then needs to invoke a Reader to convert those strings back into objects. I was successful in creating an ui/label object and transferring it. I defined readers for several ui classes. See the following code fragment.

:describe (do
                        (debug "===> executing :describe")
                        (write {"format" "edn"
                                "namespaces" [(describe-ns "pod.tddpirate." (find-ns 'membrane.java2d))
                                              (describe-ns "pod.tddpirate." (find-ns 'membrane.ui))
                                              (describe-ns "pod.tddpirate." (find-ns 'membrane.component))
                                              (describe-ns "pod.tddpirate." (find-ns 'membrane.basic-components))
                                              ]
                                "readers" {"membrane.ui.Font" "pod.tddpirate.membrane.ui/Font"
                                           "membrane.ui.Label" "pod.tddpirate.membrane.ui/Label"
                                           ;; All types having IDraw
                                           "membrane.ui.WithStrokeWidth" "pod.tddpirate.membrane.ui/WithStrokeWidth"
                                           "membrane.ui.WithStyle"       "pod.tddpirate.membrane.ui/WithStyle"
                                           ;;"membrane.ui.LabelRaw"        "pod.tddpirate.membrane.ui/"
                                           "membrane.ui.Image"           "pod.tddpirate.membrane.ui/Image"
                                           "membrane.ui.Translate"       "pod.tddpirate.membrane.ui/Translate"
                                           "membrane.ui.TextSelection"   "pod.tddpirate.membrane.ui/TextTranslation"
                                           "membrane.ui.TextCursor"      "pod.tddpirate.membrane.ui/TextCursor"
                                           "membrane.ui.Path"            "pod.tddpirate.membrane.ui/Path"
                                           "membrane.ui.RoundedRectangle" "pod.tddpirate.membrane.ui/RoundedRectangle"
                                           "membrane.ui.WithColor"       "pod.tddpirate.membrane.ui/WithColor"
                                           "membrane.ui.Scale"           "pod.tddpirate.membrane.ui/Scale"
                                           "membrane.ui.Arc"             "pod.tddpirate.membrane.ui/Arc"
                                           "membrane.ui.ScissorView"     "pod.tddpirate.membrane.ui/ScissorView"
                                           "membrane.ui.ScrollView"      "pod.tddpirate.membrane.ui/ScrollView"
                                           ;;"javax.swing.JComponent"      "pod.tddpirate.membrane.ui/"
                                           }
                                "id" id})
However, java2d/run seems to have some baggage which cannot be transmitted as string in a reasonable way. So when trying to deserialize, the script aborts with No reader function for tag object exception. Therefore, I believe that we need some proxy scheme. membrane would serialize its objects into special strings and when the client transmits those strings back, they will be used to fetch again the objects.

phronmophobic16:03:19

I assume that means compiling with liberica more or less worked?

phronmophobic17:03:16

There are a few problems to think through. My guess is that the immediate problem you are running into is that there's not a good way to serialize compiled functions.

phronmophobic17:03:34

My suggestion is to spend some time thinking about what the API for the pod should look like given that compiled functions don't have a good way to be serialized.

Omer ZAK17:03:08

Yes, compiling with liberica worked. About serialization of "complicated" objects (such as compiled functions), @borkdude dealt with somewhat similar issues in serializing DB connections in his SQL pods. The basic idea is to maintain a map of object -> string (which can be an UUID) and also string -> object for deserialization.

phronmophobic17:03:19

ok, so that means you have a solution to the problem you posted in this thread?

Omer ZAK17:03:48

I have an approach to a solution. But I would like to do the thinking through that you suggested above before rushing to implementation. First, are all "complicated objects" that membrane deals with - compiled functions?

phronmophobic17:03:31

java2d/run returns a map with some values that can't be serlialized

phronmophobic17:03:44

for example, the JFrame of the window created.

phronmophobic17:03:11

Other than java2d/run, I can't think of any other functions that deal with complicated objects besides compiled functions off the top of my head.

phronmophobic17:03:48

I know membrane.ui/image can accept BufferedImages, but you can just pass a file path or URL instead.

Omer ZAK17:03:16

An unrelated topic: In other pods, they define a small number of pod functions that Babashka can invoke. In membrane, I implemented an approach in which I retrieve all objects defined in a namespace using ns-map. However I see that I get a very long of objects, some of which can be variables. How feasible is it for you to add a metadata to mark functions and variables that you want to make available to Babashka? For example ^pod-func to mark pod functions ^pod-var to mark pod variables

phronmophobic17:03:39

oh, and membrane.component/make-app accepts a var and maybe an atom, which might need special handilng

Omer ZAK17:03:06

About the JFrame of the window created: We'll set a map in the pod side to translate JFrames into strings which will be transferred to Babashka side, and serve as proxies. When Babashka side sends those strings to the pod, they'll be translated back into JFrames in the pod side. The question - are JFrame and compiled functions the only "complicated" datatypes that we need to deal with?

phronmophobic17:03:34

> How feasible is it for you to add a metadata to mark functions and variables that you want to make available to Babashka? I could do that, but I'm not sure it's the best approach. If the pod needs a list of which vars should be exported, it should probably be included and maintained in the pod library itself.

phronmophobic17:03:41

If there are public functions that should be excluded from the pod interface, then it would be helpful to come up with a list of reasons why. It might make sense to mark functions based on their intrinsic properties.

Omer ZAK17:03:13

Can I rely upon you to make the list of functions and variables to be exported by the pod to Babashka? It can be either in the form of: • objects with meta tags (and then I'll use ns-map with filter on the meta tags) • a proper list You decide. I can adjust my code to accommodate either.

phronmophobic17:03:42

I'm not that familiar with pods or babashka.

phronmophobic17:03:45

The list of functions exported by the pod should be maintained as part of the pod library. It should most likely be a subset of the public functions available

Omer ZAK17:03:03

About "complicated" objects (JFrame, compiled functions, etc.) - is it possible to have a predicate to tell if an object is "complicated". When complicated, I'll add it to map of proxies and transfer only the proxy across the Babashka-pod interface. The proxy in @borkdude's SQL pods looks like this: {:podnamespace/proxy #uuid"something"}

phronmophobic17:03:51

I'm not sure you can tell if an object is definitely not serializable, but you can usually tell if an object is serializable.

Omer ZAK17:03:01

https://clojurians.slack.com/archives/CVB8K7V50/p1710784362071449?thread_ts=1710759942.512309&amp;cid=CVB8K7V50 I'll try to simplify for you the mental model of pods. The pod side includes the membrane library and manipulates objects. The Babashka side manipulates only basic Clojure data types and proxies for those objects. It tells the pod side what "complicated" operations to do with "complicated" objects.

Omer ZAK18:03:16

Thanks for the discussion. I think I now have some idea what to do.

👍 1
Omer ZAK18:03:59

@U7RJTCH6J, I forgot to ask you - are the "complicated" objects, which you manipulate in membrane, immutable?

phronmophobic18:03:51

it depends on the object. usually not

phronmophobic18:03:09

functions: yes JFrame: no atoms and vars:no

Omer ZAK18:03:01

When dealing with the mutable objects, are there reasons why does the Babashka side need to have an handle on the object? For example, when there are more than one JFrame in the application, and the Babashka side needs to manipulate one of them. When Babashka needs to manipulate those mutable objects, do we have (in pod side) any stable handle that we can use as a mapping key?

phronmophobic18:03:53

You can always create one by keeping a map of obj -> uuid

Omer ZAK18:03:31

I need to ensure that uuid maps to the same object even when the object mutates.

phronmophobic18:03:48

I believe the maps will use the object's hash, which won't change.

Omer ZAK19:03:55

I'll use (unless advised otherwise): number? string? char? keyword? symbol? list? vector? set? map? to identify "simple" objects. All other objects will be added to a map which transforms them into {::proxy (str (java.util.UUID/randomUUID))}.

phronmophobic19:03:34

for the collection types, you also need to make sure all of their values are simple

phronmophobic19:03:00

unless that's already being done by the serializer

Omer ZAK19:03:55

Will test and see. Fun!

Omer ZAK11:03:11

I'l ask @borkdude about this.