Anki/proto/anki/scheduler.proto
Damien Elmes 45f5709214
Migrate to protobuf-es (#2547)
* Fix .no-reduce-motion missing from graphs spinner, and not being honored

* Begin migration from protobuf.js -> protobuf-es

Motivation:

- Protobuf-es has a nicer API: messages are represented as classes, and
fields which should exist are not marked as nullable.
- As it uses modules, only the proto messages we actually use get included
in our bundle output. Protobuf.js put everything in a namespace, which
prevented tree-shaking, and made it awkward to access inner messages.
- ./run after touching a proto file drops from about 8s to 6s on my machine. The tradeoff
is slower decoding/encoding (#2043), but that was mainly a concern for the
graphs page, and was unblocked by
37151213cd

Approach/notes:

- We generate the new protobuf-es interface in addition to existing
protobuf.js interface, so we can migrate a module at a time, starting
with the graphs module.
- rslib:proto now generates RPC methods for TS in addition to the Python
interface. The input-arg-unrolling behaviour of the Python generation is
not required here, as we declare the input arg as a PlainMessage<T>, which
marks it as requiring all fields to be provided.
- i64 is represented as bigint in protobuf-es. We were using a patch to
protobuf.js to get it to output Javascript numbers instead of long.js
types, but now that our supported browser versions support bigint, it's
probably worth biting the bullet and migrating to bigint use. Our IDs
fit comfortably within MAX_SAFE_INTEGER, but that may not hold for future
fields we add.
- Oneofs are handled differently in protobuf-es, and are going to need
some refactoring.

Other notable changes:

- Added a --mkdir arg to our build runner, so we can create a dir easily
during the build on Windows.
- Simplified the preference handling code, by wrapping the preferences
in an outer store, instead of a separate store for each individual
preference. This means a change to one preference will trigger a redraw
of all components that depend on the preference store, but the redrawing
is cheap after moving the data processing to Rust, and it makes the code
easier to follow.
- Drop async(Reactive).ts in favour of more explicit handling with await
blocks/updating.
- Renamed add_inputs_to_group() -> add_dependency(), and fixed it not adding
dependencies to parent groups. Renamed add() -> add_action() for clarity.

* Remove a couple of unused proto imports

* Migrate card info

* Migrate congrats, image occlusion, and tag editor

+ Fix imports for multi-word proto files.

* Migrate change-notetype

* Migrate deck options

* Bump target to es2020; simplify ts lib list

Have used caniuse.com to confirm Chromium 77, iOS 14.5 and the Chrome
on Android support the full es2017-es2020 features.

* Migrate import-csv

* Migrate i18n and fix missing output types in .js

* Migrate custom scheduling, and remove protobuf.js

To mostly maintain our old API contract, we make use of protobuf-es's
ability to convert to JSON, which follows the same format as protobuf.js
did. It doesn't cover all case: users who were previously changing the
variant of a type will need to update their code, as assigning to a new
variant no longer automatically removes the old one, which will cause an
error when we try to convert back from JSON. But I suspect the large majority
of users are adjusting the current variant rather than creating a new one,
and this saves us having to write proxy wrappers, so it seems like a
reasonable compromise.

One other change I made at the same time was to rename value->kind for
the oneofs in our custom study protos, as 'value' was easily confused
with the 'case/value' output that protobuf-es has.

With protobuf.js codegen removed, touching a proto file and invoking
./run drops from about 8s to 6s.

This closes #2043.

* Allow tree-shaking on protobuf types

* Display backend error messages in our ts alert()

* Make sourcemap generation opt-in for ts-run

Considerably slows down build, and not used most of the time.
2023-06-14 22:47:37 +10:00

332 lines
8.4 KiB
Protocol Buffer

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
syntax = "proto3";
option java_multiple_files = true;
package anki.scheduler;
import "anki/generic.proto";
import "anki/cards.proto";
import "anki/decks.proto";
import "anki/collection.proto";
import "anki/config.proto";
service SchedulerService {
rpc GetQueuedCards(GetQueuedCardsRequest) returns (QueuedCards);
rpc AnswerCard(CardAnswer) returns (collection.OpChanges);
rpc SchedTimingToday(generic.Empty) returns (SchedTimingTodayResponse);
rpc StudiedToday(generic.Empty) returns (generic.String);
rpc StudiedTodayMessage(StudiedTodayMessageRequest) returns (generic.String);
rpc UpdateStats(UpdateStatsRequest) returns (generic.Empty);
rpc ExtendLimits(ExtendLimitsRequest) returns (generic.Empty);
rpc CountsForDeckToday(decks.DeckId) returns (CountsForDeckTodayResponse);
rpc CongratsInfo(generic.Empty) returns (CongratsInfoResponse);
rpc RestoreBuriedAndSuspendedCards(cards.CardIds)
returns (collection.OpChanges);
rpc UnburyDeck(UnburyDeckRequest) returns (collection.OpChanges);
rpc BuryOrSuspendCards(BuryOrSuspendCardsRequest)
returns (collection.OpChangesWithCount);
rpc EmptyFilteredDeck(decks.DeckId) returns (collection.OpChanges);
rpc RebuildFilteredDeck(decks.DeckId) returns (collection.OpChangesWithCount);
rpc ScheduleCardsAsNew(ScheduleCardsAsNewRequest)
returns (collection.OpChanges);
rpc ScheduleCardsAsNewDefaults(ScheduleCardsAsNewDefaultsRequest)
returns (ScheduleCardsAsNewDefaultsResponse);
rpc SetDueDate(SetDueDateRequest) returns (collection.OpChanges);
rpc SortCards(SortCardsRequest) returns (collection.OpChangesWithCount);
rpc SortDeck(SortDeckRequest) returns (collection.OpChangesWithCount);
rpc GetSchedulingStates(cards.CardId) returns (SchedulingStates);
// This should be implemented by the frontend, and should return the values
// from the reviewer. The backend method will throw an error.
rpc GetSchedulingStatesWithContext(generic.Empty)
returns (SchedulingStatesWithContext);
// This should be implemented by the frontend, and should update the state
// data in the reviewer. The backend method will throw an error.
rpc SetSchedulingStates(SetSchedulingStatesRequest) returns (generic.Empty);
rpc DescribeNextStates(SchedulingStates) returns (generic.StringList);
rpc StateIsLeech(SchedulingState) returns (generic.Bool);
rpc UpgradeScheduler(generic.Empty) returns (generic.Empty);
rpc CustomStudy(CustomStudyRequest) returns (collection.OpChanges);
rpc CustomStudyDefaults(CustomStudyDefaultsRequest)
returns (CustomStudyDefaultsResponse);
rpc RepositionDefaults(generic.Empty) returns (RepositionDefaultsResponse);
}
message SchedulingState {
message New {
uint32 position = 1;
}
message Learning {
uint32 remaining_steps = 1;
uint32 scheduled_secs = 2;
}
message Review {
uint32 scheduled_days = 1;
uint32 elapsed_days = 2;
float ease_factor = 3;
uint32 lapses = 4;
bool leeched = 5;
}
message Relearning {
Review review = 1;
Learning learning = 2;
}
message Normal {
oneof kind {
New new = 1;
Learning learning = 2;
Review review = 3;
Relearning relearning = 4;
}
}
message Preview {
uint32 scheduled_secs = 1;
bool finished = 2;
}
message ReschedulingFilter {
Normal original_state = 1;
}
message Filtered {
oneof kind {
Preview preview = 1;
ReschedulingFilter rescheduling = 2;
}
}
oneof kind {
Normal normal = 1;
Filtered filtered = 2;
}
// The backend does not populate this field in GetQueuedCards; the front-end
// is expected to populate it based on the provided Card. If it's not set when
// answering a card, the existing custom data will not be updated.
optional string custom_data = 3;
}
message QueuedCards {
enum Queue {
NEW = 0;
LEARNING = 1;
REVIEW = 2;
}
message QueuedCard {
cards.Card card = 1;
Queue queue = 2;
SchedulingStates states = 3;
SchedulingContext context = 4;
}
repeated QueuedCard cards = 1;
uint32 new_count = 2;
uint32 learning_count = 3;
uint32 review_count = 4;
}
message GetQueuedCardsRequest {
uint32 fetch_limit = 1;
bool intraday_learning_only = 2;
}
message SchedTimingTodayResponse {
uint32 days_elapsed = 1;
int64 next_day_at = 2;
}
message StudiedTodayMessageRequest {
uint32 cards = 1;
double seconds = 2;
}
message UpdateStatsRequest {
int64 deck_id = 1;
int32 new_delta = 2;
int32 review_delta = 4;
int32 millisecond_delta = 5;
}
message ExtendLimitsRequest {
int64 deck_id = 1;
int32 new_delta = 2;
int32 review_delta = 3;
}
message CountsForDeckTodayResponse {
int32 new = 1;
int32 review = 2;
}
message CongratsInfoResponse {
uint32 learn_remaining = 1;
uint32 secs_until_next_learn = 2;
bool review_remaining = 3;
bool new_remaining = 4;
bool have_sched_buried = 5;
bool have_user_buried = 6;
bool is_filtered_deck = 7;
bool bridge_commands_supported = 8;
string deck_description = 9;
}
message UnburyDeckRequest {
enum Mode {
ALL = 0;
SCHED_ONLY = 1;
USER_ONLY = 2;
}
int64 deck_id = 1;
Mode mode = 2;
}
message BuryOrSuspendCardsRequest {
enum Mode {
SUSPEND = 0;
BURY_SCHED = 1;
BURY_USER = 2;
}
repeated int64 card_ids = 1;
repeated int64 note_ids = 2;
Mode mode = 3;
}
message ScheduleCardsAsNewRequest {
enum Context {
BROWSER = 0;
REVIEWER = 1;
}
repeated int64 card_ids = 1;
bool log = 2;
bool restore_position = 3;
bool reset_counts = 4;
optional Context context = 5;
}
message ScheduleCardsAsNewDefaultsRequest {
ScheduleCardsAsNewRequest.Context context = 1;
}
message ScheduleCardsAsNewDefaultsResponse {
bool restore_position = 1;
bool reset_counts = 2;
}
message SetDueDateRequest {
repeated int64 card_ids = 1;
string days = 2;
config.OptionalStringConfigKey config_key = 3;
}
message SortCardsRequest {
repeated int64 card_ids = 1;
uint32 starting_from = 2;
uint32 step_size = 3;
bool randomize = 4;
bool shift_existing = 5;
}
message SortDeckRequest {
int64 deck_id = 1;
bool randomize = 2;
}
message SchedulingStates {
SchedulingState current = 1;
SchedulingState again = 2;
SchedulingState hard = 3;
SchedulingState good = 4;
SchedulingState easy = 5;
}
message CardAnswer {
enum Rating {
AGAIN = 0;
HARD = 1;
GOOD = 2;
EASY = 3;
}
int64 card_id = 1;
SchedulingState current_state = 2;
SchedulingState new_state = 3;
Rating rating = 4;
int64 answered_at_millis = 5;
uint32 milliseconds_taken = 6;
}
message CustomStudyRequest {
message Cram {
enum CramKind {
// due cards in due order
CRAM_KIND_DUE = 0;
// new cards in added order
CRAM_KIND_NEW = 1;
// review cards in random order
CRAM_KIND_REVIEW = 2;
// all cards in random order; no rescheduling
CRAM_KIND_ALL = 3;
}
CramKind kind = 1;
// the maximum number of cards
uint32 card_limit = 2;
// cards must match one of these, if unempty
repeated string tags_to_include = 3;
// cards must not match any of these
repeated string tags_to_exclude = 4;
}
int64 deck_id = 1;
oneof value {
// increase new limit by x
int32 new_limit_delta = 2;
// increase review limit by x
int32 review_limit_delta = 3;
// repeat cards forgotten in the last x days
uint32 forgot_days = 4;
// review cards due in the next x days
uint32 review_ahead_days = 5;
// preview new cards added in the last x days
uint32 preview_days = 6;
Cram cram = 7;
}
}
message SchedulingContext {
string deck_name = 1;
uint64 seed = 2;
}
message SchedulingStatesWithContext {
SchedulingStates states = 1;
SchedulingContext context = 2;
}
message CustomStudyDefaultsRequest {
int64 deck_id = 1;
}
message CustomStudyDefaultsResponse {
message Tag {
string name = 1;
bool include = 2;
bool exclude = 3;
}
repeated Tag tags = 1;
uint32 extend_new = 2;
uint32 extend_review = 3;
uint32 available_new = 4;
uint32 available_review = 5;
// in v3, counts for children are provided separately
uint32 available_new_in_children = 6;
uint32 available_review_in_children = 7;
}
message RepositionDefaultsResponse {
bool random = 1;
bool shift = 2;
}
message SetSchedulingStatesRequest {
string key = 1;
SchedulingStates states = 2;
}