Move Builder to own module, allow for shared runtime reference. - #115

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2023-05-swap-out-builder
Jun 16, 2023
Merged

Move Builder to own module, allow for shared runtime reference.#115
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2023-05-swap-out-builder

Conversation

@tnull

Copy link
Copy Markdown
Collaborator

Firstly, as lib.rs got quite big and Builder made up a good chunk of it, we now move it to a dedicated submodule.

We then split the actual logic of build_with_store from the API method, which is needed as VSS in the
future will need to share the Runtime, i.e., will need to hand in a Arc<Rwlock<Runtime>>> parameter that we'll set Some in Node::start.

Finally, we do exactly that: we allow build_with_store_internal to take a runtime parameter.

@tnull

tnull commented Jun 13, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Now added a commit that re-introduces a paradigmatic Builder for the Rust API that takes &mut and returns a Node instead of an Arc<Node>. For uniffi we now export an ArcedNodeBuilder as Builder that wraps the former and returns an Arc<Node>.

I think it's preferable to have a clean Rust API here and have changed my mind regarding the necessary maintenance overhead (shouldn't be too bad now that we have the uniffi feature, we just have to deal with it). As this partly reverts #88, excuse the unnecessary churn! :(

tnull added 3 commits June 13, 2023 20:46
As `lib.rs` got quite big and `Builder` made up a good chunk of it, we
now move it to a dedicated submodule.
Splitting the actual logic from the API method is needed as VSS in the
future will need to share the Runtime, i.e., will need to hand in a
`Arc<Rwlock<Runtime>>>` parameter that we'll set `Some` in
`Node::start`.
This is required to allow the VSS `KVStore` to share a reference to the runtime.
The runtime option will be set `Some` by `Node::start`.
@tnull
tnullforce-pushed the 2023-05-swap-out-builder branch from a9c072f to 0e65429CompareJune 13, 2023 18:48
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Rebased on main.

Comment threadsrc/builder.rs Outdated
Comment threadsrc/builder.rs Outdated
pub fn set_entropy_seed_path(&self, seed_path: String) {
*self.entropy_source_config.write().unwrap() =
Some(EntropySourceConfig::SeedFile(seed_path));
pub fn set_entropy_seed_path(&mut self, seed_path: String) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Any reason we can't use move semantics now (i.e., take self and return Self)? That way you can chain mutators.

@tnulltnullJun 14, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I think move semantics still wouldn't work seamlessly as the ArcedNodeBuilder wrapper needs to own the inner NoderBuilder and we couldn't move/modify the RwLock'ed value in place? I guess we could work around that by ArcedNodeBuilder holding an inner: RwLock<Option<NodeBuilder>> and taking and re-setting the value upon each call. Not sure this is preferable to taking &mut and returning &mut Self?

Generally, the lack of mutator chaining is a good point: I simply had forgotten to add the &mut Self return values, now added.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think move semantics still wouldn't work seamlessly as the ArcedNodeBuilder wrapper needs to own the inner NoderBuilder and we couldn't move/modify the RwLock'ed value in place? I guess we could work around that by ArcedNodeBuilder holding an inner: RwLock<Option<NodeBuilder>> and taking and re-setting the value upon each call. Not sure this is preferable to taking &mut and returning &mut Self?

Right, it would require using an Option, I believe.

Generally, the lack of mutator chaining is a good point: I simply had forgotten to add the &mut Self return values, now added.

One reason I'd prefer returning Self over &mut Self is the former makes for a cleaner way of chaining when you need to do intermediary calculations before using the builder again. The latter requires initializing a variable with the builder before doing any chaining. Otherwise, you'd return a reference to a temporary, which the compiler will complain about:

creates a temporary which is freed while still in use

@tnulltnullJun 15, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

One reason I'd prefer returning Self over &mut Self is the former makes for a cleaner way of chaining when you need to do intermediary calculations before using the builder again. The latter requires initializing a variable with the builder before doing any chaining. Otherwise, you'd return a reference to a temporary, which the compiler will complain about:

creates a temporary which is freed while still in use

Mh, I now experimented with it and have it working in an experimental branch, see: tnull@3efa72e

Few thoughts:

  1. Move semantics indeed 'feel' a bit 'cleaner' too me as well.
  2. While moving indeed allow to 'quick safe' the builder state, it also requires the user to do so, i.e., if they don't want to do it all in a single line, they'd have to do
let builder = Builder::new().set_X()

rather than just

letmut builder = Builder.new();
builder.set_X();

This is a bit unfortunate also as the latter corresponds to the syntax they'd have to use in the bindings, i.e., our Rust docs would be a bit less transferable/relevant to the binding languages.

  1. Consuming restricts the use to a 1:1 relationship between Builder and Node. However, AFAIK there is no reason we shouldn't allow creation of multiple nodes with the same builder. Having the Builder be non-reusable also means that in bindings we would panic on a second build() command for now (https://github.com/tnull/ldk-node/blob/3efa72e1354bda556518873358f452ae29b3af9b/src/builder.rs#L314) and would need to introduce a dedicated BuildError::AlreadyBuilt type that's only returned for that case in Prefer returning errors over panicking where possible #119.

Not entirely convinced one way or the other yet tbh. Moving/consuming 'feels' more paradigmatic, but &mut self would allow us to keep the APIs closer together.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Mh, I now experimented with it and have it working in an experimental branch, see: tnull@3efa72e

Few thoughts:

  1. Move semantics indeed 'feel' a bit 'cleaner' too me as well.
  2. While moving indeed allow to 'quick safe' the builder state, it also requires the user to do so, i.e., if they don't want to do it all in a single line, they'd have to do
let builder = Builder::new().set_X()

rather than just

letmut builder = Builder.new();
builder.set_X();

Actually, this will cause a compilation error when the builder is later used since set_X consumed it but never reassigned it to builder. But maybe I'm misunderstanding what version each of the above is demonstrating...

This is a bit unfortunate also as the latter corresponds to the syntax they'd have to use in the bindings, i.e., our Rust docs would be a bit less transferable/relevant to the binding languages.

That's a fair point.

  1. Consuming restricts the use to a 1:1 relationship between Builder and Node. However, AFAIK there is no reason we shouldn't allow creation of multiple nodes with the same builder. Having the Builder be non-reusable also means that in bindings we would panic on a second build() command for now (https://github.com/tnull/ldk-node/blob/3efa72e1354bda556518873358f452ae29b3af9b/src/builder.rs#L314) and would need to introduce a dedicated BuildError::AlreadyBuilt type that's only returned for that case in Prefer returning errors over panicking where possible #119.

Alternatively, build functions don't necessarily need to consume if we want reuse. But if it consumes, then we could implement Clone to support building additional nodes. If not, then note that we are already internally cloning config fields to pass to the node. The consuming variation is a bit more efficient as those fields could be moved instead and only cloned when the user explicitly clones the builder.

The bindings version could internally clone the config to avoid needing a BuildError::AlreadyBuilt.

Not entirely convinced one way or the other yet tbh. Moving/consuming 'feels' more paradigmatic, but &mut self would allow us to keep the APIs closer together.

I don't feel very strongly if you prefer to keep the APIs similar. The common case of building a node in one statement looks the same either way. So I suppose it's a matter of whether API similarity is more important than how uncommon cases are formulated.

@tnulltnullJun 16, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Actually, this will cause a compilation error when the builder is later used since set_X consumed it but never reassigned it to builder. But maybe I'm misunderstanding what version each of the above is demonstrating...

Yes, this wouldn't work for the moving/consuming variant, which is exactly my point.

I don't feel very strongly if you prefer to keep the APIs similar. The common case of building a node in one statement looks the same either way.

Right the &mut variant is a bit more flexible as in that it allows for either the chaining pattern or the "setter" pattern. While the former would be mandatory for the moving variant, it doesn't work for the ArcedNodeBuilder exposed in Uniffi, and the "setter" pattern needed there doesn't work for NodeBuilder. This leads to an API incompatibility that's not only unfortunate, it for example also breaks the crate's doc test (see above linked commit), requiring a fix like: tnull@b0e2813. While the feature gating fix makes the doctest pass, it doesn't actually mask the docs, so they show both variants.. I'm not sure there is a good way working around this short of ignoreing this doctest, which I'd like to avoid as it functions as the main usage example.

However, I think I might take this example as a first sign that the slight Rust-only benefits of the moving variant might not be worth dealing with the increased complexity of API incompatibility going forward.

TLDR: I see moving/consuming could have some minor benefits and feels more paradigmatic, but I'm not sure it's worth having to deal with two API-incompatible builders going forward. At least with the &mut variant there is some common ground, also having the benefit that the Rust-only docs stay more relevant to the bindings.

Comment threadsrc/builder.rs

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM. Please squash fix-ups.

While this generates a bunch of boilerplate, it's probably worth the
maintainance effort to have a clean/paradigmatic Rust API. And, as we
had previously introduced a `uniffi` feature, we're now able to easily
switch out `Builder` exports based on it.
@tnull
tnullforce-pushed the 2023-05-swap-out-builder branch from 873fdc5 to a25632cCompareJune 16, 2023 16:52
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

LGTM. Please squash fix-ups.

Squashed without further changes.

@tnull
tnull merged commit 821b06a into lightningdevkit:mainJun 16, 2023
Sign up for freeto 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.

2 participants

@tnull@jkczyz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Move Builder to own module, allow for shared runtime reference. - #115

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2023-05-swap-out-builder
Jun 16, 2023
Merged

Move Builder to own module, allow for shared runtime reference.#115
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2023-05-swap-out-builder

Conversation

@tnull

Copy link
Copy Markdown
Collaborator

Firstly, as lib.rs got quite big and Builder made up a good chunk of it, we now move it to a dedicated submodule.

We then split the actual logic of build_with_store from the API method, which is needed as VSS in the
future will need to share the Runtime, i.e., will need to hand in a Arc<Rwlock<Runtime>>> parameter that we'll set Some in Node::start.

Finally, we do exactly that: we allow build_with_store_internal to take a runtime parameter.

@tnull

tnull commented Jun 13, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Now added a commit that re-introduces a paradigmatic Builder for the Rust API that takes &mut and returns a Node instead of an Arc<Node>. For uniffi we now export an ArcedNodeBuilder as Builder that wraps the former and returns an Arc<Node>.

I think it's preferable to have a clean Rust API here and have changed my mind regarding the necessary maintenance overhead (shouldn't be too bad now that we have the uniffi feature, we just have to deal with it). As this partly reverts #88, excuse the unnecessary churn! :(

