anki/rslib/rust_interface.rs

292 lines
9.0 KiB
Rust
Raw Normal View History

Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use std::env;
use std::fmt::Write;
use std::path::PathBuf;
use anki_io::write_file_if_changed;
use anki_proto_gen::get_services;
use anki_proto_gen::BackendService;
use anki_proto_gen::CollectionService;
use anki_proto_gen::Method;
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
use anyhow::Context;
use anyhow::Result;
use inflections::Inflect;
Improve presentation of importing results (#2568) * Implement import log screen in Svelte * Show filename in import log screen title * Remove unused NoteRow property * Show number of imported notes * Use a single nid expression * Use 'count' as variable name for consistency * Import from @tslib/backend instead * Fix summary_template typing * Fix clippy warning * Apply suggestions from code review * Fix imports * Contents -> Fields * Increase max length of browser search bar https://github.com/ankitects/anki/pull/2568/files#r1255227035 * Fix race condition in Bootstrap tooltip destruction https://github.com/twbs/bootstrap/issues/37474 * summary_template -> summaryTemplate * Make show link a button * Run import ops on Svelte side * Fix geometry not being restored in CSV Import page * Make VirtualTable fill available height * Keep CSV dialog modal * Reword importing-existing-notes-skipped * Avoid mentioning matching based on first field * Change tick and cross icons * List skipped notes last * Pure CSS spinner * Move set_wants_abort() call to relevant dialogs * Show number of imported cards * Remove bold from first sentence and indent summaries * Update UI after import operations * Add close button to import log page Also make virtual table react to resize event. * Fix typing * Make CSV dialog non-modal again Otherwise user can't interact with browser window. * Update window modality after import * Commit DB and update undo actions after import op * Split frontend proto into separate file, so backend can ignore it Currently the automatically-generated frontend RPC methods get placed in 'backend.js' with all the backend methods; we could optionally split them into a separate 'frontend.js' file in the future. * Migrate import_done from a bridgecmd to a HTTP request * Update plural form of importing-notes-added * Move import response handling to mediasrv * Move task callback to script section * Avoid unnecessary :global() * .log cannot be missing if result exists * Move import log search handling to mediasrv * Type common params of ImportLogDialog * Use else if * Remove console.log() * Add way to test apkg imports in new log screen * Remove unused import * Get actual card count for CSV imports * Use import type * Fix typing error * Ignore import log when checking for changes in Python layer * Apply suggestions from code review * Remove imported card count for now * Avoid non-null assertion in assignment * Change showInBrowser to take an array of notes * Use dataclasses for import log args * Simplify ResultWithChanges in TS * Only abort import when window is modal * Fix ResultWithChanges typing * Fix Rust warnings * Only log one duplicate per incoming note * Update wording about note updates * Remove caveat about found_notes * Reduce font size * Remove redundant map * Give credit to loading.io * Remove unused line --------- Co-authored-by: RumovZ <gp5glkw78@relay.firefox.com>
2023-08-02 12:29:44 +02:00
use itertools::Itertools;
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
use prost_reflect::DescriptorPool;
pub fn write_rust_interface(pool: &DescriptorPool) -> Result<()> {
let mut buf = String::new();
buf.push_str("use crate::error::Result; use prost::Message;");
let (col_services, backend_services) = get_services(pool);
Improve presentation of importing results (#2568) * Implement import log screen in Svelte * Show filename in import log screen title * Remove unused NoteRow property * Show number of imported notes * Use a single nid expression * Use 'count' as variable name for consistency * Import from @tslib/backend instead * Fix summary_template typing * Fix clippy warning * Apply suggestions from code review * Fix imports * Contents -> Fields * Increase max length of browser search bar https://github.com/ankitects/anki/pull/2568/files#r1255227035 * Fix race condition in Bootstrap tooltip destruction https://github.com/twbs/bootstrap/issues/37474 * summary_template -> summaryTemplate * Make show link a button * Run import ops on Svelte side * Fix geometry not being restored in CSV Import page * Make VirtualTable fill available height * Keep CSV dialog modal * Reword importing-existing-notes-skipped * Avoid mentioning matching based on first field * Change tick and cross icons * List skipped notes last * Pure CSS spinner * Move set_wants_abort() call to relevant dialogs * Show number of imported cards * Remove bold from first sentence and indent summaries * Update UI after import operations * Add close button to import log page Also make virtual table react to resize event. * Fix typing * Make CSV dialog non-modal again Otherwise user can't interact with browser window. * Update window modality after import * Commit DB and update undo actions after import op * Split frontend proto into separate file, so backend can ignore it Currently the automatically-generated frontend RPC methods get placed in 'backend.js' with all the backend methods; we could optionally split them into a separate 'frontend.js' file in the future. * Migrate import_done from a bridgecmd to a HTTP request * Update plural form of importing-notes-added * Move import response handling to mediasrv * Move task callback to script section * Avoid unnecessary :global() * .log cannot be missing if result exists * Move import log search handling to mediasrv * Type common params of ImportLogDialog * Use else if * Remove console.log() * Add way to test apkg imports in new log screen * Remove unused import * Get actual card count for CSV imports * Use import type * Fix typing error * Ignore import log when checking for changes in Python layer * Apply suggestions from code review * Remove imported card count for now * Avoid non-null assertion in assignment * Change showInBrowser to take an array of notes * Use dataclasses for import log args * Simplify ResultWithChanges in TS * Only abort import when window is modal * Fix ResultWithChanges typing * Fix Rust warnings * Only log one duplicate per incoming note * Update wording about note updates * Remove caveat about found_notes * Reduce font size * Remove redundant map * Give credit to loading.io * Remove unused line --------- Co-authored-by: RumovZ <gp5glkw78@relay.firefox.com>
2023-08-02 12:29:44 +02:00
let col_services = col_services
.into_iter()
.filter(|s| s.name != "FrontendService")
.collect_vec();
let backend_services = backend_services
.into_iter()
.filter(|s| s.name != "BackendFrontendService")
.collect_vec();
render_collection_services(&col_services, &mut buf)?;
render_backend_services(&backend_services, &mut buf)?;
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
let buf = format_code(buf)?;
// println!("{}", &buf);
// panic!();
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
let out_dir = env::var("OUT_DIR").unwrap();
let path = PathBuf::from(out_dir).join("backend.rs");
write_file_if_changed(path, buf).context("write file")?;
Ok(())
}
fn render_collection_services(col_services: &[CollectionService], buf: &mut String) -> Result<()> {
for service in col_services {
render_collection_trait(service, buf);
render_individual_service_run_method_for_collection(buf, service);
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
}
render_top_level_run_method(
col_services.iter().map(|s| (s.index, s.name.as_str())),
"&mut self",
"crate::collection::Collection",
buf,
);
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
Ok(())
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
}
fn render_backend_services(backend_services: &[BackendService], buf: &mut String) -> Result<()> {
for service in backend_services {
render_backend_trait(service, buf);
render_delegating_backend_methods(service, buf);
render_individual_service_run_method_for_backend(buf, service);
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
}
render_top_level_run_method(
backend_services.iter().map(|s| (s.index, s.name.as_str())),
"&self",
"crate::backend::Backend",
buf,
);
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
Ok(())
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
}
fn format_code(code: String) -> Result<String> {
let syntax_tree = syn::parse_file(&code)?;
Ok(prettyplease::unparse(&syntax_tree))
}
fn render_collection_trait(service: &CollectionService, buf: &mut String) {
let name = &service.name;
writeln!(buf, "pub trait {name} {{").unwrap();
for method in &service.trait_methods {
render_trait_method(method, "&mut self", buf);
}
buf.push('}');
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
}
fn render_trait_method(method: &Method, self_kind: &str, buf: &mut String) {
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
let method_name = &method.name;
let input_with_label = method.get_input_arg_with_label();
let output_type = method.get_output_type();
writeln!(
buf,
"fn {method_name}({self_kind}, {input_with_label}) -> Result<{output_type}>;"
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
)
.unwrap();
}
fn render_backend_trait(service: &BackendService, buf: &mut String) {
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
let name = &service.name;
writeln!(buf, "pub trait {name} {{").unwrap();
for method in &service.trait_methods {
render_trait_method(method, "&self", buf);
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
}
buf.push('}');
}
fn render_delegating_backend_methods(service: &BackendService, buf: &mut String) {
buf.push_str("impl crate::backend::Backend {");
for method in &service.delegating_methods {
render_delegating_backend_method(method, service.name.trim_start_matches("Backend"), buf);
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
}
buf.push('}');
}
fn render_delegating_backend_method(method: &Method, method_qualifier: &str, buf: &mut String) {
let method_name = &method.name;
let input_with_label = method.get_input_arg_with_label();
let input = method.text_if_input_not_empty(|_| "input".into());
let output_type = method.get_output_type();
writeln!(
buf,
"fn {method_name}(&self, {input_with_label}) -> Result<{output_type}> {{
self.with_col(|col| {method_qualifier}::{method_name}(col, {input})) }}",
)
.unwrap();
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
}
// Matches all service types and delegates to the revelant self.run_foo_method()
fn render_top_level_run_method<'a>(
// (index, name)
services: impl Iterator<Item = (usize, &'a str)>,
self_kind: &str,
struct_name: &str,
buf: &mut String,
) {
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
writeln!(buf,
r#" impl {struct_name} {{
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
pub fn run_service_method({self_kind}, service: u32, method: u32, input: &[u8]) -> Result<Vec<u8>, Vec<u8>> {{
match service {{
"#,
).unwrap();
for (idx, service) in services {
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
writeln!(
buf,
"{idx} => self.run_{service}_method(method, input),",
service = service.to_snake_case()
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
)
.unwrap();
}
buf.push_str(
r#"
_ => Err(crate::error::AnkiError::InvalidServiceIndex),
}
.map_err(|err| {
let backend_err = err.into_protobuf(&self.tr);
let mut bytes = Vec::new();
backend_err.encode(&mut bytes).unwrap();
bytes
})
} }"#,
);
}
fn render_individual_service_run_method_for_collection(
buf: &mut String,
service: &CollectionService,
) {
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
let service_name = &service.name.to_snake_case();
writeln!(
buf,
"#[allow(unused_variables, clippy::match_single_binding)]
impl crate::collection::Collection {{ pub(crate) fn run_{service_name}_method(&mut self,
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
method: u32, input: &[u8]) -> Result<Vec<u8>> {{
match method {{",
)
.unwrap();
for method in &service.trait_methods {
render_method_in_match_expression(method, &service.name, buf);
}
buf.push_str(
r#"
_ => Err(crate::error::AnkiError::InvalidMethodIndex),
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
}
} }
"#,
);
}
fn render_individual_service_run_method_for_backend(buf: &mut String, service: &BackendService) {
let service_name = &service.name.to_snake_case();
writeln!(
buf,
"#[allow(unused_variables, clippy::match_single_binding)]
impl crate::backend::Backend {{ pub(crate) fn run_{service_name}_method(&self,
method: u32, input: &[u8]) -> Result<Vec<u8>> {{
match method {{",
)
.unwrap();
for method in &service.trait_methods {
render_method_in_match_expression(method, &service.name, buf);
}
for method in &service.delegating_methods {
render_method_in_match_expression(method, "crate::backend::Backend", buf);
Refactor service generation (#2552) * Automatically elide empty inputs and outputs to backend methods * Refactor service generation Despite the fact that the majority of our Protobuf service methods require an open collection, they were not accessible with just a Collection object. To access the methods (e.g. because we haven't gotten around to exposing the correct API in Collection yet), you had to wrap the collection in a Backend object, and pay a mutex-acquisition cost for each call, even if you have exclusive access to the object. This commit migrates the majority of service methods to the Collection, so they can now be used directly, and improves the ergonomics a bit at the same time. The approach taken: - The service generation now happens in rslib instead of anki_proto, which avoids the need for trait constraints and associated types. - Service methods are assumed to be collection-based by default. Instead of implementing the service on Backend, we now implement it on Collection, which means our methods no longer need to use self.with_col(...). - We automatically generate methods in Backend which use self.with_col() to delegate to the Collection method. - For methods that are only appropriate for the backend, we add a flag in the .proto file. The codegen uses this flag to write the method into a BackendFooService instead of FooService, which the backend implements. - The flag can also allows us to define separate implementations for collection and backend, so we can e.g. skip the collection mutex in the i18n service while also providing the service on a collection.
2023-06-19 07:33:40 +02:00
}
buf.push_str(
r#"
_ => Err(crate::error::AnkiError::InvalidMethodIndex),
}
} }
"#,
);
}
fn render_method_in_match_expression(method: &Method, method_qualifier: &str, buf: &mut String) {
let decode_input =
method.text_if_input_not_empty(|kind| format!("let input = {kind}::decode(input)?;"));
let rust_method = &method.name;
let input = method.text_if_input_not_empty(|_| "input".into());
let output_assign = method.text_if_output_not_empty(|_| "let output = ".into());
let idx = method.index;
let output = if method.output().is_none() {
"Vec::new()"
} else {
"{ let mut out_bytes = Vec::new();
output.encode(&mut out_bytes)?;
out_bytes }"
};
writeln!(
buf,
"{idx} => {{ {decode_input}
{output_assign} {method_qualifier}::{rust_method}(self, {input})?;
Ok({output}) }},",
)
.unwrap();
}
trait MethodHelpers {
fn input_type(&self) -> Option<String>;
fn output_type(&self) -> Option<String>;
fn text_if_input_not_empty(&self, text: impl Fn(&String) -> String) -> String;
fn get_input_arg_with_label(&self) -> String;
fn get_output_type(&self) -> String;
fn text_if_output_not_empty(&self, text: impl Fn(&String) -> String) -> String;
}
impl MethodHelpers for Method {
fn input_type(&self) -> Option<String> {
self.input().map(|t| rust_type(t.full_name()))
}
fn output_type(&self) -> Option<String> {
self.output().map(|t| rust_type(t.full_name()))
}
/// No text if generic::Empty
fn text_if_input_not_empty(&self, text: impl Fn(&String) -> String) -> String {
self.input_type().as_ref().map(text).unwrap_or_default()
}
/// No text if generic::Empty
fn get_input_arg_with_label(&self) -> String {
self.input_type()
.as_ref()
.map(|t| format!("input: {}", t))
.unwrap_or_default()
}
/// () if generic::Empty
fn get_output_type(&self) -> String {
self.output_type().as_deref().unwrap_or("()").into()
}
fn text_if_output_not_empty(&self, text: impl Fn(&String) -> String) -> String {
self.output_type().as_ref().map(text).unwrap_or_default()
}
}
fn rust_type(name: &str) -> String {
let Some((head, tail)) = name.rsplit_once('.') else {
panic!()
};
format!(
"{}::{}",
head.to_snake_case()
.replace('.', "::")
.replace("anki::", "anki_proto::"),
tail
)
}