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

wip: experiment around a hybrid (nacre) shell #333

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

o0Ignition0o
Copy link

@o0Ignition0o o0Ignition0o commented Mar 22, 2025

Super early draft, a lot of things to figure out together and document.

Coauthored by @regexident.

I hope I can draft a rough explainer of the various components soon ™️

TLDR:

  • One can register a callback crux must call to generate effects out of band (not within the request/response model)
  • The effect macro generates a NacreEffect implementation, depending on the sort of effect (notify_shell, request_from_shell, stream_from_shell)
  • Nacre is some sort of middleware that plugs between the core and the bridge, that has the ability to drive streams to completion.
  • NacreBridge deals with forwarding the effects via the callback
  • Probably more but i forgot, I ll try to draw a schema sometimes.

Next things I need to do:

  • turn the UDL based definition to a macro to please uniffi with the callback / trait registration
  • rebump dependencies
  • see what ideas are good and what ideas aren't
  • ???
  • profit

@o0Ignition0o o0Ignition0o marked this pull request as draft March 22, 2025 10:25
@@ -15,11 +15,17 @@ typegen = ["crux_core/typegen"]
crux_core.workspace = true
serde = { workspace = true, features = ["derive"] }
lazy_static = "1.5.0"
uniffi = "0.29.0"
uniffi = "=0.28.3"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hope i can rebump these very soon

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What problem caused you to revert the version?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had an error with the way the shell / callback is registered in the .udl
I think I need to rely on the uniffi macros rather on the .udl file in order to rebump the dependency :/

Copy link
Collaborator

@mhedgpeth mhedgpeth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love the direction this request takes this project. I'm not a maintainer but in hopes of moving the conversation forward added my comments. Thanks so much for doing this work.

@@ -27,13 +38,43 @@ where
}
// ANCHOR_END: request

pub type RequestFfi<A> = Request<<<A as App>::Effect as Effect>::Ffi>;

pub trait NacreTrait<A: App>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to look up this word Nacre - google tells me it means "mother of pearl". I believe you're trying to express here the "rust side effects processor" here. At the very least it could be explained in a comment. Could you tell me more about what the intent/meaning is here?

Copy link
Contributor

@regexident regexident Apr 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mhedgpeth heh, while @o0Ignition0o I were sketching up the initial design for this PR we found we needed some name to refer to "the thing" we were working on ("let's do the name bike-shedding later!") and we sort of ended up at "the nacre thingy", which I had initially come up as a silly pun.

It ended up just sticking around for far longer than anticipated. I still think it's a cute name, but a more understandable "middleware"-ish one would probably be a better choice overall, for obvious reasons.
The reason we didn't just go with "middleware" was that those usually allow for multiple layers, while "nacre" is (in its current design) limited to just one layer, right between core and shell. Hence nacre, the inside of a shell.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, Middleware will do just fine, AFAICT Nacre refers to the "white side of an egg shell" as in it s the inner side if you re the shell, but it s the outer side if you're the core ^^

Middleware is more popular and may make more sense once the design settles anyway!

Self {
inner: BridgeWithSerializer::new(core),
inner: Arc::new(BridgeWithSerializer::new(core)),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After reading this through the first time I still didn't understand the need for reference counting here. What motivated this inclusion?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might actually be a left-over of a previous iteration and no longer be necessary. Can't say for sure from the top of my head. 🤔 cc @o0Ignition0o

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I think I had some send + sync invariants to uphold with the callback.

I'll need to revisit that once the middleware api is set, especially since the core doesn't need it but the bridge might in the example. I'll double check that!

effects.into_iter().map(std::convert::Into::into),
&mut <dyn erased_serde::Serializer>::erase(&mut ser),
)
.unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be a try_from to avoid the unwrap?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely :D

}
Some(id) => {
self.registry.resume(id, data)?;
self.registry.resume(id, data).expect(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be translated into an error?

let name_and_variant = quote! { #effect_name::#variant };

from_effect_to_shell_effect_impl.push(quote! { #effect_name::#variant(request) => panic!("capability {} is not implemented in the shell", stringify!(#name_and_variant)) });
match_arms.push(quote! { #effect_name::#variant(request) => panic!("capability {} is not implemented in the shell", stringify!(#name_and_variant)) });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading through this, the panic! makes me nervous but this is only for a situation where the developer doesn't have something set up fundamentally properly, has to make that change once, then it will always work. I'm honestly (as not a maintainer) not quite sure what the panic policy is on this project, this one seems reasonable.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right,

Afaict there are other panics in this macro, but maybe we can shift left and fail at compile time if this branch still exists by the time we build the app 🤔 not sure.

@@ -15,11 +15,17 @@ typegen = ["crux_core/typegen"]
crux_core.workspace = true
serde = { workspace = true, features = ["derive"] }
lazy_static = "1.5.0"
uniffi = "0.29.0"
uniffi = "=0.28.3"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What problem caused you to revert the version?

@@ -1,4 +1,6 @@
// ANCHOR: app
use super::capabilities::delay::Delay;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should fork this example, change simple_counter to be the minimum it needs to be to support this new callback (or do people need to do that?) and then add another example that shows this, since it is an advanced feature.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely, the example grew a bit because I started with an example to have an idea of what the DX should be. I'll revert most of the things and probably create a different example with a delay and an interval.

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

Successfully merging this pull request may close these issues.

3 participants