tnull added 3 commits June 13, 2023 20:46
As `lib.rs` got quite big and `Builder` made up a good chunk of it, we
now move it to a dedicated submodule.
Splitting the actual logic from the API method is needed as VSS in the
future will need to share the Runtime, i.e., will need to hand in a
`Arc<Rwlock<Runtime>>>` parameter that we'll set `Some` in
`Node::start`.
This is required to allow the VSS `KVStore` to share a reference to the runtime.
The runtime option will be set `Some` by `Node::start`.
@tnull
tnullforce-pushed the 2023-05-swap-out-builder branch from a9c072f to 0e65429CompareJune 13, 2023 18:48
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Rebased on main.

Comment threadsrc/builder.rs Outdated
Comment threadsrc/builder.rs Outdated
pub fn set_entropy_seed_path(&self, seed_path: String) {
*self.entropy_source_config.write().unwrap() =
Some(EntropySourceConfig::SeedFile(seed_path));
pub fn set_entropy_seed_path(&mut self, seed_path: String) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Any reason we can't use move semantics now (i.e., take self and return Self)? That way you can chain mutators.

@tnulltnullJun 14, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I think move semantics still wouldn't work seamlessly as the ArcedNodeBuilder wrapper needs to own the inner NoderBuilder and we couldn't move/modify the RwLock'ed value in place? I guess we could work around that by ArcedNodeBuilder holding an inner: RwLock<Option<NodeBuilder>> and taking and re-setting the value upon each call. Not sure this is preferable to taking &mut and returning &mut Self?

Generally, the lack of mutator chaining is a good point: I simply had forgotten to add the &mut Self return values, now added.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think move semantics still wouldn't work seamlessly as the ArcedNodeBuilder wrapper needs to own the inner NoderBuilder and we couldn't move/modify the RwLock'ed value in place? I guess we could work around that by ArcedNodeBuilder holding an inner: RwLock<Option<NodeBuilder>> and taking and re-setting the value upon each call. Not sure this is preferable to taking &mut and returning &mut Self?

Right, it would require using an Option, I believe.

Generally, the lack of mutator chaining is a good point: I simply had forgotten to add the &mut Self return values, now added.

One reason I'd prefer returning Self over &mut Self is the former makes for a cleaner way of chaining when you need to do intermediary calculations before using the builder again. The latter requires initializing a variable with the builder before doing any chaining. Otherwise, you'd return a reference to a temporary, which the compiler will complain about:

creates a temporary which is freed while still in use

@tnulltnullJun 15, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

One reason I'd prefer returning Self over &mut Self is the former makes for a cleaner way of chaining when you need to do intermediary calculations before using the builder again. The latter requires initializing a variable with the builder before doing any chaining. Otherwise, you'd return a reference to a temporary, which the compiler will complain about:

creates a temporary which is freed while still in use

Mh, I now experimented with it and have it working in an experimental branch, see: tnull@3efa72e

Few thoughts:

  1. Move semantics indeed 'feel' a bit 'cleaner' too me as well.
  2. While moving indeed allow to 'quick safe' the builder state, it also requires the user to do so, i.e., if they don't want to do it all in a single line, they'd have to do
let builder = Builder::new().set_X()

rather than just

letmut builder = Builder.new();
builder.set_X();

This is a bit unfortunate also as the latter corresponds to the syntax they'd have to use in the bindings, i.e., our Rust docs would be a bit less transferable/relevant to the binding languages.

  1. Consuming restricts the use to a 1:1 relationship between Builder and Node. However, AFAIK there is no reason we shouldn't allow creation of multiple nodes with the same builder. Having the Builder be non-reusable also means that in bindings we would panic on a second build() command for now (https://github.com/tnull/ldk-node/blob/3efa72e1354bda556518873358f452ae29b3af9b/src/builder.rs#L314) and would need to introduce a dedicated BuildError::AlreadyBuilt type that's only returned for that case in Prefer returning errors over panicking where possible #119.

Not entirely convinced one way or the other yet tbh. Moving/consuming 'feels' more paradigmatic, but &mut self would allow us to keep the APIs closer together.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Mh, I now experimented with it and have it working in an experimental branch, see: tnull@3efa72e

Few thoughts:

  1. Move semantics indeed 'feel' a bit 'cleaner' too me as well.
  2. While moving indeed allow to 'quick safe' the builder state, it also requires the user to do so, i.e., if they don't want to do it all in a single line, they'd have to do
let builder = Builder::new().set_X()

rather than just

letmut builder = Builder.new();
builder.set_X();

Actually, this will cause a compilation error when the builder is later used since set_X consumed it but never reassigned it to builder. But maybe I'm misunderstanding what version each of the above is demonstrating...

This is a bit unfortunate also as the latter corresponds to the syntax they'd have to use in the bindings, i.e., our Rust docs would be a bit less transferable/relevant to the binding languages.

That's a fair point.

  1. Consuming restricts the use to a 1:1 relationship between Builder and Node. However, AFAIK there is no reason we shouldn't allow creation of multiple nodes with the same builder. Having the Builder be non-reusable also means that in bindings we would panic on a second build() command for now (https://github.com/tnull/ldk-node/blob/3efa72e1354bda556518873358f452ae29b3af9b/src/builder.rs#L314) and would need to introduce a dedicated BuildError::AlreadyBuilt type that's only returned for that case in Prefer returning errors over panicking where possible #119.

Alternatively, build functions don't necessarily need to consume if we want reuse. But if it consumes, then we could implement Clone to support building additional nodes. If not, then note that we are already internally cloning config fields to pass to the node. The consuming variation is a bit more efficient as those fields could be moved instead and only cloned when the user explicitly clones the builder.

The bindings version could internally clone the config to avoid needing a BuildError::AlreadyBuilt.

Not entirely convinced one way or the other yet tbh. Moving/consuming 'feels' more paradigmatic, but &mut self would allow us to keep the APIs closer together.

I don't feel very strongly if you prefer to keep the APIs similar. The common case of building a node in one statement looks the same either way. So I suppose it's a matter of whether API similarity is more important than how uncommon cases are formulated.

@tnulltnullJun 16, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Actually, this will cause a compilation error when the builder is later used since set_X consumed it but never reassigned it to builder. But maybe I'm misunderstanding what version each of the above is demonstrating...

Yes, this wouldn't work for the moving/consuming variant, which is exactly my point.

I don't feel very strongly if you prefer to keep the APIs similar. The common case of building a node in one statement looks the same either way.

Right the &mut variant is a bit more flexible as in that it allows for either the chaining pattern or the "setter" pattern. While the former would be mandatory for the moving variant, it doesn't work for the ArcedNodeBuilder exposed in Uniffi, and the "setter" pattern needed there doesn't work for NodeBuilder. This leads to an API incompatibility that's not only unfortunate, it for example also breaks the crate's doc test (see above linked commit), requiring a fix like: tnull@b0e2813. While the feature gating fix makes the doctest pass, it doesn't actually mask the docs, so they show both variants.. I'm not sure there is a good way working around this short of ignoreing this doctest, which I'd like to avoid as it functions as the main usage example.

However, I think I might take this example as a first sign that the slight Rust-only benefits of the moving variant might not be worth dealing with the increased complexity of API incompatibility going forward.

TLDR: I see moving/consuming could have some minor benefits and feels more paradigmatic, but I'm not sure it's worth having to deal with two API-incompatible builders going forward. At least with the &mut variant there is some common ground, also having the benefit that the Rust-only docs stay more relevant to the bindings.

Comment threadsrc/builder.rs

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM. Please squash fix-ups.

While this generates a bunch of boilerplate, it's probably worth the
maintainance effort to have a clean/paradigmatic Rust API. And, as we
had previously introduced a `uniffi` feature, we're now able to easily
switch out `Builder` exports based on it.
@tnull
tnullforce-pushed the 2023-05-swap-out-builder branch from 873fdc5 to a25632cCompareJune 16, 2023 16:52
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

LGTM. Please squash fix-ups.

Squashed without further changes.

@tnull
tnull merged commit 821b06a into lightningdevkit:mainJun 16, 2023
Sign up for freeto 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.

2 participants

@tnull@jkczyz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Move Builder to own module, allow for shared runtime reference. - #115

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2023-05-swap-out-builder
Jun 16, 2023
Merged

Move Builder to own module, allow for shared runtime reference.#115
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2023-05-swap-out-builder

Conversation

@tnull

Copy link
Copy Markdown
Collaborator

Firstly, as lib.rs got quite big and Builder made up a good chunk of it, we now move it to a dedicated submodule.

We then split the actual logic of build_with_store from the API method, which is needed as VSS in the
future will need to share the Runtime, i.e., will need to hand in a Arc<Rwlock<Runtime>>> parameter that we'll set Some in Node::start.

Finally, we do exactly that: we allow build_with_store_internal to take a runtime parameter.

@tnull

tnull commented Jun 13, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Now added a commit that re-introduces a paradigmatic Builder for the Rust API that takes &mut and returns a Node instead of an Arc<Node>. For uniffi we now export an ArcedNodeBuilder as Builder that wraps the former and returns an Arc<Node>.

I think it's preferable to have a clean Rust API here and have changed my mind regarding the necessary maintenance overhead (shouldn't be too bad now that we have the uniffi feature, we just have to deal with it). As this partly reverts #88, excuse the unnecessary churn! :(

