Posted 27 April 2014 - 12:24 PM

Phobos - A JavaFX Games Engine

Part 1: An Introduction to Software Design Patterns and Threading

Engine

synchronized

public synchronized void doSomethingCool() { //I'm a synchronized method } //or public void doSomethingElseThatsCool() { //...some code... synchronized(this) { //I'm a thread-safe code block!! } }

synchronized

public class Singleton { private static volatile Singleton _instance = null; private Singleton() { } public static Singleton getInstance() { if (_instance == null) { synchronized (Singleton.class) { if (_instance == null) { _instance = new Singleton(); } } } return _instance; } }

Singleton

static

Singleton.getInstance()

private int aScore = 0; public int getScore() { return aScore; } public void setScore(int val) { aScore = val; }

int

public class SingletonExample { public static void main(String[] args) { Singleton.getInstance().setScore(10); } }

public class OutputThread extends Thread { public OutputThread() { } public void run() { int loop = 0; while(loop < 100) { int score = Singlton.getInstance().getScore(); System.out.println("Score is: " + score); loop++; } } } public class AccessThread extends Thread { public AccessThread() { } public void run() { int loop = 0; while(loop < 100) { Singleton.getInstance().setScore(new Random().nextInt()); loop++; } } }

int

public static void main(String[] args) { Singleton.getInstance().setScore(10); Thread setter = new AccessThread(); Thread getter = new OutputThread(); setter.start(); getter.start(); }

public class Singleton { private static volatile Singleton _instance = null; private int aScore = 0; private Singleton() { } public static Singleton getInstance() { if (_instance == null) { synchronized (Singleton.class) { if (_instance == null) { _instance = new Singleton(); } } } return _instance; } public int getScore() { return aScore; } public void setScore(int val) { aScore = val; } } public class OutputThread extends Thread { public OutputThread() { // TODO Auto-generated constructor stub } public synchronized void run() { int loop = 0; while(loop < 100) { synchronized(this) { int score = Singleton.getInstance().getScore(); System.out.println("Score is: " + score); } loop++; } } } public class AccessThread extends Thread { public AccessThread() { // TODO Auto-generated constructor stub } public synchronized void run() { int loop = 0; while(loop < 100) { synchronized(this) { System.out.println("Accessing"); Singleton.getInstance().setScore(new Random().nextInt()); } loop++; } } } public class SingletonExample { public static void main(String[] args) { //ensure the singleton is created in single-threaded mode and set the score to 10. Singleton.getInstance().setScore(10); Thread setter = new AccessThread(); Thread getter = new OutputThread(); setter.start(); getter.start(); } }

For the past couple of months I have been required to undertake a games design project at University. One of the things that came out of this project is Phobos - an extensible engine built with JavaFX. Now, with the blessing of my course leader, I'm pleased to offer a series of tutorials which will culminate in you building your very own game engine. (that I'll actually complete for once).Throughout this journey of code and compilation I'll introduce you to topics that were new to me at one point. Today we'll be exploring Software Design Patterns and Threading. But first, a definition of terms.A Software Design Pattern (we're going to call this DP for brevity) is a way of doing something with code. A Pattern by design. Singletons are a DP. They're a way of ensuring only one of something can exist. Put simply, a Singleton contains a circular reference to itself. I'll talk more about this later on.A Finite State Machine is a way of managing the states of a program. Again, we're going to come back to this in a bit.The two concepts above - Singletons and Finite State Machines - form the basis of Phobos. The very core of the engine, conveniently namedis a Singleton and a Finite State Machine. All of the key engine related processing - managing the state of the engine, it's screens, threads, data (or, in laymans terms, everything) - happens here. It's designed in a way that means it can be called from anywhere without instantiation and can manage everything from one place. Many engines are designed this way, though not necessarily as singletons. This design pattern is somewhat similar to MVC, another idea that we'll explore later in the series.Without further ado, however, let's explore...Consider this: A program can have a number of states. It can be running. It can be asleep. It can be deadlocked or involved in a race condition. It can be many things, but only ever one at a time. We call this its state. Let's extrapolate that to a game. It can be paused. It can be on a menu. It could be on a pause menu (!). These are also states. Within the scope of the program we require a way to define and track these states. To do this we must build a Finite State Manager.First, though, we must explore the topic of threading. I looked for a decent threading tutorial but came up pretty dry. I'd recommend doing some reading on the topic but for the purposes of this I have an analogy relating to data access among threads.Consider a person. That person is a thread. The person is assigned a task and assigned a box. That box is memory space. Now consider that there are 3 of these peoplethreads. Each person has their own box which only they can access. Now consider a bigger box that all 3 people can access. We'll call this global memory. Global Memory is volatile, meaning it will be affected by everything you can possibly imagine. What happens if 2 of the people try to change a part of the box at once? One of a few things could happen. They could both try to get to it at once - this is a race condition - or they could be super polite and keep offering it back and forth - a deadlock. Alternatively, you could have a sign telling them to form an orderly queue. This is the wonderful concept of thread synchronisation.When you have two portions of a program attempting to access shared memory space, you have to be incredibly careful to ensure that only one thread can access it at once. In Java, we do this using thestatement. There's two ways of achieving thread synchronization using this method:Choosing which one to use is important, but a relatively easy decision:In reality, creating thread-safe code is far more complicated than this and is outside of the scope of this tutorial - if it interests you I urge you to delve into some reading because it's a truly awesome topic. Alas, within the scope of this tutorial, thestatement is as far as you need to go.You might be asking why this matters at all. Singletons. That's why. If only one of them can exist then it can only exist in the global memory space we discussed above. Because of this, the data that the Singleton might contain will be volatile. Our Finite State Manager will be a singleton, meaning to say that only one of it can exist - the FSM (we'll call it this from now on, so pay attention) will need to modify its internal state and that of the states it manages. Conversely, the states will exist within the global memory and need to modify the internal state of the FSM - indirectly, mind - when performing tasks such as telling the FSM to change state. A menu transition, perhaps?So, how does one write a Singleton? Well, that's fairly easy. Let's outline a basic singleton and have a look at how it works.So, we've got a class called. Singleton has a single field, aSingleton. Singleton also has a private constructor - this means that you can't create an instance of this class (that would create some wicked cool nesting issues). Instead, to use the singleton we call. This returns the only singleton in existence. The getter first checks that the Singleton isn't null and, if it is, it creates it through a process known as "lazy initialisation" with " double-checked locking ". After that, we have a single instance of a global object of whom we can access anything!What kind of a tutorial would this be if we didn't create a proper example? So let's add a field (and some getters / setters) to our Singleton.We've got anrepresenting a score in our game - simple enough - with a getter (getScore) and a setter (setScore(int)). Let's put together a little console app to test this out.That really is bare bones, but to demonstrate a point. Something that is somewhat of a best-practice is to ensure that a singleton is initialised in something called "single-threaded space" - it's the point at which the program is running within one thread of execution. All it means for you is that nothing else can try cross-thread (get to that in a bit) access.Let's define some threads.Above we have extended the thread class to define two threads: one that continually accesses and outputs the score value in our Singleton and another that constantly attempts to set the score. For the sake of education, we'll let these run 100 times using anas a loop counter. Let's finish the main function in our SingletonExample class:All we do here is create two threads - one of each of our AccessThread and OutputThread. Go ahead and run the application. What happens?It executes out of order, right?That's because we're only ensuring that more than one thread can't access the singleton at once. Unfortunately, this poses a problem: What if we want more than one thing to access the data, but in a specific order - one after the other?There are a couple of ways to do this, including the wait-notify method. However that's a story for another day. Within the scope of this tutorial, blocking isn't a concern. Phobos isn't multithreaded, but does need to be thread-safe due to the volatile nature of its core. What we've seen today is the basics you'll need to understand to move further. I can, however, recommend some great tutorials to those who are interested - they're at the end of this tutorial along with a complete code listing for everything we've covered.Thank you for reading. I'm looking forward to actually finishing something for once. Check back in a couple weeks and I'll be back for more Java powered goodness. Next time, we're going to look at the JavaFX windowing system and the ways that we will be using it. In fact, in part 2 we will write the bare minimum required to make Phobos run - including the an entire FSM. Get ready!