package com.laytonsmith.core.functions;
import com.laytonsmith.PureUtilities.DaemonManager;
import com.laytonsmith.PureUtilities.Version;
import com.laytonsmith.abstraction.Implementation;
import com.laytonsmith.abstraction.StaticLayer;
import com.laytonsmith.annotations.api;
import com.laytonsmith.annotations.core;
import com.laytonsmith.annotations.noboilerplate;
import com.laytonsmith.annotations.seealso;
import com.laytonsmith.core.CHVersion;
import com.laytonsmith.core.ParseTree;
import com.laytonsmith.core.Script;
import com.laytonsmith.core.Static;
import com.laytonsmith.core.constructs.CArray;
import com.laytonsmith.core.constructs.CBoolean;
import com.laytonsmith.core.constructs.CClosure;
import com.laytonsmith.core.constructs.CNull;
import com.laytonsmith.core.constructs.CString;
import com.laytonsmith.core.constructs.CVoid;
import com.laytonsmith.core.constructs.Construct;
import com.laytonsmith.core.constructs.Target;
import com.laytonsmith.core.environments.Environment;
import com.laytonsmith.core.environments.GlobalEnv;
import com.laytonsmith.core.exceptions.CRE.CRECastException;
import com.laytonsmith.core.exceptions.CRE.CREInsufficientArgumentsException;
import com.laytonsmith.core.exceptions.CRE.CRENullPointerException;
import com.laytonsmith.core.exceptions.CRE.CREThrowable;
import com.laytonsmith.core.exceptions.CancelCommandException;
import com.laytonsmith.core.exceptions.ConfigCompileException;
import com.laytonsmith.core.exceptions.ConfigRuntimeException;
import com.laytonsmith.core.exceptions.FunctionReturnException;
import com.laytonsmith.core.exceptions.LoopManipulationException;
import com.laytonsmith.core.exceptions.ProgramFlowManipulationException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
/**
*
*/
@core
public class Threading {
public static String docs(){
return "This experimental and private API is subject to removal, or incompatible changes, and should not"
+ " be yet heavily relied on in normal development.";
}
@api
@noboilerplate
@seealso({x_run_on_main_thread_later.class, x_run_on_main_thread_now.class})
public static class x_new_thread extends AbstractFunction {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public boolean isRestricted() {
return true;
}
@Override
public Boolean runAsync() {
return false;
}
@Override
public Construct exec(final Target t, final Environment environment, Construct... args) throws ConfigRuntimeException {
String id = args[0].val();
if(!(args[1] instanceof CClosure)){
throw new CRECastException("Expected closure for arg 2", t);
}
final CClosure closure = (CClosure) args[1];
new Thread(new Runnable() {
@Override
public void run() {
DaemonManager dm = environment.getEnv(GlobalEnv.class).GetDaemonManager();
dm.activateThread(Thread.currentThread());
try {
closure.execute();
} catch(FunctionReturnException ex){
// Do nothing
} catch(LoopManipulationException ex){
ConfigRuntimeException.HandleUncaughtException(ConfigRuntimeException.CreateUncatchableException("Unexpected loop manipulation"
+ " operation was triggered inside the closure.", t), environment);
} catch(ConfigRuntimeException ex){
ConfigRuntimeException.HandleUncaughtException(ex, environment);
} catch(CancelCommandException ex){
if(ex.getMessage() != null){
new Echoes.console().exec(t, environment, new CString(ex.getMessage(), t), CBoolean.FALSE);
}
} finally {
dm.deactivateThread(Thread.currentThread());
}
}
}, "(" + Implementation.GetServerType().getBranding() + ") " + id).start();
return CVoid.VOID;
}
@Override
public String getName() {
return "x_new_thread";
}
@Override
public Integer[] numArgs() {
return new Integer[]{2};
}
@Override
public String docs() {
return "void {id, closure} Creates a new thread, named id, and runs the closure on that thread."
+ " Note that many operations are not advisable to be run on other threads, and unless otherwise"
+ " stated, functions are generally not thread safe. You can use " + new x_run_on_main_thread_later().getName()
+ "() and " + new x_run_on_main_thread_now().getName() + "() to ensure operations will be run"
+ " correctly, however.";
}
@Override
public Version since() {
return CHVersion.V3_3_1;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
String get_current_thread = new x_get_current_thread().getName();
String new_thread = this.getName();
return new ExampleScript[]{
new ExampleScript("Basic usage", "msg(" + get_current_thread + "());\n"
+ new_thread + "('myThread', closure(){\n"
+ "\tsleep(5); // Sleep here, to allow the main thread to get well past us, for demonstration purposes\n"
+ "\tmsg(" + get_current_thread + "());\n"
+ "});\n"
+ "msg('End of main thread');",
"MainThread\n"
+ "End of main thread\n"
+ "myThread")
};
}
}
@api
public static class x_get_current_thread extends AbstractFunction {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{};
}
@Override
public boolean isRestricted() {
return true;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
return new CString(Thread.currentThread().getName(), t);
}
@Override
public String getName() {
return "x_get_current_thread";
}
@Override
public Integer[] numArgs() {
return new Integer[]{0};
}
@Override
public String docs() {
return "string {} Returns the thread id (thread name) of the currently running thread.";
}
@Override
public Version since() {
return CHVersion.V3_3_1;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Basic usage", this.getName() + "()", "MainThread")
};
}
}
@api
@noboilerplate
@seealso({x_run_on_main_thread_now.class})
public static class x_run_on_main_thread_later extends AbstractFunction {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{};
}
@Override
public boolean isRestricted() {
return true;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(final Target t, final Environment environment, Construct... args) throws ConfigRuntimeException {
final CClosure closure = Static.getObject(args[0], t, CClosure.class);
StaticLayer.GetConvertor().runOnMainThreadLater(environment.getEnv(GlobalEnv.class).GetDaemonManager(), new Runnable() {
@Override
public void run() {
try {
closure.execute();
} catch(ConfigRuntimeException e){
ConfigRuntimeException.HandleUncaughtException(e, environment);
} catch(ProgramFlowManipulationException e){
// Ignored
}
}
});
return CVoid.VOID;
}
@Override
public String getName() {
return "x_run_on_main_thread_later";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1};
}
@Override
public String docs() {
return "void {closure} Runs the closure on the main thread later. If the function call is itself being run from the main thread, then"
+ " the function still will not block, but it is not an error to call this from the main thread. If an exception is thrown"
+ " from the closure, it is handled using the uncaught exception handler.";
}
@Override
public Version since() {
return CHVersion.V3_3_1;
}
}
@api
@noboilerplate
@seealso({x_run_on_main_thread_later.class})
public static class x_run_on_main_thread_now extends AbstractFunction {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{};
}
@Override
public boolean isRestricted() {
return true;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(final Target t, final Environment environment, Construct... args) throws ConfigRuntimeException {
final CClosure closure = Static.getObject(args[0], t, CClosure.class);
Object ret;
try {
ret = StaticLayer.GetConvertor().runOnMainThreadAndWait(new Callable<Object>() {
@Override
public Object call() throws Exception {
try {
closure.execute();
} catch(FunctionReturnException e){
return e.getReturn();
} catch(ConfigRuntimeException | ProgramFlowManipulationException e){
return e;
}
return CNull.NULL;
}
});
} catch (Exception ex) {
throw new RuntimeException(ex);
}
if(ret instanceof RuntimeException){
throw (RuntimeException)ret;
} else {
return (Construct) ret;
}
}
@Override
public String getName() {
return "x_run_on_main_thread_now";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1};
}
@Override
public String docs() {
return "mixed {closure} Runs the closure on the main thread now, blocking the current thread until it is finished."
+ " If the function call is itself being run from the main thread, then"
+ " the function still will block as expected; it is not an error to call this from the main thread. Unlike"
+ " running on the main thread later, if the underlying code throws an exception, it is thrown as a normal part of"
+ " the execution. If the closure returns a value, it is returned by " + getName() + ".";
}
@Override
public Version since() {
return CHVersion.V3_3_1;
}
}
@api
@noboilerplate
@seealso({x_new_thread.class})
public static class _synchronized extends AbstractFunction {
private static final Map<Object, Integer> syncObjectMap = new HashMap<Object, Integer>();
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRENullPointerException.class};
}
@Override
public boolean isRestricted() {
return true;
}
@Override
public Boolean runAsync() {
return false;
}
@Override
public boolean preResolveVariables() {
return false;
}
@Override
public boolean useSpecialExec() {
return true;
}
@Override
public Construct execs(Target t, Environment env, Script parent, ParseTree... nodes) {
// Get the sync object tree and the code to synchronize.
ParseTree syncObjectTree = nodes[0];
ParseTree code = nodes[1];
// Get the sync object (CArray or String value of the Construct).
Construct cSyncObject = parent.seval(syncObjectTree, env);
if(cSyncObject instanceof CNull) {
throw new CRENullPointerException("Synchronization object may not be null in " + getName() + "().", t);
}
Object syncObject;
if(cSyncObject instanceof CArray) {
syncObject = cSyncObject;
} else {
syncObject = cSyncObject.val();
}
// Add String sync objects to the map to be able to synchronize by value.
if(syncObject instanceof String) {
synchronized(syncObjectMap) {
searchLabel: {
for(Entry<Object, Integer> entry : syncObjectMap.entrySet()) {
Object key = entry.getKey();
if(key instanceof String && key.equals(syncObject)) {
syncObject = key; // Get reference, value of this assign is the same.
entry.setValue(entry.getValue() + 1);
break searchLabel;
}
}
syncObjectMap.put(syncObject, 1);
}
}
}
// Evaluate the code, synchronized by the passed sync object.
try {
synchronized(syncObject) {
parent.seval(code, env);
}
} catch(RuntimeException e) {
throw e;
} finally {
// Remove 1 from the call count or remove the sync object from the map if it was a sync-by-value.
if(syncObject instanceof String) {
synchronized(syncObjectMap) {
int count = syncObjectMap.get(syncObject); // This should never return null.
if(count <= 1) {
syncObjectMap.remove(syncObject);
} else {
for(Entry<Object, Integer> entry : syncObjectMap.entrySet()) {
if(entry.getKey() == syncObject) { // Equals by reference.
entry.setValue(count - 1);
break;
}
}
}
}
}
}
return CVoid.VOID;
}
@Override
public Construct exec(final Target t, final Environment env, Construct... args) throws ConfigRuntimeException {
return CVoid.VOID;
}
@Override
public String getName() {
return "synchronized";
}
@Override
public Integer[] numArgs() {
return new Integer[]{2};
}
@Override
public String docs() {
return "void {syncObject, code} Synchronizes access to the code block for all calls (from different"
+ " threads) with the same syncObject argument."
+ " This means that if two threads will call " + getName() + "('example', <code>), the second"
+ " call will hang the thread until the passed code of the first call has finished executing."
+ " If you call this function from within this function on the same thread using the same"
+ " syncObject, the code will simply be executed."
+ " For more information about synchronization, see:"
+ " https://en.wikipedia.org/wiki/Synchronization_(computer_science)";
}
@Override
public Version since() {
return CHVersion.V3_3_2;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Demonstrates two threads possibly overwriting eachother", ""
+ "export('log', '');\n"
+ "x_new_thread('Thread1', closure() {\n"
+ "\t@log = import('log');\n"
+ "\t@log = @log.'Some new log message from Thread1.\n'\n"
+ "\texport('log', @log);\n"
+ "});\n"
+ "x_new_thread('Thread2', closure() {\n"
+ "\t@log = import('log');\n"
+ "\t@log = @log.'Some new log message from Thread2.\n'\n"
+ "\texport('log', @log);\n"
+ "});\n"
+ "sleep(0.1);\n"
+ "msg(import('log'));",
"Some new log message from Thread1.\n"
+ "\nOR\nSome new log message from Thread2.\n"
+ "\nOR\nSome new log message from Thread1.\nSome new log message from Thread2.\n"
+ "\nOR\nSome new log message from Thread2.\nSome new log message from Thread1.\n"),
new ExampleScript("Demonstrates two threads modifying the same variable without the possibility of"
+ " overwriting eachother because they are synchronized.", ""
+ "export('log', '');\n"
+ "x_new_thread('Thread1', closure() {\n"
+ "\tsynchronized('syncLog') {\n"
+ "\t\t@log = import('log');\n"
+ "\t\t@log = @log.'Some new log message from Thread1.\n'\n"
+ "\t\texport('log', @log);\n"
+ "\t}\n"
+ "});\n"
+ "x_new_thread('Thread2', closure() {\n"
+ "\tsynchronized('syncLog') {\n"
+ "\t\t@log = import('log');\n"
+ "\t\t@log = @log.'Some new log message from Thread2.\n'\n"
+ "\t\texport('log', @log);\n"
+ "\t}\n"
+ "});\n"
+ "sleep(0.1);\n"
+ "msg(import('log'));",
"Some new log message from Thread1.\nSome new log message from Thread2.\n"
+ "\nOR\nSome new log message from Thread2.\nSome new log message from Thread1.\n")
};
}
}
}