tnull added 3 commits June 13, 2023 20:46
As `lib.rs` got quite big and `Builder` made up a good chunk of it, we
now move it to a dedicated submodule.
Splitting the actual logic from the API method is needed as VSS in the
future will need to share the Runtime, i.e., will need to hand in a
`Arc<Rwlock<Runtime>>>` parameter that we'll set `Some` in
`Node::start`.
This is required to allow the VSS `KVStore` to share a reference to the runtime.
The runtime option will be set `Some` by `Node::start`.
@tnull
tnullforce-pushed the 2023-05-swap-out-builder branch from a9c072f to 0e65429CompareJune 13, 2023 18:48
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Rebased on main.

Comment threadsrc/builder.rs Outdated
Comment threadsrc/builder.rs Outdated
pub fn set_entropy_seed_path(&self, seed_path: String) {
*self.entropy_source_config.write().unwrap() =
Some(EntropySourceConfig::SeedFile(seed_path));
pub fn set_entropy_seed_path(&mut self, seed_path: String) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Any reason we can't use move semantics now (i.e., take self and return Self)? That way you can chain mutators.

@tnulltnullJun 14, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I think move semantics still wouldn't work seamlessly as the ArcedNodeBuilder wrapper needs to own the inner NoderBuilder and we couldn't move/modify the RwLock'ed value in place? I guess we could work around that by ArcedNodeBuilder holding an inner: RwLock<Option<NodeBuilder>> and taking and re-setting the value upon each call. Not sure this is preferable to taking &mut and returning &mut Self?

Generally, the lack of mutator chaining is a good point: I simply had forgotten to add the &mut Self return values, now added.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think move semantics still wouldn't work seamlessly as the ArcedNodeBuilder wrapper needs to own the inner NoderBuilder and we couldn't move/modify the RwLock'ed value in place? I guess we could work around that by ArcedNodeBuilder holding an inner: RwLock<Option<NodeBuilder>> and taking and re-setting the value upon each call. Not sure this is preferable to taking &mut and returning &mut Self?

Right, it would require using an Option, I believe.

Generally, the lack of mutator chaining is a good point: I simply had forgotten to add the &mut Self return values, now added.

One reason I'd prefer returning Self over &mut Self is the former makes for a cleaner way of chaining when you need to do intermediary calculations before using the builder again. The latter requires initializing a variable with the builder before doing any chaining. Otherwise, you'd return a reference to a temporary, which the compiler will complain about:

creates a temporary which is freed while still in use

@tnulltnullJun 15, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

One reason I'd prefer returning Self over &mut Self is the former makes for a cleaner way of chaining when you need to do intermediary calculations before using the builder again. The latter requires initializing a variable with the builder before doing any chaining. Otherwise, you'd return a reference to a temporary, which the compiler will complain about:

creates a temporary which is freed while still in use

Mh, I now experimented with it and have it working in an experimental branch, see: tnull@3efa72e

Few thoughts:

  1. Move semantics indeed 'feel' a bit 'cleaner' too me as well.
  2. While moving indeed allow to 'quick safe' the builder state, it also requires the user to do so, i.e., if they don't want to do it all in a single line, they'd have to do
let builder = Builder::new().set_X()

rather than just

letmut builder = Builder.new();
builder.set_X();

This is a bit unfortunate also as the latter corresponds to the syntax they'd have to use in the bindings, i.e., our Rust docs would be a bit less transferable/relevant to the binding languages.

  1. Consuming restricts the use to a 1:1 relationship between Builder and Node. However, AFAIK there is no reason we shouldn't allow creation of multiple nodes with the same builder. Having the Builder be non-reusable also means that in bindings we would panic on a second build() command for now (https://github.com/tnull/ldk-node/blob/3efa72e1354bda556518873358f452ae29b3af9b/src/builder.rs#L314) and would need to introduce a dedicated BuildError::AlreadyBuilt type that's only returned for that case in Prefer returning errors over panicking where possible #119.

Not entirely convinced one way or the other yet tbh. Moving/consuming 'feels' more paradigmatic, but &mut self would allow us to keep the APIs closer together.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Mh, I now experimented with it and have it working in an experimental branch, see: tnull@3efa72e

Few thoughts:

  1. Move semantics indeed 'feel' a bit 'cleaner' too me as well.
  2. While moving indeed allow to 'quick safe' the builder state, it also requires the user to do so, i.e., if they don't want to do it all in a single line, they'd have to do
let builder = Builder::new().set_X()

rather than just

letmut builder = Builder.new();
builder.set_X();

Actually, this will cause a compilation error when the builder is later used since set_X consumed it but never reassigned it to builder. But maybe I'm misunderstanding what version each of the above is demonstrating...

This is a bit unfortunate also as the latter corresponds to the syntax they'd have to use in the bindings, i.e., our Rust docs would be a bit less transferable/relevant to the binding languages.

That's a fair point.

  1. Consuming restricts the use to a 1:1 relationship between Builder and Node. However, AFAIK there is no reason we shouldn't allow creation of multiple nodes with the same builder. Having the Builder be non-reusable also means that in bindings we would panic on a second build() command for now (https://github.com/tnull/ldk-node/blob/3efa72e1354bda556518873358f452ae29b3af9b/src/builder.rs#L314) and would need to introduce a dedicated BuildError::AlreadyBuilt type that's only returned for that case in Prefer returning errors over panicking where possible #119.

Alternatively, build functions don't necessarily need to consume if we want reuse. But if it consumes, then we could implement Clone to support building additional nodes. If not, then note that we are already internally cloning config fields to pass to the node. The consuming variation is a bit more efficient as those fields could be moved instead and only cloned when the user explicitly clones the builder.

The bindings version could internally clone the config to avoid needing a BuildError::AlreadyBuilt.

Not entirely convinced one way or the other yet tbh. Moving/consuming 'feels' more paradigmatic, but &mut self would allow us to keep the APIs closer together.

I don't feel very strongly if you prefer to keep the APIs similar. The common case of building a node in one statement looks the same either way. So I suppose it's a matter of whether API similarity is more important than how uncommon cases are formulated.

@tnulltnullJun 16, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Actually, this will cause a compilation error when the builder is later used since set_X consumed it but never reassigned it to builder. But maybe I'm misunderstanding what version each of the above is demonstrating...

Yes, this wouldn't work for the moving/consuming variant, which is exactly my point.

I don't feel very strongly if you prefer to keep the APIs similar. The common case of building a node in one statement looks the same either way.

Right the &mut variant is a bit more flexible as in that it allows for either the chaining pattern or the "setter" pattern. While the former would be mandatory for the moving variant, it doesn't work for the ArcedNodeBuilder exposed in Uniffi, and the "setter" pattern needed there doesn't work for NodeBuilder. This leads to an API incompatibility that's not only unfortunate, it for example also breaks the crate's doc test (see above linked commit), requiring a fix like: tnull@b0e2813. While the feature gating fix makes the doctest pass, it doesn't actually mask the docs, so they show both variants.. I'm not sure there is a good way working around this short of ignoreing this doctest, which I'd like to avoid as it functions as the main usage example.

However, I think I might take this example as a first sign that the slight Rust-only benefits of the moving variant might not be worth dealing with the increased complexity of API incompatibility going forward.

TLDR: I see moving/consuming could have some minor benefits and feels more paradigmatic, but I'm not sure it's worth having to deal with two API-incompatible builders going forward. At least with the &mut variant there is some common ground, also having the benefit that the Rust-only docs stay more relevant to the bindings.

Comment threadsrc/builder.rs

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM. Please squash fix-ups.

While this generates a bunch of boilerplate, it's probably worth the
maintainance effort to have a clean/paradigmatic Rust API. And, as we
had previously introduced a `uniffi` feature, we're now able to easily
switch out `Builder` exports based on it.
@tnull
tnullforce-pushed the 2023-05-swap-out-builder branch from 873fdc5 to a25632cCompareJune 16, 2023 16:52
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

LGTM. Please squash fix-ups.

Squashed without further changes.

@tnull
tnull merged commit 821b06a into lightningdevkit:mainJun 16, 2023
Sign up for freeto 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.

2 participants

@tnull@jkczyz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Move Builder to own module, allow for shared runtime reference. - #115

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2023-05-swap-out-builder
Jun 16, 2023
Merged

Move Builder to own module, allow for shared runtime reference.#115
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2023-05-swap-out-builder

Conversation

@tnull

Copy link
Copy Markdown
Collaborator

Firstly, as lib.rs got quite big and Builder made up a good chunk of it, we now move it to a dedicated submodule.

We then split the actual logic of build_with_store from the API method, which is needed as VSS in the
future will need to share the Runtime, i.e., will need to hand in a Arc<Rwlock<Runtime>>> parameter that we'll set Some in Node::start.

Finally, we do exactly that: we allow build_with_store_internal to take a runtime parameter.

@tnull

tnull commented Jun 13, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Now added a commit that re-introduces a paradigmatic Builder for the Rust API that takes &mut and returns a Node instead of an Arc<Node>. For uniffi we now export an ArcedNodeBuilder as Builder that wraps the former and returns an Arc<Node>.

I think it's preferable to have a clean Rust API here and have changed my mind regarding the necessary maintenance overhead (shouldn't be too bad now that we have the uniffi feature, we just have to deal with it). As this partly reverts #88, excuse the unnecessary churn! :(

