I have two methods that take either a Circle or a Scene and make the background flash red - green - blue by changing the fill value every N ms.
The two methods:
private void makeRGB(Circle c) {
Timer t = new Timer();
t.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
if(c.getFill() == Color.RED) {
c.setFill(Color.GREEN);
} else if (c.getFill() == Color.GREEN) {
c.setFill(Color.BLUE);
} else {
c.setFill(Color.RED);
}
}
},0, RGB_CHANGE_PERIOD);
}
private void makeRGB(Scene s) {
Timer t = new Timer();
t.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
if(s.getFill() == Color.RED) {
s.setFill(Color.GREEN);
} else if (s.getFill() == Color.GREEN) {
s.setFill(Color.BLUE);
} else {
s.setFill(Color.RED);
}
}
},0, RGB_CHANGE_PERIOD);
}
Obviously these are very similar, however as Circle and Scene aren't in the same inheritance tree I can't use the approach of calling a superclass of them both containing the .setFill() / .getFill() methods.
How would I go about removing code duplication here?
In general, you remove duplicate code by factoring the common code into a function/method/class and parameterizing the parts that vary. In this case, what varies is the way you retrieve the current fill, and the way you set the new fill. The java.util.function package provides the appropriate types for parametrizing these, so you can do:
private void makeRGB(Circle c) {
makeRGB(c::getFill, c:setFill);
}
private void makeRGB(Scene s) {
makeRGB(s::getFill, s:setFill);
}
private void makeRGB(Supplier<Paint> currentFill, Consumer<Paint> updater) {
Timer t = new Timer();
t.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
if(currentFill.get() == Color.RED) {
updater.accept(Color.GREEN);
} else if (currentFill.get() == Color.GREEN) {
updater.accept(Color.BLUE);
} else {
updater.accept(Color.RED);
}
}
},0, RGB_CHANGE_PERIOD);
}
Note, though, that you should not change the UI from a background thread. You should really do
private void makeRGB(Supplier<Paint> currentFill, Consumer<Paint> updater) {
Timer t = new Timer();
t.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
Platform.runLater(() -> {
if(currentFill.get() == Color.RED) {
updater.accept(Color.GREEN);
} else if (currentFill.get() == Color.GREEN) {
updater.accept(Color.BLUE);
} else {
updater.accept(Color.RED);
}
}
}
},0, RGB_CHANGE_PERIOD);
}
or, (better), use a Timeline to do things on a periodic basis.
As noted in the comments, you can also provide a Map that maps each color to the color that comes after it. Combining all this gives:
private final Map<Paint, Paint> fills = new HashMap<>();
// ...
fills.put(Color.RED, Color.GREEN);
fills.put(Color.GREEN, Color.BLUE);
fills.put(Color.BLUE, Color.RED);
// ...
private void makeRGB(Circle c) {
makeRGB(c::getFill, c:setFill);
}
private void makeRGB(Scene s) {
makeRGB(s::getFill, s:setFill);
}
private void makeRGB(Supplier<Paint> currentFill, Consumer<Paint> updater) {
Timeline timeline = new Timeline(Duration.millis(RGB_CHANGE_PERIOD),
e-> updater.accept(fills.get(currentFill.get())));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
}
Related
I want to create efficienty timer in LibGDX framework, that will count the time left for my character. The doSmth() method should be called as many times as some flag is set to true. I know that the third parametr of Timer is that, how many times should it trigger. For now one Im calling the method recursive, but I dont think it is the efficient way.
public void updateTimer(){
new Timer().scheduleTask(new Timer.Task() {
#Override
public void run() {
doSmth();
updateTimer();
}
},1);
}
It would be more accurate to use a repeat count. Your method will introduce a bit of error each time the task is run, because the task is run on the GL thread, so it will occur just slightly after one second, but you are repeating it one second after that. So with each repeat you are slightly further behind.
private Timer.Task myTimerTask = new Timer.Task() {
#Override
public void run() {
doSmth();
}
};
public void startTimer(){
Timer.schedule(myTimerTask, 1f, 1f);
}
And when you need to stop it:
myTimerTask.cancel();
The com.badlogic.gdx.utils.Timer executes tasks in the future on the main loop thread,even if your game is in a pause screen, a menu or in another state, you can simply control time in the render method by adding delta time.
private float timeSeconds = 0f;
private float period = 1f;
public void render() {
//Execute handleEvent each 1 second
timeSeconds +=Gdx.graphics.getRawDeltaTime();
if(timeSeconds > period){
timeSeconds-=period;
handleEvent();
}
[...]
}
public void handleEvent() {
[...]
}
To keep organized i personally have an array on my main game class that holds all my timed events and process everything on the render cycle. In your case you can put some control variables as you wish.
my implementation example:
// MainGame.java
private ObjectMap<TimedEventEnum, TimedEvent> hshTimedEvent;
public void render(){
executeTimedEvents();
}
private void executeTimedEvents() {
for (ObjectMap.Entry<TimedEventEnum, TimedEvent> entry : hshTimedEvent) {
TimedEvent event = entry.value;
event.process();
}
}
public void killEvent(TimedEventEnum event) {
hshTimedEvent.remove(event);
}
// TimedEventEnum.java
public enum TimedEventEnum {
COUNT_MONEY,
CHECK_FOR_ACHIEVS,
ANOTHER_EVENT_EXAMPLE
}
//CountMoneyTimedEvent.java
public class CountMoneyTimedEvent extends Timer implements TimedEvent {
public CountMoneyTimedEvent() {
super();
init(this, 4f, false);
}
#Override
public void execute() {
//execute logic here
}
#Override
public void reset() {
this.timesFired = 0L;
}
}
//Timer.java
public abstract class Timer {
private Float deltaCount;
private Float timeToEvent;
private Boolean isRepeatable;
protected Long timesFired;
private TimedEvent event;
Timer() {
}
public void init(TimedEvent event, Float eventTime, Boolean isRepeatable) {
this.deltaCount = 0f;
this.timeToEvent = eventTime;
this.isRepeatable = isRepeatable;
this.timesFired = 0L;
this.event = event;
}
public void process() {
if (isEventTime()) {
event.execute();
}
}
private Boolean isEventTime() {
if (event != null && (isRepeatable || timesFired == 0)) {
deltaCount += Gdx.graphics.getRawDeltaTime();
if (deltaCount > timeToEvent) {
deltaCount -= timeToEvent;
timesFired++;
return true;
}
}
return false;
}
protected void executeNextEvent() {
deltaCount = timeToEvent;
}
}
// TimedEvent.java
public interface TimedEvent {
void execute();
void reset();
void process();
}
I'm just trying to change the color of my drawn circle with a timer. I have implemented following code into my "onCreate" method:
Timer t = new Timer();
t.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
Drawing.switchColor();
}
});
}
},
1000,
1000);
The method switchColor() does the following action:
public static void switchColor() {
Random r = new Random(30);
int random = r.nextInt();
if(random < 10) {
p.setColor(Color.GREEN);
}
else if(random >10 && random < 20) {
p.setColor(Color.BLUE);
}
else {
p.setColor(Color.RED);
}
}
When I run this, the color stays at it's default.
Does anyone know whether I have to use a handler within or a different timer model?
Thanks in advance!
I now found a suitable solution:
//-------------------Part 1 of AddCircleTimer------------------------
//Declare the timerAddCircle
timerAddCircle = new Timer();
timerAddCircle.schedule(new TimerTask() {
#Override
public void run() {
TimerMethodAddCircle();
}
}, 1000, 1000);
//-------------------Part 2 of AddCircleTimer------------------------
private void TimerMethodAddCircle()
{
//This method is called directly by the timer and runs in the same thread as the timer.
//We call the method that will work with the UI through the runOnUiThread method.
this.runOnUiThread(Timer_Add);
}
private Runnable Timer_Add = new Runnable() {
public void run() {
//This method runs in the same thread as the UI.
//Do something to the UI thread here
Drawing.addCircle();
d.invalidate();
}
};
//-------------------END Part 2 of AddCircleTimer------------------------
This works very fine and I can use it for even more timers and different methods!
Thanks to all!
Your t.start() is missing, either in onCreate, onStart, or onResume, depending on when you want to start your timer.
im currently trying to create a program that dynamically changes the text on JTextArea's and JButtons when they are pressed, or display a JOptionPane. At the moment nothing is happening when i press the buttons, they are not getting updated nor are they making a dialogue box appear.
Help is appreciated
private MillionaireGui view;
private Millionaire model;
private String question;
private int gameState;
private boolean isRunning;
public MillionaireController(MillionaireGui view, Millionaire model) {
this.view = view;
this.model = model;
this.question = null;
this.gameState = 0;
this.isRunning = true;
}
public void setModel(Millionaire model) {
this.model = model;
}
public void setView(MillionaireGui gui) {
this.view = gui;
}
public void getQuestion() {
question = model.getDeck().generateQuestion();
view.setQuestion(question);
}
public void update(){
while(isRunning){
if(gameState == 0){
getQuestion();
ArrayList<String> ans = model.getDeck().getAnswers();
view.setButtonA(ans.get(0));
view.setButtonB(ans.get(1));
view.setButtonC(ans.get(2));
view.setButtonD(ans.get(3));
gameState = 1;
}
if(gameState == 1){
if(view.getAnswer() != 0){
if(model.getDeck().isCorrect(view.getAnswer())){
view.dispCorrectAnswer();
view.setAnswer(0);
gameState = 0;
}
else {
gameState = 3;
}
}
}
if(gameState == 3){
isRunning = false;
view.displayErrorMsg();
}
}
}
#Override
public void run() {
update();
}
GUI:
public void setButtonB(String str){
buttonB.setText(str);
}
public void setButtonC(String str){
buttonC.setText(str);
}
public void setButtonD(String str){
buttonD.setText(str);
}
public void setAnswer(int num){
answer = num;
}
public String getQuestion(){
return question;
}
public void setQuestion(String str){
question = str;
questionField.setText(str);
}
MAIN:
public class Millionaire_main {
public Millionaire_main(){
}
public static void main(String[] args) {
MillionaireGui gui = new MillionaireGui();
QuestionDeck deck = new QuestionDeck();
Millionaire model = new Millionaire(deck);
MillionaireController control = new MillionaireController(gui, model);
gui.setVisible(true);
Thread thread = new Thread(control);
thread.start();
}
}
The code within the update() method seems to be running with a thread. What I think is happening is that you have 2 threads, one of which is doing some background task which causes the update. The background thread is not the EDT, thus any UI updates will not be visible.
Trying the approach below would fix the problem (most likely at least)
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run() {
view.setButtonA(ans.get(0));
view.setButtonB(ans.get(1));
view.setButtonC(ans.get(2));
view.setButtonD(ans.get(3));
}
});
The above should place your button settings events on the EDT, which should trigger the change.
Looks like maybe you just need to revalidate the container.
After you set all the button text fields, call gui.revalidate() to mark everything as invalid & validate. Here's more on the differences between those 3 methods
Also (as mentioned by #npinti) - I'm not sure exactly what you're doing with the extra thread, but be aware that modifying GUI components outside of the AWT thread is NOT a good idea
I have fadeOut metod to well fadeOut scene, load another in background and fadeIn it. For fadeOut current scene I use black rectangle and TimerHandler for fade effect. To load second scene I use another thread.
My code:
public void fadeOut(final BasicScene scene){
final Thread backgroundLoad = new Thread(new Runnable() {
#Override
public void run() {
if(!scene.isLoaded()) {
scene.load();
}
threadSync("load",scene);
}
});
TimerHandler backgroudLoadingDelay = new TimerHandler(0.1f,false,new ITimerCallback() {
#Override
public void onTimePassed(TimerHandler pTimerHandler) {
mEngine.unregisterUpdateHandler(pTimerHandler);
}
});
blackRectangle = new Rectangle(0,0,CAMERA_WIDTH,CAMERA_HEIGHT,this.getVertexBufferObjectManager());
blackRectangle.setAnchorCenter(0, 0);
blackRectangle.setColor(0, 0, 0, 0f);
mEngine.getScene().attachChild(blackRectangle);
PauseableTimerHandler fadeoutHandler = new PauseableTimerHandler(0.01f, new PauseableTimerHandler.ITimerCallback() {
#Override
public void onTick(PauseableTimerHandler pTimerHandle) {
blackRectangle.setAlpha(blackRectangle.getAlpha() + 0.025f);
if (blackRectangle.getAlpha() >= 1){
blackRectangle.setAlpha(1f);
mEngine.getScene().unregisterUpdateHandler(pTimerHandle);
threadSync("transition",scene);
}
}
});
mEngine.getScene().registerUpdateHandler(fadeoutHandler);
backgroundLoad.start();
}
private void threadSync(String thread, BasicScene scene) {
if (thread.equals("load")){
loadingDone = true;
} else if (thread.equals("transition")){
transitionDone = true;
}
if(loadingDone && transitionDone){
loadingDone = transitionDone = false;
mEngine.setScene(scene.showScene());
scene.fadeIn();
runOnUpdateThread(new Runnable() {
#Override
public void run() {
blackRectangle.detachSelf();
}
});
}
}
But on my both devices (SE wt19i and desire 500) I got sharp in fade effect. They aren't when I don't load anything so I guess it's because I start another thread? Am I right or there is another way?
I have a Jframe which is my application's window (appFrame in the following code) that contains a lot of logic and takes like 1 second or so to load. In the meantime I want to show my user a very nice loading frame (initFrame). However, when I run this code, the initFrame does appear but the text in a JLabel on it doesn't appear immediately - it actually doesn't appear at all in the brief moment till the app frame is loaded.
If i comment out all the appFrame, and only launch the initFrame, the text is loaded instantly, no waiting time at all. Why is this so? Might this be a concurrency issue?
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() { //as per best practice for concurrency in swing - see http://docs.oracle.com/javase/tutorial/uiswing/concurrency/
#Override
public void run() {
final JFrame initFrame = new InitFrame();
initFrame.setVisible(true);
final AppFrame appFrame = new AppFrame();
appFrame.setVisible(true);
initFrame.setVisible(false);
initFrame.dispose();
}
});
}
I would separate the frames' creation into two threads. The first, initializing InitFrame. Running this thread and calling isShowing() on the InitFrame object. When it returns true, run the second thread to initialize and show AppFrame.
This will force a happens before relationship between the visibility of the two frames.
class Main {
JFrame initFrame = null;
AppFrame appFrame = null;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
initFrame = new InitFrame();
initFrame.setVisible(true);
}
});
while(!initFrame.isShowing()) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
}
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
appFrame = new AppFrame();
appFrame.setVisible(true);
initFrame.setVisible(false);
initFrame.dispose();
}
});
}
}
Here's an example of what might be going wrong in your AppFrame.
You can run the test with threading:
java SplashTest true
or without
java SplashTest
When threading is enabled, you see the SplashFrame and AppFrame updating every 250ms, more or less.
When threading is not enabled, you get to see the SplashFrame with no components showing, the app 'hangs' for 4 seconds, then you see the AppFrame.
The example is somewhat contrived, but might give you some ideas.
Note that the SplashFrame has no 'direct' connection to the AppFrame. All communication is through the AppFrameWorkListener interface.
I've also put the 'work' in the AppFrame. But really if there is a lot of processing to be done it should be extracted out of the UI code, run in a separate Thread, and the AppFrame would be notified of progress by the task, in the same way as the SplashFrame currently is.
import javax.swing.*;
class SplashTest {
static boolean useThread = false;
public static void main(String[] args) {
// Pass true at the command line to turn on threading.
// No args, or any value other than true will turn off threading.
if (args.length > 0) {
useThread = new Boolean(args[0]);
}
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
SplashFrame splashFrame = new SplashFrame();
splashFrame.setVisible(true);
new AppFrame(splashFrame).setVisible(true);
}});
}
private static class BaseFrame extends JFrame {
public BaseFrame() {
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setSize(200, 200);
setTitle(getClass().getSimpleName());
}
}
private static class SplashFrame extends BaseFrame implements AppFrameWorkListener {
JLabel status;
public SplashFrame() {
setLocation(0, 0);
status = new JLabel("Splash Frame");
getContentPane().add(status);
}
public void appFrameWorkStart() {
status.setText("Work started");
}
public void appFrameWorkProgress(long timeElapsed) {
status.setText("Work has taken " + timeElapsed + "ms so far");
}
public void appFrameWorkDone() {
// http://stackoverflow.com/questions/1234912/how-to-programmatically-close-a-jframe
setVisible(false);
dispose();
}
}
private static class AppFrame extends BaseFrame {
JLabel status;
AppFrameWorkListener listener;
public AppFrame(AppFrameWorkListener listener) {
setLocation(200, 200);
status = new JLabel("App Frame");
getContentPane().add(status);
this.listener = listener;
// None of this 'heavy lifting' should be in a constructor.
if (useThread) {
new Thread(new Runnable() {
#Override
public void run() {
doLotsOfWork(4);
}
}).start();
} else {
doLotsOfWork(4);
onWorkDone();
}
}
private void doLotsOfWork(int workLengthSeconds) {
// We're starting. Ensure onWorkStart is called on the EDT,
// as this method may be called from a different Thread.
invokeOnWorkStartOnEDT();
long start = System.currentTimeMillis();
// Hammer the CPU for "workLengthSeconds" number of seconds.
// And do some contrived progress reporting.
long workLengthMs = workLengthSeconds * 1000;
while (System.currentTimeMillis() - start < workLengthMs) {
long innerStart = System.currentTimeMillis();
// Consume 250ms CPU before issuing progress update.
while (System.currentTimeMillis() - innerStart < 250);
invokeOnWorkProgressOnEDT(System.currentTimeMillis() - start);
}
// We're done now. Ensure onWorkDone is called on the EDT,
// as this method may be called from a different Thread.
invokeOnWorkDoneOnEDT();
}
private void invokeOnWorkStartOnEDT() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
onWorkStart();
}
});
}
private void invokeOnWorkProgressOnEDT(final long timeElapsed) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
onWorkProgress(timeElapsed);
}
});
}
private void invokeOnWorkDoneOnEDT() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
onWorkDone();
}
});
}
private void onWorkStart() {
status.setText("Work Started");
if (null != listener) {
// Tell someone who's interested in the work status.
listener.appFrameWorkStart();
}
}
private void onWorkProgress(long timeElapsed) {
status.setText("Work has taken " + timeElapsed + "ms so far");
if (null != listener) {
// Tell someone who's interested in the work status.
listener.appFrameWorkProgress(timeElapsed);
}
}
private void onWorkDone() {
status.setText("Work Done");
if (null != listener) {
// Tell someone who's interested in the work status.
listener.appFrameWorkDone();
}
}
}
interface AppFrameWorkListener {
public void appFrameWorkDone();
public void appFrameWorkStart();
public void appFrameWorkProgress(long timeElapsed);
}
}
You Should use Java Thread and you can show an interactive Splash Screen (Custom made) to your user in the mean while while your code is generating whatever you want here is a tutorial just take a look
You should use Threads for good and efficient concurrency thats it