HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

Simple framework for looped applications

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
simpleapplicationsforloopedframework

Problem

I wrote up a quick API to help me save time while writing applications that require either a delay or frame rate. It should require me to implement a void run() method, which may or may not be annotated with the @Loop I created. If it doesn't contain the annotation, it will simply call the run() method once. If it is annotated, the client can specify either an fps (frames per second) or a delay (fps has priority over delay; if both are specified, fps will be used).

import vapp.App;
import vapp.Loop;

public class TestApp extends App {

    @Loop(fps=60)
    protected void run() {
        //my code
    }

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


This is pretty straightforward for the client. I'll accept any suggestions on this part, but what I really want to get checked out is the framework itself.

  • I'm not sure if my launch() method is breakable. I've tried breaking it, but couldn't find a way to. I haven't gotten any opinions on this yet, and I've been using it for a few months, so I would love to get some suggestions.



  • I have AppEngine to encapsulate the Thread for the app, as well was the boolean that manages it. I then pass in my appLogic, which the logic handles:



  • Checking if the run() method is annotated with @Loop (if not, just call run()).



  • Check if a frame rate is specified. If so, start a loop that manages frame rate. If not, check if delay is specified; start a loop that simply sleeps for the delay. If a delay or fps isn't specified, start a loop with no limitations.



  • Non-abstract method with no implementation. Is this the only way to ensure the subclass isn't forced to declare this method, but can if they want to?



I don't like how I create different conditional statements, each containing their own loop. I know something like this is logically needed, but it doesn't look elegant. I would love to clean it up.

Feel free to use these files for yourself if you find it functio

Solution

This idea is cool, I have some suggestions, though.

First off, you definitely should add JavaDoc to your annotation.

I wouldn't know that fps is preferred over delay, if you hadn't written it in your question (well I could find out, but it'd be a hassle). This should be done by documentation.

You can also document default values and usage constraints there, which brings me to the next point:

Your annotation only works if you annotate the run method. You might want to consider changing that a little and grab the method depending on the annotation you have (and not check for the annotation on your run method).

But that's mostly up to you ;)

Next thing I want to talk about is the units you're using.

Your delay is an int. I strongly assume the delay is given in milliseconds. If you'd want to reuse this it might be interesting to allow specifying the delay with an arbitrary TimeUnit, but that's future talk.

What's more interesting is things like this code:

int timePerFrame = 1000 / fps;


time as what? be explicit and tell the reader exactly what you mean and say: msPerFrame

It's somewhat similar when you grab the sleepTime. When first reading this I was somewhat.. confused. Tell the reader you need to divide by 1 mil. because you need to get from nanoseconds to milliseconds.
BUG ALERT

Btw, that's a bug right there. Counting the zeroes I'm getting to 10 mil. instead of 1. To prevent this you can write numeric literals with underscores in between them for easier overview:

long sleepTime = (System.nanoTime() - start) / 1_000_000 + msPerFrame;


BUG EXTERMINATION COMPLETE

Moving swiftly on to how you handle delay. It's very interesting to see the difference in how you solved the runtime of run problem from fps to delay.

I'd have expected you to do the same thing as with the fps. Instead you wait for run to complete before counting down the delay. If you want to have the same handling so fps and delay are usable interchangeably, you should do the following:

while (engine.isRunning()) {
     long start = System.nanoTime();
     run();
     long remainingDelay = delay - ((System.nanoTime() - start) / 1_000_000);
     
     if (remainingDelay > 0) {
        Thread.sleep(remainingDelay);
     } else {
        Thread.yield();
     }
}


which actually was another (rather minor) bug.

Movig swiftly on to your AppEngine, where I see you do the following twice:

running.getAndSet(...);


Which makes me ask myself: Why not just use set() or lazySet()? It seems you used to return booleans, eh? Oh and another thing:

If you already make all the methods you have final, why not also make the class final? Then you can even remove the final from all the methods you have ;)

Code Snippets

int timePerFrame = 1000 / fps;
long sleepTime = (System.nanoTime() - start) / 1_000_000 + msPerFrame;
while (engine.isRunning()) {
     long start = System.nanoTime();
     run();
     long remainingDelay = delay - ((System.nanoTime() - start) / 1_000_000);
     
     if (remainingDelay > 0) {
        Thread.sleep(remainingDelay);
     } else {
        Thread.yield();
     }
}
running.getAndSet(...);

Context

StackExchange Code Review Q#80490, answer score: 3

Revisions (0)

No revisions yet.