tnull added 3 commits June 13, 2023 20:46
As `lib.rs` got quite big and `Builder` made up a good chunk of it, we
now move it to a dedicated submodule.
Splitting the actual logic from the API method is needed as VSS in the
future will need to share the Runtime, i.e., will need to hand in a
`Arc<Rwlock<Runtime>>>` parameter that we'll set `Some` in
`Node::start`.
This is required to allow the VSS `KVStore` to share a reference to the runtime.
The runtime option will be set `Some` by `Node::start`.
@tnull
tnullforce-pushed the 2023-05-swap-out-builder branch from a9c072f to 0e65429CompareJune 13, 2023 18:48
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Rebased on main.

Comment threadsrc/builder.rs Outdated
Comment threadsrc/builder.rs Outdated
pub fn set_entropy_seed_path(&self, seed_path: String) {
*self.entropy_source_config.write().unwrap() =
Some(EntropySourceConfig::SeedFile(seed_path));
pub fn set_entropy_seed_path(&mut self, seed_path: String) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Any reason we can't use move semantics now (i.e., take self and return Self)? That way you can chain mutators.

@tnulltnullJun 14, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I think move semantics still wouldn't work seamlessly as the ArcedNodeBuilder wrapper needs to own the inner NoderBuilder and we couldn't move/modify the RwLock'ed value in place? I guess we could work around that by ArcedNodeBuilder holding an inner: RwLock<Option<NodeBuilder>> and taking and re-setting the value upon each call. Not sure this is preferable to taking &mut and returning &mut Self?

Generally, the lack of mutator chaining is a good point: I simply had forgotten to add the &mut Self return values, now added.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think move semantics still wouldn't work seamlessly as the ArcedNodeBuilder wrapper needs to own the inner NoderBuilder and we couldn't move/modify the RwLock'ed value in place? I guess we could work around that by ArcedNodeBuilder holding an inner: RwLock<Option<NodeBuilder>> and taking and re-setting the value upon each call. Not sure this is preferable to taking &mut and returning &mut Self?

Right, it would require using an Option, I believe.

Generally, the lack of mutator chaining is a good point: I simply had forgotten to add the &mut Self return values, now added.

One reason I'd prefer returning Self over &mut Self is the former makes for a cleaner way of chaining when you need to do intermediary calculations before using the builder again. The latter requires initializing a variable with the builder before doing any chaining. Otherwise, you'd return a reference to a temporary, which the compiler will complain about:

creates a temporary which is freed while still in use

@tnulltnullJun 15, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

One reason I'd prefer returning Self over &mut Self is the former makes for a cleaner way of chaining when you need to do intermediary calculations before using the builder again. The latter requires initializing a variable with the builder before doing any chaining. Otherwise, you'd return a reference to a temporary, which the compiler will complain about:

creates a temporary which is freed while still in use

Mh, I now experimented with it and have it working in an experimental branch, see: tnull@3efa72e

Few thoughts:

  1. Move semantics indeed 'feel' a bit 'cleaner' too me as well.
  2. While moving indeed allow to 'quick safe' the builder state, it also requires the user to do so, i.e., if they don't want to do it all in a single line, they'd have to do
let builder = Builder::new().set_X()

rather than just

letmut builder = Builder.new();
builder.set_X();

This is a bit unfortunate also as the latter corresponds to the syntax they'd have to use in the bindings, i.e., our Rust docs would be a bit less transferable/relevant to the binding languages.

  1. Consuming restricts the use to a 1:1 relationship between Builder and Node. However, AFAIK there is no reason we shouldn't allow creation of multiple nodes with the same builder. Having the Builder be non-reusable also means that in bindings we would panic on a second build() command for now (https://github.com/tnull/ldk-node/blob/3efa72e1354bda556518873358f452ae29b3af9b/src/builder.rs#L314) and would need to introduce a dedicated BuildError::AlreadyBuilt type that's only returned for that case in Prefer returning errors over panicking where possible #119.

Not entirely convinced one way or the other yet tbh. Moving/consuming 'feels' more paradigmatic, but &mut self would allow us to keep the APIs closer together.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Mh, I now experimented with it and have it working in an experimental branch, see: tnull@3efa72e

Few thoughts:

  1. Move semantics indeed 'feel' a bit 'cleaner' too me as well.
  2. While moving indeed allow to 'quick safe' the builder state, it also requires the user to do so, i.e., if they don't want to do it all in a single line, they'd have to do
let builder = Builder::new().set_X()

rather than just

letmut builder = Builder.new();
builder.set_X();

Actually, this will cause a compilation error when the builder is later used since set_X consumed it but never reassigned it to builder. But maybe I'm misunderstanding what version each of the above is demonstrating...

This is a bit unfortunate also as the latter corresponds to the syntax they'd have to use in the bindings, i.e., our Rust docs would be a bit less transferable/relevant to the binding languages.

That's a fair point.

  1. Consuming restricts the use to a 1:1 relationship between Builder and Node. However, AFAIK there is no reason we shouldn't allow creation of multiple nodes with the same builder. Having the Builder be non-reusable also means that in bindings we would panic on a second build() command for now (https://github.com/tnull/ldk-node/blob/3efa72e1354bda556518873358f452ae29b3af9b/src/builder.rs#L314) and would need to introduce a dedicated BuildError::AlreadyBuilt type that's only returned for that case in Prefer returning errors over panicking where possible #119.

Alternatively, build functions don't necessarily need to consume if we want reuse. But if it consumes, then we could implement Clone to support building additional nodes. If not, then note that we are already internally cloning config fields to pass to the node. The consuming variation is a bit more efficient as those fields could be moved instead and only cloned when the user explicitly clones the builder.

The bindings version could internally clone the config to avoid needing a BuildError::AlreadyBuilt.

Not entirely convinced one way or the other yet tbh. Moving/consuming 'feels' more paradigmatic, but &mut self would allow us to keep the APIs closer together.

I don't feel very strongly if you prefer to keep the APIs similar. The common case of building a node in one statement looks the same either way. So I suppose it's a matter of whether API similarity is more important than how uncommon cases are formulated.

@tnulltnullJun 16, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Actually, this will cause a compilation error when the builder is later used since set_X consumed it but never reassigned it to builder. But maybe I'm misunderstanding what version each of the above is demonstrating...

Yes, this wouldn't work for the moving/consuming variant, which is exactly my point.

I don't feel very strongly if you prefer to keep the APIs similar. The common case of building a node in one statement looks the same either way.

Right the &mut variant is a bit more flexible as in that it allows for either the chaining pattern or the "setter" pattern. While the former would be mandatory for the moving variant, it doesn't work for the ArcedNodeBuilder exposed in Uniffi, and the "setter" pattern needed there doesn't work for NodeBuilder. This leads to an API incompatibility that's not only unfortunate, it for example also breaks the crate's doc test (see above linked commit), requiring a fix like: tnull@b0e2813. While the feature gating fix makes the doctest pass, it doesn't actually mask the docs, so they show both variants.. I'm not sure there is a good way working around this short of ignoreing this doctest, which I'd like to avoid as it functions as the main usage example.

However, I think I might take this example as a first sign that the slight Rust-only benefits of the moving variant might not be worth dealing with the increased complexity of API incompatibility going forward.

TLDR: I see moving/consuming could have some minor benefits and feels more paradigmatic, but I'm not sure it's worth having to deal with two API-incompatible builders going forward. At least with the &mut variant there is some common ground, also having the benefit that the Rust-only docs stay more relevant to the bindings.

Comment threadsrc/builder.rs

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM. Please squash fix-ups.

While this generates a bunch of boilerplate, it's probably worth the
maintainance effort to have a clean/paradigmatic Rust API. And, as we
had previously introduced a `uniffi` feature, we're now able to easily
switch out `Builder` exports based on it.
@tnull
tnullforce-pushed the 2023-05-swap-out-builder branch from 873fdc5 to a25632cCompareJune 16, 2023 16:52
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

LGTM. Please squash fix-ups.

Squashed without further changes.

@tnull
tnull merged commit 821b06a into lightningdevkit:mainJun 16, 2023
Sign up for freeto 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.

2 participants

@tnull@jkczyz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Move Builder to own module, allow for shared runtime reference. - #115

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2023-05-swap-out-builder
Jun 16, 2023
Merged

Move Builder to own module, allow for shared runtime reference.#115
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2023-05-swap-out-builder

Conversation

@tnull

Copy link
Copy Markdown
Collaborator

Firstly, as lib.rs got quite big and Builder made up a good chunk of it, we now move it to a dedicated submodule.

We then split the actual logic of build_with_store from the API method, which is needed as VSS in the
future will need to share the Runtime, i.e., will need to hand in a Arc<Rwlock<Runtime>>> parameter that we'll set Some in Node::start.

Finally, we do exactly that: we allow build_with_store_internal to take a runtime parameter.

@tnull

tnull commented Jun 13, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Now added a commit that re-introduces a paradigmatic Builder for the Rust API that takes &mut and returns a Node instead of an Arc<Node>. For uniffi we now export an ArcedNodeBuilder as Builder that wraps the former and returns an Arc<Node>.

I think it's preferable to have a clean Rust API here and have changed my mind regarding the necessary maintenance overhead (shouldn't be too bad now that we have the uniffi feature, we just have to deal with it). As this partly reverts #88, excuse the unnecessary churn! :(

