Folleach / GeometryDashAPI

API for Geometry Dash
MIT License
62 stars 8 forks source link

GameManager.LoadFile(string?) or LocalLevels.LoadFile(string?) hangs for seemingly no reason when invoked at the WinForms UI Thread #34

Closed 5d-code closed 8 months ago

5d-code commented 8 months ago

Describe the bug When the LoadFile(string?) of GeometryDashAPI.Data.GameManager or GeometryDashAPI.Data.LocalLevels is invoked, the WinForms application hangs but does not cause Windows to put the application in a "Not Responding" state.

To Reproduce Steps to reproduce the behavior:

  1. Create a WinForms project.
  2. Import the GeometryDashAPI dependency to the WinForms project in question
  3. Add a Button control to the Form
  4. Add a Click Event Handler for the Button
  5. In the Click Event Handler, add some code that sets a local or instance variable of type GameManager with the name of your choice to the result of the GeometryDashAPI.Data.GameManager.LoadFile(string?) method.
  6. After that, use this GameManager instance to display a MessageBox of the player's name, which can be accessed using GameManager.PlayerName. The MessageBox can be shown with System.Windows.Forms.MessageBox.Show(string).

The same should apply to GeometryDashAPI.Data.LocalLevels.LoadFile(string?) but with a different example.

Expected behavior The player's name should be shown in a Message Box.

Additional context The GeometryDashAPI version is 0.2.24 which was installed from the official NuGet package

Folleach commented 8 months ago

This is by design for LoadFile methods.
It blocks the calling thread using GetAwaiter().GetResult(), this is just an additional method to write non-asynchronous code.

In most cases, I recommend use the LoadFileAsync method, it should reset the load to the Thread Pool when it starts reading the file.
But I tried to write an example and it still blocks the calling thread, well... I'll look for an error, thank you!

My example using AvaloniaUI:

using System;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Threading;
using GeometryDashAPI.Data;

namespace LoadTest;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        MakeDatFile();
        var counter = 0;
        var fps = 144d;
        DispatcherTimer.Run(() =>
        {
            Title = $"counter: {++counter}";
            return true;
        }, TimeSpan.FromMilliseconds(1000 / fps));
    }

    private async void InputElement_OnClick(object? sender, RoutedEventArgs e)
    {
        var x = await GameManager.LoadFileAsync("large.dat");
        (sender as Button).Content = $"button {x.DataPlist["some10"]}";
    }

    private void MakeDatFile()
    {
        var large = GameManager.CreateNew();
        for (var i = 0; i < 2_000_000; i++)
            large.DataPlist[$"some{i}"] = i;
        large.Save("large.dat");
    }
}

Workaround

Force run load in the Thread Pool

private void InputElement_OnClick(object? sender, RoutedEventArgs e)
{
    Task.Run(async () =>
    {
        var x = await GameManager.LoadFileAsync("large.dat");
        Dispatcher.UIThread.InvokeAsync(() =>
        {
            (sender as Button).Content = $"button {x.DataPlist["some10"]}";
        });
    });
}

This works well and doesn't stop the counter during LoadFile