Skip to content

Commit

Permalink
A whole lot of clean up for Lua states
Browse files Browse the repository at this point in the history
 - Use a builder instead of setting fields after construction. This
   means we can make several previously mutable fields final.
 - Run Lua coroutines using an executor, rather than explicitly creating
   a thread. This should allow us to us to pool them, or at least
   configure how Lua threads are created.
  • Loading branch information
SquidDev committed Nov 21, 2018
1 parent 08ed357 commit af74e71
Show file tree
Hide file tree
Showing 32 changed files with 338 additions and 119 deletions.
244 changes: 228 additions & 16 deletions src/main/java/org/squiddev/cobalt/LuaState.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.squiddev.cobalt.compiler.LoadState;
import org.squiddev.cobalt.compiler.LuaC;
import org.squiddev.cobalt.debug.DebugHandler;
import org.squiddev.cobalt.lib.platform.FileResourceManipulator;
import org.squiddev.cobalt.lib.platform.ResourceManipulator;

import java.io.InputStream;
Expand All @@ -36,6 +37,8 @@
import java.util.Random;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;

/**
* Global lua state
Expand All @@ -44,12 +47,12 @@ public final class LuaState {
/**
* The active input stream
*/
public InputStream stdin = System.in;
public InputStream stdin;

/**
* The active output stream
*/
public PrintStream stdout = System.out;
public PrintStream stdout;

/**
* The metatable for all strings
Expand Down Expand Up @@ -94,33 +97,52 @@ public final class LuaState {
/**
* The compiler for this threstate
*/
public LoadState.LuaCompiler compiler = LuaC.instance;
public final LoadState.LuaCompiler compiler;

/**
* The handler for the debugger. Override this for custom debug actions.
*/
public DebugHandler debug;
public final DebugHandler debug;

/**
* The random instance for this state.
*/
public Random random;

/**
* The currently executing state
*/
protected LuaThread currentThread;
private final Executor coroutineExecutor;

/**
* The root thread
*/
protected LuaThread mainThread;
LuaThread currentThread;
LuaThread mainThread;
Set<LuaThread> threads = Collections.newSetFromMap(new WeakHashMap<>());

public LuaState() {
this(new LuaState.Builder());
}

protected Set<LuaThread> threads = Collections.newSetFromMap(new WeakHashMap<LuaThread, Boolean>());
private LuaState(Builder builder) {
this.stdin = builder.stdin;
this.stdout = builder.stdout;
this.stringMetatable = builder.stringMetatable;
this.booleanMetatable = builder.booleanMetatable;
this.numberMetatable = builder.numberMetatable;
this.nilMetatable = builder.nilMetatable;
this.functionMetatable = builder.functionMetatable;
this.threadMetatable = builder.threadMetatable;
this.resourceManipulator = builder.resourceManipulator;
this.compiler = builder.compiler;
this.random = builder.random;
this.debug = builder.debug;
this.coroutineExecutor = builder.coroutineExecutor;
}

