Java-Gaming.org    
Featured games (79)
games approved by the League of Dukes
Games in Showcase (477)
Games in Android Showcase (107)
games submitted by our members
Games in WIP (536)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  Source for smallest game loop  (Read 4635 times)
0 Members and 1 Guest are viewing this topic.
Offline Rick

Junior Member


Projects: 1


Java games rock!


« Posted 2006-01-19 00:55:14 »

Since many people still ask for how to do a small game loop I thought I would start a thread to compare how different people are doing there game loop.
Here is the source for minimal game loop. If any one has a better method feel free to post improvments.

This gives a jar size no manifest  no extra compression of
jar -cvMf M.jar M.class
adding: M.class(in = 1084) (out= 690)(deflated 36%)

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.event.KeyEvent;
import java.awt.Event;
import java.awt.image.BufferStrategy;
import javax.swing.JFrame;

public class M extends JFrame
{
    boolean[] k = new boolean[256];


    public static void main(String args[])
    {
        new M();
    }

    public M()
    {
        // super("Title4k"); // optional

        setSize(26*28, 20*28);
        setResizable(false);
        show(); // depricated but less bytes than setVisible(true);
        createBufferStrategy(2);
        BufferStrategy b = getBufferStrategy();


        do
        {
            Graphics2D g = (Graphics2D)b.getDrawGraphics();
            g.setColor(new Color(0xFF000000));
            g.fillRect(0,0,26*28,20*28);

            // do game here

            b.show();
            g.dispose();
            try
            {
                Thread.sleep(10);
            }catch(Exception e){}
               
        } while(!k[KeyEvent.VK_ESCAPE] && isVisible());
        System.exit(0);

    }

    public void processKeyEvent(KeyEvent e)
    {
        // & with 0xFF to avoid any overflow of k[]

        k[e.getKeyCode()&0xFF] = e.getID() == 401;
    }

}
Offline noblemaster

JGO Ninja


Medals: 20
Projects: 10


Age of Conquest makes your day!


« Reply #1 - Posted 2006-01-19 01:10:05 »

how about:

start the thread for the game loop:
1  
2  
3  
4  
...
thread = new Thread(this);
thread.start();
...


need the global variable in same class for the thread
1  
private Thread thread;


in the same class, implement the Runnable interface
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
public void run() {
   // loop variables
   long loopStart = System.nanoTime();
   long timePerLoop = 10000000;    // time in nano seconds per loop

   // the official Sun method for loops
   Thread currentThread = Thread.currentThread();
   while (thread == currentThread()) {
        ... do something

        // wait for next loop
        try {
           while (loopStart + timePerLoop > System.nanoTime()) {
             Thread.sleep(1);
           }
           loopStart += timePerLoop;
        }
        catch (InterruptedException e) {
           throw new IllegalThreadStateException("Impossible Exception!");
        }
   }
}


to stop the thread (official method by Sun):
1  
2  
3  
  public void stop() {
    thread = null;
  }

Offline Anon666

Junior Member




aka Abuse/AbU5e/TehJumpingJawa


« Reply #2 - Posted 2006-01-19 01:33:56 »

Quote
to stop the thread (official method by Sun):

I'm curious as to what you mean by this? the Runnable interface does not define any such method.

Also, depending on your definition of exactly what your stop() method should do - your implementation maybe incorrect.
If you want your game thread to be terminated before stop() returns, you should do something like :-

Quote
public void stop() {
   if(thread!=null)
   {
      Thread t = thread;
      thread = null;
       t.join();
   }
}

ofcourse, the lack of synchronization also mandates that there is no way of restarting the game thread. (otherwise you are again introducing the potencial for bugs)
However, this is all irrelevant as there is no need for storing the game Thread as a member variable - it is unnecessarily bloating.

The only function of member variables in an optimal 4K app. is for communication between the event dispatch thread, and the game thread.
As such, you should need only *one* member variable.
This will typically be either an int[], or a boolean[] - depending on preference, and exactly what you are doing with your user input.
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline noblemaster

JGO Ninja


Medals: 20
Projects: 10


Age of Conquest makes your day!


« Reply #3 - Posted 2006-01-19 01:38:43 »

sorry, I wasn't aware I was posting in the 4K thread...

Offline Rick

Junior Member


Projects: 1


Java games rock!


« Reply #4 - Posted 2006-01-19 01:44:23 »

sorry, I wasn't aware I was posting in the 4K thread...

No problem I guess I did not specify the problem very clearly. I will restate the challenge.

Produce smallest compiled game loop that does the following.
display window or frame.
handles key board events
does a double buffered clear of the screen
exits cleanly on either window close or escape or both.

If you do post an alternative please show the compiled class size and compressed jar size as in my example.
Offline oNyx

JGO Coder


Medals: 1


pixels! :x


