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

Fornjot models as serialized data #1722

Closed
ahdinosaur opened this issue Mar 24, 2023 · 9 comments
Closed

Fornjot models as serialized data #1722

ahdinosaur opened this issue Mar 24, 2023 · 9 comments
Labels
type: development Work to ease development or maintenance, without direct effect on features or bugs

Comments

@ahdinosaur
Copy link

ahdinosaur commented Mar 24, 2023

Hi,

Lovely to follow along with the great work on Fornjot, is exciting. 😺

I'm playing with a way of writing Fornjot models in JavaScript / Typescript using deno_core.

Here might be the main example model in JavaScript:

export const params = [
  {
    name: 'outer',
    type: 'number',
    default: 1.0,
    min: ({ inner }) => inner * 1.01,
  },
  {
    name: 'inner',
    type: 'number',
    default: 0.5,
    min: ({ outer }) => outer * 0.99,
  },
  {
    name: 'height',
    type: 'number',
    default: 1.0,
  },
]

export function shape(params) {
  const { outer, inner, height } = params

  const outer_edge = Sketch.from_circle(Circle.from_radius(outer))
  const inner_edge = Sketch.from_circle(Circle.from_radius(inner))

  const footprint = outer_edge.difference(inner_edge)
  const spacer = footprint.sweep([0, 0, height])

  return spacer
}

But I'm having trouble figuring out how to use the existing fj-app and fj-host interfaces.

As far as I understand, a shape is comprised of fj values, which thankfully are serializable with serde. A model is some (serializable) metadata (like parameter definitions) plus a shape function that is given parameter values and returns (serializable) shape data. I like this, yay code as data.

But then I see fj-app and fj-host, and I'm not sure what to do. fj-app expects a Model struct, that seems very restrictive in how it works. I would much rather implement a Model trait and provide the necessary interfaces (get_parameters(), get_shape(parameters), etc) by serializing and deserializing data between Rust and v8 (JavaScript), and ignore fj-host altogether.

As another layer I could also imagine a "loader" trait that is given model ids (paths or urls) and returns models, and some such to track when models have changed. Then we could have a Rust loader, a WASM loader, a JavaScript loader, a Rhai loader, etc.

But I know this topic has been discussed before, e.g. #71, #804, etc, so is very possible I'm missing something important.

Thanks 💜

@IamTheCarl
Copy link
Contributor

I'm actually working on this right now. Most of my idea is explained here.

I'm working on a proof of concept that will still use a C ABI (I will expand it to other interfaces later). instead of calling into a host object and passing a model, it will call a function in your client to produce a byte array of a serialized model. I think it will be a big step toward what you want.

It is still very subject to change.

@hannobraun
Copy link
Owner

Thank you for opening this issue, @ahdinosaur, and not least for the kind words! As @IamTheCarl mentioned, this is an idea that has come up recently. So it's timely that you opened this issue, because we don't yet have one yet to track this work 😄

So this whole topic is subject to change, and what you're trying to do will be affected by those changes. In the meantime, I have some thoughts about what you wrote, based on the current situation and possible future directions.

I'm playing with a way of writing Fornjot models in JavaScript / Typescript using deno_core.

First, we should gain some clarity on what you're trying to do: Do you want to add support for JavaScript as a second language to Fornjot (or work towards a situation where that would be a possibility), or do you personally want to use Fornjot to write models in JavaScript?

If the former, then we should have a discussion about that somewhere. I think supporting some kind of scripting language at some point is inevitable. If JavaScript/Deno is the right choice for that, I don't know (and I have my doubts). If we decided to go ahead with that approach, then it would be best to coordinate your efforts with @IamTheCarl here in this issue.

If the latter, I think trying to use fj-app and fj-host is the wrong approach, at the current time (and as previously noted, this might change as things develop further). It would probably be best to develop your own app, and the Fornjot ecosystem is designed to let you do that. Mostly, you would just pick and choose which crates to use and how to combine them. If you ended up replicating code that is in fj-app, that's a sign we should split out more crates to make your work easier.