public LuaState(ResourceManipulator resourceManipulator) {
this.resourceManipulator = resourceManipulator;
debug = new DebugHandler(this);
/**
* The executor for coroutines
*
* @return Gets this state's coroutine executor.
* @see LuaThread#resume(Varargs)
*/
public Executor getCoroutineExecutor() {
return coroutineExecutor;
}

/**
Expand Down Expand Up @@ -178,4 +200,194 @@ public void abandon() {
break;
}
}

public static LuaState.Builder builder() {
return new LuaState.Builder();
}

/**
* A mutable builder for {@link LuaState}s.
*/
public static class Builder {
private static final AtomicInteger coroutineCount = new AtomicInteger();
private static final Executor defaultCoroutineExecutor = command ->
new Thread(command, "Coroutine-" + coroutineCount.getAndIncrement()).start();

private InputStream stdin = System.in;
private PrintStream stdout = System.out;
private LuaTable stringMetatable;
private LuaTable booleanMetatable;
private LuaTable numberMetatable;
private LuaTable nilMetatable;
private LuaTable functionMetatable;
private LuaTable threadMetatable;
private ResourceManipulator resourceManipulator = new FileResourceManipulator();
private LoadState.LuaCompiler compiler = LuaC.INSTANCE;
private Random random = new Random();
private DebugHandler debug = DebugHandler.INSTANCE;
private Executor coroutineExecutor = defaultCoroutineExecutor;

/**
* Build a Lua state from this builder
*
* @return The constructed Lua state.
*/
public LuaState build() {
return new LuaState(this);
}

/**
* Set the initial standard input for this Lua state. This defaults to {@link System#in}.
*
* @param stdin The new standard input
* @return This builder
* @see LuaState#stdin
*/
public Builder stdin(InputStream stdin) {
if (stdin == null) throw new NullPointerException("stdin cannot be null");
this.stdin = stdin;
return this;
}

/**
* Set the initial standard output for this Lua state. This defaults to {@link System#out}.
*
* @param stdout The new standard output
* @return This builder
* @see LuaState#stdout
*/
public Builder stdout(PrintStream stdout) {
if (stdout == null) throw new NullPointerException("stdout cannot be null");
this.stdout = stdout;
return this;
}

/**
* Set the initial metatable for string values within this Lua State. This defaults to {@code null}.
*
* @param metatable The initial metatable
* @return This builder
*/
public Builder stringMetatable(LuaTable metatable) {
this.stringMetatable = metatable;
return this;
}

/**
* Set the initial metatable for boolean values within this Lua State. This defaults to {@code null}.
*
* @param metatable The initial metatable
* @return This builder
*/
public Builder booleanMetatable(LuaTable metatable) {
this.booleanMetatable = metatable;
return this;
}

/**
* Set the initial metatable for numeric values within this Lua State. This defaults to {@code null}.
*
* @param metatable The initial metatable
* @return This builder
*/
public Builder numberMetatable(LuaTable metatable) {
this.numberMetatable = metatable;
return this;
}

/**
* Set the initial metatable for nil values within this Lua State. This defaults to {@code null}.
*
* @param metatable The initial metatable
* @return This builder
*/
public Builder nilMetatable(LuaTable metatable) {
this.nilMetatable = metatable;
return this;
}

/**
* Set the initial metatable for functions within this Lua State. This defaults to {@code null}.
*
* @param metatable The initial metatable
* @return This builder
*/
public Builder functionMetatable(LuaTable metatable) {
this.functionMetatable = metatable;
return this;
}

/**
* Set the initial metatable for threads within this Lua State. This defaults to {@code null}.
*
* @param metatable The initial metatable
* @return This builder
*/
public Builder threadMetatable(LuaTable metatable) {
this.threadMetatable = metatable;
return this;
}

/**
* Set the resource manipulator that the {@code os} and {@code io} libraries will use. This defaults to a
* {@link FileResourceManipulator}, which uses the default file system.
*
* @param resourceManipulator The new resource manipulator
* @return This builder
*/
public Builder resourceManipulator(ResourceManipulator resourceManipulator) {
if (this.resourceManipulator == null) throw new NullPointerException("resourceManipulator cannot be null");
this.resourceManipulator = resourceManipulator;
return this;
}

/**
* Set the compiler for this Lua state. This defaults to using the {@link LuaC} compiler.
*
* @param compiler The new compiler to use
* @return This builder
*/
public Builder compiler(LoadState.LuaCompiler compiler) {
if (compiler == null) throw new NullPointerException("compiler cannot be null");
this.compiler = compiler;
return this;
}

/**
* Set the initial random state for the Lua state. This will be used by {@code math.random}, but may
* be changed by {@code math.radomseed}
*
* @param random The new random state.
* @return This builder
*/
public Builder random(Random random) {
if (random == null) throw new NullPointerException("random cannot be null");
this.random = random;
return this;
}

/**
* Set the debug handler for this Lua state.
*
* @param debug The new debug handler
* @return This builder
*/
public Builder debug(DebugHandler debug) {
if (debug == null) throw new NullPointerException("debug cannot be null");
this.debug = debug;
return this;
}

/**
* Set the coroutine executor for this state.
*
* @param coroutineExecutor The new executor
* @return This builder
*/
public Builder coroutineFactory(Executor coroutineExecutor) {
if (coroutineExecutor == null) throw new NullPointerException("coroutineExecutor cannot be null");
this.coroutineExecutor = coroutineExecutor;
return this;
}
}
}
13 changes: 5 additions & 8 deletions src/main/java/org/squiddev/cobalt/LuaThread.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import org.squiddev.cobalt.lib.jse.JsePlatform;