« Reply #5 - Posted 2006-01-19 06:56:54 »

@Rick

>g.setColor(Color.black);

Its better to use new Color(0x000000) etc all over the place.

>Graphics2D g = (Graphics2D)b.getDrawGraphics();

Recycling the Graphics object is bad. It can lead to infinifite memory consumption on mac. Put that bit into the loop. (Also dispose() is missing.)

>Thread.sleep(10);

Thats x + ~10msec. The speed will vary alot across different machines. Some proper throtteling code can be found here (used in bad sector and fuzetsu):

http://www.java-gaming.org/forums/index.php?topic=11640.30

@kingaschi

>System.nanoTime()

Thats 1.5. You cant use it for this year's compo.

弾幕 ☆ @mahonnaiseblog
Offline Markus_Persson

JGO Wizard


Medals: 14
Projects: 19


Mojang Specifications


« Reply #6 - Posted 2006-01-19 14:14:25 »

I usually stick it all in the main method, like thusly:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.event.KeyEvent;
import java.awt.Event;
import java.awt.image.BufferStrategy;
import javax.swing.JFrame;

public class M extends JFrame
{
    static boolean[] k = new boolean[256];


    public static void main(String args[])
    {
        M m=new M();

        m.setSize(26*28, 20*28);
        m.setResizable(false);
        m.show(); // depricated but less bytes than setVisible(true);
       m.createBufferStrategy(2);
        BufferStrategy b = m.getBufferStrategy();
        Graphics2D g = (Graphics2D)b.getDrawGraphics();

        do
        {
            g.setColor(Color.black);
            g.fillRect(0,0,26*28,20*28);

            // do game here

            b.show();
            try
            {
                Thread.sleep(10);
            }catch(Exception e){}
               
        } while(!k[KeyEvent.VK_ESCAPE] && isVisible());
        System.exit(0);

    }

    public void processKeyEvent(KeyEvent e)
    {
        // & with 0xFF to avoid any overflow of k[]

        k[e.getKeyCode()&0xFF] = e.getID() == 401;
    }

}


Saves one entire method. Wink

Play Minecraft!
Offline Rick

Junior Member


Projects: 1


Java games rock!


« Reply #7 - Posted 2006-01-19 15:56:06 »

Sorry your loop is larger you removed one method but then had to create an instance and dereference it to make the calls. You came out 40 bytes larger before jaring.

I do not understand the comment about recycling the graphics object. I get a reference to an object and use it as long as I wish. There should be no need to recreate one every time. There also should be no need to dispose of it as when I am done with it the program exits.
Offline jbanes

JGO Coder


Projects: 1


"Java Games? Incredible! Mr. Incredible, that is!"


« Reply #8 - Posted 2006-01-19 16:21:50 »

I do not understand the comment about recycling the graphics object. I get a reference to an object and use it as long as I wish. There should be no need to recreate one every time. There also should be no need to dispose of it as when I am done with it the program exits.

* jbanes slaps Rick on the wrist for being naughty

Every time the buffer is flipped, the graphics context changes. The only reason I can think of why it works at all is that you're in double buffered mode instead of page flipping mode. Since you're always writing to the secondary buffer, you're getting away with not changing your Graphics object instance. Unfortunately, if ANYTHING changes, your code will blow up with a very exciting and disasterous [size=14pt]BOOM![/size]

Since you're not forcing the BufferStrategy into any particular mode, this change could happen just because someone else's PC is different. Or the user might minimize and restore the window, and the OS decides to release and recreate its graphics context. Or the OS could decide to provide a mapped area of page flipping memory that it's using (apparently Mac OS X is now VSynced) causing the context to change every frame.

In short: What you're doing is BAD. It's almost guaranteed to cause problems, even if it works on your machine. All you need to do to fix it is to move the  "g = (Graphics2D)b.getDrawGraphics();" into the loop. It shouldn't cause any new overhead in the file size, and your code will be much more compatible and portable.

Java Game Console Project
Last Journal Entry: 12/17/04
Offline oNyx

JGO Coder


Medals: 1


pixels! :x


« Reply #9 - Posted 2006-01-19 16:29:22 »

>You came out 40 bytes larger before jaring.

Before compression is irrelevant. Often it was like 200 bytes smaller before compression, but 100 bytes bigger after compression.

>I get a reference to an object and use it as long as I wish. There should be no need to recreate one every time.

The proper way is getting the graphics object, drawing and disposing... and that each frame. If you really wish to repeat my mistakes... go ahead Tongue

弾幕 ☆ @mahonnaiseblog
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline Rick

Junior Member


Projects: 1


Java games rock!


« Reply #10 - Posted 2006-01-19 16:36:22 »

