package com.drawbridge.jsengine;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import com.drawbridge.dm.DMImageFactory.DMImage;
import com.drawbridge.dm.DMPanel;
import com.drawbridge.jsengine.ast.DBParser;
import com.drawbridge.jsengine.ast.ParserListener;
import com.drawbridge.jsengine.jsobjects.Colour;
import com.drawbridge.jsengine.jsobjects.JSImage;
import com.drawbridge.jsengine.jsobjects.JSNativeFunction;
import com.drawbridge.jsengine.jsobjects.JSNumber;
import com.drawbridge.text.TextPanel;
import com.drawbridge.utils.Utils;
import com.drawbridge.vl.VLPanel;
import com.google.caja.ancillary.linter.LintHelper;
public class JsEngine
{
/**
* List of listeners that need to be notified when the model is updated
*/
private List<JSEngineListener> mListeners;
private final Scope mGlobalScope;
private static JsEngine mInstance = null;
private final String mScaffoldCode;
public final Integer mScaffoldLines;
private int mContinueASTExecutionRequests;
private final Runnable mExecutionRunnable;
private Thread mExecutionThread = null;
public static JsEngine getInstance()
{
if (mInstance != null)
{
return mInstance;
} else
{
mInstance = new JsEngine();
return mInstance;
}
}
public JsEngine() {
mListeners = new ArrayList<JSEngineListener>();
mGlobalScope = new Scope(this, null);
mScaffoldCode = Utils.loadTextFromPackagedFile("/SimpleTweeningJS/stubs.js");
mScaffoldLines = Utils.getNumberOfLinesFromPackagedFile("/SimpleTweeningJS/stubs.js");
mExecutionRunnable = new Runnable()
{
@Override
public void run()
{
while (mContinueASTExecutionRequests > 0)
{
mContinueASTExecutionRequests = 0;
getGlobalScope().resetVariables();
// Load any existing DMImages into JS Memory
loadDMImagesIntoJSEngine();
try
{
DBParser.getInstance();
DBParser.getInstance().evaluate();
} catch (Exception e)
{
e.printStackTrace();
}
try
{
Thread.sleep(30);
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
};
}
public synchronized Scope getGlobalScope()
{
return mGlobalScope;
}
public synchronized void addModelListener(JSEngineListener listener)
{
mListeners.add(listener);
}
/**
* Updates listeners that a scope has changed has changed
*
* @param child
* - the scope the change affects new JSImage(
* @param source
* - where the change was made
*/
private synchronized void notifyModelListeners(Scope child, JSEngineListener source)
{
synchronized (mListeners)
{
for (JSEngineListener name : mListeners)
{
if (!name.equals(source))
{
name.modelChange(child);
}
}
}
}
public void childChanged(Scope scope, JSEngineListener source)
{
notifyModelListeners(scope, source);
}
public interface JSEngineListener
{
/**
* Whenever the main model changes, each subscriber gets notified
*
* @param scope
* @param source
*/
public void modelChange(Scope scope);
}
/**
* Executes code in the JSEngine. If evaluate is false, the code is simply parsed and pushed to listeners
*
* @param sourceListener
* @param code
* @param evaluate
*/
public void executeCode(ParserListener sourceListener, String code, boolean evaluate)
{
Utils.out.println(getClass(), "Execute Code from " + ((sourceListener != null) ? sourceListener.getClass().getSimpleName() : "Utils") + (evaluate? "evaluating" : " not evaluating"));
LinkedList<ParserListener> listeners = new LinkedList<ParserListener>();
if (TextPanel.hasInstance())
listeners.add(TextPanel.getInstance());
if (VLPanel.hasInstance())
listeners.add(VLPanel.getInstance().mCanvas.getModel());
DBParser.getInstance().setParams(sourceListener, listeners, mGlobalScope, code);
long before = System.currentTimeMillis();
DBParser.getInstance().parse();
if (evaluate)
{
getGlobalScope().resetVariables();
// Load any existing DMImages into JS Memory
loadDMImagesIntoJSEngine();
try
{
DBParser.getInstance().evaluate();
} catch (Exception e)
{
e.printStackTrace();
}
JsEngine.getInstance().getGlobalScope().printScopeDeep(0);
}
LintHelper lh = new LintHelper();
String fullCode = mScaffoldCode + code;
lh.runTest(mScaffoldLines, fullCode);
Utils.out.println(fullCode);
long after = System.currentTimeMillis();//INVALID_ASSIGNMENT
long difference = after - before;
Utils.out.println(getClass(), "Time taken to executeCode: " + difference + " milliseconds");
}
/**
* Adds the code to an execution queue, which is executed according to how fast executions take. It then takes the
* next to be processed
*
* @param sourceListener
* @param code
*/
public void requestExecutionOfModifiedAST()
{
mContinueASTExecutionRequests++;
// Utils.out.println("Requested:" + mNumberRequested + ", processed:" +
// mNumberProcessed);
// If thread isn't working, start it
if (mExecutionThread != null && mExecutionThread.isAlive())
{
// it will process if it has time
}
else
{
mExecutionThread = new Thread(mExecutionRunnable);
mExecutionThread.start();
}
}
// Push info from model into JSEngine
public void loadDMImagesIntoJSEngine()
{
LinkedList<DMImage> images = DMPanel.getInstance().mModel.getModelObjects();
for (int i = 0; i < images.size(); i++)
{
JSImage newImage = new JSImage(images.get(i));
String name = "image" + i;
SymbolTableEntry entry = new SymbolTableEntry(newImage);
JsEngine.getInstance().getGlobalScope().declareVariable(null, name, entry);
}
JsEngine.getInstance().getGlobalScope().declareVariable(null, "WIDTH", new SymbolTableEntry(new JSNumber(DMPanel.getInstance().getWidth())));
JsEngine.getInstance().getGlobalScope().declareVariable(null, "HEIGHT", new SymbolTableEntry(new JSNumber(DMPanel.getInstance().getHeight())));
JsEngine.getInstance().getGlobalScope().declareVariable(null, "loadImage", new SymbolTableEntry(new JSNativeFunction(null, "loadImage", new Class<?>[] { JSNumber.class, JSNumber.class, JSNumber.class, JSNumber.class, JSNumber.class })));
JsEngine.getInstance().getGlobalScope().declareVariable(null, "setTimeToDestination", new SymbolTableEntry(new JSNativeFunction(null, "setTimeToDestination", new Class<?>[] { JSNumber.class })));
JsEngine.getInstance().getGlobalScope().declareVariable(null, "Colour", new SymbolTableEntry(new Colour()));
}
}