tnull added 3 commits June 13, 2023 20:46
As `lib.rs` got quite big and `Builder` made up a good chunk of it, we
now move it to a dedicated submodule.
Splitting the actual logic from the API method is needed as VSS in the
future will need to share the Runtime, i.e., will need to hand in a
`Arc<Rwlock<Runtime>>>` parameter that we'll set `Some` in
`Node::start`.
This is required to allow the VSS `KVStore` to share a reference to the runtime.
The runtime option will be set `Some` by `Node::start`.
@tnull
tnullforce-pushed the 2023-05-swap-out-builder branch from a9c072f to 0e65429CompareJune 13, 2023 18:48
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Rebased on main.

Comment threadsrc/builder.rs Outdated
Comment threadsrc/builder.rs Outdated
pub fn set_entropy_seed_path(&self, seed_path: String) {
*self.entropy_source_config.write().unwrap() =
Some(EntropySourceConfig::SeedFile(seed_path));
pub fn set_entropy_seed_path(&mut self, seed_path: String) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Any reason we can't use move semantics now (i.e., take self and return Self)? That way you can chain mutators.

@tnulltnullJun 14, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I think move semantics still wouldn't work seamlessly as the ArcedNodeBuilder wrapper needs to own the inner NoderBuilder and we couldn't move/modify the RwLock'ed value in place? I guess we could work around that by ArcedNodeBuilder holding an inner: RwLock<Option<NodeBuilder>> and taking and re-setting the value upon each call. Not sure this is preferable to taking &mut and returning &mut Self?

Generally, the lack of mutator chaining is a good point: I simply had forgotten to add the &mut Self return values, now added.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think move semantics still wouldn't work seamlessly as the ArcedNodeBuilder wrapper needs to own the inner NoderBuilder and we couldn't move/modify the RwLock'ed value in place? I guess we could work around that by ArcedNodeBuilder holding an inner: RwLock<Option<NodeBuilder>> and taking and re-setting the value upon each call. Not sure this is preferable to taking &mut and returning &mut Self?

Right, it would require using an Option, I believe.

Generally, the lack of mutator chaining is a good point: I simply had forgotten to add the &mut Self return values, now added.

One reason I'd prefer returning Self over &mut Self is the former makes for a cleaner way of chaining when you need to do intermediary calculations before using the builder again. The latter requires initializing a variable with the builder before doing any chaining. Otherwise, you'd return a reference to a temporary, which the compiler will complain about:

creates a temporary which is freed while still in use

@tnulltnullJun 15, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

One reason I'd prefer returning Self over &mut Self is the former makes for a cleaner way of chaining when you need to do intermediary calculations before using the builder again. The latter requires initializing a variable with the builder before doing any chaining. Otherwise, you'd return a reference to a temporary, which the compiler will complain about:

creates a temporary which is freed while still in use

Mh, I now experimented with it and have it working in an experimental branch, see: tnull@3efa72e

Few thoughts:

  1. Move semantics indeed 'feel' a bit 'cleaner' too me as well.
  2. While moving indeed allow to 'quick safe' the builder state, it also requires the user to do so, i.e., if they don't want to do it all in a single line, they'd have to do
let builder = Builder::new().set_X()

rather than just

letmut builder = Builder.new();
builder.set_X();

This is a bit unfortunate also as the latter corresponds to the syntax they'd have to use in the bindings, i.e., our Rust docs would be a bit less transferable/relevant to the binding languages.

  1. Consuming restricts the use to a 1:1 relationship between Builder and Node. However, AFAIK there is no reason we shouldn't allow creation of multiple nodes with the same builder. Having the Builder be non-reusable also means that in bindings we would panic on a second build() command for now (https://github.com/tnull/ldk-node/blob/3efa72e1354bda556518873358f452ae29b3af9b/src/builder.rs#L314) and would need to introduce a dedicated BuildError::AlreadyBuilt type that's only returned for that case in Prefer returning errors over panicking where possible #119.

Not entirely convinced one way or the other yet tbh. Moving/consuming 'feels' more paradigmatic, but &mut self would allow us to keep the APIs closer together.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Mh, I now experimented with it and have it working in an experimental branch, see: tnull@3efa72e

Few thoughts:

  1. Move semantics indeed 'feel' a bit 'cleaner' too me as well.
  2. While moving indeed allow to 'quick safe' the builder state, it also requires the user to do so, i.e., if they don't want to do it all in a single line, they'd have to do
let builder = Builder::new().set_X()

rather than just

letmut builder = Builder.new();
builder.set_X();

Actually, this will cause a compilation error when the builder is later used since set_X consumed it but never reassigned it to builder. But maybe I'm misunderstanding what version each of the above is demonstrating...

This is a bit unfortunate also as the latter corresponds to the syntax they'd have to use in the bindings, i.e., our Rust docs would be a bit less transferable/relevant to the binding languages.

That's a fair point.

  1. Consuming restricts the use to a 1:1 relationship between Builder and Node. However, AFAIK there is no reason we shouldn't allow creation of multiple nodes with the same builder. Having the Builder be non-reusable also means that in bindings we would panic on a second build() command for now (https://github.com/tnull/ldk-node/blob/3efa72e1354bda556518873358f452ae29b3af9b/src/builder.rs#L314) and would need to introduce a dedicated BuildError::AlreadyBuilt type that's only returned for that case in Prefer returning errors over panicking where possible #119.

Alternatively, build functions don't necessarily need to consume if we want reuse. But if it consumes, then we could implement Clone to support building additional nodes. If not, then note that we are already internally cloning config fields to pass to the node. The consuming variation is a bit more efficient as those fields could be moved instead and only cloned when the user explicitly clones the builder.

The bindings version could internally clone the config to avoid needing a BuildError::AlreadyBuilt.

Not entirely convinced one way or the other yet tbh. Moving/consuming 'feels' more paradigmatic, but &mut self would allow us to keep the APIs closer together.

I don't feel very strongly if you prefer to keep the APIs similar. The common case of building a node in one statement looks the same either way. So I suppose it's a matter of whether API similarity is more important than how uncommon cases are formulated.

@tnulltnullJun 16, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Actually, this will cause a compilation error when the builder is later used since set_X consumed it but never reassigned it to builder. But maybe I'm misunderstanding what version each of the above is demonstrating...

Yes, this wouldn't work for the moving/consuming variant, which is exactly my point.

I don't feel very strongly if you prefer to keep the APIs similar. The common case of building a node in one statement looks the same either way.

Right the &mut variant is a bit more flexible as in that it allows for either the chaining pattern or the "setter" pattern. While the former would be mandatory for the moving variant, it doesn't work for the ArcedNodeBuilder exposed in Uniffi, and the "setter" pattern needed there doesn't work for NodeBuilder. This leads to an API incompatibility that's not only unfortunate, it for example also breaks the crate's doc test (see above linked commit), requiring a fix like: tnull@b0e2813. While the feature gating fix makes the doctest pass, it doesn't actually mask the docs, so they show both variants.. I'm not sure there is a good way working around this short of ignoreing this doctest, which I'd like to avoid as it functions as the main usage example.

However, I think I might take this example as a first sign that the slight Rust-only benefits of the moving variant might not be worth dealing with the increased complexity of API incompatibility going forward.

TLDR: I see moving/consuming could have some minor benefits and feels more paradigmatic, but I'm not sure it's worth having to deal with two API-incompatible builders going forward. At least with the &mut variant there is some common ground, also having the benefit that the Rust-only docs stay more relevant to the bindings.

Comment threadsrc/builder.rs

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM. Please squash fix-ups.

While this generates a bunch of boilerplate, it's probably worth the
maintainance effort to have a clean/paradigmatic Rust API. And, as we
had previously introduced a `uniffi` feature, we're now able to easily
switch out `Builder` exports based on it.
@tnull
tnullforce-pushed the 2023-05-swap-out-builder branch from 873fdc5 to a25632cCompareJune 16, 2023 16:52
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

LGTM. Please squash fix-ups.

Squashed without further changes.

@tnull
tnull merged commit 821b06a into lightningdevkit:mainJun 16, 2023
Sign up for freeto 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.

2 participants

@tnull@jkczyz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Move Builder to own module, allow for shared runtime reference. - #115

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2023-05-swap-out-builder
Jun 16, 2023
Merged

Move Builder to own module, allow for shared runtime reference.#115
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2023-05-swap-out-builder

Conversation

@tnull

Copy link
Copy Markdown
Collaborator

Firstly, as lib.rs got quite big and Builder made up a good chunk of it, we now move it to a dedicated submodule.

We then split the actual logic of build_with_store from the API method, which is needed as VSS in the
future will need to share the Runtime, i.e., will need to hand in a Arc<Rwlock<Runtime>>> parameter that we'll set Some in Node::start.

Finally, we do exactly that: we allow build_with_store_internal to take a runtime parameter.

@tnull

tnull commented Jun 13, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Now added a commit that re-introduces a paradigmatic Builder for the Rust API that takes &mut and returns a Node instead of an Arc<Node>. For uniffi we now export an ArcedNodeBuilder as Builder that wraps the former and returns an Arc<Node>.

I think it's preferable to have a clean Rust API here and have changed my mind regarding the necessary maintenance overhead (shouldn't be too bad now that we have the uniffi feature, we just have to deal with it). As this partly reverts #88, excuse the unnecessary churn! :(