Thanks for the lesson on graphics object I will change that. oNyx is also correct about the use of Color It save 22 bytes if a new is done in the loop and 15 is a color variable is created outside the loop. Markus loop was larger both before and after jaring. I have edited the original post to reflect the comments received.
Offline x30ice

Senior Newbie





« Reply #11 - Posted 2006-01-19 19:59:52 »

So will be this code correct and small enough or it's better to change the way Markus_Persson said ? I wonder if the 'extras' "m." could penalize what's gained from keeping all in the main method.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.event.KeyEvent;
import java.awt.Event;
import java.awt.image.BufferStrategy;
import javax.swing.JFrame;

public class Q extends JFrame
{
    boolean[] k = new boolean[256];

    public static void main(String args[])
    {
        new Q();
    }

    public Q()
    {
      long lastFrame=0;
      float yield=10000f;
      float frameAverage=16f; //start with desired msec per frame

      //super("title"); // title

        setSize(640,480);
        setResizable(false);
        show(); // depricated but less bytes than setVisible(true);
        createBufferStrategy(2);
        BufferStrategy b = getBufferStrategy();
        Graphics2D g;

        do
        {
         g=(Graphics2D)b.getDrawGraphics();
         g.setColor(new Color(0x000000));
         g.fillRect(0,0,640,480);
            // do game here

            b.show();
            try
            {
            long timeNow = System.currentTimeMillis();
            //rolling average for the last 10 time inputs
           frameAverage = (frameAverage * 10 + (timeNow - lastFrame)) / 11;
            lastFrame=timeNow;
            //16f = for 16msec
           //0.1f = damping value
           //and that +0.05f bit is for ensuring that it can grow faster after it ran flat out for a while
           yield+=yield*((16f/frameAverage)-1)*0.1f+0.05f;

            for(int i=0;i<yield;i++)
               Thread.yield();
            }catch(Exception e){}
            g.dispose();
        } while(!k[27] && isVisible());
        System.exit(0);
    }

    public void processKeyEvent(KeyEvent e)
    {
        // & with 0xFF to avoid any overflow of k[]
        k[e.getKeyCode()&0xFF] = e.getID() == 401;
    }
}
Offline Anon666

Junior Member




aka Abuse/AbU5e/TehJumpingJawa


« Reply #12 - Posted 2006-01-19 21:59:56 »

You would do well to refactor the fps managing code so it doesn't rely on floats.
It'll save you atleast 16bytes in the constants pool (float constants 10000f, 16f, 0.1f and 0.05f)
Offline Rick

Junior Member


Projects: 1


Java games rock!


« Reply #13 - Posted 2006-01-19 22:26:46 »

So will be this code correct and small enough or it's better to change the way Markus_Persson said ? I wonder if the 'extras' "m." could penalize what's gained from keeping all in the main method.

The extra m. references do cost more.

I did not include the code to do the fixed frame rate as I thought the resolution of the timer is too low. That is it is normally only good to 10 to 20 millsec. I just wait 10 millsec so that on fast machines the game does not go to fast. On slow machines sure it would be good to take into account how long it took in the loop and only wait 10 - time taken. But can we actually measure the time taken to less than 10 millisec accuracy? I guess by using the rolling average as suggested we can get a better idea of the time taken but I think the extra bytes are not worth it. Slow machines will just run a little slow.
Offline Anon666

Junior Member




aka Abuse/AbU5e/TehJumpingJawa


« Reply #14 - Posted 2006-01-19 23:05:29 »

I don't think it makes a blind bit of difference whether your game code is in the constructor, or in main(...).

