1

In my Processing application, I listen for mouse clicks and then handle them in a button class. The button class then sets the right content via calling setScreenIndex. The function detects if this is the first time loading the screen, and then sets it up (not set up upon instantiation so unnecessary screens arent loaded if the user doesnt need them). The setting up is done via a thread that calls a setup function. This thread however seems to cut off when the mouse is clicked again whilst it is busy. This results in either the loading screen displaying indefinitely (the thread still registers as alive even though it no longer appears to do anything), or the loading screen freezing completely. I believe this is a threading issue, and somethings interacting weirdly but I am lost and have spent a week on this problem. Relevant code below:


final class Main
{
  public synchronized void update()
  {
    try
    {
      if (threadToWaitFor != null)
      {
        if (!threadToWaitFor.isAlive())
        {
          threadToWaitFor = null;
          setScreenIndex(nextScreenIndex);
        }
      }
      currentScreen.update();
    }
    catch (Exception e)
    {
      logger.println("ERROR: Main Update: " + e.getMessage());
      logger.println("Stack Trace: ");
      e.printStackTrace(logger);
      logger.flush();
      exit();
    }
  }
  public synchronized void setScreenIndex(int screenIndex)
  {
    this.screenIndex = screenIndex;
    currentScreen = screens.get(this.screenIndex);
    if (!currentScreen.getSetup())
    {
      SetupScreenThread thread = new SetupScreenThread(currentScreen);
      thread.start();
      waitForThread(thread, screenIndex);
    }
  }
  public synchronized void waitForThread(Thread thread, int newScreenIndex)
  {
    this.threadToWaitFor = thread;
    this.nextScreenIndex = newScreenIndex;
    this.currentScreen = loadScreen;
  }
}

I have tried checking the button isnt being called twice and that the same functions arent being called at the same time using the synchronised and volatile keywords, but this doesnt help. If you do not click multiple times, everything is fine and behaves/loads correctly. I have tried implementing a lock in the main class so that the click doesnt even reach the Main class if the flag is set to busy but this also doesnt work.

  • Note that this isn't so much about Processing as being just plain Java. You might be using the processing library somewhere else in your code, but nothing you're doing here is regular Processing, you've hacked around it completely and turned it into a plain threading Java application =) – Mike 'Pomax' Kamermans May 24 '23 at 14:58

1 Answers1

1

I think you are taking a wrong approach to solve your problem.

You are trying to achieve a lazy loading of your views, which is a good idea. But trying to implement this yourself is not a good idea.

You should use the already existing classes that allow async computation. The Future API is your ally.

    public Map<String, Future<View>> views = initViews();

    public Future<View> currentView;

    public static Map<String, Future<View>> initViews() {
        Map<String, Future<View>> screens = new HashMap<>();
        screens.put("login", null);
        screens.put("home", null);

        return screens;
    }

    public synchronized void update() throws InterruptedException {
        ...
        if (currentView.isDone()) {
            try {
                View view = currentView.get();
                // do something with your view
            } catch (ExecutionException e) {
                // Exception was raised and not caught in your async method call
                throw new RuntimeException(e);
            }
        }
        ...
    }

    public synchronized void setScreenIndex(String viewName) {
        if (views.get(viewName) == null) {
            CompletableFuture<View> future = new CompletableFuture<>(() -> {
                switch (viewName) {
                    case "login":
                        return new LoginView();
                    case "home":
                        return new HomeView();
                    default:
                        throw new RuntimeException("Unknown view: " + viewName);
                }
            });
            views.put(viewName, future);
            future.whenComplete((view, throwable) -> {
                        if (throwable != null) {
                            throw new RuntimeException(throwable);
                        }
                        // Update current view
                        currentView = views.get(viewName);
                    }
            );
        } else
            currentView = views.get(viewName);
    }

This code is not meant to be perfect. Just to give you an idea on how to use the Future API to your use case.

Erwan Daniel
  • 1,319
  • 11
  • 26
  • This is very helpful! I managed to fix it eventually with a resource lock (turns out the threads were deadlocking although i couldn’t tell you over what!), but this does look much cleaner and indeed possibly more efficient. I appreciate the help! – EdwardT2021 May 26 '23 at 18:52