Code Monkey home page Code Monkey logo

Comments (11)

falkoschindler avatar falkoschindler commented on June 12, 2024 1

Thanks a lot for reporting this issue, @backbord!

I just bisected it and identified commit a2544c1 (PR #2867), which was a collaboration with @afullerx and part of release 1.4.23. Somehow the way we process the outbox queue has an effect on the Leaflet element.

I'll try to investigate further. But if anyone else has an idea, I'm all ears.

from nicegui.

afullerx avatar afullerx commented on June 12, 2024 1

I was able to consistently reproduce the bug using 1.4.21. (Windows 10, Python 3.12). While the changes made in #2867 may be causing it to be triggered more consistently for more users, it seems as though it is not the root cause.

from nicegui.

falkoschindler avatar falkoschindler commented on June 12, 2024 1

Got it! Here is what happens:

  1. The "flyTo" transition takes about 1 second to move to Berlin. It ends with a "map-zoomend" and a "map-moveend" event being sent to the server.
  2. The "map-zoomend" is handled first. It causes an update which is sent to the client. But because no "map-moveend" event has been processed yet, the update still contains the old center, which causes the client to move back to Munich.
  3. Now the "map-moveend" event is handled. The updated center is sent to the client, which cause it to move to Berlin.

from nicegui.

backbord avatar backbord commented on June 12, 2024 1

Thank you for looking into it and the fix!

from nicegui.

falkoschindler avatar falkoschindler commented on June 12, 2024

Ah, actually that kind of makes sense:

When introducing asyncio events in PR #2867, we implicitly removed a short delay of 0.01s between loop cycles. Now the outbox reacts immediately when new messages are added to the queue, which could have changed the behavior of ui.leaflet. When adding an asyncio.sleep(0.01) after the event has been awaited

await asyncio.wait_for(self._enqueue_event.wait(), timeout=1.0)

the flicker disappears.

Why does the problem already exist on Windows before PR #2867? As we noticed in #2482, Windows tends to ignore delays shorter than 0.016s, so the implicit delay of 0.01s is ineffective.

Now I want to find out the exact order of messages that leads to the endless loop. Maybe it's a Leaflet-specific problem. Otherwise we need to rethink the outbox.

from nicegui.

afullerx avatar afullerx commented on June 12, 2024

You nailed it there @falkoschindler. Here is the sequence of messages/updates after the button is clicked:

Message: ('78ed9408-c2ec-4c5c-95e8-79f3d503e2e7', 'run_javascript', {'code': 'return runMethod(5, "run_map_method", ["flyTo",[52.5,13.4],9,{"duration":1.0}])'})

After the "flyTo" operation is completed, the following updates are issued (only the _props attribute of the queued Leaflet object was recorded):

{'resource_path': '/_nicegui/1.4.20/resources/763203f93f18a3f1f5d14f74197580e4', 'center': (48.1, 11.6), 'zoom': 9, 'options': {}, 'draw_control': False}
{'resource_path': '/_nicegui/1.4.20/resources/763203f93f18a3f1f5d14f74197580e4', 'center': [52.5, 13.4], 'zoom': 9, 'options': {}, 'draw_control': False}
{'resource_path': '/_nicegui/1.4.20/resources/763203f93f18a3f1f5d14f74197580e4', 'center': [48.1, 11.6], 'zoom': 9, 'options': {}, 'draw_control': False}
{'resource_path': '/_nicegui/1.4.20/resources/763203f93f18a3f1f5d14f74197580e4', 'center': [52.5, 13.4], 'zoom': 9, 'options': {}, 'draw_control': False}
[...repeated endlessly]

An incorrect update is issued with the original Munich coordinates, quickly followed by the one with the correct Berlin coordinates. When a delay is present, the first update is overwritten before it is emitted, and everything functions properly. Without the delay, both updates are issued in quick succession, causing the leaflet to enter an endless loop navigating between them.

from nicegui.

falkoschindler avatar falkoschindler commented on June 12, 2024

Thanks, @afullerx!

By the way, I thought I might be able to reproduce the problem with plain HTML/JavaScript, but this code works perfectly fine:

<html>
  <head>
    <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
  </head>
  <body>
    <div id="map" style="height: 400px"></div>
    <button id="flyToBerlin">Berlin</button>
    <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
    <script>
      const map = L.map("map").setView([48.1, 11.6], 10);
      L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png").addTo(map);
      document.getElementById("flyToBerlin").onclick = () => map.flyTo([52.5, 13.4], 9, { duration: 1.0 });
      map.on("moveend", console.log);
    </script>
  </body>
</html>

from nicegui.

falkoschindler avatar falkoschindler commented on June 12, 2024

I think outbox.py is ok and ui.leaflet is doing something fishy. Let's add

print(f'{time.time():.6f}', 'outbox enqueue_update', getattr(element, 'center', None))

and

print(f'{time.time():.6f}', 'outbox enqueue_message', message_type, data)

to outbox.py and run this slightly modified example:

count = {'value': 0}

def tick():
    count['value'] += 1
    if count['value'] == 10:
        sys.exit(0)  # interrupt endless loop

m = ui.leaflet(center=(48.1, 11.6), zoom=10)
m.on('map-moveend', lambda e: (print(f'{time.time():.6f}', 'on map-moveend:', e.args['center']), tick()))
ui.button('Berlin', on_click=lambda: (print('click'), m.run_map_method('flyTo', [52.5, 13.4], 9, {'duration': 1.0})))

Output:

click
1715678352.542828 outbox enqueue_message run_javascript {'code': 'return runMethod(4, "run_map_method", ["flyTo",[52.5,13.4],9,{"duration":1.0}])'}
1715678353.564281 outbox enqueue_update [48.1, 11.6]
1715678353.564758 outbox enqueue_update [52.5, 13.4]
1715678353.565157 on map-moveend: [52.5, 13.4]
1715678353.569320 outbox enqueue_update [48.1, 11.6]
1715678353.572813 on map-moveend: [48.1, 11.6]

And the oscillation begins.
But why is there an update with the old center [48.1, 11.6] one second after "flyTo" has been started? Is this the root cause?

from nicegui.

falkoschindler avatar falkoschindler commented on June 12, 2024

I just noticed that the "Wait for Initialization" demo is flickering as well. It's probably the same problem.

from nicegui.

backbord avatar backbord commented on June 12, 2024

Hi again!

While the patch fixes the behavior in most cases, sometimes the map still begins to flicker.
I recon this was expected as with a unfortunate ordering of events (#3035 (comment)), this is a question of timing and batching those events.

For my use case, I've come up with this workaround, which is far from perfect.
In most cases (on my machine), it manages to stop the flickering.

def avoid_flickering(leaflet: ui.leaflet) -> None:
    # This is a workaround for https://github.com/zauberzeug/nicegui/issues/3035
    # as it hasn't been solved completely.

    last_centers: deque[tuple[float, float]] = deque(maxlen=2)
    last_move_at = 0.0

    def _avoid_flickering(event: events.GenericEventArguments) -> None:
        nonlocal last_move_at

        lat, lon = event.args["center"]
        center = (lat, lon)
        now = time.monotonic()
        time_since_last_move = now - last_move_at
        # print(f"{time_since_last_move:.4f} {last_centers} {center}")
        if (
            time_since_last_move < 0.05
            and len(last_centers) > 1
            and last_centers[1] != center == last_centers[0]
        ):
            # try to stop flickering
            leaflet.run_map_method("off", "moveend")
            leaflet.set_center(last_centers[1])
            leaflet.run_map_method("on", "moveend")
            print(
                f"Flickering detected. "
                f"Attempting to stop by setting center to {last_centers[-1]}."
            )

        last_centers.append(center)
        last_move_at = now

    leaflet.on("map-moveend", _avoid_flickering)

Please feel free to suggest improvements. :)

Hope this helps!

Thanks

from nicegui.

afullerx avatar afullerx commented on June 12, 2024

@backbord, you could also try increasing the sleep durations in_handle_moveend() and _handle_zoomend():

async def _handle_moveend(self, e: GenericEventArguments) -> None:
await asyncio.sleep(0.02) # NOTE: wait for zoom to be updated as well
self.center = e.args['center']
async def _handle_zoomend(self, e: GenericEventArguments) -> None:
await asyncio.sleep(0.02) # NOTE: wait for center to be updated as well
self.zoom = e.args['zoom']

It would be useful to know if there's a duration that works reliably for you. With the current sleep of only 20ms, there's a lot of room to play with.

from nicegui.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.