1/*
2 * Copyright (c) 2009-2010 jMonkeyEngine
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 *   notice, this list of conditions and the following disclaimer.
11 *
12 * * Redistributions in binary form must reproduce the above copyright
13 *   notice, this list of conditions and the following disclaimer in the
14 *   documentation and/or other materials provided with the distribution.
15 *
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 *   may be used to endorse or promote products derived from this software
18 *   without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33package com.jme3.system;
34
35import com.jme3.input.JoyInput;
36import com.jme3.input.KeyInput;
37import com.jme3.input.MouseInput;
38import com.jme3.input.TouchInput;
39import com.jme3.input.dummy.DummyKeyInput;
40import com.jme3.input.dummy.DummyMouseInput;
41import com.jme3.renderer.Renderer;
42import java.util.concurrent.atomic.AtomicBoolean;
43import java.util.logging.Level;
44import java.util.logging.Logger;
45
46public class NullContext implements JmeContext, Runnable {
47
48    protected static final Logger logger = Logger.getLogger(NullContext.class.getName());
49
50    protected AtomicBoolean created = new AtomicBoolean(false);
51    protected AtomicBoolean needClose = new AtomicBoolean(false);
52    protected final Object createdLock = new Object();
53
54    protected int frameRate;
55    protected AppSettings settings = new AppSettings(true);
56    protected Timer timer;
57    protected SystemListener listener;
58    protected NullRenderer renderer;
59
60    public Type getType() {
61        return Type.Headless;
62    }
63
64    public void setSystemListener(SystemListener listener){
65        this.listener = listener;
66    }
67
68    protected void initInThread(){
69        logger.info("NullContext created.");
70        logger.log(Level.FINE, "Running on thread: {0}", Thread.currentThread().getName());
71
72        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
73            public void uncaughtException(Thread thread, Throwable thrown) {
74                listener.handleError("Uncaught exception thrown in "+thread.toString(), thrown);
75            }
76        });
77
78        timer = new NanoTimer();
79        renderer = new NullRenderer();
80        synchronized (createdLock){
81            created.set(true);
82            createdLock.notifyAll();
83        }
84
85        listener.initialize();
86    }
87
88    protected void deinitInThread(){
89        listener.destroy();
90        timer = null;
91        synchronized (createdLock){
92            created.set(false);
93            createdLock.notifyAll();
94        }
95    }
96
97    private static long timeThen;
98    private static long timeLate;
99
100    public void sync(int fps) {
101        long timeNow;
102        long gapTo;
103        long savedTimeLate;
104
105        gapTo = timer.getResolution() / fps + timeThen;
106        timeNow = timer.getTime();
107        savedTimeLate = timeLate;
108
109        try {
110            while (gapTo > timeNow + savedTimeLate) {
111                Thread.sleep(1);
112                timeNow = timer.getTime();
113            }
114        } catch (InterruptedException e) {
115            Thread.currentThread().interrupt();
116        }
117
118        if (gapTo < timeNow) {
119            timeLate = timeNow - gapTo;
120        } else {
121            timeLate = 0;
122        }
123
124        timeThen = timeNow;
125    }
126
127    public void run(){
128        initInThread();
129
130        while (!needClose.get()){
131            listener.update();
132
133            if (frameRate > 0)
134                sync(frameRate);
135        }
136
137        deinitInThread();
138
139        logger.info("NullContext destroyed.");
140    }
141
142    public void destroy(boolean waitFor){
143        needClose.set(true);
144        if (waitFor)
145            waitFor(false);
146    }
147
148    public void create(boolean waitFor){
149        if (created.get()){
150            logger.warning("create() called when NullContext is already created!");
151            return;
152        }
153
154        new Thread(this, "Headless Application Thread").start();
155        if (waitFor)
156            waitFor(true);
157    }
158
159    public void restart() {
160    }
161
162    public void setAutoFlushFrames(boolean enabled){
163    }
164
165    public MouseInput getMouseInput() {
166        return new DummyMouseInput();
167    }
168
169    public KeyInput getKeyInput() {
170        return new DummyKeyInput();
171    }
172
173    public JoyInput getJoyInput() {
174        return null;
175    }
176
177    public TouchInput getTouchInput() {
178        return null;
179    }
180
181    public void setTitle(String title) {
182    }
183
184    public void create(){
185        create(false);
186    }
187
188    public void destroy(){
189        destroy(false);
190    }
191
192    protected void waitFor(boolean createdVal){
193        synchronized (createdLock){
194            while (created.get() != createdVal){
195                try {
196                    createdLock.wait();
197                } catch (InterruptedException ex) {
198                }
199            }
200        }
201    }
202
203    public boolean isCreated(){
204        return created.get();
205    }
206
207    public void setSettings(AppSettings settings) {
208        this.settings.copyFrom(settings);
209        frameRate = settings.getFrameRate();
210        if (frameRate <= 0)
211            frameRate = 60; // use default update rate.
212    }
213
214    public AppSettings getSettings(){
215        return settings;
216    }
217
218    public Renderer getRenderer() {
219        return renderer;
220    }
221
222    public Timer getTimer() {
223        return timer;
224    }
225
226    public boolean isRenderable() {
227        return true; // Doesn't really matter if true or false. Either way
228                     // RenderManager won't render anything.
229    }
230}
231