tnull added 3 commits June 13, 2023 20:46
As `lib.rs` got quite big and `Builder` made up a good chunk of it, we
now move it to a dedicated submodule.
Splitting the actual logic from the API method is needed as VSS in the
future will need to share the Runtime, i.e., will need to hand in a
`Arc<Rwlock<Runtime>>>` parameter that we'll set `Some` in
`Node::start`.
This is required to allow the VSS `KVStore` to share a reference to the runtime.
The runtime option will be set `Some` by `Node::start`.
@tnull
tnullforce-pushed the 2023-05-swap-out-builder branch from a9c072f to 0e65429CompareJune 13, 2023 18:48
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Rebased on main.

Comment threadsrc/builder.rs Outdated
Comment threadsrc/builder.rs Outdated
pub fn set_entropy_seed_path(&self, seed_path: String) {
*self.entropy_source_config.write().unwrap() =
Some(EntropySourceConfig::SeedFile(seed_path));
pub fn set_entropy_seed_path(&mut self, seed_path: String) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Any reason we can't use move semantics now (i.e., take self and return Self)? That way you can chain mutators.

@tnulltnullJun 14, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I think move semantics still wouldn't work seamlessly as the ArcedNodeBuilder wrapper needs to own the inner NoderBuilder and we couldn't move/modify the RwLock'ed value in place? I guess we could work around that by ArcedNodeBuilder holding an inner: RwLock<Option<NodeBuilder>> and taking and re-setting the value upon each call. Not sure this is preferable to taking &mut and returning &mut Self?

Generally, the lack of mutator chaining is a good point: I simply had forgotten to add the &mut Self return values, now added.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think move semantics still wouldn't work seamlessly as the ArcedNodeBuilder wrapper needs to own the inner NoderBuilder and we couldn't move/modify the RwLock'ed value in place? I guess we could work around that by ArcedNodeBuilder holding an inner: RwLock<Option<NodeBuilder>> and taking and re-setting the value upon each call. Not sure this is preferable to taking &mut and returning &mut Self?

Right, it would require using an Option, I believe.

Generally, the lack of mutator chaining is a good point: I simply had forgotten to add the &mut Self return values, now added.

One reason I'd prefer returning Self over &mut Self is the former makes for a cleaner way of chaining when you need to do intermediary calculations before using the builder again. The latter requires initializing a variable with the builder before doing any chaining. Otherwise, you'd return a reference to a temporary, which the compiler will complain about:

creates a temporary which is freed while still in use

@tnulltnullJun 15, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

One reason I'd prefer returning Self over &mut Self is the former makes for a cleaner way of chaining when you need to do intermediary calculations before using the builder again. The latter requires initializing a variable with the builder before doing any chaining. Otherwise, you'd return a reference to a temporary, which the compiler will complain about:

creates a temporary which is freed while still in use

Mh, I now experimented with it and have it working in an experimental branch, see: tnull@3efa72e

Few thoughts:

  1. Move semantics indeed 'feel' a bit 'cleaner' too me as well.
  2. While moving indeed allow to 'quick safe' the builder state, it also requires the user to do so, i.e., if they don't want to do it all in a single line, they'd have to do
let builder = Builder::new().set_X()

rather than just

letmut builder = Builder.new();
builder.set_X();

This is a bit unfortunate also as the latter corresponds to the syntax they'd have to use in the bindings, i.e., our Rust docs would be a bit less transferable/relevant to the binding languages.

  1. Consuming restricts the use to a 1:1 relationship between Builder and Node. However, AFAIK there is no reason we shouldn't allow creation of multiple nodes with the same builder. Having the Builder be non-reusable also means that in bindings we would panic on a second build() command for now (https://github.com/tnull/ldk-node/blob/3efa72e1354bda556518873358f452ae29b3af9b/src/builder.rs#L314) and would need to introduce a dedicated BuildError::AlreadyBuilt type that's only returned for that case in Prefer returning errors over panicking where possible #119.

Not entirely convinced one way or the other yet tbh. Moving/consuming 'feels' more paradigmatic, but &mut self would allow us to keep the APIs closer together.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Mh, I now experimented with it and have it working in an experimental branch, see: tnull@3efa72e

Few thoughts:

  1. Move semantics indeed 'feel' a bit 'cleaner' too me as well.
  2. While moving indeed allow to 'quick safe' the builder state, it also requires the user to do so, i.e., if they don't want to do it all in a single line, they'd have to do
let builder = Builder::new().set_X()

rather than just

letmut builder = Builder.new();
builder.set_X();

Actually, this will cause a compilation error when the builder is later used since set_X consumed it but never reassigned it to builder. But maybe I'm misunderstanding what version each of the above is demonstrating...

This is a bit unfortunate also as the latter corresponds to the syntax they'd have to use in the bindings, i.e., our Rust docs would be a bit less transferable/relevant to the binding languages.

That's a fair point.

  1. Consuming restricts the use to a 1:1 relationship between Builder and Node. However, AFAIK there is no reason we shouldn't allow creation of multiple nodes with the same builder. Having the Builder be non-reusable also means that in bindings we would panic on a second build() command for now (https://github.com/tnull/ldk-node/blob/3efa72e1354bda556518873358f452ae29b3af9b/src/builder.rs#L314) and would need to introduce a dedicated BuildError::AlreadyBuilt type that's only returned for that case in Prefer returning errors over panicking where possible #119.

Alternatively, build functions don't necessarily need to consume if we want reuse. But if it consumes, then we could implement Clone to support building additional nodes. If not, then note that we are already internally cloning config fields to pass to the node. The consuming variation is a bit more efficient as those fields could be moved instead and only cloned when the user explicitly clones the builder.

The bindings version could internally clone the config to avoid needing a BuildError::AlreadyBuilt.

Not entirely convinced one way or the other yet tbh. Moving/consuming 'feels' more paradigmatic, but &mut self would allow us to keep the APIs closer together.

I don't feel very strongly if you prefer to keep the APIs similar. The common case of building a node in one statement looks the same either way. So I suppose it's a matter of whether API similarity is more important than how uncommon cases are formulated.

@tnulltnullJun 16, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Actually, this will cause a compilation error when the builder is later used since set_X consumed it but never reassigned it to builder. But maybe I'm misunderstanding what version each of the above is demonstrating...

Yes, this wouldn't work for the moving/consuming variant, which is exactly my point.

I don't feel very strongly if you prefer to keep the APIs similar. The common case of building a node in one statement looks the same either way.

Right the &mut variant is a bit more flexible as in that it allows for either the chaining pattern or the "setter" pattern. While the former would be mandatory for the moving variant, it doesn't work for the ArcedNodeBuilder exposed in Uniffi, and the "setter" pattern needed there doesn't work for NodeBuilder. This leads to an API incompatibility that's not only unfortunate, it for example also breaks the crate's doc test (see above linked commit), requiring a fix like: tnull@b0e2813. While the feature gating fix makes the doctest pass, it doesn't actually mask the docs, so they show both variants.. I'm not sure there is a good way working around this short of ignoreing this doctest, which I'd like to avoid as it functions as the main usage example.

However, I think I might take this example as a first sign that the slight Rust-only benefits of the moving variant might not be worth dealing with the increased complexity of API incompatibility going forward.

TLDR: I see moving/consuming could have some minor benefits and feels more paradigmatic, but I'm not sure it's worth having to deal with two API-incompatible builders going forward. At least with the &mut variant there is some common ground, also having the benefit that the Rust-only docs stay more relevant to the bindings.

Comment threadsrc/builder.rs

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM. Please squash fix-ups.

While this generates a bunch of boilerplate, it's probably worth the
maintainance effort to have a clean/paradigmatic Rust API. And, as we
had previously introduced a `uniffi` feature, we're now able to easily
switch out `Builder` exports based on it.
@tnull
tnullforce-pushed the 2023-05-swap-out-builder branch from 873fdc5 to a25632cCompareJune 16, 2023 16:52
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

LGTM. Please squash fix-ups.

Squashed without further changes.

@tnull
tnull merged commit 821b06a into lightningdevkit:mainJun 16, 2023
Sign up for freeto 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.

2 participants

@tnull@jkczyz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Move Builder to own module, allow for shared runtime reference. - #115

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2023-05-swap-out-builder
Jun 16, 2023
Merged

Move Builder to own module, allow for shared runtime reference.#115
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2023-05-swap-out-builder

Conversation

@tnull

Copy link
Copy Markdown
Collaborator

Firstly, as lib.rs got quite big and Builder made up a good chunk of it, we now move it to a dedicated submodule.

We then split the actual logic of build_with_store from the API method, which is needed as VSS in the
future will need to share the Runtime, i.e., will need to hand in a Arc<Rwlock<Runtime>>> parameter that we'll set Some in Node::start.

Finally, we do exactly that: we allow build_with_store_internal to take a runtime parameter.

@tnull

tnull commented Jun 13, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Now added a commit that re-introduces a paradigmatic Builder for the Rust API that takes &mut and returns a Node instead of an Arc<Node>. For uniffi we now export an ArcedNodeBuilder as Builder that wraps the former and returns an Arc<Node>.

I think it's preferable to have a clean Rust API here and have changed my mind regarding the necessary maintenance overhead (shouldn't be too bad now that we have the uniffi feature, we just have to deal with it). As this partly reverts #88, excuse the unnecessary churn! :(

