membrane

Omer ZAK 2024-03-18T11:05:42.512309Z

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.

phronmophobic 2024-03-18T16:59:19.434249Z

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

phronmophobic 2024-03-18T17:02:16.878089Z

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.

phronmophobic 2024-03-18T17:03:34.115699Z

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.

phronmophobic 2024-03-18T17:04:11.783309Z

Omer ZAK 2024-03-18T17:25:08.001579Z

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.

phronmophobic 2024-03-18T17:29:19.521769Z

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

Omer ZAK 2024-03-18T17:33:48.020789Z

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?

phronmophobic 2024-03-18T17:35:16.771219Z

No.

phronmophobic 2024-03-18T17:36:31.795289Z

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

phronmophobic 2024-03-18T17:36:44.442959Z

for example, the JFrame of the window created.

phronmophobic 2024-03-18T17:38:11.732379Z

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.

phronmophobic 2024-03-18T17:38:48.061529Z

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

Omer ZAK 2024-03-18T17:40:16.322809Z

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

phronmophobic 2024-03-18T17:40:39.279229Z

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

Omer ZAK 2024-03-18T17:42:06.457489Z

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?

phronmophobic 2024-03-18T17:44:34.459509Z

> 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.

phronmophobic 2024-03-18T17:46:41.470789Z

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 ZAK 2024-03-18T17:50:13.588229Z

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.

phronmophobic 2024-03-18T17:52:42.071449Z

I'm not that familiar with pods or babashka.

phronmophobic 2024-03-18T17:54:45.215459Z

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 ZAK 2024-03-18T17:55:03.229139Z

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"}

phronmophobic 2024-03-18T17:56:51.010619Z

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 ZAK 2024-03-18T17:59:01.042969Z

https://clojurians.slack.com/archives/CVB8K7V50/p1710784362071449?thread_ts=1710759942.512309&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 ZAK 2024-03-18T18:03:16.069189Z

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

👍 1
Omer ZAK 2024-03-18T18:43:59.163089Z

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

phronmophobic 2024-03-18T18:44:51.383839Z

it depends on the object. usually not

phronmophobic 2024-03-18T18:45:09.784089Z

functions: yes JFrame: no atoms and vars:no

Omer ZAK 2024-03-18T18:49:01.299129Z

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?

phronmophobic 2024-03-18T18:50:53.667459Z

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

Omer ZAK 2024-03-18T18:53:31.672039Z

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

phronmophobic 2024-03-18T18:55:48.199949Z

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

Omer ZAK 2024-03-18T19:08:55.800449Z

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))}.

phronmophobic 2024-03-18T19:11:34.192129Z

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

phronmophobic 2024-03-18T19:12:00.097069Z

unless that's already being done by the serializer

Omer ZAK 2024-03-18T19:12:55.038399Z

Will test and see. Fun!

Omer ZAK 2024-03-18T11:06:11.588869Z

I'l ask @borkdude about this.