Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Speed up deck list by rendering it with the backend #11599

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import com.ichi2.libanki.backend.exception.DeckRenameException
import com.ichi2.libanki.exception.EmptyMediaException
import com.ichi2.libanki.sched.AbstractSched
import com.ichi2.libanki.sched.DeckDueTreeNode
import com.ichi2.libanki.sched.TreeNode
import com.ichi2.libanki.utils.TimeManager
import com.ichi2.utils.FileUtil.internalizeUri
import com.ichi2.utils.JSONArray
Expand Down Expand Up @@ -375,28 +376,44 @@ class CardContentProvider : ContentProvider() {
rv
}
DECKS -> {
val allDecks = col.sched.deckDueList()
val columns = projection ?: FlashCardsContract.Deck.DEFAULT_PROJECTION
val rv = MatrixCursor(columns, allDecks.size)
for (deck: DeckDueTreeNode? in allDecks) {
val id = deck!!.did
val name = deck.fullDeckName
addDeckToCursor(id, name, getDeckCountsFromDueTreeNode(deck), rv, col, columns)
val allDecks = col.sched.deckDueTree()
val rv = MatrixCursor(columns, 1)
fun forEach(nodeList: List<TreeNode<DeckDueTreeNode>>, fn: (DeckDueTreeNode) -> Unit) {
for (node in nodeList) {
fn(node.value)
forEach(node.children, fn)
}
}
forEach(allDecks) {
addDeckToCursor(
it.did,
it.fullDeckName,
getDeckCountsFromDueTreeNode(it),
rv,
col,
columns
)
}
rv
}
DECKS_ID -> {

/* Direct access deck */
val columns = projection ?: FlashCardsContract.Deck.DEFAULT_PROJECTION
val rv = MatrixCursor(columns, 1)
val allDecks = col.sched.deckDueList()
val deckId = uri.pathSegments[1].toLong()
for (deck: DeckDueTreeNode? in allDecks) {
if (deck!!.did == deckId) {
addDeckToCursor(deckId, deck.fullDeckName, getDeckCountsFromDueTreeNode(deck), rv, col, columns)
return rv
val allDecks = col.sched.deckDueTree()
val desiredDeckId = uri.pathSegments[1].toLong()
fun find(nodeList: List<TreeNode<DeckDueTreeNode>>, id: Long): DeckDueTreeNode? {
for (node in nodeList) {
if (node.value.did == id) {
return node.value
}
return find(node.children, id)
}
return null
}
find(allDecks, desiredDeckId)?.let {
addDeckToCursor(it.did, it.fullDeckName, getDeckCountsFromDueTreeNode(it), rv, col, columns)
}
rv
}
Expand All @@ -413,10 +430,9 @@ class CardContentProvider : ContentProvider() {
}
}

private fun getDeckCountsFromDueTreeNode(deck: DeckDueTreeNode?): JSONArray {
@KotlinCleanup("use a scope function")
private fun getDeckCountsFromDueTreeNode(deck: DeckDueTreeNode): JSONArray {
val deckCounts = JSONArray()
deckCounts.put(deck!!.lrnCount)
deckCounts.put(deck.lrnCount)
deckCounts.put(deck.revCount)
deckCounts.put(deck.newCount)
return deckCounts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.ichi2.libanki.backend

import BackendProto.Backend
import BackendProto.Backend.ExtractAVTagsOut
import BackendProto.Backend.RenderCardOut
import android.content.Context
Expand All @@ -26,6 +27,8 @@ import com.ichi2.libanki.Decks
import com.ichi2.libanki.TemplateManager.TemplateRenderContext
import com.ichi2.libanki.backend.exception.BackendNotSupportedException
import com.ichi2.libanki.backend.model.SchedTimingToday
import com.ichi2.libanki.sched.DeckDueTreeNode
import com.ichi2.libanki.sched.TreeNode
import com.ichi2.utils.KotlinCleanup
import net.ankiweb.rsdroid.RustV1Cleanup

Expand Down Expand Up @@ -84,4 +87,9 @@ interface DroidBackend {

@Throws(BackendNotSupportedException::class)
fun renderCardForTemplateManager(templateRenderContext: TemplateRenderContext): RenderCardOut

fun deckDueTree(includeCounts: Boolean): Backend.DeckTreeNode

@KotlinCleanup("move to SchedV2 once it's converted to Kotlin")
fun legacyDeckDueTree(includeCounts: Boolean): List<TreeNode<DeckDueTreeNode>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.ichi2.libanki.backend

import BackendProto.Backend
import BackendProto.Backend.ExtractAVTagsOut
import BackendProto.Backend.RenderCardOut
import android.content.Context
Expand All @@ -25,6 +26,8 @@ import com.ichi2.libanki.TemplateManager.TemplateRenderContext
import com.ichi2.libanki.backend.exception.BackendNotSupportedException
import com.ichi2.libanki.backend.model.SchedTimingToday
import com.ichi2.libanki.backend.model.SchedTimingTodayProto
import com.ichi2.libanki.sched.DeckDueTreeNode
import com.ichi2.libanki.sched.TreeNode
import net.ankiweb.rsdroid.BackendFactory
import net.ankiweb.rsdroid.database.RustV11SQLiteOpenHelperFactory

Expand Down Expand Up @@ -94,6 +97,14 @@ open class RustDroidBackend(
throw BackendNotSupportedException()
}

override fun deckDueTree(includeCounts: Boolean): Backend.DeckTreeNode {
TODO("Not yet implemented")
}

override fun legacyDeckDueTree(includeCounts: Boolean): List<TreeNode<DeckDueTreeNode>> {
TODO("Not yet implemented")
}

companion object {
const val UNUSED_VALUE = 0
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import com.ichi2.libanki.DB
import com.ichi2.libanki.TemplateManager
import com.ichi2.libanki.backend.BackendUtils.to_json_bytes
import com.ichi2.libanki.backend.model.to_backend_note
import com.ichi2.libanki.sched.DeckDueTreeNode
import com.ichi2.libanki.sched.TreeNode
import com.ichi2.libanki.utils.TimeManager
import com.ichi2.utils.JSONObject
import net.ankiweb.rsdroid.BackendFactory
import net.ankiweb.rsdroid.BackendV1
Expand Down Expand Up @@ -71,4 +74,36 @@ class RustDroidV16Backend(private val backendFactory: BackendFactory) : RustDroi
backend.renderExistingCard(templateRenderContext._card.id, templateRenderContext._browser)
}
}

override fun deckDueTree(includeCounts: Boolean): Backend.DeckTreeNode {
val now = if (includeCounts) {
TimeManager.time.intTime()
} else {
0 // counts are skipped
}
return backend.deckTree(now, 0)
}

override fun legacyDeckDueTree(includeCounts: Boolean): List<TreeNode<DeckDueTreeNode>> {
fun toLegacyNode(node: Backend.DeckTreeNode, parentName: String): TreeNode<DeckDueTreeNode> {
val thisName = if (parentName.isEmpty()) {
node.name
} else {
"$parentName::${node.name}"
}
val treeNode = TreeNode(
DeckDueTreeNode(
thisName,
node.deckId,
node.reviewCount,
node.learnCount,
node.newCount,
)
)
treeNode.children.addAll(node.childrenList.asSequence().map { toLegacyNode(it, thisName) })
return treeNode
}
val top = deckDueTree(includeCounts)
return toLegacyNode(top, "").children
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import java.util.*
* [processChildren] should be called if the children of this node are modified.
*/
abstract class AbstractDeckTreeNode(
val col: Collection,
/**
* @return The full deck name, e.g. "A::B::C"
*/
Expand Down Expand Up @@ -68,7 +67,7 @@ abstract class AbstractDeckTreeNode(
)
}

abstract fun processChildren(children: List<AbstractDeckTreeNode>, addRev: Boolean)
abstract fun processChildren(col: Collection, children: List<AbstractDeckTreeNode>, addRev: Boolean)

override fun toString(): String {
val buf = StringBuffer()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,6 @@ abstract class AbstractSched {
*/
abstract fun extendLimits(newc: Int, rev: Int)

/**
* @return [deckname, did, rev, lrn, new]
*/
abstract fun deckDueList(): List<DeckDueTreeNode>

/**
* @param cancelListener A task that is potentially cancelled
* @return the due tree. null if task is cancelled
Expand All @@ -197,7 +192,7 @@ abstract class AbstractSched {
/**
* @return The tree of decks, without numbers
*/
abstract fun quickDeckDueTree(): List<TreeNode<DeckTreeNode>>
abstract fun<T : AbstractDeckTreeNode> quickDeckDueTree(): List<TreeNode<T>>

/** New count for a single deck.
* @param did The deck to consider (descendants and ancestors are ignored)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.ichi2.libanki.sched
import com.ichi2.libanki.Collection
import com.ichi2.libanki.Decks
import com.ichi2.utils.KotlinCleanup
import net.ankiweb.rsdroid.RustCleanup
import java.util.*
import kotlin.math.max
import kotlin.math.min
Expand All @@ -34,7 +35,8 @@ import kotlin.math.min
*/
@KotlinCleanup("maybe possible to remove gettres for revCount/lrnCount")
@KotlinCleanup("rename name -> fullDeckName")
class DeckDueTreeNode(col: Collection, name: String, did: Long, override var revCount: Int, override var lrnCount: Int, override var newCount: Int) : AbstractDeckTreeNode(col, name, did) {
@RustCleanup("after migration, consider dropping this and using backend tree structure directly")
class DeckDueTreeNode(name: String, did: Long, override var revCount: Int, override var lrnCount: Int, override var newCount: Int) : AbstractDeckTreeNode(name, did) {
override fun toString(): String {
return String.format(
Locale.US, "%s, %d, %d, %d, %d",
Expand All @@ -50,7 +52,7 @@ class DeckDueTreeNode(col: Collection, name: String, did: Long, override var rev
newCount = max(0, min(newCount, limit))
}

override fun processChildren(children: List<AbstractDeckTreeNode>, addRev: Boolean) {
override fun processChildren(col: Collection, children: List<AbstractDeckTreeNode>, addRev: Boolean) {
// tally up children counts
for (ch in children) {
lrnCount += ch.lrnCount
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
package com.ichi2.libanki.sched

import com.ichi2.libanki.Collection
import com.ichi2.utils.KotlinCleanup
import net.ankiweb.rsdroid.RustCleanup

@KotlinCleanup("confusing nullability for col, verify real nullability after code related to scheduling is fully migrated to kotlin")
class DeckTreeNode(col: Collection?, name: String, did: Long) : AbstractDeckTreeNode(col!!, name, did) {
override fun processChildren(children: List<AbstractDeckTreeNode>, addRev: Boolean) {
@RustCleanup("processChildren() can be removed after migrating to backend implementation")
class DeckTreeNode(name: String, did: Long) : AbstractDeckTreeNode(name, did) {
override fun processChildren(col: Collection, children: List<AbstractDeckTreeNode>, addRev: Boolean) {
// intentionally blank
}
}
4 changes: 2 additions & 2 deletions AnkiDroid/src/main/java/com/ichi2/libanki/sched/Sched.java
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ private void unburyCardsForDeck(@NonNull List<Long> allDecks) {
* Returns [deckname, did, rev, lrn, new]
*/
@Override
public @Nullable List<DeckDueTreeNode> deckDueList(@Nullable CancelListener cancelListener) {
protected @Nullable List<DeckDueTreeNode> deckDueList(@Nullable CancelListener cancelListener) {
_checkDay();
mCol.getDecks().checkIntegrity();
List<Deck> allDecksSorted = mCol.getDecks().allSorted();
Expand Down Expand Up @@ -242,7 +242,7 @@ private void unburyCardsForDeck(@NonNull List<Long> allDecks) {
// reviews
int rev = _revForDeck(deck.getLong("id"), rlim);
// save to list
deckNodes.add(new DeckDueTreeNode(mCol, deck.getString("name"), deck.getLong("id"), rev, lrn, _new));
deckNodes.add(new DeckDueTreeNode(deck.getString("name"), deck.getLong("id"), rev, lrn, _new));
// add deck as a parent
lims.put(Decks.normalizeName(deck.getString("name")), new Integer[]{nlim, rlim});
}
Expand Down
32 changes: 21 additions & 11 deletions AnkiDroid/src/main/java/com/ichi2/libanki/sched/SchedV2.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import android.text.style.StyleSpan;
import android.util.Pair;

import com.ichi2.anki.AnkiDroidApp;
import com.ichi2.anki.R;
import com.ichi2.async.CancelListener;
import com.ichi2.async.CollectionTask;
Expand Down Expand Up @@ -522,14 +523,14 @@ protected int _walkingCount(@NonNull LimitMethod limFn, @NonNull CountMethod cnt
*
* Return nulls when deck task is cancelled.
*/
public @NonNull List<DeckDueTreeNode> deckDueList() {
private @NonNull List<DeckDueTreeNode> deckDueList() {
return deckDueList(null);
}

// Overridden
/**
* Return sorted list of all decks.*/
public @Nullable List<DeckDueTreeNode> deckDueList(@Nullable CancelListener collectionTask) {
protected @Nullable List<DeckDueTreeNode> deckDueList(@Nullable CancelListener collectionTask) {
_checkDay();
mCol.getDecks().checkIntegrity();
List<Deck> allDecksSorted = mCol.getDecks().allSorted();
Expand Down Expand Up @@ -560,7 +561,7 @@ protected int _walkingCount(@NonNull LimitMethod limFn, @NonNull CountMethod cnt
int rlim = _deckRevLimitSingle(deck, plim, false);
int rev = _revForDeck(deck.getLong("id"), rlim, childMap);
// save to list
deckNodes.add(new DeckDueTreeNode(mCol, deck.getString("name"), deck.getLong("id"), rev, lrn, _new));
deckNodes.add(new DeckDueTreeNode(deck.getString("name"), deck.getLong("id"), rev, lrn, _new));
// add deck as a parent
lims.put(Decks.normalizeName(deck.getString("name")), new Integer[]{nlim, rlim});
}
Expand All @@ -573,13 +574,16 @@ protected int _walkingCount(@NonNull LimitMethod limFn, @NonNull CountMethod cnt
requires multiple database access by deck. Ignoring this number
lead to the creation of a tree more quickly.*/
@Override
public @NonNull List<TreeNode<DeckTreeNode>> quickDeckDueTree() {
// Similar to deckDueTree, ignoring the numbers
public @NonNull
List<? extends TreeNode<? extends AbstractDeckTreeNode>> quickDeckDueTree() {
if (AnkiDroidApp.TESTING_USE_V16_BACKEND) {
return mCol.getBackend().legacyDeckDueTree(false);
}

// Similar to deckDueList
ArrayList<DeckTreeNode> allDecksSorted = new ArrayList<>();
for (JSONObject deck : mCol.getDecks().allSorted()) {
DeckTreeNode g = new DeckTreeNode(mCol, deck.getString("name"), deck.getLong("id"));
DeckTreeNode g = new DeckTreeNode(deck.getString("name"), deck.getLong("id"));
allDecksSorted.add(g);
}
// End of the similar part.
Expand All @@ -593,12 +597,18 @@ protected int _walkingCount(@NonNull LimitMethod limFn, @NonNull CountMethod cnt
}

@Nullable
@RustCleanup("enable for v2 once backend is updated to 2.1.41+")
@RustCleanup("once both v1 and v2 are using backend, cancelListener can be removed")
public List<TreeNode<DeckDueTreeNode>> deckDueTree(@Nullable CancelListener cancelListener) {
List<DeckDueTreeNode> allDecksSorted = deckDueList(cancelListener);
if (allDecksSorted == null) {
return null;
if (AnkiDroidApp.TESTING_USE_V16_BACKEND) {
return mCol.getBackend().legacyDeckDueTree(true);
} else {
List<DeckDueTreeNode> allDecksSorted = deckDueList(cancelListener);
if (allDecksSorted == null) {
return null;
}
return _groupChildren(allDecksSorted, true);
}
return _groupChildren(allDecksSorted, true);
}

/**
Expand Down Expand Up @@ -665,7 +675,7 @@ public List<TreeNode<DeckDueTreeNode>> deckDueTree(@Nullable CancelListener canc
TreeNode<T> toAdd = new TreeNode<>(child);
toAdd.getChildren().addAll(childrenNode);
List<T> childValues = childrenNode.stream().map(TreeNode::getValue).collect(Collectors.toList());
child.processChildren(childValues, "std".equals(getName()));
child.processChildren(mCol, childValues, "std".equals(getName()));

sortedChildren.add(toAdd);
}
Expand Down
2 changes: 1 addition & 1 deletion AnkiDroid/src/main/java/com/ichi2/libanki/sync/Syncer.java
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ public JSONObject sanityCheck() {
mCol.getModels().save();
}
// check for missing parent decks
mCol.getSched().deckDueList();
mCol.getSched().quickDeckDueTree();
// return summary of deck
JSONArray check = new JSONArray();
JSONArray counts = new JSONArray();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ public void deckDueTreeInconsistentDecksPasses() {
addDeckWithExactName(child);

getCol().getDecks().checkIntegrity();
assertDoesNotThrow(() -> getCol().getSched().deckDueList());
assertDoesNotThrow(() -> getCol().getSched().deckDueTree());
}


Expand Down
Loading