This site requires JavaScript, please enable it in your browser!
Greenfoot back
KlausKrieger
KlausKrieger wrote ...

2021/3/16

How could the World actually work?

KlausKrieger KlausKrieger

2021/3/16

#
I was wondering how some things like getWorld() would work. Since Actor and World are abstract, they can't be created, to which I assume that the magic doesnt lie in both classes having an object of each other and referencing them. I reckon that the programm of Greenfoot creates a static world somewhere else (maybe main method). Thats the only way I can imagine getWorld(), setWorld(), addObject() being that accessible and the screenworld being distinguished from potentially other created worlds. I know this is just hypothetical, but I want to know how they (at least how they could) did that to understand the game better. There probably is a way simpler solution, would be grateful to get a reply.
RcCookie RcCookie

2021/3/16

#
Well, it's actually not sooo simple, but you can look up how it works by taking a look into the source code of Greenfoot (take a look at the download page). But here is how some of the stuff works: First of all: There are two types of abstract in java: an abstract class and an interface. Interfaces really only contain the names and return types of the methods the class has, they all have to be overridden. A super simple interface is for example java.lang.Runnable, with no method except the void run(), which looks about like this:
1
2
3
4
5
package java.lang;
 
public interface Runnable {
    public void run();
}
If you actually want to implement Runnable, it could look like this:
1
2
3
4
5
6
public class PrintOnRun implements Runnable {
    @Override
    public void run() {
        System.out.println("Hello World");
    }
}
The "@Override" is like always optional. An abstract class however can include implementations of methods, but not neccecarily. In fact, both Actor and World don't even contain any actual abstract methods that have to be overridden, so they could theoretically also be non-abstract. However their purpose is to be overridden so that custom functionallity can be added. They do contain a lot of code themselfes though, that handles all the underlying actor-world interactions and also the rendering of the world. It may not be possibly to instatiate them directly, but they do still automatically do something whenever you call the "super(...)" method in your implementations constructor, so in some ways, they can be created, just not without a subclass. getWorld() (source code line 385) Every Actor has a (not to the outside visible) field "world" (l. 87) where it saves its current world. The field is initially null (specifically it is simply not assigned) because by default an actor is not in any world. It first has to be added to a world which will then assign itself to the world field. "getWorld()"(l. 385) does nothing else other than returning the value of that field. If you for example call "setLocation(int, int)" on an actor it will check weather the value of the world field is null and if it is throw an exception because the location of an actor requires it to be in a world. addObject(Actor, int, int) (l. 420) This actually consists of two parts: Adding the object to the world the method is executed on, and setting the actor's location. The adding to the world part was already mentioned above. Specifically, it first checks weather the actor to be added is already in a world, and if that is the case it will tell that world that the actor is not in it no more. Then it will tell the actor that it is now in this world, and also save that actor to a list inside of the world class (l. 77). This is important so that the actor will actually be drawn in the end and also that it will be included in "getObjects(Class<?>)"(l. 492) which make use of the list. After the object is in the world it is set to the given location. The location of an actor is actually stored inside of the actor itself (l. 64/70). The world simply calls a method in actor which will set the actors x and y fields to the according values. setWorld(World) (Greenfoot class, l. 67) This is actually a bit more complicated because it has deeper to do with how the internal Greenfoot framework works. Looking into the source code of this method does not really explain how it works right away:
1
2
3
4
5
6
7
8
public static void setWorld(World world)
{
    if ( world == null ) {
        throw new NullPointerException("The given world cannot be null.");
    }
 
    WorldHandler.getInstance().setWorld(world, true);
}
As you can see the method checks agains null and then simply calls a different method in "WorldHandler"(greenfoot.core.WorldHandler), a more internal class of Greenfoot. "getInstance()" returns the one and only instance of WorldHandler, so let's look into the implementation of that's getWorld method (WorldHandler class l.382). This method actually does some things. The worldHandler does actually have a field containing the current world (l. 64), which is being set to the new world. It also notifies a class named Simulation (greenfoot.core.Simulation) that the world has changed (ll. 411-413). Simulation is in many ways the core of the greenfoot gameplay simulation. It deos for examply handle stuff like the speed to which the slider is set, the current world, and actually calling the "act" method on the current world and its actors (l. 491, 563, 569). The main method As you may know the act method is the entry point of every java program, and so it is for Greenfoot (actually also BlueJ which is underlying). The root class of Greenfoot is greenfoot.core.GreenfootLauncherDebugVM, which creates the whole Greenfoot framework. The absolute root classs of BlueJ and Greenfoot is the class "bluej.Boot". It actually does contains the main method, but I honestly don't think that it is the 'real' main method, because if it wouldn't really do anything. I rather think javafx launches this class from somewhere. But in general, this is the absolute entry point of Greenfoot, and indirectly everything is created from here on. I hope I could give you some insights into how Greenfoot works internally. Of course I haven't coded it myself and may be wrong about some speculations, but generally I simply looked into the source code and tried to figure out how this works. If you want to know more or want to know how exactly I've figured this out (hint: not with notepad) you may want to join our little discord: https://discord.gg/CUCXGXzM. There I could for examply share my screen or similar.
KlausKrieger KlausKrieger

2021/3/16

#
This is great. I am pretty tired right now (living in Europe, its late), but reading through this is absolutely worth it. Very interesting to get that inside, yet i don’t know if I understood the interrelated process: a world is created somewhere, then, whenever that world executes addObject(...), the actor copies the reference value of the whole world object to its own world and the reference of the object is stored in a list in the world, which is used to draw the world somewhere else. That makes sense to me, since it is only a reference, you can change the world by just using the actors own world reference. who knew the created world could handle those things on its own :D so Which world is drawn is dealt at worldhandler and is stored in a currentWorld variable, and as all those things are handled, what and where the main method is is not anymore important to the question. I hope I got that right, but if any questions come up again I will get back to your comment. This must have taken a long time. Thank you again, helped alot.
RcCookie RcCookie

2021/3/17

#
No problem:-) Concerning the change of the world, this might also help: Every frame of the Greenfoot application the current world gets rendered by (simplified) stacking the images of all the actors on top of the worlds background image. If the world changes, the images used to render will simply look different on the next frame, which results in the world being switched visually. Additionally, Simulation will call act on the new world.
You need to login to post a reply.