Mastodon

JavaFX Series - Threading Issues

For the next iteration of my tiny game, I wanted to add some action. I decided to let resources grow on themselve: Every three seconds, a resource spawner should create one more resource. Because of the architecture of my application, I ran into some threading problems that shall be the topic for todays post.

As I wrote in a recent article of this series, the business logic of my application is separated from the Java FX user interface. This means that the reaction of a user input is determined by the classes in the business layer. Also, repeating events should be managed by the business layer and only graphically represented by JavaFX. Regarding the mentioned resource spawners, this means that the logic for the creation of new resources is stored in the business class ResourceSpawner:

public ResourceSpawner(CartesianCoordinate position, double radius) {
    super();
    this.position = position;
    this.radius = radius;
    resources = new HashSet<Resource>();
    
    // TODO Make this editable from the UI
    new Timer().schedule(new TimerTask() {
        @Override
        public void run() {
            spawn();
        }
    }, 0, 3000);
}

As you can see, every 3 seconds a new resource is created. This resource gets registered in the world (not shown here) and finally displayed by JavaFX. The later happens in the class JavaFxApplication:

@Override
public void notifyCreation(final WorldObject newWorldObject) {

	// All new objects are created on the JavaFX Application Thread, even if
	// the call to this method came from another thread, such as a timing
	// thread started by the business logic.
	Platform.runLater(new Runnable() {
		@Override
		public void run() {
			if (newWorldObject instanceof ResourceSpawner) {
				ResourceSpawnerFX newSpawnerFX = new ResourceSpawnerFX(
						(ResourceSpawner) newWorldObject);
				rootGroup.getChildren().add(newSpawnerFX);
			} else if (newWorldObject instanceof Resource) {
				rootGroup.getChildren().add(
						new ResourceFX((Resource) newWorldObject));
			}
		}
	});
}

Because the call to add a new object came from a thread started by a business class, it can not get directly processed. This would cause the following exception:

Exception in thread "Timer-1" java.lang.IllegalStateException: Not on FX application thread; currentThread = Timer-1

This is the reason why the whole body of the method is wrapped in Platform.runLater(). Quoting from the API doc, this method runs the specified Runnable on the JavaFX Application Thread at some unspecified time in the future. This method, which may be called from any thread, will post the Runnable to an event queue and then return immediately to the caller. The Runnables are executed in the order they are posted.

This method allows for a complete separation of the UI and the business logic. Before reading about this method here, I thought that every call to a JavaFX component has to come directly from the FX application thread. That would have broken my architecture and my separations of concerns.

Get the Code

I pushed the code in my repository on github. The codebase for this article is tagged as Codebase_ThreadingIssues.

Next Steps

My original plan for this post was to add much more content into the game, but this has to wait for next week. My idea was to introduce bases or cities into the world. They can harvest resources and grow in size. Every base can operate in one of the following modi:

  1. green resource harvesting: nearby resources will be harvested, but not totally depleted. This leads to a slow but steady stream of resources for the city.
  2. greedy resource harvesting: nearby resources will be harvested until nothing is there anymore. This leads to a quick resource boost in the short run and no resources at all in the long run.
  3. Trading: Connecting to other bases, the city will gain resources depending on the size of the other trading partner. The generated resources will be generated into the world and hence not lower the amount of the trading partners. Because of the dependence between gained resources and the size of the partnering base, small bases will have a huge bonus by partnering with big cities. Also, this modi can be used even when there are no resources left on the map.
  4. Conquer: This is a far-fetched idea for the future of the game. When there are more groups or countries, the user can order a base to attack another base. This will use a lot of resources and will either work or not, depending on the size and the resources of the attacked city. Also, this modus raises the need for another possibility to conquer enemy cities in a peaceful way.

What do you think, are these cool features?