tnull added 3 commits June 13, 2023 20:46
As `lib.rs` got quite big and `Builder` made up a good chunk of it, we
now move it to a dedicated submodule.
Splitting the actual logic from the API method is needed as VSS in the
future will need to share the Runtime, i.e., will need to hand in a
`Arc<Rwlock<Runtime>>>` parameter that we'll set `Some` in
`Node::start`.
This is required to allow the VSS `KVStore` to share a reference to the runtime.
The runtime option will be set `Some` by `Node::start`.
@tnull
tnullforce-pushed the 2023-05-swap-out-builder branch from a9c072f to 0e65429CompareJune 13, 2023 18:48
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Rebased on main.

Comment threadsrc/builder.rs Outdated
Comment threadsrc/builder.rs Outdated
pub fn set_entropy_seed_path(&self, seed_path: String) {
*self.entropy_source_config.write().unwrap() =
Some(EntropySourceConfig::SeedFile(seed_path));
pub fn set_entropy_seed_path(&mut self, seed_path: String) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Any reason we can't use move semantics now (i.e., take self and return Self)? That way you can chain mutators.

@tnulltnullJun 14, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I think move semantics still wouldn't work seamlessly as the ArcedNodeBuilder wrapper needs to own the inner NoderBuilder and we couldn't move/modify the RwLock'ed value in place? I guess we could work around that by ArcedNodeBuilder holding an inner: RwLock<Option<NodeBuilder>> and taking and re-setting the value upon each call. Not sure this is preferable to taking &mut and returning &mut Self?

Generally, the lack of mutator chaining is a good point: I simply had forgotten to add the &mut Self return values, now added.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think move semantics still wouldn't work seamlessly as the ArcedNodeBuilder wrapper needs to own the inner NoderBuilder and we couldn't move/modify the RwLock'ed value in place? I guess we could work around that by ArcedNodeBuilder holding an inner: RwLock<Option<NodeBuilder>> and taking and re-setting the value upon each call. Not sure this is preferable to taking &mut and returning &mut Self?

Right, it would require using an Option, I believe.

Generally, the lack of mutator chaining is a good point: I simply had forgotten to add the &mut Self return values, now added.

One reason I'd prefer returning Self over &mut Self is the former makes for a cleaner way of chaining when you need to do intermediary calculations before using the builder again. The latter requires initializing a variable with the builder before doing any chaining. Otherwise, you'd return a reference to a temporary, which the compiler will complain about:

creates a temporary which is freed while still in use

@tnulltnullJun 15, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

One reason I'd prefer returning Self over &mut Self is the former makes for a cleaner way of chaining when you need to do intermediary calculations before using the builder again. The latter requires initializing a variable with the builder before doing any chaining. Otherwise, you'd return a reference to a temporary, which the compiler will complain about:

creates a temporary which is freed while still in use

Mh, I now experimented with it and have it working in an experimental branch, see: tnull@3efa72e

Few thoughts:

  1. Move semantics indeed 'feel' a bit 'cleaner' too me as well.
  2. While moving indeed allow to 'quick safe' the builder state, it also requires the user to do so, i.e., if they don't want to do it all in a single line, they'd have to do
let builder = Builder::new().set_X()

rather than just

letmut builder = Builder.new();
builder.set_X();

This is a bit unfortunate also as the latter corresponds to the syntax they'd have to use in the bindings, i.e., our Rust docs would be a bit less transferable/relevant to the binding languages.

  1. Consuming restricts the use to a 1:1 relationship between Builder and Node. However, AFAIK there is no reason we shouldn't allow creation of multiple nodes with the same builder. Having the Builder be non-reusable also means that in bindings we would panic on a second build() command for now (https://github.com/tnull/ldk-node/blob/3efa72e1354bda556518873358f452ae29b3af9b/src/builder.rs#L314) and would need to introduce a dedicated BuildError::AlreadyBuilt type that's only returned for that case in Prefer returning errors over panicking where possible #119.

Not entirely convinced one way or the other yet tbh. Moving/consuming 'feels' more paradigmatic, but &mut self would allow us to keep the APIs closer together.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Mh, I now experimented with it and have it working in an experimental branch, see: tnull@3efa72e

Few thoughts:

  1. Move semantics indeed 'feel' a bit 'cleaner' too me as well.
  2. While moving indeed allow to 'quick safe' the builder state, it also requires the user to do so, i.e., if they don't want to do it all in a single line, they'd have to do
let builder = Builder::new().set_X()

rather than just

letmut builder = Builder.new();
builder.set_X();

Actually, this will cause a compilation error when the builder is later used since set_X consumed it but never reassigned it to builder. But maybe I'm misunderstanding what version each of the above is demonstrating...

This is a bit unfortunate also as the latter corresponds to the syntax they'd have to use in the bindings, i.e., our Rust docs would be a bit less transferable/relevant to the binding languages.

That's a fair point.

  1. Consuming restricts the use to a 1:1 relationship between Builder and Node. However, AFAIK there is no reason we shouldn't allow creation of multiple nodes with the same builder. Having the Builder be non-reusable also means that in bindings we would panic on a second build() command for now (https://github.com/tnull/ldk-node/blob/3efa72e1354bda556518873358f452ae29b3af9b/src/builder.rs#L314) and would need to introduce a dedicated BuildError::AlreadyBuilt type that's only returned for that case in Prefer returning errors over panicking where possible #119.

Alternatively, build functions don't necessarily need to consume if we want reuse. But if it consumes, then we could implement Clone to support building additional nodes. If not, then note that we are already internally cloning config fields to pass to the node. The consuming variation is a bit more efficient as those fields could be moved instead and only cloned when the user explicitly clones the builder.

The bindings version could internally clone the config to avoid needing a BuildError::AlreadyBuilt.

Not entirely convinced one way or the other yet tbh. Moving/consuming 'feels' more paradigmatic, but &mut self would allow us to keep the APIs closer together.

I don't feel very strongly if you prefer to keep the APIs similar. The common case of building a node in one statement looks the same either way. So I suppose it's a matter of whether API similarity is more important than how uncommon cases are formulated.

@tnulltnullJun 16, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Actually, this will cause a compilation error when the builder is later used since set_X consumed it but never reassigned it to builder. But maybe I'm misunderstanding what version each of the above is demonstrating...

Yes, this wouldn't work for the moving/consuming variant, which is exactly my point.

I don't feel very strongly if you prefer to keep the APIs similar. The common case of building a node in one statement looks the same either way.

Right the &mut variant is a bit more flexible as in that it allows for either the chaining pattern or the "setter" pattern. While the former would be mandatory for the moving variant, it doesn't work for the ArcedNodeBuilder exposed in Uniffi, and the "setter" pattern needed there doesn't work for NodeBuilder. This leads to an API incompatibility that's not only unfortunate, it for example also breaks the crate's doc test (see above linked commit), requiring a fix like: tnull@b0e2813. While the feature gating fix makes the doctest pass, it doesn't actually mask the docs, so they show both variants.. I'm not sure there is a good way working around this short of ignoreing this doctest, which I'd like to avoid as it functions as the main usage example.

However, I think I might take this example as a first sign that the slight Rust-only benefits of the moving variant might not be worth dealing with the increased complexity of API incompatibility going forward.

TLDR: I see moving/consuming could have some minor benefits and feels more paradigmatic, but I'm not sure it's worth having to deal with two API-incompatible builders going forward. At least with the &mut variant there is some common ground, also having the benefit that the Rust-only docs stay more relevant to the bindings.

Comment threadsrc/builder.rs

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM. Please squash fix-ups.

While this generates a bunch of boilerplate, it's probably worth the
maintainance effort to have a clean/paradigmatic Rust API. And, as we
had previously introduced a `uniffi` feature, we're now able to easily
switch out `Builder` exports based on it.
@tnull
tnullforce-pushed the 2023-05-swap-out-builder branch from 873fdc5 to a25632cCompareJune 16, 2023 16:52
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

LGTM. Please squash fix-ups.

Squashed without further changes.

@tnull
tnull merged commit 821b06a into lightningdevkit:mainJun 16, 2023
Sign up for freeto 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.

2 participants

@tnull@jkczyz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Move Builder to own module, allow for shared runtime reference. - #115

Merged
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2023-05-swap-out-builder
Jun 16, 2023
Merged

Move Builder to own module, allow for shared runtime reference.#115
tnull merged 4 commits into
lightningdevkit:mainfrom
tnull:2023-05-swap-out-builder

Conversation

@tnull

Copy link
Copy Markdown
Collaborator

Firstly, as lib.rs got quite big and Builder made up a good chunk of it, we now move it to a dedicated submodule.

We then split the actual logic of build_with_store from the API method, which is needed as VSS in the
future will need to share the Runtime, i.e., will need to hand in a Arc<Rwlock<Runtime>>> parameter that we'll set Some in Node::start.

Finally, we do exactly that: we allow build_with_store_internal to take a runtime parameter.

@tnull

tnull commented Jun 13, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Now added a commit that re-introduces a paradigmatic Builder for the Rust API that takes &mut and returns a Node instead of an Arc<Node>. For uniffi we now export an ArcedNodeBuilder as Builder that wraps the former and returns an Arc<Node>.

I think it's preferable to have a clean Rust API here and have changed my mind regarding the necessary maintenance overhead (shouldn't be too bad now that we have the uniffi feature, we just have to deal with it). As this partly reverts #88, excuse the unnecessary churn! :(