The default constructor exists whether or not you define it, so there is no saving by moving stuff to your main.
Equally, referencing methods declared in the class is no more expensive with either approach.
"this.methodName()" evaluates to exactly the same bytecode as "m.methodName()"
(push a local variable containing the JFrame's objectReference onto the stack, push any method parameters onto the stack, and then the invoke instruction)

The only difference I can think of, is when placing your game code in the JFrames constructor, you get a reference to 'this' stored in local variable 0 'for free'.
Now, if this is where you want 'this' to be located, then all is well - and you have saved yourself 1 byte.
However, typically you will want 'this' to be stored in a high index local variable, as you rarely make sufficient references to it for it to be worth-while occupying one of the quick access local variables.
Therefor, you end up inserting 3 extra bytecodes to push it back onto the stack, and pop it into a high order local variable.
This reverses your benefit, making main() the best place to put your game code!

To summarize, in some very limited circumstances putting your game code in the constructor will save you *one* byte, but generally it is best to place it in main.

It realy is irrelevant though, the alteration to the compressability of your code, by re-ordering your bytecode will make a far bigger difference.
Hence, any bytecode optimisation that saves you less than 50 bytes is generally not worth even thinking about.
Offline oNyx

JGO Coder


Medals: 1


pixels! :x


« Reply #15 - Posted 2006-01-19 23:17:13 »

>Slow machines will just run a little slow.

No, it runs *very* slow and thats despise the fact that the machine is good enough to run the game just fine.

Say you sleep for 20msec and it takes only 1msec to draw everything... so you end up with 21msec per frame, which results in ~48 fps. And now imagine there is some machine which needs 20msec per frame - it could run it at the same speed, but instead you end up with 40msec per frame, which means the framerate is halved for no reason.

This rolling average adaptive yield thingy works pretty well, if the time per frame doesnt fluctuate too much. In fuzetsu the difference between maximum and minimum is pretty huge (from zero bullets to 1500 bullets + 300 particles), but the changes from one frame to the next are pretty small (dont be misleaded by the speedup after killing... thats intentionally for clearing the screen quickly).

It works pretty well. Even if you cripple the resolution of the timer to the win9x level (50-55msec). Thanks to averaging of the last 10 frames we get a pretty good idea how much time we spend per frame... well, on average that is. So, you get some delay on adjustments and it will over/understeer if the difference from the current yield count to the desired yield count is... how should I put it... extreme. Like if you have a very slow machine and start with a gigantic yield count of 50k it will be too slow for a second, then too fast for another second and finally it will run at the right speed with say 400 yields. Since the adjusting is proportional (with damping) it draws near a good value quickly.

I used linear (+/-1) adjusting before without any damping. That worked sorta alright and the adjusting on slow machines was also pretty quick (since the yield values were pretty small), but it wasnt that smooth and it didnt cope that well with game-typical time-per-frame-changes.

You can also use delta timing, but that effectively means ~20 (different) fps on win9x and it makes everything unnecessarily complicated.

弾幕 ☆ @mahonnaiseblog
Offline Rick

Junior Member


Projects: 1


Java games rock!


« Reply #16 - Posted 2006-01-20 01:48:25 »

Quote
I don't think it makes a blind bit of difference whether your game code is in the constructor, or in main(...).

I tend to agree. But the difference in size between the two posted versions is 40 bytes before jaring and 13 bytes after.

Code in main
adding: M.class(in = 1276) (out= 803)(deflated 37%)

Code in constructor
adding: M.class(in = 1236) (out= 790)(deflated 36%)

I guess part of the difference is in the extra local variable to hold the reference to the class and the need to make the keyboard input array static when running in main.

I have tried both methods in a full game and the Code in constructor came out smaller there as well.


Offline jbanes

JGO Coder


Projects: 1


"Java Games? Incredible! Mr. Incredible, that is!"


« Reply #17 - Posted 2006-01-20 03:26:43 »

Using the Main method is smaller if you need to throw an exception. If you use the constructor, you have two separate methods throwing the exception. (Or one method throwing the exception, and the other one trapping it.) With just the main method you only have one method throwing the exception.

Java Game Console Project
Last Journal Entry: 12/17/04
Offline Rick

Junior Member


Projects: 1


Java games rock!


« Reply #18 - Posted 2006-01-20 04:32:04 »

Good point I tried it out and here are the results. Note the code in constructor is still smaller by the same 13 bytes. But while the throw is larger than the catch uncompressed the throw saves an additional 10 bytes once compressed over the try catch. 

Code in Main Exception thrown instead of try catch
adding: M.class(in = 1282) (out= 793)(deflated 38%)

Code in Constructor Exception thrown in both constructor and main no try block
E:\lode>jar -Mcvf M.jar M.class
adding: M.class(in = 1253) (out= 780)(deflated 37%)
Pages: [1]
  ignore  |  Print  
 
 
You cannot reply to this message, because it is very, very old.

 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

Riven (12 views)
2014-07-29 18:09:19

Riven (8 views)
2014-07-29 18:08:52

Dwinin (9 views)
2014-07-29 10:59:34

E.R. Fleming (25 views)
2014-07-29 03:07:13

E.R. Fleming (10 views)
2014-07-29 03:06:25

pw (39 views)
2014-07-24 01:59:36

Riven (39 views)
2014-07-23 21:16:32

Riven (27 views)
2014-07-23 21:07:15

Riven (28 views)
2014-07-23 20:56:16

ctomni231 (59 views)
2014-07-18 06:55:21
HotSpot Options
by dleskov
2014-07-08 03:59:08

Java and Game Development Tutorials
by SwordsMiner
2014-06-14 00:58:24

Java and Game Development Tutorials
by SwordsMiner
2014-06-14 00:47:22

How do I start Java Game Development?
by ra4king
2014-05-17 11:13:37

HotSpot Options
by Roquen
2014-05-15 09:59:54

HotSpot Options
by Roquen
2014-05-06 15:03:10

Escape Analysis
by Roquen
2014-04-29 22:16:43

Experimental Toys
by Roquen
2014-04-28 13:24:22
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!