mirror of https://codeberg.org/gitnex/GitNex.git
Improve markdown rendering performance (#890)
Use object pooling with up to 45 threads for improved parallelization in markdown rendering. Reviewed-on: https://codeberg.org/gitnex/GitNex/pulls/890 Reviewed-by: M M Arif <mmarif@noreply.codeberg.org> Co-Authored-By: opyale <opyale@noreply.codeberg.org> Co-Committed-By: opyale <opyale@noreply.codeberg.org>
This commit is contained in:
parent
bd3e6ff20a
commit
8104889bf6
|
@ -88,6 +88,7 @@ Thanks to all the open source libraries, contributors and donators.
|
|||
- [ge0rg/MemorizingTrustManager](https://github.com/ge0rg/MemorizingTrustManager)
|
||||
- [mikaelhg/urlbuilder](https://github.com/mikaelhg/urlbuilder)
|
||||
- [ACRA/acra](https://github.com/ACRA/acra)
|
||||
- [chrisvest/stormpot](https://github.com/chrisvest/stormpot)
|
||||
|
||||
#### Icon sets
|
||||
- [feathericons/feather](https://github.com/feathericons/feather)
|
||||
|
|
|
@ -112,5 +112,6 @@ dependencies {
|
|||
implementation "org.codeberg.gitnex:tea4j:1.0.5"
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5"
|
||||
implementation 'androidx.biometric:biometric:1.1.0'
|
||||
implementation 'com.github.chrisvest:stormpot:2.4.1'
|
||||
|
||||
}
|
||||
|
|
|
@ -305,7 +305,7 @@ public class FileViewActivity extends BaseActivity implements BottomSheetFileVie
|
|||
|
||||
if(!tinyDB.getBoolean("enableMarkdownInFileView")) {
|
||||
|
||||
new Markdown(ctx, EmojiParser.parseToUnicode(binding.contents.getContent()), binding.markdown);
|
||||
Markdown.render(ctx, EmojiParser.parseToUnicode(binding.contents.getContent()), binding.markdown);
|
||||
|
||||
binding.contents.setVisibility(View.GONE);
|
||||
binding.markdownFrame.setVisibility(View.VISIBLE);
|
||||
|
|
|
@ -582,7 +582,7 @@ public class IssueDetailActivity extends BaseActivity implements LabelsListAdapt
|
|||
viewBinding.issueTitle.setText(HtmlCompat.fromHtml(issueNumber_ + " " + EmojiParser.parseToUnicode(singleIssue.getTitle()), HtmlCompat.FROM_HTML_MODE_LEGACY));
|
||||
String cleanIssueDescription = singleIssue.getBody().trim();
|
||||
|
||||
new Markdown(ctx, EmojiParser.parseToUnicode(cleanIssueDescription), viewBinding.issueDescription);
|
||||
Markdown.render(ctx, EmojiParser.parseToUnicode(cleanIssueDescription), viewBinding.issueDescription);
|
||||
|
||||
RelativeLayout.LayoutParams paramsDesc = (RelativeLayout.LayoutParams) viewBinding.issueDescription.getLayoutParams();
|
||||
|
||||
|
|
|
@ -127,7 +127,7 @@ public class DraftsAdapter extends RecyclerView.Adapter<DraftsAdapter.DraftsView
|
|||
holder.repoInfo.setText(headTitle);
|
||||
holder.draftWithRepository = currentItem;
|
||||
|
||||
new Markdown(mCtx, currentItem.getDraftText(), holder.draftText);
|
||||
Markdown.render(mCtx, currentItem.getDraftText(), holder.draftText);
|
||||
|
||||
if(!currentItem.getCommentId().equalsIgnoreCase("new")) {
|
||||
holder.editCommentStatus.setVisibility(View.VISIBLE);
|
||||
|
|
|
@ -332,7 +332,7 @@ public class IssueCommentsAdapter extends RecyclerView.Adapter<IssueCommentsAdap
|
|||
.centerCrop()
|
||||
.into(holder.avatar);
|
||||
|
||||
new Markdown(ctx, EmojiParser.parseToUnicode(issueComment.getBody()), holder.comment);
|
||||
Markdown.render(ctx, EmojiParser.parseToUnicode(issueComment.getBody()), holder.comment);
|
||||
|
||||
StringBuilder informationBuilder = null;
|
||||
if(issueComment.getCreated_at() != null) {
|
||||
|
@ -349,9 +349,7 @@ public class IssueCommentsAdapter extends RecyclerView.Adapter<IssueCommentsAdap
|
|||
}
|
||||
|
||||
if(!issueComment.getCreated_at().equals(issueComment.getUpdated_at())) {
|
||||
|
||||
if(informationBuilder != null) {
|
||||
|
||||
informationBuilder.append(ctx.getString(R.string.colorfulBulletSpan)).append(ctx.getString(R.string.modifiedText));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -165,11 +165,11 @@ public class MilestonesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
|||
milestoneId.setText(String.valueOf(dataModel.getId()));
|
||||
milestoneStatus.setText(dataModel.getState());
|
||||
|
||||
new Markdown(context, dataModel.getTitle(), msTitle);
|
||||
Markdown.render(context, dataModel.getTitle(), msTitle);
|
||||
|
||||
if(!dataModel.getDescription().equals("")) {
|
||||
|
||||
new Markdown(context, EmojiParser.parseToUnicode(dataModel.getDescription()), msDescription);
|
||||
Markdown.render(context, EmojiParser.parseToUnicode(dataModel.getDescription()), msDescription);
|
||||
}
|
||||
else {
|
||||
|
||||
|
|
|
@ -129,7 +129,7 @@ public class ReleasesAdapter extends RecyclerView.Adapter<ReleasesAdapter.Releas
|
|||
}
|
||||
|
||||
if(!currentItem.getBody().equals("")) {
|
||||
new Markdown(mCtx, currentItem.getBody(), holder.releaseBodyContent);
|
||||
Markdown.render(mCtx, currentItem.getBody(), holder.releaseBodyContent);
|
||||
}
|
||||
else {
|
||||
holder.releaseBodyContent.setText(R.string.noReleaseBodyContent);
|
||||
|
|
|
@ -324,7 +324,7 @@ public class RepoInfoFragment extends Fragment {
|
|||
switch(response.code()) {
|
||||
|
||||
case 200:
|
||||
new Markdown(ctx, response.body(), binding.repoFileContents);
|
||||
Markdown.render(ctx, response.body(), binding.repoFileContents);
|
||||
break;
|
||||
|
||||
case 401:
|
||||
|
|
|
@ -9,8 +9,11 @@ import androidx.core.content.res.ResourcesCompat;
|
|||
import org.mian.gitnex.R;
|
||||
import org.mian.gitnex.clients.PicassoService;
|
||||
import org.mian.gitnex.core.MainGrammarLocator;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import io.noties.markwon.AbstractMarkwonPlugin;
|
||||
import io.noties.markwon.Markwon;
|
||||
import io.noties.markwon.core.CorePlugin;
|
||||
|
@ -26,6 +29,13 @@ import io.noties.markwon.syntax.Prism4jThemeDarkula;
|
|||
import io.noties.markwon.syntax.Prism4jThemeDefault;
|
||||
import io.noties.markwon.syntax.SyntaxHighlightPlugin;
|
||||
import io.noties.prism4j.Prism4j;
|
||||
import stormpot.Allocator;
|
||||
import stormpot.BlazePool;
|
||||
import stormpot.Config;
|
||||
import stormpot.Pool;
|
||||
import stormpot.Poolable;
|
||||
import stormpot.Slot;
|
||||
import stormpot.Timeout;
|
||||
|
||||
/**
|
||||
* @author opyale
|
||||
|
@ -33,26 +43,66 @@ import io.noties.prism4j.Prism4j;
|
|||
|
||||
public class Markdown {
|
||||
|
||||
private static final ExecutorService executorService = Executors.newCachedThreadPool();
|
||||
private static final int MAX_POOL_SIZE = 45;
|
||||
private static final int MAX_THREAD_KEEP_ALIVE_SECONDS = 120;
|
||||
private static final int MAX_CLAIM_TIMEOUT_SECONDS = 5;
|
||||
|
||||
private final Context context;
|
||||
private final String markdown;
|
||||
private final TextView textView;
|
||||
private static final Timeout timeout = new Timeout(MAX_CLAIM_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
|
||||
public Markdown(@NonNull Context context, @NonNull String markdown, @NonNull TextView textView) {
|
||||
private static final ExecutorService executorService =
|
||||
new ThreadPoolExecutor(MAX_POOL_SIZE / 2, MAX_POOL_SIZE, MAX_THREAD_KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, new SynchronousQueue<>());
|
||||
|
||||
this.context = context;
|
||||
this.markdown = markdown;
|
||||
this.textView = textView;
|
||||
private static final Pool<Renderer> rendererPool;
|
||||
|
||||
executorService.execute(new Renderer());
|
||||
static {
|
||||
|
||||
Config<Renderer> config = new Config<>();
|
||||
|
||||
config.setBackgroundExpirationEnabled(true);
|
||||
config.setPreciseLeakDetectionEnabled(true);
|
||||
config.setSize(MAX_POOL_SIZE);
|
||||
config.setAllocator(new Allocator<Renderer>() {
|
||||
|
||||
@Override
|
||||
public Renderer allocate(Slot slot) throws Exception {
|
||||
return new Renderer(slot);
|
||||
}
|
||||
|
||||
@Override public void deallocate(Renderer poolable) throws Exception {}
|
||||
|
||||
});
|
||||
|
||||
rendererPool = new BlazePool<>(config);
|
||||
|
||||
}
|
||||
|
||||
private class Renderer implements Runnable {
|
||||
public static void render(Context context, String markdown, TextView textView) {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Renderer renderer = rendererPool.claim(timeout);
|
||||
|
||||
if(renderer != null) {
|
||||
renderer.setParameters(context, markdown, textView);
|
||||
executorService.execute(renderer);
|
||||
}
|
||||
} catch(InterruptedException ignored) {}
|
||||
}
|
||||
|
||||
private static class Renderer implements Runnable, Poolable {
|
||||
|
||||
private final Slot slot;
|
||||
|
||||
private Markwon markwon;
|
||||
|
||||
private Context context;
|
||||
private String markdown;
|
||||
private TextView textView;
|
||||
|
||||
public Renderer(Slot slot) {
|
||||
this.slot = slot;
|
||||
}
|
||||
|
||||
private void setup() {
|
||||
|
||||
Prism4jTheme prism4jTheme = TinyDB.getInstance(context).getString("currentTheme").equals("dark") ?
|
||||
Prism4jThemeDarkula.create() :
|
||||
|
@ -72,16 +122,56 @@ public class Markdown {
|
|||
@Override
|
||||
public void configureTheme(@NonNull MarkwonTheme.Builder builder) {
|
||||
builder.codeBlockTypeface(Typeface.createFromAsset(context.getAssets(), "fonts/sourcecodeproregular.ttf"));
|
||||
builder.codeBlockMargin((int) (context.getResources().getDisplayMetrics().density * 10));
|
||||
builder.blockMargin((int) (context.getResources().getDisplayMetrics().density * 10));
|
||||
builder.codeTextSize((int) (context.getResources().getDisplayMetrics().scaledDensity * 13));
|
||||
builder.codeTypeface(Typeface.createFromAsset(context.getAssets(), "fonts/sourcecodeproregular.ttf"));
|
||||
builder.linkColor(ResourcesCompat.getColor(context.getResources(), R.color.lightBlue, null));
|
||||
}
|
||||
});
|
||||
|
||||
Markwon markwon = builder.build();
|
||||
Spanned spanned = markwon.toMarkdown(markdown);
|
||||
markwon = builder.build();
|
||||
|
||||
textView.post(() -> markwon.setParsedMarkdown(textView, spanned));
|
||||
}
|
||||
|
||||
public void setParameters(Context context, String markdown, TextView textView) {
|
||||
|
||||
this.context = context;
|
||||
this.markdown = markdown;
|
||||
this.textView = textView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
Objects.requireNonNull(context);
|
||||
Objects.requireNonNull(markdown);
|
||||
Objects.requireNonNull(textView);
|
||||
|
||||
if(markwon == null) setup();
|
||||
|
||||
Spanned processedMarkdown = markwon.toMarkdown(markdown);
|
||||
|
||||
TextView localReference = textView;
|
||||
localReference.post(() -> localReference.setText(processedMarkdown));
|
||||
|
||||
release();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
|
||||
context = null;
|
||||
markdown = null;
|
||||
textView = null;
|
||||
|
||||
slot.release(this);
|
||||
|
||||
}
|
||||
|
||||
public void expire() {
|
||||
slot.expire(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package org.mian.gitnex.helpers;
|
||||
package org.mian.gitnex.helpers.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
|
@ -16,6 +16,8 @@ import androidx.annotation.ColorInt;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import org.mian.gitnex.core.MainGrammarLocator;
|
||||
import org.mian.gitnex.helpers.AppUtil;
|
||||
import org.mian.gitnex.helpers.TinyDB;
|
||||
import io.noties.markwon.syntax.Prism4jSyntaxHighlight;
|
||||
import io.noties.markwon.syntax.Prism4jTheme;
|
||||
import io.noties.markwon.syntax.Prism4jThemeDarkula;
|
|
@ -99,7 +99,7 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<org.mian.gitnex.helpers.SyntaxHighlightedArea
|
||||
<org.mian.gitnex.helpers.views.SyntaxHighlightedArea
|
||||
android:id="@+id/contents"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
Loading…
Reference in New Issue