tnull added 3 commits June 13, 2023 20:46
As `lib.rs` got quite big and `Builder` made up a good chunk of it, we
now move it to a dedicated submodule.
Splitting the actual logic from the API method is needed as VSS in the
future will need to share the Runtime, i.e., will need to hand in a
`Arc<Rwlock<Runtime>>>` parameter that we'll set `Some` in
`Node::start`.
This is required to allow the VSS `KVStore` to share a reference to the runtime.
The runtime option will be set `Some` by `Node::start`.
@tnull
tnullforce-pushed the 2023-05-swap-out-builder branch from a9c072f to 0e65429CompareJune 13, 2023 18:48
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

Rebased on main.

Comment threadsrc/builder.rs Outdated
Comment threadsrc/builder.rs Outdated
pub fn set_entropy_seed_path(&self, seed_path: String) {
*self.entropy_source_config.write().unwrap() =
Some(EntropySourceConfig::SeedFile(seed_path));
pub fn set_entropy_seed_path(&mut self, seed_path: String) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Any reason we can't use move semantics now (i.e., take self and return Self)? That way you can chain mutators.

@tnulltnullJun 14, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I think move semantics still wouldn't work seamlessly as the ArcedNodeBuilder wrapper needs to own the inner NoderBuilder and we couldn't move/modify the RwLock'ed value in place? I guess we could work around that by ArcedNodeBuilder holding an inner: RwLock<Option<NodeBuilder>> and taking and re-setting the value upon each call. Not sure this is preferable to taking &mut and returning &mut Self?

Generally, the lack of mutator chaining is a good point: I simply had forgotten to add the &mut Self return values, now added.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think move semantics still wouldn't work seamlessly as the ArcedNodeBuilder wrapper needs to own the inner NoderBuilder and we couldn't move/modify the RwLock'ed value in place? I guess we could work around that by ArcedNodeBuilder holding an inner: RwLock<Option<NodeBuilder>> and taking and re-setting the value upon each call. Not sure this is preferable to taking &mut and returning &mut Self?

Right, it would require using an Option, I believe.

Generally, the lack of mutator chaining is a good point: I simply had forgotten to add the &mut Self return values, now added.

One reason I'd prefer returning Self over &mut Self is the former makes for a cleaner way of chaining when you need to do intermediary calculations before using the builder again. The latter requires initializing a variable with the builder before doing any chaining. Otherwise, you'd return a reference to a temporary, which the compiler will complain about:

creates a temporary which is freed while still in use

@tnulltnullJun 15, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

One reason I'd prefer returning Self over &mut Self is the former makes for a cleaner way of chaining when you need to do intermediary calculations before using the builder again. The latter requires initializing a variable with the builder before doing any chaining. Otherwise, you'd return a reference to a temporary, which the compiler will complain about:

creates a temporary which is freed while still in use

Mh, I now experimented with it and have it working in an experimental branch, see: tnull@3efa72e

Few thoughts:

  1. Move semantics indeed 'feel' a bit 'cleaner' too me as well.
  2. While moving indeed allow to 'quick safe' the builder state, it also requires the user to do so, i.e., if they don't want to do it all in a single line, they'd have to do
let builder = Builder::new().set_X()

rather than just

letmut builder = Builder.new();
builder.set_X();

This is a bit unfortunate also as the latter corresponds to the syntax they'd have to use in the bindings, i.e., our Rust docs would be a bit less transferable/relevant to the binding languages.

  1. Consuming restricts the use to a 1:1 relationship between Builder and Node. However, AFAIK there is no reason we shouldn't allow creation of multiple nodes with the same builder. Having the Builder be non-reusable also means that in bindings we would panic on a second build() command for now (https://github.com/tnull/ldk-node/blob/3efa72e1354bda556518873358f452ae29b3af9b/src/builder.rs#L314) and would need to introduce a dedicated BuildError::AlreadyBuilt type that's only returned for that case in Prefer returning errors over panicking where possible #119.

Not entirely convinced one way or the other yet tbh. Moving/consuming 'feels' more paradigmatic, but &mut self would allow us to keep the APIs closer together.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Mh, I now experimented with it and have it working in an experimental branch, see: tnull@3efa72e

Few thoughts:

  1. Move semantics indeed 'feel' a bit 'cleaner' too me as well.
  2. While moving indeed allow to 'quick safe' the builder state, it also requires the user to do so, i.e., if they don't want to do it all in a single line, they'd have to do
let builder = Builder::new().set_X()

rather than just

letmut builder = Builder.new();
builder.set_X();

Actually, this will cause a compilation error when the builder is later used since set_X consumed it but never reassigned it to builder. But maybe I'm misunderstanding what version each of the above is demonstrating...

This is a bit unfortunate also as the latter corresponds to the syntax they'd have to use in the bindings, i.e., our Rust docs would be a bit less transferable/relevant to the binding languages.

That's a fair point.

  1. Consuming restricts the use to a 1:1 relationship between Builder and Node. However, AFAIK there is no reason we shouldn't allow creation of multiple nodes with the same builder. Having the Builder be non-reusable also means that in bindings we would panic on a second build() command for now (https://github.com/tnull/ldk-node/blob/3efa72e1354bda556518873358f452ae29b3af9b/src/builder.rs#L314) and would need to introduce a dedicated BuildError::AlreadyBuilt type that's only returned for that case in Prefer returning errors over panicking where possible #119.

Alternatively, build functions don't necessarily need to consume if we want reuse. But if it consumes, then we could implement Clone to support building additional nodes. If not, then note that we are already internally cloning config fields to pass to the node. The consuming variation is a bit more efficient as those fields could be moved instead and only cloned when the user explicitly clones the builder.

The bindings version could internally clone the config to avoid needing a BuildError::AlreadyBuilt.

Not entirely convinced one way or the other yet tbh. Moving/consuming 'feels' more paradigmatic, but &mut self would allow us to keep the APIs closer together.

I don't feel very strongly if you prefer to keep the APIs similar. The common case of building a node in one statement looks the same either way. So I suppose it's a matter of whether API similarity is more important than how uncommon cases are formulated.

@tnulltnullJun 16, 2023

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Actually, this will cause a compilation error when the builder is later used since set_X consumed it but never reassigned it to builder. But maybe I'm misunderstanding what version each of the above is demonstrating...

Yes, this wouldn't work for the moving/consuming variant, which is exactly my point.

I don't feel very strongly if you prefer to keep the APIs similar. The common case of building a node in one statement looks the same either way.

Right the &mut variant is a bit more flexible as in that it allows for either the chaining pattern or the "setter" pattern. While the former would be mandatory for the moving variant, it doesn't work for the ArcedNodeBuilder exposed in Uniffi, and the "setter" pattern needed there doesn't work for NodeBuilder. This leads to an API incompatibility that's not only unfortunate, it for example also breaks the crate's doc test (see above linked commit), requiring a fix like: tnull@b0e2813. While the feature gating fix makes the doctest pass, it doesn't actually mask the docs, so they show both variants.. I'm not sure there is a good way working around this short of ignoreing this doctest, which I'd like to avoid as it functions as the main usage example.

However, I think I might take this example as a first sign that the slight Rust-only benefits of the moving variant might not be worth dealing with the increased complexity of API incompatibility going forward.

TLDR: I see moving/consuming could have some minor benefits and feels more paradigmatic, but I'm not sure it's worth having to deal with two API-incompatible builders going forward. At least with the &mut variant there is some common ground, also having the benefit that the Rust-only docs stay more relevant to the bindings.

Comment threadsrc/builder.rs

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM. Please squash fix-ups.

While this generates a bunch of boilerplate, it's probably worth the
maintainance effort to have a clean/paradigmatic Rust API. And, as we
had previously introduced a `uniffi` feature, we're now able to easily
switch out `Builder` exports based on it.
@tnull
tnullforce-pushed the 2023-05-swap-out-builder branch from 873fdc5 to a25632cCompareJune 16, 2023 16:52
@tnull

Copy link
Copy Markdown
CollaboratorAuthor

LGTM. Please squash fix-ups.

Squashed without further changes.

@tnull
tnull merged commit 821b06a into lightningdevkit:mainJun 16, 2023
Sign up for freeto 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.

2 participants

@tnull@jkczyz