halfhp / androidplot

Charts and plots for Android
http://androidplot.com
Apache License 2.0
505 stars 159 forks source link

Plot not displayed with background thread if initial size is 0 #120

Open harbulot opened 9 months ago

harbulot commented 9 months ago

This is using Androidplot 1.5.10.

Due to various degrees of nesting (e.g. XYPlot within a LinearLayout, within a fragment that replaces a FrameLayout of the fragment of a ViewPager2's page, ...), I've encountered a situation where the one of the dimensions of the XYPlot instance is 0, before being expended to its actual size later on. (Apologies, I'm unable to re-create a small fully contained example.)

In this case, renderOnCanvas is called with a null Canvas, but when we're using a background thread, isIdle is never set to true in this case (as it would with non-zero dimensions). As a result, the background thread is stuck waiting at renderSync.wait(), and the plot can never be rendered by resizing.

Details

In a situation where rendering is done in background-thread mode, this shows these logs:

plot.addOnLayoutChangeListener(
    (view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
        XYPlot plot = (XYPlot) view;
        Log.d(TAG, String.format(
                "Plot layout change: %s/%s/%s/%s -> %s/%s/%s/%s",
                 oldLeft, oldTop, oldRight, oldBottom,
                 left, top, right, bottom));
    });
D  Plot layout change: 0/0/0/0 -> 0/0/1080/0
I  Thread started with id 143665258
D  Plot layout change: 0/0/1080/0 -> 0/53/1080/824

The problem here is that the background thread, and the first pass at rendering starts with a dimension of 0 height.

In this case, the Canvas in the pingPong instance is null, in Plot.startBackgroundrendering().

Indeed, the BufferedCanvas is resized using (0,0), which makes its bgBuffer null, and therefore makes getCanvas() return null.

As a result, renderOnCanvas returns straight away:

    protected synchronized void renderOnCanvas(@Nullable Canvas canvas) {
        if(canvas == null) {
            return;
        }

The problem here, is that isIdle is never set to true (as it would with a non-null canvas).

As a result, redraw() never does anything, since isIdle is always false:

    public void redraw() {

        if (renderMode == RenderMode.USE_BACKGROUND_THREAD) {

            // only enter synchronized block if the call is expected to block OR
            // if the render thread is idle, so we know that we won't have to wait to
            // obtain a lock.
            if (renderThread != null && isIdle) {
                synchronized (renderSync) {
                    renderSync.notify();
                }
            }

Because of that, renderSync.notify() can never be called, so the background thread is stuck at renderSync.wait(), so no further redrawing can take place.

Solution

Setting isIdle = true in Plot.renderOnCanvas seems to fix the problem:

    protected synchronized void renderOnCanvas(@Nullable Canvas canvas) {
        if(canvas == null) {
            isIdle = true;
            return;
        }
halfhp commented 9 months ago

Thanks for the report @harbulot!

I know you stated in the first sentence that you were unable to make a code demo of the issue, but just being perfectly honest without a demo, it could be a while before I am able to dedicate the time to investigate and fix. Alternatively you're more than welcome to open a PR if you've got a clean solution that will not negatively impact other user's use cases!