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

Refactor semagrams initialization code #130

Open
filonik-cmu opened this issue Jun 5, 2023 · 9 comments
Open

Refactor semagrams initialization code #130

filonik-cmu opened this issue Jun 5, 2023 · 9 comments
Assignees

Comments

@filonik-cmu
Copy link
Collaborator

filonik-cmu commented Jun 5, 2023

Currently, Semagrams calls the render function inside its intialization code:

def main(div: dom.Element, init: js.UndefOr[String]) = {
val base = baseSvg()
render(div, base)
val startup = for {
eventQueue <- Queue.unbounded[IO, Event]
es <- Dispatcher.sequential[IO] use { dispatcher =>
{
val es = EditorState(base, dispatcher, eventQueue)
run(es, init.toOption)
}
}
} yield ()
startup.unsafeRunAndForget()(unsafe.IORuntime.global)
}

It would be better to separate the code that creates the svg element into something like a "semagram widget", so that it can be inserted at arbitrary places inside of a Laminar application. In the long run, I think we may realistically want to have multiple "semagram widgets" within a more complex DOM structure.

I think it would be nice if the "apps" were in control of calling render and determining what gets passed to it.

      val sharedState: ACSet = ...
      val appElement = div(
            h1("Process"),
            SemagramWidget(projectProcess(sharedState)),
            h1("Structure"),
            SemagramWidget(projectStructure(sharedState)),
      )
      render(appContainer, appElement)

The pseudo-code above is meant to illustrate a page with two embedded semagram widgets that share some common state via an ACSet. It's intended as a sketch, a lot of details still need to be fleshed out.

@olynch
Copy link
Collaborator

olynch commented Jun 6, 2023

The crucial question to answer for this is how does the semagram widget interact with the code outside of it?

One possibility is to have the code outside throw highlevel events to it, which are processed in the normal event loop.

Another possibility, suggested by the above, is to have the actual Var containing the ACSet be exposed.

Laminar is very flexible with state management, so there are a lot of different ways to structure this.

For starters, I might just make an API where you overload a class method of type (div: dom.Element) -> IO[Unit], and then provide some methods for setting up a Semagram manually in that context, by binding an EditorState to an svg element with an IO action.

@filonik-cmu
Copy link
Collaborator Author

filonik-cmu commented Jun 7, 2023

That is very true. Based on my conversations with @sjbreiner, it seems likely that we will eventually want to present multiple views of one underlying ACSet, so passing in the Var from the outside seems reasonable.

Right now, my understanding is that Semagrams provides generic functionality, mainly event handling and mapping ACSet elements to graphical elements. An app currently corresponds to one specific type of diagram (petri, dwd, etc.). For me, it would be natural to think of Semagrams as a collection of diagram components, where each component can be used to view/edit ACSets of a given schema. These diagrams should be built on top of the generic functionality that Semagrams already provides. However, apps should ideally be able to freely mix and match these diagram components as needed, along with other domain specific Laminar code.

Currently, is there a plan for Semagrams to support more complex apps with multiple diagrams?

@olynch
Copy link
Collaborator

olynch commented Jun 7, 2023

On second thoughts, the API that @filonik-cmu proposed makes more sense: there should be a function that returns an svg element that a semagram is attached to.

The question is, what should the arguments to this function be? Probably it should be a Var of whatever the state is, and then also an eventbus?

@olynch
Copy link
Collaborator

olynch commented Jun 7, 2023

On further reflection, the real question here is that a Semagram consists of two things. One is laminar based, and one is cats-effect based. How do we compose this with other stuff that has both cats-effect and laminar components?

@filonik-cmu
Copy link
Collaborator Author

filonik-cmu commented Jun 7, 2023

The question is, what should the arguments to this function be? Probably it should be a Var of whatever the state is, and then also an eventbus?

That actually sounds like a very nice API to me. I certainly haven't thought about all the consequences and impacts it would have on the Semagrams code base, but from a developer perspective, it seems quite natural.

Am I correct in understanding that the cats-effect based thing you are referring to is the IO? Wouldn't that be covered by passing in the eventbus as an argument? Or is there some other aspect that I haven't considered?

I understand that event handling is quite tricky, and there are probably some global events like key down/up that we would have to think about. But if we engineer this correctly, it would improve the versatility of Semagrams, and would probably make nice things like unit testing viable.

I keep hearing that Semagrams was at some stage demoed inside of a Jupyter notebooks. Is that functionality still around? I imagine that would have posed some similar challenges?

@filonik-cmu
Copy link
Collaborator Author

filonik-cmu commented Jun 7, 2023

One analogy that shouldn't be taken too literally, but may be worth exploring anyhow, is how Vue.js works. I know that it is different from Laminar, because of things like the virtual DOM, but I think it is also similar to what we are trying to do in many respects.

In Vue.js, you have one Vue instance, which you initialize and it essentially manages all the global state, and then you can have any number of components, which are essentially independent, but they maintain a reference to the Vue instance.

There is probably a way to manage this thing in Scala using implicit/given. But I kind of like the idea of having to pass in the dependencies explicitly, due to the above mentioned reasons (versatility, testability, etc.).

@olynch
Copy link
Collaborator

olynch commented Jun 7, 2023

One option would be to split Semagrams into two parts. One would be the Semagrams window, which essentially just accepted a signal of sprites. And the second part would be the Semagrams state management, which would have event handlers, global state, etc.

So the Semagrams window would just be a "thin client" for Semagrams state management. Semagrams state management would be somewhat agnostic to how it was rendered, and could receive commands/queries from anywhere. This could help with testing.

@olynch
Copy link
Collaborator

olynch commented Jun 7, 2023

The problem with Semagrams inside Jupyter/Pluto is that I never figured out a good way to save the state. Which meant that if you refreshed the page/reopened the notebook, whatever you had done would be gone.

More generally, I want to move past the notebook paradigm for scientific computing. The "reproducable output" of exploratory work should be a language-independent model, not arbitrary code running in a notebook. You should not save the model construction steps.

That said, notebooks have a place for making documentation; having an easy way to weave code and text is useful. And in that context, it would be good to embed Semagrams. So I'll want to enable that kind of stuff again at some point.

@filonik-cmu
Copy link
Collaborator Author

Sure, whether notebooks are a good paradigm for scientific computing is a deeper discussion. I was mainly interested in the technical aspect of having one or more Semagrams (i.e. diagrams with different underlying ACSets) running on one page in Jupyter notebook cells.

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