So which crates would you choose?

  • There are the basic ones, like fj-math, fj-interop, and fj-kernel. Without them, you're not really using Fornjot. You could avoid fj-math/fj-kernel and still use fj-viewer, for example, but our renderer isn't so great that this would make sense, I think. So yeah, if you want to use Fornjot, use those basic crates.
  • The first real question is, do you want to use fj (API for Rust models) and fj-operations (translation layer between fj and fj-kernel)? I think there's a good case to be made that you shouldn't:
    • The vast majority of recent effort has gone into fj-kernel, and fj is turning more and more into a vestigial afterthought. This is going to change at some point (it is the interface users are supposed to use after all), but in the near future, the gap in capabilities is only going to widen (see next item).
    • I'm currently developing a new API for creating and updating geometry, initially for use within the kernel (I've put down my initial thoughts in #42, and the pull requests referencing Create builder API for Solid/Shell validation tests #1713 have the implementation so far). How and when to expose that capability through fj is an open question, and not a priority at present.
    • fj-operations should generally be a thin (and thus easily replaced) layer over fj-kernel. Where it isn't, that probably means that we should move more code into fj-kernel.
    • fj is targeted at Rust anyway. Adapting its API for another language might not make sense.
    • Trying a new approach (maybe one based on more back-and-forth between model and kernel) might be more valuable for the project anyway, than shoehorning a new thing into the existing architecture.
  • fj-window should in principle be usable, unless you want to do something that's very different from fj-app (browser-based?), but in reality, it depends on fj-host. I think that's a mistake, and I made a note that I need to open an issue about that. Not sure what to recommend here. If you're up for it, breaking that dependency would certainly be a welcome contribution.
  • Using fj-viewer and fj-export is a no-brainer, I think, unless you specifically want to experiment in that area (people keep talking about using Bevy, but I'm no fan of that idea).
  • fj-app and fj-host is what I would avoid and replicate. fj-host, because it's specifically designed to do a thing that's different from what you want to do. fj-app, because it combines all these crates, including fj-host. There might be useful parts of fj-host though, like watching files and triggering loads. It might make sense to extract those from fj-host into a new library.

But then I see fj-app and fj-host, and I'm not sure what to do. fj-app expects a Model struct, that seems very restrictive in how it works. I would much rather implement a Model trait and provide the necessary interfaces (get_parameters(), get_shape(parameters), etc) by serializing and deserializing data between Rust and v8 (JavaScript), and ignore fj-host altogether.

As I said above, unless your goal is specifically to make Fornjot more flexible, I'd avoid fj-host. But if you're interested in working towards the inevitable multi-language future of Fornjot, then what you write here (turning Model into a trait) sounds very reasonable.

As another layer I could also imagine a "loader" trait that is given model ids (paths or urls) and returns models, and some such to track when models have changed. Then we could have a Rust loader, a WASM loader, a JavaScript loader, a Rhai loader, etc.

Totally agree. I can imagine having an add-on system long-term, where the fj-app-based stack is pretty bare-bones, and all support for building, loading, etc., is added using separate add-ons that just implement common traits. This is not a priority for myself right now, but if that's something you'd like to work on, I'd certainly like merging anything that moves us into that direction.

I think the first steps would probably be to untangle the various things fj-host does (watching files, building models, loading models) and create nice traits for each of those. Initially, we could just have multiple implementations within fj-host, before we move to a full-blown add-on model.


Sorry for the large brain dump! I hope it's helpful.

@hannobraun hannobraun added topic: host type: development Work to ease development or maintenance, without direct effect on features or bugs labels Mar 24, 2023
@ahdinosaur
Copy link
Author

ahdinosaur commented Mar 25, 2023

First, we should gain some clarity on what you're trying to do: Do you want to add support for JavaScript as a second language to Fornjot (or work towards a situation where that would be a possibility), or do you personally want to use Fornjot to write models in JavaScript?

I'm keen to see Fornjot support JavaScript / TypeScript. In the long-term I'd like to use Fornjot for my own project, but in the meantime I wanted to try a proof-of-concept to help push Fornjot in a useful direction, explore some ideas. I personally don't have enough time or energy to dedicate towards the complete adventure, but I enjoy playing around and showing what's possible. ☺️

Here's my proof-of-concept, which works: https://github.com/villagekit/fornjot-sandkit 🌟

I went with an approach using deno_ops and serde_v8, where every function call in JavaScript (v8) goes to Rust and back to v8. I made some wrapper Deno ops and wrapper JavaScript classes to make this possible. I was impressed how easy this was, how everything just worked because the fj structs implement serde::Serialize and serde::Deserialize.

For the viewer, I just made something basic with kiss3d.

Currently only handles a subset of the fj API. And doesn't handle parameters, live-reloading of your models, or much anything beyond the proof-of-concept.

Another approach would be to not have any v8 -> Rust -> v8 ops, the user API is defined completely in JavaScript, and instead the model's shape function returns a JavaScript object, which is called and then parsed in Rust from a v8 object to a Fornjot shape. Would be pretty straightforward as well. Update: Here's what that looks like: villagekit/fornjot-sandkit#1.

  • The first real question is, do you want to use fj (API for Rust models) and fj-operations (translation layer between fj and fj-kernel)? I think there's a good case to be made that you shouldn't:

    • The vast majority of recent effort has gone into fj-kernel, and fj is turning more and more into a vestigial afterthought. This is going to change at some point (it is the interface users are supposed to use after all), but in the near future, the gap in capabilities is only going to widen (see next item).
    • I'm currently developing a new API for creating and updating geometry, initially for use within the kernel (I've put down my initial thoughts in #42, and the pull requests referencing Create builder API for Solid/Shell validation tests #1713 have the implementation so far). How and when to expose that capability through fj is an open question, and not a priority at present.
    • fj-operations should generally be a thin (and thus easily replaced) layer over fj-kernel. Where it isn't, that probably means that we should move more code into fj-kernel.
    • fj is targeted at Rust anyway. Adapting its API for another language might not make sense.
    • Trying a new approach (maybe one based on more back-and-forth between model and kernel) might be more valuable for the project anyway, than shoehorning a new thing into the existing architecture.

I'm aware I'm a bit early here, I'm happy to wait until there's a stable kernel API, with enough examples on possible user APIs. No rush from me, I'm in for the long game.

Sorry for the large brain dump! I hope it's helpful.

Yeah for sure, if not for me directly, is always helpful for other wandering adventurers, thanks!

@IamTheCarl
Copy link
Contributor

I have a working proof of concept for the serialization based ABI here. It worked on the first try which has me feeling... suspicious of it.

I'm not ready to make a merge request with it yet as there's still a lot of vestigial code remaining of the old ABI and broken tests, but I thought it may be good to let both of you see how it currently works and provide some opinions.

@hannobraun
Copy link
Owner

Thank you for your work, @IamTheCarl! I've taken a quick look and have no comments so far. This concept is still relatively new to me, and I assume I'll need some time to form strong opinions on how to do things. For now, I'm happy to merge whatever works and see where it goes from there.

I'm not ready to make a merge request with it yet as there's still a lot of vestigial code remaining of the old ABI and broken tests

The broken tests need to be fixed, but the vestigial code is not a blocker for merging. It would be better if it weren't there, of course, but it's not something we need to delay merging a pull request for.

@IamTheCarl
Copy link
Contributor

Okay, I'm axing the documentations examples around this macro because the macro no longer exists in this version.

I'm just pointing that out so you know I'm not just deleting tests as a convenient way out of this.

And as far as I can tell, those were the only tests I broke so that's good to go.
There's still a good bit of vestigial code left but I'm okay opening a merge request so you can see the changes more clearly.

@IamTheCarl
Copy link
Contributor

I'm just going to brain dump a few of my next plans.
Once I have that branch cleaned up and ready to merge, I'd like to focus on abstracting the client to some kind of trait so we can have different back ends, such as one that compiles to an executable and uses stdin/stdout (for testing purposes, not a long term solution), a WASM backend, and a remote client over websocket.

@hannobraun
Copy link
Owner

Thank you, @IamTheCarl, I agree with both of your comments.

@hannobraun
Copy link
Owner

This issue is no longer applicable. The affected code has been removed. See A New Direction for context.

Thanks for all the input here! But as of now, a model system that goes beyond using the kernel as a library, is no longer in scope.

@hannobraun hannobraun closed this as not planned Won't fix, can't repro, duplicate, stale May 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: development Work to ease development or maintenance, without direct effect on features or bugs
Projects
None yet
Development

No branches or pull requests

3 participants