import java.lang.ref.WeakReference;
import java.util.concurrent.atomic.AtomicInteger;

import static org.squiddev.cobalt.ValueFactory.valueOf;
import static org.squiddev.cobalt.ValueFactory.varargsOf;
Expand Down Expand Up @@ -64,8 +63,6 @@
* @see CoroutineLib
*/
public class LuaThread extends LuaValue {
private static final AtomicInteger coroutineCount = new AtomicInteger();

/**
* Interval at which to check for lua threads that are no longer referenced.
* This can be changed by Java startup code if desired.
Expand Down Expand Up @@ -247,7 +244,7 @@ public Varargs resume(Varargs args) {
return state.resume(this, args);
}

private static class State implements Runnable {
private static final class State implements Runnable {
private final LuaState state;
private final WeakReference<LuaThread> thread;
private final LuaFunction function;
Expand All @@ -259,7 +256,7 @@ private static class State implements Runnable {

private State(LuaThread thread, LuaFunction function) {
this.state = thread.luaState;
this.thread = new WeakReference<LuaThread>(thread);
this.thread = new WeakReference<>(thread);
this.function = function;
}

Expand All @@ -281,14 +278,14 @@ public synchronized void run() {
}
}

protected synchronized Varargs resume(LuaThread newThread, Varargs args) {
synchronized Varargs resume(LuaThread newThread, Varargs args) {
LuaThread previous = state.currentThread;
try {
state.currentThread = newThread;
this.args = args;
if (status == STATUS_INITIAL) {
status = STATUS_RUNNING;
new Thread(this, "Coroutine-" + coroutineCount.getAndIncrement()).start();
state.getCoroutineExecutor().execute(this);
} else {
notify();
}
Expand Down Expand Up @@ -333,7 +330,7 @@ protected synchronized Varargs yield(Varargs args) {
}
}

protected synchronized void abandon() {
synchronized void abandon() {
LuaThread currentThread = state.currentThread;
try {
currentThread.state.status = STATUS_NORMAL;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/squiddev/cobalt/cmd/lua.java
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public static void main(String[] args) throws IOException {
}

// new org.squiddev.cobalt.cmd.lua state
LuaState state = new LuaState(new FileResourceManipulator());
LuaState state = new LuaState();
_G = JsePlatform.debugGlobals(state);
_G.load(state, new ProfilerLib(new ProfilerLib.FileOutputProvider()));
for (int i = 0, n = libs != null ? libs.size() : 0; i < n; i++) {
Expand Down
3 changes: 1 addition & 2 deletions src/main/java/org/squiddev/cobalt/cmd/luac.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import org.squiddev.cobalt.compiler.DumpState;
import org.squiddev.cobalt.compiler.LuaC;
import org.squiddev.cobalt.lib.jse.JsePlatform;
import org.squiddev.cobalt.lib.platform.FileResourceManipulator;

import java.io.*;

Expand Down Expand Up @@ -138,7 +137,7 @@ private luac(String[] args) throws IOException {
// process input files
OutputStream fos = new FileOutputStream(output);
try {
JsePlatform.standardGlobals(new LuaState(new FileResourceManipulator()));
JsePlatform.standardGlobals(new LuaState());
processing = true;
for (int i = 0; i < args.length; i++) {
if (!processing || !args[i].startsWith("-")) {
Expand Down
Loading

0 comments on commit af74e71

Please sign in to comment.